基于STM32的智能风扇系统:从环境感知到PID温控的嵌入式实战

1. 项目概述:从“会转”到“会思考”的风扇

几年前,我还在用着那种只有三档风速、一个摇头开关的机械式风扇。夏天最热的时候,要么风太大吹得头疼,要么风太小闷得难受,半夜还得爬起来调档位。后来接触到单片机,第一个念头就是:能不能让风扇自己“知道”什么时候该吹、该吹多大风?于是,这个“基于STM32的智能风扇系统”就成了我折腾的起点。它本质上是一个集成了环境感知、智能决策和精准执行能力的闭环控制系统,核心目标就是让风扇告别“傻吹”,实现按需、舒适、节能的送风。

这个系统听起来高大上,其实拆解开来,核心就三件事:感知思考执行。感知靠传感器(比如温湿度、人体红外),思考靠STM32单片机的大脑,执行靠电机驱动模块。它非常适合两类朋友:一类是电子、自动化或物联网相关专业的学生,想找一个能串联起传感器、MCU、电机控制、算法和无线通信的综合项目来练手;另一类是像我一样的硬件爱好者,想给家里添置点既实用又能彰显技术力的“小玩意儿”。通过这个项目,你不仅能深入理解嵌入式系统开发的完整流程,更能亲手打造一个能真实改善生活体验的智能设备,这种成就感是单纯看教程无法比拟的。

2. 系统整体设计与核心思路拆解

2.1 为什么选择STM32作为核心?

市面上主控芯片选择很多,从51单片机到Arduino,再到ESP32。我最终锁定STM32,主要是基于以下几点考量:

性能与资源的平衡:智能风扇系统需要实时采集多路传感器数据(温度、湿度、人体信号),运行控制算法(比如PID温控),同时还要处理可能的用户交互(按键、屏幕)和通信(蓝牙/WIFI)。STM32F103系列(俗称“蓝桥杯”或“战舰板”同款)的Cortex-M3内核,主频72MHz,拥有足够的计算能力和内存(几十KB的RAM)来流畅处理这些任务,不会像51单片机那样捉襟见肘。

丰富的外设接口:这是STM32的强项。我们需要ADC(模数转换器)来读取模拟温湿度传感器的电压值,需要定时器产生PWM(脉冲宽度调制)波来精确控制风扇电机转速,需要GPIO(通用输入输出)连接按键、指示灯和人体红外模块的数字输出,还需要UART(串口)与蓝牙模块通信。STM32将这些外设集成在片内,无需额外扩展芯片,大大简化了硬件设计和成本。

开发生态与成本:STM32拥有成熟的Keil MDK或STM32CubeIDE开发环境,丰富的标准库(Standard Peripheral Library)和硬件抽象层(HAL库)资料,社区支持极其庞大,遇到问题几乎都能找到解决方案。从成本看,一片STM32F103C8T6(核心板)价格已非常亲民,完全在个人项目可接受范围内。

注意:对于初次接触STM32的朋友,建议从F1系列开始。F4或H7系列性能更强,但复杂度也更高,在风扇这个项目上属于“性能过剩”,可能增加不必要的学习门槛。

2.2 系统架构与模块选型思路

整个系统的架构可以看作一个清晰的信号流:环境信号输入 -> MCU处理 -> 动力输出。围绕这个流,我们来拆解各个模块的选型。

1. 感知层(输入模块)

  • 温湿度传感器:DHT11和DHT22是经典选择。DHT11成本低,但精度和分辨率一般(温度±2℃,湿度±5%RH);DHT22精度更高(温度±0.5℃,湿度±2%RH),响应更快,价格稍贵。对于追求舒适度的风扇,我推荐DHT22,细微的温度变化也能被捕捉到。它们都使用单总线协议,只需要一个GPIO口进行通信,接线简单。
  • 人体红外传感器(HC-SR501):这是实现“人来风起,人走风停”的关键。它检测人体发出的特定波长红外线,输出数字信号(高电平表示有人)。需要注意其触发方式(可重复触发/不可重复触发)、延时时间和灵敏度调节,这些都需要根据安装环境(比如书房、卧室)来微调,防止误触发或不触发。
  • 交互输入:除了自动模式,保留手动控制是必要的。可以选用轻触按键进行模式切换、风速设定,或者搭配一个旋转编码器,实现无极调速,手感会好很多。

