简介:这份资源面向电子信息、自动化等专业的课程设计与单片机入门学习者,提供一套基于51单片机的温度报警系统完整实现方案,采用AT89C52/STC89C52搭配DS18B20温度传感器、数码管与蜂鸣器,可完成0至99度范围内的实时测温与超限报警。包内包含源代码与配套论文,压缩包约9.45MB,以程序源码和文档类文件为主,源码对应按键扫描、温度读取、数码管动态显示与报警逻辑,论文则覆盖系统原理、硬件电路与软件流程,便于对照理解整体设计。系统支持设置温度上下限并具备掉电保存功能,按键可连加连减,低于下限或高于上限时蜂鸣器与LED同时报警,数码管带小数点显示,并配有上电复位、手动复位与晶振电路。目前已有1121人学习下载,适合作为课设、毕设或Proteus仿真的参考模板,帮助读者快速搭建可运行的温度报警装置并掌握51单片机外设驱动思路。
1. 基于51单片机的温度报警系统:从DS18B20到蜂鸣器,一个能写进论文的完整方案
很多人第一次接触单片机课程设计,拿到的题目就是“基于51单片机的温度报警系统”。看起来简单——读温度、比阈值、超限报警,三个动作。但真动手做,你会发现事情没那么简单:DS18B20的时序死活读不出数据、数码管显示跳变、蜂鸣器一响单片机就复位。我带过几届学生的课程设计,也帮某公司的硬件岗新人做过入职练手项目,这个题目翻车的比例远比想象中高。它本质上是一个完整的嵌入式最小系统:传感器采集、MCU处理、人机交互、执行器输出,四块全齐。适合谁?电子类专业做课程设计的学生、刚转嵌入式的开发者、需要快速搭一个温度监控原型的工程师。下面我把整个方案拆开讲,从硬件选型到代码实现到论文写作,每一步都给出可复现的细节。
2. 硬件选型与最小系统搭建:为什么是DS18B20而不是热敏电阻
2.1 传感器选型的三个实际考量
温度传感器可选的东西很多:NTC热敏电阻、LM35模拟传感器、DS18B20数字传感器、DHT11温湿度模块。做报警系统,我一般会推荐DS18B20,原因有三。
第一是精度和量程。DS18B20在-10°C到+85°C范围内精度是±0.5°C,分辨率可配置为9到12位,最小能到0.0625°C。NTC热敏电阻虽然便宜,但线性度差,需要查表或拟合,标定麻烦。LM35输出模拟电压,精度不错,但51单片机没有内置ADC,你得外挂ADC0832或XPT2046,多一颗芯片多一份成本。
第二是接口。DS18B20用单总线协议,一根数据线就能通信,占用IO口少。51的IO资源本来就紧张,省一根是一根。
第三是抗干扰。数字信号比模拟信号在长导线上的衰减和噪声容忍度高得多。温度报警系统经常要把传感器放到几米外的设备旁边,模拟方案在这个距离上基本不可用。
DHT11虽然也能读温度,但精度只有±2°C,而且响应速度慢(采样周期不低于1秒),做报警系统响应太迟钝。
2.2 最小系统的元件清单与接线
整个系统需要的元件不多,我列一个表:
| 元件 | 型号/规格 | 数量 | 作用 |
|---|---|---|---|
| 单片机 | STC89C52RC | 1 | 主控 |
| 温度传感器 | DS18B20(TO-92封装) | 1 | 采集温度 |
| 晶振 | 11.0592MHz | 1 | 系统时钟 |
| 复位电容 | 10μF电解 | 1 | 上电复位 |
| 复位电阻 | 10kΩ | 1 | 复位电路 |
| 去耦电容 | 0.1μF瓷片 | 2 | 电源滤波 |
| 上拉电阻 | 4.7kΩ | 1 | DS18B20数据线上拉 |
| 数码管 | 共阳四位一体 | 1 | 显示温度 |
| 蜂鸣器 | 5V有源 | 1 | 报警 |
| LED | 红色5mm | 1 | 报警指示 |
| 三极管 | S8550(PNP) | 1 | 蜂鸣器驱动 |
| 限流电阻 | 1kΩ | 1 | LED限流 |
| 排阻 | 470Ω | 2 | 数码管段选限流 |
接线方面,DS18B20的数据脚接P3.7,同时通过4.7kΩ上拉到VCC。数码管段选接P0口(P0需要外接上拉排阻,因为51的P0是开漏输出),位选接P2.0到P2.3。蜂鸣器通过S8550驱动,基极接P1.0。LED接P1.1。
这里有一个容易忽略的点:DS18B20的DQ引脚是开漏输出,必须加上拉电阻,否则读不到数据。我见过至少三个人的板子因为这个电阻没焊,调了一整天。
2.3 电源与滤波的实际处理
51单片机对电源噪声比较敏感,尤其是蜂鸣器启动瞬间的电流冲击。我在VCC和GND之间并一个0.1μF瓷片电容,位置尽量靠近单片机的电源引脚。蜂鸣器两端也并一个0.1μF电容,减少对电源的干扰。
如果用的是开发板,这些通常已经做好了。但如果是自己焊的洞洞板,这一步不能省。血泪经验:蜂鸣器一响单片机就复位,十有八九是电源滤波没做好。
3. DS18B20单总线驱动:时序、复位与温度读取的完整代码
3.1 单总线协议的时序要求
DS18B20的通信全靠一根线,靠的是严格的时序。51单片机在11.0592MHz晶振下,一个机器周期是1.085μs。单总线协议的关键时间参数如下:
| 操作 | 时序要求 | 51延时实现 |
|---|---|---|
| 复位脉冲 | 拉低至少480μs | 延时500μs |
| 存在脉冲 | 释放后15-60μs内DS18B20拉低 | 延时60μs后读取 |
| 写0时隙 | 拉低60-120μs | 延时80μs |
| 写1时隙 | 拉低1-15μs后释放 | 延时10μs后释放 |
| 读时隙 | 拉低1-15μs后采样 | 延时10μs后读取 |
这些延时不能用库函数,得自己写。我一般用_nop_()或者简单的for循环。
3.2 复位、写字节、读字节的代码实现
#include <reg52.h> #include <intrins.h> sbit DQ = P3^7; // DS18B20数据线 // 微秒级延时,11.0592MHz下约1μs void delay_us(unsigned int us) { while(us--) { _nop_(); _nop_(); _nop_(); _nop_(); } } // 复位DS18B20,返回0表示存在,1表示不存在 bit ds_reset(void) { bit presence; DQ = 0; delay_us(500); // 拉低至少480μs DQ = 1; delay_us(60); // 等待15-60μs presence = DQ; // 读取存在脉冲,DS18B20会拉低 delay_us(400); // 等待时序结束 return presence; } // 写一个字节,LSB先行 void ds_write_byte(unsigned char dat) { unsigned char i; for(i = 0; i < 8; i++) { DQ = 0; _nop_(); _nop_(); DQ = dat & 0x01; // 写0则保持低,写1则拉高 delay_us(80); // 保持60-120μs DQ = 1; dat >>= 1; } } // 读一个字节,LSB先行 unsigned char ds_read_byte(void) { unsigned char i, dat = 0; for(i = 0; i < 8; i++) { DQ = 0; _nop_(); _nop_(); DQ = 1; // 释放总线 _nop_(); _nop_(); dat >>= 1; if(DQ) dat |= 0x80; // 采样 delay_us(60); // 等待时隙结束 } return dat; }逻辑说明:ds_reset先拉低总线500μs,然后释放,等待60μs后读取总线状态。如果DS18B20存在,它会拉低总线,读到0。ds_write_byte从最低位开始,每个位先拉低,然后根据数据位决定是否拉高,保持80μs后释放。ds_read_byte先拉低产生读时隙,然后释放,在10μs左右采样总线状态。
参数说明:延时函数的精度直接影响通信成功率。如果晶振不是11.0592MHz,需要重新计算延时。比如用12MHz晶振,机器周期是1μs,延时函数要相应调整。
3.3 温度转换与读取的完整流程
DS18B20的温度读取分两步:先发转换命令,等待转换完成,再读暂存器。
// 读取温度值,返回单位为0.1°C的整数 int ds_read_temp(void) { unsigned char tl, th; int temp; ds_reset(); ds_write_byte(0xCC); // 跳过ROM匹配 ds_write_byte(0x44); // 启动温度转换 // 等待转换完成,12位精度需要750ms delay_us(750000); // 实际用延时函数或定时器 ds_reset(); ds_write_byte(0xCC); ds_write_byte(0xBE); // 读暂存器 tl = ds_read_byte(); // 读低字节 th = ds_read_byte(); // 读高字节 temp = (th << 8) | tl; // 处理负温度 if(temp & 0x8000) { temp = ~temp + 1; temp = -temp; } // 12位精度,低4位是小数,转换为0.1°C单位 temp = temp * 10 / 16; return temp; }逻辑说明:0xCC是跳过ROM命令,总线上只有一个DS18B20时用。0x44启动转换,0xBE读暂存器。温度值存在两个字节里,高字节的高5位是符号位。负温度用补码表示,需要取反加一。12位精度下,低4位是小数部分,乘以10除以16就得到0.1°C为单位的整数。
参数说明:转换时间取决于分辨率。9位精度93.75ms,10位187.5ms,11位375ms,12位750ms。如果对实时性要求高,可以降低分辨率。但报警系统一般对响应速度要求不高,用12位精度更准。
注意:delay_us(750000)在实际代码中不能用循环延时,因为750ms的循环延时会占用大量CPU时间。我一般用定时器中断来计时,或者简单点,在等待期间刷新数码管显示。
4. 报警逻辑与显示驱动:阈值设定、数码管动态扫描与蜂鸣器控制
4.1 报警阈值的设定与迟滞处理
最简单的报警逻辑是:温度超过上限就响,低于下限就响。但实际用的时候,如果温度在阈值附近波动,蜂鸣器会断断续续地响,很烦人。我一般加一个迟滞区间,比如上限设30°C,但只有降到28°C以下才解除报警。
#define TEMP_HIGH 300 // 上限30.0°C #define TEMP_LOW 100 // 下限10.0°C #define HYSTERESIS 20 // 迟滞2.0°C bit alarm_state = 0; // 当前报警状态 void check_alarm(int temp) { if(!alarm_state) { // 当前不报警,检查是否触发 if(temp > TEMP_HIGH || temp < TEMP_LOW) { alarm_state = 1; } } else { // 当前报警,检查是否解除 if(temp < TEMP_HIGH - HYSTERESIS && temp > TEMP_LOW + HYSTERESIS) { alarm_state = 0; } } // 控制蜂鸣器和LED if(alarm_state) { BEEP = 0; // 有源蜂鸣器低电平响 LED = 0; // LED亮 } else { BEEP = 1; LED = 1; } }逻辑说明:alarm_state记录当前是否在报警。触发和解除用了不同的阈值,中间留了2°C的迟滞区间。这样温度在30°C附近波动时,不会频繁切换报警状态。
参数说明:迟滞区间的大小取决于系统的热惯性和温度波动幅度。如果传感器响应快、环境温度稳定,迟滞可以小一点,比如1°C。如果环境温度波动大,迟滞要相应加大。
4.2 数码管动态扫描的实现
四位一体数码管用动态扫描,每1-2ms切换一位。刷新率太低会闪烁,太高则亮度不够。
// 共阳数码管段码表,0-9 unsigned char code seg_code[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90 }; // 显示缓冲区,4位 unsigned char disp_buf[4]; // 定时器0中断,1ms一次 void timer0_isr(void) interrupt 1 { static unsigned char digit = 0; TH0 = (65536 - 1000) / 256; // 1ms定时 TL0 = (65536 - 1000) % 256; // 消隐 P0 = 0xFF; P2 = (P2 & 0xF0) | 0x0F; // 关闭所有位选 // 输出段码 P0 = seg_code[disp_buf[digit]]; // 打开当前位 P2 = (P2 & 0xF0) | (~(1 << digit) & 0x0F); digit++; if(digit >= 4) digit = 0; }逻辑说明:定时器0每1ms中断一次,每次刷新一位数码管。先消隐(关闭所有位选,段选输出0xFF),然后输出当前位的段码,再打开对应的位选。disp_buf里存的是要显示的数字,比如温度25.3°C,存为{2, 5, 3, 0},最后一位不显示或显示单位。
参数说明:定时器初值(65536 - 1000)对应1ms,前提是晶振11.0592MHz且定时器工作在模式1(16位)。如果晶振不同,需要重新计算。刷新率是4ms一轮,即250Hz,肉眼看不到闪烁。
4.3 主循环的组织方式
主循环里做三件事:读温度、更新显示缓冲、检查报警。读温度不能太频繁,DS18B20转换需要时间,我一般500ms读一次。
void main(void) { int temp; // 初始化 TMOD = 0x01; // 定时器0模式1 TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; ET0 = 1; // 允许定时器0中断 EA = 1; // 开总中断 TR0 = 1; // 启动定时器0 while(1) { temp = ds_read_temp(); // 更新显示缓冲 if(temp < 0) { disp_buf[0] = 0xBF; // 负号 temp = -temp; } else { disp_buf[0] = temp / 100; } disp_buf[1] = (temp / 10) % 10; disp_buf[2] = temp % 10; disp_buf[3] = 0x00; // 不显示 check_alarm(temp); // 延时500ms delay_ms(500); } }逻辑说明:主循环每500ms执行一次,读温度、更新显示、检查报警。显示缓冲的更新考虑了负温度的情况,第一位显示负号。disp_buf[3]设为0x00表示不显示,因为温度只用到三位。
参数说明:500ms的循环周期是经验值。太短了DS18B20来不及转换,太长了显示更新慢。如果要用按键设置阈值,主循环里还要加按键扫描。
5. 避坑与排查:DS18B20读不出、数码管闪烁、蜂鸣器复位
5.1 DS18B20始终读不到数据
现象:ds_reset()始终返回1,读到的温度是0或85。
原因:最常见的是上拉电阻没接或阻值不对。DS18B20的DQ引脚是开漏输出,没有上拉电阻无法拉高。其次是延时函数不准,时序不满足要求。还有可能是传感器供电不足,DS18B20在转换期间电流可达1.5mA,如果电源内阻大,电压会跌落。
解决:先用万用表测DQ引脚在空闲时是否为高电平。如果不是,检查上拉电阻。然后用示波器看复位脉冲宽度是否达到480μs。如果没有示波器,用LED接在DQ上,看复位时是否闪一下。延时函数可以用示波器校准,或者用定时器精确延时。
5.2 数码管显示闪烁或亮度不均
现象:数码管某一位特别亮或特别暗,或者整体闪烁。
原因:动态扫描的刷新率不对。刷新率低于50Hz会闪烁,高于200Hz亮度不够。另外,段选和位选的切换顺序有问题,如果先开位选再改段选,会出现鬼影。
解决:确保定时器中断周期在1-2ms。在中断里先消隐(关闭位选、段选输出0xFF),再输出段码,最后打开位选。如果还有鬼影,在段选和位选之间加一个小延时。
5.3 蜂鸣器一响单片机就复位
现象:温度超限,蜂鸣器启动,单片机重启,温度显示归零。
原因:蜂鸣器启动瞬间电流大,导致电源电压跌落,单片机欠压复位。有源蜂鸣器的工作电流一般在20-30mA,如果电源容量不足或滤波不好,就会出问题。
解决:在蜂鸣器两端并一个0.1μF电容,在单片机电源引脚附近加一个10μF电解电容。如果还不行,用三极管驱动蜂鸣器,基极串一个1kΩ电阻,减少对单片机IO口的冲击。电源最好用独立的稳压芯片,比如7805,不要和电机等大电流设备共用电源。
5.4 温度值跳变严重
现象:读到的温度在±2°C范围内频繁跳动。
原因:DS18B20的分辨率设置过高,或者电源噪声大。12位分辨率下,最后几位本身就不稳定。另外,如果传感器引线太长,没有屏蔽,也会引入噪声。
解决:降低分辨率到10位或11位,牺牲一点精度换稳定性。或者在软件里做滑动平均滤波,连续读5次取中间值。引线长的话,用屏蔽线,屏蔽层接地。
5.5 论文写作中数据与代码对不上
现象:论文里写的温度精度是0.1°C,但代码里实际显示的是整数。
原因:代码里temp = temp * 10 / 16这一步做了转换,但显示的时候只用了整数部分。如果论文里写了0.1°C精度,代码里要把小数位也显示出来。
解决:显示缓冲里增加一位小数。比如温度25.3°C,disp_buf存{2, 5, 3, 0},最后一位显示小数点后的数字。或者直接用浮点数,但51单片机浮点运算慢,不推荐。
6. 从课程设计到论文:让方案经得起答辩的四个技巧
6.1 用实测数据代替理论描述
论文里不要只写“DS18B20精度±0.5°C”,要给出实测数据。比如用标准温度计对比,在10°C、20°C、30°C、40°C四个点各测5次,算出误差范围。我一般会做一个表格:
| 标准温度 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均误差 |
|---|---|---|---|---|---|---|
| 10.0°C | 10.1 | 10.0 | 9.9 | 10.1 | 10.0 | +0.02°C |
| 20.0°C | 20.2 | 20.1 | 20.0 | 20.1 | 20.1 | +0.10°C |
| 30.0°C | 30.1 | 30.2 | 30.0 | 30.1 | 30.1 | +0.10°C |
| 40.0°C | 40.3 | 40.2 | 40.1 | 40.2 | 40.2 | +0.20°C |
这样的数据比任何文字描述都有说服力。答辩老师一看就知道你真的测过。
6.2 报警响应时间的测量方法
报警响应时间是从温度超过阈值到蜂鸣器响的时间。这个时间取决于DS18B20的转换周期和主循环的周期。我的方案里,转换周期750ms,主循环500ms,最坏情况下响应时间是1.25秒。
测量方法:用示波器同时抓DS18B20的DQ信号和蜂鸣器的驱动信号,看从温度转换完成到蜂鸣器启动的时间差。没有示波器的话,用秒表粗略测,多测几次取平均。
论文里要写清楚这个时间的计算过程,以及如何优化。比如把主循环周期缩短到100ms,响应时间可以降到850ms。
6.3 阈值设定的依据
不要随便写一个30°C。要说明这个阈值是怎么来的。比如做的是温室监控,查阅资料得知大多数蔬菜的适宜温度上限是28-30°C,所以设30°C。做的是机房监控,设备运行温度上限一般是35°C,所以设35°C。
论文里要有一段“阈值设定依据”,引用具体的应用场景和参考数据。这样才显得不是拍脑袋定的。
6.4 代码注释与论文附录的对应
论文附录里放代码,但不要放全部。挑核心函数:ds_reset、ds_read_temp、check_alarm、timer0_isr。每个函数前面写一段说明,解释这个函数的作用、输入输出、关键参数。
代码里的注释要详细,但不要每行都注释。关键时序、关键判断、关键参数要注释。比如delay_us(500)后面写// 拉低至少480μs,temp = temp * 10 / 16后面写// 12位精度转0.1°C。
答辩的时候,老师经常会指着某一行代码问“这里为什么这么写”。如果注释清楚,你答起来也顺。
6.5 一个我踩过的坑
最后说一个我自己的教训。第一次做这个题目的时候,我把DS18B20的转换命令和读命令放在同一个函数里,中间用delay_ms(750)等待。结果数码管在等待期间完全不刷新,显示卡住。后来改成用定时器中断刷新数码管,主循环里用状态机的方式分步执行转换和读取,才解决了这个问题。
所以,如果你的系统里有多个任务(读温度、刷新显示、扫描按键),不要用阻塞延时。用定时器中断做时间基准,主循环里用状态机切换任务。这个习惯一旦养成,后面做更复杂的系统会轻松很多。
希望帮到你。
本文还有配套的精品资源,点击获取