ARTICLE DETAIL

资讯详情

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

2025年STM32F1仍不过时:从选型到DHT11温湿度采集实战

2025年STM32F1仍不过时:从选型到DHT11温湿度采集实战 还有人觉得STM32F1已经“过时”了吗如果你在2025年问我会不会在新项目里继续用STM32F103我的答案仍然是看场景。前阵子帮朋友做一个环境监测小盒子主控选了F103C8T6传感器用了最经典的DHT11成本压到十几块钱从画板到交付只花了两周。F1主频确实只有72MHzCortex-M3的确没有M4好看但它的资料密度、外设覆盖度、以及围绕它沉淀了十几年的工程例程至今没有任何一颗入门芯片能完全替代。这篇文章我会把F1的选型思路、工程搭建、以及用DHT11做温湿度采集的完整过程都过一遍最后再分享几个在F1上实操才容易踩的坑。适合刚接触嵌入式、或者正打算用F1做小项目的读者也适合那些纠结“F1是不是该淘汰”的人。1. 2025年还在用STM32F1它凭什么没被淘汰1.1 72MHz单周期乘法性能到底够不够用先说结论对大部分控制类任务F1的性能远远够用。Cortex-M3在72MHz下大约能跑90 DMIPS单周期硬件乘法和除法这个算力放在串口通信、I2C/SPI读写、ADC采集、PWM输出、简单PID调节这些场景里几乎不会成为瓶颈。比如DHT11这种单总线传感器时序要求是微秒级的主机只需要精确控制拉低时间和采样电平72MHz的主频意味着每个指令周期约13.9ns微秒级延时用循环或者DWT计数器都游刃有余。真正让我觉得F1吃力的场景是大量浮点运算、FFT频谱分析、复杂的GUI渲染、或者同时跑多个重负载协议栈。这些任务F4、H7会更合适因为它们有FPU和更高的主频硬算就能省下一大截CPU时间。所以选型的第一步不是问“这颗料是不是最新的”而是问“我的程序到底在算什么”。1.2 生态护城河教程、例程、踩坑记录全网最多这个因素常被低估但我个人觉得它比芯片本身更重要。STM32F1从2007年前后发布至今中文互联网上的教程、开源例程、毕设参考、问答帖数量几乎是其他型号加起来的几倍。不管是正点原子、野火的老版开发板资料还是各类博客里用F103做的项目你搜“STM32F103 串口乱码”“F103 I2C 硬件失败”这类问题基本都有现成答案。这意味着什么意味着新手上手阶段的“试错成本”被摊薄了。你遇到一个奇葩问题大概率早有人踩过并在某个论坛写下了解决办法。对做产品的人来说这种确定性非常值钱——它直接决定了开发周期和风险。相比之下一颗更新但资料稀少的芯片性能再好看碰到问题卡你三天成本就全回去了。1.3 什么时候真的该换F4/H7我不主张无脑吹F1它确实有明确的适用边界。下面这张表是我常用的对比逻辑对比项STM32F1STM32F4STM32H7内核Cortex-M3Cortex-M4FCortex-M7最高主频72MHz168MHz480MHzFPU无有有双精度典型Flash/RAM64KB~512KB / 20KB~64KB128KB~1MB / 64KB~192KB1MB~2MB / 864KB适合场景控制、采集、通信、小屏显示浮点运算、信号处理、复杂界面高性能计算、AI推理、音视频如果你要做电机控制、仪器仪表、智能家居网关、环境监测这类“逻辑为主、采集为辅”的项目F1绰绰有余。如果你要做音频效果器、振动分析、需要跑大量浮点滤波的那就别犹豫直接F4甚至H7。另外如果你打算跑比较重的RTOSLVGL界面云连接全家桶F1的RAM和主频会让你调得很痛苦这时候选F4更划算。2. 看懂F1家族的“家谱”选型才有底气2.1 F103/F101/F100/F105/F107一张表看懂很多初学者只听说过F103其实F1是一个家族不同后缀差异不小型号系列内核/主频最大Flash / RAM关键特色STM32F100M3 24MHz128KB / 8KB主打低功耗外设精简STM32F101M3 36MHz512KB / 32KB无USB外设较少STM32F102M3 48MHz128KB / 16KBUSB从机简化版STM32F103M3 72MHz512KB / 64KB外设最全最主流STM32F103T/UM3 72MHz32KB / 10KB极小封装成本优先STM32F105M3 72MHz512KB / 64KB带USB OTGSTM32F107M3 72MHz512KB / 64KB带以太网MAC实际设计里90%的情况你只需要在F103里挑子型号。F100的低功耗优势在现在很多超低功耗MCU面前并不明显F105/F107则适合需要USB主机或以太网但不想外扩芯片的场景。如果你只是做串口协议转换、传感器采集、简单控制F103就是最稳的选择。2.2 从封装和引脚数选型C8T6、RCT6、ZET6怎么选同样一颗F103封装不同可用外设数量完全不同。最常遇到的三个型号STM32F103C8T6LQFP4864KB Flash20KB RAM。这是“蓝色药丸板”的经典型号价格便宜适合小产品、学习板和简单样机。缺点是引脚少一个稍微复杂的项目就可能引出不够用。STM32F103RCT6LQFP64256KB Flash48KB RAM。这是我认为的“性价比甜点”Flash和RAM都翻了几倍引脚也够用适合跑RTOS、带小屏幕、接多个传感器。STM32F103ZET6LQFP144512KB Flash64KB RAM。资源最全适合学习和做功能复杂的样板但芯片封装大、PCB走线也更费事量产成本偏高。我的建议很简单学习阶段直接拿C8T6小系统板练手画板选型直接上RCT6除非你要做数据记录和复杂人机交互再考虑ZET6。不要一上来就“性能焦虑”很多项目死在功能膨胀上而不是芯片性能不够。2.3 从外设需求反推USB、以太网、CAN要看哪些选型还有一种做法叫做“外设反向约束”。先列出你项目必须用的通信接口再回来挑芯片需要USB从机做虚拟串口或U盘F103全系基本都带USB Device但F105/F106才支持OTG和双角色。量大的产品如果只是做数据导出F103C8就够了。需要以太网直接看F107它内置10/100M MAC但要注意还得外挂一颗PHY芯片和变压器并非“焊上就能上网”。需要CAN总线F103标准型号基本都带CAN控制器不过只有部分型号带两个CAN做双冗余总线时要仔细查数据手册。需要多个UARTC8T6有3个USARTRCT6和ZET6有5个。如果需要同时接GPS、4G模组、RS485、调试串口引脚数少于64的封装会很紧张。这一步想清楚基本能砍掉一半候选型号。2.4 我自己选型时的决策顺序这些年下来我形成了一套固定选型顺序分享出来供参考先列必需外设和接口数量比如几路UART、几个ADC、几路PWM写下来。根据外设数量反推最小引脚数再选封装。能用48脚绝不上64脚能省一颗电阻的封装就是好封装。估算Flash和RAM固件里如果有中文字库、波形表、日志存储Flash需求会剧增如果跑FreeRTOS至少预留8KB RAM给任务栈。看价格和供货。F1系列生命周期已经很长货源和价格都比较稳定但也要查一下目标型号当前现货情况避免画完板子买不到料。按这个顺序走下来往往不用半小时就能锁定型号而且不会犯“引脚挂不下”的低级错误。3. 开发环境与工程骨架CubeMXKeil的爽和坑3.1 用STM32CubeMX生成F1工程先把时钟树看明白现在做F1开发我强烈建议用STM32CubeMX生成初始工程哪怕你后面所有外设驱动都打算自己写寄存器。它能帮你把最枯燥的时钟配置、引脚映射、启动文件一次性搞定省下的时间用来读手册写逻辑非常划算。操作流程大概这几步新建工程在MCU选择器里搜STM32F103C8或RCT6。在System Core里配置RCC把HSE设成Crystal/Ceramic Resonator。如果你的板子上是8MHz晶振后面PLL倍频直接设9SYSCLK就是72MHz。在SYS里把Debug选成Serial Wire否则烧录口可能被禁用。按需要勾选USART、GPIO、I2C、定时器等外设。Project Manager里选择Toolchain为MDK-ARM生成代码后用Keil打开。这里最容易踩的坑有两个一个是忘了把SYS的Debug设为Serial Wire导致第一次烧录后SWD口失效、再也连不上调试器另一个是HSE频率填错导致CubeMX自动算出来的串口波特率全是错的出现乱码。后者我在做国产板卡时踩过——标称8MHz的晶振实际焊了12MHz串口数据全是花码排查了很久才发现根源在晶振。3.2 HAL库、标准库、LL库F1上到底该用哪个这个问题几乎每隔一段时间就有新人问一次。我的看法是分情况方案优点缺点适合谁标准外设库SPL代码直观直接操作寄存器封装运行效率高官方已停止更新新外设没有老工程师、追求极致时效的人HAL库自动初始化好外设抽象统一CubeMX优先支持代码层次多中断和回调不直观Flash占用大大多数新项目和初学者LL库轻量级接近寄存器和CubeMX兼容资料相对少配置要自己调对HAL不满、想要标准库效率的人纯寄存器完全可控最小Flash占用开发慢移植差学习内核和协议时值得练手我自己的推荐组合是工程生成用CubeMXHAL但像DHT11这种单总线小外设不用HAL那套复杂的GPIO读写封装直接操作寄存器或者标准库风格的函数来完成。这样既有工程化的便利也有底层驱动的透明感。对纯新手先用HAL跑通整体流程再回头逐行读DHT11驱动代码理解深度会好很多。3.3 最小工程验证点亮板载LED再谈其他不管目标功能是什么第一步我都会先把一块板载LED点亮确认编译、烧录、复位这三条链路顺畅。CubeMX里把PB1配置成GPIO_Output生成代码后main里加几行void led_task(void) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1); HAL_Delay(500); } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { led_task(); } }如果LED闪起来了说明时钟树、启动文件、调试器都正常这个工程骨架就是可信的。后面接串口、接DHT11都是在同一套骨架上加功能。这个“先跑通最小系统”的习惯帮我省掉了无数调试时间。4. DHT11实战把温湿度数据从传感器里“抠”出来4.1 为什么是DHT11先想清楚应用再选传感器DHT11的精度其实不算好湿度精度±5%RH温度精度±2℃采样周期必须大于1秒。但它价格就几块钱单总线只用一根IO资料多到随便搜这三点决定了它依旧是入门温湿度采集最合适的练手对象。如果你需要做药品仓库、精密农业或者其他对数据质量要求高的场景就不建议DHT11了直接用SHT30或BME280这类I2C传感器。这篇我们聚焦F1DHT11重点不是传感器多高级而是把单总线的时序理解透。4.2 硬件接线上拉电阻不是可选项DHT11标准接法是三根线VCC接3.3V或5VGND共地DATA接一个GPIO。这里最关键的一点是数据线上必须有上拉电阻一般取4.7kΩ到10kΩ接到VCC。很多市售DHT11模块已经板载了上拉电阻和滤波电容你直接接就行但如果买的是裸传感器或者自己做板子千万记得把这个电阻画上去否则通信会时好时坏错误率很高。F1的大部分IO引脚都是5V容忍的数据手册里PinOut会标注FT所以DHT11用5V供电、数据线直接接F1引脚也没问题。但要注意不是所有引脚都支持5V容忍接之前查一下引脚表一般标着FT的才行。我习惯把DHT11接到PB0因为PB0/PC5这类引脚在F1上兼容5V而且不占用串口和调试口。4.3 单总线时序拆解从起始信号到40位数据DHT11的通信协议不算复杂但它是“一条线双向传输”的典型读不好几乎全是时序问题。整个读数据过程分四段主机拉低DATA线至少18ms再拉高20~40us。这个低电平是给DHT11的“唤醒信号”时间太短传感器不响应。主机释放总线把IO切回输入模式。DHT11收到信号后会自己拉低约80us再拉高约80us表示“我准备好了”。之后每位数据都从“50us低电平”开始如果后面跟的高电平持续26~28us表示逻辑0如果高电平持续约70us表示逻辑1。DHT11连续发送40位数据前16位是湿度整数小数接下来16位是温度整数小数最后8位是校验和。校验和等于前四个字节相加的低8位成立才算一次成功读取。很多人读DHT11失败根子都在第二步——主机发出起始信号后忘了把引脚从输出模式切回输入模式导致后面根本收不到传感器的响应。这个细节用万用表量不出来但逻辑分析仪一抓就一目了然。4.4 完整驱动代码从起始信号到校验验证我习惯用标准库风格的代码写F1驱动这样不管是Keil还是新版的CubeIDE都能直接编译。DHT11初始化代码如下#include stm32f1xx.h #define DHT11_PORT GPIOB #define DHT11_PIN GPIO_PIN_0 #define DHT11_RCC RCC_APB2Periph_GPIOB static void DHT11_ModeOutput(void) { GPIO_InitTypeDef gpio; gpio.GPIO_Pin DHT11_PIN; gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, gpio); } static void DHT11_ModeInput(void) { GPIO_InitTypeDef gpio; gpio.GPIO_Pin DHT11_PIN; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, gpio); }微秒延时我用DWT计数器实现精确且不依赖中断状态比写空循环可靠static void DWT_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; DWT-CYCCNT 0; } static void DHT11_DelayUs(uint32_t us) { DWT-CYCCNT 0; while (DWT-CYCCNT us * 72); // 72MHz }接下来是核心的读取函数注意流程顺序和各段时间uint8_t DHT11_ReadData(uint8_t *humi_h, uint8_t *humi_l, uint8_t *temp_h, uint8_t *temp_l) { uint8_t buf[5] {0}; uint8_t i, j; DHT11_ModeOutput(); // 主机起始信号拉低 18ms GPIO_ResetBits(DHT11_PORT, DHT11_PIN); DHT11_DelayUs(19000); GPIO_SetBits(DHT11_PORT, DHT11_PIN); DHT11_DelayUs(30); // 拉高 20~40us // 主机释放总线切换到输入模式 DHT11_ModeInput(); DHT11_DelayUs(10); // 等待传感器响应先低80us再高80us while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1); while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 0); while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1); // 读取5字节每字节8位 for (j 0; j 5; j) { for (i 0; i 8; i) { buf[j] 1; // 每位数据起始是一个50us低电平 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 0); // 高电平开始后约30us采样持续偏长就是1偏短就是0 DHT11_DelayUs(30); if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1) { buf[j] | 1; } // 等这个位的高电平走完 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1); } } // 校验和判定 if ((uint8_t)(buf[0] buf[1] buf[2] buf[3]) buf[4]) { *humi_h buf[0]; *humi_l buf[1]; *temp_h buf[2]; *temp_l buf[3]; return 1; } return 0; }主函数里调用时需要注意采样间隔。DHT11手册明确写了两次读取间隔至少1秒我一般在循环里加一个HAL_Delay(1500)避免因为读取太频繁导致传感器反应异常。4.5 DHT11调试排错链路五种症状对应五种原因假设你照着上面的代码接了线还是读不到数据大概率是以下几个原因之一症状常见原因解决方向读到的数据全是0数据线上拉缺失或IO初始化成了推挽输出没切回输入补上4.7k上拉检查DHT11_ModeInput是否执行等待响应时死循环卡死DATA引脚接触不良、传感器供电不稳、上拉太弱检查杜邦线/焊点把上拉换成4.7k温度和室内明显不符偏高DHT11紧贴主板发热器件或供电电压过高导致自热让传感器远离MCU的LDO和功率管保持通风偶发校验失败采样间隔太短、读取时序被中断打断拉长轮询周期或在读取函数里暂时关闭中断第一次上电能读到之后一直失败上电瞬间GPIO默认电平导致DHT11进入异常状态初始化后先延时2秒再发起始信号其中“上电瞬间失败”最容易被忽略。F1的GPIO在复位后默认是浮空输入如果DHT11数据线恰好被板上的其他信号拉低传感器可能会上电就进入非正常状态。解决办法也很简单给DHT11的VCC单独加一个RC延时或者在主函数初始化后先放2秒钟再首次读取。5. 别让数据躺着睡觉三个低成本落地场景5.1 OLED显示SSD1306两线清爽上屏温湿度读出来了光在调试串口里看太浪费。最省事的做法是接一块0.96寸SSD1306 OLED屏I2C两根线搞定F1的硬件I2C在很多人手里容易出问题所以这里我建议直接用软件I2C模拟稳定且移植方便。先把SCL对应PB6、SDA对应PB7然后写一个OLED_ShowString(0, 0, Temp: 25.6 C)的显示函数刷新频率不用太高1秒一次刚好匹配DHT11的采样周期。SSD1306初始化序列网上模板很多核心就是把显存RAM写好、把对比度调好。显示文字时需要把ASCII码映射到16x8字模数组这部分代码量不小但F1的Flash完全放得下。我第一次做完OLEDDHT11后成就感比点亮LED大得多因为它给数据提供了一个“看得见”的出口。5.2 串口上报与设备联网USARTESP8266的经典组合如果想把数据传到电脑或云端最简单的路径是USART上报。F1的USART1接了USB转串口芯片配好波特率后重定向printf上位机就能实时收到温湿度。代码里只需重写fputc指向串口发送函数int fputc(int ch, FILE *f) { while (!(USART1-SR USART_SR_TXE)); USART1-DR ch; return ch; }再进一步接一个ESP8266模组做成Wi-Fi温湿度计F1通过USART2给ESP8266发AT指令用TCP或者MQTT协议把数据推到云平台。注意给ESP8266供电要单独稳压或者加电容否则Wi-Fi发射瞬间的电流会把MCU的电源拉垮出现“一联网就重启”的现象。这个组合很经典适合做智能家居的样板。5.3 把数据存进Flash做一个能断电恢复的记录仪有时候没有上位机也没有云平台我想要的是设备自己把历史温湿度记录下来。F1内部Flash就能干这事。STM32F103的Flash按扇区划分写之前必须先把整个扇区擦除而且擦写寿命约1万次所以不能每1秒都写一次。我的做法是每5分钟写一条记录一个扇区写满后轮换到下一个扇区同时把“当前写入位置”单独存到一个固定地址。这样既延长了Flash寿命又能在掉电重启后从上次位置继续记录。读Flash的代码比外挂EEPROM还简单直接用指针访问映射地址即可但记得操作前要关闭中断避免在擦写过程中被其他代码打断导致总线错误。这个场景我实际做过存了几百条温湿度记录后回读数据一条不丢F1的Flash虽然不算大但记录低频数据绰绰有余。5.4 低功耗改造睡眠唤醒模式下如何安排采样如果你做的是电池供电的温湿度标签F1的停止STOP模式值得好好利用。思路是默认让MCU睡在STOP模式每隔一段时间用RTC或者外部RTC闹钟唤醒唤醒后给DHT11上电延时2秒等它稳定读取一次数据存到Flash或通过低功耗蓝牙发出然后继续睡觉。这里有两个注意点一是DHT11本身在上电后需要1秒稳定时间采样又必须间隔1秒以上所以一次唤醒流程至少预留3秒二是如果传感器一直在供电它的自热会让温度读数比环境高几度最好在睡眠期间都把传感器的VCC断掉用GPIO控制一颗MOS管来切换电源。这套组合拳做完一节CR2032电池撑几个月很正常。6. 我在F1上踩过的坑这些经验值几个通宵6.1 串口乱码的元凶时钟树配错了第一次用国产F103小系统板时我按默认8MHz晶振配置串口结果电脑收到的全是乱码。后来拿示波器测了晶振发现板子上实际焊接的是12MHz晶振。CubeMX里改一下HSE参数PLL倍频自动调整好了串口立刻正常。这个坑提醒我一件事不要盲目信任板子丝印上电先用RCC_GetClocksFreq读一下实际系统时钟或者干脆用逻辑分析仪抓串口波形数波特率。6.2 DHT11数据跳变电源纹波远比想的更捣乱有次做环境箱测试DHT11读数稳定了一小时突然开始每隔几次就校验失败。最后发现是环境箱的加热丝每隔一段时间就开启一次瞬间电流导致电源电压跌落几毫秒DHT11在这种时刻恰好处于数据输出阶段波形被拉坏。解决办法是把传感器的供电从主电源改成独立LDO并且在数据线上加一个100pF的小电容滤掉高频毛刺。从此我养成了一个习惯任何传感器的供电电平和主控的供电地之间必须保证干净。6.3 调试口被复用的教训SWD引脚别乱接F1的SWD调试口占用PA13和PA14这两个引脚同时也是普通IO。我在一个项目里为了让LED多用一个引脚把LED接到了PA13结果第一次烧录正常第二次就再也连不上调试器。因为程序一旦跑起来代码把PA13初始化成了推挽输出改变了SWDIO的电平波形ST-Link自然无法握手。从那以后我设计的板子都会特意避开PA13/PA14除非空间实在不够否则不给调试留隐患。6.4 5V容忍引脚的误判不是所有引脚都能直连5VDHT11用5V供电时DATA线上的高电平是5V。F1的大部分IO能容忍5V但ADC相关的PA0~PA7在手册里明确标注“不是5V容忍”。我见过有人把5V输出的传感器信号直接接到PA1上做ADC采集结果是ADC读数异常且芯片发热原因就是超过了引脚的绝对最大额定值。接任何外部信号之前查一下对应引脚的FT标识这是嵌入式工程师的基本素养。6.5 给F1新手的五项建议最后压箱底地分享几条长期经验拿到板子第一件事先点灯别急着调传感器工具链可靠是前提。遇到奇怪问题先查时钟再查电源最后才怀疑代码逻辑。写时序驱动之前先把手册里的时序图画在纸上标清每个高电平低电平的时间范围。调试温湿度这类慢速信号逻辑分析仪比示波器好用几十块钱的就能让你看清完整波形。原理图上给关键信号留测试点哪怕只是过孔你调试时会感谢自己。这次把DHT11接上F1核心本质其实就是处理一条线的高低电平。2025年了各种传感器都开始转I2C、SPI但“一根线干所有事”的单总线思维依然是嵌入式工程师的基本功。F1老归老在这个学习路径里它依然是试错成本最低、生态资料最厚的那块训练场。能用一颗几块钱的芯片把时序啃明白再去看I2C、SPI这些带时钟线的协议你会觉得一切都顺畅得多。
返回列表