基于STC89C52的绿色智能风扇设计:从DS18B20到PWM调速
2026/9/22 5:44:55 网站建设 项目流程

各位做单片机毕业设计或者课程设计的同学,大家好。

临近毕业季,很多人在选题时会在网上找各种现成的项目资料。风扇控制类题目是单片机毕业设计里的经典方向,因为需求明确、容易出效果、答辩时也好讲。不过很多资料只有残缺代码或者效果图,不够完整。这篇文章围绕“绿色风扇的设计与实现”这个题目,从需求分析、硬件选型、电路设计、软件框架、代码实现到调试方法做一次系统梳理。

绿色风扇的“绿色”不只是颜色上的绿色,更重要的是节能与智能控制:风扇能根据环境温度自动调整转速,也可以手动切换档位,并且通过数码管或 LCD 实时显示当前温度与风扇档位。这样既能降低不必要的电能消耗,又能体现“单片机 + 传感器 + 执行机构 + 人机交互”的完整嵌入式系统设计思路。

本文面向两类读者。第一类是正在准备单片机毕设的本科生,第二类是希望掌握 51 单片机项目完整开发流程的电子爱好者。读完后你应该能画出系统框图、读懂或写出基于 STC89C52 的风扇控制代码、在开发板上完成基本调试,并能在答辩中解释各个模块的工作原理。

1. 背景与核心概念

1.1 什么是绿色智能风扇

传统风扇使用机械开关,只有“开”和“关”两个状态,不能根据温度自动调节,也无法查看当前环境温度。所谓绿色智能风扇,核心是引入单片机作为控制中心,通过温度传感器采集环境数据,再由单片机决定风扇的开关与转速,最终达到“需要多少风就吹多少风”的节能目的。

从自动控制角度看,这是一个典型的开环或半闭环控制系统:温度传感器作为输入环节,单片机作为运算控制环节,电机驱动与直流电机作为执行环节,按键与显示器作为人机交互环节。

1.2 主控芯片为什么选 STC89C52

STC89C52 是 51 内核单片机中非常普及的一类芯片,在毕业设计中使用率很高。选择它主要有几个原因:

  1. 引脚与标准 51 兼容,参考资料多,遇到问题容易搜索解决。
  2. 内部含 8KB Flash 程序存储器和 512 字节 RAM,对于风扇控制这种规模的程序完全足够。
  3. 支持 ISP 下载,不用专门的编程器,用 USB 转 TTL 模块就能烧录程序。
  4. 价格便宜,开发板或自制最小系统板成本低。

当然,如果后续想扩展 WiFi、蓝牙或更复杂的 GUI,可以考虑 STM32 系列。但就“绿色风扇”这个毕业设计题目而言,51 单片机已经能把知识点讲透,答辩时也不会因为芯片过于复杂而难以解释。

1.3 温度传感器 DS18B20 的工作特点

DS18B20 是最常见的单总线数字温度传感器。所谓单总线,就是数据读写和通信都通过一根 I/O 口线完成。

它有几点非常适合毕业设计:

  • 测量范围 -55℃ 到 +125℃,日常环境温度检测足够。
  • 12 位分辨率时温度精度为 0.0625℃,可以显示小数。
  • 每个芯片有唯一 64 位序列号,理论上一条总线可以挂多个传感器。
  • 直接输出数字信号,不需要 ADC 模块。

DS18B20 编程的关键在于严格的时序控制。初始化时序、读时序、写时序,每一步都需要若干微秒级延时。如果用 STC89C52 的 12MHz 晶振,一个机器周期是 1μs,代码里的延时函数比较直观。

1.4 为什么需要电机驱动电路

单片机 I/O 口输出能力有限。STC89C52 的 I/O 口在准双向模式下拉电流和灌电流能力都不大。普通直流电机启动电流可能达到几百毫安甚至更大,如果直接把电机接到单片机引脚上,轻则无法启动,重则烧毁引脚。

因此需要通过驱动电路或驱动芯片来放大控制信号。常见方案有三种:

  1. 三极管开关电路,只适合控制风扇开或关,不能调速。
  2. L298N 电机驱动模块,可以控制直流电机正反转和 PWM 调速。
  3. ULN2003 达林顿管驱动,适合驱动小功率电机或步进电机。

绿色风扇如果只需要正转无级调速,最简单的方案是使用 NPN 三极管或 MOSFET,让单片机输出 PWM 信号,通过功率管控制电机电源通断。但为了让论文和答辩内容更充实,也可以使用 L298N 模块,把 PWM 接到使能端,通过 I/O 口控制转速。

下面硬件设计中会继续比较不同方案的差异。

2. 环境准备与版本说明

2.1 开发工具清单

编写和烧录单片机程序需要准备以下工具:

  • Keil C51 开发环境,用于编写 C 语言程序并生成 HEX 文件。不同系统版本界面略有差异,但 51 工程配置基本一致。
  • STC-ISP 下载软件,用于把 HEX 烧录进 STC89C52。
  • 硬件:单片机最小系统板或开发板、DS18B20 模块、LCD1602/数码管、直流电机与驱动模块、独立按键、USB 转 TTL 下载器。
  • 如果做仿真验证,可以使用 Proteus 完成原理图和仿真。

