基于STM32F103C8T6的多功能智能温控风扇系统设计与开源
2026/9/8 23:29:40 网站建设 项目流程

基于STM32F103C8T6的多功能智能温控风扇系统,代码、原理图、仿真工程我都整理好开源了。这个项目解决的是一个很具体的问题:手头一台12V直流风扇,希望它能根据环境温度自动调速,温度高了风扇转得快,温度低了安静停转,同时能实时看到当前温度和转速等级。它不算高深,但把嵌入式开发最常见的硬件设计、外设驱动、PWM控制、页面显示、仿真调试全流程串了一遍,非常适合正在学STM32或者准备做相关课程设计的读者直接照抄,实验室机箱散热、功放散热这类场景也能直接改来用。

1. 这个项目解决了什么问题:从手动风扇到自动温控的选型逻辑

普通风扇只有开和关两个状态,手动拨开关、手动拧调速,温度升高了还得自己走过去操作。这个项目要做的就是把“需要人盯着的重复劳动”变成闭环:温度传感器持续采样,STM32根据温度值实时计算PWM占空比,风扇转速跟着自动调整。本质上这就是一个“传感器—控制器—执行器”闭环,虽然结构简单,但每一环都有值得展开讲的地方。

1.1 为什么选STM32F103C8T6,而不是51或Arduino

很多初学者上来就纠结用51、Arduino还是STM32。这三类平台我都用过,直接说结论:这个项目最终落在STM32F103C8T6上,是因为它恰好卡在一个甜点位。

对比项STC89C52(51)Arduino UnoSTM32F103C8T6
主频12MHz左右16MHz72MHz
硬件PWM多数型号没有,需要定时器模拟有,但精度和通道数有限支持多路高级定时器PWM
ADC通常要外扩PCF8591等芯片10位ADC12位ADC,多通道
调试方式串口烧录,在线调试困难串口/仿真器SWD,硬件断点观察
学习价值偏基础,外设扩展成本高封装过厚,底层被库挡住寄存器/标准库/HAL都能深入学

这个项目需要做DHT11或DS18B20的单总线通信、PWM调压、ADC采样、OLED显示四件事。51做起来会非常吃力,尤其单总线对时序要求高,主频低还要卡时序,中断和主循环容易互相干扰;Arduino写起来快,但很多细节都被库挡住了,等你调通这个项目,对寄存器、定时器、中断的理解提升有限。STM32F103C8T6性能足够跑完整套逻辑,资料多到看不完,开发成本也很低,从标准库过渡到HAL库非常顺。

1.2 系统整体架构与功能拆解

我没有把功能一股脑堆上去,而是拆成了几个能独立测试的模块:

  1. 温度采集模块:DHT11读取温湿度,或者用DS18B20读高精度温度,同时提供NTC热敏电阻加ADC的低成本方案。
  2. 控制核心模块:STM32F103C8T6负责定时采集、数据滤波、转速策略计算,通过PWM输出控制风扇。
  3. 风扇驱动模块:PWM信号驱动功率MOS管,实现占空比调速,并对电机反电动势做续流保护。
  4. 人机交互模块:0.96英寸OLED显示实时温度和转速百分比,三个按键完成开关、模式切换、温度阈值设定。
  5. 供电与接口:USB 5V输入或外部直流电源输入,板载3.3V稳压,预留UART串口用于日志调试。

这样拆分之后,每一块都能单独验证。烧个裸机程序点灯验证最小系统,再分别测传感器读数和PWM占空比,最后才把控制策略加进去联调。开发时如果发现某个功能不对,能立刻缩小排查范围,不至于整个项目一起抓瞎。

1.3 开源内容里到底有什么

仓库里不是丢一个main.c就完事,而是按下面结构组织的:

  • 代码工程:基于标准外设库(SPL)的完整工程,同时提供HAL库移植说明,包含BSP驱动、App应用层、Main入口。
  • 原理图:立创EDA和KiCad两种格式的原工程,外加PDF版本方便直接查看,元件参数、封装、接线表都写在文档里。
  • 仿真项目:Proteus 8仿真电路,还有Wokwi在线仿真配置,不出硬件也能先跑逻辑。
  • 文档:接线定义、引脚分配、烧录步骤写成了独立README,关键代码还有一篇说明文件逐步讲。

2. 原理图纸拆开看:最小系统、传感器、MOS驱动三块核心

原理图是很多人拿到开源项目后懒得看、真出问题了又不得不看的文件。其实这张图的核心就三块:STM32最小系统、温度采集接口、风扇驱动电路,其他按键、OLED、电源都是外围附属。下面逐块拆开讲。

