news 2026/10/6 7:15:52

STM32F1入门实战:从Cortex-M3到DHT11温湿度驱动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F1入门实战:从Cortex-M3到DHT11温湿度驱动

1. STM32F1到底是个什么定位

1.1 一颗芯片撑起整个入门生态

先说结论:STM32F1是基于ARM Cortex-M3内核的32位微控制器,主频最高72MHz,Flash从16KB到512KB不等,RAM从4KB到64KB。这个参数放在今天看并不惊艳,但它几乎是整个中文互联网上教程最多、资料最全、开发板最便宜的单片机,没有之一。你在淘宝搜"STM32F1",能看到几块钱一片的裸芯片、十几块的迷你开发板、几十块带屏幕带传感器的套餐板,价格低到你甚至不好意思说它是32位平台。很多人的嵌入式生涯就是从这一颗Cortex-M3开始的。

为什么F1能火这么多年?硬件上它确实没什么"黑科技",但生态带来的价值远超过芯片本身。无论是刚学会C语言的大学生,还是从51单片机转过来的电工,第一句看到的教程几乎都是"STM32F1是什么"。配套的库函数、标准外设库、HAL库、寄存器版本的例程、各种传感器的驱动代码,在GitHub和CSDN上多到看不完。STM32F1这颗芯片的出现,把原来只属于工程师的32位单片机门槛拉低到了普通电子爱好者的桌面,这让它成了事实上的"行业入门教材"。

我自己最早的STM32F1项目就是用一个蓝板子(就是那种十几块钱的STM32F103C8T6最小系统板)驱动DHT11温湿度传感器,把温湿度数据打到串口助手和一块0.96寸OLED上。很多人以为做这种项目只是为了好玩,但实际上它把GPIO、延时、串口、I2C(或SPI)、传感器时序这些东西全串起来了。做完这一个,后面再玩任何外设都不会再有"无从下手"的感觉。

1.2 F1家族成员怎么分清楚

STM32F1不是一个具体型号,而是一个庞大的家族。最常用的子系列有F101、F102、F103,还有一些带后缀的如F105、F107。其中F103系列是绝对的销量冠军,也是最合适拿来学习的。

  • F101:基础型,主频36MHz,没有USB、没有CAN,功能少但便宜。
  • F102:USB基本型,带USB设备控制器,适合做USB转串口这种应用。
  • F103:增强型,主频72MHz,Flash、RAM、外设数量都最均衡,是学习首选。
  • F105/F107:互联型,带USB OTG和以太网MAC,适合做网络类产品。

芯片命名里还有容量标识需要看懂。比如STM32F103C8T6,中间的"C8"表示64KB Flash,"C"代表48引脚封装,"8"代表64KB。同理"R6"是64引脚+32KB,"Z"是144引脚。后面"T6"指示封装是LQFP48,温度范围是-40℃到85℃。这些信息在选型时很重要,因为同一个F103,引脚数不同、Flash不同,外设复用关系也不一样,代码虽然通用,但接线和启动文件却有区别。

我见过不少人买了STM32F103ZET6(144脚大板)把例程烧到STM32F103C8T6(48脚小板)上,结果下载报错或者程序跑飞。原因就在于Flash大小不一样,启动文件可能选错。所以确认型号再选启动文件是第一课。

这颗芯片能做的事,可以从一个很直观的维度来理解:它相当于把一台主频72MHz的小电脑、一组丰富的I/O口和通信接口集成到一个芯片里,Cortex-M3内核支持硬件乘除法指令,跑一些轻量级的算法、状态机、Modbus协议栈、PID控制完全没问题。虽然它没有MMU跑不了Linux,但针对"采集传感器数据、控制电机、与人机界面交互"这类典型的物联网终端任务,STM32F1是性能和成本的平衡点。

2. 开发环境与工程搭建完整指南

2.1 工具链怎么选

开发STM32F1的主流方案大致有三套:标准外设库(Standard Peripheral Library,简称SPL)、HAL库(Hardware Abstraction Layer)、寄存器直接操作。三套方案各有侧重,没有绝对的优劣。