2. 控制核心(处理模块)

  • STM32最小系统板:选择STM32F103C8T6核心板即可,它包含了芯片、晶振、复位电路和基本的电源滤波,引出了所有IO口,免去了自己画最小系统的麻烦,能让你快速聚焦于功能开发。

3. 执行层(输出模块)

  • 风扇电机与驱动:这是项目的动力部分。普通直流风扇电机(额定电压12V或5V)需要电机驱动模块来控制。L298N是一个经典的双H桥驱动芯片,可以驱动两个直流电机,驱动能力强,但发热较大。TB6612FNG是更现代的选择,效率高,发热小,外围电路简单,我个人更推荐。驱动模块接收STM32产生的PWM信号,通过调节占空比来改变电机两端的平均电压,从而实现无级调速。
  • 摇头舵机:如果想实现自动扫风,可以增加一个SG90或MG90S这类微型舵机。STM32通过定时器产生一个特定周期的PWM信号,通过调节脉冲宽度来控制舵机角度。

4. 人机交互与通信(辅助模块)

  • 显示单元:0.96寸或1.3寸的OLED屏(I2C接口)是最佳选择,它功耗低、显示清晰,可以实时显示温度、湿度、风速档位、工作模式等信息,比单纯的LED指示灯直观太多。
  • 无线通信(可选):如果想用手机控制,可以添加蓝牙模块(如HC-05/06,串口透传)或WIFI模块(如ESP-01S)。我建议初期先用蓝牙,协议简单,易于调试,手机端用一个串口调试APP就能实现基本控制。

2.3 控制逻辑与算法设计要点

硬件搭好只是躯体,逻辑和算法才是灵魂。智能风扇的核心逻辑可以设计为几种工作模式:

  1. 手动模式:最基础的模式,用户通过按键或编码器直接设定PWM占空比(风速),风扇无条件执行。
  2. 自动温控模式:这是重点。系统设定一个目标温度(例如26℃)。MCU持续读取当前温度,与目标温度比较,根据差值来动态调节风扇转速。这里就可以引入经典的PID控制算法。虽然对于风扇来说,一个简单的比例(P)控制或分段控制可能就够用,但实现一个完整的PID是极好的练习。它能让你理解如何让系统更平滑、无超调地达到设定值。
  3. 人体感应模式:在此模式下,风扇转速可能由温控决定,但启停由人体传感器控制。无人时,风扇自动停止或降至最低转速(保持空气微循环);有人时,恢复温控逻辑。
  4. 睡眠模式:可以设计一个随时间推移,风速自动线性降低的模式,避免用户睡着后持续吹风着凉。

实操心得:在算法设计初期,不要追求一步到位的复杂PID。可以先实现一个“温差-风速”的查表法。例如,温差在0-1℃时,10%转速;1-2℃时,30%转速……这样逻辑简单,调试方便,先让整个系统跑起来,获得正反馈后,再迭代升级为PID控制,学习曲线会更平滑。

3. 硬件电路设计与连接详解

3.1 核心控制电路与电源设计

电源是整个系统的基石,不稳定会导致单片机复位、传感器读数漂移、电机转速异常等一系列灵异问题。

方案选择:由于系统中存在不同的电压需求(STM32核心板常为3.3V,DHT22、OLED等模块多为3.3V或5V,电机驱动和12V风扇需要12V),推荐使用多级降压方案

  1. 一个12V/2A的直流电源适配器作为总输入。
  2. 12V直接供给L298N或TB6612的电机驱动电源输入端。
  3. 通过一个降压模块(如LM2596)将12V降至5V。这个5V用于给HC-SR501、舵机、以及可能需要的5V逻辑器件供电。
  4. 再从5V线路,通过一个AMS1117-3.3线性稳压芯片,得到稳定的3.3V,供给STM32核心板、DHT22、OLED等。

