做单片机毕业设计的人,很多都会被“绿色”这两个字误导。有人以为绿色风扇就是把扇叶做成绿色,有人以为是在风扇周边加几棵仿真植物,还有人直接把它做成了一个普通调速风扇的换皮版。等到答辩时才发现,评委问的不是“风扇能不能转”,而是“你的设计到底哪里绿色”。一个听起来高级的题目,如果拆解不透,反而比简单题目更难拿分。
“绿色风扇”真正应该落地的方向,是节能与智能:根据环境温度自动调节转速,不让人工一直手动干预;用 PWM 调速代替电阻降压调速,减少无效发热;在无人操作时进入低功耗待机,而不是让电路板一直满负荷空转。这套设计听完就知道,它既不是外观层的“绿色”,也不是概念层的“绿色”,而是从传感器输入、控制逻辑到电机驱动整个闭环都在围绕“按需运行”做文章。
本文不打算给你一个只能照抄的仿真工程,而是把设计思路、硬件电路、软件架构、调试顺序和常见坑一次讲透。你可以基于本文重新组织一套属于自己的毕业设计说明,也可以直接在最小系统上完成实物验证。无论选 51 系列单片机,还是打算用 STM32,核心原理都是一样的。
1. 这篇文章真正要解决的问题
毕业设计最容易出现两种极端。第一种是选题太简单,功能只有“按键开关 + 电机转”,代码量只有几十行,文档撑不满二十页,答辩时拿不出能讲两分钟的技术点。第二种是选题看起来复杂,但只是简单把一个传感器模块、一个电机模块和一个显示屏拼在一起,模块之间没有逻辑联系,评委一问“传感器数据如何影响输出”就卡壳。
“绿色风扇的设计与实现”正好卡在中间:题目有开放性,但必须靠控制策略来“立住”。风扇本身不稀奇,电机驱动也不稀奇,决定水平的是你如何证明“我的风扇比普通风扇更节能、更智能、更绿色”。这就迫使你使用闭环控制、状态机、定时器、PWM、低功耗处理这些真正属于单片机的知识,而不是只会让 LED 闪烁。
从笔者接触过的毕设题目看,风扇类题目保持热度的原因也很现实:它有机械结构,能做出实物;它的功率不大,适合在实验室和宿舍调试;它和日常生活相关,用户在答辩时容易理解;它还能挂上空调节能、智能家居、环保低碳这些大方向。对本科生而言,是一个“讲得清楚、做得出实物、演示效果好”的选择。但正因为大家都会做风扇,如果你不把“绿色”的定义落到硬指标上,很容易被评委认为是普通风扇换了个名字。
本文要解决的核心问题有三个:
- 把一个模糊的“绿色风扇”题目,拆成可量化、可实现的功能指标。 2.告诉你每个硬件模块为什么存在,而不是停留在线路连接层面。
- 给出完整的嵌入式 C 代码结构和调试排错方法,让你不但能跑通,还能解释清楚。
读这篇文章的人,如果正在为单片机毕业设计选题发愁,或者已经选了类似风扇类题目但不知道如何避免同质化,那么重点看第 2 章和第 3 章。如果你已经画好原理图,不知道怎么把代码组织得像一个工程而不是一个流水账,重点看第 5 章和第 8 章。如果你正在为“为什么我的风扇不转”“为什么温度采样不稳定”这类实际问题焦虑,可以直接跳到第 7 章的排查表。
2. “绿色风扇”的概念拆解与技术建模
2.1 绿色不是颜色,是运行策略
先做一个判断:对于一个以单片机为核心的风扇控制系统,绿色本质上是三个可量化目标,节能、低功耗、按需运行。
节能体现在驱动方式上。很多低成本风扇还在用“串联电阻降压”或“继电器切档”的方式调速。电阻降压的问题在于多余能量全部变成热量;继电器切档的问题在于电机只能工作在几个固定转速点,无法匹配连续变化的温度需求。使用 PWM 控制电机平均电压,电机在单位周期内开关切换,电流波形更接近连续调速,在低速档位时不会像电阻降压那样产生大量损耗。
低功耗体现在系统状态管理上。控制板不是永远要让传感器、显示屏、主控芯片全速工作。默认情况下,风扇可以在无人操作 30 秒后关闭数码管或背光,只保留温度采集和控制功能;如果环境温度低于启动阈值,整个系统可以进入低功耗待机,而不是白白让电机维持最低转速空转。这个逻辑用继电器很难做细,但用单片机休眠和定时唤醒很容易实现。
按需运行体现在控制闭环里。普通风扇不管人在不在、环境热不热,只要打开就是固定档位。绿色风扇应该让“环境温度”成为唯一的调速依据,或者至少允许用户选择“自动档”把控制权交给系统。这就让项目的核心从“执行机构控制”变成了“传感器采集 + 控制策略 + 执行输出”,整体技术含量完全不同。
如果一个风扇项目能把上面三件事都做出来,它自然配得上“绿色”二字。如果只做按键档位切换,那这个题目就名不副实。
2.2 系统输入端:测什么才能支撑控制
绿色风扇不应该只靠一个温度传感器走天下。为了更像一个可用的智能系统,一个完整方案可以同时采集两路物理量:
| 物理量 | 传感器方案 | 作用 |
|---|---|---|
| 环境温度 | DS18B20 或 NTC 热敏电阻 | 判断环境是否炎热,作为自动调速主依据 |
| 人体检测 | 红外热释电模块或人体红外传感器 | 判断是否有人在场,无人时降低转速或进入待机 |
具体到毕设,很多人会把“人体检测”砍掉,理由是增加成本。但在“绿色”主题里,人体检测恰恰是人走风扇关功能的物理基础。如果没有它,绿色只能停留在“根据温度自动调速”这个层面,说服力不够强。用热释电模块的另一个好处是它输出数字信号,代码上只读一个引脚的电平即可,不需要复杂的数字解码,对新手很友好。如果预算有限,至少也要保留温度传感器加按键手动档的结构,但这会让项目少一个亮点。
2.3 系统输出端:电机驱动和状态展示
直流电机是执行机构。对毕设来说,最常用的驱动方案有两种:小功率电机用三极管或 MOS 管直接驱动,大一点的电机用 L298N 或 TB6612 这类驱动模块。驱动模块的好处是内置逻辑、隔离和续流保护,但缺点是需要额外几根杜邦线电源线,显得不够紧凑。如果选 3 到 5V 的小型直流电机,只要用一颗 NPN 三极管或者 N 沟道 MOS 管就能把 PWM 信号变成电机功率驱动。这个方案更容易在洞洞板上手工焊接,实物也更像“自己设计”的成果,而不是模块堆叠。
状态展示建议至少包含两类信息,当前风速档位和环境温度。数码管显示档位最直观,LCD1602 或 OLED 可以显示两行文字,在答辩时更容易解释。如果选择 OLED,可以同时显示温度、档位、运行模式三行内容,整个界面信息量更大。这里有一个容易被忽略的点:显示器件不要放在电机背面或电池旁边,电机转动产生的振动和磁场干扰会影响显示屏稳定性,在安装实物时要把显示区固定好。
2.4 系统控制核心:把输入映射成输出
控制核心的任务是把传感器采集到的信息翻译成恰当的 PWM 占空比。最简单的控制逻辑是阈值比较:温度低于 25 度时关闭风扇,25 到 30 度之间用低速,30 到 35 度之间用中速,35 度以上用高速。这个逻辑很容易实现,演示效果也清楚。但如果你想让设计更有亮点,可以引入简单线性映射:
- 设最低启动温度为
T_min,最高全速温度为T_max。 - 当温度小于
T_min时,占空比为 0。 - 当温度在
T_min到T_max之间时,占空比随温度线性增加。 - 当温度大于
T_max时,占空比为 100%。
这么做的好处是避免风扇在阈值附近频繁跳档。比如温度在 24.9 度和 25.1 度之间波动时,阈值判断会导致风扇一会儿停一会儿转,体验很差。加入滞回区间,也就是“启动温度”和“停止温度”留 2 度左右的回差,可以显著改善稳定性。这个细节虽然写起来不复杂,但答辩时讲出来,老师会认为你真的考虑过控制工程的问题。
3. 总体方案设计与器件选型
3.1 MCU 选择:51 和 STM32 怎么取舍
在搜索引擎上搜“单片机毕业设计”,出现频率最高的就是 51 单片机、STM32,以及各类开发板。对这个题目的建议是:如果你对单片机还处于“跟着例程点灯”阶段,优先选择 51 系列,比如 STC89C52RC 或者 AT89C52 兼容型号。51 的资料量大、例程多、Keil 工程容易搭建,而且芯片引脚少,手工焊接失败率低。更为关键的是,51 的定时器和 GPIO 操作模式简单,你可以用最直接的方式理解 PWM 的产生过程。
如果你已经能熟练使用 HAL 库或标准库,或者系统需要跑图形界面,可以选择 STM32。STM32 的优势是内部有硬件定时器 PWM 输出通道,还能用 ADC 直接采集 NTC 电压,无需外接 ADC 芯片。并且 STM32 的低功耗模式比 51 更丰富,可以用 STOP 模式把待机功耗压得很低,这样“绿色”主题可以做得更深入。
从毕设评分角度看,51 做出的实物更符合“单片机原理”这门课的定位,STM32 则更像嵌入式系统课程的作品。哪个更容易过,取决于你的指导老师所在课程组更偏向哪类内容。本文的代码逻辑以 51 平台为例,但控制思路和模块划分只要稍作修改就能移植到 STM32。
3.2 温度传感器:DS18B20 与 NTC 的对比
温度采集有两个主流路径。
DS18B20 是数字式单总线传感器,测温范围 -55 到 125 摄氏度,分辨率可配置到 12 位。它不需要 ADC 通道,一条数据线就能和单片机通信。缺点是单总线时序要求严格,初学者照着例程抄可能能跑,但一旦改用不同延时函数,时序就容易出问题。此外 DS18B20 的传感器响应速度在密封环境里偏慢,因为传感器探头是金属封装,热量传导需要时间。
NTC 热敏电阻是模拟量输出,需要配合分压电阻,把电阻变化转换成电压变化,再送进单片机的 ADC 模块。NTC 优点是成本低、响应快、电路简单。缺点是需要查表或者用公式计算温度,且热敏电阻本身有非线性,厂商参数不一致时要现场校准。如果对温度精度要求不高,这个误差完全可接受。
在绿色风扇这个项目里,我更推荐 NTC。理由有三个:第一,风扇本身不需要极高温度精度,能区分 25 度和 35 度就足够;第二,使用 NTC 可以展示你对 ADC、分压电路、标定方法的掌握,知识覆盖面比单线数字传感器多看一层;第三,不用依赖复杂的单总线时序库,对新手更友好。
3.3 电机驱动器件:三极管、MOS 管还是驱动模块
风扇电机属于感性负载。感性负载在断电瞬间会产生反向感应电动势,如果不做续流处理,会直接冲击驱动管或者单片机引脚。所以电机驱动电路里必须考虑续流二极管,这一点往往是新手最容易忽略的。
如果用小功率 5V 直流电机,且额定电流在 200mA 以下,一颗 S8050 三极管就能驱动。三极管方案成本最低,饱和压降在 0.2V 左右,但大电流下压降会带来发热。PWM 频率较低时,三极管在线性区和截止区之间切换的时间变长,开关损耗也更明显。
如果有 1A 左右的电流,或者希望达到更好的绿色节能效果,应该选 N 沟道 MOS 管。常见的低压 MOS 管如 AO3400,在 4.5V 栅极电压下导通电阻很低,损耗显著小于三极管。在单片机上要想让 MOS 管完全导通,要用推挽输出模式驱动引脚,必要时加一个 10k 下拉电阻到地,防止单片机复位期间栅极悬空导通。
驱动模块方案比如 L298N 的好处是省心,缺点是模块体积大、效率低。毕设里如果只带一个小电机,用模块反而显得设计不足。建议自己用 MOS 管搭建驱动级,并且把原理图和工作原理写在设计文档里,这是评分的加分项。
3.4 显示、按键与电源方案
显示部分用 1602 LCD 或 OLED 都可以。LCD1602 需要占用至少 6 个 IO 口,调试时要注意对比度电位器。OLED 基于 I2C 或 SPI 接口,I2C 模式只占 2 根 IO 线,更省引脚。如果要展示更多参数,OLED 在无背光的概念上更贴合“绿色”。
按键设计上推荐用独立按键而不是矩阵键盘。绿色风扇最多只需要 3 个按键:模式切换键、风速加键、风速减键。独立按键每个占一个 GPIO,配合软件消抖就够了,不必为了复杂度强行引入 4×4 矩阵。
电源方案要看你的系统是电池供电还是 USB 供电。如果是 USB 供电,5V 电源直接给电机供电,MCU 和传感器如果需要的电压不同,要通过 LDO 电源芯片供电。如果是锂电池供电,要对 3.7V 升压或直接使用 3.3V 电机型号,电路复杂度会增加不少。毕设阶段建议优先考虑 DC 5V 或者 USB 供电,把精力放在控制逻辑而非电源设计上。
4. 硬件电路设计与关键细节
4.1 电源输入与系统复位
一个完整的控制板电源输入,至少需要经过两个环节:滤波电容和去耦电容。在电源输入端并联 100uF 电解电容和 0.1uF 陶瓷电容,电解电容吸收低频纹波,陶瓷电容滤除高频噪声。如果电机和单片机共用一组电源,电机启动瞬间电流可能拉低母线电压,导致单片机复位。所以不要让电机直接跨接在单片机电源引脚旁,而要让电机的电源线靠近输入插座端走线。条件允许时,电机驱动部分和逻辑部分使用星型接地,即各自独立回到电源地,不要串在一起。
51 单片机系统必须有复位电路。最常用的是上电自动复位:复位引脚通过 10k 电阻接地,再通过 10uF 电容接 VCC。系统上电瞬间电容充电,复位引脚短暂保持高电平完成复位。这个电路虽然基础,但焊接时如果电容失效或者电阻焊错位置,会导致单片机上电后无法正常运行。
4.2 电机驱动电路与续流保护
下面以 NPN 三极管驱动小功率直流电机为例,讲解关键电路结构。三极管基极通过 1k 电阻接单片机 PWM 引脚,发射极接地,集电极接电机负极,电机正极接电源正极。在电机两端反并联一个 1N4007 或者 1N5819 二极管,二极管负极接电源正极,正极接集电极。这个二极管的专业名称是续流二极管,作用是给电机断电时的反向电流提供泄放回路。
这里的顺序很重要:一定要先接电机,再接二极管,确认二极管极性后再上电。如果二极管接反,等于把电源短路,上电瞬间就会烧毁二极管。在三极管基极串联电阻的选择上,阻值取 1k 到 4.7k 都比较常见。阻值太大驱动电流不足,三极管无法完全饱和;阻值太小,单片机引脚输出电流可能超限。具体阻值可以按三极管最小基极电流估算,但作为毕设,1k 电阻通常是稳妥选择。
如果换成 MOS 管驱动,电路结构变成:栅极接单片机引脚,源极接地,漏极接电机负极。栅极和地之间加 10k 下拉电阻,防止单片机复位时电机误启动。二极管仍然反并联在电机两端。
4.3 NTC 温度采集电路
NTC 热敏电阻和普通电阻串联构成分压电路。假设 NTC 放在电源侧,普通电阻接地,则 ADC 采样点应该选在两个电阻的中间节点。温度升高时 NTC 阻值下降,采样点电压下降。这个电压与温度的关系不是线性的,需要在代码里通过查表或计算转换。
分压电阻的阻值选择有讲究。如果分压电阻太接近 NTC 在目标温度范围内的阻值变化区间,输出电压变化范围最宽,分辨率最好。常规做法是先查看你手里 NTC 的规格书,它在 25 度时的阻值,比如 10k,然后选择 10k 分压电阻。这样在 25 度附近,采样输出电压约等于电源电压的一半,正好落在 ADC 中间量程。
ADC 参考电压的稳定性会直接影响测温精度。如果你用的是 5V 供电的 51 单片机外接 ADC,参考电压来自 VCC,而 VCC 又因为电机启动波动,那么 ADC 读数会在不同时间差出很大一段。想避免这个问题,要么单独给传感器供电,要么在软件里对 ADC 参考电压进行补偿。很多 STM32 芯片内部有稳压源,可以算出实际 VDD,从而实现补偿。对 51 而言,最简单的方法是保证电机供电与单片机供电不要共用一个 LDO。
4.4 显示屏与按键电路
OLED 的 I2C 接口需要 SCL 和 SDA 两根线。如果使用开漏模式的 I2C,需要外接上拉电阻,通常 4.7k。你在买 OLED 模块时,模块本身已经带了上拉,所以单片机侧不用重复加。OLED 的 VCC 和 GND 要从电源处接,不要从单片机引脚取电,否则屏幕亮度不稳可能出现花屏。
按键电路推荐用“一端接单片机引脚、另一端接地”的结构。单片机引脚内部开启上拉,平时读到高电平,按下时引脚被拉低。如果单片机内部没有上拉,就要在按键并联一个 10k 上拉电阻。网上经常有人问“4×4 键盘用不用上拉电阻”,答案是看 MCU 内部是否有可配置上拉;对独立按键,外接上拉是最稳妥的习惯,避免引脚悬空造成误触发。
5. 软件架构与核心代码实现
5.1 软件总体架构:主循环加定时器中断
绿色风扇的代码不要写成一个大循环顺序到底。推荐采用“主循环做决策,定时器中断产生时间基准”的结构。主循环里时刻采集按键状态、读取温度、计算目标转速、刷新显示;定时器中断则负责维护一个时钟计数,用来做按键消抖计时、PWM 输出或者待机计时。
如果选 51 单片机,PWM 可以用软件方式生成:定时器中断每 100us 触发一次,定义一个pwm_period和一个pwm_duty变量。每次中断计数器加一,当计数器小于pwm_duty时输出高电平,否则输出低电平。计数器达到pwm_period后归零并重复。比如pwm_period = 100,pwm_duty = 70,就意味着占空比 70%。这个方法的优点是任何 IO 引脚都能输出 PWM,缺点是定时器中断服务程序执行频率高,不建议在主循环里做长延时或者耗时的屏幕刷新操作。
实际项目管理时,要把代码拆成几个独立模块:pwm.c负责电机控制,temp.c负责温度采样,key.c负责按键处理,display.c负责屏幕显示,main.c做系统顶层调度。不要把所有函数都堆在一个main.c里,否则后期排查问题时寸步难行。这不仅是代码风格问题,更是毕设文档里“软件设计”章节能否写清楚的关键。
5.2 引脚定义与初始化示例
下面是一个基于 51 单片机风格的 C 语言示例。这里以 STC89C52RC 为例,假设 PWM 输出引脚是 P2.0,按键接在 P1.0、P1.1、P1.2,NTC 通过外部 ADC 模块采集,或使用单片机内部比较器时做法略有差异。阅读时重点看结构,不必死抠芯片寄存器。
// 文件路径:src/main.h #ifndef __MAIN_H__ #define __MAIN_H__ #include <reg52.h> // 引脚定义 sbit MOTOR_PWM = P2^0; // 电机PWM输出引脚 sbit KEY_MODE = P1^0; // 模式切换键 sbit KEY_UP = P1^1; // 风速增加键 sbit KEY_DOWN = P1^2; // 风速降低键 // 运行模式 #define MODE_AUTO 0 #define MODE_MANUAL 1 // PWM参数 #define PWM_PERIOD 100 // PWM周期计数 #define PWM_HIGH 1 #define PWM_LOW 0 // 函数声明 void system_init(void); void timer0_init(void); void set_motor_duty(unsigned char duty); #endif这个头文件把系统里最重要的宏定义集中起来。后续要改引脚、改风速上限、改按键定义,都只需要改这一个文件,不需要在多个 C 文件里重复找。写程序和写论文文档一样,可维护性要摆在前面。
5.3 PID 风格调速还是查表调速
在绿色风扇控制中,如果你只做线性映射,也就是温度越高占空比越大,不需要 PID。因为风扇是一个开环控制对象:你给它多大的占空比,它就输出多大的转速,当前转速没有反馈给单片机。如果一定要做转速闭环,需要增加霍尔传感器或者编码器测量转速,然后通过 PID 调节占空比。那会显著增加难度,也会让毕设从“能跑”上升到“有控制深度”。
这里需要做一个取舍。一个稳妥的毕设方案是:档位显示仍然按低、中、高显示,但每个档位背后对应一个 PWM 占空比,自动模式下控制器根据温度选择合适占空比。比如当前温度 28 度,线性映射得到的占空比是 65%,控制器把它归到“中速偏高档”显示。这个方法既展示了你理解线性控制和 PWM,又不至于为了 PID 调参把整个项目拖进深坑。
代码上可以这样实现:
// 文件路径:src/speed_control.c // 根据当前温度计算PWM占空比 unsigned char calc_auto_duty(unsigned int temp_x10) { // temp_x10 表示温度乘以10,例如 26.5 度传入 265 #define T_MIN_X10 250 // 25.0摄氏度 #define T_MAX_X10 350 // 35.0摄氏度 #define DUTY_MIN 40 // 最低启动占空比 40% #define DUTY_MAX 90 // 最高输出占空比 90% if (temp_x10 <= T_MIN_X10) { return 0; } else if (temp_x10 >= T_MAX_X10) { return DUTY_MAX; } else { unsigned long delta = (unsigned long)(temp_x10 - T_MIN_X10) * (DUTY_MAX - DUTY_MIN); unsigned char duty = DUTY_MIN + (unsigned char)(delta / (T_MAX_X10 - T_MIN_X10)); return duty; } }这个函数的输入是温度值的 10 倍整数,输出是 PWM 占空比。这样处理可以避免 float 运算,51 单片机没有硬件浮点单元,用浮点算会拖慢主循环且占用大量 code 空间。把温度扩大 10 倍存储,是一种常用的定点数处理技巧。注意T_MIN_X10和T_MAX_X10是可调参数,你可以在代码里把 25 度调到 22 度,把 35 度调到 33 度,以适应不同实验环境。
下方调用它之前,你需要一个能返回整型温度值的函数。如果直接使用 NTC 加 ADC,温度计算要经过热力学公式,下面给出一种查表法实现思路。
5.4 NTC 温度采集与查表转换
NTC 的温度转换如果走 B 值公式,会用到自然对数函数,51 的数学库支持但会带来较大代码开销。更直观的方法是在代码中保存一张温度-ADC值映射表,然后通过线性插值得到当前温度。下面给出一个精简的映射思路:
// 文件路径:src/temp_table.c // 示例:ADC读数和温度对照表,实际值需要根据分压电阻与NTC型号标定 #define TABLE_SIZE 10 code unsigned int adc_table[TABLE_SIZE] = {100, 180, 260, 350, 450, 560, 680, 800, 860, 920}; code unsigned int temp_table[TABLE_SIZE] = { 50, 45, 40, 36, 32, 28, 25, 22, 20, 18 }; // 注意方向:温度越高,NTC阻值越小,ADC采样电压越低,所以ADC表从小往大排列时,温度表从高往低排列 unsigned int adc_to_temp(unsigned int adc_val) { unsigned char i; if (adc_val <= adc_table[0]) { return temp_table[0] * 10; } if (adc_val >= adc_table[TABLE_SIZE - 1]) { return temp_table[TABLE_SIZE - 1] * 10; } for (i = 0; i < TABLE_SIZE - 1; i++) { if (adc_val >= adc_table[i] && adc_val < adc_table[i + 1]) { unsigned int range = adc_table[i + 1] - adc_table[i]; unsigned int offset = adc_val - adc_table[i]; unsigned long temp_delta = (unsigned long)offset * (temp_table[i] - temp_table[i + 1]); unsigned int temp_result = temp_table[i] * 10 - (unsigned int)(temp_delta / range) * 10; return temp_result; } } return temp_table[TABLE_SIZE - 1] * 10; }这里用code关键字把常量表定义在程序存储区,不占宝贵的 RAM。表里的数据只是占位符,实际项目中你必须根据自己 NTC 型号和分压电阻,通过实测或者厂商曲线换算出来。比较好的办法是:先用电烙铁加热或冰水降温,读出不同温度下的 ADC 原始值,记录至少 6 组数据后更新到表里。这样做出的风扇测温会比较准,也不会在答辩时被老师拿一杯热水顶住探头,看到温度毫无变化。
这个查表插值函数虽然代码行数不多,但已经足够展示你对“模拟量标定、查表、线性插值”这些嵌入式基础概念的掌握。
5.5 PWM 输出与定时器中断
在 51 单片机里可以用定时器 0 做 100us 的定时中断,每次中断触发一次软件 PWM 状态更新。这个中断频率不宜太高。比如选择 100us 中断,PWM 周期是 100 个计数点,周期约 10ms,PWM 频率约 100Hz。对小型直流电机来说,100Hz 到 1kHz 都是常见范围,频率太低会听到明显噪声,太高会使软件中断负担过重。
// 文件路径:src/pwm_timer.c volatile unsigned char pwm_counter = 0; volatile unsigned char pwm_duty = 0; void timer0_init(void) { TMOD &= 0xF0; // 清空定时器0模式 TMOD |= 0x01; // 方式1:16位定时器 TH0 = 0xDB; // 约100us定时初值,具体需按晶振计算 TL0 = 0xFF; ET0 = 1; // 开启定时器0中断 EA = 1; // 开启总中断 TR0 = 1; // 启动定时器0 } void timer0_isr(void) interrupt 1 { TH0 = 0xDB; // 重装初值 TL0 = 0xFF; pwm_counter++; if (pwm_counter >= PWM_PERIOD) { pwm_counter = 0; } if (pwm_counter < pwm_duty) { MOTOR_PWM = PWM_HIGH; } else { MOTOR_PWM = PWM_LOW; } }这段代码里pwm_duty是全局变量,控制逻辑在main.c中不断更新它。只要pwm_duty的值在 0 到 PWM_PERIOD 之间,电机就会按对应占空比旋转。你可以手工把pwm_duty改成 30、60、90 三个值,观察电机转速是否有明显差别。如果没有差别,大概率是驱动电路没有工作在开关状态,而是被管子压降拖垮了。
5.6 主循环与按键状态机
主循环要做的事情很直接,判断按键并把模式切换到自动或者手动。按键处理最忌讳用长延时消抖,那会让你在长延时期间无法响应其他事件。更合理的方法是每隔 10ms 采样一次按键电平,连续两次读到相同状态才认为按键有效,把这个机制放在主循环状态机里。
// 文件路径:src/main.c 主循环逻辑片段 unsigned char run_mode = MODE_AUTO; unsigned char duty = 0; void update_system(void) { // 自动模式:读取温度并计算占空比 if (run_mode == MODE_AUTO) { unsigned int temp_x10 = ntc_read_temp_x10(); duty = calc_auto_duty(temp_x10); } set_motor_duty(duty); display_refresh(run_mode, duty, last_temp_x10); } void main(void) { system_init(); while (1) { key_scan(); if (key_mode_pressed()) { // 切换运行模式 if (run_mode == MODE_AUTO) { run_mode = MODE_MANUAL; } else { run_mode = MODE_AUTO; } } if (run_mode == MODE_MANUAL) { if (key_up_pressed() && duty < 90) { duty += 10; } if (key_down_pressed() && duty > 0) { duty -= 10; } } update_system(); } }这段代码体现了单片机系统最常见的“轮询 + 事件”模式。主循环不断扫描按键和更新输出,按键按下的效果通过改变全局状态来体现。整个程序没有一条阻塞型延时,所有实时任务都由定时器中断保证,因此系统响应按键时不会被慢速传感器拖住,传感器采样也能稳定进行。
6. 运行结果与效果验证
6.1 分模块验证顺序
毕设最常见的翻车方式,是做完整机才开始上电测试,结果所有问题同时出现,无法定位。正确做法是分模块验证。
第一步,烧录一个最简程序,让单片机引脚输出固定电平。用万用表量引脚电压,确认单片机最小系统没有虚焊或者晶振不起振。第二步,单独测试电机驱动。让 PWM 引脚输出固定高电平,观察电机是否转动;转动后断开,听是否有明显的反向冲击声,能感受到冲击声说明续流二极管可能没接好。第三步,测试温度采样。先在室温下读 ADC 原始值,再用手握住 NTC,看 ADC 读数是否明显变化。第四步,测试按键。在调试器里观察按键变量,按下时是否从高电平变低电平,松手后是否恢复。第五步,联调整个系统。把自动模式开启,模拟环境温度变化,用吹风机对着温度传感器吹热风,观察转速是否提升,读数是否稳定;再用空调冷风或者把传感器放到窗外,观察转速是否下降。
这个顺序的逻辑是先电源、后信号,先输出、后输入,先单元、后整机。只要每一步都留下实验记录,写进论文里就是相当完整的调试章节。
6.2 如何验证“绿色”指标
很多同学在答辩时只会说“我这个风扇比较省电”,但没有数据支撑。为了让绿色理念站得住,可以做一个简单的对比实验:相同的电机,在相同负载下,分别用电阻降压方式和 PWM 方式驱动到相同平均转速。用万用表电流档记录总电流,用红外测温枪测试电机表面温度,运行十分钟后对比两项数据。PWM 驱动通常会表现出更低的静态电流损耗,电机发热也相对不明显。这就是一个能写进报告里的实测结论。
同时记录待机电流。在代码中加入 30 秒无操作后关闭 OLED 背光,再在待机时让单片机进入低功耗模式,用万用表毫安档测整机电流。这个电流结果可以直接做进设计文档,用数字说明“绿色”的实现效果。需要注意的是,测量电流时万用表要串接在电源回路里,不要并接,否则会烧保险丝。
6.3 仿真与实物的区别
如果你用 Proteus 做仿真,经常能在软件里看到转速、温度这些变量变化,但一到实物就失灵。原因多半不是代码问题,而是硬件细节问题:NTC 分压计算时用了 Proteus 里理想的电压源,但实物里 ADC 参考电压随负载波动;Proteus 里电机是个理想模型,不需要续流二极管也能转,但实物里没有二极管就可能烧管子。所以仿真通过只能说明逻辑正确,并不意味着实物可以直接复现。比较稳妥的策略是,先搭建一个二极管、三极管、NTC、按键都齐备的最小硬件系统,再在实机上调参。仿真作为预习,实物作为最终验证。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 上电后单片机无反应 | 晶振未起振或复位电路异常 | 示波器或万用表测量复位引脚和晶振引脚电压 | 检查晶振电容、复位电阻电容,确认排针没有错位 |
| 程序烧录失败 | 下载线接线错误或电平不匹配 | 重新核对 TX/RX/GND 连接,查看下载软件提示 | 调整串口参数,冷启动下载方式要重新断电上电 |
| 电机不转但程序在跑 | 驱动管虚焊或电机供电被切断 | 万用表测量驱动管基极/栅极电压和电机两端电压 | 补焊,检查电机供电线,确认续流二极管极性正确 |
| 电机转速无法随占空比变化 | PWM 频率过高或驱动管未进入开关状态 | 用示波器查看 PWM 引脚波形,检查驱动管型号 | 降低 PWM 频率,更换更合适的 MOS 管,增加栅极驱动电阻 |
| 温度读数跳动很大 | ADC 采样未做滤波,或供电受电机干扰 | 在代码里多次采样取平均,观察不同供电状态读数 | 软件滑动滤波,电机供电和传感器供电分开 |
| 按键按下有时灵有时不灵 | 缺少消抖或引脚悬空 | 用调试器观察按键引脚电平变化 | 增加软件消抖,外接 10k 上拉电阻 |
| OLED 花屏或闪烁 | 电压不足或 I2C 线路受干扰 | 检查模块供电是否为稳定 3.3V/5V | OLED 电源线与电机线分开走,加 100nF 去耦电容 |
| 低功耗模式电流降不下来 | 外设未关闭或引脚状态不对 | 逐一断开外设,测量各模块电流 | 进入睡眠前关闭输出,把未用引脚设为推挽低或下拉 |
| 自动模式在 25 度附近频繁跳档 | 缺少滞回区间 | 打印温度和占空比变化曲线 | 增加启动温度和停止温度之间的回差 |
| OLED 显示的温度始终不变 | NTC 分压参数错误或 ADC 通道配置错误 | 先读取 ADC 原始值,确认随温度变化 | 检查分压电阻和换算表,重新标定 |
实际调试中,第一反应不要改代码,而是先把万用表和示波器架起来,判断问题是出在传感器端、控制端还是执行端。比如电机不转,你直接改代码里面的占空比,很可能改一百遍都没用。而你先量一下电机两端电压是不是 5V,这个问题直接就能定位到驱动电路。调试思维比写代码能力更影响项目进度。
8. 最佳实践与工程化建议
8.1 从时间片到任务调度
绿色风扇的功能并不复杂,但如果把待机计时、按键消抖、温度采样、显示刷新全堆在主循环里,时间一长,你会发现某几个功能互相“抢时间”。一个可行的改进方案是引入时间片轮询:用定时器产生 1ms 的时钟节拍,然后定义task_10ms、task_100ms、task_1s三个标志位。主循环每次判断这些标志位,决定是否执行对应任务。
这种写法有两个好处。第一,电机 PWM 不需要在主循环里频繁操作,由中断维持稳定输出。第二,像 NTC 采样这种需要等待 ADC 稳定或者多次取平均的操作,可以放在 100ms 任务里执行,避免拖慢系统响应。搜“单片机如何实现同时运行多个任务调度机制”的同学,本质上就是想解决这个问题。你不需要从一开始就学 RTOS,理解时间片轮询,已经足够应付绿色风扇这一类小系统。
8.2 代码规范与文档同步
毕业设计代码里,命名混乱是大忌。建议全局变量用带前缀的命名,比如g_motor_duty表示全局目标占空比,局部变量用短小明了的名称。每个函数前面写一行注释说明输入、输出和调用条件。宏定义统一放在头文件。不要写那种只有自己能看懂的“魔法数”,比如代码里直接出现if (a > 3000),三天后你回头看肯定不知道 3000 是什么含义。应该定义为#define TEMP_HIGH_ADC_VAL 3000。
写论文或者设计报告的时候,把程序结构图和模块划分图放在“软件设计”部分。并不是要求你必须画出复杂的流程图,而是要让人一眼看懂系统运行逻辑。主循环流程用文字加简单框图表达即可,重点是要能解释“如果没有定时器中断会发生什么”,这类问题往往比代码本身更考验理解。
8.3 安全边界与生产环境提醒
这道题的控制对象是小功率直流电机,看起来安全,但涉及硬件电路时仍然要注意三个问题。
第一,焊接和使用电源时,养成先断开电源再插拔杜邦线的习惯。避免带着电插拔线路,否则很容易导致单片机和传感器芯片损坏。第二,如果后续想扩展到 220V 交流风扇,绝对不要直接让单片机控制交流电。交流电机需要隔离、过零检测、可控硅或继电器等专门驱动方案,且实验环境必须有完善的电气保护。涉及强电的部分,文章所有电路逻辑都不再适用,需要重新设计。第三,如果未来做产品,必须在电机两端并联合适的吸收电路,使用阻燃外壳,并通过相关电气安全认证。毕业设计阶段你不需要考虑这些,但写总结时可以提一句,体现工程意识。
9. 从毕设到工程:这类设计的延伸思考
绿色风扇的本质是一个温控闭环系统,它背后的知识不止能用来做风扇。稍微改一改,它可以变成智能台灯亮度调节、大棚温度通风控制、机箱散热风扇控制器。真正有价值的是那套“传感器采集、控制算法、PWM 执行、低功耗管理”的骨架,而不是电机本身。
如果想让毕设继续往深处走,可以从两个方向扩展。第一个方向是增加转速反馈,通过编码器或者霍尔传感器检测实际转速,用 PID 或简单 ON-OFF 控制让转速更稳定。第二个方向是增加无线通信模块,比如通过蓝牙或者 Wi-Fi 模块把数据传到手机 App 或上位机,让用户远程查看温度和控制风扇。第二个方向会让整个系统的功能体量明显变大,但要注意这么做之后论文的核心可能会从“风扇控制”滑向“物联网应用”,你在选题时要提前确定主线。
还有一点建议放在结尾:毕设并不是代码越复杂越好,而是问题定义越清晰越好。把“绿色”先用三行文字说清楚,对应到哪些指标,用什么传感器,用什么算法,怎么做验证,整篇文档就有了骨架。硬件调试的时候,准备一本实物调试记录本,把每次出现的问题、原因和解决过程都记下来。这些内容写进论文的调试章节,能够显著提升真实感和完成度,远好过在最后一周临时补一堆空话。如果你正在准备这个题目,建议先花半天把系统框图和数据流图画在纸上,确认无误后再动手焊接和编写代码。等你实物能自动跟随温度调速,并把功耗数据测出来,这个题目基本就站稳了。