STM32CubeMX+DHT11+OLED+蓝牙串口:构建一个无线环境监测终端
1. 项目背景与核心功能
这个无线环境监测终端项目,本质上是一个融合了传感器技术、嵌入式开发和无线通信的典型物联网应用。我最初做这个项目的动机很简单——工作室的植物总是养不活,想实时监控温湿度但又不希望被线缆束缚。通过STM32F103C8T6作为主控,搭配DHT11采集环境数据,OLED本地显示,再通过蓝牙将数据同步到手机,最终实现了在10米范围内自由移动监测的目标。
硬件组合的精妙之处在于:DHT11的单总线协议节省了IO口资源,0.96寸OLED通过I2C接口实现低功耗显示,而JDY-30蓝牙模块的透传模式让无线传输变得异常简单。实测下来,整套系统待机电流仅8.2mA,持续工作时也不过23mA,用500mAh的锂电池可以稳定工作48小时以上。
2. 硬件选型与电路设计
2.1 关键器件参数对比
| 器件 | 型号 | 通信接口 | 工作电压 | 关键特性 |
|---|---|---|---|---|
| 主控MCU | STM32F103C8T6 | 多种 | 3.3V | 72MHz Cortex-M3,64KB Flash |
| 温湿度传感器 | DHT11 | 单总线 | 3.3-5.5V | 精度±2℃/±5%RH,0.5s响应 |
| OLED显示屏 | SSD1306 | I2C | 3.3V | 128x64分辨率,0.96寸 |
| 蓝牙模块 | JDY-30 | UART | 3.3V | 蓝牙4.2,最大波特率115200bps |
2.2 电路连接要点
在实际焊接时,这几个细节需要特别注意:
- DHT11的数据线要加上拉电阻(4.7KΩ),我最初没加导致数据读取不稳定
- OLED的I2C地址通常是0x78,但有些厂家会做成0x7A,遇到显示异常时要先用I2C扫描工具确认
- 蓝牙模块的TX/RX要交叉连接:MCU的TX接蓝牙RX,MCU的RX接蓝牙TX
特别提醒:所有3.3V器件共地是关键!曾经因为忘记连接蓝牙模块的地线,导致数据传输时出现乱码,排查了整整一个下午。
3. STM32CubeMX工程配置
3.1 时钟树配置
在RCC配置中选择HSE(外部8MHz晶振),PLL倍频到72MHz。这里有个坑点:如果后续要用USB功能,必须保持48MHz的USB时钟,但我们这个项目不需要,所以直接最大频率运行即可。
3.2 外设参数设置
GPIO配置:
- PA1设置为GPIO_Output(DHT11数据线)
- PB6/PB7配置为I2C1_SCL/I2C1_SDA
- PA9/PA10配置为USART1_TX/USART1_RX
定时器配置: 使用TIM2生成精确延时,Prescaler设为71,Counter设为65535,这样每个计数正好是1μs(72MHz/72=1MHz)
I2C配置: 标准模式(100kHz)即可,实测SSD1306在400kHz下也能稳定工作,但需要缩短线长
USART配置: 蓝牙模块默认波特率9600,数据位8,无校验,停止位1。记得开启串口中断,方便后续扩展数据接收功能。
4. 传感器驱动开发
4.1 DHT11单总线协议解析
DHT11的时序要求非常严格,这里分享我的调试经验:
- 起始信号:拉低总线至少18ms后,再拉高20-40μs
void DHT11_Rst(void) { DHT11_IO_OUT(); HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET); delay_us(30); }- 数据读取技巧:
- 等待80μs低电平响应信号
- 每位数据前的50μs高电平是判断关键
- 高电平持续26-28μs表示"0",持续70μs表示"1"
4.2 数据校验机制
DHT11的40bit数据包含:
- 16bit湿度整数+小数
- 16bit温度整数+小数
- 8bit校验和(前四个字节相加)
校验代码示例:
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi = (buf[0]<<8) + buf[1]; *temp = (buf[2]<<8) + buf[3]; }5. OLED显示实现
5.1 屏幕初始化序列
SSD1306需要发送一系列初始化命令:
uint8_t CMD_Data[] = { 0xAE, 0x00, 0x10, 0x40, 0xB0, 0x81, 0xFF, 0xA1, 0xA6, 0xA8, 0x3F, 0xC8, 0xD3, 0x00, 0xD5, 0x80, 0xD8, 0x05, 0xD9, 0xF1, 0xDA, 0x12, 0xD8, 0x30, 0x8D, 0x14, 0xAF };5.2 中文显示方案
我采用的取模方式是逐行式,16x16点阵:
void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no) { uint8_t t; OLED_Set_Pos(x,y); for(t=0;t<16;t++) OLED_WR_DATA(Hzk[2*no][t]); OLED_Set_Pos(x,y+1); for(t=0;t<16;t++) OLED_WR_DATA(Hzk[2*no+1][t]); }6. 蓝牙数据传输优化
6.1 数据帧格式设计
为了提高传输可靠性,我自定义了简单的帧结构:
[HEADER(0xAA)][LENGTH][DATA][CHECKSUM]示例代码:
void Bluetooth_Send(uint8_t *data, uint16_t len) { uint8_t checksum = 0; uint8_t frame[50] = {0xAA, len}; memcpy(&frame[2], data, len); for(int i=0; i<len; i++) checksum ^= data[i]; frame[2+len] = checksum; HAL_UART_Transmit(&huart1, frame, len+3, 100); }6.2 手机端数据解析
在安卓APP中,可以通过以下方式处理数据流:
private StringBuilder mBuffer = new StringBuilder(); @Override public void onDataReceived(byte[] data) { String str = new String(data, StandardCharsets.UTF_8); mBuffer.append(str); int startIdx = mBuffer.indexOf("AA"); if(startIdx >= 0 && mBuffer.length() >= startIdx+4) { int length = Integer.parseInt(mBuffer.substring(startIdx+2, startIdx+4), 16); if(mBuffer.length() >= startIdx+4+length) { String payload = mBuffer.substring(startIdx+4, startIdx+4+length); processData(payload); mBuffer.delete(0, startIdx+4+length); } } }7. 系统整合与调试
7.1 主程序逻辑架构
int main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); // 外设初始化 OLED_Init(); while(DHT11_Init()) { printf("DHT11初始化失败!\r\n"); HAL_Delay(500); } // 主循环 while(1) { uint16_t temp, humi; if(DHT11_Read_Data(&temp, &humi) == 0) { // OLED显示 OLED_ShowTempHum(temp, humi); // 蓝牙发送 char buf[20]; sprintf(buf, "T:%d H:%d", temp>>8, humi>>8); Bluetooth_Send((uint8_t*)buf, strlen(buf)); } HAL_Delay(2000); } }7.2 常见问题排查
- DHT11无响应:
- 检查上拉电阻是否连接
- 用逻辑分析仪抓取时序波形
- 尝试降低通信速率(调整延时时间)
- OLED花屏:
- 确认I2C地址是否正确
- 检查电源是否稳定(可并联100nF电容)
- 重新初始化显示屏
- 蓝牙连接不稳定:
- 确保模块供电充足(电流≥50mA)
- 避开WiFi频段(尝试修改蓝牙信道)
- 添加简单的数据重传机制
这个项目最让我惊喜的是蓝牙模块的传输距离,在开阔场地实测能达到15米以上,完全满足家庭温室监测的需求。下次迭代准备加入锂电池管理功能,让设备可以完全无线化运行。