重要提示电机电源和单片机逻辑电源一定要隔离!电机启停时会产生很大的反向电动势和电源噪声。务必确保电机驱动模块的电源(VM)和逻辑电源(VCC)是分开供电的,或者使用二极管和大容量电解电容(如470uF-1000uF)在电机电源入口处进行滤波和续流。最好在12V转5V的降压模块前后也加上滤波电容(如100uF电解电容并联0.1uF瓷片电容)。

STM32最小系统:使用现成的核心板,你只需要关心其IO口的引出。规划好每个功能模块连接的引脚,并记录在案,方便后续编程。

3.2 传感器与执行器接口电路

DHT22连接:DHT22有3个引脚(VCC, DATA, GND)。DATA引脚连接STM32的某个GPIO(如PA0),同时需要接一个4.7K-10K的上拉电阻到3.3V,以保证单总线在空闲时为高电平。

HC-SR501连接:其输出端(OUT)直接连接STM32的另一个GPIO(如PA1),设置为上拉输入模式。注意调节模块背面的两个电位器:一个是灵敏度(探测距离),一个是延时时间(输出高电平的持续时间)。

OLED (I2C)连接:标准的I2C四线接法:VCC->3.3V, GND->GND, SCL->STM32的PB6(I2C1_SCL), SDA->PB7(I2C1_SDA)。同样,I2C总线建议加上拉电阻(通常OLED模块内部已集成)。

TB6612FNG驱动连接

  • VM -> 12V(电机电源)
  • VCC -> 5V(逻辑电源)
  • GND -> 共地
  • STBY(待机控制)-> 接高电平(如直接接VCC)或由STM32控制使能。
  • AIN1, AIN2 -> 接STM32两个GPIO,控制电机转向(对于风扇,一般固定一个方向)。
  • PWMA -> 接STM32的一个定时器通道引脚(如PA8, TIM1_CH1),用于输入PWM信号调速。
  • AO1, AO2 -> 连接直流风扇电机的两极。

接线核对表: 在焊接或插接前,强烈建议制作这样一个表格,避免接错烧毁器件。

模块引脚连接至STM32引脚备注
DHT22DATAPA0加上拉电阻至3.3V
HC-SR501OUTPA1配置为上拉输入
OLEDSCLPB6I2C1_SCL
OLEDSDAPB7I2C1_SDA
TB6612PWMAPA8 (TIM1_CH1)PWM输出
TB6612AIN1PA9固定高电平(正转)
TB6612AIN2PA10固定低电平
按键1PB0模式切换,配置为上拉输入
编码器CLK, DTPB12, PB13配置为外部中断或编码器模式

3.3 PCB布局与焊接注意事项

如果从面包板过渡到自制PCB,有几个原则:

  1. 电源路径优先:电源走线要尽量粗、短,减少压降和噪声。
  2. 模拟数字分离:虽然本项目模拟信号不多,但最好将传感器区域(模拟地)和电机驱动区域(大电流地)的接地路径分开,最后在电源入口处单点共地,防止噪声串扰。
  3. 去耦电容就近放置:在每个芯片的电源和地引脚附近,务必放置一个0.1uF的瓷片电容,这是抑制高频噪声的关键。
  4. 电机接口:连接电机的接线端子要选用能承受电流的规格,焊盘要加固,避免因振动脱落。

焊接时,先焊接高度最低的器件(如电阻、电容),再焊接芯片座、接口等。给STM32芯片焊接时,使用助焊剂和刀头烙铁,检查有无虚焊、连锡。

4. 嵌入式软件设计与代码实现

4.1 开发环境搭建与工程配置

