从零开始手搓 AI 桌面机器人:在ESP32-S3的LCD屏幕上显示 ASCII 字符

在上一篇文章中 [从零开始手搓AI桌面机器人:手把手教你用I80接口让ESP32-S3屏幕亮起来],我们成功点亮了 ESP32-S3 的 LCD 屏幕。但一块只能显示纯色的屏幕显然无法满足我们的开发需求。今天,我们将迈出图形显示的第一步——在屏幕上显示 ASCII 字符。

本文将基于 ESP-IDF 官方的 esp_lcd 组件,带你从底层原理到代码实现,彻底搞懂单片机是如何在屏幕上“画”出文字的。

核心原理:屏幕上的“十字绣”

在计算机和单片机的世界里,并没有“文字”的概念,只有 0 和 1。LCD 屏幕由二维像素阵列构成,任何图形、文字最终都通过控制每个像素的亮/灭(或灰度/颜色)来呈现。

对于字符,最常见的方式是使用点阵字库——将每个字符定义为一个固定大小的黑白位图(如 12×12、16×16 像素),每个像素用 1 bit 表示是否点亮。

所以要在 LCD 上显示一个字母 'A',本质上是在屏幕特定的坐标区域内,按照 'A' 的形状点亮对应的像素点。

为什么不用矢量字体?

矢量字体(如 TrueType)需要解析轮廓、栅格化,计算量大且占用内存,对于资源有限的 MCU 来说不现实。而点阵字库是预先计算好的静态数据,直接存储于 Flash,显示时只需简单读取并逐点绘制,效率极高。

什么是字模(Font Bitmap)?

字模就是字符的“像素图纸”。我们通常使用取模软件(如 PCtoLCD2002 或在线字模工具),将字符转换成十六进制的数组。当程序需要显示某个字符时,会从对应的数组中读取该字符的列数据,然后逐列(或逐行)写入 LCD 的显存(或通过 SPI/I8080 接口发送),从而在屏幕上“画”出该字符。

例如,一个 8×16 的字母 'A',在内存中可能长这样:

// 每一行由 2 个字节(16位)组成,1代表亮,0代表灭0x00, 0x00, // 00000000 000000000x18, 0x18, // 00011000 000110000x24, 0x24, // 00100100 001001000x42, 0x42, // 01000010 010000100x7E, 0x7E, // 01111110 01111110...

显示流程

第一步:获取编码:读取字符串中字符的 ASCII 码。

第二步:查表:根据 ASCII 码在字模数组中找到对应的点阵数据。

第三步:描点:遍历点阵数据的每一个 bit,如果是 1,则在 LCD 对应的 (x, y) 坐标上绘制前景色;如果是 0,则跳过(或绘制背景色)。

点阵大小与内存计算

在提取字模时,我们经常会看到 6×8、8×16、16×16 甚至 24×24 等不同的点阵大小。这究竟是如何决定的?它们又占用了多少内存呢?

宽高比例约定

在英文字符(ASCII)字库中,为了达到较好的视觉比例,字符的宽度通常设定为高度的一半。例如:

一个 16×16 的点阵,实际宽度是 16 / 2 = 8 列。

一个 12×12 的点阵,实际宽度是 12 / 2 = 6 列。

一个 24×24 的点阵,实际宽度是 24 / 2 = 12 列。

内存占用计算公式

单片机内存寸土寸金,我们需要精确计算每个字符占用的字节数。基于“逐列取模”的方式,计算公式为:

总字节数 = [ (size / 8) + ((size % 8) ? 1 : 0) ] × (size / 2)

每列字节数:高度除以 8(每 8 个像素占 1 个字节),若有余数则进位加 1。

总列数:即 size / 2(ASCII 字符宽度折半)。

实战举例

16 x 16 点阵:每列需 (16/8 + 0) = 2 字节,共 16/2 = 8 列。总占用 = 2 × 8 = 16 字节。

12 x 12 点阵:每列需 (12/8 + 1) = 2 字节,共 12/2 = 6 列。总占用 = 2 × 6 = 12 字节。

8 x 8 点阵:每列需 (8/8 + 0) = 1 字节,共 6 列。总占用 = 1 × 6 = 6 字节。

注意:上述公式中的 (size/2) 是针对 ASCII 字符的。如果是方形的汉字(如 16×16 汉字),宽度不折半,总占用则为 2 × 16 = 32 字节。

程序实现

本程序基于上一篇文章中 [从零开始手搓AI桌面机器人:手把手教你用I80接口让ESP32-S3屏幕亮起来]工程修改实现,在点亮屏幕的同时,会在屏幕的左上角显示“Hello World”字样。

  • 在目录 components/BSP/LCD下创建lcdfont.h文件。

  • 修改components/BSP/LCD目录下的lcd.h和lcd.c文件,在lcd.h文件中添加如下代码,lcd.c因内容太多不在这里贴源代码,要在源代码中直接查看代码:

void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color);  void lcd_show_char(uint16_t x, uint16_t y, char chr, uint8_t size, uint8_t mode, uint16_t color);void lcd_show_string(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, char *p, uint16_t color);
  • 修改main.c代码,增加代码:

lcd_show_string(10, 10, 240, 32, 32, "Hello World", RED);
  • 完成代码编写后,编译并下载代码到开发板,就可以看到开发板上显示如下图样子。

  • 代码地址:

    https://github.com/bkssxzdwy/esp32s3_samples/tree/main/examples/mcu_lcd_ascii_project

如何显示中文?

在本实验中只实现显示 ASCII字符,其实ASCII 字符只有 128 个,用数组存储毫无压力。但汉字有数万个,如果全部放在 Flash 中,ESP32-S3 的内存和 Flash 都会瞬间爆炸。

工业界的标准做法是:

1. 外部字库芯片:使用 SPI Flash 字库芯片,内置了完整的 GBK/Unicode 字库。

2. SD 卡字库:将字库文件(如 .fon 或 .bin)放在 SD 卡中,运行时按需读取。

3. LVGL 框架:如果你需要复杂的 UI,强烈建议直接上 LVGL。LVGL 内置了强大的字体引擎,支持从外部文件流式加载 TTF/OTF 字体,完美解决内存瓶颈。

总结

在 ESP32-S3 上显示字符,本质上就是“数据搬运”:将字符的点阵数据从Flash搬运到RAM。将RAM中的点阵数据转换为像素颜色。通过 esp_lcd_panel_draw_bitmap 将像素搬运到 LCD 控制器。

掌握了这个原理,你不仅可以显示 ASCII,还可以显示自定义的图标、进度条甚至简单的动画。

在接下来的文章,将使用通过 SD 卡更新字库。ESP32 读取存在 SD 卡里面的字库,然后将汉字显示在 LCD 上面。后面也会引入LVGL图像界面,来显示。

敬请期待后续更新!谢谢~~

扩展阅读:

从零开始手搓AI桌面机器人:屏幕接口那么多,到底怎么选?一文搞懂所有主流显示接口

从零开始手搓AI桌面机器人:手把手教你用I80接口让ESP32-S3屏幕亮起来

发表评论