1.环境
- 官方给的内核文件(linux3.4)和根文件系统、linux2.6和配套根文件系统
- 不同的内核文件编译时用的交叉编译器不同,linux3.4用4.x的编译器,linux2.6用3.x的编译器,否则不能运行
- !!注意:在写代码时要用GB2312编码写,否则程序中的"中"是UTF-8,显示出来就是"涓"字
2.架构思路
- 打开屏所对应的文件,调用fctl函数使可以用地址来操作文件
- 获得屏的一些信息(如:屏幕一个像素点占用的位数,屏的分辨率等等)
- 打开汉字库文件,调用fctl函数使可以用地址来操作文件
- 获得汉字库文件的一些信息(如大小)
- 清屏
- 写ascii字符和汉字
3.知识点
-
LCD设备fb0的file_operations是fb_fops(位于fbmem.c)
fb_fops的write成员是fb_write()函数.
发现write()函数直接是对显存地址写数据, 所以使用echo “hello”> /dev/fb0时会直接出现乱码(没有点阵信息)
-
ioctl函数
octl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
ioctl获取屏幕的信息:ioctl(fd_fb , FBIOGET_FSCREENINFO, &fix)
FBIOGET_VSCREENINFO:获取fb_info-> var成员(可变信息:xy分辨率,像素位数等)
FBIOGET_FSCREENINFO:获取fb_info-> fix成员(固定信息:缓存地址,每行字节数,)
- mmap函数
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即可.
- ASCII码字库文件使用
在linux工程文件中搜索font,内核有很多font文件,我们使用font_8*16.c,这里面就存着字符对应的点阵信息。
数组的下标就是字符对应的ascii码
- HZK16汉字库文件使用
(1)HZK16是按分区表排列的点阵文件,由于每个汉字是2字节,每个字节的点阵是8x16,所以HZK16里的每个汉字点阵大。小:2816=32字节.
(2)然后还要将编码转为点阵码,我们以"中"为例:
“中” 的GBK编码为D6 D0.
转为分区表(每字节减去A1): 35 2F
所以中的点阵位于:(35*94+2F)32~(3594+2F)*32+31
注意!!
注意编写程序的软件的编码方式是什么,HZK16使用的是GBK的编码,所以如果编辑器的编码方式不是ANSI的,那么就需要在编译的时候加上
gcc -finput-charset=UTF-8 -fexec-charset=GBK -o ansi ansi.c
附录:源码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#define FONTDATAMAX 4096
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
... /*asic编码*/
}
int fd_fb; //屏的文件描述符
int fd_hzk16; //汉字库文件的描述符
struct fb_var_screeninfo var; //用来存屏的可变信息的,对应驱动
struct fb_fix_screeninfo fix; //用来存屏不可变信息的
int screen_size; //屏幕大小
unsigned char *fbmem; //屏的framebuffer
struct stat hzk_stat; //汉字文件的属性
unsigned char *hzkmem; //汉字文件的指针,可以直接用这个指针去操作那个文件
unsigned int line_width; //一行有多少个字节
unsigned int pixel_width; //一行有多少个字节
//color : 0x00RRGGBB
//板子驱动上是每个像素16位
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;
unsigned int red,green,blue; //这里用int不用char,是因为在给color赋值时需要左移11位,如果是char可能会出错
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch(var.bits_per_pixel) //根据每个像素多少位选择用哪个地址
{
case 8 : *pen_8 = color;break;
case 16:
{
/*565*/
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = color &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 %d bpp\n",var.bits_per_pixel);break; //不能支持这种像素
}
}
void lcd_put_ascii(int x, int y, unsigned char c)
{
//存放字符c的点阵的首地址
//每个字符需要16个字节存放,所以第c个字符就是c*16
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i,b;
unsigned char byte;
for(i = 0; i < 16; i++)
{
byte = *(dots+i); // <==> byte=dots[i]
for(b = 7; b >= 0; b--)
{
if(byte & (1<<b))
{
/*show*/
lcd_put_pixel(x+7-b , y+i ,0xffffff); //白色
}
else
{
/*hide*/
lcd_put_pixel(x+7-b , y+i ,0); //黑色
}
}
}
}
void lcd_put_chinese(int x,int y, unsigned char *str)
{
unsigned int area = str[0] - 0xA1; //这个汉字所在的区,汉字的第一个字符-0xa1
unsigned int where = str[1] - 0xA1; //这个汉字在这个区的什么地方,汉字的第二个字符-0xa1
unsigned char *dots =(unsigned char *)(hzkmem + (area*94 + where)*32) ; //一个区有94个字符,一个字符占用32字节(16*16/8)
unsigned char byte;
int i,j,b;
for(i = 0; i < 16; i++)
{
for(j = 0; j < 2; j++)
{
byte = dots[i*2 + j]; //一个汉字占用两个8*16的地方,地址是横着算的,不是先竖着弄完一个8*16,然后再接着另外一个8*16
for(b =7; b >=0; b--)
{
if(byte & (1<<b))
{
/*show*/
lcd_put_pixel(x + j*8 + 7-b , y + i , 0xffffff);
}
else
{
lcd_put_pixel(x + j*8 + 7-b , y + i , 0);
}
}
}
}
}
int main(int argc,char **argv)
{
unsigned char str[]="中";
//屏的初始化
fd_fb = open("/dev/fb0",O_RDWR);
if(fd_fb <0) //没打开
{
printf("can't open /dev/fb0\n");
}
//首先获得LCD的一些参数,如每个点用多少位来显示,XY的分辨率是多少
//这些信息在写LCD驱动的时候,都放在了一个fb_info的结构体中
//可以返回去驱看当时的驱动,在lcd_init函数里
if(ioctl(fd_fb , FBIOGET_VSCREENINFO, &var)) //可变信息:分辨率、RBG谁在高位、每一个点需要几位数据
{
printf("can't get var\n");
return -1;
}
if(ioctl(fd_fb , FBIOGET_FSCREENINFO, &fix)) //不可变数据:显存长度、设备名、一行的长度等
{
printf("can't get fix\n");
return -1;
}
screen_size = var.xres * var.yres *var.bits_per_pixel /8; //算出一屏有多大,x*y*每个像素的位数/8 (Bytes)
pixel_width = var.bits_per_pixel / 8; //每个像素占多少字节:每个像素占多少位/8
line_width = var.xres * pixel_width; //一行多少个像素乘以每个像素多少字节
//这个是把文件当作指针来操作,就不用对文件描述符来操作了
//对那个文件描述符操作,就是对framebuffer操作
//void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
// 指定地址,不指定系统分配 属性
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");
}
//汉字库初始化
fd_hzk16 = open("HZK16",O_RDONLY);
if(fd_hzk16 < 0)
{
printf("can't open HZK16\n");
return -1;
}
//int fstat(int fd, struct stat *buf); 获得这个文件的一些信息
if(fstat(fd_hzk16 , &hzk_stat))
{
printf("can't get HZK16\n");
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\n");
}
/*清屏:全部设为黑色*/
memset(fbmem, 0 ,screen_size);
lcd_put_ascii(var.xres/2 , var.yres/2 ,'A');
printf("chines code: %02x %02x \n",str[0] , str[1]);
lcd_put_chinese(var.xres/2+8,var.yres/2,str);
}