在上章 - 学习了数码相框的框架分析 (1) 了
本章主要内容如下:
1)熟悉 ASCII/GB2312/Unicode 编码
2)写应用程序, 使 LCD 显示汉字和字符
大家都知道, 数据传输的是二进制, 而字符和汉字却有各种各样的, 所以便通过二进制将字符和汉字编成一个字符集(charset).
1. 而字符集 (charset) 又经历 3 个阶段
ASCII 码
最早的计算机采用 ASCII 码, 一个字节便包括了英文数字这些符号
GB2312 编码
由于不支持中文, 那时候的常用汉字就有 6763 个, 所以中国人发明了 GB2312(GB 国标), 汉字为 2 个字节, 与 ascll 码兼容, 后来又继续扩展汉字, 所以又有了 GBK 编码.
GB2312 编码是将字符进行一个分区处理, 共有 94 个区, 每个区有 94 个位, 所以区位码范围为 0000~9393
汉字分为了一级汉字 (常用) 和二级汉字(不常用).
其中 GB2312 分区表如下图所示:
比如啊, 位于第 16 区第 1 位, 也就是 1500.
然后分别在区和位上加 0xA1, 便转换为了 GB2312 编码(编码从 0xA1A1 开始是为了兼容英文字符,)
所以啊的 GB2312 编码为: 0xB0A1
15(区)+0xA1=0xB0
00(位)+0xA1=0xA1
这种编码方式仅仅在中国行的通, 若去浏览繁体字或日文时, 便会出现乱码, 因为繁体字使用的是 Big5 编码, 日文则需要安装日本的 Shift_JIS 编码才行.
在不同的国家的编码标准都不同, 所以在 PC 里, 使用 ANSI 编码来代表它们, 比如中文 PC 里, ANSI 编码代表 GBK 编码.
Unicode 编码(统一世界所有符号)
包括中日韩英文等字符, 格式有 utf-32utf-16utf-8
utf-32
指每个字符都采用 4 个字节(32 位), 缺点在于浪费空间, 比如: a=0x0000 0061,
utf-16(错一个字节, 则整个乱码)
每个字符的长度为 2 字节或 4 字节, 常用的都是 2 字节(包括汉字等). 比如: a=0x0061, 啊 = 0x554A.
utf-8(容错能力高)
指每个字符的长度为 1~4 个字节, 越常用的字符, 字节越短, 比如: a=0x61, 啊 = 0x5958A
可以通过 utf-16 转换过来, 高 4 位表示有多少个字节, 然后剩下的每个字节的高 2 位都为 10(表示只有一个字节), 剩下的值加起来就是 utf-16 编码, 如下图所示:
一般一个文件的开头会有标志, 通过十六进制编辑文件, 便可以看到
EF BB BF 表示 utf-8
FE FF 表示 utf-16 大端(大开头, 比如 a=00 61)
FF FE 表示 utf-16 小端(小开头, 比如 a=61 00)
2. 所以文件格式不同, 执行的结果也不同
2.1 我们下面代码为例:
- #include <stdio.h>
- int main(int argc,char **argv)
- {
- int i=0;
- unsigned char s[]="abc 中";
- while(s[i])
- {
- printf("%02x",s[i]);
- i++;
- }
- printf("\n");
- return 0;
- }
然后在 PC 上, 另存为 ANSI.c 和 UTF-8.c, 编码分别选择 ANSI(GBK 编码)和 UTF-8
2.2 然后拖到 linux 里编译运行:
- gcc -o ANSI ANSI.c
- gcc -o UTF-8 UTF-8.c
我们可以指定字符集(charset), 强制使它以什么编码格式解析
- man gcc // 查看 gcc 使用手册
- /charset // 搜索 charset 相关字
找到:
- -finput-charset=charset // 表示源文件的编码方式, 默认以 UTF-8 来解析
- -fexec-charset=charset // 表示可执行程序里的字时候以什么编码方式来表示, 默认是 UTF-8
3.1 指定字符集(charset)
gcc -finput-charset=GBK -fexec-charset=UTF-8 -o utf-8_2 ANSI.c
如上图所示, 通过参数, 告诉 gcc 该文件是 GBK 编码, 然后需要生成 UTF-8 编码的格式, 便解决了文件格式问题.
4.LCD 显示文字
4.1 首先 LCD 设备 fb0 的 file_operations 是 fb_fops(位于 fbmem.c)
fb_fops 的 write 成员是 fb_write()函数.
发现 write()函数直接是对显存地址写数据, 所以使用 echo hello> /dev/fb0 时会直接出现乱码(没有点阵信息)
而 ioctl 成员是 do_fb_ioctl()函数, 我们需要通过 ioctl()获取 LCD 驱动的数据:
FBIOGET_VSCREENINFO: 获取 fb_info-> var 成员(可变信息: xy 分辨率, 像素位数等)
FBIOGET_FSCREENINFO: 获取 fb_info-> fix 成员(固定信息: 缓存地址, 每行字节数,)
4.2 mmap
mamp()函数: 申请一段用户空间的内存区域, 并映射到内核空间某个内存区域.
接下来, 我们便申请一块内存映射到 fb0 文件, 然后应用程序直接向内存写数据, 即可直接写入 fb0 文件(显存地址)
man mmap // 搜索 mmap 如何使用
找到:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
返回值: 失败返回 - 1, 并设置 errno 值. 成功, 返回映射的地址指针. 若指定 start 则返回 0
start: 需要映射的内存起始地址, 通常填 NULL, 表示让系统自动映射, 映射成功后返回该地址.
length: 映射地址的大小, 填 LCD 显存字节数即可, 因为 2440 一个地址存 8 位.
prot: 对映射地址的保护 (protect) 方式, 常用组合如下:
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不可访问
flag: 填 MAP_SHARED 即可, 表示共享此映射, 对其它进程可见.
fd: 需要将内存映射到哪个文件描述符(以后便可以直接通过内存来直接操作该文件)
offset: 映射偏移值, 填 0 即可.
int munmap(void* start,size_t length);
返回值: 成功返回 0, 失败返回 - 1, 并设置 errno 值
start: 要取消映射的内存起始地址
length: 映射地址的大小
mmap 的参数详情使用请参考: http://blog.csdn.net/dlutbrucezhang/article/details/9080173
4.3 ASCII 码字库文件使用
在 si 里搜索 font, 找到内核有个 font_8x16.c 文件(位于 drivers/video/console)
如下图所示, 找到 8*16 的点阵存在 fontdata_8x16[]数组里:
我们以 0x41(A)为例, 找到该点阵信息为:
可以看到一个 ASCII 代表了 16 字节. 所以 0x41(A)位于 0x41*16~0x41*16+15
后面我们直接将 fontdata_8x16[]数组拷贝到应用程序里, 用来显示 ASCII
4.3 HZK16 汉字库文件使用
1)HZK16 描述
HZK16 是按分区表排列的点阵文件, 由于每个汉字是 2 字节, 每个字节的点阵是 8*16.
所以 HZK16 里的每个汉字点阵大小: 2*8*16=32 字节.
2)然后还要将编码转为点阵码, 我们以中为例:
中的 GBK 编码为 D6 D0
转为分区表(每字节减去 A1): 35 2F
所以中的点阵位于:
(35*94+2F)*32~(35*94+2F)*32+31 //94: 每个区占据 94 位 32: 每个汉字点阵为 32 字节
注意: 2440 的 LCD 是 RGB565 的. 所以点阵每一位, 又是一个 16 位的数据地址
5. 接下来开始写应用程序
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/mman.h>
- #include <sys/ioctl.h>
- #include <string.h>
- #include <linux/fb.h>
- unsigned char *fbmem;
- unsigned char *hzkmem;
- struct fb_var_screeninfo fb_var;
- struct fb_fix_screeninfo fb_fix;
- unsigned int screensize;
- #define FONTDATAMAX 4096
- static const unsigned char fontdata_8x16[FONTDATAMAX] = {
- //ASCII 码点阵太长, 省略...
- };
- /*rgb565*/
- void pixel_show(int x,int y, unsigned int color)
- {
- unsigned int red,green,blue;
- switch(fb_var.bits_per_pixel) //rgb 像素
- {
- case 32:
- {
- unsigned int *addr=(unsigned int *)fbmem+(fb_var.xres*y+x);
- *addr=color;
- break;
- }
- case 24:
- {
- unsigned int *addr=(unsigned int *)fbmem+(fb_var.xres*y+x);
- *addr=color;
- break;
- }
- case 16: // 将 RGB888 转为 RGB565
- {
- unsigned short *addr=(unsigned short *)fbmem+(fb_var.xres*y+x);
- red = (color >> 16) & 0xff;
- green = (color >> 8) & 0xff;
- blue = (color >> 0) & 0xff;
- color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
- *addr = color;
- break;
- }
- case 8:
- {
- unsigned char *addr=(unsigned char *)fbmem+(fb_var.xres*y+x);
- *addr = (unsigned char)color;
- break;
- }
- default:
- {
- printf("can't surport %dbpp \n",fb_var.bits_per_pixel);
- break;
- }
- }
- }
- /* 显示 ascii 码 */
- void lcd_put_char(int x,int y, unsigned char s)
- {
- unsigned char *index=(unsigned char *)&fontdata_8x16[s*16];
- unsigned char i,j;
- for(i=0;i<16;i++) //8*16
- for(j=0;j<8;j++)
- {
- // 从高位到低
- if(index[i]&(1<<(7-j))) // 亮
- pixel_show(x+j,y+i, 0xffffff); // 白色
- else // 灭
- pixel_show(x+j,y+i, 0x0); // 黑色
- }
- }
- /* 显示 GBK 码 */
- void lcd_put_chinese(int x,int y, unsigned char *s)
- {
- unsigned char i,j,k;
- // 将编码转为区码
- unsigned int index=(s[0]-0xA1)*94+(s[1]-0xA1);
- // 转为点阵码(每个汉字 32 字节)
- unsigned char *dots=hzkmem+index*32;
- for(i=0;i<16;i++) //16*16
- for(k=0;k<2;k++)
- for(j=0;j<8;j++)
- {
- if((dots[i*2+k]>>(7-j))&0X01) // 亮
- pixel_show(x+8*k+j,y+i, 0xffffff); // 白色
- else // 灭
- pixel_show(x+8*k+j,y+i, 0x0); // 黑色
- }
- }
- void lcd_put(int x,int y, unsigned char *s)
- {
- while(*s)
- {
- if(*s<0xA1) //ASCII 码 8*16
- {
- printf("ASCII %x \r\n",*s );
- lcd_put_char(x,y,*s);
- s+=1;
- x+=8;
- }
- else //GB2313 16*16
- {
- printf("GBK %x %x\r\n",*s, *(s+1));
- lcd_put_chinese(x,y,s);
- s+=2;
- x+=16;
- }
- }
- }
- int main(int argc,char **argv)
- {
- int fd_fb,fd_hzk;
- struct stat hzk_start; //HZK16 文件信息
- unsigned char s[]="abc 中国 chinese";
- fd_hzk=open("HZK16",O_RDONLY);
- if(fd_hzk<0)
- {
- printf("can't open HZK16 \n");
- return 0;
- }
- if(fstat(fd_hzk,&hzk_start)<0) // 获取 HZK16 文件信息
- {
- printf("can't get fstart \n");
- return 0;
- }
- hzkmem =(unsigned char *)mmap(NULL,hzk_start.st_size, PROT_READ,MAP_SHARED,fd_hzk, 0);
- // 映射 HZK16 文件
- if(!hzkmem)
- {
- printf("can't map HZK16 \n");
- return 0;
- }
- fd_fb=open("/dev/fb0", O_RDWR);
- if(fd_fb<0)
- {
- printf("can't open /dev/fb0 \n");
- return 0;
- }
- if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&fb_var)<0)
- {
- printf("can't get var \n");
- return 0;
- }
- if(ioctl(fd_fb,FBIOGET_FSCREENINFO,&fb_fix)<0)
- {
- printf("can't get fix \n");
- return 0;
- }
- screensize=fb_var.xres*fb_var.yres*(fb_var.bits_per_pixel/8); // 显存大小
- fbmem =(unsigned char *)mmap(NULL,screensize, PROT_READ|PROT_WRITE,MAP_SHARED,fd_fb, 0);
- // 映射 fb0
- if(!fbmem)
- {
- printf("can't map /dev/fb0 \n");
- return 0;
- }
- memset(fbmem, 0, screensize); // 清屏黑色
- /* 显示数据 */
- lcd_put(0,fb_var.yres/2,s);
- munmap(hzkmem,hzk_start.st_size);
- munmap(fbmem,screensize);
- return 0;
- }
6. 编译运行
6.1 编译代码后, 然后使内核支持 LCD 启动
make menuconfig
进入 Device Drivers -> Graphics support -> Support for frame buffer devices
- <*> S3C2410 LCD framebuffer support // 编译进内核
- <*> Silicon Motion SM501 framebuffer support // 编译进内核
并修改 linux-3.4.2/drivers/video/Makefile
- #obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o
- obj-$(CONFIG_FB_S3C2410) += 9th_lcd.o // 添加以前写的 lcd 驱动
6.2 编译并启动内核后, 运行程序:
未完待续, 接下来下章学习如何实现矢量字体的显示~
来源: https://www.cnblogs.com/lifexy/p/8485634.html