要在 LCD 中显示一个 ASCII 字符,即英文字母这些字符,首先是要找到字符对应的点阵。在 Linux 内核源码中有这个文件:lib\fonts\font_8x16.c,里面以数组形式保存各个字符的点阵,比如:
数组里的数字是如何表示点阵的?以字符 A 为例,如图所示:
上图左侧有 16 行数值,每行 1 个字节。每一个节对应右侧一行中 8 个像素:像素从右边数起,bit0 对应第 0 个像素,bit1 对应第 1 个像素,……,bit7 对应第 7 个像素。某位的值为 1 时,表示对应的像素要被点亮;值为 0 时表示对应的像素要熄灭。所以要显示某个字符时,根据它的 ASCII 码在 fontdata_8x16 数组中找到它的点阵,然后取出这 16 个字节去描画 16 行像素。
比如字符 A 的 ASCII 值是 0x41,那么从 fontdata_8x16[0x41*16]开始取其点阵数据。
获取点阵
对于字符 c,char c,它的点阵获取方法如下:
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
描点
根据“图 6.11 字符 A 的点阵”,我们分析下如何利用点阵在 LCD 上显示一个英文字母。因为有十六行,所以首先要有一个循环 16 次的大循环,然后每一行里有 8位,那么在每一个大循环里也需要一个循环 8 次的小循环。小循环里的判断单行的描点情况,如果是 1,就填充白色,如果是 0 就填充黑色,如此一来,就可以显示出黑色底,白色轮廓的英文字母。
程序代码
#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>
#include <sys/ioctl.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 */
..............
..............
};
static int fd_fb;
static struct fb_var_screeninfo var;
static unsigned char *fb_base;
static unsigned int screen_size;
static unsigned int line_width;
static unsigned int pixel_width;
static unsigned int arr_ascii[] = {0x41, 0x42, 0x43, 0x44, 0x45};
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fb_base + y * line_width + x * pixel_width;
unsigned short *pen_16 = (unsigned short *)pen_8;
unsigned int *pen_32 = (unsigned int *)pen_8;
unsigned int red, green, blue;
switch (var.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | ((blue >> 3) << 0);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
break;
}
}
}
void lcd_put_ascii(int x, int y, int 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))
{
lcd_put_pixel(x + 7 - b, y + i, 0xffffff);
}
else
{
lcd_put_pixel(x + 7 - b, y + i, 0x000000);
}
}
}
}
int main(int argc, char *argv[])
{
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("打开/dev/fb0设备失败\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("获取屏幕参数失败\n");
return -1;
}
pixel_width = var.bits_per_pixel / 8;
line_width = var.xres * pixel_width;
screen_size = var.xres * var.yres * pixel_width;
fb_base = (unsigned char *)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1)
{
printf("获取基地址失败\n");
return -1;
}
memset(fb_base, 0x0000000, screen_size);
for (int i = 0; i < sizeof(arr_ascii) / sizeof(int); i++)
{
lcd_put_ascii(var.xres / 2 + i * 8, var.yres / 2, arr_ascii[i]);
}
// 实现运行时输入参数
lcd_put_ascii(var.xres / 2, var.yres / 2, *argv[1]);
munmap(fb_base, screen_size);
close(fd_fb);
}