标准库是ST官方在F1时代主推的封装库,把寄存器操作封装成一个个函数和结构体,比直接写寄存器省心,比HAL更轻量。ST在2019年前后停止了对标准库的维护,但它依然是F1最常用、历史资料最多的库。大量老教程、CSDN博客都是基于标准库写的。

HAL库是ST现在主推的库,配合STM32CubeMX图形化配置工具使用,可以自动生成初始化代码,跨系列移植性好。代价是函数调用层级深、代码量大、效率略低,但在F1上跑HAL库绰绰有余。

寄存器操作就是直接读写寄存器地址,效率最高、对芯片理解最深,但开发速度慢,只适合研究原理或做极限性能优化。

我给新人的建议是:如果只是学F1,用标准库加一句注释"ST公司已停止更新但很经典";如果你打算以后往F4、F7、H7系列走,直接学HAL库更划算,因为CubeMX生成的代码风格在各系列间是统一的。我自己在开发产品的时候,更多是用HAL库配合CubeMX做工程骨架,再自己写业务逻辑。

开发IDE方面,最主流的组合是Keil MDK。虽然界面老旧,但启动快、调试方便、破解教程遍地都是。CLion搭配STM32CubeMX插件和OpenOCD也是不错的选择,对代码补全和Git支持比Keil好太多,适合喜欢现代IDE的开发者。如果你装了VS Code也可以用PlatformIO,它内置了F1的支持,写代码体验也不错。工具链的差异不会影响最终程序,选自己用着舒服的就好。

2.2 最小系统和工程创建的坑

STM32F1最小系统非常简单,核心就是四块:3.3V电源、复位电路、8MHz晶振(外部晶振可以省略,但建议保留)、BOOT0引脚处理。这块内容网上资料很多,我只讲实际板子上最容易出问题的三件事。

第一是电源。F103的IO引脚对外输出能力有限制,典型值是灌电流25mA、拉电流20mA左右。直接用IO驱动继电器、电机这类大功率负载基本不现实,必须通过三极管、MOS管或专门的驱动芯片来扩展。很多新手做DHT11这类传感器项目没问题,但一驱动的器件多了,3.3V稳压器就发热甚至重启,这时候要检查是不是某路负载电流超标了。

第二是外部晶振。CubeMX生成的工程里,如果选择了外部晶振(HSE)作为系统时钟源但板子上没焊接晶振,程序就会卡在初始化等待HSE就绪的死循环里,表现就是程序下载后不运行、调试停在HAL_RCC_ClockConfig处。开发板通常默认有8MHz晶振,但自己用最小板做东西时一定要确认。

第三是启动模式。STM32F1有两个引脚BOOT0和BOOT1控制启动方式。BOOT0拉低从主Flash启动,这是正常模式;BOOT0拉高且BOOT1拉低会从系统存储器启动,即进入串口ISP下载模式;BOOT0和BOOT1都拉高则从内置SRAM启动,主要用来调试。开发板一般都设置好了,但如果自己做板子,BOOT0最好加一个10k下拉电阻,避免悬空导致启动状态不稳定。

工程创建方面,如果你用CubeMX,流程是:选芯片型号→配置时钟树(外部晶振8MHz→倍频到72MHz)→配置外设(串口、GPIO等)→生成工程代码。这里有个关键参数别搞错:CubeMX中HCLK要改成72MHz,如果只改倍频不选总线分频,可能导致外设时钟频率不符合预期。我自己见过好多人在这一步选择了72MHz,但又同时默认打开了某个PLL分频,结果系统时钟跑在36MHz,串口波特率怎么调都不对。

2.3 烧录和调试方式区别

STM32F1支持三种常见烧录方式:JTAG、SWD、串口ISP。

JTAG需要20针接口,速度快、功能多,但占用的引脚也最多(PA13-PA15、PB3-PB4等)。SWD只需要两根线:SWDIO和SWCLK,加上地线共三根,非常节省引脚,是所有开发板的标配调试接口。我日常调试都是用ST-Link V2通过SWD连接,便宜、稳定、速度快,推荐新手直接买。

