ASCII 字符的点阵显示

488 阅读3分钟

要在 LCD 中显示一个 ASCII 字符,即英文字母这些字符,首先是要找到字符对应的点阵。在 Linux 内核源码中有这个文件:lib\fonts\font_8x16.c,里面以数组形式保存各个字符的点阵,比如:

image.png

数组里的数字是如何表示点阵的?以字符 A 为例,如图所示:

image.png 上图左侧有 16 行数值,每行 1 个字节。每一个节对应右侧一行中 8 个像素:像素从右边数起,bit0 对应第 0 个像素,bit1 对应第 1 个像素,……,bit7 对应第 7 个像素。某位的值为 1 时,表示对应的像素要被点亮;值为 0 时表示对应的像素要熄灭。所以要显示某个字符时,根据它的 ASCII 码在 fontdata_8x16 数组中找到它的点阵,然后取出这 16 个字节去描画 16 行像素。 比如字符 A 的 ASCII 值是 0x41,那么从 fontdata_8x16[0x41*16]开始取其点阵数据。

image.png

获取点阵

对于字符 c,char c,它的点阵获取方法如下:
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];

描点

根据“图 6.11 字符 A 的点阵”,我们分析下如何利用点阵在 LCD 上显示一个英文字母。因为有十六行,所以首先要有一个循环 16 次的大循环,然后每一行里有 8位,那么在每一个大循环里也需要一个循环 8 次的小循环。小循环里的判断单行的描点情况,如果是 1,就填充白色,如果是 0 就填充黑色,如此一来,就可以显示出黑色底,白色轮廓的英文字母。 image.png

程序代码

#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);
}