ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STC89C52的绿色智能风扇设计:从DS18B20到PWM调速

基于STC89C52的绿色智能风扇设计:从DS18B20到PWM调速 各位做单片机毕业设计或者课程设计的同学大家好。临近毕业季很多人在选题时会在网上找各种现成的项目资料。风扇控制类题目是单片机毕业设计里的经典方向因为需求明确、容易出效果、答辩时也好讲。不过很多资料只有残缺代码或者效果图不够完整。这篇文章围绕“绿色风扇的设计与实现”这个题目从需求分析、硬件选型、电路设计、软件框架、代码实现到调试方法做一次系统梳理。绿色风扇的“绿色”不只是颜色上的绿色更重要的是节能与智能控制风扇能根据环境温度自动调整转速也可以手动切换档位并且通过数码管或 LCD 实时显示当前温度与风扇档位。这样既能降低不必要的电能消耗又能体现“单片机 传感器 执行机构 人机交互”的完整嵌入式系统设计思路。本文面向两类读者。第一类是正在准备单片机毕设的本科生第二类是希望掌握 51 单片机项目完整开发流程的电子爱好者。读完后你应该能画出系统框图、读懂或写出基于 STC89C52 的风扇控制代码、在开发板上完成基本调试并能在答辩中解释各个模块的工作原理。1. 背景与核心概念1.1 什么是绿色智能风扇传统风扇使用机械开关只有“开”和“关”两个状态不能根据温度自动调节也无法查看当前环境温度。所谓绿色智能风扇核心是引入单片机作为控制中心通过温度传感器采集环境数据再由单片机决定风扇的开关与转速最终达到“需要多少风就吹多少风”的节能目的。从自动控制角度看这是一个典型的开环或半闭环控制系统温度传感器作为输入环节单片机作为运算控制环节电机驱动与直流电机作为执行环节按键与显示器作为人机交互环节。1.2 主控芯片为什么选 STC89C52STC89C52 是 51 内核单片机中非常普及的一类芯片在毕业设计中使用率很高。选择它主要有几个原因引脚与标准 51 兼容参考资料多遇到问题容易搜索解决。内部含 8KB Flash 程序存储器和 512 字节 RAM对于风扇控制这种规模的程序完全足够。支持 ISP 下载不用专门的编程器用 USB 转 TTL 模块就能烧录程序。价格便宜开发板或自制最小系统板成本低。当然如果后续想扩展 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 口在准双向模式下拉电流和灌电流能力都不大。普通直流电机启动电流可能达到几百毫安甚至更大如果直接把电机接到单片机引脚上轻则无法启动重则烧毁引脚。因此需要通过驱动电路或驱动芯片来放大控制信号。常见方案有三种三极管开关电路只适合控制风扇开或关不能调速。L298N 电机驱动模块可以控制直流电机正反转和 PWM 调速。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 设计目标与功能需求绿色风扇系统重点实现以下功能温度检测。使用 DS18B20 实时采集环境温度并通过 LCD1602 显示。档位控制。提供手动模式和自动模式。手动模式由按键切换风扇停止、低速、中速、高速四个档位。自动恒温控制。自动模式下系统根据设定温度与实际环境温度差值自动选择 PWM 占空比。状态指示。LCD1602 的第一行显示温度第二行显示当前档位或模式。节能逻辑。环境温度低于设定下限时风扇自动停止温度越高转速越高。从绿色与节能角度讲自动模式比单纯手动更有说服力。如果答辩被问到“哪里体现了绿色”可以这样回答普通风扇无论室温高低都保持恒定档位而本系统根据实际温度需求动态调整 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 接到单片机 RXDTXD 接 RXD也就是交叉连接。4.2 温度传感器电路DS18B20 的三个引脚分别是 VDD、DQ、GND。DQ 是数据线必须接一个 4.7kΩ 上拉电阻到 VDD。上拉电阻的作用是确保数据总线在空闲时为高电平因为单总线协议要求释放总线时由外部上拉电阻把电平拉高。接线如下DS18B20 引脚连接VDD5VDQP3.7GNDGND上拉电阻DQ 到 5V4.7kΩ如果使用寄生供电方式可以把 VDD 直接接地靠 DQ 信号线上的高电平时段为传感器提供能量。不过为了方便调试推荐外部供电。4.3 电机驱动方案选择在绿色风扇中电机驱动电路是一个非常关键的区分点。如果用一个简单的 NPN 三极管驱动小功率直流电机只能通断不能调速。若想实现低速、中速、高速的调节需要 PWM 调速。本设计使用 L298N 驱动模块。L298N 是一颗双 H 桥驱动芯片能够驱动两个直流电机或一个步进电机。模块上通常有 12V 电源输入、5V 输出、使能端 ENA/ENB、逻辑输入 IN1-IN4。当只驱动一个风扇电机时电机正极接 OUT1负极接 OUT2。IN1 1IN2 0 时电机正转。IN1 0IN2 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 软件整体框架单片机软件通常采用循环扫描结构。其核心思想是系统上电后初始化各模块。主循环中不断执行温度读取、按键扫描、显示刷新、输出控制。所有任务分时执行确保温度、按键、显示能同时响应。主程序流程如下系统上电 - 初始化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 工作方式 116 位定时器每次溢出重新装载计数初值。// 假设使用 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 启动温度转换并读取温度的完整步骤是复位。发送跳过 ROM 指令 0xCC。发送启动温度转换指令 0x44。等待转换完成通常需要 750ms12 位分辨率。再复位。发送跳过 ROM 指令 0xCC。发送读暂存器指令 0xBE。读取两个字节温度低字节和温度高字节。如果不想等待 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 P24 位模式下数据线连接到 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 延时太短会造成读取函数返回的温度值更新不及时。更合理的做法是周期性启动转换。主循环继续执行其他任务。切换标志位判断是否读取完成。不过对于课程设计简单阻塞式读取也能工作只要按键扫描对时序不敏感即可。为了提升用户体验建议把温度读取的间隔控制到 1s 左右避免 LCD 频繁刷新闪烁。6. 按键控制与防抖实现6.1 独立按键扫描独立按键一般按下为低电平。软件扫描步骤检测到引脚为低电平说明可能有按键按下。延时 10ms 到 20ms。再次检测引脚电平如果仍然为低电平则认为确实按下。等待按键释放如果不等待释放一次按键可能被误判为多次触发。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 仿真。搭建电路的思路如下放置 STC89C52 或 AT89C52。放置 DS18B20注意数据线上拉电阻 4.7kΩ。放置 LCD1602连接好 RS、RW、EN、D0-D7 或 D4-D7。放置直流电机和 L298N 模块Proteus 中的 L298N 在搜索栏中可以直接找到。添加独立按键和必要的排阻。仿真中容易犯的错误没有连接共地端。忘记给单片机接晶振和复位电路。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 电容缩短连接线排查顺序用万用表测 DS18B20 VDD 与 GND 是否 5V 左右。测 DQ 引脚静态是否被上拉到高电平。代码中在初始化时连续读两次温度看是否稳定。使用逻辑分析仪观察波形条件不足时可在不同延时值下多次测试。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 温度显示与按键交互设计。不管你是已经完成了全部功能还是刚刚选定方向下一步可以考虑几个进阶方向把 DS18B20 换成温湿度传感器 DHT11实现“温度湿度”双参数采集。增加蓝牙模块 HC-05 或 WiFi 模块 ESP8266通过手机 App 控制风扇。增加人体红外传感器实现“有人时转无人时自动停”的更智能节能策略。把主控换成 STM32使用内部 ADC、定时器 PWM 输出和 RTOS 多任务调度体验更接近产品级开发。在做毕业设计时硬件调试要有耐心。不要程序一编译不报错就觉得能跑硬件上的电平关系、上电顺序、电源共地任何一个细节都可能导致整个系统不工作。建议先把最小系统跑起来再逐个模块调通最后整合。如果你手头还没有完整代码可以根据本篇文章的模块拆分方式先把每个模块的单文件示例编译通过再组装到 main.c 中。这样每一步的错误都能定位到具体模块比一次性写完所有代码再查错要轻松得多。希望这篇教程能帮你顺利完成“绿色风扇”项目。如果文章里有不清楚的地方可以通过评论区继续交流。
返回列表