数码相框(三、LCD显示文字)

注:本人已购买韦东山第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。

    文字在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_operationsfb_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开发板运行,运行结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_35031421/article/details/107926085