2.1 STM32最小系统与SWD调试接口的必备细节

STM32F103C8T6最小系统不复杂,但细节决定稳定性。

晶振电路用8MHz主晶振接OSC_IN和OSC_OUT,两个引脚各接一个20pF负载电容到地。电容值不是随手写的,它要和晶振的负载电容CL匹配,经验公式大约是CL≈C1×C2/(C1+C2)+寄生电容,所以20pF是常见取值。如果手里只有15pF或22pF也不是不能用,后续用示波器测一下起振波形即可。

复位电路是NRST引脚接一个100nF电容到地,再接一个10kΩ上拉到3.3V。上拉保证平时复位引脚是高电平,电容在按键按下瞬间提供低电平脉冲,MCU才能可靠复位。BOOT0串10kΩ电阻下拉到地,BOOT1直接下拉,确保从Flash启动。如果买的是带按键的蓝丸板,这些板上都处理好了。

每个VDD引脚旁边要放一个100nF陶瓷电容去耦,尽量靠近引脚,电源输入端加一个10μF电解电容。这个对ADC采样精度和系统抗干扰影响很大,看不出什么原理但实测很管用。SWD调试接口留4根线:SWDIO、SWCLK、GND、3.3V,最好再把NRST引出。用ST-Link的SWD模式只需要两根信号线加电源地,比JTAG省引脚,下载调试都够用。

原理图里还有一个容易被忽略的点:板上有模拟电路和数字电路混合时,模拟部分的地单独走过一段,最后在主板单点连接或用0Ω电阻隔开。我这个项目里温度传感器的模拟参考就独立处理了,ADC采样稳定性比直接裸接地好不少。

2.2 温度采集的三种方案:DHT11、DS18B20和NTC

温度采集是整个系统的输入源头,选错传感器整个项目都会被牵着走。三种方案我都试过,区别和坑如下:

方案输出类型精度速度接线复杂度适合场景
DHT11单总线数字±2℃采样间隔不小于1秒一根数据线,需上拉温湿度都要看,成本敏感的DIY
DS18B20单总线数字±0.5℃转换时间约750ms一根数据线,需上拉对温度精度有要求的场合
NTC热敏电阻模拟电压依赖分压电阻和标定实时电阻分压,ADC读取想把成本压到最低的批量方案

DHT11的电路最简单:VCC接3.3V,GND接地,DATA引脚接一个MCU的GPIO,同时加一个4.7kΩ上拉到3.3V。这里有一个很多人翻车的点:DHT11的数据线和DS18B20虽然都叫单总线,但初始化方式和位时序细节不同,代码里要区分开。

DS18B20建议3.3V供电,数据线上拉电阻也是4.7kΩ。它的优点是ROM里有唯一64位序列号,可以在同一根总线上挂多个传感器做多点测温;缺点是转换时间较长,快速响应的PID温控采样周期必须按750ms以上设计。

NTC方案我用的是10kΩ NTC配合10kΩ精密电阻分压,分压点接到STM32的ADC输入,通过查表或Steinhart-Hart方程换算温度。好处是没有时序问题,代码省心,但要注意热敏电阻自热效应,激励电流不能太大,分压电阻上最好并一个100nF电容滤波。

2.3 风扇驱动电路:MOS管选型与续流保护

风扇驱动是原理图里最容易画错的地方。一个12V、0.2A的直流风扇看起来电流不大,但电机是感性负载,PWM调速时会产生反电动势,如果直接用GPIO驱动,要么电流不够,要么MOS被反向电压击穿。驱动电路我这样设计:

  1. 选用N沟道MOS管,AO3400这类VGS阈值低的型号,3.3V电平可以完全导通。
  2. GPIO输出的PWM先串一个100Ω电阻到MOS管栅极,再在栅极和源极之间并一个10kΩ下拉电阻,保证MCU复位期间MOS管不会误导通。
  3. 风扇正极接电源正,负极接MOS管漏极,MOS管源极接地,构成低边开关。
  4. 风扇两端并联一个1N5819肖特基二极管,方向是风扇负极接二极管阳极、正极接二极管阴极,PWM关断瞬间电机反向电流通过它续流,避免尖峰打坏MOS管。
  5. 电源输入端加一个470μF电解电容,吸收电机启动瞬间的大电流。

如果风扇功率更大,比如12V/1A以上,建议换更大电流的MOS,并给MOS管加散热焊盘。还有一点,STM32的GPIO输出PWM频率如果低于1kHz,电机会有明显可闻噪声。建议初始就设为20kHz以上,或者加一个RC低通滤波把PWM变成模拟电压再驱动,风扇声音会安静不少。