版本提示:

Keil C51 与 Keil MDK 是不同产品。单片机毕业设计用到的是 Keil C51,而不是 MDK。安装完成后打开 Keil,如果新建工程时能看到 Atmel、STC、NXP 等 51 内核厂商前缀,说明是 C51 版本。如果只有 ARM 系列,说明装成了 MDK。

2.2 工程配置要点

在 Keil 中新建工程后,需要从芯片列表里选择对应型号。如果安装的 Keil 没有 STC 选项,可以先选择 Atmel 的 AT89C52,也可以安装 STC 官方插件。两者的代码基本兼容,差别主要体现在一些特殊功能寄存器上。本设计使用的 STC89C52 与 AT89C52 在定时器、中断、串口等模块的使用方法上相同。

新建工程时有几个容易忽略的选项:

  • 勾选 “Create HEX File” 或等价选项,否则编译后不会生成 HEX 文件。
  • 单片机型号选择、晶振频率设置要与实际硬件一致。代码中的延时依靠定时器时,晶振频率特别重要。
  • 代码文件要加入工程中,否则 Keil 不会参与编译。

2.3 本文示例约定

为了方便说明,下面的电路和代码以 STC89C52 + DS18B20 + L298N + LCD1602 + 独立按键为例。所有代码在 C 语言环境下编写,重点不是直接抄到板上就能跑,而是理解每个模块的接口和逻辑。

实际做毕设时,你手里的开发板引脚可能和本文不同。改代码前,建议先画出引脚分配表:

模块单片机引脚说明
DS18B20 数据线P3.7单总线通信
LCD1602 RSP2.0寄存器选择
LCD1602 RWP2.1读写选择
LCD1602 ENP2.2使能信号
LCD1602 D4-D7P2.4-P2.74 位数据线
按键 K1P1.0档位切换/开关
L298N ENAP1.1PWM 调速
L298N IN1P1.2电机控制
L298N IN2P1.3电机控制
风扇转速反馈/预留P3.2可选

3. 系统总体设计

3.1 设计目标与功能需求

绿色风扇系统重点实现以下功能:

  1. 温度检测。使用 DS18B20 实时采集环境温度,并通过 LCD1602 显示。
  2. 档位控制。提供手动模式和自动模式。手动模式由按键切换风扇停止、低速、中速、高速四个档位。
  3. 自动恒温控制。自动模式下,系统根据设定温度与实际环境温度差值自动选择 PWM 占空比。
  4. 状态指示。LCD1602 的第一行显示温度,第二行显示当前档位或模式。
  5. 节能逻辑。环境温度低于设定下限时,风扇自动停止;温度越高,转速越高。

从绿色与节能角度讲,自动模式比单纯手动更有说服力。如果答辩被问到“哪里体现了绿色”,可以这样回答:普通风扇无论室温高低都保持恒定档位,而本系统根据实际温度需求动态调整 PWM 占空比,避免无效运行,从长期使用看可以达到节能效果。

3.2 系统总体框图

系统的工作流程如下:

温度传感器 DS18B20 ---> STC89C52 单片机 ---> LCD1602 显示 ^ 按键输入 / 档位/模式设置 | v L298N 电机驱动 | v 直流风扇电机

单片机是核心节点。它一方面周期性读取 DS18B20 温度值,另一方面扫描按键输入。当用户按键后,系统根据设定改变运行模式或档位。LCD1602 显示温度和当前状态。如果是自动模式,单片机结合温度数据计算 PWM 占空比,并输出到电机驱动模块。

3.3 工作模式设计

为了让系统更有层次,建议将软件设计为两种模式。

手动模式:

  • 按键 K1 用作开关机。
  • 按键 K2 每按下一次,档位从停止到低、中、高循环。
  • 档位与 PWM 占空比直接对应,例如 0%、40%、70%、100%。

自动模式:

  • 按键 K3 切换手动与自动。
  • 温度低于 20℃ 时风扇停止。
  • 温度在 20℃ 到 30℃ 之间时,按温度高低计算 PWM 占空比。
  • 温度高于 30℃ 时风扇全速运行。

为了简化按键程序,还可以只使用一个按键长按/短按实现不同功能。比如短按 K1 切换档位,长按 K1 切换自动与手动模式。不过短按长按逻辑较复杂,对于毕业设计答辩,使用多个独立按键更直观。

4. 硬件设计详解

4.1 单片机最小系统

STC89C52 的最小系统包括电源、晶振电路、复位电路和下载电路。

电源部分:

  • 系统供电一般用 5V USB 电源。
  • DS18B20 与单片机可以直接共用 5V。
  • 如果使用 L298N 模块,不同模块的电源要求不同。常见的 L298N 模块有 12V 供电的版本,电机可以接 12V,而逻辑部分可以由板上 5V 稳压输出供电。
  • 在给电机单独供电时,要注意 GND 必须与单片机 GND 连在一起,否则驱动信号没有参考地,电机无法正常工作。

