1. 硬件准备与环境搭建
第一次接触STM32和串口屏的时候,我也被各种接线和配置搞得晕头转向。后来发现只要把硬件环境搭建好,后面的工作就会顺利很多。这里我以STM32F103C8T6和陶晶驰T0系列串口屏为例,手把手带你完成准备工作。
核心硬件清单:
- STM32F103C8T6开发板(俗称"蓝莓派")
- 陶晶驰T0系列2.4寸串口屏
- 杜邦线若干(建议使用彩色线方便区分)
- USB转TTL模块(用于调试)
- 5V/2A电源适配器
注意:串口屏的供电很关键,电流不足会导致屏幕闪烁或无法正常工作。我实测用电脑USB口供电时经常出问题,后来改用独立电源就稳定了。
接线其实特别简单,记住"三线法"就行:
- 串口屏的5V接STM32的5V
- 串口屏的TX接STM32的PA10(RX)
- 串口屏的RX接STM32的PA9(TX)
- 串口屏的GND接STM32的GND
这里有个坑我踩过:有些开发板的PA9/PA10被USB接口占用了,如果发现串口通信异常,可以尝试改用其他串口比如USART2(PA2/PA3)。
2. 串口通信配置详解
串口配置是项目的核心,我花了三天时间才搞明白各种参数的含义。下面这段配置代码是我优化过的版本,加入了详细的注释:
void uart_init(u32 bound){ // 时钟配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); // GPIO初始化 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); // 中断配置 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 串口参数设置 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = bound; // 波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位 USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式 USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启接收中断 USART_Cmd(USART1, ENABLE); // 使能串口 }实际项目中我发现有几个关键点需要注意:
- 波特率一定要和屏幕保持一致(通常是9600或115200)
- 中断优先级设置不当会导致系统卡死
- GPIO模式配置错误是最常见的通信失败原因
3. 串口屏指令封装技巧
直接发送原始指令既麻烦又容易出错,我总结了一套高效的指令封装方法。先看最基本的发送函数:
// 发送字符串指令 void HMISendStr(const char *cmd) { while(*cmd) { USART_SendData(USART1, *cmd++); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } // 发送结束符 USART_SendData(USART1, 0xFF); USART_SendData(USART1, 0xFF); USART_SendData(USART1, 0xFF); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); }基于这个基础函数,我们可以封装更高级的功能:
// 清屏并设置背景色 void HMIClearScreen(uint16_t color) { char cmd[20]; sprintf(cmd, "cls %s", color == RED ? "RED" : color == GREEN ? "GREEN" : color == BLUE ? "BLUE" : "BLACK"); HMISendStr(cmd); } // 设置文本控件内容 void HMISetText(uint8_t id, const char *text) { char cmd[50]; sprintf(cmd, "t%d.txt=\"%s\"", id, text); HMISendStr(cmd); } // 页面切换 void HMIGotoPage(uint8_t page) { char cmd[10]; sprintf(cmd, "page %d", page); HMISendStr(cmd); }在实际项目中,我建议把常用指令都封装成函数,这样主程序会非常简洁:
HMIClearScreen(BLUE); HMISetText(0, "温度:25.6℃"); HMISetText(1, "湿度:60%");4. 完整项目实战演练
现在我们把所有知识点串联起来,实现一个完整的温湿度监控界面。假设我们已经用DHT11传感器获取了数据,重点看HMI部分的实现。
工程结构:
project/ ├── drivers/ │ ├── uart.c │ └── hmi.c ├── inc/ │ ├── uart.h │ └── hmi.h └── main.cmain.c中的关键代码:
int main(void) { // 初始化系统 SystemInit(); delay_init(); uart_init(9600); DHT11_Init(); // 初始化HMI界面 HMIClearScreen(WHITE); HMISetText(0, "智能环境监测系统"); HMISetText(1, "温度:--.-℃"); HMISetText(2, "湿度:--%"); HMIDrawLine(10, 40, 230, 40, BLUE); // 画分隔线 while(1) { if(DHT11_Read()) { char buf[20]; // 更新温度显示 sprintf(buf, "温度:%.1f℃", DHT11_GetTemp()); HMISetText(1, buf); // 更新湿度显示 sprintf(buf, "湿度:%d%%", DHT11_GetHum()); HMISetText(2, buf); } delay_ms(2000); // 2秒刷新一次 } }调试技巧:
- 先用串口助手测试指令是否正确
- 添加调试打印确认数据发送时机
- 使用逻辑分析仪抓取通信波形
- 遇到问题时先检查电源稳定性
我在这部分实现时遇到过一个典型问题:屏幕偶尔会显示乱码。后来发现是因为连续发送指令太快,解决方案是在关键指令之间添加10ms的延时。