用单片机给风扇做控制系统很多本科毕业设计第一反应是做一个“能调速的风扇”。可课题一旦强调“绿色”重点就不再只是多一个按键、多一档风速而是整套方案如何降低无效能耗、如何根据环境自动调节、如何让待机功耗与整机效率都经得起追问。下面以“绿色风扇的设计与实现”这个典型的 51 单片机毕业设计课题为线索把从需求拆解到硬件电路、C 语言程序、Proteus 仿真、故障排查的完整过程理一遍最终得到一个以 STC89C52 为核心、由 DS18B20 测温并按 PWM 占空比自动调节直流风扇转速的最小样机。这篇文章适合正在做单片机课程设计或毕业设计的学生也适合想自己动手做一台低功耗智能风扇的开发者。完成之后你能得到一份可以直接上板调试的原理图逻辑、一套 Keil5 工程代码以及一份覆盖“自动调速、定时关机、手动模式、温度显示”的验证方法。1. 先拆解“绿色风扇”的功能边界和评价指标1.1 普通风扇浪费能量的三个阶段普通风扇的“不绿色”不一定出在电机本身更多出在控制逻辑上。第一个浪费阶段是无差别全速运行。固定档位风扇不管当前温度是 24℃ 还是 32℃只要按下开关就是同一转速降温需求和机械输出之间没有对应关系。第二个浪费阶段是运行时间失控人睡觉或离开后风扇仍然在转这部分电能完全无效。第三个浪费阶段容易被忽视为了做无级调速直接用电阻串联降压多余能量变成热量散掉电机端电压下降但电源侧电流反而没有降下来。所以“绿色风扇”的本质是三点让风量跟随温度变化、让运行时长可控、让调速过程不产生额外损耗。1.2 核心功能定位自动温度调速、定时关机、低功耗显示围绕上面三个浪费环节样机功能可以拆成四块。第一块是温度采集与自动调速。用 DS18B20 测量环境温度单片机根据温度区间计算 PWM 占空比控制直流电机转速。温度高时转速提高温度降下来后自动切到低档或停止。第二块是手动/自动模式切换。手动模式保留传统按键调速习惯适合用户明确想吹风的情况自动模式则交给传感器和策略代码判断。第三块是定时关机。默认提供 30 分钟、60 分钟两个定时档位防止遗忘关机造成的长期空转。第四块是运行状态显示。用 LCD1602 显示当前温度、模式、风速档位和剩余时间让用户能直观判断风扇当前是在“节能运行”还是“全速运行”。1.3 硬件和软件的分工硬件要解决的是“单片机凭什么能驱动电机”“传感器怎么把温度变成数字信号”“怎么防止电机启停时把单片机复位”。软件要解决的是“多久测一次温度”“怎么生成稳定 PWM”“按键去抖怎么处理”“自动模式怎么防止温度临界点反复开停”。如果只把硬件搭好风扇能转但不智能如果只写软件没有正确驱动电路电机转不起来还会烧 IO 口。因此下面从器件选型开始逐步把这两个层面串起来。2. 器件选型围绕电压、传感器、驱动方式做对比2.1 系统总体结构整个系统由五个模块组成。控制核心STC89C52 单片机负责按键扫描、温度读取、PWM 生成、显示刷新。温度采集模块DS18B20 单总线数字温度传感器输出数字温度值。执行模块直流电机加上 MOSFET 驱动电路接收单片机 PWM 信号。人机交互模块独立按键输入和 LCD1602 液晶显示。电源模块为单片机提供 5V为电机提供独立 12V 电源。这种模块化结构的好处是把“弱电控制”和“驱动部分”分开后续如果要替换成更大功率电机或交流风机只需要改执行模块不需要重写单片机逻辑。2.2 控制器选型STC89C52 为什么够用STC89C52 是 51 内核单片机中很常见的一款片内 8KB Flash512 字节 RAM有 3 个定时器支持 ISP 串口下载。对这个毕业设计而言最核心的资源是定时器和外部中断。软件 PWM 需要一个定时中断产生基准节拍按键去抖和显示刷新也依赖定时扫描。STC89C52 的定时器 0、定时器 1 足够覆盖这些任务。相比 STM32 方案51 单片机资料多、仿真模型成熟、焊接难度低适合作为课程设计和毕业设计的入门载体。选择时注意一个版本问题STC89C52 的复位电路和下载方式与 AT89C52 基本一致但 Proteus 中默认使用的是 AT89C52 模型配置工程时要把晶振频率和 Keil 工程保持一致避免仿真时序偏差。2.3 温度传感器选择DS18B20 的接口特点DS18B20 是常见的单总线数字温度传感器测温范围 -55℃ 到 125℃12 位分辨率时温度精度为 0.0625℃。它在毕业设计里的优势很明显只需要一根数据线引脚少Proteus 里有现成仿真模型。但单总线也有代价。数据传输必须严格遵循时序复位脉冲、存在脉冲、读写时隙都有几百微秒到几十微秒的量级。程序里一旦用delay()处理时把延时写错读回的数据就会异常常见故障是固定显示 85℃ 或随机跳变。这个 85℃ 是 DS18B20 上电复位后的默认温度寄存器值如果程序一直没读到真实温度界面上就会停留在 85℃。后续排错章节会专门说明。2.4 驱动方案选型对比风扇电机驱动方案要根据电机类型选择不能把单片机 IO 口直接接到电机上。51 单片机普通 IO 口输出电流能力有限直接驱动电机不仅电流不够电机启停瞬间的反向电动势还可能把 IO 口烧坏。下面用表格对比常见的三种方案。方案控制对象调速方式优点缺点适用阶段MOSFET 低速驱动直流电机12V/5V 直流电机PWM 占空比调速电路简单、安全、易调试只能用于直流小电机课程设计样机双向可控硅调节交流风机220V 交流电机过零触发改变导通角可直接用市电风扇电机涉及强电隔离、调试风险高产品化扩展无刷直流电机驱动BLDC 电机电子换相加 PWM效率高、噪声低需要驱动芯片或三相半桥产品化扩展毕业设计样机优先选择第一种。12V 直流电机加上逻辑电平 MOSFET可以在低电压下安全完成整个 PWM 调速闭环。如果课题要求“绿色”更严格可以额外测量电机电流和转速但那是验证层面的增强不改变硬件主体。2.5 显示与按键的方案取舍显示模块选择 LCD1602 在 4 位数据线模式下只需要 7 根控制线能显示温度和档位。相比数码管LCD1602 不需要动态扫描单片机中断压力小显示内容也更直观。按键采用独立按键即可。三个按键分别承担“模式切换”“风速/定时加”“风速/定时减”功能配合不同长按和短按状态可以扩展更多操作。这里不建议做矩阵键盘否则按键扫描和温度采样会互相抢夺主循环时间增加程序复杂度。3. 硬件电路连接与设计要点3.1 引脚分配做硬件之前先把引脚分配确定下来后面写程序才不用反复改头文件。常见分配如下。外设使用引脚说明DS18B20 数据线 DQP1.0单总线需 4.7k 上拉电阻风扇 PWM 输出P1.1接 MOSFET 栅极驱动电阻模式按键 KEY1P3.0手动/自动切换加档按键 KEY2P3.1风速加或定时加减档按键 KEY3P3.2风速减或定时减LCD1602 RSP2.0寄存器选择LCD1602 RWP2.1读/写选择可直接接地LCD1602 ENP2.2使能信号LCD1602 D4~D7P2.3~P2.64 位数据线这里把 LCD1602 的 RW 直接接地因为程序只向液晶写入不做读忙检测靠延时等待完成可以省一根 IO 口。注意使用 P2 口时通常不用外接上拉电阻但如果把数据线放在 P0 口P0 是开漏输出必须加 4.7k 到 10k 的上拉电阻否则液晶显示会非常不稳定。3.2 单片机最小系统与下载电路最小系统由电源、复位电路、晶振电路组成。复位电路可以采用经典 RC 复位复位引脚接 10uF 电解电容到 VCC同时通过 10k 电阻到 GND上电瞬间产生高电平复位脉冲。晶振电路在 XTAL1 和 XTAL2 之间接 11.0592MHz 晶振两个引脚各接一个 30pF 电容到地。下载电路采用串口 ISP 方式。STC89C52 使用 P3.0/RXD 和 P3.1/TXD 与 USB 转 TTL 模块通信下载时先点软件里的“下载”按钮再给单片机重新上电。如果电脑上没有识别到串口要检查 USB 转 TTL 芯片驱动以及 P3.0、P3.1 是否被其他外设占用。3.3 DS18B20 接口与上拉电阻DS18B20 的数据线 DQ 是漏极开路结构正常工作时外部必须有上拉电阻推荐阻值 4.7k。如果通信线很短单片机到传感器距离小于 20cm4.7k 通常没问题如果传感器通过长线引出要适当增大上拉电阻并缩短读取间隔。原理图上 DQ 引脚接 DS18B20 的中间引脚另外两个引脚分别是 GND 和 VCC。传感器电源可以直接用单片机 5V。为了滤除电源噪声在 DS18B20 的 VCC 和 GND 之间放一个 0.1uF 陶瓷电容更好。3.4 电机驱动、续流二极管和抗干扰电机驱动采用 N 沟道 MOSFET 低边驱动。12V 电源正极接电机一端电机另一端接 MOSFET 漏极MOSFET 源极接 GND。单片机 PWM 信号经过一个 100Ω 到 220Ω 的栅极电阻接到 MOSFET 栅极栅极与源极之间并一个 10k 到 100k 电阻保证单片机复位时 MOSFET 处于关断状态。这里最容易犯的错误是忘记续流二极管。电机是感性负载PWM 关断瞬间电流不能突变如果漏极没有对 12V 的二极管续流回路漏极会产生很高的尖峰电压轻则让 MOSFET 发热重则通过地线干扰单片机复位。续流二极管应选用快恢复二极管或肖特基二极管阳极接 MOSFET 漏极阴极接 12V 电源正极。这个二极管并联在电机两端也可以但必须保证关断瞬间电流能形成回路方向不能接反。选 MOSFET 时还要注意逻辑电平问题。IRF540 这类普通 N-MOS 在栅源电压 5V 时导通电阻偏大容易发热。推荐使用逻辑电平 MOSFET例如 IRLZ44N这类管子能在 5V 栅源电压下充分导通。下面给出电机驱动部分的电路结构示意图。12V ---- 电机正极 电机负极 ---- D(漏极) S(源极) ---- GND G(栅极) ---- 220R ---- P1.1(PWM) | 10k | GND续流二极管阳极接电机负极与MOSFET漏极连接点 阴极接12V注意这个样机只处理 5V/12V 直流小电机全程不接触 220V 交流强电。后续若改为可控硅调压驱动交流风扇必须增加光耦隔离、过零检测和电源隔离操作强电时要由专业人员完成。3.5 供电与地线处理单片机用 5V电机用 12V建议不要把两者共用一个稳压器。常见的做法是12V 开关电源总输入一路给电机驱动另一路用 7805 或 DC-DC 降压到 5V 给单片机。地线处理很关键。电机工作时电流变化大如果电机回路和单片机回路共同走过一段细长地线地线上产生的压降会让单片机复位。推荐单点接地5V 电源地、12V 电源地、电机驱动地先在电源输入端的滤波电容处汇合再铺一整块地。电机电源两端并联 100uF 电解电容和 0.1uF 陶瓷电容单片机电源附近也同样放滤波电容两个电容离芯片引脚越近越好。4. 软件实现主循环、定时中断和状态机4.1 程序模块划分如果单片机程序只有“读温度、调电扇”两个动作看起来简单但实际运行时按键扫描、LCD 刷新、DS18B20 读取都是耗时操作全部放在main()里顺序执行会互相等待。推荐把功能分成三层底层驱动DS18B20 单总线时序、LCD1602 显示函数。中间层控制软件 PWM 输出、按键去抖。上层逻辑调速状态机、定时关机状态。主循环只负责处理“有什么事件发生了”比如按键事件、2000ms 温度采样事件而不是在循环里反复执行大延时。DS18B20 的温度转换最长需要 750ms如果每读一次温度就让主循环死等 750ms按键响应就会非常迟钝所以温度采集要放到定时事件里执行并且采取“上一次结果先用着下一次转换在后台进行”的方式。4.2 软件 PWM 实现软件 PWM 的核心是一个定时器中断。以 11.0592MHz 晶振、12T 模式为例定时器 0 每 100us 进入一次中断每 20 次中断构成一个 2ms 的 PWM 周期也就是 500Hz。#include reg52.h sbit FAN P1^1; // 风扇控制引脚 #define PWM_PERIOD 20 // PWM周期 20 * 100us 2ms约500Hz unsigned char pwm_tick 0; // 当前PWM计数 unsigned char pwm_duty 0; // 占空比范围0~20 void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器0方式116位 TH0 0xFF; // 定时约100us 11.0592MHz TL0 0xA4; // 65536 - 92 65444 0xFFA4 ET0 1; TR0 1; EA 1; } void Timer0_ISR(void) interrupt 1 { TH0 0xFF; TL0 0xA4; pwm_tick; if (pwm_tick PWM_PERIOD) { pwm_tick 0; } // 占空比为0时完全关断占空比达到周期时全速运行 if (pwm_duty 0) { FAN 0; } else if (pwm_duty PWM_PERIOD) { FAN 1; } else { FAN (pwm_tick pwm_duty) ? 1 : 0; } }这段代码的关键是“不要用延时循环刷 PWM”。如果把电平翻转写进主循环里的delay()按键一按下PWM 波形就被拉断电机转速会出现瞬间抖动。把 PWM 放在定时中断里主循环无论做什么周期和占空比都稳定。500Hz 只是演示值实际听感上可能有一点噪声。如果希望更平滑可以缩短定时器周期把 PWM 频率提到 1kHz 以上。51 单片机纯软件生成高频 PWM 比较吃力产品化时应改用带硬件 PWM 的芯片或专用电机驱动模块。4.3 DS18B20 单总线时序与温度读取DS18B20 的操作顺序是复位、跳过 ROM、启动温度转换、等待转换完成、再次复位、跳过 ROM、读取暂存器。下面是读写单字节函数。写数据时要注意时隙是按“低电平持续时间”来表达 0 和 1 的不能靠简单延时蒙混。sbit DQ P1^0; unsigned char ds18b20_reset(void) { unsigned char presence 0; DQ 0; delay_us(500); // 复位脉冲至少480us DQ 1; // 释放总线 delay_us(70); // 等器件应答 if (DQ 0) { presence 1; // 检测到存在脉冲 } delay_us(400); return presence; } void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; if (dat 0x01) { DQ 1; } delay_us(60); DQ 1; dat 1; } } unsigned char ds18b20_read_byte(void) { unsigned char i; unsigned char dat 0; for (i 0; i 8; i) { dat 1; DQ 0; _nop_(); _nop_(); DQ 1; _nop_(); if (DQ) { dat | 0x80; } delay_us(60); } return dat; }读取温度时把 DS18B20 返回的两个字节组成 16 位数据再右移 4 位得到整数摄氏度。C51 的int是 16 位温度转化时要考虑补码否则零下温度会显示错误。int ds18b20_get_temp(void) { unsigned char low, high; unsigned int raw; int temp; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过ROM ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); // 12位精度最长转换时间 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 low ds18b20_read_byte(); high ds18b20_read_byte(); raw ((unsigned int)high 8) | low; temp (int)raw 4; // 带符号右移得到整数摄氏度 return temp; }这段代码在原理上够用但要注意 C51 的sprintf和浮点数转换开销较大。要在 LCD 上显示 0.1℃ 精度时建议直接用整数运算拆分小数不要依赖sprintf(%.1f)否则程序会变得很臃肿。4.4 自动调速策略要加回差自动模式的调速逻辑不是简单“温度高就开、温度低就关”否则当温度在某一个值附近波动时电机会频繁启停电流冲击大也更容易坏。推荐的做法是设置两个不同门槛。运行状态温度条件动作当前停止温度 28℃启动风扇设定低档当前运行温度 30℃升到高档当前运行温度 27℃维持中档当前运行温度 25℃维持低档当前运行温度 25℃停止风扇风扇从停止到启动需要 28℃但停止后要等温度降到 25℃ 以下才关机中间这 3℃ 就是回差。这样设计后温度在 26℃ 左右波动时风扇不会反复启动。对应代码如下。void auto_control(int temp) { if (fan_running 0) { if (temp 28) { fan_running 1; pwm_duty 6; // 低档启动 } } else { if (temp 30) { pwm_duty 18; // 高档 } else if (temp 27) { pwm_duty 12; // 中档 } else if (temp 25) { pwm_duty 6; // 低档 } else { fan_running 0; pwm_duty 0; // 低于25℃后停止 } } }这样的调速逻辑既满足“绿色节能”又能在答辩时清楚解释为什么温度临界点不会导致频繁动作。这一条是毕业设计里很容易被老师追问的细节。4.5 按键、显示与运行状态机按键处理采用定时扫描加状态位。程序每 1ms 产生一个系统节拍每隔 20ms 扫描一次按键连续两次读到相同电平才认为按键有效这就是软件消抖。主程序状态机可以这样写void main(void) { system_init(); lcd_init(); while (1) { if (scan_flag) // 每20ms置1由定时器产生 { scan_flag 0; key_scan(); } if (key_event) // 按键处理 { key_event 0; key_process(); } if (sample_flag) // 每2000ms置1 { sample_flag 0; work_state.temp ds18b20_get_temp(); if (work_state.mode MODE_AUTO) { auto_control(work_state.temp); } } lcd_refresh(); } }在手动模式下用户按一次加档就提高一段占空比并在pwm_duty上封顶。在定时关机模式下用秒变量累计时间到点后强制关闭风扇并把风扇切换到待机状态。LCD 显示内容建议固定格式例如第一行显示T: 27 C MODE:AUTO SPD: MID TIMER:30m℃符号在 LCD1602 的字符表中没有标准定义直接写会变成乱码通常用字符C代替。如果需要显示“自动、手动”等汉字要使用支持中文的 LCD12864 或自己造字不要想当然地往 1602 里写汉字。5. Keil 工程搭建与 Proteus 仿真验证5.1 Keil5 工程配置要点Keil 工程创建后第一件事是选中对应型号。仿真阶段可以使用 AT89C52 替代 STC89C52两者内核相同Proteus 默认能识别 AT89C52。关键配置在Options for Target中Output选项卡勾选Create HEX File否则没有下载文件。Debug选项卡如果使用 Keil 内置仿真选择Use Simulator如果配合 Proteus 联合调试需要配置Use Keil Monitor-51 Driver或使用 Proteus 的Keil4/5 VDM驱动。晶振频率要设置为 11.0592MHz与原理图一致。很多同学在 Keil 仿真面板里打开 System Viewer发现外设寄存器空白通常就是Debug配置没有选仿真器类型或者程序没有运行到断点位置。先点单步运行或运行到main()行再打开外设窗口才会看到数值变化。5.2 Proteus 仿真图画法和注意事项Pro