晶振电路:

  • 本设计使用 12MHz 晶振,两个 30pF 电容分别接到晶振两端,并连接到地。
  • 如果代码中有延时函数,注意晶振频率与实际要一致。例如 11.0592MHz 是为了得到精确波特率,12MHz 更常用于简单延时计算。

复位电路:

  • 10μF 电解电容与 10kΩ 电阻构成上电复位电路,复位引脚 RST 接高电平至少两个机器周期即可复位。
  • 实际开发板通常还会接按键复位,方便在程序跑飞时手工复位。

下载电路:

  • STC89C52 的串口下载需要把 USB 转 TTL 模块的 TXD 接到单片机 RXD,TXD 接 RXD,也就是交叉连接。

4.2 温度传感器电路

DS18B20 的三个引脚分别是 VDD、DQ、GND。DQ 是数据线,必须接一个 4.7kΩ 上拉电阻到 VDD。上拉电阻的作用是确保数据总线在空闲时为高电平,因为单总线协议要求释放总线时由外部上拉电阻把电平拉高。

接线如下:

DS18B20 引脚连接
VDD5V
DQP3.7
GNDGND
上拉电阻DQ 到 5V,4.7kΩ

如果使用寄生供电方式,可以把 VDD 直接接地,靠 DQ 信号线上的高电平时段为传感器提供能量。不过为了方便调试,推荐外部供电。

4.3 电机驱动方案选择

在绿色风扇中,电机驱动电路是一个非常关键的区分点。如果用一个简单的 NPN 三极管驱动小功率直流电机,只能通断,不能调速。若想实现低速、中速、高速的调节,需要 PWM 调速。

本设计使用 L298N 驱动模块。L298N 是一颗双 H 桥驱动芯片,能够驱动两个直流电机或一个步进电机。模块上通常有 12V 电源输入、5V 输出、使能端 ENA/ENB、逻辑输入 IN1-IN4。

当只驱动一个风扇电机时:

  • 电机正极接 OUT1,负极接 OUT2。
  • IN1 = 1,IN2 = 0 时电机正转。
  • IN1 = 0,IN2 = 1 时电机反转。
  • 如果两个输入相同,比如同时为 0,电机制动。
  • ENA 接单片机 PWM 引脚,通过 PWM 占空比控制平均电压,从而调速。

这种方案可以回答“为什么不用继电器”的问题:继电器只能频繁通断,无法连续调速,且触点寿命有限。H 桥驱动模块支持 PWM,控制更平滑。

4.4 显示电路

毕业设计中显示方案常见两种:LCD1602 和数码管。

LCD1602 优点是能显示字母、数字与简单符号,适合显示 “Temp: 25.5C” 这样的信息。缺点是接线较多、代码初始化复杂。

数码管优点是显示亮度高、代码简单、教学效果好,缺点是无法显示完整单词。

推荐使用 LCD1602,因为它能让自动模式和温度显示更直观。如果学校要求较低或想减少代码量,也可以使用 4 位数码管显示温度。

LCD1602 采用 4 位数据接线方式时,只需要 RS、RW、EN、D4-D7 共 7 根信号线,程序可以写精简版驱动。这种方式不仅省引脚,也可以增加代码的可讲解性。

4.5 按键输入电路

独立按键电路很简单,按键一端接地,另一端接单片机引脚,再接一个上拉电阻到 VCC。按键未按下时引脚读到高电平,按下后引脚接地,读到低电平。

如果开发板内部 I/O 已经具备弱上拉,可以省去外部上拉电阻。不过采用 STC89C52 时,要先把对应引脚配置成准双向 I/O 口。默认的 P1 口上电后为准双向模式,外部按键按下低电平是能够识别的。

按键需要软件消抖。因为机械按键在按下与释放过程中会产生几毫秒到十几毫秒的抖动,如果不对电平变化进行延时判断,一次按键可能被识别成多次。

5. 软件设计详解

5.1 软件整体框架

单片机软件通常采用循环扫描结构。其核心思想是:

  1. 系统上电后初始化各模块。
  2. 主循环中不断执行温度读取、按键扫描、显示刷新、输出控制。
  3. 所有任务分时执行,确保温度、按键、显示能同时响应。

主程序流程如下:

系统上电 -> 初始化LCD、定时器、DS18B20、PWM -> 读取保存在变量里的默认温度和模式 -> 进入主循环 -> 每隔一段时间读取DS18B20温度 -> 扫描按键,更新模式/档位 -> 刷新LCD显示 -> 根据模式/温度输出PWM控制电机 -> 回到主循环开头

这里有一个重要概念:单片机主循环是“顺序执行”的。如果 DS18B20 读取时的延时很长,会导致按键响应变慢。因此可以考虑把按键扫描放在主循环中,温度转换通过定时器节拍管理。

5.2 定时器与延时

DS18B20 的读写需要微秒级延时。在 12MHz 晶振下,可以写一个简单的基于循环的延时函数。

void delay_us(unsigned int us) { while (us--) { _nop_(); } }