我使用的是STM32CubeIDE,它是ST官方推出的免费集成开发环境,集成了CubeMX图形化配置工具和基于Eclipse的编辑调试界面,对新手非常友好。

  1. 新建工程:选择对应的芯片型号(STM32F103C8Tx)。
  2. 时钟配置(RCC):在CubeMX的图形界面中,将高速外部时钟(HSE)选择为“Crystal/Ceramic Resonator”,系统时钟源选择PLL,然后将主频调到72MHz。这是保证所有外设正常工作的基础。
  3. 外设配置
    • GPIO:将连接按键、人体传感器的引脚设置为GPIO_Input,模式为上拉(Pull-up)。将连接电机驱动方向控制的引脚设为GPIO_Output。
    • 定时器(TIM)用于PWM:选择一个高级控制定时器(如TIM1)或通用定时器(如TIM3)。选择一个通道(如Channel 1)为“PWM Generation CH1”。预分频器(PSC)和自动重载值(ARR)决定了PWM的频率。对于风扇电机,频率通常在1kHz到20kHz之间。频率太低可能听到啸叫,太高则驱动芯片开关损耗大。可以设PSC=71,ARR=999,则PWM频率 = 72MHz / ((71+1)*(999+1)) = 1kHz。
    • ADC:如果使用模拟温度传感器(如NTC),需要配置ADC通道。这里我们用的DHT22是数字的,ADC可能用于其他扩展。
    • I2C:用于OLED,选择I2C1,模式为I2C,速度模式为标准模式(100kHz)或快速模式(400kHz)。
    • USART:如果使用蓝牙,配置一个串口(如USART1),异步模式,波特率9600或115200。
  4. 生成代码:配置好后,生成初始化代码。CubeIDE会自动生成main.cgpio.ctim.c等外设初始化文件,极大简化了底层寄存器操作。

4.2 传感器数据采集与处理

DHT22数据读取: DHT22的时序要求严格,必须用微秒级延时。通常用一个GPIO口模拟单总线协议。

// 伪代码流程 void DHT22_Read(float *temperature, float *humidity) { // 1. 主机拉低总线至少1ms,然后拉高20-40us,发起起始信号 Set_Pin_Output(); HAL_GPIO_WritePin(DHT22_GPIO_Port, DHT22_Pin, GPIO_PIN_RESET); Delay_ms(2); HAL_GPIO_WritePin(DHT22_GPIO_Port, DHT22_Pin, GPIO_PIN_SET); Delay_us(30); // 2. 切换为输入模式,等待从机响应(80us低电平 + 80us高电平) Set_Pin_Input(); // ... 检测响应信号 // 3. 接收40位数据(16位湿度 + 16位温度 + 8位校验和) // 每位数据都以50us低电平开始,高电平持续时间决定0(26-28us)或1(70us) for(int i=0; i<40; i++) { // ... 检测低电平结束 Delay_us(40); // 等待到数据位中间 if(Read_Pin() == HIGH) data[i/8] |= (1 << (7-(i%8))); // ... 等待高电平结束 } // 4. 校验数据,并转换为浮点数 if(校验通过) { *humidity = (float)((data[0]<<8)|data[1]) / 10.0; *temperature = (float)(((data[2]&0x7F)<<8)|data[3]) / 10.0; if(data[2] & 0x80) *temperature = -(*temperature); // 负温度 } }

注意事项:DHT22读取间隔建议大于2秒。频繁读取会导致其内部温升,影响测量精度。在代码中最好加一个时间戳判断。

HC-SR501状态读取: 这个简单,直接读取GPIO电平即可。但要注意防抖处理。

// 在定时器中断或主循环中 if(HAL_GPIO_ReadPin(PIR_GPIO_Port, PIR_Pin) == GPIO_PIN_SET) { person_present = 1; person_last_time = HAL_GetTick(); // 记录最后一次检测到人的时间 } else { // 可以加入一个延时判断,比如超过2分钟没检测到人,才认为人离开 if((HAL_GetTick() - person_last_time) > 120000) { person_present = 0; } }

4.3 核心控制逻辑与PID算法实现

在主循环中,我们可以这样组织逻辑:

