數碼相冊——英文和漢字的點陣顯示
- 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
- 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
- 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
- 開發環境:Linux 3.4.2內核、arm-linux-gcc 4.3.2工具鏈
目錄
一、前言
之前的博文中介紹了數碼相冊的框架以及字符編碼的知識,在這篇中,編程實現在開發板上顯示英文和中文。
此處主要實現功能,對於程序的框架考慮可能不足。
二、編程實現
1、打開LCD設備
static int fd_fb;
/* 打開設備:支持讀寫 */
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0) {
printf("can not open /dev/fb0 , err code :%d\n", fd_fb);
return -1;
}
2、獲取LCD信息
static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;
/* 獲得可變信息 */
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
printf("can not get var\n");
return -1;
}
/* 獲得固定信息 */
if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)) {
printf("can not get var\n");
return -1;
}
3、映射Framebuffer到內存
/* 直接映射到內存的Framebuffer */
screen_size = var.xres * var.yres * var.bits_per_pixel / 8; // 屏幕總像素所佔的字節數
line_width = var.xres * var.bits_per_pixel / 8; // 每行像素所佔的字節數
pixel_width = 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 not mmap\n");
return -1;
}
4、打開漢字庫並獲取漢字庫文件的大小
- 需要在可執行程序的當前目錄下放置文件名爲
HZK16
的漢字庫 - 使用
fstat()
函數來獲取文件名爲HZK16
的漢字庫的相關信息,關注大小
1、定義函數 int fstat(int fildes,struct stat *buf);
2、函數說明 fstat()用來將參數fildes所指的文件狀態,複製到參數buf所指的結構中(struct stat)。
/* 打開漢字庫 */
fd_hzk16 = open("HZK16", O_RDONLY);
if (fd_hzk16 < 0) {
printf("can not open /dev/fb0 , err code :%d\n", fd_hzk16);
return -1;
}
/* 計算字庫文件大小 */
if (fstat(fd_hzk16, &hzk_stat)) {
printf("can not get fstat\n");
return -1;
}
5、把漢字庫直接映射到內存中
- 目的:方便的使用漢字庫中的信息
/* 漢字庫直接映射到內存 */
hzkmem = (unsigned char *)mmap(NULL, hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0);
if (hzkmem == (unsigned char *)-1) {
printf("can not mmap for hzk16err code :%d\n", fd_hzk16);
return -1;
}
6、顯示英文與中文
/* 清屏 */
memset(fbmem, 0, screen_size);
/* 屏幕中間顯示字母 */
lcd_put_ascii(var.xres/2, var.yres/2, 'A');
/* 屏幕中間顯示中文 */
printf("chinese code : %02x %02x\n", str[0], str[1]); //打印編碼
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
7、LCD顯示屏描色函數實現
/* lcd描色 color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
/* 該座標在內存中對應像素的位置 */
pen_8 = fbmem+y*line_width+x*pixel_width;
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:
/* RGB:565 */
red = ((color >> 16) & 0xffff) >> 3;
green = ((color >> 8) & 0xffff) >> 2;
blue = ((color >> 0) & 0xffff) >> 3;
*pen_16 = (red << 11) | (green << 5) | blue;
break;
case 32:
*pen_32 = color;
break;
default:
printf("can not surport &dbpp\n", var.bits_per_pixel);
break;
}
}
8、LCD顯示英文函數實現
- 實現方法簡述:如下圖所示
1、對於A的ASCII的由16個字節組成,每行佔據一個字節
2、對於其編碼中的1位,對應LCD則會進行顯示,0位則不顯示
3、對每一行的像素進行判斷for (i = 0; i < 16; i++)
3.1 判斷一行中點亮的像素點位置
每一行的像素點由8位二進制數表示,從高位開始判斷for (b = 7; b >= 0; b--)
該爲是否爲1if (byte & (1<<b))
4、如果從高位開始判斷,則在下圖中每個像素點的座標爲(7-b, i)
/* lcd顯示ASCII碼 */
void lcd_put_ascii(int x, int y, unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
unsigned char byte;
int i, b;
/* 每行進行每位檢查,爲1則亮,否則滅 */
for (i = 0; i < 16; i++) {
byte = dots[i];
for (b = 7; b >= 0; b--) {
if (byte & (1<<b))
lcd_put_pixel(x+7-b, y+i, 0xffffff); //亮
else
lcd_put_pixel(x+7-b, y+i, 0x000000); //滅
}
}
}
9、LCD顯示中文函數實現
- 實現方法簡述:
1、HZK16字庫裏的漢字爲16×16,即需要256個點來顯示,也就是說需要32個字節才能達到顯示一個普通漢字的目的。
2、一個漢字佔兩個字節,這兩個中前一個字節爲該漢字的區號,後一個字節爲該字的位號
3、爲了兼容其他字符編碼,所以漢字編碼是從0xA1區開始的,如下圖。所以代碼爲str[0] - 0xA1;, str[1] - 0xA1;
4、其在內存的絕對的位置爲:hzkmem + (area * 94 + where) * 32
5、對每一行的像素進行判斷for (i = 0; i < 16; i++)
5.1 對每一行的每個字節進行判斷for (j = 0; j < 2; j++)
5.2 對每個字節的每一位for (b = 7; b >= 0; b--)
進行判斷是否等於1if (byte & 1 << b)
/* lcd顯示中文 */
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;
int i, j, b;
unsigned char byte;
/* 由於漢字需要16*16個點陣,即一行需要2個字節的數據
* 所以在提取字節時需要:i*2跳到對應行,j跳到對應的字節
* 在描點時需要x+7-b跳到該行該字節的每個位,j*8跳到對應的字節
*/
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)
lcd_put_pixel(x+7-b+j*8, y+i, 0xffffff); //亮
else
lcd_put_pixel(x+7-b+j*8, y+i, 0x000000); //滅
}
}
}
}
10、完整的代碼
#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] = {
/* 方便顯示,這裏省略了字庫編碼,可以到內核的font_8x16.c文件中找*/
};
/* 文件操作 */
static int fd_fb;
static unsigned char *fbmem;
/* hzk16字庫操作 */
static int fd_hzk16;
static struct stat hzk_stat;
static unsigned char *hzkmem;
/* lcd參數 */
static int screen_size;
static unsigned int line_width;
static unsigned int pixel_width;
static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;
/* lcd描色 color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
/* 該座標在內存中對應像素的位置 */
pen_8 = fbmem+y*line_width+x*pixel_width;
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:
/* RGB:565 */
red = ((color >> 16) & 0xffff) >> 3;
green = ((color >> 8) & 0xffff) >> 2;
blue = ((color >> 0) & 0xffff) >> 3;
*pen_16 = (red << 11) | (green << 5) | blue;
break;
case 32:
*pen_32 = color;
break;
default:
printf("can not surport &dbpp\n", var.bits_per_pixel);
break;
}
}
/* lcd顯示ASCII碼 */
void lcd_put_ascii(int x, int y, unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
unsigned char byte;
int i, b;
/* 每行進行每位檢查,爲1則亮,否則滅 */
for (i = 0; i < 16; i++) {
byte = dots[i];
for (b = 7; b >= 0; b--) {
if (byte & (1<<b))
lcd_put_pixel(x+7-b, y+i, 0xffffff); //亮
else
lcd_put_pixel(x+7-b, y+i, 0x000000); //滅
}
}
}
/* lcd顯示中文 */
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;
int i, j, b;
unsigned char byte;
/* 由於漢字需要16*16個點陣,即一行需要2個字節的數據
* 所以在提取字節時需要:i*2跳到對應行,j跳到對應的字節
* 在描點時需要x+7-b跳到該行該字節的每個位,j*8跳到對應的字節
*/
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)
lcd_put_pixel(x+7-b+j*8, y+i, 0xffffff); //亮
else
lcd_put_pixel(x+7-b+j*8, y+i, 0x000000); //滅
}
}
}
}
int main(int argc, char **argv)
{
unsigned char str[] = "中";
/* 打開設備:支持讀寫 */
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0) {
printf("can not open /dev/fb0 , err code :%d\n", fd_fb);
return -1;
}
/* 獲得可變信息 */
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
printf("can not get var\n");
return -1;
}
/* 獲得固定信息 */
if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)) {
printf("can not get var\n");
return -1;
}
/* 直接映射到內存的Framebuffer */
screen_size = var.xres * var.yres * var.bits_per_pixel / 8; // 屏幕總像素所佔的字節數
line_width = var.xres * var.bits_per_pixel / 8; // 每行像素所佔的字節數
pixel_width = 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 not mmap\n");
return -1;
}
/* 打開漢字庫 */
fd_hzk16 = open("HZK16", O_RDONLY);
if (fd_hzk16 < 0) {
printf("can not open /dev/fb0 , err code :%d\n", fd_hzk16);
return -1;
}
/* 計算字庫文件大小 */
if (fstat(fd_hzk16, &hzk_stat)) {
printf("can not 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("can not mmap for hzk16err code :%d\n", fd_hzk16);
return -1;
}
/* 清屏 */
memset(fbmem, 0, screen_size);
/* 屏幕中間顯示字母 */
lcd_put_ascii(var.xres/2, var.yres/2, 'A');
/* 屏幕中間顯示中文 */
printf("chinese code : %02x %02x\n", str[0], str[1]); //打印編碼
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str);
return 0;
}
三、編譯與運行
1、編譯
使用arm-linux-gcc -o show_font show_font.c
,生成可執行文件。
2、運行
把可執行文件與HZK16字庫放到開發板根文件系統的同一目錄下
執行./show_font
,可以看到開發板中正確顯示了“A中”