资讯详情

STM32F407多传感器智能风扇:从硬件接线到状态机控制

📅 2026/9/10 3:25:38 | 华诺云谱 👁 阅读
STM32F407多传感器智能风扇:从硬件接线到状态机控制
简介一套基于STM32F407的智能风扇系统设计资料面向嵌入式单片机学习者、课程设计及电子竞赛参赛者。系统以人体感应、温度采集与火焰检测为核心能自动判断是否有人、环境是否过热或存在火灾险情并据此调节风扇启停与发出警报从而避免无人时持续运转的电力浪费也提升危险场景下的响应速度。压缩包共84个文件、约14.54MB包含H/C语言源文件、Keil工程配置uvprojx、演示视频mp4、答辩文档doc等从源码到可执行文件一应俱全。源码按模块拆分为多组头文件与C文件传感器驱动、应用逻辑分层清晰便于理解外设初始化和中断处理项目采用CMSIS标准目录结构方便直接移植两个演示视频展示实际运行效果配套答辩讲稿帮助梳理设计思路。已有1943人学习下载适合需要快速搭建智能风扇或同类温控、安防联动项目的开发者参考。1. 为什么风扇要“智能”比想象中复杂一个风扇加三个传感器图纸上只多几根线实际调起来却不是一回事。PIR输出数字电平DS18B20返回单总线时序火焰传感器输出模拟电压三路信号格式和实时性完全不同。只对原理图接线不区分GPIO、ADC、外部中断的边界后面每一步都在返工。这套基于STM32F407的智能风扇系统核心是三个判断是否有人、温度是否超标、是否有火焰再组合成风扇启停与调速策略。它解决两类问题人走风扇不停导致的电力浪费以及高温或火情出现后人工响应不及时。对嵌入式开发新手和智能家居原型设计这个结构都值得拆。接下来依次讲硬件选型、驱动实现、调度逻辑以及调试和答辩演示的细节。2. STM32F407智能风扇的系统选型与硬件接线2.1 为什么选STM32F407外设资源和实时性边界先回答一个实际的问题这个项目用8位单片机也能做为什么选STM32F407关键在三个地方。PIR触发一次外部中断系统要在极短时间内完成状态置位中断响应延迟过大会丢掉人进人出的沿信号DS18B20是单总线协议对微秒级时间窗要求很高主频低的芯片在几十微秒的时隙里容易被其他逻辑打乱风扇调速需要定时器输出kHz级PWM分辨率低于8位时低速档会出现明显的电机抖振。STM32F407的168MHz主频、NVIC可编程优先级和16位定时器把这三个条件一次满足。嵌入式硬件开发里还有一个容易被低估的点外设之间是否存在资源竞争。如果火焰传感器的ADC采样和PWM占空比更新共用同一个DMA流某次ADC触发可能延迟PWM更新风扇转速就会偶发抖动。STM32F407有独立ADC、独立定时器和独立EXTI中断线我一般把三个传感器拆开放到互不争夺的外设上而不是把所有信号挤在同一个端口上这样后续扩展其他功能时不用重构底层。这里的固件选经典标准外设库不用HAL库。F407的标准库工程目录一般是CMSIS、Project、USER三个文件夹启动文件、外设库和用户代码分层清晰。拿这个项目举例解压出来就是Fan_Project其下正好对应这三层结构。标准库代码里直接操作外设寄存器结构体一件事对应一个库函数答辩时对着工程逐段讲很直观。HAL库当然也能跑但初始化代码包含大量句柄绑定逻辑新手讲“为什么这么配置”时容易被外设句柄绕进去。对课设和第一版产品原型标准库更合适。2.2 三路传感器的接口选型工程里用到的三路传感器列成一张表方便对照选型传感器输出类型F407外设数据格式典型参数PIR人体红外HC-SR501数字电平GPIO外部中断EXTI0高/低电平探测距离37米可调DS18B20温度传感器单总线GPIO模拟时序16位温度数据-55125℃分辨率0.0625℃火焰传感器模块模拟电压ADC1通道PA103.3V火焰探测距离约1米选型逻辑很简单。PIR模块自带红外热释电信号处理输出稳定的数字电平不需要单片机做滤波直接进GPIO外部中断。DS18B20挂在任意空闲GPIO上不需要占用硬件I2C或SPIF407的IO翻转速度完全能模拟单总线时序。火焰模块自带LM393比较器DO脚可以输出数字量但接了AO模拟输出才能判断火焰远近这个电压信号进ADC1的PA1通道。接线时有两个坑经常导致返工。第一个是DS18B20的数据线必须配4.7kΩ上拉电阻到3.3V。买模块板时上拉电阻已经在板上裸芯片就需要自己加。若上拉到5V数据线高电平超过GPIO耐压短时间工作正常时间一长引脚内部保护二极管老化读数会随机跳变。第二个是火焰模块AO输出的分压。模块用5V供电时AO满量程也是5V直接进PA1会超出ADC量程我用10kΩ和5.6kΩ电阻分压/* 火焰AO输出05V经10k/5.6k分压后进PA1 满量程电压 5 * 5600 / (10000 5600) 1.79V */ #define FIRE_VREF 1.79f #define FIRE_ADC_MAX 4095PA1作为ADC输入时不要开启内部上拉。分压网络已经有确定的对地阻抗内部上拉会改变分压比例读出来的电压整体偏高。2.3 风扇驱动电路与引脚映射风扇不能直接挂在STM32F407的GPIO上。GPIO推挽输出虽然能瞬间给出20mA以上电流但风扇正常工作电流在200mA量级启动瞬间更高直接驱动会烧内部驱动管。我用N沟道MOS管AO3400做功率开关栅极串一个100Ω电阻接PWM输出脚源极接地漏极接风扇负极风扇正极接5V。风扇是感性负载PWM关断瞬间会产生反向电动势必须在风扇两端反向并联一颗1N5819肖特基二极管把这个尖峰钳位到电源轨内否则MOS管的DS电压会被瞬间拉到几十伏一次就击穿。实际工程里我用的引脚映射如下信号分布在F407不同端口上既方便跳线也方便在逻辑上区分信号类型信号MCU引脚连接目标说明PIR_OUTPE0HC-SR501 DO检测到人时输出高电平DQPE1DS18B20数据单总线需4.7kΩ上拉FIRE_AOPA1火焰模块AOADC1_IN1分压后输入FAN_PWMPB6MOS管栅极TIM4_CH110kHz PWMGNDGND各模块地与MCU共地这套映射还有个好处PIR和DS18B20在不同端口即使某个引脚损坏只需改宏定义不影响其他模块。火焰ADC通道和PWM定时器通道相互独立采样和输出之间不会发生外设资源抢占。电源入口建议加一个470uF电解电容否则风扇启动瞬间会把5V拉低轻则PIR电平抖动重则MCU直接复位这个现象在USB供电时尤其明显。3. 基于标准外设库的驱动层实现3.1 GPIO与外部中断配置先完成PIR的GPIO初始化和外部中断映射。static void Sensor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); /* PE0接PIR输出 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOE, GPIO_InitStructure); /* PE1接DS18B20数据线输入模式下等待外部上拉 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOE, GPIO_InitStructure); /* PIR上升沿和下降沿都捕获 */ SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource0); EXTI_InitStructure.EXTI_Line EXTI_Line0; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Rising_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }这里没有开启内部上拉或下拉因为HC-SR501模块DO引脚是推挽输出外部电平已经确定内部上拉反而会干扰低电平判断。EXTI触发方式选择双边沿模块检测到人时输出高电平延时结束后恢复低电平两个沿都代表状态变化必须都收。NVIC抢占优先级设为1后面PWM更新中断如果需要开启优先级要高于这个值否则长时间运行后占空比会出现微小偏差。中断服务函数里只做一件事读电平置全局标志。不要在中断里做温度读取或ADC转换这些操作耗时超过1ms会挤占主循环中对DS18B20时序的要求。volatile uint8_t pir_flag 0; void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_0) SET) { pir_flag 1; } else { pir_flag 0; } EXTI_ClearITPendingBit(EXTI_Line0); } }pir_flag是volatile全局变量主循环每次循环都重新读取当前状态。这样设计即使PIR在极短时间反复触发主循环也只处理最终状态不会积压一大堆中断事件。3.2 DS18B20单总线时序实现DS18B20是整个工程里对时序最敏感的部分。我先用开漏输出的方式初始化PE1利用外部上拉电阻直接模拟总线的释放与拉低不需要反复切换GPIO模式#define DQ_PORT GPIOE #define DQ_PIN GPIO_Pin_1 #define DQ_LOW() GPIO_ResetBits(DQ_PORT, DQ_PIN) #define DQ_HIGH() GPIO_SetBits(DQ_PORT, DQ_PIN) /* 释放总线由外部上拉拉高 */ #define DQ_READ() GPIO_ReadInputDataBit(DQ_PORT, DQ_PIN) uint8_t DS18B20_Reset(void) { uint8_t presence; DQ_LOW(); /* 主机拉低总线 */ delay_us(480); /* 复位脉冲不小于480us */ DQ_HIGH(); /* 释放总线 */ delay_us(70); /* 等待从机回应 */ presence DQ_READ(); /* 0表示设备存在 */ delay_us(410); return presence; }读一位的时序更讲究。DS18B20规定主机拉低总线产生起始信号然后在15us内读取引脚电平这个采样窗口非常窄。如果此时PIR外部中断插入读到的位就错了温度值会变成0xFFFF或剧烈跳变。我在读位函数里临时关中断uint8_t DS18B20_ReadBit(void) { uint8_t val 0; __disable_irq(); DQ_LOW(); delay_us(1); DQ_HIGH(); /* 释放总线进入采样窗口 */ delay_us(10); if (DQ_READ()) val 1; delay_us(45); __enable_irq(); return val; }延时6us结束之后主机拉高释放总线从设备在15us内把数据放到总线上。关中断的时长大约在60us左右对系统其他功能影响很小。如果后面移植到FreeRTOS这里要换成临界区保护函数而不是直接__disable_irq否则会和任务调度器冲突。读温度时每次先发复位然后跳过ROM、启动转换等待750ms后再读暂存器float DS18B20_GetTemperature(void) { uint8_t low, high; int16_t raw; if (DS18B20_Reset() ! 0) return -1000; /* 返回哨兵值表示设备丢失 */ DS18B20_WriteByte(0xCC); /* 跳过ROM指令 */ DS18B20_WriteByte(0x44); /* 启动温度转换 */ delay_ms(750); DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); /* 读暂存器 */ low DS18B20_ReadByte(); high DS18B20_ReadByte(); raw (high 8) | low; return raw * 0.0625f; /* 12位分辨率每位0.0625℃ */ }返回-1000这个哨兵值是有意的。主循环里判断如果温度小于-50就认为传感器丢失使用上一次正常值或默认25℃继续运行不让风扇因为这个故障停摆。调试时若打印出125或85通常是上拉电阻没接或PE1的ODR被意外改动先查硬件再查代码。3.3 定时器PWM输出与ADC采样通道风扇调速用TIM4的通道1输出引脚是PB6。初始化代码如下void FAN_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4); TIM_TimeBaseStructure.TIM_Period 8400 - 1; TIM_TimeBaseStructure.TIM_Prescaler 0; /* 84MHz直接计数 */ TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, 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(TIM4, TIM_OCInitStructure); TIM_Cmd(TIM4, ENABLE); }参数上要算一笔账TIM4挂在APB1上系统主频168MHz时APB1定时器时钟是84MHz。预分频设0即84MHz直接计数自动重装值8400-1PWM频率就是84MHz/840010kHz。10kHz对直流无刷风扇是合适的选择MOS开关损耗可控也听不到可闻噪声。占空比则由TIM_Pulse控制CCR值越大高电平时间越长。几个常用档位我整理成表风速档位CCR1值占空比适用场景停止00%无人且温度低低速210025%有人且室温正常高速525062.5%温度超过上限全速8400100%火警状态完整档位低档从25%起步而不是从0%开始这是有意为之。直流风扇PWM占空比低于20%时电机转矩不足以克服静摩擦会出现“通电但不转”的状态电流还在白白消耗。需要用全速PWM启动1秒再切回低速工程里叫软启动。火焰传感器的ADC配置和读取也放这一层void ADC1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, GPIO_InitStructure); ADC_InitStructure.ADC_Resolution ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion 1; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_56Cycles); ADC_Cmd(ADC1, ENABLE); } uint16_t GetFireADC(void) { ADC_SoftwareStartConv(ADC1); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); }采样时间选择56周期对火焰传感器这种变化较慢的模拟信号足够采样值稳定。每次转换由软件触发主循环调用一次就阻塞等待结果不引入DMA中断逻辑最简单也最不容易出错。4. 智能调度逻辑从传感器数据到风扇控制4.1 状态机模型空闲、有人、高温、火警三路传感器组合出来的情况很多我把它收敛成4个状态不给每种组合单独写if-else状态触发条件风扇动作FAN_OFF无人、温度低于下限、无火焰停止FAN_NORMAL有人、温度低于上限、无火焰低速25%FAN_HIGH温度超过上限且有人或温度超高无人高速62.5%FIRE火焰ADC超过触发阈值全速100%这里最关键的设计是火警状态的优先级。温度高、有人、火焰三者同时存在时状态必须从任何当前状态强制切到FIRE。这不是简单的代码顺序问题而是整个控制策略的安全边界。如果优先级写反人动一下风扇反而停在低速高温房间需要几秒才进入高速这种响应在火灾场景下完全不能接受。4.2 阈值与滞回比较温度阈值不能只设一个点。只设27℃启动风扇、低于27℃关闭当温度在26.9℃和27.1℃之间波动时风扇会以秒级频率反复启停MOS管很快过热。所以代码里设两个温度阈值中间留出滞回区间#define TEMP_ON_SET 27.5f /* 温度超过该值进入高温档 */ #define TEMP_OFF_SET 24.5f /* 回落到该值以下才退出高温档 */ #define FIRE_TRIGGER 2400 /* 火焰ADC触发阈值 */ #define FIRE_RELEASE 1800 /* 火警解除阈值 */ typedef enum { STATE_FAN_OFF 0, STATE_FAN_NORMAL, STATE_FAN_HIGH, STATE_FIRE } FanState; volatile FanState fan_state STATE_FAN_OFF; volatile uint8_t pir_flag 0; volatile float temp_c 25.0f; volatile uint16_t fire_adc 0; void FanSys_Update(void) { switch (fan_state) { case STATE_FAN_OFF: if (pir_flag || temp_c TEMP_ON_SET) fan_state STATE_FAN_NORMAL; if (fire_adc FIRE_TRIGGER) fan_state STATE_FIRE; break; case STATE_FAN_NORMAL: if (fire_adc FIRE_TRIGGER) fan_state STATE_FIRE; else if (!pir_flag temp_c TEMP_OFF_SET) fan_state STATE_FAN_OFF; else if (temp_c TEMP_ON_SET) fan_state STATE_FAN_HIGH; break; case STATE_FAN_HIGH: if (fire_adc FIRE_TRIGGER) fan_state STATE_FIRE; else if (temp_c TEMP_OFF_SET) fan_state STATE_FAN_NORMAL; break; case STATE_FIRE: if (fire_adc FIRE_RELEASE) fan_state (pir_flag) ? STATE_FAN_HIGH : STATE_FAN_OFF; break; } }逻辑上有两个注意点。第一每个状态的入口都先判断火警保证火焰始终优先。第二FAN_HIGH退出到FAN_NORMAL用的是24.5℃进入时是27.5℃这3℃就是滞回带。温度从28℃缓慢降到24℃风扇才从高速退回低速不会在27.5℃附近来回切换。FIRE_TRIGGER和FIRE_RELEASE之间也有600个ADC码值的滞回距离防止火焰在临界距离闪烁导致状态频繁抖动。风扇转速输出单独放一个函数状态切换和输出驱动分离static const uint16_t duty_table[4] {0, 2100, 5250, 8400}; void FanSys_Apply(void) { if (fan_state STATE_FIRE) { TIM_SetCompare1(TIM4, duty_table[STATE_FIRE]); } else { TIM_SetCompare1(TIM4, duty_table[fan_state]); } }占空比数组的索引直接用枚举值读代码时一眼能看出每个状态对应的PWM输出。FIRE状态单独处理是防止有人后续调整枚举顺序导致数组错位这个细节在工程维护时很有价值。4.3 自动/手动模式与安全兜底成品风扇通常会留一个物理按键切换自动和手动模式。函数逻辑如下uint8_t manual_mode 0; uint8_t manual_level 1; void FanSys_ManualSwitch(void) { if (manual_mode) { manual_mode 0; FanSys_Update(); /* 切回自动立即按当前状态调整 */ } else { manual_mode 1; TIM_SetCompare1(TIM4, duty_table[manual_level]); } }进入手动模式后状态机仍然继续运行只是输出不再自动更新由按键调整扇速档位。这里有一个安全兜底一旦fire_adc超过FIRE_TRIGGER主循环检测后立即清除manual_mode强制回到自动并全速运行。做这个兜底的原因很实际手动模式的目的是省电和静音但火警出现时任何省电策略都要给安全让路。主循环按固定节奏执行整个调度while (1) { temp_c DS18B20_GetTemperature(); if (temp_c -50) temp_c 25.0f; /* 传感器丢失时维持默认温度 */ fire_adc GetFireADC(); if (!manual_mode) { FanSys_Update(); FanSys_Apply(); } else { if (fire_adc FIRE_TRIGGER) { /* 火警优先于手动 */ manual_mode 0; FanSys_Update(); FanSys_Apply(); } } printf(temp%d.%d fire%u state%d pwm%u\r\n, (int)temp_c, (int)(temp_c * 10) % 10, fire_adc, fan_state, TIM_GetCapture1(TIM4)); delay_ms(200); IWDG_ReloadCounter(); /* 喂狗 */ }200ms一次的循环节拍足够快PIR中断置位的事件最迟200ms内就会被处理人体走动在风扇响应上察觉不到延迟。独立看门狗超时会导致芯片复位复位后PWM引脚默认空闲风扇停止相当于一个故障安全位置。如果后续移植到FreeRTOS喂狗操作要放在低优先级任务里否则空闲任务占用CPU时看门狗容易误复位。5. 串口调试、阈值标定与答辩演示技巧5.1 用串口打印状态切换点多路传感器同时工作只靠眼睛盯风扇转速很难判断状态机到底走了哪一步。我习惯重定向printf到USART2主循环每秒打印一组完整数据int fputc(int ch, FILE *f) { USART_SendData(USART2, (uint8_t)ch); while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); return ch; }printf的参数顺序和状态机一一对应串口屏上看一行数据就能定位问题。调试时常用的观测点整理如下动作串口显示说明人走进探测区pir_flag1state变1HC-SR501距离调到最小吹风机热风靠近state变2pwm5250热源不要直接贴传感器打火机火花靠近state变3pwm8400保持30cm以上闪一下就移开人走开后延时结束state回1或0先调小PIR延时电位器如果state在0和1之间无规律跳多半是pir_flag没有正确清掉如果温度打印出-1000或125说明DS18B20复位失败先检查上拉电阻。5.2 阈值标定方法温度阈值不能凭感觉定。把DS18B20和水银温度计放在同一位置等5分钟后读取单片机打印值记录偏差。如果整体偏高2℃代码里加一个偏移量补偿不需要改硬件。火焰阈值用打火机在20cm、15cm、10cm处分别记录ADC值取触发距离对应的AD值再留20%余量每组数据至少测三次取中位数。5.3 演示视频的录制建议演示视频分两个场景拍。第一个拍硬件完整运行从PIR触发、风扇启动到温度上升后切换高速画面同时包含开发板和传感器模块。第二个拍示波器或串口画面把示波器探头夹在PB6引脚显示频率和占空比读数同时画面里能看到风扇转速变化这样切换档位时占空比数值和风扇转速同步变化硬件证据链就完整了。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。