串口ISP则是在没有调试器的时候用串口下载程序。把BOOT0拉高、按一下复位、通过串口工具选择hex文件烧写。这种方式的缺点是只能下载程序,不能在线调试,没有断点、没有变量观察,效率和体验都差不少。但作为一种备用方案,特别在调试器丢失或者目标板不适合接SWD时还是很管用的。

调试器驱动也是新人容易卡住的地方。装完ST-Link驱动后,设备管理器里如果看不到ST-Link设备,通常是驱动没装好或线序接错了。SWD接线其实就三个信号:SWDIO、SWCLK、GND,目标板要上电,调试器也要识别到目标芯片。有一种特例:如果用SWD连接不上,先按住复位键,连接前一瞬间松开,有时候能绕过低电平锁定状态。

3. 核心外设的技术要点与原理剖析

3.1 GPIO不是简单的高低电平

STM32F1的GPIO比51单片机复杂得多,每个引脚都可以配置成输入、输出、复用、模拟四种模式,每种模式又有细分。最常用的输出模式是推挽输出(Push-Pull)和开漏输出(Open-Drain)。

推挽输出能主动输出高电平和低电平,带负载能力强,日常驱动LED、控制逻辑电平都用这个。开漏输出只能主动拉低或释放(高阻态),高电平需要外接上拉电阻提供,常用于I2C这类需要"线与"的总线,或者需要实现电平转换的场景。

输入模式中,浮空输入、上拉输入、下拉输入要分清。如果外接的传感器在无信号时处于高阻态,就要用上拉或下拉输入来固定电平,否则引脚会悬空,检测到的电平随机跳变。DHT11正好就是这种情况:它空闲时靠外部上拉电阻维持高电平,主机要读数据时引脚要先处于输出模式发送起始信号,再切回输入模式读取响应和数据。GPIO模式的切换是DHT11驱动里最核心的一环,很多人第一次接触"一个引脚既要输出又要输入"会懵,搞懂这一点就等于入门了。

GPIO还有一个重要知识点是复用功能(AFIO)。USART的TX/RX、定时器的PWM输出、SPI和I2C的信号线,都是通过复用功能映射到具体引脚的。F1的引脚复用不像F4那么自由,一个外设引脚基本上固定在某一组引脚上,所以查数据手册的Alternate function mapping表格比死记硬背更高效。我自己做硬件接线之前,一定会把CubeMX引脚配置页面打开,先把外设引脚分配好再画原理图,这样可以避免后面软件里发现引脚冲突又要改板子的尴尬。

3.2 定时器:F1最值钱的外设

STM32F1内部有多个定时器,按功能分成三类:基本定时器(TIM6/TIM7)、通用定时器(TIM2/3/4/5)、高级定时器(TIM1/TIM8)。基本定时器只能做定时,通用定时器在定时基础上还能输出PWM、做输入捕获、编码器接口,高级定时器则多了互补输出和刹车功能,专门为电机控制设计。

定时器定时的本质是:一个计数器在一个时钟源驱动下不断累加,从0数到自动重载值(ARR),数到顶就产生更新事件并将计数器清零。只要算好预分频值(PSC)和自动重载值,就能得到任意想要的定时时长。公式很简单:定时时间 = (PSC+1) × (ARR+1) / 定时器时钟频率。比如定时器时钟72MHz,想让定时器1ms溢出一次,可以设PSC=71(即72分频),那么计数频率变成1MHz,即每1us计数一次,ARR=999表示从0数到999需要1000us,正好1ms。

这个计算方式几乎所有F1开发者都躲不开,做延时、做周期采样、做按键消抖、做PWM输出都要用到。PWM输出就是让定时器的输出引脚在计数器小于某个比较值(CCR)时输出高电平,大于时输出低电平,通过改变CCR就能改变占空比,通过改变ARR能改变频率。用这种方式驱动LED渐亮渐暗、控制舵机角度、控制直流电机转速,都是最基础的项目。

