注:本人已购买韦东山第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。
文字在LCD上的显示其实就是LCD上的一些点的显示与不显示,这些显示的点就像我们的笔画一样,有笔画经过的地方就显示,没有笔画经过的地方就不显示,这些显示与不显示的点组合在一起就构成了LCD上显示的文字。所以,在LCD上显示文字,需要知道文字的点阵数据。这些点阵数据也称为字模,字库就是字模的集合。
1. ASCII码字库文件使用
在linux-4.15内核目录下搜索font(输入命令:find -name "font*"
),可以找到font_8x16.c文件,如下图所示:
我们可以在.font_8x16.c文件中找到8*16的点阵存在fontdata_8x16[]数组里,如下图所示:
在fontdata_8x16[]数组里我们可以找到A(对应ASCII码0x41)的点阵数据,如下图所示,从图中可知,这些点阵数组组成了一个“A”字。
从“A”字的点阵数据可知,一个ASCII字符的点阵数据占据了16字节,所以“A”字的点阵数据位于0x41*16~0x41*16+15之间。在后面的文字显示实现中,我们可以直接将fontdata_8x16[]数组拷贝到应用程序里,用来显示ASCII。
2. HZK16汉字库文件使用
HZK16 字库是符合GB2312标准的16×16点阵字库,该字库里的16*16汉字需要256个点来显示,所以每个16*16汉字点阵所占的内存为16*16/8 = 32 字节。由数码相框(二、字符的编码方式)的GB2312编码可知,一个汉字的编码使用两个字节表示,其中高字节表示汉字的区号,低字节表示汉字的位号,区号和位号的范围都是0xA1-0xFE(一共有94个区号、94个位号)。要在字库里找到对应汉字的点阵数据,必须知道汉字的区码和位码(其实汉字的区位码就是汉字点阵数据的索引)。
区码: 区号(汉字的第一个字节)- 0xA0
位码: 位号(汉字的第二个字节)- 0xA0
(注:因为汉字编码是从0xA0区开始的,所以文件最前面就是从0xA0区开始,要算出相对区码)
因此,汉字在HZK16汉字库的绝对偏移地址为:
offset = (94 * (区码 - 1) + (位码 - 1)) * 32
注:① 区码减1是因为数组是以0为开始而区号位号是以1为开始的;
②最后乘以32是因为HZK16中每个汉字的点阵数据占据32字节。
以“中”为例,“中”的GBK编码为D6 D0,所以:
区码 = 0xD6 - 0xA0 = 0x36;
位码 = 0xD0 - 0xA0 = 0x30;
offset = (94 * (0x36 - 1) + (0x30 -1)) * 32 = (94 * 0x35 + 0x2F) * 32
因此:“中”的点阵数据位于 (94 * 0x35 + 0x2F) * 32 ~ (94 * 0x35 + 0x2F) * 32 + 31。
3. LCD 显示文字
(1) 以读写方式打开LCD设备fb0;
(2) 利用ioctl函数直接获取LCD的 var 和 fix 相关参数:
对于 LCD 设备fb0,它的file_operations是fb_fops,其中ioctl函数对应fb_fops的结构体成员函数unlocked_ioctl(.unlocked_ioctl = fb_ioctl),最终通过fb_ioctl函数调用do_fb_ioctl函数获取LCD的var 和 fix 相关参数,内核函数fb_ioctl、do_fb_ioctl的代码如下:
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct fb_info *info = file_fb_info(file);if (!info)return -ENODEV;return do_fb_ioctl(info, cmd, arg);
}static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg)
{struct fb_ops *fb;struct fb_var_screeninfo var;struct fb_fix_screeninfo fix;struct fb_con2fbmap con2fb;struct fb_cmap cmap_from;struct fb_cmap_user cmap;struct fb_event event;void __user *argp = (void __user *)arg;long ret = 0;switch (cmd) {case FBIOGET_VSCREENINFO:if (!lock_fb_info(info))return -ENODEV;var = info->var;unlock_fb_info(info);ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;break;case FBIOPUT_VSCREENINFO:if (copy_from_user(&var, argp, sizeof(var)))return -EFAULT;console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}info->flags |= FBINFO_MISC_USEREVENT;ret = fb_set_var(info, &var);info->flags &= ~FBINFO_MISC_USEREVENT;unlock_fb_info(info);console_unlock();if (!ret && copy_to_user(argp, &var, sizeof(var)))ret = -EFAULT;break;case FBIOGET_FSCREENINFO:if (!lock_fb_info(info))return -ENODEV;fix = info->fix;unlock_fb_info(info);ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;break;case FBIOPUTCMAP:if (copy_from_user(&cmap, argp, sizeof(cmap)))return -EFAULT;ret = fb_set_user_cmap(&cmap, info);break;case FBIOGETCMAP:if (copy_from_user(&cmap, argp, sizeof(cmap)))return -EFAULT;if (!lock_fb_info(info))return -ENODEV;cmap_from = info->cmap;unlock_fb_info(info);ret = fb_cmap_to_user(&cmap_from, &cmap);break;case FBIOPAN_DISPLAY:if (copy_from_user(&var, argp, sizeof(var)))return -EFAULT;console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}ret = fb_pan_display(info, &var);unlock_fb_info(info);console_unlock();if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))return -EFAULT;break;case FBIO_CURSOR:ret = -EINVAL;break;case FBIOGET_CON2FBMAP:if (copy_from_user(&con2fb, argp, sizeof(con2fb)))return -EFAULT;if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)return -EINVAL;con2fb.framebuffer = -1;event.data = &con2fb;if (!lock_fb_info(info))return -ENODEV;event.info = info;fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);unlock_fb_info(info);ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;break;case FBIOPUT_CON2FBMAP:if (copy_from_user(&con2fb, argp, sizeof(con2fb)))return -EFAULT;if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)return -EINVAL;if (con2fb.framebuffer >= FB_MAX)return -EINVAL;if (!registered_fb[con2fb.framebuffer])request_module("fb%d", con2fb.framebuffer);if (!registered_fb[con2fb.framebuffer]) {ret = -EINVAL;break;}event.data = &con2fb;console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}event.info = info;ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);unlock_fb_info(info);console_unlock();break;case FBIOBLANK:console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}info->flags |= FBINFO_MISC_USEREVENT;ret = fb_blank(info, arg);info->flags &= ~FBINFO_MISC_USEREVENT;unlock_fb_info(info);console_unlock();break;default:if (!lock_fb_info(info))return -ENODEV;fb = info->fbops;if (fb->fb_ioctl)ret = fb->fb_ioctl(info, cmd, arg);elseret = -ENOTTY;unlock_fb_info(info);}return ret;
}
从上面的代码可知,在用户空间调用以下函数可以获取LCD的 var 和 fix 驱动数据:
ioctl(fd_fb, FBIOGET_VSCREENINFO, &var) /*FBIOGET_VSCREENINFO:获取fb_info-> var成员(可变信息:xy分辨率,像素位数等)*/
ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix) /*FBIOGET_FSCREENINFO:获取fb_info-> fix成员(固定信息:缓存地址,每行字节数)*/
(3) 利用mamp()函数申请一段用户空间的内存区域,并映射到内核空间某个内存区域;
mamp() 函数原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
返回值: 失败返回-1,并设置errno值.成功,返回映射的地址指针.若指定start则返回0;
addr: 需要映射的内存起始地址,通常填NULL,表示让系统自动映射,映射成功后返回该地址;
length: 映射地址的大小,填LCD显存字节数即可,因为2440一个地址存8位;
prot: 对映射地址的保护(protect)方式,常用组合如下:
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不可访问
flag: 填MAP_SHARED即可,表示共享此映射,对其它进程可见.
fd: 需要将内存映射到哪个文件描述符(以后便可以直接通过内存来直接操作该文件)
offset: 映射偏移值,填0即可.
int munmap(void *addr, size_t length);
返回值: 成功返回0,失败返回-1,并设置errno值;
addr: 要取消映射的内存起始地址;
length: 映射地址的大小;
LCD显示文字的应用程序代码如下:(文件名为show_a.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>
#include <unistd.h>
#include "fb.h" /*把8x16 ASCII码字库文件拷贝到fb.h, 成一个头文件*/unsigned char *fbmem; /*framebuffer mem*/
unsigned int line_width; /*每一行数据的大小(字节)*/
unsigned int pixel_width; /*每一个像素的大小(字节)*/struct fb_var_screeninfo var; /*LCD 显示屏的可变参数结构 var*/
struct fb_fix_screeninfo fix; /*LCD 显示屏的固定参数结构 fix*/
int screen_size;unsigned char *hzkmem;void lcd_put_pixel(unsigned int x, unsigned int y, unsigned int color)
{unsigned char *pen_8 = fbmem + y * line_width + x * pixel_width;unsigned short *pen_16 = (unsigned short *)pen_8;unsigned int *pen_32 = (unsigned int *)pen_8;unsigned int red, green, blue;switch (var.bits_per_pixel){case 8:{*pen_8 = (unsigned char)color;break;}case 16:{/*RGB565 格式* 对于 16BPP: color 的格式为 0xAARRGGBB (AA = 透明度,此处为 0),需要转换为 5:6:5 格式*/red = (color >> 16) & 0xff;green = (color >> 8) & 0xff;blue = (color >> 0) & 0xff; *pen_16 = (unsigned short)(((red >> 3) << 11) | ((green >> 2) << 5) | ((blue >> 3)<< 0)) & 0xffff;break;}case 32:{ *pen_32 = color;break;}default:{printf("can't surport %dbpp\n", var.bits_per_pixel);break;}}
}void lcd_put_ascii(unsigned int x, unsigned int y, unsigned char c)
{unsigned char *dots = (unsigned char *)&fontdata_8x16[c * 16];unsigned char byte;unsigned int i,j;for(i = 0; i < 16; i++){byte = dots[i];for(j = 0; j < 8; j++){if(byte & (1 << (7-j))){lcd_put_pixel(x + j, y + i, 0xffffff); /*显示白色*/}else{lcd_put_pixel(x + j, y + i, 0); /*显示黑色*/}}}
}void lcd_put_chinese(unsigned int x, unsigned int y, unsigned char *str)
{ /*GB2312 编码*/unsigned int area = str[0] - 0xA1;unsigned int where = str[1] - 0xA1;unsigned char *dots = hzkmem + (area * 94 + where) * 32;/*16*16的汉字,每一个汉字的位图所占的内存为 16*16/8 = 32 字节*/unsigned char byte;unsigned int i, j, k;for(i = 0; i < 16; i++) /*总共16行*/{for(j = 0; j < 2; j++) /*一行占据两个字节*/{byte = dots[i*2 + j];for(k = 0; k < 8; k++){if(byte & (1 << (7-k))){lcd_put_pixel(x + j*8 + k, y + i, 0xffffff); /*显示白色*/}else{lcd_put_pixel(x + j*8 + k, y + i, 0); /*显示黑色*/}}}}
}int main(int argc, char **argv)
{int fd_fb, fd_hzk;struct stat hzk_stat;int errno;/*打开framebuffer*/fd_fb = open("/dev/fb0", O_RDWR);if(fd_fb < 0){fprintf(stderr, "Can't open /dev/fb0: %s\n", strerror(errno));return -1;}/*利用ioctl直接获取lcd 的var 和 fix 相关参数*/if(ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)){printf("can't get var\n");return -1;}if(ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)){printf("can't get fix\n");}line_width = var.xres * var.bits_per_pixel / 8;pixel_width = var.bits_per_pixel / 8;screen_size = var.xres * var.yres * var.bits_per_pixel / 8;fbmem = (unsigned char *)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);if(fbmem == (unsigned char *)-1){printf("can't mmap\n");return -1;}fd_hzk = open("HZK16", O_RDONLY);if(fd_hzk< 0){fprintf(stderr, "Can't open HZK16: %s\n", strerror(errno));return -1;}if(fstat(fd_hzk, &hzk_stat)){printf("can't get fstat\n");return -1;}hzkmem = (unsigned char *)mmap(NULL, hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk, 0);if(hzkmem == (unsigned char *)-1){printf("can't mmap for hzk\n");return -1;}/*清屏,将屏幕全部写 0,置黑 */memset(fbmem, 0, screen_size);/* 利用 lcd_put_ascii 函数,向 LCD 写入字符'A'*/lcd_put_ascii(var.xres/2, var.yres/2, 'A');lcd_put_chinese(var.xres/2 + 8, var.yres/2,"中");munmap(hzkmem,hzk_start.st_size);munmap(fbmem,screensize);return 0;
}
执行以下命令编译应用程序:
arm-linux-gcc -o show_a show_a.c -fexec-charset=GBK
把编译好的应用程序拷贝到Jz2440开发板运行,运行结果如下: