news 2026/4/15 18:56:14

LCD1602液晶显示屏程序自定义字符实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LCD1602液晶显示屏程序自定义字符实现详解

玩转LCD1602:如何用代码“画”出自定义图标?

你有没有在电子秤上看到过一个小小的“砝码”符号?或者在温控器的屏幕上见过闪烁的温度计图标?这些可不是标准键盘能打出来的字符——它们是程序员一笔一划“画”出来的图形,而实现这一切的核心,就是那块看起来普普通通的LCD1602液晶屏

别看它只能显示两行、每行16个字符,一旦你掌握了它的“隐藏技能”,这块黑白小屏就能变成你的迷你图形画布。今天我们就来揭开这个秘密:如何通过编程,在LCD1602上自定义显示箭头、电池、心形甚至动画帧?


为什么还在用LCD1602?它不早就过时了吗?

坦率说,现在满大街都是彩色TFT触摸屏,动辄几百上千分辨率,LCD1602确实显得有点“复古”。但它依然活跃在无数设备中——从工厂里的PLC控制面板,到家里的智能电表、空气净化器,甚至一些高端示波器的状态栏。

原因很简单:省资源、够稳定、成本低

  • 它不需要操作系统;
  • 不消耗MCU太多算力;
  • 静态显示几乎不耗电;
  • 接线简单,4根数据线+3根控制线就能跑起来;
  • 最关键的是:便宜!几块钱一片。

尤其是在STM8、51单片机这类资源紧张的小系统里,给用户一点直观反馈,LCD1602仍是首选。

但问题来了:ASCII字符集就那么几十个字母数字和标点,怎么表达“当前正在加热”、“信号弱”这种状态呢?

答案就是:自定义字符(Custom Character)


自定义字符是怎么“画”出来的?点阵背后的逻辑

LCD1602每个字符的位置其实是一个5×8 像素的小网格。你可以把它想象成一张 tiny 的像素画板,每一列宽5个点,高8行。

比如你想画一个向上的箭头 ↑,大概是这样:

● ●●● ● ● ● ● ● ● ●

我们把“亮”的点记为1,“灭”的点记为0,每一行转换成一个字节的二进制数,只取低5位(因为宽度只有5列),高位补0。于是上面这个图案就变成了8个字节的数据:

