STM32F103+DHT11实战:从时钟配置到串口输出温湿度

发布时间:2026/10/28 18:21:26
STM32F103+DHT11实战:从时钟配置到串口输出温湿度 1. 为什么STM32F1至今仍是嵌入式入门的首选如果你在电子论坛或者技术群里问一句新手学单片机选什么十有八九会有人回你STM32F1。这个答案从十年前一直延续到现在多少有点反直觉——毕竟芯片迭代这么快F1系列按理说早该被F4、G0、H7这些后辈拍在沙滩上了。但实际情况是STM32F1的出货量依然巨大教程存量惊人二手开发板便宜到十几块钱一块DHT11这类经典传感器配F1的例程更是满地都是。我身边不少做工业控制、小家电、仪器仪表的朋友项目里跑的还是F103。这件事背后的逻辑其实很朴素F1系列踩中了一个性能够用、价格够低、生态够厚的甜蜜点。Cortex-M3内核主频72MHz64KB到512KB的Flash20KB SRAM外设该有的都有——GPIO、USART、SPI、I2C、ADC、定时器、CAN、USB。对于绝大多数不需要跑复杂算法、不需要大内存缓冲的控制类场景这套配置绰绰有余。更关键的是ST官方那套标准外设库Standard Peripheral Library和后来居上的HAL库把寄存器操作封装得明明白白新手不用啃几百页参考手册也能点灯、串口打印、读传感器。我打算把这篇内容写成一份从零到能跑DHT11温湿度采集的完整实战记录。不是那种复制粘贴官方例程的流水账而是把选型思路、时钟配置的坑、DHT11时序的坑、串口调试的坑一个个掰开揉碎讲清楚。适合刚接触STM32F1的朋友也适合手上有块吃灰开发板想重新捡起来的人。读完你应该能独立完成一个F103 DHT11 串口输出温湿度的最小系统并且知道每一步为什么这么做。2. 把F103C8T6这块板子摸清楚再动手2.1 核心板与最小系统的区别别买错了市面上最常见的STM32F1开发板有两类一类是核心板只有MCU、晶振、复位电路、稳压芯片和排针另一类是最小系统板或者开发板上面还带了USB转串口芯片、LED、按键、甚至OLED接口。新手最容易犯的错就是买了个纯核心板结果发现没法直接下载程序还得额外配一个USB-TTL模块。我的建议是第一块板子直接买带CH340或者CP2102的F103C8T6最小系统板价格通常二十块以内。它自带Micro USB口插上电脑就能识别出串口配合STM32CubeProgrammer或者FlyMcu就能烧录。省去你折腾下载器的时间把精力放在代码上。判断一块板子值不值得入手看这几个点MCU型号F103C8T6是48脚Flash 64KBSRAM 20KB够跑DHT11这种小项目。想要更多IO就选F103RCT664脚256KB Flash。晶振板子上应该有8MHz外部晶振HSE和32.768kHz低速晶振LSE做RTC用。有些廉价板省掉了LSE如果你要用RTC就得注意。BOOT跳线BOOT0和BOOT1的跳线帽必须能方便切换否则烧录模式切换会很痛苦。复位按键和用户LED调试时点灯和复位是最基础的手段没有的话自己飞线也行但有现成的更省事。2.2 供电与下载新手最容易卡住的两道坎供电这块F103的工作电压是2.0V到3.6V典型3.3V。板子上的稳压芯片常见是AMS1117-3.3把USB的5V降到3.3V。注意F103的IO口虽然标称5V容忍FT引脚但不是所有引脚都容忍5V具体要看数据手册的引脚定义表。DHT11的数据线是3.3V逻辑直接接没问题但如果你接5V的传感器最好加电平转换或者确认该引脚是FT类型。下载方式有三种我按推荐顺序排串口下载ISP通过板载USB转串口把BOOT0接1、BOOT1接0复位后进入系统存储器启动模式用FlyMcu烧录。优点是零成本缺点是每次烧录要手动切跳线。ST-Link下载买个十几块的ST-Link V2接SWDIO、SWCLK、GND、3.3V四根线配合STM32CubeProgrammer或者Keil直接下载调试。这是最推荐的方式支持在线调试、断点、看变量效率高太多。J-Link性能更好但价格贵新手没必要。提示用ST-Link的时候如果CubeProgrammer提示无法连接目标先检查板子是否上电、SWD线是否接反、以及是否被之前的程序禁用了SWD引脚有些代码会把PA13/PA14复用成普通IO导致再也连不上这时候要用BOOT0拉高进ISP模式擦除。2.3 开发环境选型Keil、CubeIDE还是PlatformIO这个问题争论很多我说说我的实际体验。Keil MDK是存量最大的选择教程多、芯片包全、编译速度快。缺点是界面老旧免费版有32KB代码限制F103C8T6的工程一般不会超但加了HAL库和DHT11驱动后要留意。如果你跟着网上的教程走大概率是Keil。STM32CubeIDE是ST官方亲儿子基于Eclipse免费无限制集成了CubeMX配置工具图形化配时钟树和外设非常直观。缺点是Eclipse系IDE启动慢、吃内存第一次用要适应。PlatformIOVS Code插件适合喜欢命令行和现代编辑器的朋友依赖管理方便但配置STM32需要写platformio.ini对纯新手有一点门槛。我的建议如果你完全零基础先用Keil 标准库例程入门把点灯和串口跑通等熟悉了再用CubeMX HAL库做项目。标准库的代码更贴近寄存器能帮你理解底层HAL库抽象程度高开发效率高但容易知其然不知其所以然。3. 时钟树配置72MHz不是随便写上去的3.1 从8MHz晶振到72MHz的倍频链路很多人用CubeMX配时钟看到HCLK填72MHz就完事了从来没想过这个数字是怎么来的。但一旦你换了一块晶振频率不同的板子或者想超频到128MHz不懂时钟树就会抓瞎。F103的时钟来源有几个HSI内部8MHz RC、HSE外部晶振通常8MHz、PLL锁相环倍频。系统时钟SYSCLK最大72MHz。典型配置链路是这样的HSE 8MHzHSE经过PLL的预分频PLLXTPRE通常不分频后进入PLLSRCPLL倍频系数PLLMUL设为9得到8MHz × 9 72MHzSYSCLK选择PLL输出即72MHzAHB预分频器HPRE设为1HCLK 72MHzAPB1预分频器PPRE1设为2PCLK1 36MHzAPB1最大36MHzAPB2预分频器PPRE2设为1PCLK2 72MHzAPB2最大72MHz这里有个关键点APB1上的外设如USART2/3、I2C1/2、SPI2时钟是36MHz而APB2上的USART1、SPI1、ADC是72MHz。如果你用USART2做串口波特率计算时的时钟源就是36MHz算错了就会乱码。3.2 用CubeMX配时钟树的实操步骤打开CubeMX选择STM32F103C8T6进入Clock Configuration标签页在Input frequency里填8对应HSE晶振频率。在PLL Source Mux选择HSE。在PLLMul选择x9。在System Clock Mux选择PLLCLK。确认HCLK显示72MHzAPB1显示36MHzAPB2显示72MHz。如果某个框变红说明超频了需要调整预分频。配好后CubeMX会自动生成SystemClock_Config()函数里面就是那一长串RCC-CFGR的寄存器操作。你可以对照参考手册的RCC章节看会发现CubeMX生成的代码和手册描述完全对应。注意如果你用的是没有外部晶振的廉价核心板只能用HSI8MHz内部RC倍频到64MHzHSI/2 × 16。HSI精度差串口波特率会有误差长距离通信可能丢包。所以强烈建议用带8MHz晶振的板子。3.3 时钟配置错了会有什么现象我踩过的坑有一次帮朋友调一块板子串口打印全是乱码。查了半天代码没问题最后发现他板子上的晶振是12MHz的某宝买的杂牌板但代码里按8MHz配的PLL结果SYSCLK变成了108MHzUSART1的波特率自然全错。改成12MHz后一切正常。所以记住一个排查口诀串口乱码先查时钟时钟不对先查晶振。用示波器或者逻辑分析仪量一下晶振引脚确认实际频率比对着代码猜快得多。4. DHT11的时序为什么必须用微秒级延时4.1 单总线协议的本质靠时间长短传0和1DHT11是个便宜好用的温湿度传感器但它的通信协议对时序要求很严。它只有一根数据线主机和从机轮流拉低拉高靠高电平持续的时间长短来区分数据位是0还是1。一次完整的通信流程主机拉低数据线至少18ms然后拉高20-40us作为起始信号。DHT11检测到起始信号后拉低80us再拉高80us作为响应。之后DHT11连续发送40位数据8位湿度整数 8位湿度小数 8位温度整数 8位温度小数 8位校验和。每一位数据以50us低电平开始然后是26-28us的高电平表示070us的高电平表示1。最后DHT11拉低50us表示结束。关键数字判断0和1的阈值大约在40us左右。如果你的延时函数误差超过10us就可能把1读成0。这就是为什么DHT11驱动必须用微秒级延时而且延时要准。4.2 用SysTick做微秒延时的正确姿势标准库和HAL库都提供了毫秒级延时delay_ms但微秒级延时需要自己实现。最常用的方法是配置SysTick定时器为1us中断一次然后在中断里递减一个计数器。// 微秒级延时基于SysTick void delay_us(uint32_t us) { uint32_t start SysTick-VAL; uint32_t ticks us * (SystemCoreClock / 1000000); uint32_t cnt 0; while (1) { uint32_t now SysTick-VAL; if (now start) { cnt start - now; } else { cnt start (SysTick-LOAD - now); } start now; if (cnt ticks) break; } }这段代码的原理是读取SysTick的当前计数值通过前后两次的差值累加算出经过了多少个时钟周期。SystemCoreClock是72MHz时1us对应72个周期。但更简单可靠的做法是把SysTick配置成1us中断一次在中断服务函数里维护一个全局的微秒计数器delay_us直接读这个计数器做差值。这样不依赖SysTick-VAL的读取代码更直观。volatile uint32_t usTicks 0; void SysTick_Handler(void) { if (usTicks) usTicks--; } void delay_us(uint32_t us) { usTicks us; while (usTicks); }注意这个delay_us是阻塞式的在中断里调用会出问题。DHT11的读取过程本身就要阻塞几毫秒所以放在主循环里调用没问题但别在定时器中断里读DHT11。4.3 读DHT11的完整代码逻辑与常见错误我把读DHT11的核心逻辑拆成几步每一步都标注了容易出错的地方#define DHT11_GPIO_PORT GPIOA #define DHT11_GPIO_PIN GPIO_Pin_1 uint8_t DHT11_Read_Byte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { // 等待50us低电平结束 while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) 0); delay_us(40); // 延时40us后采样 byte 1; if (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) 1) { byte | 1; // 等待高电平结束 while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) 1); } } return byte; }常见错误一GPIO模式切换不及时。DHT11通信时主机发送起始信号要输出模式接收数据要输入模式。切换的时候要先配置再操作顺序错了就读不到数据。常见错误二上拉电阻没接。DHT11的数据线需要4.7k到10k的上拉电阻。有些模块自带裸传感器必须自己加。没有上拉电阻数据线拉高时是浮空的读出来全是0或全是1。常见错误三延时函数被编译器优化。如果delay_us里的循环变量没加volatile编译器可能直接优化掉整个循环。我见过有人用for循环做延时Release模式下编译出来延时为0Debug模式正常查了一下午。常见错误四两次读取间隔太短。DHT11的采样周期不能低于1秒连续快速读取会返回上一次的旧数据或者直接失败。建议主循环里每隔2秒读一次。5. 串口打印温湿度从乱码到清晰输出的排查链路5.1 USART1的初始化参数逐个解释串口是调试的眼睛配不好就只能靠LED猜。USART1挂在APB2上时钟72MHz。初始化参数波特率9600或115200。115200传输快但线长或者时钟误差大时容易出错。调试阶段用9600更稳。字长8位。停止位1位。校验位无。模式收发都开。硬件流控无。用标准库配置的代码大概长这样void USART1_Init(uint32_t baud) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // PA9 TX 复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA10 RX 浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate baud; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); }注意PA9必须配成复用推挽输出AF_PP不是普通推挽。配错了TX没输出这是新手高频错误。5.2 printf重定向让调试信息一行搞定标准库下重定向printf需要实现fputc函数int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }然后在Keil的Target选项里勾选Use MicroLIB。不勾的话标准C库的printf会依赖半主机模式程序可能卡死在启动阶段。重定向之后你就可以用printf(Temp: %d.%d C, Humi: %d.%d %%\r\n, ...)这样的语句输出数据了。记得加\r\n否则串口助手里所有内容挤在一行。5.3 串口乱码的四种可能原因与验证方法串口乱码是调试中最常见的问题我按排查优先级列一下现象可能原因验证方法全是乱码偶尔有正确字符波特率不匹配确认代码波特率和串口助手一致全是固定乱码时钟源频率错误检查晶振实际频率与代码配置是否一致完全无输出TX引脚配置错误用示波器量PA9是否有波形输出一半就卡死发送未等待完成检查是否等待了TC标志我遇到最多的是第一种和第二种。有一次用了一块12MHz晶振的板子代码按8MHz配SYSCLK变成108MHzUSART1的波特率寄存器值算出来偏差巨大串口助手收到的全是乱码。把晶振频率改对之后立刻正常。提示如果你不确定板子晶振频率可以用STM32CubeProgrammer连接后读取芯片信息或者写一段代码把HSE频率通过MCO引脚输出用示波器量。MCO输出需要配置RCC_CFGR的MCO位把PA8配成复用推挽。6. 从能跑到稳定几个让项目更靠谱的细节6.1 电源去耦和信号完整性DHT11读数据偶尔失败很多时候不是代码问题而是电源噪声。F103和DHT11共用3.3V电源时如果去耦电容不够MCU开关噪声会干扰单总线通信。我的做法是在DHT11的VCC和GND之间并一个100nF陶瓷电容越靠近传感器越好。MCU的每个VDD引脚附近都要有100nF去耦电容板子自带的通常够用。数据线如果超过20cm考虑加一个100欧姆的串联电阻抑制振铃。这些细节在面包板上不明显但一旦做成PCB或者线拉长问题就暴露了。6.2 看门狗与异常恢复DHT11如果坏了或者接触不良while循环等待电平变化的地方可能死循环整个程序卡住。加一个独立看门狗IWDG能自动复位void IWDG_Init(void) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(IWDG_Prescaler_64); // 40kHz / 64 625Hz IWDG_SetReload(625); // 625 / 625Hz 1s IWDG_ReloadCounter(); IWDG_Enable(); }然后在主循环里定期调用IWDG_ReloadCounter()喂狗。这样即使DHT11读取卡死1秒后系统自动复位不会一直僵在那里。但要注意喂狗的位置要放在主循环末尾不能放在中断里否则主循环卡死而中断还在喂狗看门狗就失去意义了。6.3 数据校验与错误处理DHT11的40位数据里最后8位是校验和等于前32位的和取低8位。读完之后必须校验if (buf[4] (buf[0] buf[1] buf[2] buf[3])) { // 校验通过 humidity buf[0]; temperature buf[2]; } else { // 校验失败丢弃本次数据 printf(DHT11 checksum error\r\n); }校验失败时不要直接用数据也不要立刻重试DHT11需要间隔等下一个2秒周期再读。我见过有人校验失败后while循环一直重读结果DHT11彻底不响应了。6.4 用逻辑分析仪抓一次完整时序如果你有逻辑分析仪几十块的那种就行强烈建议抓一次DHT11的完整通信波形。把起始信号、响应信号、40位数据的高低电平时间都看清楚对照数据手册的时序图。这样一旦读取出错你能立刻判断是主机起始信号不对还是从机响应没来还是数据位判断阈值有问题。我自己的经验是看波形十分钟胜过改代码两小时。尤其是DHT11这种时序敏感的传感器波形是最直接的证据。7. 这套最小系统还能怎么扩展跑通DHT11之后这块F103C8T6其实还有大把资源没用。我列几个自然的扩展方向都是我自己做过或者见别人做过的加OLED显示0.96寸I2C OLEDSSD1306驱动把温湿度直接显示出来脱离电脑串口。I2C1挂在APB1上注意时钟是36MHz。加ESP8266上传数据通过USART2接ESP8266用AT指令把温湿度发到物联网平台。注意ESP8266峰值电流能到300mAF103板子的LDO可能带不动要单独供电。加SD卡记录SPI模式驱动SD卡把温湿度按时间戳存成CSV文件。SPI1在APB2上72MHz速度够用。低功耗改造DHT11本身功耗不高F103可以进Stop模式用RTC定时唤醒做电池供电的温湿度记录仪。这时候LSE晶振就派上用场了。每个扩展都会引入新的坑比如I2C的时钟拉伸、ESP8266的电源跌落、SD卡的FATFS移植。但基础跑通了后面就是查手册、抓波形、调参数的循环。嵌入式这行说到底就是跟时序和电气特性打交道F103 DHT11这个组合刚好能把这两样都练一遍。我个人在实际操作中的体会是别急着上RTOS或者复杂框架先把裸机的主循环跑稳。一个2秒周期的温湿度采集加上看门狗和校验代码不到200行但覆盖了GPIO、时钟、延时、串口、看门狗五个核心模块。把这200行吃透比复制一个几千行的工程更有价值。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询