定时器输入捕获则用于测量外部信号的频率或脉宽。原理是当引脚出现上升沿或下降沿时,硬件自动把当前计数器的值保存到捕获寄存器里,软件再算出前后两次捕获的差值,乘以计数周期就是时间。超声波测距模块HC-SR04就是靠这个功能测量回波时间来计算距离的。可以说,用好定时器,你的STM32F1水平已经超过一半的新手了。

3.3 USART串口通信的几个细节

USART(通用同步/异步收发器)是STM32F1里最常用、也最能反映一个工程师基本功扎实与否的外设。它的核心任务是把并行数据变成串行数据按帧发送,以及从串行数据恢复出并行数据。说到串口,很多人第一反应是"能打印日志就行",但实际调试中串口往往给你埋了很多坑。

第一个坑是波特率误差。串口通信要求收发双方的波特率误差不能太大,一般容差在2%到3%以内。STM32F1的波特率由串口时钟、USARTDIV分频值共同决定。如果你的系统时钟不是精确的72MHz(比如用了内部HSI 8MHz经过PLL倍频到64MHz而不是72MHz),那么用57600、115200这类波特率时误差可能超标,导致乱码。排查串口乱码首先要确认系统时钟,而不是怀疑代码逻辑。

第二个坑是引脚复用和重映射。F103的USART1固定在PA9(TX)和PA10(RX),但也可以重映射到PB6和PB7,USART2可以重映射到PD5和PD6。很多开发板的丝印上标了"A9/A10 串口1",但有些板子为了布线方便把串口重映射到了别的引脚,代码里如果不开启AFIO重映射,数据从错误引脚出来自然收不到。在CubeMX里配置串口时,它会自动检查引脚冲突并给出提示,这也是我推荐新手先用CubeMX的原因之一。

第三个坑是接收中断的使用方式。很多例程都用"接收一个字节进一次中断"的方式,但实际项目里数据往往是一帧一帧到达的,比如一个协议帧8个字节。正确做法是在中断里把每个字节存进环形缓冲区,主循环再按帧解析。直接在主循环里用阻塞方式等待接收数据,会严重拖慢系统,而且在高速通信时几乎必定丢字节。

串口还有一个容易被忽略的功能:半双工模式下的单线通信。DHT11实际上是单总线协议,跟串口半双工模式有点类似,但DHT11的时序是微秒级别的,USART不一定能直接适配它,所以更多人选择GPIO模拟时序而不是硬接串口。这个我在第四部分详细说。

3.4 I2C和SPI在F1上的注意事项

I2C和SPI是另外两个必须掌握的总线协议。I2C用两根线(SCL时钟线、SDA数据线)就能挂多个设备,每个设备有独立地址,适合连接温湿度传感器、EEPROM、OLED这类低速设备。SPI用四根线(SCLK、MOSI、MISO、CS),通信速率高,适合Flash存储器、SD卡、显示屏这类需要高速传输的设备。

STM32F1的硬件I2C模块历史上口碑不佳,主要是它的状态机设计复杂,程序容易进BUG,早期网上甚至流传"F1的I2C有问题,最好用软件模拟"。实际情况是硬件I2C能用,但注意中断处理和超时判断的细节很多。很多成熟的工程师在F1上宁可GPIO模拟I2C,也不愿意花时间去调硬件I2C的坑。SPI则稳定得多,直接使用硬件SPI配合DMA,可以做到高速无CPU干预传输。

这里给一个选型思路:如果你在F1上连接的是一个地址固定的传感器(比如DHT11、DS18B20、OLED),单总线或I2C软件模拟最顺手;如果连接的是大容量数据(如W25Q64 Flash、SD卡),用硬件SPI加DMA。做项目优先考虑稳定性和自己熟悉的方式,而不是一味追求"用硬件外设"的好看名头。

4. 实战:基于STM32F1驱动DHT11温湿度传感器

4.1 DHT11传感器的工作机制

DHT11是一个典型的单总线温湿度传感器,内部包含一个电阻式测湿元件、一个NTC测温元件和一个8位单片机。它通过一根数据线(DATA)与主机通信,在同一根线上既发送数据又接收指令,数据的每一位都是靠高低电平的宽度来区分的。它一次通信需要传输40位数据:8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。校验和的计算方式是前四个字节相加,低八位如果等于第五个字节,就认为传输无误。

