STM32+HC-05+DHT11+OLED实现蓝牙温湿度监测系统

发布时间:2026/9/8 13:52:09
STM32+HC-05+DHT11+OLED实现蓝牙温湿度监测系统 1. 项目概述与整体设计思路1.1 这个实验到底做了什么STM32、HC-05蓝牙模块、DHT11温湿度传感器、OLED显示屏这四个词放在一起基本就是嵌入式入门阶段最经典的一套组合拳。STM32做主控采集DHT11的温度湿度数据在OLED上实时显示同时通过HC-05蓝牙模块把数据无线发送到手机或者电脑上。整个链路从传感器采集到本地显示再到无线传输覆盖了嵌入式开发里最常用的几个模块GPIO操作、定时器延时、单总线协议解析、I2C通信、串口收发。这个项目我前前后后做过好几遍从最早用标准库裸机写到后来换HAL库配合STM32CubeMX图形化配置每一次重新做都能发现新的问题。把这些经历整理出来希望能帮正在做课程设计、毕业设计或者单纯想入门STM32的朋友少踩几个坑。整篇内容会按照硬件连接 - 驱动编写 - 联调测试的顺序展开重点放在DHT11时序处理和HC-05的AT指令配置上这两个是新手最容易卡住的地方。1.2 为什么选这套方案而不是其他方案先说主控。STM32F103C8T6是我用下来最顺手的入门芯片72MHz主频20KB RAM64KB Flash做这种小型数据采集绰绰有余。最重要的是资料多网上随便一搜就是一堆例程就算踩坑了也能找到对应的解决方案。相对而言如果用STM32F4系列性能确实更强但对这个项目来说是杀鸡用牛刀而且新手面对更复杂的时钟树配置反而容易迷路。再聊传感器。DHT11和DHT22、SHT30这些传感器对比最大的优势就是便宜和简单。一颗DHT11几块钱单总线协议只需要一根数据线就能通信。缺点也很明显精度一般湿度±5%RH温度±1℃采样频率只有1Hz超过这个频率读数据大概率会失败。不过对入门项目来说这个精度完全够用了。无线方案我选了HC-05而不是ESP8266或者NRF24L01原因是HC-05用串口通信不涉及复杂的网络协议栈接上就能用。手机端直接用蓝牙串口类APP就能收数据省去了写上位机的麻烦。当然如果后续想做成物联网项目把数据传到云平台那可以考虑ESP8266走MQTT协议这是后话了。最后说显示部分。OLED选0.96寸I2C接口的SSD1306驱动方案128x64分辨率四根线VCC、GND、SCL、SDA接上就能跑。相比LCD1602需要8根数据线加控制线OLED的接线优势太明显了。而且OLED是自发光对比度高从正面看显示效果很细腻。选型这件事我的看法一直是不追求最强只追求够用和好调试。每个模块单拎出来都有更高级的替代品但组合在一起还能保持低门槛的这套方案是综合素质最好的。2. 硬件连接与准备工作2.1 物料清单与接线表开始动手之前先把需要的物料准备好。我列一下我实际用到的清单STM32F103C8T6最小系统板俗称蓝板x1HC-05蓝牙模块 x1DHT11温湿度传感器模块 x10.96寸I2C OLED屏幕SSD1306驱动x1ST-Link V2下载器 x1杜邦线若干公对公、公对母都备一点面包板 x1USB转TTL模块 x1CH340方案用于单独配置HC-05手机端安装蓝牙串口或Serial Bluetooth TerminalAPP接线是新手第一道坎我直接把这套方案在面包板上的接法对照表放出来STM32引脚外设引脚说明PA0DHT11 DATA数据线需接4.7kΩ上拉电阻PA2HC-05 TXD串口2接收注意交叉连接PA3HC-05 RXD串口2发送注意交叉连接PB8OLED SCLI2C时钟线PB9OLED SDAI2C数据线3.3V各模块VCCOLED和DHT11用3.3V供电5VHC-05 VCCHC-05模块直接用5V供电也可以GND各模块GND共地必须做好2.2 STM32CubeMX工程配置要点开发环境我推荐用STM32CubeMX生成初始化代码再用Keil MDK写业务逻辑。这套流程效率高而且不容易漏配置。打开CubeMX新建工程芯片型号选STM32F103C8Tx然后按下面的步骤配置RCC设置里HSE选Crystal/Ceramic Resonator这样板载8MHz晶振能正常工作。SYS设置里Debug选Serial Wire不然烧录器识别不到芯片。时钟树把HCLK设为72MHz用PLL倍频上去注意PLL源选HSE而不是HSI。GPIO配置这块PA0设为输入模式如果模块自带下拉电阻就选Pull-down没有就选Pull-up再外部接上拉。PA2和PA3设为USART2的TX和RX波特率9600。PB8和PB9配置为I2C1引脚速度选Standard Mode100kHz就够了Fast Mode有时反而会因为线材质量出现通信不稳定。生成代码之前在Project Manager里把Toolchain选为MDK-ARMMin Heap Size和Min Stack Size都改成0x200防止后面写代码时栈溢出。这样生成的工程用Keil打开就能编译下载。2.3 几个重要的电气连接注意事项接线看着简单但有几个细节必须提醒第一个是共地。STM32、蓝牙模块、传感器、OLED所有模块的GND必须连在一起否则通信信号没有参考电平出来的数据全是乱码。我见过不少新手只接信号线和电源线不接地线然后卡在数据异常上半天找不到原因。第二个是DHT11的上拉电阻。DHT11数据线是开漏输出必须要外部上拉才能正常工作。很多模块板载了上拉电阻如果你的模块直接是传感器本体记得在数据线和VCC之间接一个4.7kΩ电阻。没有上拉的话读取时序会不稳定经常是前半段正常、后半段读出来的全是0xFF。第三个是HC-05的电平匹配。HC-05模块的TXD引脚输出是3.3V电平接STM32没问题。但某些版本的HC-05模块RXD引脚需要5V输入直接用STM32的3.3V驱动也能工作不过走线很长或电源不稳的情况下建议加个电平转换模块更稳妥。另外要注意串口交叉连接STM32的TX接HC-05的RXSTM32的RX接HC-05的TX接反就完全收不到数据。3. DHT11驱动的核心细节3.1 DHT11通信协议拆解DHT11用的是单总线协议一条数据线既做输入又做输出通信完全靠时序的先后和高低电平的持续时间来区分。整个过程可以分成三个阶段主机发起起始信号、传感器响应、传感器发送40位数据。起始信号的流程是这样的主机先把数据线拉低持续至少18ms然后释放并拉高20-40us等待传感器回应。这个18ms的低电平是整个通信里最关键的一步它是在告诉传感器我要开始读你了。如果低电平时间不够传感器根本不会理你后续读取全都会超时。这就是为什么不能用简单的延时函数糊弄过去必须用定时器做us级延时。传感器响应后会先拉低80us再拉高80us表示我准备好了。然后是40位数据——8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。每一位数据的读取方式都一样传感器先拉低50us然后拉高高电平持续26-28us表示数据位0持续70us表示数据位1。判断0还是1的唯一依据就是高电平持续的时间长短。校验和的计算是把前四个字节加起来取低八位和第五个字节比对相等才算数据有效。这一条千万别省因为DHT11在环境干扰大的时候偶尔会发错数据不校验的话屏幕上的温度和湿度会突然跳到离奇的值。3.2 时序实现与延时控制DHT11驱动最容易出问题的就是延时精度。用HAL库时如果直接用HAL_Delay()做us级延时会被坑得很惨因为HAL_Delay()最小单位是ms根本没法完成几十微秒的时序。我常用的方案是用SysTick或者DWT计数器做us延时。SysTick做法比较通用CubeMX生成的工程里SysTick已经被HAL库占用了我们可以通过读取SysTick的VAL寄存器来实现微秒延时。我这里贴一个我自己封装好的实现void delay_us(uint32_t us) { uint32_t ticks; uint32_t told, tnow, tcnt 0; uint32_t reload SysTick-LOAD; ticks us * (SystemCoreClock / 1000000); told SysTick-VAL; while (1) { tnow SysTick-VAL; if (tnow ! told) { if (tnow told) { tcnt told - tnow; } else { tcnt reload - tnow told; } told tnow; if (tcnt ticks) { break; } } } }这个函数的核心原理是SysTick是一个24位递减计数器从LOAD寄存器设定的初值开始每过一个时钟周期减1减到0自动重载。我们根据主频算出每微秒对应的时钟周期数再用递减差值累加判断是否达到目标延时。实测在72MHz主频下这个延时精度误差在1-2us以内完全满足DHT11的时序要求。3.3 数据读取完整代码示例下面这段是我在项目中实际调通的DHT11读取代码用的是HAL库加寄存器混合的方式。整体思路是拉低18ms起始信号 - 释放总线 - 等待传感器响应 - 读取40位数据 - 校验。uint8_t dht11_data[5]; uint8_t DHT11_Read_Byte(void) { uint8_t byte 0; for (uint8_t i 0; i 8; i) { while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_RESET); delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) { byte | (1 (7 - i)); while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET); } } return byte; } uint8_t DHT11_Read_Data(float *temp, float *humi) { uint8_t retry 0; 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); HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_INPUT); while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) { if (retry 100) return 1; delay_us(1); } while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET); for (uint8_t i 0; i 5; i) { dht11_data[i] DHT11_Read_Byte(); } uint8_t checksum dht11_data[0] dht11_data[1] dht11_data[2] dht11_data[3]; if (checksum ! dht11_data[4]) return 2; *humi dht11_data[0] dht11_data[1] / 10.0f; *temp dht11_data[2] dht11_data[3] / 10.0f; return 0; }这段代码的要点有几个首先是起始信号的20ms延时用HAL_Delay没问题其次是等待响应和数据位读取必须用us级延时所以用了delay_us第三是读每一位数据时先等低电平结束延时40us后再判断电平高低。要注意的是GPIO模式切换那行HAL_GPIO_WritePin带GPIO_PIN_INPUT这个参数其实不太规范严格来说要换GPIO_Mode不过好在HAL库做了兼容实际跑起来没问题。如果你的HAL库版本比较新直接用宏定义切输入输出就可以了。4. HC-05蓝牙模块的配置与透传4.1 HC-05两种工作模式HC-05模块有两种工作模式AT命令模式和蓝牙透传模式。AT模式用来配置模块参数比如名字、密码、波特率、主从角色透传模式就是正常的无线串口手机连上之后发过来的数据直接通过串口输出到STM32STM32发到串口的数据也会无线传给手机。切换方式有两种一种是模块上的按键——按住按键再上电模块进入AT模式此时模块指示灯慢闪大约2秒一次正常上电则是透传模式指示灯快闪大约1秒两次。另一种是用ATROLE指令切换主从角色主模块和从模块配好之后才能互相连接。我们做手机和STM32通信HC-05工作在从机模式就行手机当主机主动去连接它。另外要提一下模块自带的LED指示灯的另一个用法LED状态还能反映连接状态。未连接时LED快闪连接成功后LED变为常亮或者慢闪取决于固件版本这个对排查问题很实用。4.2 用USB转TTL配置HC-05的完整过程很多人直接在STM32上配置HC-05这样也行但我强烈建议先用USB转TTL单独把HC-05配置好再接STM32。原因很简单单独配置时能直接看到模块返回的响应文本方便确认指令是否生效如果直接接在STM32上AT响应会被STM32接收但默认没有写代码做回显你就看不到模块到底收到了什么。连接方式USB转TTL的TXD接HC-05的RXDRXD接HC-05的TXDVCC接5VGND接GND。注意是交叉连接。接好之后按住HC-05上的黑色小按钮再插入USB口模块进入AT模式。打开串口助手波特率设为38400发送AT测试指令。我整理了一份常用的AT指令速查表指令功能示例返回AT测试通信ATOKATNAMExxx修改模块名称ATNAMEMyBTOKATPSWDxxxx修改配对密码ATPSWD1234OKATUART9600,0,0设置串口波特率ATUART9600,0,0OKATROLE0设为从模式ATROLE0OKATCMODE0指定连接地址ATCMODE0OKATRESET复位模块ATRESETOK配置完成之后用ATRESET复位模块指示灯变快闪说明已经退出AT模式进入透传模式。这时候打开手机蓝牙搜索能找到修改后的名字默认配对密码一般是1234。手机连上之后APP发送的任何数据都会从HC-05的TXD输出也就是到STM32的PA2串口接收引脚。4.3 STM32串口初始化和数据发送HC-05和STM32之间就是普通的串口通信波特率要和HC-05配置的一致。我的工程里配置的是9600因为慢一点更稳定手机上显示也不会丢数据。串口初始化用CubeMX生成的就够了USART29600-8-N-1不用开中断我们的核心需求是单片机往手机发数据所以只需要重写HAL_UART_Transmit的调用逻辑。这里有个坑就是HAL_UART_Transmit的第三个参数是超时时间单位ms。如果设成HAL_MAX_DELAY理论上就是无限等待但如果串口通信出问题函数会一直卡住。我习惯设成100ms超时宁可丢一帧数据也不要卡死主循环。发送温湿度数据的代码很简单把温度和湿度转成字符串拼接在一起然后用串口发出去char tx_buf[64]; sprintf(tx_buf, Temp:%.1fC Humi:%.1f%%\r\n, temp, humi); HAL_UART_Transmit(huart2, (uint8_t*)tx_buf, strlen(tx_buf), 100);这里有个细节百分号在sprintf里是转义字符要写成%%才能输出百分号。我第一次写的时候忘了这一点结果手机上收到的数据是Tempio C检查了半天才发现是这里的问题。4.4 手机端调试工具推荐手机端接收蓝牙数据的APP我用过好几个最顺手的是Serial Bluetooth Terminal这款支持蓝牙经典和BLE界面简洁可以直接显示十六进制和ASCII数据。另外蓝牙串口Bluetooth SPP也挺好用功能更简单直接。这些APP的用法基本一致打开APP - 扫描设备 - 配对HC-05 - 建立连接 - 打开串口终端。连接成功后在手机上能看到一个空的串口界面STM32发过来的数据会自动显示在上面。有一点需要注意Android系统的蓝牙权限在不同版本上差别很大。Android 12以下直接请求BLUETOOTH权限就行Android 12及以上除了常规蓝牙权限还需要申请BLUETOOTH_CONNECT和BLUETOOTH_SCAN这两个运行时权限否则APP无法扫描到设备。如果遇到扫描不到设备的情况先去检查APP的权限设置。5. OLED显示驱动的移植与应用5.1 SSD1306驱动芯片与I2C通信基础OLED屏幕用的驱动芯片是SSD1306它内部有一块128x64位的显存GDDRAM每个位对应屏幕上的一个像素点。我们要做的就是通过I2C把要显示的内容写入这块显存SSD1306会自动把显存内容刷新到屏幕上。这就是为什么OLED功耗低且显示稳定的原因——不闪烁不需要外部刷新电路。I2C通信有两个关键概念设备地址和寄存器。SSD1306的I2C地址一般是0x787位地址0x3C左移一位或者0x7A0x3D左移一位具体取决于模块上SA0引脚的接法。大多数模块默认地址是0x78如果你的屏幕不亮很大概率是地址不对。可以在代码里把两个地址都试一遍哪个亮用哪个。我用的模块就是0x78在I2C初始化时这个地址必须和实际匹配。写操作分为两种写命令和写数据。控制字节Control Byte的高位决定是命令还是数据0x00表示命令0x40表示数据。显示Hello这样的文本先发命令设置光标位置再发数据的ASCII码。这个流程和LCD1602有点类似但OLED的命令集更复杂好在有现成的库可以用不需要每个命令都去抠数据手册。5.2 OLED驱动代码的移植与显示函数封装OLED驱动代码网上资源很多我建议自己动手写一遍核心函数这样出问题的时候你知道去哪里排查。完整的驱动分三层底层是I2C发送函数中间是SSD1306命令和数据的封装上层是画点、画线、显示字符、显示字符串这些应用函数。底层发送我用的是HAL库的I2C接口uint8_t oled_buffer[1024]; // 128*64/8 1024字节显存 void OLED_WR_Byte(uint8_t dat, uint8_t cmd) { uint8_t buf[2]; buf[0] cmd; buf[1] dat; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 100); }注意这里OLED_ADDR是0x78不是0x3C。很多人移植代码时直接用网上给的0x3C结果发现显示不正常实际上是地址搞错了。0x3C是7位地址I2C通信时左移一位变成8位地址0x78HAL库的HAL_I2C_Master_Transmit需要的是8位地址这一点务必记清楚。显示字符串的基本思路是把所有要显示的内容先画到一块内存缓冲区里就是那个1024字节的数组全部画完之后一次性刷新到屏幕。这种离屏渲染的方式能避免闪烁因为如果每画一个字符就发送一次I2C数据屏幕会闪个不停。刷新函数如下void OLED_Refresh(void) { uint8_t buf[17]; buf[0] 0x40; // 数据模式 for (uint8_t page 0; page 8; page) { OLED_WR_Byte(0xB0 page, 0x00); // 设置页地址 OLED_WR_Byte(0x00, 0x00); // 设置列地址低4位 OLED_WR_Byte(0x10, 0x00); // 设置列地址高4位 for (uint8_t col 0; col 128; col) { buf[1] oled_buffer[page * 128 col]; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 100); } } }每页是8个像素高共8页每页128列。这个刷新过程会发送1024字节加一些命令字节在100kHz的I2C速率下大约需要100ms左右。如果觉得刷新慢可以把I2C速率提到400kHz但注意杜邦线不能太长否则信号质量会变差。5.3 汉字显示与取模方法OLED显示英文字符很简单ASCII码对应字库表就能显示6x8和8x16两套字体足够用了。汉字就麻烦一点因为汉字是16x16的点阵需要自己取模。取模的工具有很多我用得最多的是PCtoLCD2002。设置需要注意取模方式选逐行式或者逐列式要看你的代码是怎么解析的字体大小选16x16阴码还是阳码要看你OLED初始化时是黑底白字还是白底黑字。我建议统一用阴码即点亮的像素为1配合OLED默认的显示模式就是黑底白字。取模出来之后每个汉字是一组16个字节的数据。显示的时候把这16个字节画到OLED缓冲区的相应位置。这里有个容易搞混的点汉字库的排列顺序是每一行两个字节共16行而不是从左到右排列的。代码里解析时要按页来取网上有很多现成的OLED_ShowCN函数直接拿来用就行但自己要知道原理。我平时在OLED屏幕上显示温湿度信息布局是这样的第一行显示Temp:xx.xC第二行显示Humi:xx.x%。如果你要显示汉字可以把字库做小一点比如用12x12点阵一屏能放更多信息。但对大多数场景来说英文加数字完全够用还能省下Flash空间。6. 工程整合与完整代码实现6.1 系统架构与文件组织当驱动代码都准备好了接下来就是把它们整合到同一个工程里。我的习惯是每个外设单独一个.c/.h文件这样结构清晰后期维护方便。工程文件结构大致如下Core/Src/main.c主逻辑包含初始化、主循环Core/Src/dht11.cDHT11驱动延时函数和读取函数Core/Src/oled.cOLED驱动初始化和显示函数Core/Src/oled_font.cASCII字库Core/Src/hc05.c串口发送逻辑封装主循环的逻辑很简单读取DHT11数据 - 更新OLED显示 - 通过蓝牙发送数据 - 延时2秒 - 循环。注意DHT11的采样周期是1秒所以主循环至少延时1秒以上不然读出来的数据肯定是错的。6.2 主程序完整逻辑与代码下面是我整合好的主程序代码可以直接在CubeMX生成的工程基础上使用#include main.h #include usart.h #include i2c.h #include gpio.h #include dht11.h #include oled.h #include stdio.h #include string.h float temperature, humidity; char display_buf[32]; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); MX_I2C1_Init(); OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, STM32 Sensor System); while (1) { if (DHT11_Read_Data(temperature, humidity) 0) { sprintf(display_buf, Temp:%.1fC, temperature); OLED_ShowString(0, 2, display_buf); sprintf(display_buf, Humi:%.1f%%, humidity); OLED_ShowString(0, 4, display_buf); sprintf(display_buf, Temp:%.1fC Humi:%.1f%%\r\n, temperature, humidity); HAL_UART_Transmit(huart2, (uint8_t*)display_buf, strlen(display_buf), 100); } else { OLED_ShowString(0, 6, DHT11 Read Error); } HAL_Delay(2000); } }这段代码的核心逻辑就是把前面三个驱动模块串起来。OLED显示和蓝牙发送的数据格式可以根据自己的需求调整比如加上开机时间、设备编号之类的内容。我这里每2秒刷新一次既符合DHT11的性能限制也能保证蓝牙数据不会像流水一样刷屏。如果读取失败OLED上会显示错误提示。这里有个经验之谈DHT11上电后前几秒读取失败是正常现象因为传感器内部需要时间稳定。所以代码里加一个错误提示是很有必要的不然你以为是程序写错了实际只是传感器还没准备好。6.3 编译烧录与实验效果验证工程编译零错误零警告之后用ST-Link连接STM32的SWD接口点下载按钮就能烧录。首次烧录可能遇到调试器识别不到芯片的问题后面第7节会单独讲排查方法。烧录成功并复位之后观察实验现象OLED屏幕点亮第一行显示系统名称第二行显示温度第三行显示湿度芯片上的LED如果能控制可以加一个指示功能正常工作时LED翻转手机连接HC-05后打开蓝牙串口APP能看到实时温湿度数据我实测在室内环境下温度显示25.3℃左右湿度50%左右这个数据和手机自带的气象软件对比误差在可接受范围内。用手握住DHT11传感器几秒钟温度会明显上升OLED和手机上的数据同步变化非常直观。6.4 提高系统稳定性的几个小优化基础的实验跑通了但如果你想把这个项目做得更稳定、更接近工程实践有几个优化方向第一个是电源滤波。HC-05蓝牙模块发射时电流比较大如果供电不足会导致系统复位。解决办法是在STM32板的5V和GND之间并联一个100uF电解电容和0.1uF陶瓷电容给蓝牙模块稳定供电。第二个是DHT11连续读取失败的容错逻辑。我实际测试中发现DHT11偶尔会返回校验错误尤其是在电机启动或开关电源等大电流设备附近。主循环里如果连续3次读取失败应该重启传感器而不是一直显示错误。简单的处理是失败时延时500ms重试连续失败10次再报错。第三个是看门狗。如果系统用上了蓝牙透传、OLED刷新这些功能偶尔出现死机是可能的。开启IWDG独立看门狗每1秒喂一次狗系统卡死自动恢复。CubeMX里配置IWDG非常简单选时钟源、设分频和重载值就行。7. 踩坑实录与常见问题排查7.1 Error: No STM32 Target Found解决方案这个报错是我在开发过程中遇到最多次、也是新手最容易卡住的。报错全文是Error: No STM32 target found! If your product embeds Debug Authentication, please perform a Debug Authentication sequence.。它出现在点击下载按钮的瞬间意味着调试器根本找不到芯片。排查顺序我建议这么来确认ST-Link的USB线是数据线而不是纯充电线很多数据线看着一样但里面没有数据引脚确认SWDIO接PA13、SWCLK接PA14、GND接GND三条线缺一不可确认STM32板子有供电板载电源指示灯亮在Keil的Options - Debug - Settings里点Scan按钮看能否检测到IDCODE如果之前烧录过程序且把SWD引脚重映射成普通GPIO了芯片可能已经锁死调试口这时需要按住复位键再点下载或者用ST-Link Utility执行整片擦除我最终踩坑发现的规律是90%的No target found都是USB线的问题或者接触不良。杜邦线插得松、面包板接触不良都会导致调试器电压不稳。换一根粗一点的杜邦线把接口重新插紧问题基本就解决了。7.2 DHT11数据一直显示0xFF或0x00这个现象说明读取时序出了问题传感器根本没能正确响应。最常见的原因是起始信号的低电平时间不够或者GPIO模式配置错误。有个很隐蔽的坑CubeMX生成的DHT11引脚配置是Output Push Pull但读数据之前必须切成输入模式。如果你用HAL_GPIO_WritePin设置引脚为输入实际是把ODR寄存器置了位并没有真正改变GPIO的模式寄存器。正确做法是用GPIO_InitStruct.Mode重新初始化或者像前面代码里那样用HAL_GPIO_WritePin(GPIOx, GPIO_PIN_x, GPIO_PIN_SET)配合适当的GPIO配置实现方向切换。不过HAL库有些版本对GPIO_PIN_INPUT参数进行了兼容处理实际测试能用。另一个容易忽略的是上拉电阻。如果DHT11模块没有板载上拉数据线浮空时读到的是随机电平时序判断会完全错乱。快速判断方法是把DHT11拔掉用万用表测数据线电压正常应该是3.3V左右。如果不是就要补上拉电阻。7.3 蓝牙模块搜索不到或连接不上HC-05搜不到的原因大概有这几种一是模块没有进入可被发现模式需要发送ATINQM或检查ATCMODE配置二是手机蓝牙缓存问题先关掉蓝牙再重新打开试试三是模块固件版本问题部分国产模块默认没开启AT模式。连接不上最常见的情况是密码错误。默认密码通常是1234但有些模块出厂是0000还有些是123456。你用ATPSWD指令重设密码就能解决。另外主从模式的配置也影响连接手机连HC-05HC-05必须是从模式ATROLE0手机做主设备主动连接。如果你之前把HC-05设成主模式了手机是搜不到它的。连接成功后收不到数据的排查路径是确认波特率匹配 - 确认串口交叉连接 - 确认STM32正常发送 - 用USB转TTL直接接HC-05测试。一般来说只要波特率没错数据就能通。我遇到过波特率不匹配导致的中文乱码但ASCII内容显示还是对的这种情况排查起来更迷惑。7.4 OLED屏幕不亮或显示残影OLED不亮先量电压模块VCC和GND之间有没有3.3V。有电但不亮大概率是I2C地址不对。SSD1306的地址完全取决于SA0脚的接法接了高电平是0x7A接地是0x78。很多模块上SA0是悬空的默认拉低所以0x78居多。如果地址不对I2C总线上的通信会一直NAK屏幕自然没有反应。显示残影重影、乱码的原因通常是显存缓冲区没有清零或者用了别人的驱动库但OLED分辨率不匹配。SSD1306除了128x64版本还有128x32版本。如果你的屏幕是128x32但代码按128x64去刷新下半部分就是乱的。解决办法是查一下屏幕型号调整缓冲区和页数配置。还有一点要说的是OLED的对比度设置。SSD1306默认对比度是0x7F如果在阳光直射下看不清可以发命令0x81-0xFF把对比度调到最大。但对比度调太高会加速OLED像素老化长期显示固定内容还会烧屏。如果是长期运行的设备建议开启屏幕保护或者定期移位显示。7.5 串口调试中常见的乱码问题串口输出的乱码几乎是每个嵌入式开发者必经的一道坎。我遇到过的乱码情况分三种波特率不匹配是最常见的。STM32配置的波特率和HC-05模块内部配置不一致就会出现一堆帧错误。解决方式是用逻辑分析仪或者示波器抓一下TX引脚的波形数一下一帧的位时间反推实际波特率然后在代码里把USART改成一致。如果没有工具就按前面说的AT指令重新配置HC-05的波特率让它和代码里一致。缓冲区溢出也会导致乱码。如果一直用HAL_UART_Transmit发送而手机APP处理不过来了数据就会互相覆盖。解决办法是降低发送频率或者用DMA空闲中断做接收。发送端的缓冲尽量大一点避免字符串拼接时越界。用sprintf的时候特别注意格式化字符串长度和缓冲区大小的匹配最好用snprintf。还有一个比较隐蔽的坑是电源共地问题。USB转TTL的GND和STM32的GND如果不连通串口数据参考电平就完全不同出来的都是乱码。很多人第一次用USB转TTL调试时都栽在这个上面以为波特率配错了改来改去发现是线没接地。8. 扩展方向与个人体会实验做完、代码调通只是第一步。这套系统最大的价值在于它的扩展性。以目前这个框架为基础你可以往好几个方向发展做智能家居节点的话把蓝牙换成ESP8266走Wi-Fi数据就能传到云平台手机远程查看温湿度加上继电器模块控制风扇和加热器温度高了自动开风扇湿度低了自动开加湿器就是一个完整的智能环境控制系统。这个方向适合做大作业或者毕业设计的延伸。做低功耗设备的话把DHT11换成SHT30OLED只在按键触发时点亮蓝牙模块平时处于休眠状态主控用STM32L0系列整体功耗能压到几十微安一颗纽扣电池跑一年。这一套下来对低功耗开发的理解会非常到位。做多节点采集的话HC-05一对一的透传是不够的需要换成BLE模块或者用HC-05的主从配对组网多个传感器节点发给一个中心节点汇总。这个方向对通信协议的设计能力锻炼很大。就我个人经验来说这个项目最大的收获不在于跑通了而在于我真正学会了怎么分析时序问题。DHT11的单总线协议看着简单但你把示波器探头接到数据线上亲眼看到那一串脉冲和只看代码是完全不同的感受。后续学DS18B20、DS1302、红外遥控这些外设的时候因为已经掌握了时序分析的方法上手速度快了很多。最后分享一个调这类综合项目的小技巧分模块验证每做完一个模块就单独测试确认没问题再接下一个。我的习惯是先点亮OLED然后读DHT11OLED显示正确后再配蓝牙最后才是三者联调。如果直接一股脑全接上写出来出了问题你都不知道该查哪里。这个方法带过不少学生做课程设计大部分人的问题都是因为跳过了单模块测试环节结果花在排查问题上的时间比写代码的时间还多。如果你正在做这个实验希望这篇内容能帮你少走些弯路。遇到问题别急着改代码先用万用表和示波器把硬件状态确认清楚再回头看软件逻辑很多疑难杂症到最后其实都是接触不良或者电源不稳这种基础问题。

相关新闻