简介:
本文主要介绍如何通过应用程序控制内核驱动以实现在LCD屏幕上显示字符和汉字。
声明:
本文主要是看韦东山老师视频后并结合自己所查的一些知识所写,希望可以对你有所帮助。同时本文主要是针对应用程序编程,所以在阅读本文之前希望您先了解与LCD相关的驱动。
平台:
Linux内核:Linux-4.3.2
LCD驱动:嵌入式Linux——LCD驱动 (这篇文章是我以前写的LCD驱动,这其中有对本文所用LCD的详细介绍)
思路:
首先,要实现在LCD上显示字母的功能,我们就需要去获得这个字母的实现数组。其实我们可以将一个屏幕想想成一个由点组成的二维数组,而我们通过控制这个二维数组中各个位置的亮灭从而已实现显示字母的功能。而这个数组就是我们常说的ASCII码。这里有一个字母A的数组,他是一个8*16像素的字母:
上面显示的是用ASCII表示的躺倒了的字母A。而我们写程序要做的就是将这些ASCII码中的各个位对应到LCD的像素中,即通过控制各个位的值为1或0来表示各个像素的亮灭。而通过向framebuffer中写入对应的数组来实现从LCD显示字符和汉字的功能。
字符编码:
在开始实现功能之前我想先介绍一些基础的知识——字符编码。以前我在使用电脑时喜欢将各种文件命名为中文的,这样可以方便去查找,同时也可以一目了然的知道各个文件的功能,但是这样的做法却是个不好的习惯,因为有的时候我电脑中的文件拷贝到他人的Linux服务器时会出现乱码的现象。我们应该使用英文的方式来为各个文件或者其他的东西命名。这样的好处是很难出现乱码。那时的我很少考虑字符编码的事,只是想着出现乱码可能是因为机器的问题。以前的我在使用LCD或者OLED时很少会使用到汉字,也就只是使用了ASCII码,而当我真正要在LCD上显示一个汉字时才发现他并不是那么简单的。他不会像ASCII码那样可以使用一个字节就可以表示出来,同时汉字在中国也有不同的版本。下面我列举一些常用到的字符编码方式:
ASCII:
ascii是计算机最早使用的编码系统,由于计算机是欧美人发明的,所以他们认为用一字节的数据就可以完全的概括他们所需要的字母,所以ASCII是最早的编码集,他只占用一字节的数据空间。
GB2312:
随着计算机的不断发展,中国人也希望可以将自己的汉字写到编码集中,所以大陆方面设计了自己国标码,也就是GB2312码,在国标码中,包含了六千多我们常用的汉字。而国标码显然超出一个字节数据所能表示的范围,所以国标码一般使用两个字节的数据表示。
BIG5:
上面所说的国标码适用于我们现在的简体汉字,而在台湾等地方使用的是繁体的汉字,所以他们也设计了一套自己的编码基,即BIG5,BIG5码最早是由在台湾的5个大的公司提议所创建的,所以后面就叫BIG5码了。而BIG5码同样占用两个字节的数据空间。但是要注意的是,虽然国标码和BIG5码同样使用两个字节的数据表示一个汉字,但是同样一个两字节的数据在国标码和BIG5中却表示的是不同的汉字。所以有时候我们打开台湾的网站会发现虽然也显示的是汉字,但是却不能连城句子,就是因为两个字符集不同,出现乱码。
Unicode:
如上面所说,同样一个两字节的数据在不同字符集中却表示的是不同的字符,这就容易出现乱码,所以我们需要一个统一的编码集,而Unicode就是这样一个统一的编码集,或者说是编码转化器。而常见的实现形式有UTF-8和UTF-16。
UTF-8:
随着互联网的普及UTF-8得到了很好的应用,他被称为是一种会变长的字符编码,他有1到4个字节组成,同时UTF-8有一定的容错性,即一个数据出错只会影响到一个字符而并不会影响到其他的字符。UTF-8的设计有以下的多字符组序列的特质:
- 单字节字符的最高有效比特永远为0。
- 多字节序列中的首个字符组的几个最高有效比特决定了序列的长度。最高有效位为110的是2字节序列,而1110的是三字节序列,如此类推。
- 多字节序列中其余的字节中的首两个最高有效比特为10。
ioctl函数:
我们以往编程时用到ioctl函数常用到的是通过应用函数向内核驱动中的ioctl函数传递特定的参数,而这些参数在驱动的ioctl函数中通过switch来选取预设的特定命令,从而执行相应的命令。而对于这个项目中用到的ioctl函数就不一样了,他可以通过特定的宏定义参数从内核中获得驱动或者硬件相关的参数。而在电子相框这个项目中,我们通过宏FBIOGET_VSCREENINFO和FBIOGET_FSCREENINFO来获得我们在驱动中定义的LCD显示器的可变参数和固定参数。即struct fb_var_screeninfo 和struct fb_fix_screeninfo。详细的代码为:
if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&var)){
printf("can't get var
");
return -1;
}
if(ioctl(fd_fb,FBIOGET_FSCREENINFO,&fix)){
printf("can't get fix
");
return -1;
}
而在驱动函数的可变参数和固定参数中就有对LCD屏幕的设置,这里我复制在驱动中所写的关于可变参数和固定参数的中的各个设置变量:
固定参数(固定参数多为硬件相关而不会变化的,如屏幕的尺寸,显存物理地址,和屏幕类型等):
fix.smem_len = 480*272*16/8; //显存的长度
fix.type = FB_TYPE_PACKED_PIXELS;
fix.visual = FB_VISUAL_TRUECOLOR; //TFT屏为真彩 {MOD}
fix.line_length = 480*16/8; //一行的长度(单位:type)
可变参数(而可变的参数是可以根据不同情况而进行不同设置的,如:x,y方向虚拟分辨率,多少字节每像素,以及RGB所占有的比例等):
var.xres = 480; //x方向真实的分辨率
var.yres = 272; //y方向真实的分辨率
var.xres_virtual = 480; //x方向虚拟的分辨率
var.yres_virtual = 272; //y方向虚拟的分辨率
var.xoffset = 0; //x方向真实分辨率与虚拟分辨率的差值
var.yoffset = 0; //y方向真实分辨率与虚拟分辨率的差值
var.bits_per_pixel = 16; //设置16个字节每像素
/*RGB:565*/
var.red.offset = 11;
var.red.length = 5;
var.green.offset = 5;
var.green.length = 6;
var.blue.offset = 0;
var.blue.length = 5;
通过上面的介绍我们已经清楚,通过获得显示屏的可变参数和固定参数可以获得与显示屏相关的信息。
申请内存进行操作:
通过上面的介绍我们获得了与LCD屏幕相关的所有信息,下面我们就要通过获得的LCD屏幕信息来为framebuffer申请空间了。这里有说明的一点是,由于在驱动中已有设置,所以我们LCD显示是通过从framebuffer中读出与LCD屏幕像素相应大小的数据去对LCD进行填充。所以这里要申请一个framebuffer大小的空间,代码为:
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 fbmem
");
return -1;
}
我想可能会有人问为什么这里使用的是mmap函数来申请空间而不是malloc函数,我也查了一些资料,但是感觉有些抽象,所以这里我写一些自己的理解,不是是否正确:
mmap是在进程中申请的空间,他其实是向进程申请空间,所以他就是声明要使用进程中的内存,或者说他只是使用进程中的内存,所以他内存申请的大小相对要小,同时也说明这片内存在申请之前就是存在的已经分给了进程。而malloc是向内核申请空间,所以他申请的空间相对较大,同时malloc申请时内存是不存在的,只有你申请了你才有。
操作LCD现实汉字:
其实完成上面的步骤我们就可以通过将英文字母对应的数组写到framebuffer中来让LCD显示英文字母了。而ASCII码对应的数组大家可以很容易在网上找到。而大家需要做的就是将数组中每个位对应的1或者0表示为LCD中像素的亮和灭就好。代码为:
void lcd_put_ascii(int x,int y,unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i,b;
unsigned char byte;
for(i=0;i<16;i++){
byte = dots[i];
for(b=7;b>=0;b--){
if(byte & (1<
LCD能显示英文后我们下一个要做的就是让LCD显示汉字。这里我们要使用一个库:HZK16 。网上关于HZK16的介绍很多,我在下面的参考文献中也列举了几篇。其中hzk16需要我们知道的几点是,hzk16中使用16*16的矩阵来表示一个汉字,而在hzk16中使用两字节的数据来查找一个汉字,这两个字节的前一个字节数据表示的是区码,即这个汉字在那个汉字区,而第二个数据表示的是位码,即你所查找的汉字在这个区中具体哪个位置。所以我们通过区码和位码就可以找到这个汉字所在的位置。由于区码和位码都是从A1位置开始的。所以每个区中有94个汉字,由于每个汉字都是16*16像素的所以一个汉字要使用16*16/2=32字节的数据来存储。所以我们要查找的数组开始位置应该为:
unsigned int area = str[0] - 0xA1;
unsigned int where = str[1] - 0xA1;
unsigned char *dots = hzkmem + (area * 94 + where) * 32;
我们需要从网上下载这个hzk16的文件并从中读取相应的信息,这是就要用到一个函数:fstat 。通过该函数可以读取文件的大小以及一些其他的信息,由于这里我们是需要获得文件的大小所以认识并不深刻,详细的信息大家可以百度一下。相关的代码为:
fd_hzk16 = open("HZK16",O_RDONLY);
if(fd_hzk16 < 0){
printf("can't open HZK16
");
return -1;
}
if(fstat(fd_hzk16,&hzk_stat)){
printf("can't get HZK16
");
return -1;
}
hzkmem = (unsigned char *)mmap(NULL,hzk_stat.st_size,PROT_READ ,MAP_SHARED,fd_hzk16,0);
if(hzkmem == (unsigned char *)-1){
printf("can't mmap hzkmem
");
return -1;
}
void lcd_put_chinese(int x,int y,unsigned char *str)
{
unsigned int area = str[0] - 0xA1;
unsigned int where = str[1] - 0xA1;
unsigned char *dots = hzkmem + (area * 94 + where) * 32;
unsigned char byte;
int i,j,b;
for(i=0;i<16;i++){
for(j=0;j<2;j++){
byte = dots[i*2+j];
for(b=7;b>=0;b--){
if(byte & (1<
好了讲到这里就讲完这些知识了。而整个实现的代码在文章的最后。
参考文献:
字符编码笔记:ASCII,Unicode 和 UTF-8
unicode,ansi,utf-8,unicode big endian编码的区别
十分钟搞清字符集和字符编码
UTF-8
mmap和malloc的区别是什么
进程表结构以及malloc和mmap的区别
字符理论--hzk16的介绍以及简单的使用方法 (转)
HZK16汉字16*16点阵字库的使用及示例程序
hzk16的介绍以及简单的使用方法
fstat
C语言:stat,fstat和lstat函数
C语言用fstat函数获取文件的大小
整体代码:
#include
#include
#include
#include
#include
#include
#include
#include
#define FONTDATAMAX 4096
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
/* 0 0x00 '^@' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
········································
/* 255 0xff '??' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
};
int fd_fb;
int fd_hzk16;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct stat hzk_stat;
int screen_size;
unsigned char *fbmem;
unsigned char *hzkmem;
unsigned int line_width;
unsigned int pixel_width;
void lcd_put_pixel(int x,int y,unsigned int color)
{
unsigned char *pen_8 = fbmem + y*line_width + x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
unsigned int red,green,blue;
switch(var.bits_per_pixel){
case 8 :{
*pen_8 = color;
break;
}
case 16 :{
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32 :{
*pen_32 = color;
break;
}
default:{
printf("can't support %dbpp
",var.bits_per_pixel);
break;
}
}
}
void lcd_put_ascii(int x,int y,unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i,b;
unsigned char byte;
for(i=0;i<16;i++){
byte = dots[i];
for(b=7;b>=0;b--){
if(byte & (1<=0;b--){
if(byte & (1<