最近在做一个嵌入式课程设计,需要实现一个智能风扇系统,要求能根据温度自动调节风速,还要支持手动和自动双模式切换。网上找了一圈,发现很多教程要么代码不完整,要么缺少关键配置说明,调试起来特别费劲。正好手头有江科大的STM32开发套件,于是决定自己从头实现一个完整的智能风扇项目,把温度采集、PWM控制、模式切换、报警功能都整合进去。
本文就是这次项目的完整实战记录,从硬件选型、电路设计到代码编写、调试优化,每一步都有详细说明和完整代码。无论你是正在做课程设计的学生,还是想学习STM32实际应用的开发者,都能跟着一步步做出可用的智能风扇系统。
1. 项目背景与核心功能设计
智能风扇是一个典型的嵌入式物联网应用,它融合了传感器数据采集、执行器控制和用户交互。使用STM32单片机作为核心控制器,成本低、性能强、生态完善,非常适合作为学习和项目开发平台。
本项目基于江科大STM32开发套件(通常指正点原子或野火等常见开发板,核心为STM32F103系列),实现一个具备以下核心功能的智能风扇系统:
- 温度监测与显示:通过DS18B20数字温度传感器实时采集环境温度,并在OLED屏幕上显示。
- 自动温控模式:
- 系统根据设定的温度阈值自动控制风扇转速。
- 例如:温度低于25°C时,风扇停止;温度在25°C~30°C时,风扇低速运行;温度高于30°C时,风扇全速运行。
- 手动控制模式:用户可以通过按键手动设置风扇的PWM占空比(即风速等级),实现无级或有级调速。
- 双模式切换:通过另一个按键在“自动模式”和“手动模式”之间自由切换,当前模式在OLED上有明确标识。
- 温度报警功能:当温度超过设定的安全阈值(如35°C)时,系统通过蜂鸣器鸣叫和LED闪烁进行声光报警,提醒用户注意。
系统整体架构: 传感器(DS18B20)将温度数据发送给STM32,STM32核心根据当前模式(自动/手动)和算法,计算出应对的PWM信号输出给风扇驱动电路(如MOS管),从而控制风扇转速。同时,STM32驱动OLED显示状态,并监听按键输入。整个逻辑清晰,涵盖了输入、处理、输出、人机交互等嵌入式系统核心环节。
2. 硬件准备与电路设计
“江科大STM32套件”通常包含STM32最小系统板、下载器、杜邦线、常用传感器和模块。以下是本项目所需的核心硬件清单:
- 主控芯片:STM32F103C8T6最小系统板(核心板)。
- 温度传感器:DS18B20(单总线数字温度传感器,精度±0.5°C)。
- 显示模块:0.96寸OLED显示屏(I2C接口,SSD1306驱动)。
- 风扇模块:5V直流风扇(注意电流,开发板IO口不能直接驱动)。
- 驱动电路:N沟道MOS管(如IRF540N)或电机驱动模块(如L298N),用于驱动风扇。
- 输入设备:两个轻触按键。
- 报警设备:有源蜂鸣器、LED灯。
- 其他:电阻、面包板、杜邦线若干。
2.1 核心电路连接原理
STM32的IO口驱动能力有限(通常8-20mA),无法直接驱动风扇(电流可能超过100mA)。因此,必须使用MOS管或电机驱动芯片作为“开关”和“放大器”。
1. 风扇驱动电路(关键!)这是最容易出问题的地方。推荐使用N沟道MOS管方案,成本低、电路简单。
- STM32 PWM输出引脚(如PA8)连接到MOS管的栅极(G)。
- MOS管的漏极(D)连接风扇的负极。
- 风扇的正极接5V电源正极。
- MOS管的源极(S)接地(GND)。
- 在栅极(G)和地(GND)之间连接一个10kΩ的下拉电阻,确保STM32复位期间MOS管关闭。
- 在栅极(G)和STM32引脚之间串联一个100-220Ω的电阻,用于限流,保护STM32的IO口。
- 电源注意:开发板的3.3V或5V引脚可能无法提供风扇所需的大电流,建议使用外部5V电源单独为风扇供电,但务必确保外部电源的地(GND)和开发板的地(GND)连接在一起,即“共地”。
2. 温度传感器DS18B20连接DS18B20采用单总线协议,仅需一根数据线。
- VDD接3.3V或5V。
- DQ(数据线)接STM32任一GPIO口(如PA1),同时通过一个4.7kΩ上拉电阻接VDD。
- GND接地。
3. OLED显示屏连接(I2C)
- VCC接3.3V。
- GND接地。
- SCL接STM32的PB6(I2C1_SCL)。
- SDA接STM32的PB7(I2C1_SDA)。
4. 按键连接两个按键分别接STM32的GPIO口(如PA0, PA1),另一端接地。GPIO配置为上拉输入模式,按键未按下时读到的为高电平,按下时为低电平。
5. 蜂鸣器与LED
- 有源蜂鸣器正极通过一个三极管或MOS管(小功率可直接串联电阻)接STM32 GPIO(如PB8),负极接地。
- LED阳极串联一个220Ω-1kΩ的限流电阻后接STM32 GPIO(如PB9),阴极接地。
2.2 软件环境准备
- IDE:Keil uVision 5 (MDK-ARM)。确保已安装STM32F1系列的Device Family Pack。
- 固件库:使用标准外设库(Standard Peripheral Library)或HAL库均可。本文示例基于标准外设库,因为它更贴近硬件,便于理解原理。江科大的教学资料也多以此为基础。
- 串口调试助手:用于调试时打印数据,如SecureCRT、Putty或野火/正点原子自带的调试助手。
- 程序下载器:ST-Link V2 或 J-Link,用于将编译好的程序烧录到STM32芯片中。
3. 软件架构与关键驱动实现
在开始写主程序之前,我们需要先为各个外设编写可靠的底层驱动程序。一个好的驱动应该封装好初始化、读、写等基本操作,为上层的应用逻辑提供清晰的接口。
3.1 DS18B20温度传感器驱动
DS18B20的时序要求严格,需要微秒级延时。我们首先实现底层的延时、复位、读写位函数。
// ds18b20.c #include "ds18b20.h" #include "delay.h" // 需要一个精准的微秒延时函数,需自己实现或使用SysTick // 引脚定义,根据实际连接修改 #define DS18B20_PORT GPIOA #define DS18B20_PIN GPIO_Pin_1 #define DS18B20_DQ_OUT PAout(1) // 输出宏,来自GPIO位带操作 #define DS18B20_DQ_IN PAin(1) // 输入宏 // 初始化,将引脚设置为推挽输出 void DS18B20_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = DS18B20_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DS18B20_PORT, &GPIO_InitStructure); DS18B20_DQ_OUT = 1; // 先拉高 } // 写一个字节 void DS18B20_WriteByte(u8 dat) { u8 i; for(i=0; i<8; i++) { DS18B20_DQ_OUT = 0; // 拉低开始写时序 delay_us(5); // 保持低电平至少1us,我们延时5us DS18B20_DQ_OUT = dat & 0x01; // 输出数据位 delay_us(60); // 保持60us DS18B20_DQ_OUT = 1; // 释放总线 dat >>= 1; delay_us(5); // 恢复时间 } } // 读一个字节 u8 DS18B20_ReadByte(void) { u8 i, value = 0; for(i=0; i<8; i++) { value >>= 1; DS18B20_DQ_OUT = 0; // 拉低开始读时序 delay_us(2); // 拉低2us后释放 DS18B20_DQ_OUT = 1; delay_us(10); // 等待10us后采样 if(DS18B20_DQ_IN) value |= 0x80; delay_us(50); // 等待该时隙结束 } return value; } // 开始温度转换 void DS18B20_Start(void) { DS18B20_Init(); DS18B20_Reset(); // 复位函数需实现 DS18B20_WriteByte(0xCC); // 跳过ROM命令 DS18B20_WriteByte(0x44); // 开始转换命令 } // 读取温度值(整数部分) float DS18B20_GetTemp(void) { u8 tempL, tempH; u16 temp; float temperature; DS18B20_Init(); DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器命令 tempL = DS18B20_ReadByte(); tempH = DS18B20_ReadByte(); temp = (tempH << 8) | tempL; if(temp & 0x8000) { // 负温度 temperature = -(float)((temp ^ 0xFFFF) + 1) * 0.0625; } else { // 正温度 temperature = (float)temp * 0.0625; // 精度0.0625°C/LSB } return temperature; }3.2 OLED显示屏驱动(I2C)
OLED驱动通常使用现成的ssd1306或oled.c库。这里展示关键初始化与显示函数接口。
// oled.h #ifndef __OLED_H #define __OLED_H #include "stm32f10x.h" void OLED_Init(void); void OLED_Clear(void); void OLED_ShowString(u8 x, u8 y, const u8 *p); void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size); void OLED_ShowFloat(u8 x, u8 y, float num, u8 int_len, u8 dec_len, u8 size); void OLED_Refresh(void); // 更新显存到屏幕 #endif// oled.c (部分关键函数) // 假设已移植好底层I2C读写和字库 void OLED_ShowTempAndMode(float temp, u8 mode, u8 fan_speed) { OLED_Clear(); OLED_ShowString(0, 0, "Temp:"); OLED_ShowFloat(40, 0, temp, 2, 2, 16); // 显示温度,如“25.50” OLED_ShowString(90, 0, "C"); OLED_ShowString(0, 2, "Mode:"); if(mode == 0) { OLED_ShowString(40, 2, "AUTO"); } else { OLED_ShowString(40, 2, "MANUAL"); } OLED_ShowString(0, 4, "Speed:"); OLED_ShowNum(40, 4, fan_speed, 3, 16); // 显示速度百分比 OLED_ShowString(70, 4, "%"); OLED_Refresh(); }3.3 PWM输出配置(用于控制风扇速度)
我们使用STM32的定时器(如TIM1)的PWM输出功能来产生不同占空比的方波,控制MOS管的通断时间,从而调节风扇两端的平均电压。
// pwm.c #include "pwm.h" #include "stm32f10x.h" // 使用TIM1的通道1(PA8)输出PWM void PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); // 配置GPIO GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置定时器基础 TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装载值,决定PWM频率 TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频系数 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // 配置PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性高 TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比为0,风扇不转 TIM_OC1Init(TIM1, &TIM_OCInitStructure); // 使能预装载寄存器 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); // 使能定时器 TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器必须使能主输出 } // 设置PWM占空比 // compare: 比较值,范围0~arr。占空比 = compare / (arr+1) void PWM_SetCompare1(u32 compare) { TIM_SetCompare1(TIM1, compare); }参数计算:PWM频率 = 定时器时钟 / ((arr+1)(psc+1))。例如,定时器时钟72MHz,设置arr=999,psc=71,则PWM频率 = 72,000,000 / (100072) = 1000Hz (1kHz)。这是一个适合电机控制的频率。
3.4 按键扫描与模式切换
我们需要两个按键:KEY_MODE用于切换自动/手动模式,KEY_UP在手动模式下增加风速。
// key.c #include "key.h" #include "delay.h" // 引脚定义 #define KEY_MODE_GPIO_PORT GPIOA #define KEY_MODE_GPIO_PIN GPIO_Pin_0 #define KEY_UP_GPIO_PORT GPIOA #define KEY_UP_GPIO_PIN GPIO_Pin_1 // 初始化,上拉输入 void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = KEY_MODE_GPIO_PIN | KEY_UP_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } // 扫描函数,返回按键值,带消抖 // 0: 无按键,1: KEY_MODE按下,2: KEY_UP按下 u8 KEY_Scan(void) { static u8 key_up = 1; // 按键松开标志 if(key_up && (KEY_MODE==0 || KEY_UP==0)) { delay_ms(10); // 消抖 key_up = 0; if(KEY_MODE == 0) return 1; else if(KEY_UP == 0) return 2; } else if(KEY_MODE==1 && KEY_UP==1) { key_up = 1; } return 0; }4. 核心逻辑与主程序实现
有了底层驱动,我们就可以编写主程序的业务逻辑了。核心是一个状态机,根据当前模式决定如何响应传感器数据和按键输入。
4.1 全局变量与宏定义
// main.c 部分全局变量 #include "stm32f10x.h" #include "delay.h" #include "pwm.h" #include "ds18b20.h" #include "oled.h" #include "key.h" #include "beep.h" // 蜂鸣器驱动 #include "led.h" // LED驱动 // 系统工作模式 typedef enum { MODE_AUTO = 0, MODE_MANUAL } FanMode_t; FanMode_t g_fan_mode = MODE_AUTO; // 默认自动模式 u8 g_fan_speed_percent = 0; // 风扇速度百分比 0-100 float g_current_temp = 0.0; // 当前温度 float g_temp_high_alarm = 35.0; // 高温报警阈值 // 温度控制阈值(自动模式用) #define TEMP_THRESHOLD_LOW 25.0 #define TEMP_THRESHOLD_MID 30.0 #define TEMP_THRESHOLD_HIGH 35.0 // PWM周期值(ARR),与初始化一致 #define PWM_MAX_ARR 9994.2 自动模式控制算法
在自动模式下,系统根据采集到的温度,按照预设的规则映射到PWM占空比。
// 根据温度计算自动模式下的PWM占空比 u8 AutoMode_CalcSpeed(float temp) { u8 speed = 0; if(temp < TEMP_THRESHOLD_LOW) { speed = 0; // 低于25度,关闭 } else if(temp >= TEMP_THRESHOLD_LOW && temp < TEMP_THRESHOLD_MID) { // 25-30度,线性增速,从0%到50% speed = (u8)((temp - TEMP_THRESHOLD_LOW) / (TEMP_THRESHOLD_MID - TEMP_THRESHOLD_LOW) * 50); } else if(temp >= TEMP_THRESHOLD_MID && temp < TEMP_THRESHOLD_HIGH) { // 30-35度,线性增速,从50%到100% speed = 50 + (u8)((temp - TEMP_THRESHOLD_MID) / (TEMP_THRESHOLD_HIGH - TEMP_THRESHOLD_MID) * 50); } else { speed = 100; // 高于35度,全速 } // 限制在0-100 if(speed > 100) speed = 100; return speed; }4.3 手动模式控制逻辑
手动模式下,风速由用户通过KEY_UP按键控制。每次按下,风速增加一个步进(如10%),达到100%后循环回0%。
// 手动模式风速调整 void ManualMode_AdjustSpeed(void) { g_fan_speed_percent += 10; // 每次增加10% if(g_fan_speed_percent > 100) { g_fan_speed_percent = 0; // 超过100%则归零 } }4.4 温度报警处理
当温度超过报警阈值时,触发声光报警。这里采用非阻塞方式,在循环中检查并控制报警器。
// 报警控制函数 void Alarm_Control(float temp) { static u32 alarm_tick = 0; if(temp >= g_temp_high_alarm) { // 超过阈值,触发报警(例如LED闪烁,蜂鸣器间歇鸣叫) if((SysTick->VAL % 1000) < 500) { // 简易闪烁,约1Hz LED_ON(); BEEP_ON(); } else { LED_OFF(); BEEP_OFF(); } } else { // 温度正常,关闭报警 LED_OFF(); BEEP_OFF(); } }4.5 主函数与主循环
将以上所有模块整合到主函数中。
int main(void) { u8 key_value = 0; u32 last_temp_read_time = 0; u32 last_display_update_time = 0; // 系统初始化 delay_init(); // 延时函数初始化(使用SysTick) PWM_Init(999, 71); // PWM频率1kHz DS18B20_Init(); OLED_Init(); KEY_Init(); LED_Init(); BEEP_Init(); OLED_ShowString(0, 0, "Smart Fan Init..."); delay_ms(1000); OLED_Clear(); while(1) { u32 current_time = SysTick->VAL; // 获取系统tick,需根据实际情况调整 // 1. 按键扫描与处理(约每10ms扫描一次) key_value = KEY_Scan(); if(key_value == 1) { // 模式切换键 g_fan_mode = (g_fan_mode == MODE_AUTO) ? MODE_MANUAL : MODE_AUTO; // 切换模式时,可以有些提示,比如蜂鸣器响一声 BEEP_ON(); delay_ms(100); BEEP_OFF(); } else if(key_value == 2 && g_fan_mode == MODE_MANUAL) { // 手动模式下调速 ManualMode_AdjustSpeed(); } // 2. 温度采集(约每500ms采集一次,避免DS18B20频繁操作) if(current_time - last_temp_read_time > 500) { DS18B20_Start(); // 开始转换 delay_ms(750); // 等待转换完成,12位精度最大需750ms g_current_temp = DS18B20_GetTemp(); last_temp_read_time = current_time; } // 3. 根据模式计算目标风速 u8 target_speed = 0; if(g_fan_mode == MODE_AUTO) { target_speed = AutoMode_CalcSpeed(g_current_temp); // 自动模式下,更新全局速度变量用于显示 g_fan_speed_percent = target_speed; } else { target_speed = g_fan_speed_percent; // 手动模式直接用设定的速度 } // 4. 设置PWM输出(将百分比转换为比较值) u32 compare = (target_speed * PWM_MAX_ARR) / 100; PWM_SetCompare1(compare); // 5. 报警判断与控制 Alarm_Control(g_current_temp); // 6. 更新显示(约每200ms更新一次,避免OLED刷新过快) if(current_time - last_display_update_time > 200) { OLED_ShowTempAndMode(g_current_temp, g_fan_mode, g_fan_speed_percent); last_display_update_time = current_time; } delay_ms(10); // 主循环延时,降低CPU占用 } }5. 项目调试与常见问题排查
将代码编译下载后,可能遇到各种问题。以下是基于经验的排查清单。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 风扇不转 | 1. 电源问题 2. PWM无输出 3. MOS管接线错误或损坏 4. 风扇损坏 | 1. 用万用表测量风扇两端电压,检查5V电源和共地。 2. 用示波器或逻辑分析仪检查STM32的PWM引脚(如PA8)是否有波形输出。如果没有,检查PWM初始化代码和引脚配置。 3. 检查MOS管的G、D、S极是否接反,栅极下拉电阻和限流电阻是否焊接良好。可以用导线短暂连接D和S,看风扇是否转动,判断MOS管好坏。 4. 直接给风扇加5V,看是否转动。 |
| 温度显示为0或85°C(固定值) | 1. DS18B20初始化或时序错误 2. 上拉电阻未接或接触不良 3. 传感器损坏 | 1. 检查DS18B20的DQ线是否通过4.7kΩ电阻上拉到3.3V/5V。2. 用逻辑分析仪抓取单总线时序,与DS18B20数据手册对比。重点检查复位脉冲、应答脉冲的宽度。 3. 检查 delay_us()函数的准确性,DS18B20对微秒延时非常敏感。4. 更换一个DS18B20传感器试试。 |
| OLED白屏或不显示 | 1. I2C地址错误 2. I2C引脚配置错误 3. 初始化序列错误 4. 电源问题 | 1. 确认OLED模块的I2C地址(通常是0x78或0x7A)。 2. 用示波器检查SCL和SDA线上是否有波形。检查GPIO是否配置为开漏输出并上拉。 3. 检查OLED初始化代码,确保发送了正确的命令序列。 4. 测量OLED模块的VCC是否为3.3V。 |
| 按键无反应或连按 | 1. GPIO模式配置错误(应为上拉输入) 2. 按键消抖处理不当 3. 按键硬件接触不良 | 1. 确认按键GPIO初始化为GPIO_Mode_IPU(上拉输入)。2. 在按键扫描函数中增加合适的延时消抖(10-20ms)。 3. 用万用表测量按键按下时,IO口是否被拉低到0V。 |
| 模式切换后风扇行为不对 | 1. 全局变量g_fan_mode未正确更新2. 自动/手动速度计算逻辑有误 3. PWM设置未在模式切换后立即更新 | 1. 在OLED上显示当前模式,确认按键是否成功切换了g_fan_mode。2. 在自动模式下,用手捂住DS18B20加热,观察OLED显示的速度百分比是否按预期变化。 3. 确保每次循环都根据当前模式重新计算并设置PWM。 |
| 蜂鸣器不响或常响 | 1. 蜂鸣器是有源还是无源接错 2. 驱动三极管/MOS管电路错误 3. GPIO控制逻辑反了 | 1.有源蜂鸣器:给高电平就响,给低电平就停。无源蜂鸣器:需要给一定频率的PWM信号才会响。本项目用有源蜂鸣器做报警提示更简单。 2. 检查驱动管的基极/栅极是否通过电阻连接到STM32 IO口,集电极/漏极是否接蜂鸣器正极。 3. 检查代码中 BEEP_ON()是置高还是置低。 |
调试建议:
- 分模块调试:不要一次性写完所有代码。先单独测试DS18B20能否读出温度并串口打印,再单独测试PWM能否驱动风扇,最后测试OLED显示。
- 善用串口打印:在关键逻辑处使用
printf通过串口输出变量值(如温度、模式、速度),这是最直接的调试手段。需要初始化串口并重定向fputc函数。 - 使用调试器:利用ST-Link和Keil的在线调试功能,设置断点,观察变量实时值,单步执行,能快速定位逻辑错误。
6. 功能优化与扩展建议
完成基本功能后,可以考虑以下优化和扩展,让项目更完善、更实用。
6.1 增加更多控制模式
- 自然风模式:让PWM占空比随机或有规律地变化,模拟自然风。
- 睡眠模式:在夜间,根据时间自动降低风速或关闭风扇。
- 定时关闭:增加定时功能,设置风扇运行一段时间后自动停止。
6.2 改善用户交互
- 增加更多按键:添加“减速”、“设置”等按键。
- 使用旋转编码器:替代加减速按键,实现更流畅的速度调节。
- 添加声音反馈:不同操作(模式切换、报警)使用不同频率的提示音。
3. 使用更高级的控制器
- PID控制:在自动模式下,使用PID算法让风扇转速更平滑地跟随温度变化,避免阶跃变化。
// 简易PID思路(伪代码) error = target_temp - current_temp; integral += error; derivative = error - last_error; output = Kp*error + Ki*integral + Kd*derivative; // 将output映射为PWM占空比 - 状态机框架:使用QP等状态机框架来管理“关机”、“自动”、“手动”、“报警”等系统状态,使程序结构更清晰。
6.4 增加通信与联网功能
- 蓝牙/Wi-Fi控制:接入ESP8266或HC-05模块,通过手机APP或小程序控制风扇、设置参数、查看温度历史。
- 上位机监控:通过STM32的串口将温度、风速数据发送到电脑上位机,绘制实时曲线。
6.5 提升可靠性
- 传感器数据滤波:对DS18B20读取的温度值进行滑动平均滤波或中值滤波,避免显示值跳动。
#define FILTER_LEN 5 float temp_buffer[FILTER_LEN]; float Get_Filtered_Temp(float new_temp) { static u8 index = 0; temp_buffer[index] = new_temp; index = (index + 1) % FILTER_LEN; float sum = 0; for(u8 i=0; i<FILTER_LEN; i++) sum += temp_buffer[i]; return sum / FILTER_LEN; } - 参数存储:将用户设定的报警阈值、手动模式下的偏好速度等参数保存到STM32的Flash中,掉电不丢失。
- 看门狗:启用独立看门狗(IWDG),防止程序跑飞导致风扇失控。
这个基于江科大STM32套件的智能风扇项目,从硬件连接到软件编程,完整地走了一遍嵌入式系统开发流程。它不仅是一个课程设计,更是一个理解传感器、执行器、PWM、人机交互和状态控制的绝佳范例。代码虽然不复杂,但涵盖了嵌入式开发的核心思想:感知、决策、执行。
在实际动手时,最关键的是耐心调试硬件电路和底层驱动。遇到问题,按照第5部分的排查清单一步步来,大部分都能解决。希望这份详细的教程和代码能帮你少走弯路,顺利做出属于自己的智能风扇。