1. 项目概述
这个项目展示了如何利用STM32F103RCT6微控制器,通过STM32CubeMX配置工具和Keil5开发环境,实现串口数据发送并在LCD显示屏上实时显示的技术方案。作为一名嵌入式开发工程师,我经常需要在各种显示设备上输出调试信息或运行状态,这种串口转LCD显示的方案在实际项目中非常实用。
整套系统的工作流程可以这样理解:STM32通过USART串口接收或生成数据,经过处理后,通过GPIO接口将数据发送到LCD显示模块。在这个过程中,我们需要处理硬件接口配置、数据传输协议、显示驱动等多个技术环节。这个方案特别适合需要低成本显示方案的嵌入式设备,比如工业控制面板、智能家居终端、便携式仪器仪表等场景。
2. 硬件准备与电路设计
2.1 核心硬件选型
STM32F103RCT6是一款基于ARM Cortex-M3内核的微控制器,具有256KB Flash和48KB SRAM,足够应对大多数中等复杂度的嵌入式应用。它内置了多个USART接口,为我们的串口通信提供了硬件基础。
对于LCD显示模块,常见的有1602字符型LCD和12864图形点阵LCD两种选择:
- 1602 LCD:成本低,显示16x2个字符,适合简单信息显示
- 12864 LCD:可显示图形和汉字,功能更丰富但驱动更复杂
提示:如果是初次尝试,建议从1602 LCD开始,它的驱动相对简单,更容易实现基本功能。
2.2 电路连接方案
STM32与LCD的典型连接方式如下:
-
数据线连接:
- 对于4位模式:DB4-DB7连接至STM32的4个GPIO
- 对于8位模式:DB0-DB7连接至STM32的8个GPIO
-
控制线连接:
- RS(寄存器选择):连接至任意GPIO
- RW(读写选择):通常接地(只写模式)
- E(使能信号):连接至任意GPIO
-
背光控制:
- 通过一个GPIO控制或直接接VCC
-
串口连接:
- USART_TX连接至PC的USB转串口模块的RX
- USART_RX连接至PC的USB转串口模块的TX
3. 开发环境配置
3.1 STM32CubeMX工程创建
-
打开STM32CubeMX,选择"New Project"
-
在MCU选择器中输入"STM32F103RCT6"并确认
-
配置时钟树:
- 选择外部晶振(HSE)作为时钟源
- 设置系统时钟为72MHz
- 配置APB1总线时钟为36MHz(USART时钟源)
-
外设配置:
- 启用USART1:
- Mode: Asynchronous
- Baud Rate: 115200
- Word Length: 8 Bits
- Parity: None
- Stop Bits: 1
- 配置GPIO:
- 根据LCD接口需求配置相应GPIO为输出模式
- 建议设置为推挽输出,速度Medium
- 启用USART1:
-
生成代码:
- Toolchain/IDE选择MDK-ARM V5
- 勾选"Generate peripheral initialization as a pair of .c/.h files"
3.2 Keil5工程设置
-
打开CubeMX生成的工程
-
配置目标选项:
- Target选项卡:确认芯片型号为STM32F103RC
- Output选项卡:勾选"Create HEX File"
- Debug选项卡:选择适合的调试器(如ST-Link)
-
添加LCD驱动代码:
- 在工程中新建lcd.c和lcd.h文件
- 实现LCD初始化、写命令、写数据等基本函数
-
添加串口接收处理:
- 在usart.c中重写HAL_UART_RxCpltCallback回调函数
- 实现数据接收缓冲和显示逻辑
4. LCD驱动实现
4.1 LCD初始化序列
一个典型的1602 LCD初始化过程如下:
c复制void LCD_Init(void) {
HAL_Delay(50); // 等待LCD上电稳定
// 4位模式初始化序列
LCD_WriteCmd(0x33);
LCD_WriteCmd(0x32);
LCD_WriteCmd(0x28); // 4位模式,2行显示,5x8点阵
LCD_WriteCmd(0x0C); // 显示开,光标关,闪烁关
LCD_WriteCmd(0x06); // 地址递增,不移屏
LCD_WriteCmd(0x01); // 清屏
HAL_Delay(2);
}
4.2 LCD基本操作函数
实现LCD的基本操作函数是项目成功的关键:
c复制// 写命令函数
void LCD_WriteCmd(uint8_t cmd) {
HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_RESET);
LCD_WriteByte(cmd);
}
// 写数据函数
void LCD_WriteData(uint8_t data) {
HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_SET);
LCD_WriteByte(data);
}
// 4位模式写字节函数
static void LCD_WriteByte(uint8_t byte) {
// 写高4位
HAL_GPIO_WritePin(LCD_D4_GPIO_Port, LCD_D4_Pin, (byte>>4)&0x01);
HAL_GPIO_WritePin(LCD_D5_GPIO_Port, LCD_D5_Pin, (byte>>5)&0x01);
HAL_GPIO_WritePin(LCD_D6_GPIO_Port, LCD_D6_Pin, (byte>>6)&0x01);
HAL_GPIO_WritePin(LCD_D7_GPIO_Port, LCD_D7_Pin, (byte>>7)&0x01);
LCD_EnablePulse();
// 写低4位
HAL_GPIO_WritePin(LCD_D4_GPIO_Port, LCD_D4_Pin, byte&0x01);
HAL_GPIO_WritePin(LCD_D5_GPIO_Port, LCD_D5_Pin, (byte>>1)&0x01);
HAL_GPIO_WritePin(LCD_D6_GPIO_Port, LCD_D6_Pin, (byte>>2)&0x01);
HAL_GPIO_WritePin(LCD_D7_GPIO_Port, LCD_D7_Pin, (byte>>3)&0x01);
LCD_EnablePulse();
HAL_Delay(1);
}
// 使能脉冲函数
static void LCD_EnablePulse(void) {
HAL_GPIO_WritePin(LCD_EN_GPIO_Port, LCD_EN_Pin, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(LCD_EN_GPIO_Port, LCD_EN_Pin, GPIO_PIN_RESET);
}
5. 串口通信实现
5.1 串口初始化与配置
在CubeMX中配置USART1为异步模式,波特率115200,8位数据位,无校验位,1位停止位。生成的初始化代码如下:
c复制static void MX_USART1_UART_Init(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK) {
Error_Handler();
}
}
5.2 串口接收中断处理
为了实现串口数据的实时接收和处理,我们需要使用中断模式:
c复制// 在main.c中定义接收缓冲区
uint8_t uart_rx_buf[64];
uint8_t uart_rx_len = 0;
// 在main函数初始化后启动接收
HAL_UART_Receive_IT(&huart1, &uart_rx_buf[uart_rx_len], 1);
// 实现接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
// 处理接收到的字符
if(uart_rx_buf[uart_rx_len] == '\n' || uart_rx_len >= sizeof(uart_rx_buf)-1) {
// 换行符或缓冲区满,处理完整的一行
uart_rx_buf[uart_rx_len] = '\0'; // 添加字符串结束符
LCD_DisplayString(uart_rx_buf); // 在LCD上显示
uart_rx_len = 0;
} else {
uart_rx_len++;
}
// 重新启动接收
HAL_UART_Receive_IT(&huart1, &uart_rx_buf[uart_rx_len], 1);
}
}
6. 系统整合与功能实现
6.1 主程序逻辑设计
主程序的逻辑相对简单,主要包括初始化、数据显示和串口通信三部分:
c复制int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
LCD_Init();
LCD_DisplayString("System Ready");
// 启动串口接收中断
HAL_UART_Receive_IT(&huart1, &uart_rx_buf[uart_rx_len], 1);
while (1) {
// 主循环中可以添加其他功能
HAL_Delay(100);
}
}
6.2 LCD显示功能实现
实现一个完整的字符串显示函数需要考虑LCD的显示特性:
c复制void LCD_DisplayString(char *str) {
LCD_WriteCmd(0x01); // 清屏
HAL_Delay(2);
uint8_t line = 0;
uint8_t pos = 0;
while(*str && line < 2) { // 最多显示2行
if(*str == '\n') {
line++;
pos = 0;
str++;
continue;
}
// 设置显示位置
uint8_t addr = (line == 0) ? (0x80 + pos) : (0xC0 + pos);
LCD_WriteCmd(addr);
// 显示字符
LCD_WriteData(*str);
pos++;
str++;
// 每行最多16个字符
if(pos >= 16) {
line++;
pos = 0;
}
}
}
7. 调试与优化技巧
7.1 常见问题排查
在实际开发中,可能会遇到以下典型问题:
-
LCD无显示:
- 检查背光是否亮起
- 用示波器检查EN使能信号是否有脉冲
- 确认初始化序列是否正确执行
-
显示乱码:
- 检查数据线连接是否正确
- 确认是否选择了正确的显示模式(4位/8位)
- 检查时序延迟是否足够
-
串口通信失败:
- 检查波特率设置是否匹配
- 确认TX/RX线是否交叉连接
- 检查地线是否共地
7.2 性能优化建议
-
减少延迟:
- 将HAL_Delay替换为精确的定时器延时
- 优化LCD写操作时序,使用最小必要延迟
-
缓冲区管理:
- 实现环形缓冲区处理串口数据
- 添加数据校验机制
-
显示优化:
- 实现局部刷新,避免全屏刷新
- 添加显示滚动功能
8. 项目扩展与进阶应用
8.1 图形化显示升级
如果需要更丰富的显示效果,可以考虑升级到12864图形LCD:
-
硬件修改:
- 更换为ST7920控制的12864 LCD
- 可能需要调整接口(并行/串行)
-
软件调整:
- 实现图形绘制函数(点、线、矩形等)
- 添加汉字字库支持
8.2 多协议支持
扩展串口协议支持,增加实用性:
-
实现Modbus RTU协议:
- 添加CRC校验
- 实现功能码处理
-
支持自定义协议:
- 定义帧头、长度、校验等字段
- 实现协议解析器
8.3 低功耗优化
对于电池供电设备,可进行低功耗优化:
-
硬件层面:
- 使用低功耗LDO
- 添加LCD背光控制电路
-
软件层面:
- 实现动态时钟调节
- 添加睡眠模式唤醒机制