按键和OLED的电路相对常规:按键接GPIO和GND,外部上拉10k;OLED用I2C接口,SCL和SDA各接一个4.7kΩ上拉,电源引脚附近放100nF电容。

3. 固件代码逐段拆解:从传感器时序到控制策略

代码是这个项目的灵魂。工程基于STM32标准外设库,因为对初学者来说,标准库函数比直接撸寄存器容易理解,又比HAL库少很多抽象层,适合学原理。软件结构分三层:底层驱动(DHT11/OLED/PWM)、中间层(数据处理与策略)、应用层(主循环状态机)。

3.1 单总线时序的底层实现

DHT11的读取时序最容易写歪。DHT11单总线的规格是:总线空闲时高电平,MCU先拉低至少18ms作为启动信号,然后释放总线,DHT11回一个80μs低脉冲再接一个80μs高脉冲,之后开始输出40位数据。每一位的高电平长度不同,逻辑0约26-28μs,逻辑1约70μs,所以读取时靠高电平持续时间判断。

下面是DHT11触发和读字节的核心代码,用GPIO模拟时序:

// 拉低总线,触发DHT11 void DHT11_Start(void) { DHT11_DATA_GPIO_MODE(GPIO_Mode_Out_PP); // 配置为推挽输出 DHT11_DATA_LOW(); delay_ms(20); // 至少18ms DHT11_DATA_HIGH(); delay_us(30); // 释放总线30us } // 从DHT11读取一个字节 uint8_t DHT11_ReadByte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { while (DHT11_DATA_READ() == RESET); // 等待高电平开始 delay_us(40); // 延时40us后再判断电平 if (DHT11_DATA_READ() == SET) data |= (0x80 >> i); while (DHT11_DATA_READ() == SET); // 等待该位结束 } return data; }

这段代码有两个关键点:启动信号低电平必须保持足够时间;读位时延时40μs后再采样,延时太短容易把0误判成1,太长又容易丢位。72MHz主频下延时精度足够,但移植到其他MCU时务必用示波器重新校准。严格来说,等待电平的while循环要加超时保护,防止总线被外部干扰拉死,产品代码里必须考虑。

DHT11读到40位数据后,前16位是湿度和温度整数部分,中间16位是小数,最后8位是校验和。校验方法是前四字节相加的低8位等于校验字节,不等就丢弃这次数据。不加校验直接读,偶尔会出现温度乱跳。

DS18B20的时序和DHT11不同,但同样有跳过ROM、启动转换、读暂存器这些指令。仓库里我单独写了ds18b20.c,初始化时会检测总线上是否存在设备,读取时也做了CRC校验。移植时注意DS18B20启动转换后必须等待至少750ms,不能用DHT11那套时间逻辑。

3.2 定时器PWM初始化与转速控制

PWM部分用TIM3的CH1,映射到PA6。初始化流程如下:

void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 开启时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // PA6配置为复用功能推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 定时器时基:72MHz/(71+1)=1MHz,计数到999,PWM频率为1kHz // 这个示例用1kHz便于观察,实际驱动建议调到20kHz TIM_TimeBaseStructure.TIM_Period = 999; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &TIM_OCInitStructure); TIM_Cmd(TIM3, ENABLE); } void Fan_SetDuty(uint16_t duty) // duty范围0-1000 { TIM_SetCompare1(TIM3, duty); }

这里有个实际调过的坑:直接抄网上的代码把PWM频率设成1kHz,风扇工作时的线圈啸叫声非常明显。后来我把定时器改成20kHz,也就是Prescaler设71、Period设49,耳朵贴到风扇附近也几乎听不到高频噪声。用普通直流电扇和MOS低边驱动时,20kHz是个比较舒服的折中。控制函数封装成Fan_SetDuty(duty),duty范围0-1000对应0-100%占空比,策略层不会直接操作寄存器,主循环保持清爽。

3.3 温度映射与迟滞控制策略

控制策略是这个项目最核心的部分。最简单的做法是把温度直接线性映射成PWM占空比:

if (temp < 25.0f) duty = 0; else if (temp > 35.0f) duty = 1000; else duty = (uint16_t)((temp - 25.0f) / 10.0f * 1000.0f);

线性映射可行,但有一个经典问题:当温度在25℃附近波动时,PWM会在0和一个小占空比之间反复切换,风扇呼一下停一下呼一下停一下,噪声和电机寿命都受影响。所以我加了迟滞控制:启动温度和停止温度做成不同值,比如降到28℃以下才彻底停转,上升到30℃以上才开始有输出。这本质上是施密特触发器的思路,工业控制里很常见。