DHT11的精度并不高,湿度精度±5%RH,温度精度±2℃,测量范围湿度20%-90%RH、温度0℃-50℃。所以它更适合做环境监测、智慧农业的简单采集、室内舒适度判断这类对精度不敏感的场景,而不是实验室级别的精密测量。如果项目需要高精度,应该选SHT30或HTU21D这类数字传感器。

DHT11的供电范围是3.3V到5.5V,所以5V也可以驱动。但在STM32F1这样的3.3V系统里要注意:如果传感器模块板上带有稳压和上拉电路,接到3.3V最保险;如果板子是直接用5V供电的,DATA引脚输出的高电平也可能接近5V,直接连到STM32的3.3V引脚上可能超压。针对这一点,最稳妥的电路设计是模块用3.3V供电,数据线上接一个4.7kΩ到10kΩ的上拉电阻到3.3V。多数市售模块自带板载上拉电阻,但自己做板子时务必自己加。

4.2 通信时序详解:读懂这张图就成功了

DHT11单总线通信的时序非常固定,可以分成四个阶段。第一次接触的人先去网上找一张"时序图"照着看,就会发现所谓的驱动其实就是在正确的时间点把GPIO拉高或拉低。

第一个阶段是主机发送起始信号。主机先把数据线拉低,保持至少18ms(实际编程中一般用20ms),然后释放总线(拉高),让上拉电阻把电平拉回高电平。这个低电平脉冲的作用是让DHT11从低功耗模式唤醒并准备应答。

第二个阶段是DHT11响应。主机释放总线后,DHT11会在20us到40us之后把总线拉低,持续约80us,表示"我准备好了";随后再拉高约80us,告诉主机"准备开始传数据"。如果这个响应信号迟迟不来,多半是接线错误、上拉电阻缺失、或者传感器供电异常。

第三个阶段是数据位传输。DHT11每发送一个数据位,都先把总线拉低约50us作为位同步信号,然后释放总线。释放之后总线维持高电平的时间决定了这位是0还是1:如果是0,高电平持续约26us到28us;如果是1,高电平持续约70us。简单说,"低电平时间固定,高电平时间区分0和1"。

第四个阶段是通信结束。DHT11发完40位数据后把总线拉低约50us表示结束,然后释放总线进入空闲状态,等待下一次主机起始信号。

这四十个比特的读取方式是:在检测到每个数据位开头的下降沿之后,延时30us到40us再读取引脚电平。如果引脚为高,说明这一位是高电平约70us的"1";如果引脚为低,说明这一位是"0"。这个延时窗口非常关键,如果延时太短,可能读在高电平的起始处,如果太长又可能错过。实践中常见的做法是延时40us后读一次,因为在40us这个点上,"0"的26us高电平已经结束了(引脚回到低),而"1"的70us高电平还在持续,区分效果最好。

4.3 从零写一版DHT11驱动代码

先讲硬件连接。以STM32F103C8T6最小系统板为例,DHT11模块的VCC接3.3V,GND接GND,DATA接PA0。如果你的模块没有板上上拉,在PA0和3.3V之间加一个10kΩ电阻。接线完成后的初始化十分简单:把PA0配置为推挽输出,先输出高电平,让总线处于空闲状态。

然后是微秒级延时函数的问题。HAL库的HAL_Delay()只能保证毫秒级精度,DHT11的时序需要几十微秒的延时,所以必须自己写一个us级延时。最简单可靠的方案是用SysTick定时器做一个基准。SysTick是一个24位递减计数器,给它配置好重载值后,每过1us产生一次中断,用一个全局变量累加,delay_us()函数读这个变量的差值来实现延时。在72MHz主频下,重载值设72-1就是1us一次中断。但这个方案有一个缺点:中断频繁会影响CPU效率,而且要求你已经正确配置了SysTick。

另一个常见方案是for循环空转粗略延时:

void delay_us(uint32_t nus) { // 72MHz下约循环8次接近1us,实际需用示波器或逻辑分析仪校正 for (uint32_t i = 0; i < nus * 8; i++) { __NOP(); } }

这种方式的精度和编译器优化等级直接相关,必须在-O0或当前优化等级下实测校正。项目正式场合更推荐用定时器方式做us延时。我个人的习惯是统一用一个TIM定时器做微秒延时服务,不管是OLED驱动、DS18B20还是DHT11都用它,这样时序精度可控又不互相干扰。

DHT11主机起始信号发送和读取代码的核心结构如下:

// 发送起始信号 void DHT11_Start(void) { GPIO_SetPinOutput(DHT11_PORT, DHT11_PIN); // 改为输出模式 GPIO_WriteLow(DHT11_PORT, DHT11_PIN); // 拉低总线 delay_us(20000); // 至少18ms,用20ms稳妥 GPIO_WriteHigh(DHT11_PORT, DHT11_PIN); // 释放总线 delay_us(30); // 释放后等30us再切输入 GPIO_SetPinInput(DHT11_PORT, DHT11_PIN); // 改为输入模式 }

注意这里GPIO模式切换的代码,是驱动里最容易出错的地方。如果默认把GPIO配置成了推挽输出,那么输入模式下必须把引脚脚配置为上拉输入或浮空输入,同时外部又有上拉电阻,才能正确读到高电平。如果固件库配置不当,读到的一直是低电平,DHT11的数据就全是0。

读取一个字节和读取一帧数据的代码逻辑:

uint8_t DHT11_ReadByte(void) { uint8_t value = 0; for (int i = 0; i < 8; i++) { // 等待低电平开始(位同步信号) while (GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == 0); delay_us(40); // 重点:延时40us后读电平 value <<= 1; if (GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == 1) { value |= 1; } // 等待高电平结束,进入下一位 while (GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == 1); } return value; }

完整读取一帧的过程是:先启动通信(拉低20ms→释放→等应答),然后读取5个字节。读完后先做校验:如果byte[0]+byte[1]+byte[2]+byte[3]的低八位等于byte[4],这帧数据有效,湿度是byte[0],温度是byte[2]。如果校验失败,建议丢弃这一帧,过2秒再重新采样。DHT11推荐的采样周期是1秒以上,你采集再频繁它也不会给出新数据,反而可能让时序错乱。

另外,每次读取前主机起始信号至少18ms,但也不能太长。有人图省事把20ms改成50ms甚至100ms,这在多数模块上也能工作,但不推荐。DHT11在唤醒后需要准备时间响应主机,如果起始低电平过长,它的内部单片机可能已经把这一电平当成了一次通信开始,时序就会乱。严格按照数据手册是20ms,实测下来这是最稳的。

4.4 实测数据与调试方法

把上面的代码下载进STM32F1,通过串口打印湿度、温度数据,不出意外的话你应该能看到类似"Humidity: 56.0% Temperature: 24.0°C"这样的输出。如果你看到的湿度或者温度是0,或者数据跳动剧烈,不要急着改代码,先按顺序排查:

  • 接线是否正确,DATA是否真的接在PA0上,模块供电电压是否在规格内。
  • 数据线上的上拉电阻是否正常。无上拉时,空闲高电平无法维持,通信必失败。
  • 读取时序中40us的延时是否准确。用逻辑分析仪抓取总线波形,对比提前提到的时序图看高电平宽度是否在28us和70us附近。
  • 是否在上一次通信结束后等够了1秒以上再发下一次起始信号。

我在实际调试时最常用的工具是逻辑分析仪,几十块钱的那种就行,把DHT11的数据线接到分析仪通道上,抓一次完整通信过程,直接看波形。比盲改代码高效得多。如果你想自己确认延时的误差,也可以在GPIO翻转上做一个辅助引脚,比如每次读完一个位翻转一次PB0,用示波器测PB0的波形周期来反推代码运行时间。

DHT11在量产级项目里其实不是首选,它的精度和一致性只能说够用。做一批产品,不同批次的DHT11读出来的值可能有差别,这是器件本身的一致性决定的。对电路可靠性和数据准确性要求高的项目,DHT11只能用来做功能演示和原型验证,不能直接用在计量级产品上。这是选择传感器时必须清楚的认知。

5. 常见问题与排查技巧实录

5.1 程序下载失败的三种典型原因

下载失败是STM32F1新人遇到最多的故障,我总结基本逃不出三类。

第一类是调试器连接不上。典型现象是Keil报No target connected或者Connection error。原因通常是SWD线序接反、目标板没供电、或者芯片上一次烧录时禁用了SWD引脚。前两个好解决,第三类比较隐蔽:如果代码里将PA13或PA14(也就是SWDIO和SWCLK)配置成了普通GPIO并输出,调试接口就会被关闭。解决办法是在下载时按住复位键,点击下载后马上松开,让芯片在复位瞬间能被调试器连接上。根治办法是把这两个引脚设置为复用、不使能SWD调试引脚的方案,换用串口ISP烧录恢复。

第二类是能识别芯片但擦除失败。常见原因是芯片读保护被开启。在Keil的Flash Download页面勾选"Reset and Run"之外,还要确认没有开启读保护,或者通过调试器的整片擦除命令解除保护。老版本固件库的某些擦写代码也可能触发意外保护,这种情况换新版固件库就行。

第三类是程序能烧进去但不运行。这里要先查BOOT0引脚的电平,再查复位电路。个别开发板SWD下载完会自动复位运行,有些板子则需要手动按复位键。另外检查CubeMX配置中System Core > SYS里的Debug选项,如果选的是No Debug,调试器在复位后可能无法接管芯片。解决方法是把这个选项改为Serial Wire,这也是很多"开发板程序跑不起来"的隐藏原因。

5.2 时钟配置错误引发的诡异故障

时钟配置错误的表现非常多样:串口波特率不对、定时器计时不准确、PWM频率偏差、系统运行变慢但功能正常。这些问题的实质性原因是芯片实际运行的时钟频率和你代码里假设的频率不一致。

F103外部晶振通常为8MHz,经PLL倍频后得到72MHz系统时钟。如果外部晶振换成了12MHz,而代码里按照8MHz配置PLL,实际系统时钟会变成108MHz,超频到极限可能导致程序不稳定;反过来如果外部晶振没有焊接,CubeMX默认HSE方式初始化时程序直接卡死等待。所以第一步永远是确认硬件上实际使用的晶振频率,再去配置时钟树。

第二种情况是HSI内部8MHz时钟作为PLL输入,默认情况下PLL倍频系数如果设定为9,得到72MHz也无问题,但HSI精度不如外部晶振(误差可达1%),串口通信的波特率误差就会偏高。如果项目有串口通信需求,建议外部晶振必须存在,同时做通信测试时用示波器测TX引脚的实际波形频率,才能确认时钟是否精确。

调试时想快速判断系统时钟,最直接的方式是在主循环里周期性翻转一个GPIO,用示波器或逻辑分析仪测它的精确频率。如果翻转周期为100ms(如HAL_Delay(100)加翻转),实测却是110ms,那说明系统时钟跑在了实际频率的90%左右。

5.3 DHT11数据异常的专项排查

DHT11这类单总线传感器出问题时,现象往往就那么几种,但背后的原因各不相同。

  • 数据全部是0:通常是GPIO没有正确切换到输入模式,或者切换后引脚被配置成浮空输入但缺少上拉电阻,总线空闲时电平不确定,读到的一直是0。处理方法是检查配置代码,确保空闲态为高。
  • 数据全是255:通常是GPIO输入配置成了上拉输入,但传感器发送的低电平不能把总线拉到足够低的水平,多数情况是因为模块上DATA没有正确接地,或者接线松动。
  • 数据随机跳变:一般是时序延时不准确,40us的延时偏差过大。建议用定时器延时替代for循环延时,并抓波形对照。
  • 第一次读取失败、第二次成功:因为DHT11上电后的稳定时间不够。在初始化完GPIO后先延时1秒再开始第一次通信,能缓解这个问题。

还有一个容易忽略的点:DHT11模块的DATA引脚如果和STM32之间串了一个过大的电阻(比如10kΩ串联而非上拉),会让电平转换变慢,导致时序边沿不陡峭。用示波器看波形的话,可以发现上升沿缓慢上升而不是陡峭跳变,这种情况下通信都会变得不稳定。正确的接法是上拉电阻接到3.3V,而不是在数据线上串联电阻。

5.4 性能优化与资源占用心得

STM32F1的性能上限是72MHz,在当代MCU里不算强,但用好它的资源需要一些思路上的转变。

第一是DMA一定要用起来。串口发送、SPI读写、ADC采样,这些场景都适合DMA。用DMA搬运数据时CPU可以去做别的事情,处理几十个字节的协议帧、刷新OLED、读取按键扫描都不会卡顿。很多人觉得DMA复杂不敢碰,其实它就是把"源地址、目标地址、长度"设置好,然后启动传输,完成时触发中断,你就收到了一个"搬运完成了"的通知。

第二是中断优先级要规划。NVIC中抢占优先级和子优先级各用多少位,由SCB->AIRCR寄存器决定。一个实用的配置是用优先级分组2,即2位抢占优先级、2位子优先级,这样最多可以安排4个抢占优先级等级,足够大多数项目使用。SysTick应该设置为最低优先级,它只是一个时基基准,不应该打断关键通信中断。

第三是结构体加状态机的编程方式远比散乱的全局变量好维护。DHT11的采集状态可以定义为一个枚举:IDLE、START、WAITING、READING、DONE,主循环里根据状态执行对应的步骤。这种写法在一个项目里有多个传感器时尤其有用,不会出现一个传感器读数据时另一个传感器被阻塞的情况。

6. F1之后往哪儿走

我在做完DHT11这个项目之后,又陆续用STM32F1做过遥控小车、倒车雷达、电压采集记录仪、简易逻辑分析仪。这颗芯片帮我把C语言、硬件电路、调试方法论串成了一条完整的技能链。如果你已经能用F1驱动温湿度传感器并且掌握了信号时序分析、异常排查这些方法,接下来可以考虑往两个方向走。

一个是往更高的MCU平台走。STM32F4系列有Cortex-M4内核和FPU,主频跑到168MHz甚至更高,DSP指令让数字信号处理应用得心应手,适合做FOC电机控制、音频处理、更多的传感器融合。H7系列则是双核Cortex-M7加M4,性能更强,但学习曲线也陡。你从F1转到F4,CubeMX生成的工程骨架几乎一样,HAL库API风格的差异也不大,最大的工作量在熟悉新外设的特性和引脚复用关系。

另一个方向是往物联网系统走。STM32F1跑个轻量级RTOS(如FreeRTOS),加上Wi-Fi模块(ESP8266或ESP32),就能组成一个温湿度数据上云的终端。这时你会接触到协议栈、MQTT、JSON解析、远程OTA这些东西,原来的单片机程序从"裸机跑一个大循环"变成"OS调度的多任务系统",复杂度又上一个台阶。但这正是F1在物联网终端里最常见的真实形态:MCU负责本地数据采集和控制,通信模块负责上云。

回到标题本身,STM32F1系列真正不可替代的价值,是它用极低的成本提供了一个足够复杂的硬件平台,迫使每个开发者去理解时钟树、中断、GPIO复用、总线时序这些底层的嵌入式概念。你把F1吃透了,后面学任何别的MCU都会很快,因为MCU的内核、外设、开发方法大同小异,差的只是细节。至少我自己的经历是这样:当年花了一个星期让DHT11的数据成功出现在OLED上,那种成就感直到今天都还在支撑我继续写代码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 7:15:31

电位器三引脚接线全攻略:从识别到故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:13:00

SERDES高速接口实战:从并行瓶颈到FPGA链路调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:12:34

FPGA里的CORDIC IP核:三角运算、相位幅度转换与Vivado配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:12:32

全贴片Kazzo烧录器改造:STM32+CPLD与PCB设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:12:32

网络监控拓扑图选型与落地:从SNMP到VRRP的高可用监控体系

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:12:26

DeepSeek多平台本地部署实战:Ollama+Open WebUI打造私有AI服务

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华