const uint8_t up_arrow[8] = { 0b00100, // 第1行:中间亮 0b01110, // 第2行:中间三个亮 0b10101, // 第3行:两边和中间亮 0b00100, 0b00100, 0b00100, 0b00100, 0b00000 // 最后一行留空,避免粘连 };

这8个字节,就是一个完整的自定义字符的“图纸”。


图纸画好了,往哪儿存?CG RAM揭秘

LCD1602内部有个叫CG RAM(Character Generator RAM)的地方,专门用来存放这些“图纸”。它总共有64字节,分成8组,每组8字节,刚好容纳8个自定义字符。

也就是说,你最多可以同时定义8个不同的图标。

这些字符编号从0到7,不是ASCII码,而是内部索引。当你后续往屏幕写入数据时,只要写入0,就会显示第一个自定义图形;写入1,就显示第二个……

⚠️ 注意:CG RAM 是易失性内存,断电即清空。所以每次上电后,必须重新把这8组数据写一遍。

那怎么告诉LCD:“我现在要往CG RAM里写东西了”?

这就涉及到LCD控制器的一个关键机制:地址指针切换


控制核心:HD44780是如何管理内存的?

几乎所有字符型LCD都基于HD44780 或其兼容芯片(如ST7066U)。它内部有两块主要的RAM区域:

名称作用
DD RAMDisplay Data RAM —— 存放屏幕上实际显示的字符编码
CG RAMCharacter Generator RAM —— 存放自定义字符的点阵数据

还有一个地址计数器(AC),决定了当前操作的是哪块内存。

要写入自定义字符,流程非常清晰:

  1. 发送命令0x40 | (n << 3),设置CG RAM地址指针(n为字符编号)
  2. 连续写入8字节点阵数据
  3. 切回DD RAM模式
  4. 向DD RAM写入字符编号 n,即可显示对应图形

举个例子:想把上面的箭头存为第0号字符,起始地址就是0x40 + 0*8 = 0x40。发送命令0x40后,接下来的8次数据写入都会进入CG RAM。


实战代码:封装一个可复用的创建函数

下面这段C语言代码适用于STM32、AVR、51等常见MCU平台,假设你已经有了基本的IO驱动函数(如LCD_WriteCommand,LCD_WriteData)。

/** * @brief 创建一个自定义字符 * @param location: 字符编号 (0~7) * @param char_map: 指向8字节数组的指针 */ void LCD_CreateChar(uint8_t location, const uint8_t* char_map) { location &= 0x07; // 限制范围,防止越界 // 设置CG RAM地址:0x40 + location * 8 LCD_WriteCommand(0x40 | (location << 3)); for (int i = 0; i < 8; i++) { LCD_WriteData(char_map[i]); } }

是不是很简洁?只需要传入编号和数组,一行命令加一个循环搞定。

再来看怎么使用它:

// 定义几个实用图标 const uint8_t icon_temp[8] = { /* 温度计 */ }; const uint8_t icon_battery_full[8] = { /* 满电量 */ }; const uint8_t icon_arrow_up[8] = { /* 上箭头 */ }; void LCD_InitWithIcons(void) { LCD_Init(); // 先初始化LCD // 一次性加载所有需要的图标 LCD_CreateChar(0, icon_temp); LCD_CreateChar(1, icon_battery_full); LCD_CreateChar(2, icon_arrow_up); // 清屏 LCD_WriteCommand(0x01); Delay_ms(2); } // 显示示例:"Temp: 25°C ↑" void ShowTemperature(float temp) { LCD_GotoXY(0, 0); LCD_Print("Temp: "); LCD_PrintFloat(temp, 1); // 显示带一位小数 LCD_WriteData(0xDF); // 输出 ° 符号(部分型号支持) LCD_WriteData('C'); LCD_WriteData(2); // 显示↑图标(编号2) }

注意最后这句LCD_WriteData(2)—— 我们并没有发送字符’B’或’X’,而是直接发了个数字2。LCD会自动查找CG RAM中编号为2的图形并显示出来。


小技巧:还能玩出什么花样?

✅ 动态效果模拟

虽然LCD1602不能真正“刷新”局部画面,但你可以利用多个自定义字符实现简单的动画。

例如,定义三个风扇叶片旋转的不同姿态,编号分别为1、2、3:

const uint8_t fan_frame1[8] = { ..., 0b01110, ... }; // 叶片朝右 const uint8_t fan_frame2[8] = { ..., 0b00100, ... }; // 垂直 const uint8_t fan_frame3[8] = { ..., 0b00010, ... }; // 斜向

然后在主循环中轮询切换:

while (1) { LCD_GotoXY(10, 1); LCD_WriteData((frame++ % 3) + 1); // 循环显示三帧 Delay_ms(300); }

视觉上就有了一种“转动”的感觉。

✅ 电量条/进度条

可以用不同填充程度的方块表示电量:

编号图形含义
00%
125%
250%
375%
4100%

然后根据ADC读数选择对应的字符输出,比打印“Battery: 75%”更直观。


踩坑提醒:新手最容易忽略的几个细节

  1. 写完CG RAM记得切回DD RAM
    - 如果忘记切换,后续所有写入都会继续修改CG RAM,导致显示异常。
    - 建议在LCD_CreateChar末尾加一句LCD_WriteCommand(0x80);回到DD RAM首地址。

  2. 对比度调节Vo千万别接错
    - Vo引脚控制液晶偏压,直接影响是否能看清内容。
    - 正确做法是接一个10kΩ电位器,两端分别接Vcc和GND,滑动端接Vo。
    - 接错了可能全黑或全白。

  3. 避免频繁重载CG RAM
    - 每次写入都有延迟,建议只在初始化阶段集中写入一次。
    - 若需动态更换图标,提前规划好编号分配。

  4. 背光节能设计
    - LED+可通过NPN三极管由MCU控制,长时间无操作自动关闭背光。
    - 对电池供电设备尤为重要。

  5. 不同厂家略有差异
    - 有些LCD对时序要求更严格,延时不够会导致初始化失败。
    - 建议关键步骤后加入适当Delay(如2ms),或查询忙标志位。


为什么这项“老技术”还值得学?

也许你会问:现在都2025年了,谁还用手敲LCD1602驱动?

但正是这种“原始”的外设,教会我们最本质的东西:

  • 内存映射思想:DD RAM vs CG RAM 的分离设计,是现代显存架构的缩影;
  • 位操作实践:每一位代表一个像素,强化了对二进制的理解;
  • 硬件时序掌控:E脉冲、RS选择、建立保持时间……这些都是嵌入式开发的基本功;
  • 资源优化思维:在仅有几百字节RAM的系统中,如何高效利用每一个bit。

掌握LCD1602的自定义字符,不只是为了做一个温度图标。它是通往更复杂GUI系统的第一级台阶。当你以后面对TFT屏幕+TouchGFX框架时,你会发现很多概念一脉相承:显存管理、字体渲染、图层合成……

而这一切,都可以从这8个字节开始。


如果你也在做某个小项目,正愁怎么优雅地提示“运行中”状态,不妨试试用自定义字符画个小齿轮或者心跳动画。你会发现,哪怕是最简单的硬件,也能讲出动人的交互故事。

你在项目中用过哪些有趣的自定义字符?欢迎在评论区晒出你的“像素艺术”作品!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 1:56:16

Vosk-Browser语音识别开发实战:构建零依赖智能应用完整指南

Vosk-Browser语音识别开发实战&#xff1a;构建零依赖智能应用完整指南 【免费下载链接】vosk-browser A speech recognition library running in the browser thanks to a WebAssembly build of Vosk 项目地址: https://gitcode.com/gh_mirrors/vo/vosk-browser 在现代…

作者头像 李华
网站建设 2026/4/11 12:07:45

Zotero MarkDB-Connect终极配置教程:5步实现文献与笔记无缝连接

Zotero MarkDB-Connect终极配置教程&#xff1a;5步实现文献与笔记无缝连接 【免费下载链接】zotero-markdb-connect Zotero plugin that links your Markdown database to Zotero. Jump directly from Zotero Items to connected Markdown files. Automatically tags Zotero I…

作者头像 李华
网站建设 2026/3/27 6:30:27

S32DS安装教程:构建路径与库文件配置要点

从零开始配置S32DS&#xff1a;构建路径与库文件的实战指南你有没有遇到过这样的情况&#xff1f;好不容易装好了S32 Design Studio&#xff08;S32DS&#xff09;&#xff0c;导入了NXP的SDK工程&#xff0c;信心满满地点下“Build”&#xff0c;结果编译器弹出一堆红字&#…

作者头像 李华
网站建设 2026/4/14 7:01:00

3步搞定OpenMir2传奇服务器部署:从零开始的完整配置指南

3步搞定OpenMir2传奇服务器部署&#xff1a;从零开始的完整配置指南 【免费下载链接】OpenMir2 Legend of Mir 2 Game server 项目地址: https://gitcode.com/gh_mirrors/op/OpenMir2 想要重温经典传奇2的游戏体验&#xff1f;OpenMir2开源服务器项目让你轻松搭建专属游…

作者头像 李华
网站建设 2026/4/15 6:28:43

OpenLRC:颠覆传统!用AI魔法让音频秒变精准字幕的终极指南

还在为制作音频字幕而头疼吗&#xff1f;手动调整时间轴、逐字翻译的时代已经过去了&#xff01;今天我要向你介绍一款革命性的AI工具——OpenLRC&#xff0c;它能够智能地将任何音频内容转换为精准同步的LRC字幕文件&#xff0c;彻底解放你的双手&#xff01; 【免费下载链接】…

作者头像 李华