news 2026/9/4 21:41:12

基于STM32的智能绿色风扇毕业设计:从硬件选型到代码实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于STM32的智能绿色风扇毕业设计:从硬件选型到代码实现详解

许多单片机毕业设计题目看起来相似,但真正拉开差距的往往是系统设计的完整性和“绿色智能”这两个词的落地程度。市面上很多风扇控制方案只是用手动按键调速,离“智能”还有一段距离;而如果只做一个温度控制风扇,又缺少人体感应、自动/手动模式切换等工程化设计,难以体现完整的系统思维。

本文围绕“基于 STM32 的智能绿色风扇”展开,梳理一套可直接用于毕业设计答辩和实物演示的实现方案。内容覆盖需求分析、器件选型、电路设计、STM32 核心代码、传感器驱动、PWM 调速逻辑、调试方法以及常见坑点。无论你是刚接触 STM32 的本科生,还是想快速搭建一个单片机综合项目的开发者,都可以照着本文一步步完成。

1. 智能绿色风扇的需求背景与设计思路

1.1 什么是智能绿色风扇

智能绿色风扇并不是一个标准名词,而是单片机系统设计中常见的“节能 + 自动控制”类项目。它通常具备以下能力:

  • 根据环境温度自动调整风扇转速,温度高时转速升高,温度低时转速降低或停止。
  • 通过人体红外感应判断是否有人存在,无人在场时自动进入待机/停转状态。
  • 提供自动模式和手动模式,既能全自动运行,也能由用户手动调速。
  • 能够显示当前温度、工作模式、风速档位等信息,方便演示和答辩。

从“绿色”角度看,核心目标不是“风扇本身有多节能”,而是通过合理控制策略,避免风扇长期满速空转,从而节省电能、延长电机寿命。

从“智能”角度看,系统至少需要两路输入:一路是环境温度,一路是人体存在信号。单片机作为控制核心,根据这两路输入决定执行器(直流电机/风扇模块)的输出状态。如果再增加光照检测、定时、无线控制,就属于进阶扩展。

1.2 为什么选择 STM32 作为主控

这套设计选择 STM32 而不是传统的 51 单片机,主要考虑以下几点:

  • STM32 主频高、外设丰富,除了普通 GPIO,还内置多路定时器、ADC、I2C、USART 等外设,扩展能力强。
  • 项目演示时,STM32 能够直接驱动 OLED、读取传感器数据、产生 PWM 波形,整体可展示性强。
  • 毕业生在黑板上/论文中能写的内容更多,比如中断优先级、定时器配置、PWM 频率计算、ADC 采样,这些都是答辩时可以深入展开的知识点。
  • STM32 开发资料非常成熟,网上可以找到大量参考工程,遇到问题容易排查。

当然,如果项目要求比较简单,用 STM32F103C8T6 最小系统板作为核心即可,成本较低、资料多、引脚足够。

1.3 系统整体框图

在正式开发前,先画出系统框图。典型的智能绿色风扇系统结构如下:

[温度传感器 DS18B20 / DHT11] ---> STM32 GPIO 读取 [人体红外模块 HC-SR501] ---> STM32 GPIO 电平检测 [按键(模式切换/手动调速)] ---> STM32 GPIO 输入 [OLED 显示屏] ---> STM32 I2C 接口 [直流电机/风扇模块] ---> STM32 定时器 PWM 输出 \\--- 电机驱动电路

系统的信息流是:

  1. STM32 周期性读取温度传感器数据。
  2. STM32 检测人体红外模块输出电平。
  3. 根据当前模式(自动/手动)计算目标风速。
  4. 通过 PWM 占空比调节电机转速。
  5. OLED 显示温度值、模式和档位信息。
  6. 按键可切换模式、手动调节档位。

2. 硬件选型与开发环境准备

2.1 主控芯片与开发板

推荐使用 STM32F103C8T6 最小系统板,也就是常说的“STM32 蓝板”或“核心板”。它成本低、引脚引出方便,非常适合毕设实物焊接和面包板搭建。

关键技术参数:

  • 内核:ARM Cortex-M3
  • 主频:最高 72 MHz
  • Flash:64 KB
  • SRAM:20 KB
  • 定时器:多个,包含高级定时器 TIM1 和通用定时器 TIM2/TIM3/TIM4
  • ADC:12 位逐次逼近型 ADC,多通道
  • 通信接口:I2C、SPI、USART、CAN 等

对于风扇控制项目,Flash 和 SRAM 完全够用。

2.2 电机与驱动模块

智能风扇项目通常有两种方式:

  • 小功率直流风机:直接使用 5V 直流风扇模块,配合三极管/MOS 管或者 L298N/ULN2003 驱动。这种方式适合桌面小风扇演示。
  • 大功率交流风扇:需要继电器或可控硅调压,涉及 220V 强电,安全性要求高,一般不推荐毕设实物随意操作。

如果没有特殊说明,建议选择 5V 直流电机或电脑散热风扇模块,由 USB/充电宝/电源适配器供电即可。驱动方式可以使用 NPN 三极管(如 S8050)+ 续流二极管,或者使用 MOSFET(如 AO3400),或者使用现成的 DRV8833 / L298N 模块。从学习角度出发,使用三极管或 MOS 管自己搭一个 PWM 驱动电路更有技术含量,但直接使用 L298N 模块更稳定。

在 PWM 驱动电机时,要注意频率不能太低,否则电机会发出明显的噪声;也不能太高,否则开关损耗增加。常见习惯选择 10kHz~20kHz 之间的 PWM 频率。

2.3 温度传感器选型

智能风扇需要感知环境温度。常见选择有两种:

  • DS18B20:单总线数字温度传感器,测量范围 -55℃~+125℃,精度 ±0.5℃(-10℃~+85℃),可通过 GPIO 口直接读取,无需 ADC。
  • DHT11/DHT22:温度和湿度传感器,DHT11 精度相对较低,适合要求不高的项目。

毕业设计建议优先使用 DS18B20。理由是:

  1. 单总线协议简单直观,能够展示底层时序编程能力。
  2. 数据是数字量,不需要校准。
  3. 演示时可以用手捂住传感器看到温度变化,再观察风扇转速变化,演示效果直观。

2.4 人体感应模块

常用模块是 HC-SR501 人体红外感应模块,基于热释电红外传感器(PIR)。

模块特性:

  • 工作电压 5V~20V,通常用 5V 供电。
  • 输出高电平信号,默认无入时输出低电平,检测到人体活动时输出高电平。
  • 可以通过模块上的两个电位器调节灵敏度和延时时间。

使用时要特别注意:

  • HC-SR501 在刚上电时有约 30s~60s 的预热/初始化时间,此时输出不稳定,容易被误触发。
  • 人体传感器检测的是人体移动,静止的人长时间待在原地可能会因红外变化不明显而恢复低电平。所以软件控制逻辑要设计合理,不能完全依赖它判断“房间是否始终有人”。

2.5 显示模块

推荐使用 0.96 寸 OLED 显示屏,I2C 接口,SSD1306 驱动芯片。OLED 显示清晰、占用引脚少、刷新简单,非常适合在毕设实物中展示当前状态。

如果不想用 OLED,也可以用 LCD1602 液晶屏。但 LCD1602 需要占用 6 个甚至更多 IO,并且显示内容有限。对于能显示中文和图形的需求,OLED 是更好的选择。如果题目明确要求更复杂的界面,可以考虑带中文字库的 LCD12864 或 TFT 彩屏。

2.6 按键与电源

按键用于切换工作模式和手动调速。常见做法是接 4 个独立按键:

  • 模式切换键(自动 / 手动)
  • 风速加
  • 风速减
  • 开关机/确认

按键电路采用“按键一端接 GPIO,另一端接 GND”,内部启用上拉电阻,或外部接 10kΩ 上拉电阻。按下时引脚读到低电平,实现低电平有效。

电源部分,如果所有模块都是 5V 供电,可以直接用 USB 5V 电源;如果个别模块需要 3.3V,则用 AMS1117-3.3 稳压芯片转换,或者使用开发板自带的 3.3V 输出。

2.7 开发软件与版本说明

本文示例使用以下开发环境:

  • Keil MDK 5(ARMCC 编译器),也可以使用 STM32CubeMX + Keil/GCC 组合,或者 VSCode + EIDE/PlatformIO,核心思路一致。
  • STM32 标准外设库(Standard Peripheral Library),对于 STM32F1 系列资料较多,示例代码易读性好。
  • STM32CubeMX 用于生成初始化代码时,可以选择 HAL 库版本,结构会更清晰。

版本方面,不同系列 Pack 会导致函数名略有差异。实际环境中的芯片型号、器件库版本可能不同,所以示范代码需要按自己的工程情况调整。最关键的是理解配置原理,而不是死记某个函数名。

3. 核心模块原理与配置方法

3.1 定时器 PWM 输出原理

PWM(Pulse Width Modulation,脉宽调制)通过调节高电平在一个周期内的占比,也就是占空比,来改变输出到电机的平均电压。

STM32 通用定时器输出 PWM 的核心逻辑是:

  • 定时器计数器 CNT 从 0 增加到自动重装载值 ARR。
  • 比较寄存器 CCR 存储一个比较值。
  • 当 CNT 小于 CCR 时输出有效电平,当 CNT 大于等于 CCR 时输出另一个电平。
  • 改变 CCR 即可改变占空比。

