数码相册——英文和汉字的点阵显示
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》
- 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
目录
一、前言
之前的博文中介绍了数码相册的框架以及字符编码的知识,在这篇中,编程实现在开发板上显示英文和中文。
此处主要实现功能,对于程序的框架考虑可能不足。
二、编程实现
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中”