用STM32F1点亮第一行文字:串口字符型LCD从零开始实战指南
你有没有过这样的经历?写好了传感器采集代码,烧录进STM32板子,满怀期待地打开串口助手……结果只看到一串乱码或者干脆没输出。更糟的是,设备在现场独立运行时,根本没法接电脑调试——它就像个“黑盒子”,出了问题只能靠猜。
今天,我们就来解决这个痛点:让STM32自己“说话”。
不是通过串口发给PC,而是直接驱动一块小小的液晶屏,把系统状态、温度数据、运行提示清清楚楚地显示出来。而我们要用的,是一款对新手极其友好的显示模块——串口字符型LCD,搭配嵌入式开发界的“老将”STM32F1系列。
别被“嵌入式显示”吓到。这一次,我们不需要画图、不用管显存、不研究复杂的时序。只需要一根线,几行代码,就能让你的单片机拥有“视觉”。
为什么是串口字符型LCD?因为它真的简单到离谱
市面上能和MCU对接的显示屏不少:TFT彩屏功能强大但配置复杂;OLED精致小巧却要折腾I2C/SPI驱动;传统的并行接口1602 LCD呢?光控制线就七八根,接错一根就得从头查。
而串口字符型LCD是什么?
你可以把它理解为一个“会看懂串口指令的智能灯牌”。你只要通过UART发字符串过去,比如:
"当前温度: 25.6°C"它就会自动显示在屏幕上。想换行?发个控制命令就行。想清屏?发个0xFE, 0x01。完全不用关心背后的E、RS、RW那些让人头大的时序信号。
它的内部其实集成了一个协处理器(通常是HD44780兼容芯片),外加一个串行转并行的协议解析引擎。你发的数据,它自己会处理成标准的LCD操作流程。换句话说——你是在“打印”,而不是“驱动”。
它到底有多省事?
| 操作 | 传统并行1602 | 串口字符型LCD |
|---|---|---|
| 接线数量 | ≥6根 | 仅需1根TX |
| 初始化代码长度 | 50+行 | <10行 |
| 显示一句话 | 需分批写入DDRAM | 直接printf风格发送 |
| 上手难度 | 新手劝退 | 半小时搞定 |
如果你是个刚学完GPIO点灯、UART通信的新手,这块屏就是你迈向人机交互的第一步。
STM32F1 + 串口LCD:黄金搭档的技术细节拆解
我们选STM32F103C8T6(蓝 pill 核心)作为主控,原因很简单:
- 多达3个USART接口,资源充裕;
- 主频72MHz,处理能力足够;
- GPIO电平3.3V,与大多数串口LCD兼容;
- 社区资料丰富,HAL库支持完善;
- 成本低,适合教学与原型验证。
而串口字符型LCD常见的型号如LCM1602C、GY-1602A等,基本都是基于ST7066U或兼容控制器,支持标准HD44780指令集扩展,并内置UART接收逻辑。
🔍 小知识:虽然叫“串口”,但它通常不是原生UART。很多模块内部用了类似SCM80C51的单片机做桥接,把串口数据翻译成并行时序送给LCD本体。但对我们来说,这不重要——我们只关心“输入什么,输出什么”。
关键参数速览(直接影响设计)
| 项目 | 典型值 | 注意事项 |
|---|---|---|
| 工作电压 | 5V / 3.3V 可选 | 确认模块是否支持3.3V输入 |
| 通信方式 | UART 异步串行 | 波特率默认9600bps |
| 数据格式 | 8N1(8位数据,无校验,1停止位) | 必须匹配STM32设置 |
| 控制命令起始字节 | 0xFE | 后跟具体操作码 |
| 字符编码 | ASCII(0x20 ~ 0x7E) | 不要发中文或特殊控制符 |
| IO电平 | TTL电平(3.3V/5V兼容) | 若模块为5V且无电平转换,需加TXS0108E等芯片 |
⚠️ 血泪教训:我曾烧坏过两块STM32板子,就是因为直接把PA9接到一个5V供电且未做电平隔离的LCD上。记住:STM32 GPIO耐压最大3.6V,超过即有风险!
建议做法:
- 使用自带电平转换电路的模块(淘宝搜“3.3V兼容串口1602”);
- 或外加电平转换芯片(如TXS0108E、MAX3312);
- 或使用光耦隔离方案(高可靠性场景)。
原理讲透:它是怎么“读懂”你的命令的?
串口字符型LCD的工作机制可以分为三层:
第一层:物理连接 —— 就是一根TX线
STM32 PA9 (USART1_TX) │ ▼ [可选] 电平转换 │ ▼ LCD_RX 引脚注意:大多数情况下,我们只用单向通信(MCU → LCD)。LCD不会回传数据,所以RX引脚悬空即可。
第二层:协议解析 —— “0xFE” 是它的“唤醒词”
当你发送一个字节0xFE,LCD模块就知道:“接下来要执行命令了!”
紧随其后的字节才是真正的操作码。例如:
| 命令序列 | 功能说明 |
|---|---|
0xFE, 0x01 | 清屏 |
0xFE, 0x80 | 光标跳转至第一行首地址(0x80 = 0x00 + 0x80) |
0xFE, 0xC0 | 跳转至第二行首地址(0xC0 = 0x40 + 0x80) |
0xFE, 0x0C | 开启显示,关闭光标和闪烁 |
0xFE, 0x18 | 整体左移一格(可用于滚动效果) |
这些地址映射来源于HD44780的DDRAM地址结构:
第1行地址:0x00 ~ 0x27 (对应屏幕第1~40列) 第2行地址:0x40 ~ 0x67 (实际显示位置偏移+0x40)但因为我们用的是串口模块,只需记住常用偏移即可,无需手动计算忙标志或延时等待。
第三层:字符渲染 —— 自动写入DDRAM
当你直接发送ASCII字符,比如'A'(0x41),模块会将其存入当前光标位置的DDRAM,并立即刷新显示。
支持的标准字符包括:
- 数字0-9
- 大小写字母A-Z,a-z
- 符号. , : ; ! ? @ # $ % ^ & * ( ) - + = < > [ ] { } \ | ' " / ~ _
- 空格
部分高级模块还支持自定义字符(CGROM),最多可定义8个5×8点阵图标,比如🌡️、⚡、✅等,但这属于进阶玩法。
实战!手把手教你让STM32说出第一句话
下面我们以STM32F103C8T6 + HAL库 + Keil MDK为例,完成一次完整的初始化与显示流程。
步骤1:硬件连接
| STM32F103C8T6 | 串口字符型LCD |
|---|---|
| 3.3V | VCC |
| GND | GND |
| PA9 (USART1_TX) | RX |
✅ 提示:如果模块是5V供电,请务必确认其RX是否支持3.3V输入,否则必须加电平转换!
步骤2:使用STM32CubeMX配置USART1
打开STM32CubeMX,选择芯片后进行如下配置:
- RCC:HSE选择Crystal/Ceramic Resonator
- Clock Configuration:72MHz系统时钟
- USART1:
- Mode: Asynchronous
- Baud Rate: 9600
- Word Length: 8 Bits
- Parity: None
- Stop Bits: 1
- GPIO: PA9 自动设为
USART1_TX复用推挽输出
生成工程,选择MDK-ARM,导出代码。
步骤3:核心代码实现
在main.c中添加以下函数:
#include "usart.h" #include <string.h> #include <stdio.h> // 发送一条原始数据 void LCD_Send(uint8_t *data, uint16_t size) { HAL_UART_Transmit(&huart1, data, size, 100); } // 发送控制命令 void LCD_Command(uint8_t cmd) { uint8_t command[2] = {0xFE, cmd}; LCD_Send(command, 2); HAL_Delay(2); // 给模块留出处理时间 } // 在指定位置写字符串(行列从0开始) void LCD_WriteString(uint8_t row, uint8_t col, char *str) { uint8_t addr; if (row == 0) addr = 0x80 + col; else if (row == 1) addr = 0xC0 + col; else return; LCD_Command(addr); // 设置光标位置 HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), 100); }步骤4:主函数调用
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_Delay(100); // 等待LCD上电稳定 // 初始化LCD LCD_Command(0x01); // 清屏 HAL_Delay(5); LCD_Command(0x0C); // 显示开,光标关 LCD_Command(0x06); // 自动增量模式,不移屏 // 显示内容 LCD_WriteString(0, 0, "Hello STM32!"); LCD_WriteString(1, 0, "Temp: 25.6 C"); while (1) { HAL_Delay(1000); } }编译、下载、上电——恭喜你,屏幕上出现了属于你的第一行文字!
常见坑点与调试秘籍
别高兴太早,实际调试中总会遇到些“玄学”问题。以下是几个高频故障及解决方案:
❌ 屏幕全黑 or 无显示?
- 检查电源是否正常(用万用表测VCC-GND是否有电压);
- 查看背光是否亮起(有些模块可通过跳帽控制背光);
- 确认波特率是否一致(尝试9600/115200切换测试);
❌ 显示乱码 or 方块?
- 波特率不匹配(最常见原因);
- 数据位/校验位设置错误(确保是8N1);
- 模块固件异常,尝试断电重启;
❌ 命令无效(不清屏、不换行)?
- 没有在命令前加
0xFE; - 发送太快,模块来不及响应,加入适当延时(2~5ms);
- 模块不支持该指令(查看模块说明书);
❌ STM32串口卡死?
- 使用阻塞式
HAL_UART_Transmit时,若线路异常可能导致死锁; - 解决方案:启用中断发送或DMA,避免长时间阻塞;
- 加入超时机制,例如:
c if (HAL_UART_Transmit(&huart1, data, len, 50) != HAL_OK) { // 处理错误,重试或报警 }
进阶思路:让它不只是“显示器”
你以为这只是个静态信息展示工具?远远不止。
结合STM32的其他外设,你可以打造一个真正意义上的微型HMI终端:
✅ 动态刷新 + 实时监控
用定时器每秒读取DS18B20温度,实时更新屏幕:
float temp = Read_Temperature(); char buf[20]; sprintf(buf, "Temp: %.1f C", temp); LCD_WriteString(1, 0, buf);✅ 多页面菜单系统
通过按键切换显示不同界面:
- 页面1:系统状态
- 页面2:传感器数据
- 页面3:错误日志
✅ 自定义图标提升体验
利用CGROM生成自定义字符,比如:
- 🌡️ 表示温度
- 💧 表示湿度
- ⚙️ 表示设置模式
✅ 双输出:既上屏又上报
复用同一串口,通过条件判断同时输出到LCD和PC端:
#ifdef DISPLAY_LCD LCD_WriteString(0,0,"Running..."); #else printf("Running...\n"); #endif写在最后:这是起点,不是终点
当你第一次看到那行“Hello STM32!”出现在小小的屏幕上时,也许会觉得不过如此。但请记住:每一个复杂的图形界面系统,都始于这样一行简单的文本输出。
掌握STM32与串口字符型LCD的协同工作,不仅仅是学会了一个外设的使用方法,更是建立起一种“软硬协同”的思维方式:
- 如何将抽象需求转化为具体电信号?
- 如何通过协议封装降低复杂度?
- 如何在资源受限环境下做出合理取舍?
这些问题的答案,正是嵌入式工程师的核心竞争力。
未来你可以继续挑战SPI TFT屏、LVGL图形库、触摸交互设计……但无论走得多远,不妨回头看看这块小小的字符屏——它是你嵌入式旅程中,最朴实也最坚实的起点。
如果你正在学习STM32,手上正好有一块闲置的串口LCD,不妨今晚就试试点亮它。也许那一瞬间的成就感,就是你坚持下去的理由。
欢迎在评论区分享你的首次显示截图,我们一起见证每一行代码带来的光亮。