ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F1为什么仍是入门首选?DHT11温湿度驱动实战全解析

STM32F1为什么仍是入门首选?DHT11温湿度驱动实战全解析 很多人问我2017 年之后的芯片已经满地都是了现在的新项目还值得看 STM32F1 系列吗我的回答通常是值得而且不是情怀。这颗芯片发布超过十五年价格被打到几块钱资料多到刷不完社区里几乎所有坑都被踩平了。尤其你手里正握着“dht11温湿度传感器 stm32f1”这类需求时F1 几乎是成本最低、最容易跑通的选择。这篇文章我就从选型、开发环境、实战驱动到量产稳定性的完整链路把 F1 该知道的东西一次讲透。1. 为什么到了今天我依然建议你把F1当主力入门芯片1.1 F1的生态优势到底在哪里先说价格。一块 STM32F103C8T6 在正常市场行情下几块钱人民币就能买到蓝板开发板更是烂大街。对比新一代的 G0、L4 甚至 H7F1 的单价优势在批量打样阶段非常明显。你做一个温湿度采集、电机控制、IO 扩展这类中低速应用F103 的 72MHz 主频、64KB 到 128KB Flash 完全够用根本不需要为用不上的性能买单。再说资料。F1 有几本核心文档参考手册 RM0008、数据手册、编程手册 PM0056。这三本在 ST 官网直接下载网上中文翻译版也一堆。更重要的是老工程师们留下的博客和代码从 GPIO 点灯到摄像头采集几乎每个外设都有“能直接抄”的例程。这个红利是新一代芯片短期给不了的——你用 G0 踩一个坑搜出来的可能只有英文论坛上两三条回复。最后是生态工具。Keil、IAR 都完整支持 F1STM32CubeMX 对 F1 的引脚配置和时钟树生成也很成熟ST-Link 调试器几十块钱一个。无论是学生做课程设计还是工程师快速验证方案F1 的上手成本几乎为零。1.2 什么场景下别选F1先想清楚我不建议无脑上 F1。以下场景直接绕开要做浮点密集运算比如实时音频滤波、电机滑模控制F1 没有 FPU硬算会拖垮主循环。老老实实上 F4 或者 G4。要做超低功耗电池产品F1 的 STOP 模式功耗毫安级别比不上 L4、L5 那套超低功耗架构。你一个温湿度记录仪要是靠纽扣电池跑一年F1 不是最优解。需要 USB 高速、以太网大吞吐F105/F107 勉强能摸到边但复杂协议栈跑起来资源紧张建议直接考虑带 MACPHY 或者更强内核的型号。判断逻辑很简单先看外设需求和运算量再把价格、资料、功耗放一起权衡。F1 的定位是“皮实、够用、便宜”超出这个定位别硬上。2. 先学会看型号再买芯片F103/F105/F107怎么挑2.1 后缀拆解闪存容量、引脚数、封装和温度等级STM32F1 系列的命名看着吓人拆开其实就几块信息。拿最常见的 STM32F103RCT6 举例103产品线。STM32F103 是通用型内置 USB 设备、CAN、多路定时器和 ADCSTM32F105 在 103 基础上加入 USB OTG FSSTM32F107 再加入以太网 MAC。R引脚数。L 是 48 脚R 是 64 脚V 是 100 脚Z 是 144 脚。引脚越多可用的 GPIO 和外设复用越丰富PCB 面积也越大。CFlash 容量。8 代表 64KBB 代表 128KBC 代表 256KBE 代表 512KB。你写复杂应用或者要留 OTA 空间容量就得往大选。T封装形式T 代表 LQFP。QFN、BGA 也有对应字母打样手工焊接优先选 LQFP。6温度等级6 是 -40℃ 到 85℃7 是 -40℃ 到 105℃。工业现场、户外设备选 7。选型时最容易忽略的是 Flash 容量和引脚数的绑定关系。比如 STM32F103C8T6 只有 48 脚但 Flash 64KB你如果要用 TFT 屏、文件系统、字库64KB 会很紧张必须换到 RCT6 或者 VET6。同样的封装不同容量型号价格差距不大预留一点空间更划算。2.2 F105/F107和F103的真正区别USB与以太网很多新人分不清 F103 和 F105/F107。三者的内核都是 Cortex-M3主频都是 72MHz外设大量共通最大的差别在互联型功能F103 的 USB 是 USB 2.0 Full Speed Device只能做从设备不能接 U 盘、不能做 OTG。F105/F107 的 USB 是 USB OTG FS可以做 Host可以直接读写 U 盘、连接鼠标键盘。F107 在 F105 基础上多了一个 10/100M 以太网 MAC需要外接 PHY 芯片比如 LAN8720才能上网。所以你的项目如果要做 USB 转串口工具F103 够用要做 U 盘升级固件或者 USB 主机选 F105要同时兼顾网络和 USB才轮到 F107。选型时别只看“都是 F1”外设差异很关键。2.3 引脚兼容性与PCB复用要提前确认F1 系列的引脚兼容性整体不错但“兼容”不等于“直接替换”。同一个引脚在不同型号上可能复用不同外设比如 F103 和 F105 的 PA9、PA10 在 F103 上可以复用 USART1在 F105/107 上可能同时接 USB OTG。做 PCB 之前一定打开对应型号的数据手册对照引脚定义特别是电源引脚和 VCAP 引脚。这里分享一个我踩过的坑F103 某些封装有 VCAP 引脚需要外接 1μF 或 2.2μF 电容到地漏接或者电容值不对会导致芯片上电后不启动调试器也连不上。很多人在立创打样时按网上原理图抄结果少画了 VCAP 电容整板变砖。我的经验是拿到新封装先看数据手册里的“Pinouts and pin description”表格把所有电源、VCAP、BOOT、NRST 引脚逐一核对再画原理图。3. 开发环境里最折磨人的三件事库、时钟、下载调试3.1 标准外设库还是HAL我给新手的明确建议F1 的开发库主要有两套ST 早期力推的标准外设库SPL和现在主推的 HAL 库另外还有一套 LL 库。很多人纠结学哪个我的建议很直接如果你是学生、刚入门想搞懂 GPIO、定时器、DMA 这些寄存器的底层逻辑标准外设库更容易读。它的代码把寄存器操作封装得很薄一个GPIO_InitStructure能看到每一位的配置逻辑很容易建立寄存器模型。如果你在做项目、需要快速出产品直接上 HAL CubeMX。CubeMX 帮你生成引脚初始化、时钟树和中间件配置HAL 在 F1 和 F4、G0 之间写法高度一致以后换芯片平台迁移成本低。但我必须提醒网上大量老教程用的都是标准外设库你拿 CubeMX 生成 HAL 工程后照着老教程移植代码会发现函数名对不上。我的做法是备一套标准外设库的例程用于读逻辑工程本身用 HAL 写。这两个库并存并不冲突F1 的寄存器地址是固定的两个库只是不同壳子底层操作都一样。3.2 时钟配置出错的表现和排查方法F1 的时钟树不算复杂但配置错的表现非常迷惑。最常见的三种情况串口波特率乱码。USART1 挂在 APB2 总线USART2、3 挂在 APB1。如果你只配了系统时钟为 72MHz忘记 APB1 预分频为 2APB1 就会跑到 72MHz 而不是 36MHz波特率自然对不上。GPIO 翻转速度慢。GPIO 翻转速度跟 APB2 时钟有关也跟 GPIO 配置的 Speed 有关。代码里明明调用了翻转函数示波器一看只有几百 kHz多半是系统时钟根本没跑到 72MHz或者 GPIO 配了 Low Speed。定时器定时不准。定时器时基来自 APB1/APB2 分频后的时钟APB 分频没配500ms 的定时可能变成 250ms。排查时钟问题我一般先看 CubeMX 生成的SystemClock_Config确认 HSE 外接晶振频率、PLL 倍频系数、AHB/APB1/APB2 分频值。F103 常见配置是 HSE 8MHzPLL 9 倍频得到 72MHzAHB 不分频APB1 分频 2APB2 不分频。如果外部晶振不起振程序会卡在 HSE 启动等待的 while 循环里这时候把 HSE 超时处理加上或者直接切换到 HSI 内部时钟 8MHz 先跑起来。3.3 SWD连接失败、烧录异常的高频原因F1 最常用的下载调试方案是 ST-Link SWD 四根线SWDIO、SWCLK、GND、3.3V。这个方案简单但翻车现场也不少BOOT0 没有拉低。BOOT0 为高电平时芯片进入内置 bootloader此时 SWD 可能还能连但用户程序跑不起来。典型现象是烧录成功但复位后没反应。SWDIO/SWCLK 引脚被复用。F1 的 PA13、PA14 默认是 SWDIO、SWCLK如果你的代码把这两个引脚复用成普通 GPIO 或者其他外设调试器就连不上了。有个恢复技巧先按住复位引脚在 Keil 或 STM32CubeProgrammer 里点击连接然后立刻放开复位趁程序还没跑起来把目标锁住再擦除 Flash。复位电路异常。NRST 引脚对地接了太大的电容或者板上干扰严重会导致 ST-Link 无法同步目标。我在一块自己画的板子上遇到过 NRST 电容用 100nF 导致连接超时换成 10nF 就正常。后来查手册F1 的复位引脚建议电容是 100nF 级别但部分仿真器对复位时序敏感实测小一点更稳。下载失败这类问题我的排查顺序永远是硬件接线 - 供电电压 - BOOT 状态 - 复位电路 - 芯片是否被锁不要一上来就怀疑 IDE 设置。4. 实战把DHT11温湿度传感器接到F1上4.1 读懂DHT11单总线时序是驱动成功的关键DHT11 是典型的单总线传感器一根数据线既要输出又要输入。它每次通信由主机发起主机先把数据线拉低至少 18ms建议 20ms 以上然后释放并拉高 20~40μsDHT11 检测到这个起始信号后会拉低约 80μs 作为响应再拉高 80μs 准备发送数据。之后就是 40 位数据湿度整数、湿度小数、温度整数、温度小数、校验和每字节 8 位先高位后低位。每一位的格式是先拉低 50μs然后拉高高电平持续 26~28μs 表示“0”高电平持续约 70μs 表示“1”。所以读位的关键就是等一个低电平结束延时 40μs再看引脚电平。如果还是高就是“1”如果已经变低就是“0”。这个时序用逻辑分析仪看得最直观。我第一次写 DHT11 驱动时就是把逻辑分析仪挂在数据线上一帧一帧对照时序图才发现自己起始信号的时间差了几百微秒导致传感器根本没响应。对于 F1 这种 72MHz 主频的芯片GPIO 操作速度完全够难点只在于延时精度和中断干扰。4.2 一份调试好的驱动参考代码下面这份代码基于 STM32CubeMX 生成的 HAL 工程用 PB0 作为数据引脚。延时函数我用了简单的空循环计数实际项目里如果你开了多个中断建议换用定时器微秒延时否则读位时被中断打断时序就乱了。#define DHT11_PORT GPIOB #define DHT11_PIN GPIO_PIN_0 void DHT11_Delay_us(uint32_t us) { // 72MHz主频下一个循环大约4个时钟周期 uint32_t count us * 18; while (count--) { __NOP(); } } void DHT11_SetOutput(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); } void DHT11_SetInput(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); } uint8_t DHT11_ReadBit(void) { uint16_t timeout 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET) { if (timeout 1000) return 0xFF; } DHT11_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { timeout 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { if (timeout 1000) return 0xFF; } return 1; } return 0; } uint8_t DHT11_ReadByte(void) { uint8_t data 0; uint8_t bit 0; for (int i 0; i 8; i) { bit DHT11_ReadBit(); if (bit 0xFF) return 0xFF; data (data 1) | (bit 0x01); } return data; } uint8_t DHT11_Read(float *temp, float *humi) { uint8_t data[5] {0}; uint16_t timeout 0; // 主机起始信号 DHT11_SetOutput(); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); DHT11_Delay_us(20000); // 拉低20ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); DHT11_Delay_us(30); // 拉高30us DHT11_SetInput(); // 等待DHT11响应低电平 timeout 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { if (timeout 2000) return 0; // 无响应 } // 等待响应低电平结束 timeout 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET) { if (timeout 2000) return 0; } // 等待响应高电平结束 timeout 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { if (timeout 2000) return 0; } for (int i 0; i 5; i) { data[i] DHT11_ReadByte(); if (data[i] 0xFF) return 0; } if ((uint8_t)(data[0] data[1] data[2] data[3]) ! data[4]) { return 0; // 校验失败 } *humi (float)data[0] data[1] / 10.0f; *temp (float)data[2] data[3] / 10.0f; return 1; }调用方式很简单每隔 2 秒读一次读之前不要频繁发送起始信号。float temp 0, humi 0; if (DHT11_Read(temp, humi)) { printf(temp: %.1f C, humi: %.1f %%\n, temp, humi); }4.3 实测中反复出现的三个坑和处理办法第一个坑是上电后立刻读取返回全 0xFF。DHT11 上电后需要 1 秒左右完成内部初始化期间你给它发起始信号它根本没准备好。程序启动后先 sleep 1 秒再读或者用重试机制连续失败三次再报错。第二个坑是两次读取间隔太短导致数据不变。DHT11 的采样周期固定是 1 秒以上你 200ms 读一次读到的可能是上一次的缓存值甚至因为传感器还在内部测量数据线状态不对导致读位超时。我的经验是至少间隔 1.5 秒到 2 秒实测最稳。第三个坑是延时函数受编译优化影响。上面代码里的__NOP()空循环在 O0 优化下没问题开到 O2 优化后循环时间会大幅缩短读位就全部错乱。我把主频固定 72MHz、延时系数 18 的写法是针对我常用的 Keil AC5 编译器调的。你换成 AC6、IAR 或者 GCC一定要用逻辑分析仪或者示波器重新标定。更省心的做法是用定时器做微秒延时或者直接改用HAL_Delay配合高优先级中断来调度虽然慢一点但可移植性强。另外一个很多人忽略的点DHT11 的数据线通常需要外接 4.7kΩ 上拉电阻。F1 内部的上拉电阻也能凑合用但线长、干扰大的场合外部上拉更可靠。如果板子上有别的 3.3V 设备共用电源DHT11 供电脚最好加一个 0.1μF 去耦电容否则读数在跳变边沿容易多计或少计一位。5. 从能跑到稳定跑F1项目量产的必修课5.1 独立看门狗的正确用法和喂狗位置F1 有两个看门狗窗口看门狗WWDG和独立看门狗IWDG。量产项目我优先用 IWDG它由 LSI 内部低速时钟驱动即使主时钟失效也能继续计数适合防止程序跑飞后系统挂死。IWDG 的典型配置LSI 频率约 40kHz预分频 64重装载值 625得到约 1 秒的超时时间。代码里在初始化后必须立即启动否则复位后就永远不允许关闭写保护。喂狗位置要特别注意。很多人把喂狗放在定时器中断里看起来没问题但一旦主循环卡在某个死等逻辑里中断依然在跑看门狗永远不超时系统就“假死”了。正确做法是让主循环的每个关键节点都做标记喂狗前检查这些节点是否都成功执行过。比如你的程序要读 DHT11、要刷 OLED、要上报数据那就用一个全局变量记录各节点完成度完成度达标才喂狗。这种设计能真正起到“看门”作用而不是养一条只吃饭不干活的狗。5.2 低功耗、复位与硬件设计中的细节F1 的低功耗模式有 Sleep、Stop、Standby。Stop 模式在小型电池设备里比较常见唤醒后需要重新配置时钟——这是最容易翻车的地方。很多人从 Stop 唤醒后直接继续跑外设结果串口乱码、定时器不准因为 PLL 没有重新锁定。正确流程是进入 Stop 前保存关键状态唤醒后重新执行时钟配置函数再初始化需要的外设。还有一个细节如果启用了 IWDG进入 Stop 模式前要考虑好喂狗策略。IWDG 在 LSI 时钟下运行Stop 模式下芯片时钟停了但 LSI 可能还在跑看门狗照样递减如果不提前喂狗或者关闭看门狗MCU 会在睡眠途中被强制复位。硬件设计方面F1 有几个点我每次画板都会核对VDDA 引脚必须接模拟电源滤波通常串联 10Ω 电阻再接 1μF 和 10nF 电容ADC 采样才会稳定。VCAP 电容别省参考原理图手册的推荐值。BOOT0 引脚建议串 10kΩ 下拉电阻防止上电瞬间浮空误入 bootloader。NRST 复位电路用 100nF 电容 10kΩ 上拉是标准做法但有些仿真器挑复位时序备选再留一个 10nF 的位置。这些细节在设计阶段处理好能省掉调试阶段一大半的“玄学故障”。5.3 通过串口IAP做固件升级的思路F1 芯片本身带一个 ROM 里的系统 bootloader支持通过串口下载程序到 Flash。但量产设备不能老让用户短接 BOOT0 和复位键来升级所以常见做法是自己在 Flash 前部写一段 IAPIn-Application Programming引导程序。我的实现思路是把 Flash 分成两个区IAP 区 8KBAPP 区从 0x08002000 开始。IAP 区程序负责检查串口是否有升级命令没有就直接跳转 APP。APP 区程序通过串口接收固件包写入 APP 区后半部分完成后软复位进入新固件。跳转 APP 前关闭所有中断复位外设状态APP 启动后重新初始化中断。F1 与 F4 的一个显著差异是F1 的向量表重定位没有 F4 那样简单。F4 可以通过设置 SCB-VTOR 寄存器把向量表偏移到 APP 地址但 F1 这颗 Cortex-M3 核针对向量表偏移的处理要绕一下。我的做法是不依赖启动文件自带的向量表设置而是把中断向量表放在 Flash 开头固定区域APP 通过一个固定的函数指针表跳转或者干脆在 APP 启动代码里手动设置地址映射。具体做法建议参考 ST 官方的 AN2557 应用笔记那里有针对 F1 的 IAP 跳转标准流程。顺带一提固件升级一定要带版本号和 CRC 校验。我见过太多产品因为升级到一半断电导致整个 APP 区数据损坏变成砖。设计一个 A/B 区备份机制升级失败还能回滚到上一版这个成本远低于售后返修。6. 关于F1我最后的几句大实话很多人一听 STM32F1 就说“太旧了”但所谓新旧是相对的。这几年我做过消费电子、工业控制、环境监测三类项目F103 依然占了不小比例。它的优势不是性能强而是“确定性强”你查到的每一个坑、每一个外设行为几乎都有前人验证过。对一个要交期、要成本控制的产品来说这种确定性比纸面参数值钱得多。如果你现在手里只有一块 F103C8T6 和一颗 DHT11我建议别只停留在点灯。试着把传感器驱动写到独立模块加上软件定时器管理采样周期再把数据通过串口传到电脑上位机显示曲线。完成这个闭环你就已经把 GPIO、定时器、串口、中断、状态机这几块 F1 最核心的内容都过了一遍。再往后接 OLED、接蓝牙模块、接 WiFi 模块本质上都是同一个套路看时序、查数据手册、写驱动、排干扰。最后分享一个我自己的小习惯每一个 F1 项目我都会把“怀疑对象”按概率排序——先怀疑时序配置再怀疑电平匹配然后怀疑电源纹波最后才怀疑芯片本身。F1 的芯片质量和抗干扰能力经过这么多年市场验证真的很少是它的问题。多数时候是我们对它的外设和时序理解得不够透。多花点时间在逻辑分析仪上比什么玄学排查都管用。
返回列表