再进一步,想让风扇在设定范围内更平滑,可以用分段映射:

温度区间占空比行为
低于25℃0%停止
25℃~28℃0%(迟滞区)维持上次状态
28℃~33℃30%~70%线性跟随升温
33℃~38℃70%~100%线性快速散热
高于38℃100%全速转动

这里30%-70%的曲线还可以用查表或拟合曲线做非线性映射,不必死板线性。实测下来这些参数足够日常场景;如果要做恒定温度控制,比如培养箱或加热台,建议上PID。

滤波方面,我用了滑动平均:维护一个长度为10的温度采样数组,读满后取平均值,能滤掉DHT11的偶发跳变。如果换NTC,ADC采样还要多次采样滤波再查表,避免量化噪声直接导致控制输出毛刺。

3.4 OLED显示与按键交互的软件结构

OLED部分用SSD1306驱动,I2C接口。我封装了简洁的显示函数,主循环里每秒刷新一次,显示当前温度、湿度、转速百分比和运行模式。OLED刷新不需要太快,但要注意不要把刷新逻辑和主控制循环放在同一个阻塞调用里,否则会拖慢PWM响应。我做成非阻塞状态机:每500ms更新一次,更新期间不阻止风扇控制。

按键部分做了防抖和短按、长按区分:

void Key_Scan(void) { static uint8_t lastKey = 1, keyFilter = 1; uint8_t keyNow = GPIO_ReadInputDataBit(KEY_GPIO_PORT, KEY_PIN); if (keyNow != keyFilter) { keyFilter = keyNow; if (keyFilter == 0) // 确认按下 Key_Event |= KEY_FLAG_MODE; // 触发模式切换 } }

这是一个轮询式消抖,实际工程里也可以改成中断+定时器扫描。最终选轮询是因为整个系统是一个控制循环,不需要抢占,逻辑简单不易出bug,而且这种写法在Proteus仿真里也更容易复现。

4. 仿真环境怎么搭、怎么调:Proteus与Wokwi实战记录

软件写完之后,不一定马上就能拿到板子。我在仓库里配了两套仿真:Proteus 8适合模拟完整电路,Wokwi在线仿真适合快速调业务逻辑。两套仿真的目的和坑不太一样。

4.1 Proteus里搭建温控风扇仿真并观察PWM波形

Proteus 8 Professional搭建步骤大体是这样:

  1. 新建工程,从元件库搜索STM32F103C8T6,放到画布。
  2. 添加LM35温度传感器,输出接到STM32的PA0,PA0配置为ADC输入;或者在仿真里用一个电位器模拟温度变化,更直观。
  3. 添加一个电机作为风扇负载,用理想开关或场效应管作为低边开关,PWM信号从PA6接入。
  4. 添加虚拟示波器,连接到PWM引脚和电机两端,观察占空比变化。
  5. 给STM32加载编译好的hex文件,配置外部晶振8MHz,然后运行。

Proteus最常见的坑是:不配置外部晶振,仿真直接提示芯片不运行;或者加载的hex是用HAL库编译出来的,Proteus对HAL库的支持不如标准库稳定。所以我建议仿真阶段用标准库工程。另一个坑是LM35输出是模拟电压,在Proteus里可以直接接到ADC,但必须在ADC初始化里把采样通道配好,否则读到的值一直是0。真机用LM35时注意其输出电压范围约0-1.5V,3.3V供电下ADC量程0-3.3V,可以直接接入。

用虚拟示波器观察PWM信号时,当传感器温度升高,PWM占空比会发生明显变化。如果在仿真里用LM35,记得在旁边加一个电压源模拟温度变化,否则温度恒定电机转速恒定,看不到自动调速的动态过程。

4.2 Wokwi在线仿真的优势与坑

Wokwi是浏览器端的嵌入式仿真平台,支持STM32F103C8T6、ESP32、Arduino等主流MCU。把它放进开源仓库的原因很简单:很多读者不方便安装Proteus,而Wokwi打开网页就能跑,还能配合DHT11的虚拟器件实时模拟传感器数据,对验证控制逻辑非常高效。

在Wokwi里创建工程时先选择STM32F103C8T6,它会自动生成platformio.ini等配置文件,然后可以添加DHT11、OLED、按键、直流电机等器件。Wokwi里的DHT11模型可以随时在右侧面板调整温湿度数值,主循环能立刻读到变化,调试控制策略时优势巨大——不用拿电烙铁去改变环境温度。

Wokwi的坑也有两个:一是它不是完整的硬件模拟器,对GPIO电平细节、I2C时序和PWM波形的模拟并不精确,不能完全代替Proteus或真板验证电路;二是部分硬件模型对单总线时序容忍度更宽松,代码时序参数不正确也常常能跑过,上了真机问题就暴露。换句话说,Wokwi调通不代表真机一定没问题。

我的建议是:拿到开源代码先Wokwi调业务逻辑,再到Proteus验电路连接,最后才上真机;如果已经有板子,Wokwi可以当作快速演示环境。

4.3 仿真转真机时的注意事项

从仿真转到真机,最大的差异是时序和电平。DHT11在仿真里用延时就能稳定读取,到真机可能因为GPIO速度、上拉电阻、线缆电容导致时序不对。解决办法是调试时用示波器抓总线波形,确认高低电平宽度和理想时序一致。

真机ADC采样还会受电源纹波影响,温度曲线可能不一致。原理图阶段要做好模拟电源滤波,MCU的VREF+接到干净的3.3V,采样端并一个小电容。仿真正常但真机ADC值总在某个值附近跳动,优先检查电源纹波和地线回路,再考虑软件滤波。另外,真机的风扇驱动MOS管发热、续流二极管方向接反、PWM频率不合适,这些问题在仿真里压根不会暴露,上真机后需要逐项检查。

5. 开源仓库的整理思路与后续扩展建议

开源一个项目不是丢个压缩包,而是要让人能看得懂、跑得起来。整理仓库时,我重点放在“读者拿到手第一步就能顺利复现”上。

5.1 仓库目录结构与README要点

仓库目录大致如下:

smart-temp-fan/ ├── docs/ │ ├── 接线表.md │ ├── 烧录与调试指南.md │ └── 硬件BOM表.xlsx ├── firmware/ │ ├── STM32F103C8T6_SPL_Project/ │ ├── STM32F103C8T6_HAL_Project/ │ └── README.md ├── hardware/ │ ├── schematic_pdf/ │ ├── kicad_source/ │ └── lceda_source/ ├── simulation/ │ ├── proteus8/ │ └── wokwi/ └── README.md

README第一屏写清楚五样东西:项目名称和演示效果、引脚连接表、硬件清单BOM、编译烧录步骤、常见问题。不放这些信息,代码再漂亮别人也很难用起来。引脚连接表是关键,我把这张表放在文档最前面:

外设引脚说明
OLED SCLPB8I2C1_SCL
OLED SDAPB9I2C1_SDA
DHT11 DATAPB12需外接4.7kΩ上拉
PWM输出PA6TIM3_CH1
ADC温度采样PA0ADC1_IN0
按键K1PA1模式切换
按键K2PA2温度设定+
按键K3PA3温度设定-

有了这张表,学的人不用去原理图里一个个查引脚,直接照着接线就能跑。文档里我还放编译目标配置截图,以及ST-Link的接线方法。

5.2 二次开发的几个方向

基础功能稳定之后,可以从这几个方向继续扩展:

  1. PID恒温控制:把温度到占空比的映射改成PID输出。温度误差作为输入,PWM占空比作为输出。比例项负责快速响应,积分项消除稳态误差,微分项抑制超调。参数可以先在仿真里用Ziegler-Nichols法粗调,再上真机微调。
  2. 自动手动模式并存:按键切换自动/手动,手动模式下直接设定占空比,适合对风扇转速有明确要求的人。
  3. 增加湿度采集:换用DHT22或SHT30,同时显示温湿度,控制策略加入湿度因子。
  4. 联网或蓝牙控制:挂ESP8266模块或HC-05蓝牙模块,手机远程查看温度并控制风扇,项目就从单机控制升级成物联网终端。
  5. 低功耗改造:电池供电时进入STOP模式,用RTC唤醒周期采样,显示画面通过按键唤醒,外设不用时全部关闭。STM32F103C8T6低功耗特性虽然不如L系列,但调好了也能实现很长的待机。

这些扩展方向在仓库的docs/扩展思路.md里都有具体方案,直接跟着改就可以。

最后分享一点个人体会:这个项目的价值不在于风扇本身有多智能,而是它把“从一片空白到能跑起来的完整项目”所需的每一环都走了一遍。如果你是第一次上手,建议先把代码烧进板子跑通,再去改控制策略,最后动原理图;如果已经很熟,可以试着把DHT11换成SHT30、把OLED换成LCD,或者引入PID,看看能不能保持和原工程一样清晰的代码结构。开源的意义就是让后来者站在前面踩过的坑上继续往前走,遇到问题把现象和测量波形发出来一起讨论,我很乐意帮大家排查。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询