注:本人已购买韦东山第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。
文字在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);
else
ret = -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开发板运行,运行结果如下: