ARTICLE DETAIL

资讯详情

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

C51温度报警系统实战:DS18B20采集与LCD1602显示完整实现

C51温度报警系统实战:DS18B20采集与LCD1602显示完整实现 简介这是一套基于 C51 单片机AT89C51设计的温度检测报警系统课程设计资料适合正在准备单片机课程设计、毕业设计或工程实训的初学者与进阶学习者。项目通过 DS18B20 温度传感器采集环境温度并在 LCD1602 液晶屏上实时显示用户可按键设定温度上下限当温度越过阈值时触发声光报警并联动电机正反转整体逻辑覆盖传感器读取、显示驱动、按键处理与报警控制等典型单片机开发环节。压缩包共 9 个文件、约 2.69MB主要包含两个 C 源码文件LCD 显示与电机控制、两个 Word 课程设计报告、两个答辩 PPT、一个 Proteus 仿真工程、一张工程截图及一份说明文档便于对照学习软硬件设计与调试流程。目前已有 150 人学习使用。资料中提供了完整代码、仿真工程与报告范文可作为课程设计与毕设的参考蓝本帮助理解系统框架后自行扩展和修改功能。1. 为什么这套 C51 温度报警系统值得拆开看做单片机课程设计时最怕的不是代码写不出来而是拿到一个能跑通的工程却不知道每一步为什么这么做。市面上大量的温度报警系统代码核心逻辑都差不多DS18B20 采集温度LCD1602 显示数值按键调整阈值超限就驱动 LED 和电机。但这套基于 AT89C51 的设计有个值得注意的细节——它把电机正转和反转同时纳入报警响应高温时电机正转散热低温时电机反转加热这比单纯亮灯报警多了一个执行机构维度也更容易在答辩时讲出设计层次。本文会从 DS18B20 的时序驱动、LCD1602 显示、按键阈值设定、报警执行逻辑四条线展开最后落到 Proteus 仿真联调和答辩高频问题。整篇文章基于 Keil C51 工程源码来拆解每一步都给到可直接参考的代码写法。适合正在做课程设计或毕业设计的学生也适合想快速上手 C51 外设驱动、看别人工程时能快速定位关键代码的开发者。2. DS18B20 温度采集时序、驱动源码与参数调优2.1 为什么选 DS18B20 而不是热敏电阻很多入门设计会选 NTC 热敏电阻配 ADC0809 采样电路简单但有几个实际工程问题热敏电阻的阻值-温度曲线是非线性的需要查表或拟合公式ADC 转换精度受参考电压影响每个通道要单独标定。DS18B20 把这些问题全部封装在芯片内部单总线数字输出直接给单片机 12 位温度数据不需要 ADC 前端电路。DS18B20 测温范围是 -55℃ 到 125℃12 位分辨率下精度为 0.0625℃对于温度监控系统课程设计来说完全够用。更重要的是DS18B20 使用单总线协议只需要一根 I/O 口线就能完成数据通信节省单片机引脚资源。在 AT89C51 这种引脚本来就不富裕的芯片上这个优势很明显。2.2 单总线时序的三个关键阶段单总线协议的核心在于严格的时序控制。DS18B20 的所有操作都遵循一个固定流程初始化复位、发送 ROM 命令、发送功能命令、读写数据。AT89C51 的工作频率通常为 12MHz一个机器周期 1μs所以延时函数只要精确到微秒级就能满足时序要求。初始化的时序是主机拉低总线 480μs 以上然后释放总线DS18B20 会在 15~60μs 内拉低总线作为存在脉冲应答。这个应答信号是判断传感器是否在线的关键如果读不到低电平说明传感器没接好或者线路有问题。实际调试时我习惯在初始化失败时让蜂鸣器响一声这样硬件连接问题能第一时间发现。ROM 命令中 Skip ROM0xCC是最常用的因为系统里只挂了一个 DS18B20跳过 ROM 匹配直接进行温度转换可以省去 64 位序列号的读取和匹配流程代码更简洁。如果以后要挂多个传感器才需要换成 Search ROM0xF0和 Match ROM0x55配合 64 位序列号操作。2.3 核心驱动代码与延时参数说明#include reg51.h #include intrins.h sbit DQ P3^7; // DS18B20 数据线接 P3.7 // 微秒级延时函数12MHz 晶振下各指令周期固定 void Delay_OneWire(unsigned int t) { while(t--) { _nop_(); // 1μs } } // DS18B20 初始化返回 0 表示存在1 表示无响应 bit DS18B20_Init(void) { bit presence; DQ 1; Delay_OneWire(2); DQ 0; Delay_OneWire(240); // 拉低 480μs 以上 DQ 1; Delay_OneWire(30); // 等待 15~60μs presence DQ; // 读取存在脉冲 Delay_OneWire(140); return presence; // 读到低电平返回 0 }这段代码里的几个延时参数都是按照 DS18B20 数据手册的时序要求设定的。240 次的循环大约产生 480μs 的低电平满足初始化要求的 480~960μs。读取存在脉冲前等待 30μs恰好落在传感器应答信号的窗口内。要注意的是这些延时是在 12MHz 晶振下算出来的如果工程里改了晶振频率所有Delay_OneWire的参数都要等比修正。2.4 温度转换与读取的完整代码// 跳过 ROM启动温度转换 void DS18B20_ConvertT(void) { DS18B20_Init(); DQ 1; Delay_OneWire(2); DQ 0; Delay_OneWire(10); DQ 1; // 发送 Skip ROM 命令 0xCC DS18B20_WriteByte(0xCC); // 发送 Convert T 命令 0x44 DS18B20_WriteByte(0x44); } // 读取温度值返回整数部分和一位小数 unsigned int DS18B20_ReadT(void) { unsigned char low, high; unsigned int temp; DS18B20_Init(); DS18B20_WriteByte(0xCC); // Skip ROM DS18B20_WriteByte(0xBE); // 读暂存器命令 low DS18B20_ReadByte(); high DS18B20_ReadByte(); temp (high 8) | low; // 负温度处理符号位为 1 时取反加一 if(temp 0xF800) { temp ~temp 1; } return temp; }读回来的原始数据是两个字节低字节的 bit0~bit3 是小数部分高字节的 bit4~bit0 是符号位和整数高位。12 位分辨率下温度值换算公式是实际温度 原始值 × 0.0625。比如原始值 0x0191 等于 401乘以 0.0625 为 25.0625℃这就是 25.1℃ 的显示来源。代码里的temp 0xF800是判断符号位因为 12 位数据中最高位是符号位如果该位为 1 表示零下温度需要做补码转换。2.5 写命令和读数据的位时序实现// 写一个字节低位在前 void DS18B20_WriteByte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { DQ 0; // 拉低总线 _nop_(); DQ dat 0x01; // 输出数据位 Delay_OneWire(10); // 保持 10μs DQ 1; // 释放总线 dat 1; } Delay_OneWire(5); } // 读一个字节低位在前 unsigned char DS18B20_ReadByte(void) { unsigned char i, dat 0; for(i 0; i 8; i) { DQ 0; _nop_(); DQ 1; // 释放总线准备读取 _nop_(); // 等待数据有效 if(DQ) { dat | 0x80; // 高位在前存入 } Delay_OneWire(15); } return dat; }写时序和读时序的差别在于写时序是主机主动拉低总线然后输出数据读时序是主机释放总线后传感器驱动总线。注意读字节函数里dat | 0x80这是把数据位放在最高位配合循环左移的下一次移位最终拼接出完整字节。两次_nop_()的时间窗口约为 2μs正好位于 DS18B20 规定的 15μs 内读取数据有效。这里有一个常见的坑很多人会把读时序的延时写长结果总线被传感器持续拉低读回来的数据全是 0xFF。原因是传感器只在主机释放总线后的很短时间内驱动数据超时后总线恢复高电平读到的一律是 1。3. LCD1602 显示驱动与按键阈值设定的实现思路3.1 LCD1602 在系统里的显示规划这个项目的显示内容比较固定第一行显示当前温度和温度单位第二行显示设定的上限和下限值。LCD1602 是字符型液晶每行 16 个字符所以要对显示内容做精确定位。我一般会在初始化时先清屏然后分别设置两行的起始地址。LCD1602 第一行地址是 0x80第二行是 0xC0后续字符地址自动递增。显示格式规划为第一行T:25.5C第二行H:30 L:10。这个格式的好处是信息密度高一屏就能看到所有关键数据。如果需要在调试模式下显示 DS18B20 的原始值可以在代码里加一个宏开关切换显示模式。3.2 LCD1602 关键驱动函数#include reg51.h #define LCD_DATA P0 // LCD1602 数据口接 P0 sbit RS P2^5; sbit RW P2^6; sbit EN P2^7; // 检测忙标志避免写入过快导致丢数据 void LCD_CheckBusy(void) { unsigned char temp; LCD_DATA 0xFF; RS 0; RW 1; do { EN 1; temp LCD_DATA; EN 0; } while(temp 0x80); // 最高位为 1 表示忙 } // 写命令到 LCD void LCD_WriteCmd(unsigned char cmd) { LCD_CheckBusy(); RS 0; RW 0; LCD_DATA cmd; EN 1; EN 0; } // 写数据到 LCD void LCD_WriteData(unsigned char dat) { LCD_CheckBusy(); RS 1; RW 0; LCD_DATA dat; EN 1; EN 0; } // LCD 初始化 void LCD_Init(void) { LCD_WriteCmd(0x38); // 8 位数据2 行5x7 点阵 LCD_WriteCmd(0x0C); // 开显示关光标 LCD_WriteCmd(0x06); // 写入后地址自动加一 LCD_WriteCmd(0x01); // 清屏 }忙标志检测是关键一步。LCD1602 内部处理命令需要时间如果不检测忙标志直接连续写入很可能丢字。temp 0x80判断的是数据口最高位这是 LCD1602 约定的忙标志位。有些精简代码直接延时替代也能工作但可靠性不如忙检测。数据口接 P0 需要注意一点P0 口内部没有上拉电阻必须外接 10kΩ 排阻到 VCC否则高电平输出能力不足LCD 显示会出现乱码或者闪烁。这个细节在 Proteus 仿真里同样适用仿真也需要接上拉电阻才能看到正确效果。3.3 按键扫描与阈值调整逻辑系统需要两个按键来调整温度上下限一个切换当前编辑目标上限或下限另一个增加数值。为了在编辑时有视觉反馈我增加了第三个按键用于减少数值三个按键分别接 P1.0、P1.1、P1.2。按键扫描放在主循环里采用每 10ms 扫描一次的机制避免按键抖动导致一次按下多次触发。sbit KEY_SET P1^0; // 切换设置目标上限/下限 sbit KEY_UP P1^1; // 数值加一 sbit KEY_DOWN P1^2; // 数值减一 bit set_target 0; // 0 表示当前调上限1 表示调下限 unsigned char alarm_high 30; unsigned char alarm_low 10; // 按键扫描函数带消抖处理 void Key_Scan(void) { static unsigned int key_count 0; static bit key_pressed 0; if(KEY_SET 0 || KEY_UP 0 || KEY_DOWN 0) { key_count; if(key_count 10 key_pressed 0) { key_pressed 1; if(KEY_SET 0) { set_target ~set_target; // 切换编辑目标 } else if(KEY_UP 0) { if(set_target 0) { if(alarm_high 99) alarm_high; } else { if(alarm_low alarm_high) alarm_low; } } else if(KEY_DOWN 0) { if(set_target 0) { if(alarm_high alarm_low 1) alarm_high--; } else { if(alarm_low 0) alarm_low--; } } } } else { key_count 0; key_pressed 0; } }阈值调整的逻辑里加了数值边界保护上限最低只能调到比下限高 1下限最高只能调到比上限低 1这是为了防止用户把上下限设成矛盾的值导致报警逻辑永远触发。这个细节在实际答辩时会被问到能回答出来说明你真的理解了系统的运行逻辑。按键采用连续计数消抖超过 10 次扫描采样才认为按键有效这比delay(10)消抖更可靠因为主循环里还有其他任务要处理。4. 报警执行逻辑阈值比较、LED 与电机联动控制4.1 温度判断的分区设计温度报警系统需要定义清晰的工作区间否则会出现上限和下限同时触发的矛盾状态。正确逻辑是温度大于等于上限时触发高温报警温度小于等于下限时触发低温报警两个区间之间存在一个回差区间在这个区间内系统不报警。回差区的存在非常必要否则温度在上限附近抖动会导致报警灯和电机频繁启停。sbit LED_HIGH P2^0; // 高温报警灯 sbit LED_LOW P2^1; // 低温报警灯 sbit MOTOR_A P2^2; // 电机正转控制 sbit MOTOR_B P2^3; // 电机反转控制 sbit BEEP P2^4; // 蜂鸣器 unsigned int current_temp 0; // 当前温度扩大 10 倍存储避免浮点运算 void Alarm_Check(void) { // 将实际温度值如 25.5转为 255 与阈值比较 unsigned int temp_val current_temp; unsigned int high_val alarm_high * 10; unsigned int low_val alarm_low * 10; if(temp_val high_val) { // 高温报警亮高温灯电机正转蜂鸣器鸣叫 LED_HIGH 0; LED_LOW 1; MOTOR_A 1; MOTOR_B 0; BEEP ~BEEP; } else if(temp_val low_val) { // 低温报警亮低温灯电机反转蜂鸣器鸣叫 LED_HIGH 1; LED_LOW 0; MOTOR_A 0; MOTOR_B 1; BEEP ~BEEP; } else { // 正常区间全部关闭 LED_HIGH 1; LED_LOW 1; MOTOR_A 0; MOTOR_B 0; BEEP 0; } }温度值乘以 10 再做比较是避免浮点运算的低成本方案。单片机做浮点运算耗时且占程序空间整数运算在 12MHz 主频下效率高得多current_temp在主循环里由 DS18B20 的原始值乘 0.0625 再乘 10 得到。这里BEEP ~BEEP实现的是蜂鸣器间断鸣叫放在主循环里配合延时能产生滴滴声效果。4.2 电机驱动的 Proteus 仿真连接方式在 Proteus 里电机正反转是通过控制电机两端电平差实现的。仿真模型中我用了两个三极管来模拟 H 桥的简化版MOTOR_A 为高、MOTOR_B 为低时电流从 A 流向 B电机正转反过来就是反转。实际硬件可以使用 L298N 或 L293D 驱动芯片但在 Proteus 课程设计场景下用两个 NPN 三极管加直流电机模型足以演示效果。需要特别注意的是AT89C51 的 P2 口输出电流有限不能直接驱动电机和蜂鸣器仿真中如果不加三极管放大电路电机可能不转或者转速极低。硬件方案中推荐用 ULN2003 达林顿管阵列芯片它的驱动电流可以达到 500mA一组管脚就能驱动电机和蜂鸣器电路简洁。4.3 主循环的整体调度void main(void) { unsigned int temp_raw; unsigned char t_low, t_high; LCD_Init(); DS18B20_Init(); // 显示初始阈值 LCD_WriteCmd(0x80); LCD_WriteData(H); LCD_WriteData(:); LCD_WriteData(alarm_high / 10 0); LCD_WriteData(alarm_high % 10 0); while(1) { // 采集温度 DS18B20_ConvertT(); Delay(750); // 等待转换完成12 位精度典型值为 750ms temp_raw DS18B20_ReadT(); current_temp temp_raw * 0.0625 * 10; // 更新 LCD 显示 LCD_DisplayTemp(current_temp); // 按键扫描 Key_Scan(); // 报警判断 Alarm_Check(); } }主循环的执行顺序是采集→显示→扫键→报警判断这个顺序不能随便换。温度转换放到最前面是因为 DS18B20 的 12 位转换需要最长 750ms如果先扫键再转换按键操作会抢占转换等待时间导致温度刷新滞后。把扫描放到显示后面既保证了按键响应速度又不影响温度数据流的完整性。Delay(750)是软件延时此时单片机干等。更高效的做法是用定时器中断做时间片轮转但课程设计场景下软件延时足够代码也更好理解。如果工程对实时性要求更高建议改成定时器计数把主循环做成状态机。5. Proteus 仿真联调、常见问题与答辩高频考点5.1 仿真工程的关键设置与下载运行Proteus 仿真设计文件是Pro.pdsprj打开后需要确认几项关键设置AT89C51 的晶振频率设置为 12MHz与 Keil 工程里的延时算法匹配DS18B20 组件的地址不冲突电源 VCC 和地 GND 正确连接。双击单片机芯片在 Program File 里加载 Keil 编译生成的.hex文件然后点击运行仿真LCD 上应该立刻显示当前环境温度。一个实用的仿真技巧是在 Proteus 中把 DS18B20 的温度值手动修改选中元器件后按属性修改温度数值观察 LCD 显示和报警输出是否同步变化。修改温度到设定的上限之上高温 LED 应该立即点亮电机开始正转蜂鸣器鸣叫。这个操作能快速验证系统的报警逻辑不需要等实际环境温度变化。5.2 时序相关的三个高频坑第一个坑是 DS18B20 读不到温度LCD 显示固定为 0 或 85。85.0℃ 是 DS18B20 上电复位后的默认值看到这个数字说明初始化时序有问题或者总线根本没通。排查方法是用示波器抓单总线波形看初始化时是否有传感器的存在脉冲下拉。如果在 Proteus 仿真里出现这个问题十有八九是 DS18B20 的接地点没连到电源地。第二个坑是 LCD 显示乱码。P0 口没接上拉电阻是最常见原因。另一个原因是 LCD 初始化时序太快初始化命令之间需要有 4.1ms 以上的延时代码里用LCD_CheckBusy检测忙标志可以避免但如果初始化之前就调用忙检测可能单片机先收到了垃圾状态。稳妥做法是初始化命令序列中间硬性插入延时之后再启用忙检测。第三个坑是电机不转或转速慢。Proteus 仿真中电机模型需要一定电流才能启动如果直接从 P2 口驱动电流根本不够。解决办法是加一个 PNP 三极管作为开关或者在仿真里用MOTOR-STEPPER模型替代普通直流电机后者对驱动电流要求更低。硬件实物中电机必须续流二极管否则关断瞬间的反向电动势可能烧毁驱动管。5.3 答辩高频问题与应答要点答辩时评委经常问的问题集中在系统架构和代码实现细节上。第一个问题是为什么选用 AT89C51 而不是 STM32回答要点是温度监控对处理性能要求极低AT89C51 的 8 位处理能力完全满足且 8051 架构生态成熟、参考资料多、成本更低。课程设计重点在于掌握单总线协议和显示驱动的原理而非 CPU 的主频和算力。第二个高频问题是蜂鸣器为什么用BEEP ~BEEP这个说法的理论依据是蜂鸣器驱动需要交变电流才能发出声音持续高电平只会让蜂鸣片吸合一次。用方波信号驱动蜂鸣器才能产生声音振荡。代码里每轮主循环翻转一次电平加上循环本身的耗时就形成了频率约几百赫兹的方波驱动蜂鸣器发声。第三个问题是上下限设置边界的保护逻辑。答辩时要主动说明alarm_high alarm_low 1和alarm_low 0这两个条件的目的是保证系统在任意阈值组合下都有明确的报警分区。如果评委追问为什么下限不做最高限制可以解释为下限只需要保证低于上限即可系统设计的原则是不能出现不可用的死区。5.4 从课程设计到工程化的三个改进方向如果这套系统要做进一步升级有三个方向值得优先考虑。第一是多传感器组网把 Skip ROM 改为 Search ROM读取每个 DS18B20 的 64 位序列号并动态识别这样一套主机可以监测多个点的温度代码改动集中在 ROM 命令部分。第二是引入温度迟滞控制在报警判断中加入滞回比较区间温度高于上限后回落到上限减 2℃ 才算解除报警这样可以显著减少继电器和电机在边界温度处的频繁动作。第三是用定时器中断替代软件延时把温度转换等待改成状态机切换主循环在等待期间可以同时处理按键扫描和显示刷新系统实时性提升明显。仿真验证时这三个改动都可以直接在 Keil 工程里修改后重新加载.hex文件观察效果。本文还有配套的精品资源点击获取
返回列表