_nop_()是 Keil C51 自带的空操作指令,执行一次约 1μs。这里要说明,函数调用本身也有时间开销,所以这个函数只是近似延时,实际用起来只要符合 DS18B20 时序要求即可。更严谨的方法是使用定时器做时间基准,但在 DS18B20 单总线时序中,只要延时误差不超过允许范围,循环延时足够。

PWM 波形的产生可以使用定时器中断。定时器每 100μs 产生一次中断,中断服务程序里维护一个计数器。当计数值小于当前占空比比较值时,PWM 引脚输出高电平,否则输出低电平。这就是软件 PWM 的基本思路。

下面是一个简易的 PWM 模板。这里采用定时器 0 工作方式 1,16 位定时器,每次溢出重新装载计数初值。

// 假设使用 12MHz 晶振,定时 100us // 定时器初值需要根据晶振和定时时长计算 volatile unsigned char pwm_counter = 0; volatile unsigned char pwm_compare = 0; sbit PWM_OUT = P1^1; void timer0_init(void) { TMOD &= 0xF0; TMOD |= 0x01; // 定时器0,方式1 TH0 = 0xFF; TL0 = 0x9C; // 100us 定时初值,实际按晶振修正 ET0 = 1; EA = 1; TR0 = 1; } void timer0_isr(void) interrupt 1 { TH0 = 0xFF; TL0 = 0x9C; pwm_counter++; if (pwm_counter >= 100) { pwm_counter = 0; } if (pwm_counter < pwm_compare) { PWM_OUT = 1; } else { PWM_OUT = 0; } }

上述代码中pwm_compare取值范围从 0 到 99。pwm_compare = 0对应 0% 占空比,pwm_compare = 99对应接近 100% 占空比。通过修改pwm_compare的值,就可以控制电机转速。

需要注意的是,不同 STC 单片机的定时器初值计算是一样,但如果晶振是 11.0592MHz,同样的初值定时时间会略有不同。实际项目中可以先把初值写在注释里,再根据现象微调。

5.3 DS18B20 驱动代码

DS18B20 的驱动是很多同学的难点。下面给出核心函数框架。

sbit DS18B20_DQ = P3^7; bit ds18b20_reset(void) { bit presence; DS18B20_DQ = 0; // 主机拉低总线 delay_us(480); // 至少 480us DS18B20_DQ = 1; // 释放总线 delay_us(60); // 等待从设备应答 presence = DS18B20_DQ; // 读应答信号 delay_us(420); return presence; // 0 表示存在设备,1 表示无设备 }

这里返回值写法有讲究。通常约定函数返回 0 表示检测到器件。为了方便理解,可以这样写:

bit ds18b20_check(void) { bit flag; DS18B20_DQ = 0; delay_us(480); DS18B20_DQ = 1; delay_us(60); flag = DS18B20_DQ; delay_us(420); return flag; }

调用时,如果flag == 0,说明 DS18B20 存在。这个逻辑在硬件连接异常时要重点检查:如果数据线没接好或者没加上拉电阻,总线会一直保持低电平,程序可能误判为“一直有设备”。

写时序与读时序核心代码如下:

void ds18b20_write_bit(bit b) { DS18B20_DQ = 0; delay_us(2); DS18B20_DQ = b; delay_us(60); DS18B20_DQ = 1; delay_us(2); } bit ds18b20_read_bit(void) { bit b; DS18B20_DQ = 0; delay_us(2); DS18B20_DQ = 1; delay_us(2); b = DS18B20_DQ; delay_us(60); return b; }

读写一个字节时,需要循环 8 次,注意先从低位开始。

void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { ds18b20_write_bit(dat & 0x01); dat >>= 1; } } unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; if (ds18b20_read_bit()) { dat |= 0x80; } } return dat; }

DS18B20 启动温度转换并读取温度的完整步骤是:

  1. 复位。
  2. 发送跳过 ROM 指令 0xCC。
  3. 发送启动温度转换指令 0x44。
  4. 等待转换完成,通常需要 750ms(12 位分辨率)。
  5. 再复位。
  6. 发送跳过 ROM 指令 0xCC。
  7. 发送读暂存器指令 0xBE。
  8. 读取两个字节:温度低字节和温度高字节。

如果不想等待 750ms,可以使用 9 位分辨率,对应转换时间更短,但温度精度会降低。本设计为了显示小数温度,使用 12 位分辨率,读取间隔设为 800ms 以上。

温度数据转换成实际温度值的方法:

温度寄存器为 16 位,其中高 5 位是符号扩展位:

int temp = (raw_high << 8) | raw_low; if (temp & 0x8000) { temp = ~temp + 1; // 温度为负 } temperature_int = temp >> 4; // 整数部分 temperature_frac = (temp & 0x0F) * 625; // 小数部分,单位 0.0001℃

只讲重点:DS18B20 的 12 位分辨率下,最低位代表 0.0625℃。为了显示 25.5℃ 这种值,可以取出低 4 位并乘 625,再显示为小数点后两位或一位。

5.4 LCD1602 驱动代码

LCD1602 可以使用 8 位接口,也可以使用 4 位接口。4 位接口连线更少,代码稍复杂一点。这里只给出初始化与显示的核心函数。

sbit LCD_RS = P2^0; sbit LCD_RW = P2^1; sbit LCD_EN = P2^2; #define LCD_DATA P2

4 位模式下,数据线连接到 P2.4-P2.7。因此向 LCD 发送数据时,需要把数据的高 4 位放到引脚的高 4 位。

初始化过程的关键是延时。LCD1602 上电后需要等待内部初始化,如果延时不够,命令可能不生效。

void lcd_write_cmd(unsigned char cmd) { LCD_RS = 0; LCD_RW = 0; LCD_EN = 0; // 发送高四位 P2 = (P2 & 0x0F) | (cmd & 0xF0); LCD_EN = 1; delay_us(5); LCD_EN = 0; // 发送低四位 P2 = (P2 & 0x0F) | ((cmd << 4) & 0xF0); LCD_EN = 1; delay_us(5); LCD_EN = 0; delay_ms(2); }

写数据函数与写命令类似,只需要把 RS 置 1。

void lcd_write_data(unsigned char dat) { LCD_RS = 1; LCD_RW = 0; LCD_EN = 0; P2 = (P2 & 0x0F) | (dat & 0xF0); LCD_EN = 1; delay_us(5); LCD_EN = 0; P2 = (P2 & 0x0F) | ((dat << 4) & 0xF0); LCD_EN = 1; delay_us(5); LCD_EN = 0; delay_ms(2); }

这里有一个常见问题:P2 口既用于 LCD 控制引脚,也用于数据总线。如果 RS、RW、EN 连接了 P2.0-P2.2,那么向 P2 写数据时低 3 位会被覆盖,从而产生冲突。因此,很多 51 开发板会用 P0 或单独的并口接 LCD 数据线。本文示例为了说明原理,将数据线放在高四位,代码中通过P2 = (P2 & 0x0F) | ...尽量保留低 4 位状态。实际项目中应根据自己的连接方式修改宏定义与数据发送函数,不要盲目照抄引脚。

LCD1602 初始化命令建议:

void lcd_init(void) { delay_ms(15); lcd_write_cmd(0x03); delay_ms(5); lcd_write_cmd(0x03); delay_ms(5); lcd_write_cmd(0x03); delay_us(100); lcd_write_cmd(0x02); // 设置为 4 位模式 lcd_write_cmd(0x28); // 2 行,5x7 点阵,4 位数据 lcd_write_cmd(0x0C); // 显示开,光标关,闪烁关 lcd_write_cmd(0x06); // 写入后光标右移 lcd_write_cmd(0x01); // 清屏 delay_ms(2); }

如果你手头用的是 ALIENTEK 或其他开发板,LCD 数据线可能是 P0 口,需要使用 P0 作为数据总线。代码调整时只改LCD_DATA和引脚宏即可。

5.5 主函数逻辑

主函数需要调用模块初始化,并在循环中处理业务逻辑。下面给出一个简化示例:

void main(void) { unsigned char mode = 0; // 0 手动,1 自动 unsigned char level = 0; // 0 停止,1 低速,2 中速,3 高速 unsigned int temperature_x10 = 250; // 保存 25.0 度 bit key_flag = 0; timer0_init(); lcd_init(); ds18b20_init(); while (1) { // 1. 读取温度,放在主循环执行 temperature_x10 = ds18b20_read_temp_x10(); // 2. 按键扫描 key_flag = scan_key(); if (key_flag == KEY1_PRESSED) { if (mode == 0) { level++; if (level > 3) { level = 0; } } } else if (key_flag == KEY2_PRESSED) { mode = !mode; } // 3. 根据模式设置比较值 if (mode == 1) { // 自动模式:根据温度计算 if (temperature_x10 < 200) { // 低于 20.0℃ pwm_compare = 0; } else if (temperature_x10 > 300) { // 高于 30.0℃ pwm_compare = 99; } else { pwm_compare = (unsigned char)((temperature_x10 - 200) * 99 / 100); } } else { // 手动模式:按档位设置 if (level == 0) { pwm_compare = 0; } else if (level == 1) { pwm_compare = 35; } else if (level == 2) { pwm_compare = 70; } else { pwm_compare = 99; } } // 4. 更新显示 lcd_show_temperature(temperature_x10); lcd_show_mode_and_level(mode, level); // 5. 简单分时:读取温度后等待,避免过于频繁刷新 delay_ms(200); } }

注意:DS18B20 完整转换本身约需 750ms,所以实际读取函数内部会有一个较长等待。如果主循环里 200ms 延时太短,会造成读取函数返回的温度值更新不及时。更合理的做法是:

  1. 周期性启动转换。
  2. 主循环继续执行其他任务。
  3. 切换标志位判断是否读取完成。

不过对于课程设计,简单阻塞式读取也能工作,只要按键扫描对时序不敏感即可。为了提升用户体验,建议把温度读取的间隔控制到 1s 左右,避免 LCD 频繁刷新闪烁。

6. 按键控制与防抖实现

6.1 独立按键扫描

独立按键一般按下为低电平。软件扫描步骤:

  1. 检测到引脚为低电平,说明可能有按键按下。
  2. 延时 10ms 到 20ms。
  3. 再次检测引脚电平,如果仍然为低电平,则认为确实按下。
  4. 等待按键释放,如果不等待释放,一次按键可能被误判为多次触发。
unsigned char key_scan(void) { if (KEY1 == 0) { delay_ms(15); if (KEY1 == 0) { while (KEY1 == 0); return KEY1_PRESSED; } } if (KEY2 == 0) { delay_ms(15); if (KEY2 == 0) { while (KEY2 == 0); return KEY2_PRESSED; } } return KEY_NONE; }

这种扫描方式编写简单,缺点是在while等待释放期间,单片机不能执行其他任务。如果用户一直按住按键,温度采集就会暂停。实际项目中,很多代码用标志位的方式做非阻塞扫描,但作为毕业设计,阻塞式更能讲清楚原理。

6.2 长短按键扩展

如果板子按键数量有限,可以采用一个按键短按和长按实现两个功能。

设计思路:

  • 按键按下后记录时间。
  • 如果在 500ms 内释放,属于短按。
  • 如果超过 800ms 后释放,属于长按。

使用定时器中断计时,把按键状态机放在中断中比较合适。中断中不宜做长延时,只记录时间标志,主循环再根据标志执行操作。

伪代码如下:

// 定时器每 1ms 中断一次 if (KEY1 == 0) { if (key_press_start == 0) { key_press_start = 1; key_timer = 0; } } else { if (key_press_start == 1) { if (key_timer < 500) { short_key_flag = 1; } else { long_key_flag = 1; } key_press_start = 0; } }

这个技巧在答辩中容易加分,因为它展示了“模块化思维”和“对用户体验的考虑”。

7. 下拉框:如何生成 PWM 控制电机

7.1 软件 PWM 的占空比计算

产生 PWM 的本质是在固定周期内让引脚输出高电平时长不同。周期固定,占空比高,则平均电压高,电机转速快。

本设计设定 PWM 周期为 100 个计数单位,即占空比调节精度为 1%。例如:

  • 占空比 0%:整个周期都输出低电平。
  • 占空比 35%:一个周期内 35 个单位输出高电平,65 个单位输出低电平。
  • 占空比 100%:整个周期都输出高电平。

7.2 L298N 使能端接法说明

使用 L298N 驱动模块时,PWM 信号不是接在 IN1 或 IN2 上,而是接在使能端 ENA 上。IN1 和 IN2 只决定方向。

IN1 = 1, IN2 = 0, ENA = PWM -> 正转调速 IN1 = 0, IN2 = 1, ENA = PWM -> 反转调速 IN1 = 0, IN2 = 0, ENA = PWM -> 制动

电机启动瞬间电流较大。如果 PWM 占空比很小,可能电压不足导致电机无法启动。因此有的设计会在启动时先输出一个较高占空比,持续几十毫秒后再回到目标值。这就是“软启动”思想,也能减少启动电流对电源的冲击。

7.3 换挡保护

风扇高速运行时直接切到 0% 是正常行为。但如果要从高速切到低速,不必特殊处理。在 L298N 驱动下,只要 PWM 占空比下降,电机就会减速。部分工业场景需要通过先制动再反向启动来快速停车,但本设计不需要。

8. 完整代码整合与仿真演示

8.1 项目文件结构

在 Keil 中建议把代码按文件拆分,而不是全部写在一个 main.c 里。这种习惯在答辩时也更容易讲解。一种常见的结构如下:

green_fan/ ├── main.c ├── delay.c ├── delay.h ├── ds18b20.c ├── ds18b20.h ├── lcd1602.c ├── lcd1602.h ├── key.c ├── key.h └── pwm.c └── pwm.h

如果时间紧张,把所有代码放在 main.c 也可以完成硬件配置。但多文件结构更符合工程化要求。

8.2 main.c 完整示例

这里给出一个可编译的框架代码,便于理解模块调用关系。由于每个人 LCD 引脚不同,下面的代码把 LCD 数据发送部分融入了主要逻辑,实际项目需要根据硬件连接调整。

#include <reg52.h> #include "delay.h" #include "ds18b20.h" #include "lcd1602.h" #include "key.h" #include "pwm.h" sbit L298N_IN1 = P1^2; sbit L298N_IN2 = P1^3; void motor_stop(void) { L298N_IN1 = 0; L298N_IN2 = 0; } void motor_forward(void) { L298N_IN1 = 1; L298N_IN2 = 0; } bit key_manual = 0; // 0手动模式,1自动模式 unsigned char fan_level = 0; unsigned int temp_value = 250; void show_temp_on_lcd(unsigned int temp_x10) { unsigned char str[16]; sprintf(str, "Temp: %d.%d C", temp_x10 / 10, temp_x10 % 10); lcd_show_string(0, 0, str); } void show_status_on_lcd(bit mode, unsigned char level) { unsigned char str[16]; if (mode == 0) { switch (level) { case 0: sprintf(str, "Mode:Manual STOP "); break; case 1: sprintf(str, "Mode:Manual LOW "); break; case 2: sprintf(str, "Mode:Manual MID "); break; default: sprintf(str, "Mode:Manual HIGH "); break; } } else { sprintf(str, "Mode:Auto RUN "); } lcd_show_string(1, 0, str); } void main(void) { unsigned char keycode; timer0_init(); lcd_init(); while (1) { temp_value = ds18b20_get_temperature_x10(); keycode = key_scan(); if (keycode == KEY1_PRESS) { if (key_manual == 0) { fan_level++; if (fan_level > 3) { fan_level = 0; } } } else if (keycode == KEY2_PRESS) { key_manual = !key_manual; } if (key_manual == 1) { if (temp_value < 200) { set_pwm_duty(0); motor_stop(); } else if (temp_value >= 300) { set_pwm_duty(99); motor_forward(); } else { unsigned char duty; duty = (unsigned char)((temp_value - 200) * 99 / 100); set_pwm_duty(duty); motor_forward(); } } else { switch (fan_level) { case 0: set_pwm_duty(0); motor_stop(); break; case 1: set_pwm_duty(35); motor_forward(); break; case 2: set_pwm_duty(70); motor_forward(); break; case 3: set_pwm_duty(99); motor_forward(); break; default: break; } } show_temp_on_lcd(temp_value); show_status_on_lcd(key_manual, fan_level); delay_ms(300); } }

这段伪代码适合先自己理解,再结合手头开发板修改。它没有把所有头文件的实现细节列出来,不是完整的可复制工程,但已经把处理流程讲清楚。读者需要根据自己选择的 LCD 函数名、按键宏定义进行调整。

8.3 Proteus 仿真思路

没有硬件时,很多毕业设计会用 Proteus 仿真。搭建电路的思路如下:

  1. 放置 STC89C52 或 AT89C52。
  2. 放置 DS18B20,注意数据线上拉电阻 4.7kΩ。
  3. 放置 LCD1602,连接好 RS、RW、EN、D0-D7 或 D4-D7。
  4. 放置直流电机和 L298N 模块,Proteus 中的 L298N 在搜索栏中可以直接找到。
  5. 添加独立按键和必要的排阻。

仿真中容易犯的错误:

  • 没有连接共地端。
  • 忘记给单片机接晶振和复位电路。
  • DS18B20 在仿真中显示 25℃ 或其他固定值,但读取失败时显示 85℃。85 这个数值在 DS18B20 中比较特殊,通常表示上电后的默认值或读取失败。
  • L298N 的逻辑电源电压设置不对,导致电机无法驱动。

8.4 预期运行效果

正常运行时,LCD1602 第一行显示类似:

Temp: 25.5 C

第二行显示:

Mode:Manual LOW

自动模式下,如果用手靠近 DS18B20 让温度升高,风扇转速会逐渐增加。手动模式下,按 K1 可以循环切换停止、低、中、高。模式切换按键 K2 在手动与自动之间来回切换。

如果把环境温度降到设定下限以下,自动模式会关闭风扇,这是体现节能逻辑的重要场景。可以用空调环境或加热器件辅助演示。

9. 常见问题与排查思路

9.1 温度显示为 85℃ 或 0℃

问题现象常见原因解决思路
显示 85℃DS18B20 通信失败,读到上电默认值检查接线、上拉电阻、时序延时
显示 0℃初始化时序失败,或检测函数逻辑有误检查复位函数,测量 DQ 引脚静态电平
温度不变化没有等待温度转换完成启动转换后延时 750ms 再读
温度波动大电源噪声或接线过长在电源附近加 104 电容,缩短连接线

排查顺序:

  1. 用万用表测 DS18B20 VDD 与 GND 是否 5V 左右。
  2. 测 DQ 引脚静态是否被上拉到高电平。
  3. 代码中在初始化时连续读两次温度,看是否稳定。
  4. 使用逻辑分析仪观察波形,条件不足时可在不同延时值下多次测试。

9.2 LCD1602 显示乱码或不显示

问题现象常见原因解决思路
液晶有背光无字符对比度电位器未调好调节 VL 引脚电位器
显示乱码初始化时序不对按要求发送 0x30/0x20 等初始化命令
只有第一行正常未正确设置两行模式检查 0x28 或 0x38 命令
字符闪烁刷新频率太高增大主循环延时,避免频繁清屏

LCD1602 的 VO 引脚需要接一个可调电阻到 VCC,用来调节对比度。如果对比度调太高,会出现方块;调太低,字符看不清。很多毕设失败其实不是代码问题,而是电位器没有调好。

9.3 电机不转或转速不可调

问题现象常见原因解决思路
电机完全不转电源功率不足单独给电机供电
电机不转但 IN 引脚有电位使能端未接 PWM检查 ENA 连接
电机只有高速不调速PWM 频率太高导致波形失真降低 PWM 频率或调整定时器初值
电机转动时电压跌落共地不良检查 GND 连接

驱动 L298N 时注意,模块的 5V 输出一般用于逻辑电源。如果同时要供电给单片机,需要确保模块输入电压范围合适,且电流足够。否则建议分开供电,但共地必须建立。

9.4 按键失灵或跳档

按键失灵常见原因是消抖时间不够、引脚被占用、或者程序在按键等待释放时卡住。如果使用外部中断读取按键,还需要配置触发方式。

避免方法:

  • 消抖延时 10ms 以上。
  • 按键引脚不要与 PWM 输出引脚冲突。
  • 使用状态标志位,防止按下时重复执行。

10. 最佳实践与工程建议

10.1 硬件设计方面

电源设计是风扇系统最容易出问题的地方。不要以为 USB 5V 电源就能带动所有 L298N 模块。许多 L298N 模块内部带有 5V 稳压,但使用 12V 或更高电压供电时,模块本体发热严重,需要足够散热。

在电机两端反向并联一个续流二极管,可以吸收电机换向或断电时产生的反向电动势,这对保护驱动芯片非常重要。如果驱动板上已经集成,就不必额外添加。这个细节在答辩中非常容易成为加分点,因为它体现了工程安全性意识。

单片机 I/O 口和电机驱动之间的信号线不要太长,否则 PWM 波形可能失真。如果条件允许,在信号源端串联一个 100Ω 左右的小电阻,可以减少振铃。

10.2 软件架构方面

不要把业务逻辑全部写到中断里。中断服务函数应该保持简短,只做标志位记录、计数器累加这类简单事情。主循环再去处理模式切换、LCD 刷新和档位计算。

DS18B20 温度读取可以使用状态机,避免阻塞整个系统。这里介绍一个简化思路:

状态1:发送启动转换命令 状态2:等待转换完成 状态3:读取温度 状态4:处理温度数据并刷新显示

在主循环中不断查询当前状态,并判断是否满足跳转条件。这样系统在执行 750ms 等待过程中,仍然能响应按键。

不过对于 51 单片机初学者,如果状态机反而带来理解困难,可以先采用简单的延时等待方式实现功能,再把系统优化作为“改进方向”写在论文里。

10.3 数值处理与显示

温度小数处理时,要注意整数除法与浮点数的问题。C51 中浮点运算需要较大的代码空间和执行时间。例如 25.5℃ 可以保存为整数255,显示时用/10%10分离整数与小数,这样可以避免浮点库。

这样既能显示小数,又减少 ROM 占用。手动模式下,档位值与 PWM 占空比建议用宏定义或常量数组管理,不要散落在代码中。

#define LEVEL_STOP 0 #define LEVEL_LOW 35 #define LEVEL_MID 70 #define LEVEL_HIGH 99 unsigned char code level_duty_table[4] = {0, 35, 70, 99};

使用code关键字可以把数组定义到程序存储区中。51 单片机的 RAM 资源有限,这种优化对系统稳定性有帮助。

10.4 论文与答辩准备

毕业设计不只写代码,还要能讲清楚自己的方案。建议准备一张系统原理框图,一张完整电路图,一张程序流程图。答辩时被问到“为什么用这么简单的单片机”时,可以从成本、学习成本、项目需求三个角度回答。

另外,绿色智能风扇作为单片机方向的项目,可以在论文中加入节能数据估算。例如通过测量不同占空比下电机电流,计算一个小时内自动模式相比固定最高档节省多少电量。不过做这种测算时不要编造数据,最好基于你的实际硬件测量。

11. 总结与学习路线

本文围绕“绿色风扇的设计与实现”这个单片机毕业设计题目,重点讲解了从系统架构到硬件选型,再到软件编码与调试排错的完整流程。核心内容可以归纳为:

  • 温度传感器 DS18B20 的单总线通信原理与读写代码流程。
  • 直流电机通过 L298N + PWM 实现调速的方法。
  • 51 单片机定时器中断实现软件 PWM。
  • 手动/自动两种工作模式与节能控制逻辑。
  • LCD1602 温度显示与按键交互设计。

不管你是已经完成了全部功能,还是刚刚选定方向,下一步可以考虑几个进阶方向:

  1. 把 DS18B20 换成温湿度传感器 DHT11,实现“温度+湿度”双参数采集。
  2. 增加蓝牙模块 HC-05 或 WiFi 模块 ESP8266,通过手机 App 控制风扇。
  3. 增加人体红外传感器,实现“有人时转,无人时自动停”的更智能节能策略。
  4. 把主控换成 STM32,使用内部 ADC、定时器 PWM 输出和 RTOS 多任务调度,体验更接近产品级开发。

在做毕业设计时,硬件调试要有耐心。不要程序一编译不报错就觉得能跑,硬件上的电平关系、上电顺序、电源共地,任何一个细节都可能导致整个系统不工作。建议先把最小系统跑起来,再逐个模块调通,最后整合。

如果你手头还没有完整代码,可以根据本篇文章的模块拆分方式,先把每个模块的单文件示例编译通过,再组装到 main.c 中。这样每一步的错误都能定位到具体模块,比一次性写完所有代码再查错要轻松得多。

希望这篇教程能帮你顺利完成“绿色风扇”项目。如果文章里有不清楚的地方,可以通过评论区继续交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询