STM32F1入门实战:DHT11温湿度采集与串口调试全解析

发布时间:2026/10/27 1:10:32
STM32F1入门实战:DHT11温湿度采集与串口调试全解析 1. 为什么STM32F1至今仍是入门嵌入式的首选如果你在电子论坛或者技术群里问一句新手学单片机选什么十个人里至少有六个会告诉你从STM32F1系列开始。这个答案从十年前延续到今天热度依然没有消退。STM32F1是意法半导体基于ARM Cortex-M3内核推出的32位微控制器家族最经典的型号如STM32F103C8T6也就是大家常说的蓝板或最小系统板几十块钱就能买到一块配套的教程、例程、开源项目铺天盖地。它解决的核心问题是让没有太多电子基础的人也能用相对低的成本、相对短的时间跑通从点灯到传感器采集再到通信控制的完整链路。我接触STM32F1是从一块STM32F103C8T6最小系统板开始的当时手头只有一个DHT11温湿度传感器和几根杜邦线。目标很简单把温湿度数据读出来显示在串口助手上。听起来不难但真正动手之后才发现光是时钟配置、GPIO模式选择、DHT11的单总线时序这几件事就足够让一个新手折腾好几天。也正因为如此我后来把踩过的坑、验证过的代码、以及那些教程里往往一笔带过的细节整理下来形成了这篇内容。这篇文章适合三类人第一类是刚学完C语言、想找一个能真正动手的32位平台练手的在校学生第二类是做硬件产品原型、需要快速验证传感器方案的工程师第三类是从51单片机转过来、想搞清楚STM32到底比8位机强在哪里的爱好者。我会围绕STM32F1这个平台结合DHT11温湿度采集这个具体场景把选型逻辑、开发环境搭建、GPIO与时钟原理、单总线时序实现、串口调试、常见故障排查这些内容讲透。读完之后你应该能独立完成一个STM32F1DHT11串口输出的完整小项目并且理解每一步背后的原因而不是照抄代码。2. STM32F1的家族谱系与选型逻辑2.1 从F103到F107型号后缀到底代表什么STM32F1系列并不是一个单一型号而是一个包含多个子系列的产品线。常见的子系列有STM32F100、STM32F101、STM32F102、STM32F103、STM32F105/107等。它们的内核都是Cortex-M3但主频、外设资源、存储容量、封装引脚数差别很大。以最热门的STM32F103为例它的主频是72MHz内置Flash从16KB到1MB不等SRAM从6KB到96KB封装从LQFP48到LQFP144都有。型号命名规则里STM32F103C8T6这串字符其实包含了大量信息F103是子系列C代表48引脚8代表64KB FlashT代表LQFP封装6代表工业级温度范围-40到85摄氏度。为什么新手普遍推荐C8T6这个型号原因很实际。第一48引脚足够引出常用的GPIO、USART、I2C、SPI、ADC做小项目完全够用第二64KB Flash和20KB SRAM对于跑裸机程序、驱动几个传感器来说绰绰有余第三它的价格极低国产替代品也很多烧了也不心疼第四社区资源最丰富你遇到的几乎任何问题网上都能搜到基于C8T6的讨论。相比之下F107带有以太网和USB OTG适合做网络通信类项目但对新手来说外设复杂度高调试成本也高不建议一上来就碰。选型的时候有一个容易被忽略的点Flash容量和SRAM容量要留出余量。很多人觉得64KB写不了多少代码但实际上如果你用了HAL库、加了printf重定向、再挂一个文件系统Flash占用会迅速逼近上限。我的经验是裸机项目至少留30%的Flash余量RTOS项目至少留50%。如果预算允许直接上C8T6的升级版RBT6128KB Flash、20KB SRAM会更从容。2.2 最小系统板上那些容易被忽视的电路拿到一块STM32F103C8T6最小系统板大多数人第一眼看到的是那颗芯片、一个Micro USB口、一个复位按键、一个BOOT跳线帽还有几排排针。但真正决定这块板子能不能稳定工作的是几个不起眼的电路细节。首先是供电STM32F1的VDD范围是2.0V到3.6V典型值3.3V。最小系统板通常通过USB的5V经过一个LDO稳压到3.3V这个LDO的压差和电流能力很关键。如果你外接的传感器或者显示屏功耗较大LDO发热会导致电压跌落芯片就会莫名其妙复位。其次是BOOT引脚。STM32F1的BOOT0和BOOT1决定了芯片上电后从哪里启动。BOOT0接低电平、BOOT1任意是从主Flash启动这是正常运行模式BOOT0接高电平、BOOT1接低是从系统存储器启动也就是串口下载模式。很多新手烧录失败就是因为BOOT跳线帽没有拨到正确位置。我的习惯是下载程序时把BOOT0拨到1按一下复位下载完成后再拨回0再按复位。虽然有些下载器支持自动控制BOOT引脚但手动操作最稳妥。还有一个细节是晶振。最小系统板通常板载一个8MHz的外部高速晶振HSE配合内部PLL倍频到72MHz。如果晶振旁边的负载电容不匹配或者晶振质量差系统时钟就起不来程序会卡在时钟初始化里。判断方法很简单用示波器测晶振引脚看有没有8MHz的正弦波。没有示波器的话可以写一个最简单的GPIO翻转程序如果LED闪烁频率明显不对或者根本不闪就要怀疑时钟配置。我遇到过一块板子晶振虚焊程序一直跑在内部8MHz RC振荡器上导致串口波特率偏差巨大收上来的全是乱码。2.3 开发工具链的选择Keil、IAR还是STM32CubeIDE工具链的选择直接决定了你后续开发的效率。目前主流的STM32F1开发环境有三类Keil MDK、IAR EWARM、STM32CubeIDE。Keil MDK在国内用户最多教程最全编译器优化做得不错但它是商业软件代码超过32KB就需要授权。IAR的编译效率很高调试功能强但界面相对复杂授权费用也高。STM32CubeIDE是ST官方推出的免费工具基于Eclipse和GCC集成了CubeMX配置工具可以直接生成初始化代码对新手非常友好。我个人的建议是如果你在学校或者公司已经有Keil的授权继续用Keil没问题生态成熟遇到问题好搜答案。如果你是个人学习、不想花钱直接上STM32CubeIDE它把引脚配置、时钟树、外设初始化都图形化了能帮你省掉大量查手册的时间。不过要注意CubeIDE生成的HAL库代码体积偏大对于Flash只有64KB的C8T6来说有时候会显得紧张。这时候可以改用LL库Low Layer它更接近寄存器操作代码效率高但需要你对底层有一定了解。还有一个绕不开的话题是下载器。ST-Link是官方调试器支持SWD接口能下载能调试价格也不贵是首选。J-Link性能更强但正版价格高盗版固件容易掉。串口下载通过BOOT0进入系统存储器是最省钱的方案只需要一个USB转TTL模块但只能下载不能调试而且每次都要手动拨BOOT跳线效率低。我的建议是既然要学STM32ST-Link是值得投资的几十块钱能让你少折腾很多。3. 用DHT11打通第一个传感器采集链路3.1 DHT11的单总线协议为什么容易读失败DHT11是一款廉价的温湿度传感器测量范围温度0到50摄氏度、湿度20%到90%RH精度分别是正负2摄氏度和正负5%RH。它的通信方式是单总线也就是一根数据线同时负责发送和接收。这种设计省引脚但对时序要求极其严格。DHT11的通信过程是主机拉低总线至少18毫秒作为起始信号然后释放总线DHT11响应后依次发送40位数据包括湿度整数、湿度小数、温度整数、温度小数和校验和。问题就出在这个时序上。DHT11的每一位数据用高电平的持续时间来区分0和1高电平持续26到28微秒表示0持续70微秒表示1。如果你的延时函数不准确或者中途被中断打断读出来的数据就会错位。我最初用HAL_Delay做微秒级延时结果完全读不到数据因为HAL_Delay的最小分辨率是毫秒根本没法产生26微秒的精确延时。后来改用定时器计数或者简单的循环延时才把时序对上。另一个坑是DHT11的响应速度。它上电后需要1秒左右的稳定时间两次采集之间至少间隔1秒否则数据会漂移。很多人写代码时在while循环里连续读结果第二次开始全是0或者校验失败。正确的做法是采集一次后延时至少1秒再采集下一次。还有DHT11对电源噪声比较敏感如果和电机、继电器共用电源读数会跳变。我一般会在DHT11的VCC和GND之间并一个100nF的电容效果立竿见影。3.2 GPIO模式配置开漏输出与浮空输入的切换时机STM32F1的GPIO有八种模式输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、开漏复用、推挽复用。在DHT11的单总线通信中主机需要在输出和输入之间切换。起始信号阶段主机要拉低总线这时候GPIO配置为推挽输出输出低电平。释放总线后主机要读取DHT11的响应这时候GPIO必须切换为输入模式否则主机的输出驱动器会干扰总线电平。这里有一个细节切换模式时要不要重新配置上拉电阻DHT11的数据线通常需要外接一个4.7k到10k的上拉电阻到3.3V。如果外部已经有上拉电阻GPIO配置为浮空输入即可如果没有外部上拉就要配置为输入上拉。我见过有人既接了外部上拉又在代码里开了内部上拉结果总线被拉得太强DHT11拉不低通信失败。所以配置之前一定要确认硬件上有没有上拉电阻。切换模式的代码顺序也很重要。正确的做法是先把GPIO配置为输出拉低至少18毫秒然后拉高紧接着把GPIO配置为输入再开始检测DHT11的响应。如果先配置为输入再拉高中间会有一个短暂的高阻态总线电平不确定DHT11可能误判。这个顺序问题在教程里往往被一笔带过但实际调试时就是它导致读不到数据。3.3 微秒级延时的三种实现方式与精度对比DHT11的时序要求微秒级精度而STM32F1的SysTick默认配置是1毫秒中断HAL_Delay只能提供毫秒延时。要产生微秒延时常见有三种方法。第一种是空循环延时用for或者while循环消耗CPU周期。这种方法的精度取决于编译器优化等级和主频优点是简单缺点是不精确、不可移植。比如在72MHz下一个简单的for循环延时1微秒可能需要循环几次但开了-O2优化后循环可能被优化掉延时完全失效。第二种是用SysTick的当前计数值做忙等待。SysTick是一个24位递减计数器配置为1微秒计数一次的话可以通过读取当前值来计算经过的时间。这种方法的精度比空循环高而且不受编译器优化影响。具体做法是记录进入时的计数值然后循环读取当前值直到差值达到目标微秒数。注意要处理计数器回绕的情况。第三种是用通用定时器如TIM2做微秒计数。把定时器预分频到1MHz也就是每微秒计数一次然后读取计数器的值。这种方法最精确也不占用CPU的SysTick资源适合对时序要求严格的项目。缺点是占用一个定时器。我在DHT11项目里用的是第二种方法因为不需要额外占用定时器精度也够用。实测下来用SysTick忙等待产生的26微秒延时误差在1微秒以内DHT11能稳定识别。延时方式精度是否受编译器优化影响占用资源适用场景空循环低是无对时序要求不高的场合SysTick忙等待中高否SysTick单总线、红外解码等通用定时器高否一个定时器多路严格时序控制3.4 数据校验与异常重试机制的设计DHT11发送的40位数据里最后8位是校验和等于前32位相加取低8位。如果校验失败说明传输过程中出现了位错误。新手常见的做法是校验失败就丢弃等下一次采集。但这样会导致数据更新率下降尤其在湿度变化快的环境里可能十几秒都拿不到一个有效值。更好的做法是加入重试机制一次采集失败后立即重新发起一次通信最多重试三次。如果三次都失败再放弃并记录错误。重试的时候要注意两次通信之间必须间隔至少1秒否则DHT11还没准备好重试也是白搭。所以重试逻辑不能写成紧密循环而应该用状态机或者定时器来调度。我在实际项目里是这样处理的主循环每2秒触发一次采集采集函数内部最多重试3次每次重试之间用软件延时等待1秒。这样即使第一次失败最多3秒后也能拿到数据对大多数温湿度监控场景来说完全够用。还有一个异常情况是DHT11完全没有响应。这时候总线会一直保持高电平代码如果一直等待低电平就会死循环。所以读取函数里必须加超时判断等待响应低电平超过100微秒就退出等待响应高电平超过100微秒也退出。超时时间可以根据实际测试调整但一定要有否则程序会卡死。我一般会在超时后返回一个特定的错误码方便上层判断是传感器没接好还是时序问题。4. 串口调试让数据真正被看见4.1 USART的波特率计算与时钟源选择STM32F1的USART波特率由外设时钟和USART_BRR寄存器决定。以USART1为例它挂在APB2总线上时钟频率通常是72MHz。波特率计算公式是USARTDIV fCK / (16 × 波特率)。比如要设置115200波特率USARTDIV 72000000 / (16 × 115200) 39.0625。BRR寄存器的整数部分是39小数部分是0.0625 × 16 1所以BRR的值是0x271。如果时钟配置错了比如APB2实际是36MHz那算出来的BRR就不对串口收到的就是乱码。很多人用CubeMX配置串口直接选115200生成的代码看起来没问题但实际通信就是乱码。排查的时候第一件事就是确认系统时钟树。CubeMX里有一个Clock Configuration页面能看到各个总线的实际频率。如果HSE没起振系统可能跑在内部8MHz RC上这时候APB2只有8MHz波特率偏差巨大。我遇到过好几次都是因为晶振没焊好或者负载电容不对导致时钟源切换失败程序自动回退到内部RC。另一个容易忽略的点是过采样。STM32F1的USART支持16倍和8倍过采样。16倍过采样对波特率误差的容忍度更高适合大多数场景。8倍过采样可以让波特率更高但对时钟精度要求更严。除非你要跑很高的波特率比如1M以上否则保持默认的16倍过采样就行。4.2 printf重定向的两种写法与潜在陷阱在单片机开发里用printf打印调试信息是最方便的方式。但STM32的printf默认输出到标准输出需要重定向到串口。常见有两种写法。第一种是重写fputc函数这是Keil MDK和IAR都支持的方式。代码很简单在fputc里调用HAL_UART_Transmit发送一个字节然后返回这个字节。第二种是重写_write函数这是GCCSTM32CubeIDE的方式。两种写法的原理一样只是函数名不同。这里有一个陷阱如果你在中断服务函数里调用printf而printf底层是阻塞发送就会导致中断响应时间变长甚至引发其他中断丢失。比如你在定时器中断里打印调试信息串口发送一个字符串需要几百微秒到几毫秒这期间其他中断都被延迟了。正确的做法是中断里只置标志位主循环里判断标志位再打印。或者用DMA发送把数据丢给DMA后立即返回不占用CPU时间。还有一个坑是printf的缓冲区。标准库的printf默认使用行缓冲遇到换行符才真正输出。如果你打印的字符串没有换行可能一直留在缓冲区里看不到。解决办法是在每次printf后手动调用fflush或者在字符串末尾加\n。我习惯在调试信息末尾统一加\r\n既刷新缓冲区又让串口助手的显示更整齐。4.3 用串口助手观察DHT11数据的正确姿势串口助手是调试传感器最直接的工具。但很多人用串口助手只看数据忽略了时间戳和波形。我推荐用带时间戳功能的串口助手比如SSCOM或者XCOM这样你能看到每次数据到达的精确时间。如果DHT11每2秒采集一次串口助手上应该每2秒出现一行数据。如果时间间隔忽长忽短说明你的延时或者调度有问题。观察数据的时候不要只看数值还要看变化趋势。比如用手握住DHT11温度应该缓慢上升湿度也会变化。如果数值突然跳到0或者99说明通信出错了。这时候可以打开串口助手的十六进制显示看看原始字节是什么。DHT11的正常数据是5个字节湿度整数、湿度小数、温度整数、温度小数、校验和。如果收到的字节数不对或者校验和不匹配就能快速定位问题。我还习惯在串口输出里加上错误码。比如采集成功打印OK: T25C H60%采集失败打印ERR: timeout或者ERR: checksum。这样一眼就能看出是硬件没接好还是时序有问题。调试阶段信息越详细越好等稳定运行后再精简输出减少串口带宽占用。5. 那些教程不会告诉你的调试经验5.1 程序下载后不运行的五个排查方向程序下载成功但板子没反应这是新手最常遇到的问题。我总结下来有五个排查方向。第一检查BOOT引脚。如果BOOT0还是高电平芯片上电后进入系统存储器不会运行你下载的程序。把BOOT0拨回低电平按复位。第二检查复位电路。有些最小系统板的复位按键接触不良或者复位电容太大导致复位时间过长。用万用表测复位引脚正常应该是3.3V按下按键降到0V。第三检查时钟配置。如果程序里配置了HSE但板子上没有晶振或者晶振坏了程序会卡在等待HSE就绪的循环里。可以在时钟初始化后翻转一个GPIO用示波器或者LED判断程序有没有跑过时钟配置。第四检查堆栈大小。STM32F1的启动文件里定义了堆栈大小默认可能是1KB。如果你的程序用了较大的局部数组或者递归堆栈溢出会导致程序跑飞。可以在启动文件里把堆栈改大比如2KB或者4KB。第五检查中断向量表。如果你用了IAP或者自定义了向量表偏移但忘记设置SCB-VTOR中断就会跳到错误的位置。这个坑在带Bootloader的项目里特别常见。5.2 DHT11读数跳变的硬件与软件双重排查DHT11读数偶尔跳变比如温度突然从25跳到0再跳回来这种问题往往不是单一原因。硬件方面先检查电源。DHT11的工作电流虽然不大但瞬间拉低总线时会有电流尖峰。如果电源走线太细或者滤波电容不够电压会跌落。我在DHT11的VCC和GND之间并了一个100nF和一个10uF的电容跳变明显减少。数据线也要尽量短超过20厘米就容易受干扰最好用屏蔽线或者双绞线。软件方面检查你的读取函数有没有关中断。DHT11的时序要求微秒级如果读取过程中被其他中断打断比如SysTick中断或者串口中断就会错过电平变化。我的做法是在读取DHT11的整个过程中关闭全局中断读完再打开。关中断的时间大约几毫秒对大多数应用来说可以接受。如果系统对中断实时性要求高可以用定时器输入捕获来测量高电平持续时间这样不需要关中断但实现起来复杂一些。还有一个软件层面的问题是变量类型。DHT11返回的湿度整数是8位无符号数温度整数也是8位但温度可能是负数DHT11不支持负温但DHT22支持。如果你用char类型接收负数会变成大正数。我一般用uint8_t接收原始字节然后在应用层判断符号位。校验和计算时也要注意所有字节按无符号数相加取低8位。5.3 从裸机到RTOS什么时候该引入任务调度裸机程序用while(1)大循环加延时做一两个传感器的采集没问题。但当你要同时处理串口命令、按键扫描、显示屏刷新、多个传感器采集时大循环就会变得臃肿每个任务的实时性都难以保证。这时候就该考虑引入RTOS比如FreeRTOS。FreeRTOS在STM32F1上跑很轻松内核占用几KB Flash和1KB左右的RAMC8T6完全带得动。引入RTOS之后DHT11采集可以单独放在一个任务里任务里用vTaskDelay实现1秒间隔不会阻塞其他任务。串口命令解析放在另一个任务优先级可以设高一点保证响应及时。任务之间通过队列传递数据比如DHT11任务把温湿度数据打包成结构体发送到队列显示任务从队列里取数据刷新屏幕。这样各任务解耦代码更好维护。不过RTOS也带来了新的坑。第一中断里不能调用带阻塞的API比如vTaskDelay必须用FromISR版本。第二任务堆栈要分配够DHT11任务里如果用了printf堆栈至少256字。第三优先级反转问题如果低优先级任务持有互斥锁高优先级任务等待中等优先级任务又在跑就会导致高优先级任务被延迟。FreeRTOS的互斥锁支持优先级继承可以缓解这个问题但设计时还是要尽量避免长时间持有锁。6. 从DHT11项目延伸出的学习路径DHT11加STM32F1这个组合看起来简单但它覆盖了嵌入式开发的核心技能GPIO操作、时钟配置、微秒延时、单总线协议、串口通信、数据校验、异常处理。把这套流程走通之后你其实已经具备了驱动大多数数字传感器的能力。接下来可以尝试把DHT11换成DHT22它的时序类似但精度更高数据格式是16位能测负温。或者换成I2C接口的SHT30学习I2C总线的时序和STM32的I2C外设配置。再往后可以加一块OLED显示屏把温湿度数据显示在本地不用每次都开电脑看串口。OLED通常是I2C或SPI接口正好练习另一种总线。然后可以加一个ESP8266或者ESP32模块把数据上传到云平台学习AT指令和JSON格式。如果对低功耗有要求可以研究STM32F1的睡眠模式和停止模式用RTC定时唤醒采集用电池供电运行几个月。我自己的学习路径就是这样一步步走过来的。每加一个模块都会遇到新的问题但底层的东西是相通的。STM32F1虽然老但它的手册详细、社区活跃、价格便宜作为学习平台再合适不过。等你把F1玩透了再换F4或者G0、L4系列会发现大部分概念都能直接迁移只是外设更复杂、性能更强而已。关键不是用多新的芯片而是把一套完整的开发流程和调试方法真正掌握在手里。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询