简介:这套基于C51单片机的温度检测报警系统课程设计资料,面向单片机初学者及有进阶需求的学习者,适用于课程设计、毕业设计或工程实训等场景。项目以AT89C51为控制核心,借助DS18B20温度传感器采集环境温度,并实时显示在LCD1602液晶屏上;用户可通过按键自由设定温度上、下限值,当温度越限时,系统驱动对应报警灯点亮,并通过电机正转或反转模拟高温散热与低温加热的处置动作,逻辑完整清晰。压缩包共9个文件,大小约2.69MB,包含Keil C语言源码、Proteus仿真工程文件、课程设计报告、答辩PPT和工程截图,源码与文档配合使用,便于理解系统架构、外设驱动和调试流程;随附README说明,可辅助对照仿真环境自行修改与扩展功能。资料定位为“参考资料”而非成品定制,需要读者具备一定基础、能看懂代码并完成本地调试。目前已有150人学习下载,对想要快速搭建温度监控类项目的读者有实在的参考价值,可作为同类设计的起步模板。
1. 温度检测报警系统为什么值得用 C51 做一遍
大部分课程设计选温度检测,最后都会卡在 DS18B20 这颗芯片上。它只有一根数据线,却同时承担供电、时钟和数据传输,稍微初始化时序不对,读回来的温度就是 85°C 或者乱码。很多同学第一反应是换硬件,其实问题往往出在延时函数和时序顺序上。把 C51 的温度检测报警系统做扎实,本质上是把单片机最核心的三件事练了一遍:GPIO 模拟时序、中断与状态机、以及硬件资源(按键、蜂鸣器、数码管或 LCD)的协同调度。这套东西对之后做 STM32 的 HAL 库、写 Modbus 帧接收、甚至看 muoduo 这种嵌入式内核源码都有帮助。本文按照从器件选型、原理分析、软件实现到 Proteus 仿真和答辩准备来展开,适合正在做课程设计、准备蓝桥杯单片机模块或者刚接触 51 单片机 C 语言编程的读者。文中代码与电路以 STC89C52 / AT89C52 为基准,基本可以直接移植到普中、江协等常见开发板。
2. 硬件与测温原理:先弄清 DS18B20 的时序再写代码
2.1 器件的选型理由
温度检测报警系统的核心传感器有热电偶、热敏电阻、DS18B20 和 LM75 这几类可选。课程设计场景里,DS18B20 优势非常明显:测温范围 -55°C 至 +125°C,在 -10°C 至 +85°C 范围内精度为 ±0.5°C,而且只占用单片机一根 IO 口。相比之下,热敏电阻需要 AD 转换和查表标定,热电偶需要冷端补偿芯片 MAX6675,电路复杂度和成本都上了一个量级。
报警输出部分常见的是蜂鸣器加 LED。有源蜂鸣器只要给高电平或低电平就会响,无源蜂鸣器需要给一定频率的方波才能发声。课程设计要求达到「超限报警」,一般推荐有源蜂鸣器,因为软件上只需置位或清零一个引脚,不用维护 PWM 频率,难度低且稳定。显示部分有两种方案:LED 数码管和 LCD1602。数码管适合纯温度数值显示,LCD1602 可以同时显示当前温度、上限温度和报警状态,答辩演示时信息量更足。
2.2 STC89C52 内部资源与引脚分配
STC89C52 是 51 内核中比较适合做课设的型号,原因是它的指令集与 AT89C52 完全兼容,而且支持 ISP 串口下载,不需要额外购买编程器。常用晶振为 11.0592MHz,因为它的机器周期是 12 个时钟周期,所以 1 个机器周期约等于 1.085 微秒,在编写 DS18B20 的微秒级延时函数时可以直接用循环次数来近似。
引脚分配一般这样规划:DS18B20 数据线接 P3.7(也可选 P2.0 或 P1.0,但 P3.7 离定时器引脚较远,干扰小,所以优先);蜂鸣器接 P2.3,通过 NPN 三极管或 ULN2003 驱动;按键接 P1.0(设置)、P1.1(加)、P1.2(减)。这里有个容易被忽视的细节:DS18B20 的数据线必须接一个 4.7kΩ 的上拉电阻到 VCC,否则时序信号无法正确上拉。很多同学在 Proteus 里画完电路不加上拉电阻,仿真也能跑通,但实物就完全不能工作。这一点放到论文的「硬件调试」小节去写会非常加分。
2.3 DS18B20 的三种供电方式与时序要求
DS18B20 支持外部供电、寄生供电和强上拉寄生供电三种模式。课程设计通常采用外部供电,也就是 VDD 接 5V、GND 接地、DQ 接 IO 口并通过上拉电阻到 VCC。外部供电方式的好处是读写时序的驱动力强,不容易因为寄生电容影响而出现数据错误。
时序方面,DS18B20 的操作可以分为三个层次。第一层是初始化时序,主机拉低总线至少 480 微秒然后释放,等待器件拉低 60 至 240 微秒作为存在脉冲。第二层是写时序,主机拉低总线后,如果 15 微秒内释放则写 1,如果保持拉低 60 微秒以上则写 0。第三层是读时序,主机拉低 1 微秒后释放,然后在 15 微秒内采样总线电平。朝这三个层次去写代码,而不是直接抄一段延时,后面排查问题会方便很多。
2.3.1 命令流程:ROM 命令与功能命令
DS18B20 的每次操作都必须以初始化开始,随后发送 ROM 命令。单总线只有一个设备时,可以直接发送跳过 ROM 命令 0xCC,然后发送功能命令。功能命令中最常用的是 0x44(启动温度转换)和 0xBE(读取暂存器)。转换时间在 12 位分辨率下最长是 750 毫秒,所以代码里应等待或者延时超过这个时间再读温度,否则读到的是上一次的转换结果。
需要注意的是,0x44 是启动转换命令,0xBE 是读暂存器命令。如果主机在发送 0x44 后立刻发送 0xBE,DS18B20 正在转换中,会返回 0xFF 或者上次的值。常见做法是发送 0x44 后调用一个 750 毫秒的延时函数,或者更优的做法是用 C51 的定时器中断等待转换完成标志位。
3. C51 软件实现:从底层时序到报警逻辑
3.1 工程结构与 Keil C51 的配置要点
在 Keil 中新建工程时,芯片型号这一项需要注意。如果是 STC89C52,Keil 自带的器件库里有「STC Microelectronics」下的 STC89C52RC 选项,如果找不到,可以采用 Atmel 的 AT89C52 替代,因为引脚和 SFR(特殊功能寄存器)完全兼容。不过要注意,STC 单片机的复位延时和内部时钟选项与 Atmel 有差别,如果用 STC 型号需要在 ISP 下载软件里设置合适的频率,例如 12MHz 或 11.0592MHz 要对应代码里延时的实际时间基准。
工程文件建议分成四个模块:main.c(主循环与状态机)、ds18b20.c(时序与温度读取)、lcd1602.c(显示驱动)、alarm.c(报警和按键处理)。头文件分别对应。这样既便于答辩时讲清模块化思想,也方便报告里贴代码时按模块逐个解释。
提示:Keil C51 的默认优化等级对 DS18B20 时序有影响。如果代码在仿真里正常、上电后读温度却偶尔乱码,先把 Optimization 设为 Level 0(或关闭优化)再编译,多半能解决。原因是优化后编译器可能调整了空循环的结构,导致延时函数实际执行时间不足。
3.1.1 微秒级延时函数为什么要用 volatile
用_nop_()或者空循环实现延时,最怕编译器优化掉循环体。正确写法是在循环变量前加volatile关键字,或者把延时函数单独放在一个文件里并且关闭该文件的优化。下面是一个常见实现:
void delay_us(unsigned int us) { volatile unsigned int i; while (us--) { for (i = 0; i < 12; i++); } }以 11.0592MHz 晶振计算,for (i = 0; i < 12; i++)大约耗时 12 个机器周期,也就是 13 微秒左右。逻辑上不能精确到每一个微秒,但对 DS18B20 的最高 15 微秒采样窗口来说,这个量级的延时是足够用的。如果是 12MHz 晶振,循环次数可以调整为 10。参数的关键在于「宁长勿短」,拉低时间超过 480 微秒不影响初始化,但如果拉低时间不足 480 微秒,DS18B20 可能不响应存在脉冲。
3.2 DS18B20 驱动代码:复位、读写一个字节
先写最底层的复位函数:
bit ds18b20_reset(void) { bit presence; DQ = 0; // 主机拉低总线 delay_us(500); // 维持至少 480us 复位脉冲 DQ = 1; // 释放总线 delay_us(60); // 等待器件响应 presence = DQ; // 读取存在脉冲,低电平表示存在 delay_us(400); // 等剩余复位时间完成 return presence; // 返回 0 表示检测到设备 }读存在脉冲的关键是presence = DQ这一句必须放在释放总线后的 60 微秒到 240 微秒窗口内。如果延时太快或者太慢,读到的可能都是高电平,导致主机认为设备不存在。这个复位函数返回 0 表示存在,返回 1 表示无响应,在调试时配合if (ds18b20_reset())就能判断是接线问题还是器件本身坏掉。
写一个字节的核心是循环里逐位发送:
void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); // 写时序起始:拉低后维持至少 1us DQ = dat & 0x01; // 按位输出 delay_us(80); // 维持写时序时间 DQ = 1; // 释放总线 dat >>= 1; // 处理下一位,先写低位 } }这里坚持先写低位(LSB first)是因为 DS18B20 的数据手册明确规定了传输顺序为低位在前。_nop_()是 Keil 提供的单机器周期指令,加不加都会影响时序,但加了更保险,能够保证不因编译器优化而产生过短的起始脉冲。
读一个字节时,主机的动作是「拉低并释放,然后采样」:
unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; DQ = 0; _nop_(); // 读时隙起始 DQ = 1; // 释放总线,等待器件拉低或保持高 _nop_(); if (DQ) dat |= 0x80; // 采样,高位先出 delay_us(60); // 等待读时隙结束 } return dat; }读时序的采样点设在了释放总线后的几个微秒内,正好落在 DS18B20 输出有效数据的窗口区间。与写时序不同,读时序是高位在前,所以dat |= 0x80而不是dat |= 0x01。这一点容易写反,一旦反了读回来的温度数值会变得毫无规律,排查时也是先看这个位置。
3.3 温度读取流程:转换、读取、合并符号位
按照官方命令流程,每次读温度都需要两步:先发 0x44 启动转换,等待 750ms;再发 0xBE 读取 9 个字节的暂存器数据。实际中两个字节就够了——第 1 字节低 8 位,第 2 字节高 8 位,其中高位的前 5 位是符号扩展位。具体组合逻辑如下:
float ds18b20_get_temp(void) { unsigned char temp_l, temp_h; int temp_raw; float temp_value; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM ds18b20_write_byte(0x44); // 启动转换 delay_ms(750); // 等待转换结束 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存区 temp_l = ds18b20_read_byte(); temp_h = ds18b20_read_byte(); temp_raw = (temp_h << 8) | temp_l; if (temp_raw & 0xF800) { // 检查符号位 temp_raw = ~temp_raw + 1; // 负数时取补码 temp_value = temp_raw * (-0.0625); } else { temp_value = temp_raw * 0.0625; } return temp_value; }3.4 报警阈值设定与「消抖」处理
报警逻辑看起来简单:温度超过上限就置位蜂鸣器和 LED,温度低于下限就恢复。但课程设计里的常见坑是——温度刚好在阈值附近抖动时,蜂鸣器会反复通断,听起来像报警故障。为此需要引入回差(迟滞区间)的概念。比如上限设为 28°C,不要等降到 28°C 以下就停止报警,而是降到 26°C 以下才停止。这样避免因为 DS18B20 的 ±0.5°C 精度和显示量化误差造成的临界抖动。
按键调节阈值时也应当做按键消抖:检测到按键按下后先延时约 10 毫秒,再次确认引脚仍为低电平,才认为这是一次有效按下。设置状态下每按一次「加」键,阈值寄存器加 1,这样既方便答辩演示,又符合一般人对阈值的预期。上限阈值可以直接存为unsigned char,范围限制在 0 到 99 之间,避免超界显示时出现乱字符。
3.5 LCD1602 显示与主循环状态机
主循环建议写成有限状态机:状态 0 为正常运行,显示当前温度与报警状态;状态 1 为设置上限温度;状态 2 为设置下限温度。状态切换由独立按键中断或扫描方式触发。状态机的优越性在答辩时很容易被问:如果不用状态机,多个按键同时操作时逻辑会混乱;用了状态机,每个状态只响应「属于自己」的按键动作,代码可维护性明显好。
LCD1602 驱动部分,注意 P0 口如果没有接上拉电阻,作为数据口会输出不了高电平,这是 51 单片机最经典的一个「原理性坑」。解决方案是:在硬件上加 10kΩ 排阻到 VCC,或者改用 P2 口做数据口,因为 P2 口内部有上拉。在 Proteus 中默认 P0 需要手动接上拉电阻,不少同学仿真失败就卡在这。
4. Proteus 仿真、Keil 联调与常见故障表
4.1 最小电路搭建与元件清单
Proteus 仿真的价值在于,可以在没有实物的情况下先把逻辑调通。元件清单包括:AT89C52(或 STC89C52RC 模型)、DS18B20、LCD1602、RESPACK-8 排阻(用于 P0 口上拉)、BUZZER(有源蜂鸣器)、BUTTON 按键若干、LED、CRYSTAL 11.0592MHz、两个 30pF 瓷片电容、10μF 电解电容和 10kΩ 电阻(复位电路)。
电路连接的重点是:DS18B20 的 DQ 引脚除了接单片机 IO 口,还必须接一个 4.7kΩ 上拉电阻。Proteus 中如果省略这个电阻,仿真器不会报错,但是读到的数据在部分版本中会表现为随机值,这会干扰你对代码的信任,所以尽量把电路画完整。
4.2 Keil 生成 HEX 文件与加载到 Proteus 的步骤
第一步,在 Keil 中点击「Options for Target」进入 Output 选项卡,勾选 Create HEX File,然后重新编译,确保 Build Output 窗口出现creating hex file from ...字样。如果没有这行提示,说明配置没生效。第二步,在 Proteus 中双击 AT89C52 芯片,在 Program File 路径里选择刚刚生成的 .hex 文件,Crystal Frequency 设置为 11.0592MHz。第三步,点击运行按钮,如果代码逻辑正确,LCD 应该显示温度值。
提示:Proteus 对 DS18B20 模型的时序要求与真实芯片略有差异。如果实物正常但仿真读不到温度,检查延时函数是否被编译器优化。建议在 ds18b20.c 文件顶部使用
#pragma OPTIMIZE(0)强制关闭该文件优化。
4.3 排除故障的七个必查项
排在第一位的是晶振起振。仿真中晶振不起振通常表现为程序完全卡死,而实物中的表现是下载程序后无任何反应。用示波器或者万用表频率档测晶振两个引脚,如果量不到频率,重点检查起振电容和晶振型号。排在第二位的是复位电路。51 单片机是高电平复位,复位引脚通过 10μF 电容接 VCC、通过 10kΩ 电阻接地。如果 Reset 引脚长期为高电平,MCU 会一直处于复位状态。
其余检查项按频次排序:DS18B20 上拉电阻是否遗漏;P0 口是否接排阻上拉;蜂鸣器驱动三极管类型(NPN 还是 PNP)与基极电阻是否匹配;按键引脚是否接对地;端口方向寄存器(如果是 STC 带 P4 口)是否配置正确。
4.4 用示波器验证 DS18B20 时序的实操技巧
如果你手头有逻辑分析仪或者示波器,直接抓 DQ 引脚波形是最有效的排查方式。先把探头接在 DS18B20 的 DQ 引脚和 GND 之间,然后单步执行初始化函数。正常情况下波形是这样的:一个低电平宽度约 500 微秒的复位脉冲,随后总线被释放拉高,之后出现一个约 120 微秒的低电平存在脉冲。如果存在脉冲宽度只有 30 微秒甚至没有,说明器件没有正确响应,大概率是上拉电阻或供电问题。
接着抓写字节的波形,每次循环中应该有一个低电平脉冲,随后总线电平按照发送的数据位保持高或低。如果波形看起来「一团乱麻」,先降低主频到 12MHz 并把延时时间加长到原来的 2 倍,看是否有改善。有改善说明是时序过紧,需要重新精确计算延时。
5. 答辩 PPT 讲解思路、报告结构与可以吹的扩展点
5.1 答辩 PPT 的六页结构
答辩 PPT 不建议写超过十二页,也不要把代码全贴上去。以「讲清楚为什么这么设计」为线索,可以按六页来组织:第一页是题目和数据信息;第二页是系统框图,用画图工具画出「传感器—单片机—显示/报警」三层结构;第三页是器件选型对比表,重点写为什么选 DS18B20 而不是热敏电阻,为什么选 LCD1602 而不是数码管;第四页是软件流程图,分主程序流程图和 DS18B20 时序流程图两张;第五页是实测数据表,包括水温测试、阈值报警测试和临界抖动测试的结果;第六页是总结与展望,可以提一句「本设计保留 485 接口扩展空间,可升级为多点测温系统」。
流程图这一页最容易被问,因为答辩老师通常会追问:「如果 DS18B20 没有响应,你的程序会怎么样?」所以流程图中必须包含异常分支——初始化失败时 LCD 显示「Sensor Error」并让蜂鸣器发出短促提示音,而不是继续往下读。这个分支写在代码中只需要三行,但能显著展现你的工程意识。
5.2 报告的章节安排与关键写法
课程设计报告通常有固定模板,但核心章节建议这样组织:摘要写「本设计以 STC89C52 单片机为核心,采用 DS18B20 数字温度传感器,设计并实现了一款可设定上下限的实时温度检测与报警系统」,关键词写「C51;DS18B20;温度检测;报警系统」。正文第二章写方案论证,第三章写硬件设计,第四章写软件设计,第五章写调试与结果,第六章写总结。
写报告最容易吃亏的地方是「没有数据支撑」。所以在你调通实物或仿真以后,记得做一组记录:设定上限 28°C、下限 18°C,分别记录实际温度与显示温度,计算绝对误差。DS18B20 出厂校准温度精度为 ±0.5°C 但实际读数通常是 0.0625°C 的倍数,记录表里写清楚分辨率和精度这两个概念的区别,印象分会明显提高。
5.3 三个「面试官级别」的扩展点
第一个扩展点是把温度数据通过串口发到上位机。STC89C52 自带全双工 UART 接口,用 USB 转 TTL 模块连接 P3.0(RXD)和 P3.1(TXD),即可在上位机用串口工具或 Python pyserial 读取温度值。代码上只需要在原有主循环里调用printf("Temp:%.2f\n", temp_value),但在 C51 中要让 printf 正常工作必须重写putchar输出到 SBUF,这是很多新手移植 printf 失败的根本原因。
第二个扩展点是在报警输出端增加继电器控制散热风扇或加热丝,把系统从「检测报警」升级为「闭环控制」。代码上逻辑不变,只需要在报警触发时给继电器引脚一个低电平或高电平,注意继电器模块需要光耦隔离和续流二极管,直接接单片机引脚会烧 IO 口。这个扩展方向非常容易被老师追问,因为单片机课程设计的高分关键往往体现在「从检测到控制」的工程闭环能力上。
第三个扩展点是定时器中断代替延时等待转换完成。DS18B20 的 12 位温度转换耗时约 750 毫秒,用delay_ms(750)阻塞式等待会拉低系统的按键响应速度。改进方案是利用定时器 0 产生 1 毫秒节拍,累加 750 次后置转换完成标志,主循环检测到标志再发 0xBE 读取,这样按键在任何时候都能被扫描到,交互体验明显更好。这个是非模板化的设计亮点,写入报告的「程序设计特点」一节。
5.4 最后压箱底的一个技巧:用双关键字重置 C51 的 printf 串口重定向
顺带说一个不仅适用于本课设、也适用于之后做 C51 串口调试的通用技巧。只要你在 Keil C51 中调试「为什么串口输出乱码」,先查两处:波特率是否按2^SMOD * 11.0592M / (384 * (256 - TH1))计算,以及是否用EA = 1; ES = 1;把串口中断总开关打开。如果发送一个字节后程序卡死,绝大多数原因是发送后没有等待 TI 标志位清零。正确写法是:
void putchar(unsigned char c) { SBUF = c; while (!TI) { ; } TI = 0; }当这一版代码跑通之后,你会发现「上位机 + 单片机 + 传感器」三层链路其实并不复杂。之前卡住你的从来不是协议,而是每一个细节的时序是否严丝合缝。把 DS18B20 单总线吃透了,之后再去理解 Modbus 的帧接收、甚至去看嵌入式内核源码,都会有底气得多。
本文还有配套的精品资源,点击获取