告别Hello World:用STM32F103C8T6和LCD1602做个桌面温湿度计(附完整代码)
从零打造桌面温湿度监测站STM32F103与LCD1602实战指南在创客的世界里每个电子元件都像一块积木而STM32微控制器就是连接这些积木的万能胶。今天我们将用STM32F103C8T6这块蓝色药丸搭配经典的LCD1602显示屏打造一个既实用又能彰显极客精神的桌面温湿度监测站。不同于简单的Hello World演示这个项目将完整呈现从传感器数据采集到可视化显示的全过程让你体验真实产品开发的乐趣。1. 硬件选型与系统架构1.1 核心组件解析我们的温湿度监测站由三大核心模块构成STM32F103C8T672MHz主频的Cortex-M3内核MCU绰号蓝色药丸以极高的性价比成为嵌入式开发的瑞士军刀LCD1602液晶屏16字符×2行的经典字符型LCD无需复杂驱动电路DHT11传感器低成本数字温湿度传感器±2℃精度适合室内环境监测提示DHT11虽然精度一般但响应速度快1Hz采样率、接口简单单总线协议非常适合入门级项目。1.2 系统连接方案采用4位数据模式连接LCD1602可节省4个GPIO引脚。完整接线如下表所示STM32引脚LCD1602引脚功能说明PA0V0对比度调节PA1RS数据/命令选择PA2E使能信号PA4-PA7D4-D74位数据总线PB6DHT11 DATA温湿度数据线3.3VVDD电源正极GNDVSS/K电源地/背光负极电源注意事项LCD1602最佳工作电压为5V但STM32F103的GPIO耐压5V可直接驱动若屏幕对比度不佳可通过10K电位器调节V0引脚电压2. 开发环境搭建2.1 工具链配置推荐使用以下开发工具组合STM32CubeIDEST官方免费IDE集成CubeMX配置工具STM32CubeProgrammer烧录调试工具PuTTY串口调试终端安装步骤从ST官网下载STM32CubeIDE安装包安装时勾选STM32F1系列支持包安装完成后创建新工程选择STM32F103C8系列2.2 工程关键配置在CubeMX中需要进行以下关键设置// GPIO配置 LCD_RS_Pin PA1; // 推挽输出 LCD_E_Pin PA2; // 推挽输出 LCD_D4_Pin PA4; // 推挽输出 LCD_D5_Pin PA5; // 推挽输出 LCD_D6_Pin PA6; // 推挽输出 LCD_D7_Pin PA7; // 推挽输出 // 定时器配置 TIM2用于DHT11时序控制 TIM3用于LCD延时函数基准3. LCD1602驱动开发3.1 4位模式初始化序列LCD1602的4位模式初始化需要严格遵循以下步骤上电等待15ms以上发送0x03并等待4.1ms再次发送0x03并等待100us第三次发送0x03此时检测总线宽度切换至4位模式发送0x02发送功能设置命令0x284位模式2行显示5×8点阵显示控制命令0x0C显示开光标关清屏命令0x01输入模式设置0x06地址自动递增对应的初始化函数实现void LCD_Init(void) { HAL_Delay(20); // 上电延时 // 三次0x03写入8位模式 LCD_Write4Bits(0x03); HAL_Delay(5); LCD_Write4Bits(0x03); HAL_Delay(1); LCD_Write4Bits(0x03); HAL_Delay(1); // 切换至4位模式 LCD_Write4Bits(0x02); HAL_Delay(1); // 功能设置 LCD_SendCmd(0x28); // 显示控制 LCD_SendCmd(0x0C); // 清屏 LCD_SendCmd(0x01); // 输入模式设置 LCD_SendCmd(0x06); }3.2 核心驱动函数使能信号触发void LCD_EnablePulse(void) { HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_SET); Delay_us(10); HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_RESET); Delay_us(10); }4位数据写入void LCD_Write4Bits(uint8_t data) { HAL_GPIO_WritePin(LCD_D4_GPIO_Port, LCD_D4_Pin, (data 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(LCD_D5_GPIO_Port, LCD_D5_Pin, (data 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(LCD_D6_GPIO_Port, LCD_D6_Pin, (data 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(LCD_D7_GPIO_Port, LCD_D7_Pin, (data 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); LCD_EnablePulse(); }完整命令发送void LCD_SendCmd(uint8_t cmd) { HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_RESET); LCD_Write4Bits(cmd 4); // 高四位 LCD_Write4Bits(cmd 0x0F); // 低四位 if(cmd 0x01 || cmd 0x02) { HAL_Delay(2); // 清屏和归位需要额外延时 } }4. DHT11温湿度采集4.1 单总线协议解析DHT11采用单总线通信协议其时序要求严格主机启动信号拉低总线至少18ms然后拉高20-40us等待响应从机响应从机拉低80us然后拉高80us数据传输每位数据以50us低电平开始高电平26-28us表示0高电平70us表示1典型数据读取函数实现uint8_t DHT11_ReadByte(void) { uint8_t data 0; for(int i0; i8; i) { while(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin)); // 等待50us低电平结束 Delay_us(30); // 延时30us后检测电平状态 data 1; if(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin)) { data | 1; while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin)); // 等待高电平结束 } } return data; }4.2 数据校验与处理DHT11传输的5字节数据格式字节0湿度整数部分字节1湿度小数部分DHT11固定为0字节2温度整数部分字节3温度小数部分DHT11固定为0字节4校验和前4字节和完整读取函数uint8_t DHT11_ReadData(float *temp, float *humi) { uint8_t buf[5] {0}; // 启动信号 HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET); HAL_Delay(18); HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET); Delay_us(30); // 检测响应 if(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin)) { uint32_t timeout 10000; while(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) timeout--); timeout 10000; while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) timeout--); // 读取数据 for(int i0; i5; i) { buf[i] DHT11_ReadByte(); } // 校验 if(buf[4] (buf[0]buf[1]buf[2]buf[3])) { *humi buf[0]; *temp buf[2]; return 1; } } return 0; }5. 系统集成与优化5.1 主程序逻辑设计采用状态机架构实现非阻塞式设计typedef enum { STATE_INIT, STATE_READ_SENSOR, STATE_UPDATE_DISPLAY, STATE_DELAY } SystemState; SystemState currentState STATE_INIT; uint32_t lastUpdateTime 0; void System_Run(void) { static float temperature 0, humidity 0; switch(currentState) { case STATE_INIT: LCD_Init(); currentState STATE_READ_SENSOR; break; case STATE_READ_SENSOR: if(DHT11_ReadData(temperature, humidity)) { currentState STATE_UPDATE_DISPLAY; } else { // 读取失败处理 currentState STATE_DELAY; } break; case STATE_UPDATE_DISPLAY: LCD_Clear(); LCD_SetCursor(0, 0); LCD_Printf(Temp:%.1fC, temperature); LCD_SetCursor(0, 1); LCD_Printf(Humi:%.0f%%, humidity); lastUpdateTime HAL_GetTick(); currentState STATE_DELAY; break; case STATE_DELAY: if(HAL_GetTick() - lastUpdateTime 2000) { currentState STATE_READ_SENSOR; } break; } }5.2 显示优化技巧字符闪烁抑制void LCD_UpdateDisplay(float temp, float humi) { static char tempStr[16], humiStr[16]; snprintf(tempStr, sizeof(tempStr), Temp:%.1fC, temp); snprintf(humiStr, sizeof(humiStr), Humi:%.0f%%, humi); // 仅更新变化的部分 LCD_SetCursor(0, 0); LCD_Print(tempStr); LCD_SetCursor(0, 1); LCD_Print(humiStr); }单位符号自定义// 在初始化时创建自定义字符 void CreateCustomChars(void) { // 摄氏度符号 uint8_t degreeChar[8] {0x0E,0x0A,0x0E,0x00,0x00,0x00,0x00,0x00}; LCD_SendCmd(0x40); // CGRAM地址设置 for(int i0; i8; i) { LCD_SendData(degreeChar[i]); } }5.3 外壳设计与电源管理虽然本文聚焦电子部分但一个完整的项目还需要考虑3D打印外壳使用FreeCAD设计简约外壳预留屏幕窗口和传感器通风孔锂电池供电采用TP4056充电模块升压电路实现USB充电和5V输出低功耗优化在状态机中增加休眠状态调整DHT11采样频率为每分钟2次关闭不必要的外设时钟6. 项目扩展方向这个基础框架可以衍生出多种实用变体历史数据记录添加AT24Cxx系列EEPROM存储历史数据实现简单的高低温度报警功能无线传输版本替换为ESP8266/ESP32平台通过MQTT协议上传数据到Home Assistant多传感器融合增加BH1750光照传感器添加BMP280气压传感器实现环境综合监测交互式界面添加旋转编码器作为输入设备实现多页面显示切换// 示例简单的多页面显示实现 typedef enum { PAGE_MAIN, PAGE_HISTORY, PAGE_SETTINGS } DisplayPage; void UpdateDisplay(DisplayPage page) { switch(page) { case PAGE_MAIN: // 显示温湿度 break; case PAGE_HISTORY: // 显示历史极值 break; case PAGE_SETTINGS: // 显示配置菜单 break; } }在完成这个项目后最让我惊喜的是LCD1602在4位模式下的稳定表现。虽然初始化时序要求严格但一旦正确配置这个老牌显示屏依然能可靠工作数年。一个实际开发中的经验是当屏幕出现乱码时首先检查使能信号E的脉冲宽度是否足够其次确认V0对比度调节是否在合适电压。