STM32智能温控风扇:从DHT11采集到PWM调速的完整嵌入式实践
这个STM32多功能智能温控风扇项目我前前后后做了两周多从画原理图到Proteus仿真再到打板调试中间踩了不少坑。现在把完整工程开源出来交付物包含三块可编译的工程代码、PDF格式的原理图、以及Proteus仿真工程。主控用的是STM32F103C8T6温度传感器采用DHT11风扇用MOS管加PWM调速板载了OLED显示屏、独立按键和蜂鸣器支持自动温控和手动调速双模式。可以说一个嵌入式项目里最常用的外设驱动、定时器PWM、状态机设计和通信协议解析这个项目基本都覆盖了。如果你是刚把51单片机玩明白、想往STM32过渡的初学者或者正在愁课程设计选题又或者想找个完整工程学习一下代码结构和硬件设计套路这个项目都很适合。文章里我会把设计思路、原理图要点、关键代码逻辑、仿真调试过程全部拆开讲清楚包括那些文档里不会写的坑。你拿着这份资料自己把工程跑一遍收获比单纯看十篇教程都大。1. 项目整体设计与思路拆解1.1 功能需求拆解一个温控风扇到底要做什么做硬件项目第一件事不是画板子而是想清楚这个产品要解决什么问题。这个温控风扇的核心场景很明确环境温度升高时风扇自动加速温度降下来后风扇慢慢降速甚至停转。这比传统那种“一通电就满速”的普通风扇体验好很多而且省电、安静。围绕这个核心需求我把功能拆成了这么几块温度采集实时读取环境温度这是整个系统的“眼睛”。这里选用DHT11它把温度传感器和湿度传感器封装在一起单总线协议输出一根数据线就能读回40位数据性价比很高。风扇调速根据温度值动态调节风扇转速。转速控制不能靠简单的高低电平要输出PWM波通过改变占空比控制平均电压从而实现无级调速。模式切换增加一个按键在“自动温控模式”和“手动调速模式”之间切换。手动模式下面再分几个固定档位方便用户强制控制风扇转速。信息显示用OLED实时显示当前温度、湿度、运行模式、风扇占空比。调试的时候这个屏幕帮了大忙数据对不对一眼就能看出来。状态提示加一个蜂鸣器按键按下时提示音温度超过告警阈值时连续响铃。这套功能组合下来整个项目涉及的知识点就非常立体GPIO操作、定时器中断、PWM输出、单总线协议、I2C显示驱动、按键消抖、状态机编程基本把STM32入门的核心技能都串起来了。1.2 主控选型为什么是STM32而不是51单片机很多人会问做个风扇用51单片机不就行了吗确实51也能做但如果你以“学习ST意法半导体STM32”为目标或者想产品化主控用到Cortex-M3内核的STM32F103C8T6几乎是最佳起点。从硬件资源上对比STM32F103C8T6拥有72MHz主频、64KB Flash、20KB SRAM、4个16位定时器、3个USART、2个I2C、2个SPI、12位ADC。光是定时器输出独立PWM就很舒服51要模拟PWM往往得靠软件延时在中断里翻转IO口占用CPU不说精度也受限。而DS18B20这类单总线传感器对时序要求比DHT11更严格甚至需要微秒级别的精确延时。在72MHz的STM32上我们能用定时器或者写精准的延时函数来处理这些时序效率高很多。功耗上我就不多吹了反正这芯片在市场上稳定供货多年资料丰富到让人眼花缭乱。当然如果你目前手头只有51开发板先用51验证一下整体逻辑也行但最终这个开源工程是围绕STM32F103C8T6设计的所以强烈建议以这款芯片准没错。1.3 系统架构与工作流程整个系统的数据流是这样的STM32上电后先做系统初始化包括时钟配置、GPIO模式设置、定时器PWM初始化、I2C初始化、DHT11传感器上电等待。然后进入主循环主循环里主要做三件事周期性读取DHT11数据、根据当前模式计算目标PWM占空比、刷新OLED显示。按键采用中断或轮询方式检测每次按键动作会改变模式或手动档位蜂鸣器响一声作为确认。主循环的频率不需要太高DHT11的采样间隔至少要在1秒以上所以我把温度采集设计成每1秒执行一次。PWM占空比的更新则放到定时器中断里做保证转速调整的实时性。2. 硬件设计原理图解读与器件选型2.1 STM32最小系统别在电源和晶振上省钱原理图的核心是STM32F103C8T6最小系统。网上很多最小系统原理图可以直接参照但有几个细节我要反复提醒。电源部分USB或者外部5V输入后经过AMS1117-3.3稳压芯片转成3.3V给MCU供电。AMS1117前面要加一个10uF钽电容做输入滤波输出端加0.1uF陶瓷电容和一个10uF电容并联这样在负载突变时电压更稳定。STM32F103C8T6的数字电源和模拟电源引脚最好分别接0.1uF去耦电容每个电源引脚都要放不要省这几个电容钱。晶振电路8MHz主晶振并联两个20pF负载电容配合内部时钟电路起振。有些方案说可以不用外部晶振直接用内部HSI但内部RC振荡器精度远不如外部晶振DHT11时序和USART波特率都会受影响建议一定接外部晶振。如果还需要RTC功能得再加一颗32.768kHz的晶振。复位电路NRST引脚接一个10k上拉电阻到3.3V再接0.1uF电容到地。这样上电时能自动复位手动复位按键可以跨接在NRST和地之间。启动模式配置BOOT0和BOOT1引脚都接10k下拉电阻到地让系统从主Flash启动这是最常规的设置。下载调试接口我预留了SWD四针接口SWDIO、SWCLK、GND、3.3V比JTAG省引脚一个便宜的ST-Link就能下载程序非常方便。2.2 温度采集电路DHT11的连接与上拉电阻玄机DHT11模块本身有四个引脚实际使用中只需要接VCC、GND和DATA三个。DATA引脚是单总线结构开漏输出所以必须在数据线和VCC之间接一个上拉电阻阻值4.7k到10k都可以。上拉电阻的意义很多人理解得不够深DHT11的高电平是靠这个电阻把信号线拉到电源电压的如果漏接数据线上高电平就会被传感器内部的下拉器件拉低导致无法通信。在原理图上我还在DHT11的VCC和GND之间加了两个电容一个10uF电解电容一个0.1uF陶瓷电容用来平滑供电电压。DHT11对电源纹波比较敏感特别是启动瞬间电流会有一段波动不加电容的话偶尔会出现第一帧数据读不对的情况。需要特别注意的是如果你直接把DHT11模块插在面包板上模块上一般已经带了上拉电阻和电容。但从原理图设计角度这些器件必须画出来否则你画的板子上如果外接了没有集成元件的传感器就会出问题。这个项目里我按“最可靠”的方式把上拉电阻和滤波电容都画上无论接模块还是接裸传感器都能正常工作。2.3 风扇驱动电路GPIO为什么会烧MOS管怎么选这是整个项目里最需要讲清楚的地方也是新手最容易踩坑的部分。STM32的GPIO引脚最大输出电流只有大约20mA输出电压最高3.3V。而普通5V或12V直流风扇的额定电流在200mA到1A之间启动瞬间电流更大。直接用GPIO引脚去驱动风扇结果只有两个风扇转不起来或者STM32芯片烧掉。所以中间必须加驱动电路。这个项目里我用的是低端驱动 N沟道MOS管方案。具体接法是MCU的PWM输出引脚通过一个100Ω到220Ω的栅极电阻连接到N沟道MOS管型号AO3400的栅极GMOS管的源极S接地风扇的负极接MOS管的漏极D风扇的正极接电源5V或12V。这样当PWM输出高电平时MOS管导通风扇通电PWM输出低电平时MOS管关断风扇断电。通过调整PWM占空比就能控制风扇两端平均电压实现调速。这里选AO3400的原因是它是低压逻辑电平MOS管栅极阈值电压很低3.3V就能完全导通非常适合直连单片机。像IRLZ44N这类工业MOS管虽然电流大但栅极电容也大直连GPIO可能出现驱动不足或开关损耗过大。AO3400在SOT-23封装里就能承受5.7A的连续漏极电流驱动普通风扇绰绰有余。还有一个细节千万不能漏并联在风扇两端的续流二极管。风扇内部是电机绕组本质上是感性负载。当MOS管快速关断时电感电流不能突变会在风扇两端产生一个很高的反向感应电动势如果不加二极管泄放这个反电动势很容易击穿MOS管。续流二极管要选快恢复或者肖特基二极管比如1N5819阴极接电源正极阳极接MOS管漏极。这个二极管方向千万别接反接反了通电就短路。风扇供电和单片机供电要分开。风扇启停瞬间电流波动很大如果和STM32共用一根电源线电压跌落可能导致单片机复位。我在原理图上用两个独立的电源端子或者至少用较粗的走线把风扇电源引到主电源入口处再通过LC滤波给MCU供电。这是实际调试中我用示波器看出来的教训。2.4 人机交互电路OLED、按键与蜂鸣器的标准接法显示部分用的0.96寸OLED屏幕I2C接口四根线VCC、GND、SCL、SDA。I2C的SCL和SDA都是开漏结构必须接上拉电阻到3.3V阻值4.7k。我用STM32的PB6和PB7作为I2C1的SCL和SDA。OLED模块通常自带地址是0x3C多数固件不需要改。按键电路我做了两个独立按键一个接PB0一个接PB1。按键一端接地另一端接MCU引脚同时MCU引脚内部配置为上拉输入。平时引脚读高电平按下时接地读低电平。在软件里做10ms延时的消抖处理这种方案简单可靠比外部RC消抖电路更灵活。蜂鸣器用的是有源蜂鸣器只要给高电平就会响内部自带振荡源。驱动方式和风扇类似但不能直接接GPIO我用一个S8050三极管做驱动。GPIO通过一个1k电阻接三极管基极发射极接地集电极接蜂鸣器负极蜂鸣器正极接5V。并联一个1N4148快恢复二极管做续流保护。有源蜂鸣器不需要PWM直接拉高引脚就行控制逻辑简单很多。3. 软件设计初始化、采集与控制逻辑3.1 开发环境与工程文件组织软件工程基于Keil MDK搭建标准外设库版本。为什么不用HAL库因为这个项目追求代码的底层可控性标准外设库的代码更直观初始化寄存器一目了然。如果你习惯HAL库移植也不难逻辑都是通的。工程文件我做了模块化划分main.c主函数、系统初始化调用、主循环dht11.cDHT11驱动包括引脚定义、时序函数、数据读取和校验oled.cSSD1306驱动的显示函数包括字符和数字显示pwm.cPWM初始化与占空比设置函数key.c按键扫描与消抖函数buzzer.c蜂鸣器控制函数user_control.c模式状态机与温控策略的实现这样分文件的好处是每个模块职责单一出了问题直接定位对应文件调试效率高。别人拿到代码后也容易看懂。编译之前要记得在Keil的Target选项里选择Cortex-M3设备芯片型号选STM32F103C8并把Flash算法的Base Address设为0x08000000。很多新手编译报错就是Device型号或Flash算法没配好。3.2 DHT11时序与驱动代码要点DHT11用的是单总线协议数据传输完全靠一根线上的高低电平时序来区分0和1。整个读取过程分三步主机发起始信号、DHT11响应、DHT11发送40位数据。主机发起始信号的过程是先把数据线拉低至少18ms然后再拉高20到40us接着释放总线。DHT11收到这个起始信号后会先输出一个约80us的低电平响应信号然后拉高80us之后开始逐位发送数据。每一位数据的开始都是一个50us的低电平紧接着的高电平时间长度决定了这一位是0还是126到28us的高电平代表逻辑070us左右的高电平代表逻辑1。所以读取每一位的关键就是等50us低电平结束后延时30到40us然后采样电平采样到高就是1采样到低就是0。代码实现我用的GPIO模拟时序。先写一个微秒级延时函数然后按照时序图逐步操作。每次读取40位数据共5个字节依次是湿度整数、湿度小数、温度整数、温度小数、校验和。校验和的算法是前四个字节相加后取低8位与第五个字节相等才算读取成功否则丢弃本次数据。这里放一段核心的读取字节函数uint8_t DHT11_Read_Byte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { while (DHT11_DQ_IN 0); // 等待50us低电平结束 delay_us(40); // 延时40us后采样 if (DHT11_DQ_IN 1) { byte | (1 (7 - i)); // 高电平时间长为1 } while (DHT11_DQ_IN 1); // 等待剩余高电平时间结束 } return byte; }DHT11的上电响应时间比较长至少1秒。所以主程序里初始化传感器后要延时几百毫秒再开始第一次读取不然第一帧数据基本都是错的。另外每次读完之后到下一次起始信号之间间隔必须大于1秒DHT11的采样周期很慢强行连续读经常会读到非法数据。我在主循环里用一个计时变量控制每1秒读一次。3.3 风扇控制策略从分段调速到PID自动温控模式下核心问题是“温度如何映射到PWM占空比”。这个项目我做了两种策略通过宏定义可以切换。第一种是分段控制也叫阶梯调速。设定几个温度阈值对应不同的占空比档位温度区间风扇状态PWM占空比低于25℃关闭0%25℃ - 30℃低速转动30%30℃ - 35℃中速转动60%35℃ - 40℃高速转动90%高于40℃满速100%这种策略逻辑简单适合初学者理解但缺点是在阈值附近风扇转速会突变比如温度在29.9℃和30.1℃之间反复波动时风扇一会30%一会60%体感就是忽快忽慢。第二种是线性映射。设定最低工作温度Tmin和最高温度Tmax温度每升高一度占空比线性增加。公式是PWM (当前温度 - Tmin) / (Tmax - Tmin) × PWM_Max用C语言实现时要注意整形运算溢出问题我先把差值算成有符号数再用浮点运算或者放大10倍的定点运算来处理。线性策略比分段控制平顺很多适合实际使用。其实还有一种更高级的PID闭环控制把目标温度设成固定值比如想让环境稳定在32℃然后根据温度误差动态调节PWM。PID的调参比较花时间而且风扇这种大惯性系统P或I参数调节不好很容易振荡。我在这版开源代码里保留了线性映射和分段控制两种PID作为扩展接口留在了文件里读者可以仿照接口自己接入。3.4 PWM初始化定时器配置与占空比更新STM32F103C8T6的定时器资源很丰富。我用TIM3的通道1输出PWM引脚是PA6。初始化分三步使能TIM3和GPIOA的时钟。配置PA6为复用推挽输出频率50MHz。配置TIM3时基参数设置PWM模式、脉冲初始值、极性等。关键参数设计我在定时器里设置了PSC预分频值为71所以定时器时钟是72MHz除以72等于1MHz就是1us计数一次。ARR自动重装载值设为999这样PWM频率是1kHz。占空比精度是1000级也就是PWM_SetDuty(0到999)对应0%到100%。如果你想提高PWM频率到20kHz来降低风扇噪声可以把ARR改小比如设ARR为49频率就变成20kHz但占空比精度会降到50级。PWM输出的优势在MOS管驱动上体现得非常明显。1kHz到20kHz的PWM频率对MOS管开关完全够用特别是AO3400的开关频率能到1MHz以上。需要注意的是如果驱动方案用的是三极管PWM频率最好不要超过10kHz因为三极管关断延迟大频率太高会导致管子来不及关断一直处于半导通状态发热严重。更新占空比只需要改一个寄存器或调用一个函数void PWM_SetDuty(uint16_t duty) { if (duty 999) duty 999; TIM_SetCompare1(TIM3, duty); }实测下来我设置1kHz PWM配合风扇自身的机械惯性转速控制非常平滑也听不到明显的PWM噪声。如果换成静音要求高的场景再考虑把频率提高到25kHz。4. 仿真与调试从Proteus到真实硬件4.1 Proteus仿真环境的搭建要点Proteus是这次开源交付里很重要的一环没有实体板子的人也能先把逻辑跑通。我用的是Proteus 8.15版本这个版本对STM32F103系列的支持已经很完善了可以直接搜索到STM32F103C8。仿真工程的搭建步骤新建工程在元件库中搜索并放置STM32F103C8、DHT11、OLED屏I2C版本、电阻、MOS管、风扇、按钮等元件。在Proteus中用虚拟示波器或逻辑分析仪观察PWM波形这比实际硬件调试还方便。双击STM32F103C8芯片在Program File里加载Keil编译生成的hex文件设置Crystal Frequency为8MHz。点击运行观察OLED屏显示和PWM引脚波形。Proteus仿真和Keil可以联调即在Keil的Debug选项卡中选择Proteus VSM Simulator然后配合Proteus的VSM菜单实现协同仿真这种方式可以直接单步看代码跑到哪里变量变化一目了然。不过联调配置稍复杂如果只是跑通逻辑直接把hex加载到Proteus里运行就够了。4.2 仿真验证功能都测通了再打样在打板之前我把所有功能在仿真里完整测了一遍。自动温控模式把DHT11的仿真温度参数调高到35℃以上观察PWM波形占空比随之增大风扇模型转速变快。调低温度到28℃以下占空比回落风扇转速明显下降。这个反馈链路在仿真里直观可见比跑日志舒服多了。不过要注意Proteus自带的DHT11模型对时序要求也比较严格如果仿真里DHT11一直读不到数据大概率还是程序里延时参数不够准。因为Proteus的模拟运行速度受电脑性能影响微秒级延时的精度不如真机那么理想。我遇到过在Proteus上DHT11数据一切正常但把程序烧到真机反而第一次读失败的情况原因是真机启动后电源纹波比仿真大不少。所以仿真永远是“第一道验证关”验证通过了不代表硬件就能一次成功。按键状态机部分在仿真里点击按键按钮模式变量在“自动—手动”之间切换OLED屏幕上的Mode文字跟着变化手动档位值也会在1到4之间循环。蜂鸣器因为有源模型仿真里我直接用LED代替点亮就是触发状态。4.3 常见问题与排查技巧实录这里把我实际调试过程中踩过的坑整理成表格方便你对照排查。问题现象可能原因解决方法Keil烧录时报“No STM32 Target Found”Debug选项配置错误或者ST-Link驱动没装好确认Magic Wand——Debug里选ST-Link设置4线SW模式检查ST-Link的三根线是否接错DHT11一直读回0xFF数据线上拉电阻漏接或者GPIO模式配置成开漏检查原理图上拉电阻配置GPIO为推挽输出和浮空输入切换DHT11偶尔读到0两次读取间隔小于1秒主循环里做1秒节拍避免连续间隔过短OLED白屏I2C地址不是0x3C或者SCL/SDA接反用扫描函数读出实际设备地址检查I2C上拉电阻风扇不转但代码有PWM输出MOS管栅极电压不足或漏接续流二极管查栅极驱动信号AO3400应能从3.3V导通确认二极管方向正确仿真里DHT11温度不变化没有手动设置温湿度值用DHT11模型上的滑杆或属性窗口改变温度参数风扇转速有“嗡嗡”声PWM频率在1kHz左右所致把PWM频率提高到20kHz以上或降低占空比阶梯步长另外关于Proteus仿真报错的“no stm32 target found”这种提示在实际硬件烧录时非常常见。排查顺序是先检查ST-Link是否识别再检查SWDIO和SWCLK是否接反最后检查目标板有没有供电。注意如果STM32的引脚被程序配置成普通IO且占用SWD引脚也会导致连不上调试器所以程序里千万别乱改PA13和PA14的功能。4.4 实物调试中的三个独家心得第一个心得是电源地线要分开规划。在实物上我遇到过风扇一转OLED画面就开始闪烁甚至乱码。用示波器一看就是风扇电源的噪声通过地线耦合到了MCU电源上。解决方法是风扇回路的接地走线单点连接在主电源电容附近汇合不要和MCU的地线共享同一段长走线。第二个心得是DHT11的驱动延时要用SysTick或定时器不要用循环空指令。Keil在不同优化级别下空循环的时延差别很大O0优化和O3优化能差出10倍这会让时序完全错乱。我封装了一个基于SysTick的delay_us函数即使在优化模式下也稳定。第三个心得是接线前先看封装和丝印。AO3400的SOT-23封装丝印上G、D、S的排列顺序不同批次可能不同接反了MOS管永久导通风扇一直满速。我栽过一次跟头后来买料时就固定用某一家供应商做工程先万用表测开路确认一下引脚定义能省很多事。最后再说一个小建议很多人把仿真跑通就以为项目结束了其实仿真只是验证了软件逻辑真正考验硬件的往往是打板后的首次上电。建议第一次上电时程序里先把PWM占空比设为0风扇不要转先测MCU供电和DHT11读数是否正常再逐步开启风扇控制。一步一步来出了问题也好定位。这个项目所有资料我都整理在网盘里了工程代码、原理图、仿真文件都齐你需要的话可以直接下载。动手跑一遍比看多少遍文章都有用。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →