尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32的智能绿色风扇设计:从PWM调速到闭环控制

基于STM32的智能绿色风扇设计:从PWM调速到闭环控制 在很多单片机毕业设计题目里基于STM32的智能绿色风扇是一个很适合作为综合项目的方向。它看起来像是一个小家电改进方案实际上会用到 GPIO、定时器、PWM、ADC、外部中断、按键消抖、状态机、串口调试和低功耗设计。这类项目最大的价值不在于“风扇会转”而在于把上面的知识点串成一个能稳定运行、能现场演示、能写进论文的完整系统。这篇文章适合正在做单片机课程设计、毕业设计或技术竞赛的人阅读。你可以用同样的思路完成一个“温度采集 自动调速 手动控制 状态显示”的智能风扇控制系统并在此基础上继续加 WiFi、云平台、手机 App 等扩展功能。1. 为什么把“绿色”设计成风扇的目标之一1.1 普通风扇最大的浪费是按固定转速运行普通风扇通常只有几个固定挡位。用户手动选一个挡位后风扇就会一直以这个转速运行不管房间温度是升高还是降低。这样的方式在原理上简单但存在两个明显问题一是温度较低时仍然高速运行浪费电能二是噪声和风感并不随着人的需求变化长时间使用体验不好。在毕业设计里加入“绿色”二字并不是要求作品使用太阳能或储能电池而是要把节能设计落到控制逻辑上。风扇可以根据环境温度自动决定转速温度低时停止或低速运行温度高时逐渐加速。这样既减少了不必要的用电也降低了运行噪声整套闭环会更有实际意义。1.2 智能与绿色如何在同一个控制器里统一实现这项设计需要两类输入和一类输出输入 1当前环境温度输入 2用户选择的控制模式输出风扇电机的 PWM 占空比。控制的核心逻辑是“量入为出”。单片机周期读取温度根据预设的温度区间计算目标转速再通过 PWM 调节电机功率。用户也可以切换到手动模式用按键直接选择挡位。整个过程没有复杂算法却能把传感器、执行器、人机交互合在一起这正是单片机综合项目最常见的工程形态。1.3 项目主线从 PWM 到闭环控制很多初学者会把人机交互或 OLED 显示当作核心内容实际上这个项目的技术主线应当是用定时器产生频率稳定的 PWM 信号用 PWM 占空比控制电机功率用 ADC 或数字温度传感器得到环境温度用状态机处理自动模式和手动模式的切换用低功耗和周期间隙让系统更“绿色”。后文会按照这条主线展开。先确定硬件方案再配置工程接着写控制逻辑最后做测试和排错。2. 系统方案与硬件准备2.1 文字版系统框图和工作流程先不画复杂框图用文字描述一个最小系统的工作流程温度传感器将当前环境温度转换成电压或数字信号STM32 周期性采集温度系统判断当前处于自动模式还是手动模式自动模式下根据温度查表或线性计算目标占空比手动模式下目标占空比来自按键设置控制函数以缓慢步进方式靠近目标占空比PWM 输出到电机驱动模块最终带动风扇转动OLED 显示当前温度、模式、风速百分比和关键状态。这套流程可以在一开始被简化成“固定温度驱动风扇转动”再逐步加入自动控制。按流程做的好处是每一层都能独立验证。2.2 元器件选型与参考引脚接线课程设计常用主控是 STM32F103C8T6 最小系统板这一类芯片资料多、价格常见、CubeMX 支持完善。风扇选择 5V 直流小风扇即可如果使用 12V 风扇需要额外提供 12V 电源和合适的驱动模块。下面的引脚表是一组参考分配。实际项目中要根据自己的开发板、驱动模块和引脚占用情况微调不能直接照抄。外设MCU 引脚模块接口说明PWM 输出PA6TIM3_CH1L9110S 的 IA控制风扇转速方向控制PA7GPIO 输出L9110S 的 IB本设计固定转可输出低电平温度采集PA1ADC1_IN1LM35 的 VoutADC 读取电压OLED SCLPB6OLED 的 SCL使用软件 I2C 时配置为 GPIO 输出OLED SDAPB7OLED 的 SDA使用软件 I2C 时配置为 GPIO 输出模式按键PA0按键到 GND自动 / 手动切换调挡按键PA2按键到 GND手动模式上调风速串口发送PA9USART1_TXUSB 转 TTL 的 RXD调试日志串口接收PA10USART1_RXUSB 转 TTL 的 TXD可选遥控指令需要注意PA0 在很多板子上同时是 WKUP 引脚。如果把它作为普通按键输入需要上拉电阻并且按键按下后输出低电平。使用外部上拉时按键的另一端接 GND避免悬空导致误触发。按键开关最好配置为输入上拉。初始化时 GPIO 默认是高电平按下按键后变成低电平。读取到低电平后做 20ms 到 50ms 延时消抖再判断一次防止机械抖动产生多次触发。2.3 电机驱动与 PWM 频率的关系不要用 STM32 的 GPIO 直接驱动电机。STM32 引脚输出电流有限电机起动电流又大直接连接可能造成复位甚至芯片损坏。常见做法是使用 L9110S 这类电机驱动模块。L9110S 模块有两个输入引脚 IA 和 IB把 IA 接 PWMIB 固定为低电平即可控制一个方向转动。PWM 占空比越大电机平均电压越大转速越快。PWM 频率不能太低。如果选择 1kHz 以下的 PWM电机转动时会有明显抖动甚至出现可听的“吱吱”声。常见直流电机驱动会选择 10kHz 到 25kHz 的 PWM。下面示例工程以 20kHz 为目标频率这个频率在 STM32 定时器上很容易配置也可以在听感上避开大多数人的低频敏区间。2.4 电源与复位安全注意事项电机属于功率器件MCU 属于弱电器件两者不能过于简单共用一个供电如果使用 5V USB 供电的小风扇理论上可以和开发板共用 5V但必须共地如果使用 12V 风扇或电流较大的电机建议单独使用 12V 适配器电机电源与 MCU 电源分开MCU 端电源可以由 12V 经过 DC-DC 降压到 5V再经过 LDO 得到 3.3V也可以用独立 USB 供电驱动模块的输入信号地必须和 STM32 的地连接否则 PWM 信号没有参考地电机无法正常控制。注意任何时刻都不要在通电状态下插拔电机线和传感器线。学习板上没有完整的防护设计热插拔容易造成电压尖峰严重时可能烧坏 ADC 引脚或驱动芯片。3. 开发环境准备与工程初始化3.1 软件工具清单工具用途说明STM32CubeMX生成初始化工程用来配置时钟、GPIO、ADC、USART、TIMKeil MDK 或 STM32CubeIDE编译下载两者任选C 代码可以复用STM32 HAL 固件包提供驱动库安装与主控型号匹配的固件包串口调试助手查看调试日志用来观察温度和占空比变化万用表测量电压和通断排查接线问题USB 示波器或逻辑分析仪观察 PWM 波形不是必须但排查信号很有效在新建工程前先确认开发板、下载器、板载仿真器各自是什么类型。ST-Link、J-Link、DAP-Link 的接线方式不同最容易出问题的不是代码而是 debug 配置和驱动没装好。3.2 STM32CubeMX 关键配置以 STM32F103C8T6 为例在 CubeMX 中按以下顺序配置RCCHSE 选择 Crystal/Ceramic ResonatorDebug选择 Serial Wire保留 SWD 下载能力时钟将 SYSCLK 配置为最大工作频率常见板子可到 72MHzPA1配置为 ADC1_IN1PA6配置为 TIM3_CH1PA7配置为 GPIO OutputPA0、PA2配置为 GPIO InputPA9、PA10配置为 USART1波特率 115200PB6、PB7配置为 GPIO Output用于 OLED 软件 I2CTIM3选择 PWM Generation CH1。ADC 配置中分辨率选择 12 位转换模式可以先使用单次转换。控制逻辑中需要多个样本做平均因此在代码里手动开始转换、等待转换完成、读值再停止转换。连续转换看起来效率更高但会让初学阶段的代码排查变得复杂。使用版本较新的 CubeMX 时生成的工程以.ioc为中心。不要直接改动 Makefile 或项目文件所有引脚配置修改都要回到.ioc文件重新生成否则很容易出现“代码改乱了但配置没同步”的问题。3.3 PWM 参数计算公式20kHz PWM 的计算思路如下假如系统时钟为 72MHzAPB1 定时器时钟也设置为 72MHzPSC 设置为 0则 PWM 频率为F_PWM 定时器时钟 / ((ARR 1) * (PSC 1))如果希望频率为 20kHz则ARR 1 72000000 / 20000 3600 ARR 3599因此 TIM3 配置为Prescaler0Counter Period3599Pulse初始值 0PWM 模式Mode 1之后调用 HAL 接口启动 PWMHAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);设置不同占空比时不直接写百分比而是把百分比换算成 CCR 比较值void fan_set_speed( TIM_HandleTypeDef *htim, uint32_t channel, uint8_t percent) { uint16_t period htim-Init.Period; uint32_t ccr; if (percent 100U) { percent 100U; } ccr (uint32_t)((uint32_t)percent * (period 1U) / 100U); __HAL_TIM_SET_COMPARE(htim, channel, ccr); }这里的percent表示 0 到 100 的百分比。ARR 等于 3599 时完整周期为 3600 个计数占空比 50% 对应 CCR 约等于 1800。3.4 先做一个最小测试确认 PWM 能输出不用一上来就写完整控制逻辑先做一个最小测试int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM3_Init(); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); fan_set_speed(htim3, TIM_CHANNEL_1, 20U); HAL_Delay(2000); fan_set_speed(htim3, TIM_CHANNEL_1, 80U); HAL_Delay(2000); while (1) { fan_set_speed(htim3, TIM_CHANNEL_1, 0U); } }这个测试的检查点有四个下载后程序是否正常进入运行状态风扇是否在 2 秒后明显加速电机是否有异常噪声用手触摸驱动芯片是否温度过高。如果风扇不转先不检查控制逻辑直接测量 PWM 引脚是否输出信号。此时最容易定位问题是驱动模块没工作还是 STM32 没输出 PWM。若没有示波器也可以用万用表测量占空比高于零时引脚的平均电压但逻辑分析仪比万用表更直观。4. 控制软件实现4.1 使用状态机组织系统当系统包含自动模式、手动模式、按键和 PWM 输出时建议用一组 struct 保存所有运行状态而不是在 main 函数里堆一堆全局变量。typedef enum { FAN_MODE_AUTO 0, FAN_MODE_MANUAL } FanMode_t; typedef struct { FanMode_t mode; uint8_t cur_percent; uint8_t target_percent; float temperature_c; uint32_t last_ctrl_tick; uint32_t last_display_tick; } Fan_State_t;cur_percent表示当前 PWM 输出比例target_percent表示根据模式计算出的目标值。实际控制时每秒只变化几个百分点避免风速突变。使用 struct 的好处是调试时便于观察也方便后续把系统封装成一个模块。代码中不需要每个函数都访问全局变量而是把状态结构体的指针传给相关函数。4.2 温度采集与简单滤波LM35 是一种输出电压与环境温度成线性关系的模拟温度传感器典型输出约为 10mV/℃。25℃ 时输出约 250mV这个电压低于 3.3V因此可以接到 STM32 的 ADC 引脚。读取一次 ADC 的示例uint16_t read_adc_value(void) { uint16_t adc 0; HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 50) HAL_OK) { adc HAL_ADC_GetValue(hadc1); } HAL_ADC_Stop(hadc1); return adc; }ADC 的 12 位转换结果范围是 0 到 4095。假设 ADC 参考电压为 3.3V则每个计数对应的电压为电压(mV) adc_value * 3300.0 / 4095.0 温度(℃) 电压(mV) / 10.0把多次读取取平均可以减少噪声float read_temperature_c(void) { uint32_t sum 0U; uint8_t i; uint16_t adc_value; for (i 0U; i 8U; i) { adc_value read_adc_value(); sum adc_value; HAL_Delay(1); } adc_value (uint16_t)(sum / 8U); return (float)adc_value * 3.3f / 4095.0f * 100.0f; }这里结果的单位是 ℃。注意不要让传感器靠近 STM32、驱动芯片或电机本体。若传感器贴到发热器件上自动调速功能会错误地认为环境温度已经很高。4.3 自动模式目标风速计算温度到风速的映射不需要非常复杂但要避免“单阈值频繁启停”的问题。这里使用一个带关断回差的线性区间环境温度低于 24℃风扇停止环境温度在 24℃ 到 26℃允许最低转速运行环境温度在 26℃ 到 34℃占空比从 20% 线性增加到 80%环境温度超过 34℃自动模式最大输出 80%。自动模式下保留 80% 作为上限是考虑到节能和噪声。如果用户希望更高风速可以切到手动模式。最低转速不要从 0% 直接跳到 20%因为直流电机在太低电压下可能无法启动需要提供更柔和且足够有效的初始 PWM。计算函数可以写成uint8_t calc_auto_target(float temp_c) { if (temp_c 24.0f) { return 0U; } else if (temp_c 26.0f) { return 20U; } else if (temp_c 34.0f) { float ratio (temp_c - 26.0f) / (34.0f - 26.0f); uint8_t target 20U (uint8_t)(ratio * 60.0f); return target; } else { return 80U; } }这套区间不是唯一答案真正落地时要根据风扇电机、场景和评审需求调整。这里要解释清楚的是思路设定多个阈值而不是一个阈值让输出与温度之间形成可理解的关系。4.4 手动模式与按键读取手动模式一般提供几个固定挡位比如 0%、20%、40%、60%、80%、100%。模式按键负责在自动和手动之间切换挡位按键负责循环增加挡位。按键扫描要每隔 20ms 到 50ms 执行一次配合状态缓存做边沿检测。下面的代码思路是按下一次改变一次按住不放不会连续加挡static uint8_t last_key2 1U; uint8_t key2_pressed(void) { uint8_t current HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2); uint8_t pressed 0U; if (last_key2 1U current 0U) { pressed 1U; } last_key2 current; return pressed; }这种“上一次状态为高、当前状态为低”的写法就是下降沿检测。如果按键没有接上拉电阻GPIO 悬空会反复跳变按键功能会极不稳定。手动挡位可以定义为数组static const uint8_t manual_levels[] {0U, 20U, 40U, 60U, 80U, 100U}; static uint8_t manual_index 2U; void manual_step_up(void) { manual_index; if (manual_index (sizeof(manual_levels) / sizeof(manual_levels[0]))) { manual_index 0U; } }手动模式下system.target_percent manual_levels[manual_index]。4.5 主循环调度主循环不需要跑得特别快否则所有任务都会挤在一起。更合适的做法是让不同任务按自己的周期运行。while (1) { uint32_t now HAL_GetTick(); if (now - system.last_ctrl_tick 50U) { system.last_ctrl_tick now; if (system.mode FAN_MODE_AUTO) { system.temperature_c read_temperature_c(); system.target_percent calc_auto_target(system.temperature_c); } else { system.target_percent manual_levels[manual_index]; } if (system.cur_percent system.target_percent) { system.cur_percent; } else if (system.cur_percent system.target_percent) { system.cur_percent--; } fan_set_speed(htim3, TIM_CHANNEL_1, system.cur_percent); printf(temp%.1f target%d cur%d mode%d\r\n, system.temperature_c, system.target_percent, system.cur_percent, system.mode); } if (now - system.last_display_tick 500U) { system.last_display_tick now; display_refresh(system); } scan_mode_button(); scan_step_button(); }关键点是“先计算目标再逐步靠近目标”。如果直接把温度换算出来的占空比赋给电机温度跳变时风扇转速会瞬间突变。步进 1% 的方式让转速变化更平滑也减少对机械结构的冲击。4.6 OLED 显示内容OLED 显示并非控制系统的必备部分但能显著提升演示效果。建议显示当前温度当前模式AUTO / MANUAL当前挡位或百分比传感器是否正常。OLED 驱动选择常见的 SSD1306 方案即可。很多初学者会把 I2C 初始化和主循环刷屏放在一起导致温度采集被频繁打断。比较好的做法是设置一个 500ms 刷新周期只在主循环空闲时更新屏幕。注意OLED 屏幕刷新不要放在 PWM 中断里也不要在按键消抖死循环中长时间调用。否则会造成主循环卡顿温度更新不及时。5. “绿色”如何落地调速、休眠与能效测量5.1 自动调速本身就是节能手段传统风扇在全速运行时功耗最高。智能风扇在温度较低时输出较低占空比电机平均功率下降这是最直接、最可解释的节能收益。实际测试时不要只演示“风扇能自动转”还要设计一组对比固定全速运行一段时间记录电流或功率自动模式运行相同时间记录电流或功率把加热源移走让温度下降记录风速回落时间。通过这组测试可以解释“绿色”不是口头概念而是控制策略带来的可测量结果。5.2 选择效率更高的电机驱动方式小功率直流电机可以使用 L9110S 模块。更追求性能时可以选择由 N 沟道 MOS 管组成的电机驱动电路。MOS 导通电阻小驱动压降低整机效率更高。使用 ULN2003 这类达林顿管虽然接线简单但导通压降偏大在小电压电机上会造成额外损耗。如果毕业设计把“能效”作为重点可以对比几种驱动方式驱动方式优点注意点适用场景L9110S 模块体积小、接线简单最大电流有限小型直流风扇L298N 模块电流大、抗堵转能力较强压降大、体积大教学实验MOS 管驱动损耗低、可做到高频 PWM需要保护电路追求效率的场景5.3 降低系统自身的功耗除了电机控制器本身也有功耗。在“绿色”方向进一步优化时可以这样做不需要看 OLED 时降低刷新频率或关闭 OLED温度采集不是越快越好可以每 500ms 到 1s 读一次不需要用到的外设时钟在空闲时关闭如果使用电池供电可以尝试让 MCU 进入 Sleep 或 Stop 模式由定时器或按键唤醒。这里要区分课程设计和产品化。课程设计阶段先保证功能稳定把主循环写成周期调度即可。如果要突出低功耗再引入低功耗模式和外部中断唤醒否则会引入大量调试问题。5.4 能效测量方法没有专业功率计时可以用电流表串联在电机供电回路中。如果使用 USB 供电也可以在 VBUS 和
返回列表