while (1) { // 1. 读取传感器数据(每2秒读一次DHT22,每100ms检测一次人体) Read_Sensors(); // 2. 处理用户输入(按键、编码器) Handle_User_Input(); // 3. 根据当前模式执行控制逻辑 switch(current_mode) { case MODE_MANUAL: // 风速直接由用户设定值决定 target_speed = manual_speed_setting; break; case MODE_AUTO_TEMP: // 计算温度偏差 error = target_temperature - current_temperature; // 调用PID计算器,得到控制输出(PWM占空比) target_speed = PID_Calculate(&pid, error); // 限制输出范围在0-100% target_speed = constrain(target_speed, 0, 100); break; case MODE_SLEEP: // 根据运行时间,线性降低目标风速 target_speed = Calculate_Sleep_Speed(); break; } // 4. 人体感应模式叠加:如果启用了人体感应且无人,则将最终风速置零或设为最低 if(enable_pir && !person_present) { final_speed = MIN_SPEED; // 或 0 } else { final_speed = target_speed; } // 5. 将最终风速(百分比)转换为PWM比较值,并输出到定时器 pwm_compare_value = (final_speed / 100.0) * (ARR_VALUE); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_compare_value); // 6. 更新显示 OLED_Display(); HAL_Delay(100); // 主循环周期 }

一个简易的增量式PID实现

typedef struct { float Kp, Ki, Kd; // PID参数 float integral; // 积分项 float prev_error; // 上一次误差 float out_max, out_min; // 输出限幅 } PID_Controller; float PID_Calculate(PID_Controller *pid, float error) { float proportional = pid->Kp * error; pid->integral += error; // 积分抗饱和 if(pid->integral > pid->out_max) pid->integral = pid->out_max; else if(pid->integral < pid->out_min) pid->integral = pid->out_min; float integral = pid->Ki * pid->integral; float derivative = pid->Kd * (error - pid->prev_error); pid->prev_error = error; float output = proportional + integral + derivative; // 总输出限幅 if(output > pid->out_max) output = pid->out_max; else if(output < pid->out_min) output = pid->out_min; return output; }

参数整定心得:PID参数KpKiKd需要调试。对于风扇这种大惯性系统,可以先设Ki=0Kd=0, 只调Kp。从小到大增加Kp,直到风扇转速开始对温度变化有明显响应,但可能产生振荡。然后加入较小的Kd来抑制振荡。Ki用于消除静差(比如始终和目标差0.5度),但要小心,积分太强会导致系统反应迟钝甚至失控。可以先设一个非常小的Ki值。

4.4 OLED显示与用户界面

使用ssd1306u8g2库来驱动OLED。显示内容应清晰直观:

==================== 智能风扇系统 ==================== 模式:自动温控 设定:26.0 C 当前:27.5 C 65% 风速: 75% 人体: 检测中 ====================

界面可以设计为多级菜单,通过编码器进行切换和设置。例如,单击编码器进入菜单,旋转选择“模式设置”、“目标温度”、“风速”等,再次单击进入子项进行调节。

5. 系统调试、优化与问题排查

5.1 上电调试与模块联调

调试务必遵循“分模块、逐级联调”的原则。

  1. 电源与最小系统:先不接任何外设,只给STM32核心板上电,用串口打印一句“Hello World”,或者点亮一个LED,确保单片机本身工作正常。
  2. 传感器单独调试:接上DHT22,编写测试代码,只做读取和串口打印,看数据是否正常。同样方法测试人体传感器,用手在面前晃动,看串口输出的电平变化是否正确。
  3. 执行器单独调试:接上电机驱动和风扇(先不装扇叶!安全第一!)。写一个测试程序,让PWM占空比从0%缓慢增加到100%,观察电机是否平稳启动、加速。用手机慢动作拍摄或听声音,检查是否有异常振动或噪音。
  4. 显示模块调试:接上OLED,显示固定的文字和图形,确保通信正常。
  5. 逻辑联调:将所有模块接上,运行完整的控制程序。先从手动模式开始,测试按键/编码器控制风速是否流畅。然后切换到自动模式,用手握住DHT22使其升温,观察风扇转速是否会随之增加。

5.2 常见问题与解决方案实录

在开发过程中,我踩过不少坑,这里记录下最典型的几个:

问题1:风扇电机不转或抖动,伴有“滋滋”声。

  • 可能原因A:电源功率不足。电机启动电流很大,如果电源适配器额定电流太小(比如只有1A),在启动瞬间电压会被拉低,导致系统复位。解决:更换为12V/2A或更大电流的适配器。
  • 可能原因B:PWM频率不合适。频率太低(如几十Hz)会听到明显的线圈啸叫,且电机可能抖动;频率太高(如几十kHz)可能超出驱动芯片的响应能力。解决:将PWM频率调整到1kHz-5kHz范围内再测试。
  • 可能原因C:电机驱动芯片逻辑电源未供电或未共地。TB6612的VCC(逻辑电源)必须供电(5V),且其GND必须与STM32的GND可靠连接。解决:检查接线。

问题2:DHT22读取总是失败或数据全零。

  • 可能原因A:时序不精确。单总线协议对微秒级延时非常敏感,不同主频下HAL_Delay_us()函数可能不准。解决:使用定时器来产生精确的微秒延时,或者用__NOP()指令配合循环来编写更精确的延时函数。
  • 可能原因B:未加上拉电阻。DATA线在空闲时必须为高电平。解决:在DATA引脚和3.3V之间焊接一个4.7KΩ的电阻。
  • 可能原因C:供电不足。DHT22在转换时峰值电流较大,如果电源线太长或太细,可能导致电压跌落。解决:在DHT22的VCC和GND引脚就近并联一个100nF的电容。

问题3:人体传感器一直触发或不触发。

  • 可能原因A:感应区域有干扰源。空调出风口、暖气片、阳光直射等热源可能导致误触发。解决:调整传感器安装位置和角度,避开热源。
  • 可能原因B:延时时间设置过长。HC-SR501模块上的“Tx”电位器调得太大,导致一次触发后,输出高电平持续时间长达几分钟。解决:逆时针调节“Tx”电位器,缩短延时。
  • 可能原因C:灵敏度太低。“Sx”电位器调得太低,探测距离变短。解决:顺时针调节“Sx”电位器,提高灵敏度。

问题4:OLED显示乱码或完全不显示。

  • 可能原因A:I2C地址不对。常见的OLED模块有0x3C和0x78两种地址。解决:用I2C扫描程序确认地址,并修改驱动代码中的设备地址。
  • 可能原因B:初始化序列错误。OLED上电后需要一系列命令进行初始化。解决:确保使用的驱动库(如ssd1306)的初始化函数被正确调用,且硬件I2C初始化无误。
  • 可能原因C:电源问题。OLED模块需要稳定的3.3V。解决:检查供电电压。

5.3 性能优化与功能扩展

系统稳定运行后,可以考虑以下优化和扩展:

  1. 低功耗优化:如果希望用电池供电,可以在无人且温度适宜时,让STM32进入停止(Stop)模式,仅靠RTC或外部中断(如人体传感器信号)唤醒,大幅降低待机功耗。
  2. 增加WIFI远程控制:用ESP-01S模块通过AT指令与STM32通信,或者更进阶地,直接用ESP32替代STM32,它自带WIFI和蓝牙,性能更强,可以直接运行物联网框架(如Arduino for ESP32),连接云平台(如阿里云、Home Assistant)。
  3. 增加自然风模式:不是简单的定时开关,而是生成一个随机或模拟自然风速变化的PWM波形,使风感更柔和。
  4. 数据记录与学习:增加一个SD卡模块或EEPROM,记录每天的温度、风速曲线,甚至可以尝试简单的机器学习算法,学习用户的作息习惯,提前调节风速。
  5. 外观与结构设计:用3D打印或亚克力板为你的智能风扇制作一个漂亮的外壳,将电路板、传感器内嵌,提升整体完成度。

从一堆散乱的元器件,到最终一个能智能响应环境、安静平稳运行的风扇,这个过程充满了挑战和乐趣。我最深的体会是,嵌入式开发永远是一个“调试-发现问题-解决-再调试”的循环。硬件上的一个虚焊、软件里一个微妙的时序错误,都可能导致整个系统行为异常。耐心和系统性的调试方法至关重要。这个项目就像一把钥匙,打开了一扇通往物联网和智能硬件的大门,它教会你的不仅仅是STM32编程,更是一套解决问题的工程化思维。当你晚上躺在床上,听着风扇根据室温自动调整发出的轻柔风声,那种亲手创造价值的感觉,就是技术带给我们的最大快乐。如果还想更深入,不妨试试给它加上语音控制,或者让它能通过手机APP查看历史曲线,乐趣和挑战又会提升一个层次。