例如 ARR = 999 时,PWM 周期为 1000 个计数单位。若 CCR = 250,占空比为 25%,CCR = 500 时占空比为 50%。

PWM 频率计算公式:

PWM频率 = 定时器时钟频率 / ((ARR + 1) * (PSC + 1))

例如 STM32F103 内部定时器挂在 APB1 上,APB1 经过倍频后通常为 72MHz。如果 PSC = 71,ARR = 99,则:

PWM频率 = 72MHz / ((99 + 1) * (71 + 1)) = 72000000 / 7200 = 10000 Hz

也就是说,配置定时器分频系数为 71,自动重装载值为 99,即可得到 10kHz 的 PWM 波。

3.2 温度传感器 DS18B20 读取流程

DS18B20 使用单总线协议。一次完整温度转换需要以下步骤:

  1. 发送复位脉冲:主机将总线拉低至少 480μs,然后释放并等待存在脉冲。
  2. 检测存在脉冲:DS18B20 收到复位脉冲后,会拉低总线 60μs~240μs。
  3. 发送 ROM 命令:如果总线上只有一个传感器,可以发送跳过 ROM 命令 0xCC。
  4. 发送功能命令:启动温度转换命令 0x44。
  5. 等待转换完成:通常需要 750ms(12 位分辨率)。
  6. 再次复位并跳过 ROM,发送读取暂存器命令 0xBE。
  7. 读取低字节和高字节,合成为 16 位温度数据。

温度值计算方法:

  • 符号位为 0 时,温度为正值。
  • 温度 =(高字节 << 8 | 低字节)>> 4,精确到 0.0625℃。
  • 也可以直接乘 0.0625 得到实际小数。

在编写底层代码时,最重要的是严格控制延时。DS18B20 对时序要求比较高,如果延时不准,读取会失败。可以用 STM32 的微秒级延时函数实现。

3.3 人体红外检测逻辑

HC-SR501 输出的是数字电平信号,单片机只需要读 GPIO 电平即可。但需要注意逻辑设计,不能简单认为“检测到高电平就一直开着风扇”。

推荐的控制逻辑是:

  • 若人体传感器输出高电平,表示当前检测到有人活动,系统进入工作状态。
  • 若人体传感器输出低电平,不代表一定没人,因为模块有延时设置,通常默认延时约 2s~3s。
  • 为避免风扇因传感器短暂失效而频繁启停,软件上应加入“关断延时”。例如检测到无人后,继续运行 30s 或 60s 再停止,或者当温度较低且无人才逐渐关闭。

3.4 系统状态机设计

为了让程序结构清晰,推荐使用简单的状态机。

状态说明: [0] 关机/待机状态:不输出 PWM,OLED 显示待机。 [1] 自动模式运行状态:温度决定风速,无检测到人则等待关闭。 [2] 手动模式运行状态:用户通过按键加减风速。

主循环每次扫描按键、传感器,然后根据状态机决定输出。这种方式比让空 while 循环里的代码堆在一起更清晰,也方便答辩时讲“系统采用状态机编程”。

4. 完整代码实战

下面以一个简化但功能完整的工程为例展开代码。重点展示:

  • 初始化流程
  • DS18B20 底层读取
  • PWM 调速
  • 主循环状态控制
  • OLED 显示逻辑

为了便于阅读,以下代码使用“标准外设库”思路,不过为了工程通用性,也会注意函数命名友好。

4.1 工程结构建议

建议按照以下结构组织文件:

SmartFan/ ├── User/ │ ├── main.c │ ├── stm32f10x_it.c ├── Hardware/ │ ├── ds18b20.c │ ├── ds18b20.h │ ├── pwm.c │ ├── pwm.h │ ├── key.c │ ├── key.h │ ├── oled.c │ ├── oled.h │ ├── infrared.c │ └── infrared.h ├── System/ │ └── STM32 启动文件和库文件 └── Project/ └── MDK-ARM 工程文件

这样功能拆分明确,不同模块之间的耦合度低,后续将 OLED 换成 LCD 或增加蓝牙模块,只需要新增对应硬件文件即可。

4.2 主函数框架

