分类: 嵌入式
2015-08-23 16:32:17
字符的点阵显示
15年8月21日11:22:32
#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] = {
/* 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 */
/* 1 0x01 '^A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x7e, /* 01111110 */
0x81, /* 10000001 */
0xa5, /* 10100101 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0xbd, /* 10111101 */
0x99, /* 10011001 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0x7e, /* 01111110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
...................
};
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
int fd_hzk16;
struct stat hzk_stat;
unsigned char *hzkmem;
/* color : 0x00RRGGBB */
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;
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:RGB */
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("cannot support!\n");
break;
}
}
}
void lcd_put_ascii(int x, int y, 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<<b))
{
/* show */
lcd_put_pixel(x+7-b, y+i, 0xffffff); /* white */
}
else
{
/* hide */
lcd_put_pixel(x+7-b, y+i, 0); /* black */
}
}
}
}
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<<b))
{
/* show */
lcd_put_pixel(x+j*8+7-b, y+i, 0xffffff); /*white*/
}
else
{
/* hide */
lcd_put_pixel(x+j*8+7-b, y+i, 0); /* black */
}
}
}
}
int main(int argc, char **argv)
{
unsigned char str[] = "中";
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("cannot open /dev/fb0!\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("cannot get var\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix))
{
printf("cannot get fix\n");
return -1;
}
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("cannot mmap\n");
return -1;
}
fd_hzk16 = open("HZK16", O_RDONLY);
if (fd_hzk16 < 0)
{
printf("cannot open HZK16\n");
return -1;
}
if (fstat(fd_hzk16, &hzk_stat))
{
printf("cannot get fstat\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("cannot mmap for hzkmem\n");
return -1;
}
/* clear screen: set all the color black */
memset(fbmem, 0, screen_size);
lcd_put_ascii(var.xres/2, var.yres/2, 'A');
printf("chinese code: %02x %02x", str[0], str[1]);
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
return 0;
}
(一)首先来写main函数:
(1)打开/dev/fb0,通过ioctl函数来获取屏幕的一些可变参数和固定参数。
(2)将设备通过mmap函数映射到内存里面,这样我们就可以像访问数组一样访问文件了。
映射的时候,需要用到映射多大的内存(即mmap函数的第二个参数),这时候需要计算出来屏幕的大小:screen_size。通过下面的公式计算即可。
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
(3)显示一个英文字符。在main函数中通过调用lcd_put_ascii()函数来实现。这个函数我们下面再分析。
(4)显示一个中文字符。由于我们需要从HZK16这个文件里面取值,可以调用read(),write()等函数,但是我们想要方便一点,也将这个文件映射到内存中,这样也可以像操作数组一样从这个文件中取值了。
先打开HZK16这个文件,然后通过mmap函数来实现映射,这时候,同样需要知道映射的大小,通过调用fstat()函数来获取这个文件的一些相关信息。
fstat()函数原型:
int fstat(int fd, struct stat *buf);
这个函数返回一个stat结构体,结构体成员如下所示:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
所以在程序中,我们首先定义一个stat结构体,然后再使用fstat函数来获取信息,获取的信息就存在这个结构体中。
struct stat hzk_stat;
fstat(fd_hzk16, &hzk_stat);
获取信息以后,就可以mmap映射了。
(5)显示之前先清屏。用memset函数即可,简单粗暴。
(二)下面分析lcd_put_ascii()函数:
(1)英文字体的点阵存在fontdata_8x16[]这个数组中,这个数组是我们直接从内核的font_8*16.c中拷贝过来的。当然也可以像HZK16那样来调用。
(2)可以看到,在这个数组中,每个字符用16个byte来存储它的点阵。对于某一个字符c,因为它使用ascii来编码的,我们直接去数组的c*16位置取点阵即可。
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
(3)每个字符是8*16的点阵,将对应的每一位,1的位我们就显示为白色,0的位不显示(黑色)。
显示的时候用到了lcd_put_pixel(int x, int y, unsigned int color)函数,这个函数最后再分析。描画的时候,注意x,y坐标的表示方法,自己在草稿纸上仔细算算。
(三)下面分析lcd_put_chinese()函数:
(1)我们将汉字存储在一个数组中,它是用GBK码来存储的,每一个汉字用两个byte来存储。
由于我们已经将HZK16映射到内存中,我们需要从哪取值,可以参考《HZK16的简单介绍和使用方法》这篇文章,用下面的方法算出偏移值(area * 94 + where) * 32,然后通过 hzkmem这个指针加偏移值,就可以计算出汉字点阵在内存数组中的位置。
unsigned int area = str[0] - 0xA1;
unsigned int where = str[1] - 0xA1;
unsigned char *dots = hzkmem + (area * 94 + where) * 32;
(2)由于汉字是16*16点阵来表示的,所以一行中有两个byte,所以每行需要取两次byte的值。仔细分析分析这个函数中的循环。
(四)最后分析lcd_put_pixel(int x, int y, unsigned int color)这个函数:
(1)2440的LCD控制器直接从fbmem开始的内存取值显示在LCD上面,所以想要在坐标为(x,y)的位置显示颜色,需要去设置内存中相应位置的颜色。
由于内核中struct fb_var_screeninfo var;中是以像素为单位来存储的,而内存中是以byte为单位来存储的,所以需要转换一下。每一行的行宽line_width = var.xres * var.bits_per_pixel / 8;而像素宽度pixel_width = var.bits_per_pixel / 8;,这样,坐标(x,y)对应内存中的位置就是fbmem + y*line_width +x*pixel_width;如下所示:
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
unsigned char *pen_8 = fbmem + y*line_width +x*pixel_width;
(2)函数中的第三个参数color中是以0x00RRGGBB的形式来存储颜色的。
当var.bits_per_pixel = 8时,用到了调色板的知识,我们就直接用*pen_8 = color;来表示。
当var.bits_per_pixel = 16时,需要将color中的颜色取出来,重新组合成16位的颜色。16位颜色的组成为:565:RGB。用下面的代码从新组合:
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;