// 文件路径:User/main.c #include "stm32f10x.h" #include "delay.h" #include "ds18b20.h" #include "pwm.h" #include "key.h" #include "oled.h" #include "infrared.h" typedef enum { SYS_POWER_OFF = 0, SYS_AUTO_MODE, SYS_MANUAL_MODE } SysMode; volatile SysMode sysMode = SYS_AUTO_MODE; volatile uint8_t currentLevel = 1; // 当前风速档 0~4 volatile uint8_t manualLevel = 2; // 手动档位 volatile uint8_t personDetect = 0; // 是否检测到人 volatile float temperature = 25.0f; // 当前温度 static void System_Init(void); static void Task_KeyScan(void); static void Task_TemperatureRead(void); static void Task_MotorControl(void); static void Task_Display(void); int main(void) { System_Init(); while (1) { Task_KeyScan(); Task_TemperatureRead(); Task_MotorControl(); Task_Display(); delay_ms(100); } } static void System_Init(void) { delay_init(); OLED_Init(); DS18B20_Init(); PWM_Init(); KEY_Init(); Infrared_Init(); OLED_Clear(); OLED_ShowString(0, 0, "Smart Fan System"); }

主循环采用轮询方式,每 100ms 执行一次。对于这个项目来说,轮询足够满足实时性要求。如果后续要加入无线模块、语音识别等耗时任务,再考虑使用定时器中断调度或 FreeRTOS。

4.3 DS18B20 驱动代码

下面是 DS18B20 初始化与读取温度的核心代码。

// 文件路径:Hardware/ds18b20.c #include "ds18b20.h" #include "delay.h" // GPIO 定义,这里以 PA0 为例 #define DS18B20_PORT GPIOA #define DS18B20_PIN GPIO_Pin_0 #define DS18B20_RCC RCC_APB2Periph_GPIOA static void DS18B20_Mode_Input(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DS18B20_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DS18B20_PORT, &GPIO_InitStructure); } static void DS18B20_Mode_Output(void) { GPIO_InitTypeDef GPIO_InitStructure; 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); } static void DS18B20_Reset(void) { DS18B20_Mode_Output(); GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN); delay_us(500); GPIO_SetBits(DS18B20_PORT, DS18B20_PIN); delay_us(60); DS18B20_Mode_Input(); } static uint8_t DS18B20_CheckPresence(void) { uint8_t presence = 1; // Reset 后读取存在脉冲 if (GPIO_ReadInputDataBit(DS18B20_PORT, DS18B20_PIN) == 0) { presence = 0; // 拉低说明存在 } delay_us(200); return presence; } static void DS18B20_WriteByte(uint8_t data) { uint8_t i; for (i = 0; i < 8; i++) { DS18B20_Mode_Output(); GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN); delay_us(2); if (data & 0x01) { GPIO_SetBits(DS18B20_PORT, DS18B20_PIN); } else { GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN); } delay_us(60); GPIO_SetBits(DS18B20_PORT, DS18B20_PIN); data >>= 1; } } static uint8_t DS18B20_ReadByte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { data >>= 1; DS18B20_Mode_Output(); GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN); delay_us(2); GPIO_SetBits(DS18B20_PORT, DS18B20_PIN); DS18B20_Mode_Input(); if (GPIO_ReadInputDataBit(DS18B20_PORT, DS18B20_PIN)) { data |= 0x80; } delay_us(60); } return data; }

然后封装一个获取温度的函数:

float DS18B20_GetTemperature(void) { uint8_t tempL = 0, tempH = 0; int16_t raw = 0; float temperature = 0.0f; DS18B20_Reset(); DS18B20_CheckPresence(); DS18B20_WriteByte(0xCC); // 跳过 ROM DS18B20_WriteByte(0x44); // 启动温度转换 delay_ms(750); // 等待转换完成 DS18B20_Reset(); DS18B20_CheckPresence(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读取暂存器 tempL = DS18B20_ReadByte(); tempH = DS18B20_ReadByte(); raw = (int16_t)((tempH << 8) | tempL); temperature = raw * 0.0625f; return temperature; }

DS18B20 比较耗时的一点是温度转换等待时间较长。如果每 100ms 主循环都调用一次 DS18B20_GetTemperature,会卡住系统。因此更合理的做法是:

  • 定义转换状态,每 750ms 才发起一次完整读取。
  • 温度读取放在后台或低频任务中,主控制逻辑不依赖每次实时温度。

4.4 PWM 电机驱动代码

以 TIM2 的 CH1(PA0 在某些板子上作为 TIM2_CH1,但如果 PA0 被 DS18B20 占用,需要调整引脚,这里只作演示逻辑)为例。实际使用时,DS18B20 和数据手册引脚需要根据你的核心板分配,一般 PA0 是 TIM2_CH1 和 ADC 等多功能引脚,需要认真查看原理图。

如果使用 PA1 作为 TIM2_CH2,配置代码如下:

// 文件路径:Hardware/pwm.c #include "pwm.h" #include "stm32f10x.h" void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // TIM2_CH2 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler = 71; // 72MHz / 72 = 1MHz TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 999; // PWM 频率 1kHz TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseInit(TIM2, &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_OC2Init(TIM2, &TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE); } void PWM_SetDuty(uint8_t duty_percent) { uint16_t ccr = 0; if (duty_percent > 100) { duty_percent = 100; } ccr = (uint16_t)(duty_percent * 10); // ARR=999,占空比调整,最大不超过999 TIM_SetCompare2(TIM2, ccr); }

注意:这个配置里 PWM 频率是 1kHz,适合小风扇;如果想要 10kHz,TIM_Prescaler 不变,把 TIM_Period 改为 99,那么占空比调整公式也需要变。实际频率选择请根据电机噪声与驱动效果调试。

4.5 自动控制策略代码

下面是最关键的“智能控制逻辑”。自动模式下可以设定一个简单的温度区间:

  • 温度低于 20℃:风扇停转。
  • 温度在 20℃~25℃:风扇低速(30% PWM)。
  • 温度在 25℃~30℃:风扇中速(60% PWM)。
  • 温度高于 30℃:风扇高速(100% PWM)。
  • 如果无人检测信号超过设定时间,则不管温度多高都逐渐停止,体现节能。

具体代码如下:

static void Task_MotorControl(void) { uint8_t targetDuty = 0; if (sysMode == SYS_POWER_OFF) { PWM_SetDuty(0); return; } // 检测人体传感器电平 personDetect = Infrared_ReadLevel(); if (sysMode == SYS_AUTO_MODE) { if (personDetect == 0) { // 无人状态,为节电可降低风速;如果使用延时关机逻辑,需要在这里维护时长变量 PWM_SetDuty(0); currentLevel = 0; } else { if (temperature < 20.0f) targetDuty = 0; else if (temperature < 25.0f) targetDuty = 30; else if (temperature < 30.0f) targetDuty = 60; else targetDuty = 100; if (targetDuty > 0) { currentLevel = (uint8_t)(targetDuty / 25); // 1~4挡备查 } PWM_SetDuty(targetDuty); } } else if (sysMode == SYS_MANUAL_MODE) { switch (manualLevel) { case 0: PWM_SetDuty(0); break; case 1: PWM_SetDuty(25); break; case 2: PWM_SetDuty(50); break; case 3: PWM_SetDuty(75); break; case 4: PWM_SetDuty(100); break; default: PWM_SetDuty(0); break; } } }

上面的 targetDuty 直接代表 PWM 占空比。实际项目还会增加“无人延时关闭计时”:检测到无人后并不立即停转,而是持续运行比如 30s 再停。要完成这个功能,需要在主循环中维护一个递减计数变量,代码结构如下:

若 personDetect 为 1: 清零 noPersonCounter; 正常运行自动模式逻辑; 若 personDetect 为 0: noPersonCounter++; 若 noPersonCounter > 300(也就是30秒): 停转;否则维持当前转速;

4.6 OLED 显示逻辑

OLED 显示逻辑一般比较简单,调用字符显示接口即可。以 SSD1306 的 0.96 英寸 OLED 为例,常用 API 包括:

OLED_Clear(); OLED_ShowString(uint8_t x, uint8_t y, char *str); OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);

显示主界面时,第一行显示当前模式,第二行显示温度,第三行显示档位/转速百分比,第四行显示人体检测状态。

static void Task_Display(void) { char buf[32]; OLED_Clear(); OLED_ShowString(0, 0, "Mode:"); if (sysMode == SYS_AUTO_MODE) { OLED_ShowString(40, 0, "Auto "); } else if (sysMode == SYS_MANUAL_MODE) { OLED_ShowString(40, 0, "Manual"); } else { OLED_ShowString(40, 0, "Off "); } // 温度显示,单位 ℃ OLED_ShowString(0, 2, "Temp:"); OLED_ShowNum(36, 2, (uint32_t)temperature, 2); OLED_ShowString(56, 2, "."); uint16_t tempDec = (uint16_t)((temperature - (int)temperature) * 10); OLED_ShowNum(64, 2, tempDec, 1); OLED_ShowString(76, 2, "C "); OLED_ShowString(0, 4, "Level:"); if (currentLevel == 0) { OLED_ShowString(48, 4, "Stop "); } else { OLED_ShowNum(48, 4, currentLevel, 1); OLED_ShowString(56, 4, "/4 "); } OLED_ShowString(0, 6, "Human:"); if (personDetect) { OLED_ShowString(48, 6, "YES "); } else { OLED_ShowString(48, 6, "NO "); } OLED_Refresh(); }

OLED 刷新频率不能太高,否则会造成闪烁和占用 CPU。建议 1~5Hz 刷新即可,例如每 200ms 刷新一次。

4.7 按键处理

按键处理要处理机械抖动。一个简单的扫描函数如下:

// 返回按键值:1 模式切换,2 档位加,3 档位减,4 开关机 uint8_t Key_Scan(void) { static uint8_t keyState = 0; uint8_t keyValue = 0; if (KEY_MODE_PIN == 0 || KEY_PLUS_PIN == 0 || KEY_MINUS_PIN == 0 || KEY_POWER_PIN == 0) { delay_ms(10); if (KEY_MODE_PIN == 0) { keyValue = 1; } else if (KEY_PLUS_PIN == 0) { keyValue = 2; } else if (KEY_MINUS_PIN == 0) { keyValue = 3; } else if (KEY_POWER_PIN == 0) { keyValue = 4; } } // 等待按键释放,避免一次长按多次触发的副作用 while (KEY_MODE_PIN == 0 || KEY_PLUS_PIN == 0 || KEY_MINUS_PIN == 0 || KEY_POWER_PIN == 0) { delay_ms(10); } return keyValue; }

注意:如果在主循环中直接写死等待按键释放的 while,那么长按会阻塞整个主循环,有可能会卡住传感器读取或 PWM 控制。对于简单演示项目影响不大,但如果是工程开发,建议使用“检测下降沿 + 防抖标志再触发一次”的方式,或把按键扫描放入定时器中断。

在主函数中按键动作对应的处理:

static void Task_KeyScan(void) { uint8_t key = Key_Scan(); if (key == 1) // 模式切换:自动/手动 { if (sysMode == SYS_AUTO_MODE) { sysMode = SYS_MANUAL_MODE; } else if (sysMode == SYS_MANUAL_MODE) { sysMode = SYS_AUTO_MODE; } } else if (key == 2) // 档位加 { if (manualLevel < 4) { manualLevel++; } } else if (key == 3) // 档位减 { if (manualLevel > 0) { manualLevel--; } } else if (key == 4) // 开关机,进入待机 { sysMode = SYS_POWER_OFF; PWM_SetDuty(0); } }

5. 系统联调与演示验证

5.1 基本功能验证

完成代码后,建议按以下顺序进行硬件联调。

第一步:单独验证最小系统

只连接 STM32 最小系统板和下载器,确认程序能下载并且 LED 闪烁程序运行正常。

第二步:验证 OLED 显示

连接 OLED,下载 OLED 测试程序,观察能否正常显示字符。常见问题是 I2C 地址不对、接线反了、模块供电不足。

第三步:验证 DS18B20 温度读取

单独调试 DS18B20 驱动,用串口或 OLED 输出当前温度。可以用手捏住传感器看温度是否上升,或者用冰水测试降温。

第四步:验证 PWM 输出与电机转动

将 PWM 代码占空比固定为 50%,用示波器或万用表测量输出波形,再连接电机驱动电路,观察风扇转速是否正常。

第五步:验证人体感应模块

在代码中临时写一个读 GPIO 的逻辑,把人体感应输出接上,人走过时观察电平变化。如果一直有输出或一直无输出,要调节 HC-SR501 上的两个电位器。

全部模块单独正常工作后,再整合完整程序。

5.2 演示建议

毕业设计现场演示时,建议提前准备几个有说服力的场景:

  • 用手握 DS18B20,风扇转速会提高。这个过程只需要几秒钟,体感明显。
  • 让手掌在 HC-SR501 前晃动,风扇会立刻启动,然后人离开后几十秒自动停止。
  • 按键切换到手动模式,手动加减档位时 OLED 上档位和风速同步变化。
  • 将 OLED 放到显著位置,让评委能直观看到温度、模式等数据。

如果答辩现场环境温度已经高于 30℃,可以让温度一直处于高速状态,演示“降温到阈值后自动恢复低速”可能比较困难。这时可以在代码临时把阈值改高,例如 35℃,方便现场演示。

6. 常见问题与排查思路

在 STM32 开发过程中,最容易出问题的地方通常是硬件接线和底层延时,下面列举高频问题。

6.1 程序可以下载,但 OLED 不显示

可能原因:

  • OLED 的 SCL/SDA 引脚接错。
  • I2C 地址不对。
  • 核心板供电不足,OLED 屏幕异常。
  • 焊接虚焊或杜邦线接触不良。

解决措施:

  • 使用 I2C 扫描程序扫描地址。
  • 检查核心板原理图确认复用引脚。
  • 替换一根杜邦线或直接焊接。

6.2 DS18B20 读取温度固定为 85℃ 或 0℃

DS18B20 上电后的默认温度为 85℃,如果读到 85℃ 且不变化,大概率数据转换没成功或程序读到了“上电默认值”。

如果温度持续显示 85℃,重点检查:

  • GPIO 引脚模式是否配置为开漏/上拉输入。
  • 单总线是否需要外部 4.7kΩ 上拉电阻。
  • 复位脉冲和时序的延时是否准确。

DS18B20 数据线通常需要接一个 4.7kΩ 上拉电阻到 VCC,虽然部分开发板已经内置,但使用杜邦线连接时建议自行加上。

6.3 风扇转速无法调节,只高速或只停

可能原因:

  • PWM 频率太低或太高,导致驱动异常。
  • 电机驱动部分没有真正接 PWM 信号,而是接了普通高电平。
  • Timer 通道复用冲突,GPIO 没有配置为复用推挽输出。

解决办法:

  • 用示波器或简单用 LED 亮度观察 PWM 输出是否变化。
  • 在代码中先写死不同的占空比,验证是否逐级变化。
  • 检查是否用了正确的定时器通道函数,例如 TIM2_CH2 要调用 TIM_SetCompare2。

6.4 HC-SR501 误触发或一直触发

HC-SR501 模块上有两个电位器,分别调节灵敏度和延时。上电前需要先等待 30~60 秒预热。如果输出一直为高,可以逆时针减小灵敏度。如果输出常低,可以顺时针调高灵敏度。

6.5 系统频繁复位或 OLED 花屏

常见原因是电源纹波过大或者电机启动瞬间电流拉低电压。解决方式:

  • 电机电源与控制逻辑电源分开,电机使用单独电源。
  • 在电机电源两端并联 100μF 电解电容和 104 陶瓷电容。
  • 电机是感性负载时,续流二极管必须接好,否则驱动管容易被击穿。
问题现象常见原因解决思路
OLED 无显示接线错误/地址不对检查 I2C 地址与接线
温度不变 85℃DS18B20 上拉电阻或时序错误检查上拉电阻与延时函数
风扇转速不均PWM 频率不匹配调整定时器 PSC/ARR
电机启动导致复位供电不足或被拉低分离电源并加去耦电容
人体传感器乱触发模块调试参数不合适调节灵敏度和延时旋钮

7. 最佳实践与工程化建议

7.1 代码规范与可维护性

毕业设计如果是多人配合或需要后续扩展,建议代码上注意分层:

  • 驱动层:只负责硬件寄存器操作和底层协议,例如 DS18B20 驱动只提供 GetTemperature。
  • 逻辑层:只处理模式切换、档位计算、温度阈值判断。
  • 应用层:负责把结果显示到 OLED 或响应按键。

这样做的最大好处是,以后把 DS18B20 换成 DHT11,或者把 OLED 换成 LCD,只需要重写驱动层,核心控制逻辑不用大改。

7.2 异常处理与边界条件

不要假设传感器永远正常。例如 DS18B20 拔掉时,读取温度可能返回极值,容易导致风扇误动作。建议在代码里增加温度合法范围判断:

if ((temperature < -20.0f) || (temperature > 80.0f)) { // 传感器异常,进入安全模式:停止风扇并提示报警 PWM_SetDuty(0); return; }

如果人体传感器故障,一直输出低电平,建议设计“故障超时”或“手动模式兜底”,避免整个系统无法使用。

7.3 低功耗与节能策略

绿色智能风扇的“绿色”体现在:

  • 使用 PWM,而不是电阻降压调速,能显著降低损耗。
  • 无人自动延时停机。
  • 温度不高的场景,尽量让风扇低速运转而不是开关频繁切换。
  • 睡眠模式下,OLED 可以关闭背光或降低刷新率。

如果项目要求更严格的功耗指标,可以让 STM32 进入 STOP 或 STANDBY 模式,通过 RTC 定时唤醒或人体传感器外部中断唤醒。当然这会增加代码复杂度,答辩时可以作为“改进方向”来写。

7.4 硬件安全提醒

如果最终方案使用 220V 交流风扇,务必注意以下几点:

  • 强电与弱电必须隔离。
  • 控制板不要裸露接触高压。
  • 继电器或可控硅需要满足额定电流。
  • 实际操作和演示时必须有指导教师在场,注意人身安全。

如果只是毕设展示,通常建议以 5V 直流风扇作为执行机构,安全、方便、演示效果一样良好。

7.5 扩展方向

如果一个基础自动控制风扇不足以体现工作量,可以从以下方向扩展:

  • 增加手机 App 控制,通过 ESP8266/蓝牙模块与 STM32 通信,实现远程开关和调档。
  • 增加 WIFI 温湿度云上报,通过 ESP8266 + MQTT 上传数据到云平台。
  • 增加语音控制,用 LD3320 或离线语音模块识别“打开风扇”“提高风速”等指令。
  • 增加太阳能充电供电模块,进一步体现“绿色”主题。
  • 增加学习记忆功能,记录用户的使用习惯,自动在常用时段提前调速。

扩展时需要注意 STM32F103C8T6 的引脚资源,如果外设过多,可以把显示部分换成串口屏,或者升级到 STM32F407 等更多引脚/更高性能的型号。

8. 总结与后续学习建议

本文以智能绿色风扇为例,完整梳理了一个基于 STM32 的自动化控制系统从需求分析到硬件设计、代码实现、联调排错的闭环流程。核心知识点包括:

  • 温度传感器 DS18B20 的单总线读取方法。
  • STM32 定时器输出 PWM 的原理与配置。
  • 人体红外传感器在节能控制中的使用方式。
  • 自动/手动模式切换的状态机编程思路。
  • OLED 信息显示与按键交互设计。

如果你正在准备单片机毕业设计,下一步建议先不要急着写大量代码,而是把上面第 2 节中的硬件连接图画清楚,然后按照 5.1 的顺序逐模块验证。每完成一个模块,就拍照留档,这些都是论文和答辩 PPT 中可以使用的素材。

在此基础上,如果想让方案更有竞争力,可以继续研究 FreeRTOS 在 STM32 上的移植,把温度采集、按键扫描、屏幕刷新放到不同任务里,这会让代码结构上一个台阶。也可以结合 WIFI 模块做 App 联动,增加项目的现代感。学习时建议多阅读官方参考手册和数据手册,尤其是定时器与 GPIO 章节,这是理解所有外设的基础。对代码中的每一处延时、每一个寄存器配置,都尝试回答“如果删除这行会怎样”,能够帮助你快速建立系统级认识。实物调试时不要怕出问题,把每个现象记录下来,多试几组参数,你会逐渐积累出一套属于自己的排查方法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 21:40:50

ORB_SLAM2: Tracking::Track()

Track是ORB-SLAM2跟踪线程的主循环入口函数 &#xff0c;它负责处理每一帧输入图像&#xff0c;完成从状态管理、位姿估计、局部地图优化到关键帧决策的全链路流程。下面详细介绍该函数&#xff1a; Track() 入口│├─> [状态初始化] NO_IMAGES_YET -> NOT_INITIALIZED…

作者头像 李华
网站建设 2026/9/4 21:39:52

423道 GIT 测试题(含解释) 01 - 20 题

为方便阅读,这里整理了整个系列的索引导航。本系列共 423 道 git 测试题(含简单的题目解释),按每 20 题为一篇进行连载,点击下方链接即可跳转到对应章节,方便你按需查阅、系统复习。 423道 GIT 测试题(含解释) 01 - 20 题 423道 GIT 测试题(含解释) 21 - 40 题 423道…

作者头像 李华
网站建设 2026/9/4 21:39:04

手把手:论文的干预方案手册怎么分步编制

论文做了干预实验&#xff0c;评审时却拿不出可复现的方案说明——不少同学都卡在这里&#xff1a;方法部分只有"对实验组实施了为期 8 周的干预"一两句带过&#xff0c;执行者根本不知道具体该做什么。本文把干预方案手册的编制拆成 6 个可执行步骤&#xff0c;每步…

作者头像 李华
网站建设 2026/9/4 21:38:00

基于BLIP模型实现本地化AI图片描述生成:从环境搭建到生产部署全流程

在实际开发中&#xff0c;我们经常遇到需要为图片、视频或特定场景自动生成描述性文字的需求。无论是内容平台的智能配文、电商产品的自动标题生成&#xff0c;还是辅助工具的无障碍描述&#xff0c;将AI能力集成到文字生成流程中&#xff0c;已经成为提升效率和用户体验的关键…

作者头像 李华
网站建设 2026/9/4 21:34:58

JSP+Servlet项目实战复盘:从经典MVC到安全加固的演进之路

简介&#xff1a;这是一套基于Java Web技术栈开发的志愿者服务管理平台源码&#xff0c;面向高校课程设计、毕业设计及中小型公益组织信息化建设需求&#xff0c;解决志愿者招募、培训、项目跟踪、服务时长统计与表彰激励等全流程数字化管理问题。资源包共953个文件&#xff0c…

作者头像 李华
网站建设 2026/9/4 21:34:32

CrewAI多智能体开发实战:核心概念、任务编排与工作流构建

在实际的 AI 应用开发中&#xff0c;多智能体系统已经不是概念阶段的东西&#xff0c;而是一类可落地的工程范式。CrewAI 是其中使用成本较低、理解门槛也较友好的 Python 框架&#xff1a;开发者可以把一个复杂目标拆成多个带身份的 AI 智能体&#xff0c;每个智能体负责一个明…

作者头像 李华