简介:面向电子、自动化及相关专业毕业生与单片机初学者的毕业设计文档,围绕基于AT89C52单片机与DS18B20智能温度传感器的数字温度计展开,重点解决温度检测、LED显示及上下限报警的软硬件设计问题。压缩包内仅含1个doc文件,大小约301KB,以单篇文档形式集中呈现课题背景、Proteus与Keil开发工具简介、系统概述、传感器原理、单片机功能应用及测温报警电路等内容,便于按章节查阅和整理。目前已有157人学习下载,可作为毕业设计选题参考、课程设计说明书模板与方案撰写素材,也能帮助读者梳理DS18B20三线制连接、温度范围与分辨率设置、报警逻辑及软硬件模块划分,适合需要快速搭建数字温度计设计框架并查漏补缺的读者。
1. 从一支体温计说起:数字温度计到底在做什么
拿到“基于单片机的数字温度计”这个题目,很多人第一反应是:这不就是个数显温度计吗?拆开看其实只有三件事——一颗元件负责感知冷热,一颗芯片负责思考,一块屏幕负责说话。家里的电子体温计、冰箱门上的温度显示、机柜里的过热保护,走的是同一条路。搞懂这条路,你顺带就理解了物理量怎么变成电信号、电信号怎么变成数字、数字又怎么变成你看到的那句“25.0℃”。它不神秘,但每一段都藏着容易被忽略的细节,而这些细节恰恰是初学者最容易栽跟头的地方。
2. 为什么老式测温电路那么麻烦:模拟测温与数字测温的分岔
在单片机普及之前,测一个温度是件系统工程。你得先挑敏感元件,再配放大电路,再配滤波,再配模数转换,最后才轮到单片机算。中间任何一环出问题,最后显示的数字都不作数。这段历史不是白走的,理解了它,才知道后来那颗三只脚的小芯片到底省掉了什么,也才知道省掉的东西是不是真的可以省。
2.1 热电偶和热电阻:它们交出来的是电压,不是温度
热电偶的原理说起来很朴素:把两种不同的金属丝焊在一起,接头受热,两端就会产生一个很小的电压,这个现象叫热电动势。以常用的 K 型热电偶为例,温度每升高 1℃,输出的电压大约只变化 41 微伏。41 微伏是什么概念?一节干电池是 1.5 伏,两者差了将近四万倍。这么微弱的信号,不放大根本没法用,而一放大,放大器自己的噪声和失调就跟着进来了。
更麻烦的是,热电偶测的其实是“两端温差”,不是绝对温度。它的冷端必须知道是多少度,才能反推出热端温度,这一步叫冷端补偿。冷端温度随环境漂移,补偿就得跟着做,于是又多了一个测温通道、一颗环境温度传感器和一段补偿算法。一个看起来简单的测温点,背后常常挂着三四个器件。
热电阻走的是另一条路。铂电阻的阻值随温度升高而增大,关系相当稳定,工业上常拿它做标准。但它要测电阻,就得通一个已知的激励电流,测电流在上面产生的压降。电流一大,电阻自己就发热,测出来的温度比实际偏高,这叫自热效应;电流一小,信号又弱得可怜,容易被噪声吞掉。这个平衡点得反复试。热敏电阻便宜、灵敏,可惜阻值随温度变化的曲线弯得厉害,要算准就得查表或者拟合多项式。
共同点很清楚:它们交出来的都是电压、电流或阻值,是一根“会伸缩的液柱”,刻度得你自己画上去。
2.2 A/D 转换这道坎:模拟链路为什么越走越长
从敏感元件到单片机,中间要过的第一道关是模数转换。信号太弱,先放大;放大之后混进了工频干扰,再滤波;滤波之后要有一个稳定的参考电压,因为 A/D 转换本质上是拿输入信号去和基准比,基准不准,读数就不准。这一串下来,板子上已经站了一排器件。
误差是累积的。放大器的失调电压折算到输入端可能是几十微伏,基准电压的初始精度可能是千分之几,A/D 本身的线性度又是另一笔账,再加上量化误差和噪声。每一项单看都不大,凑在一起,最后那个小数点后一位就不太敢信了。做过这类项目的工程师大多有一段血泪经验:电路板焊完,程序跑通,显示的数字却和标准温度计差了两度,查了三天才发现是基准芯片的接地走线不对。
还有个隐形的代价——器件数量。放大器、基准、A/D、滤波电容、保护二极管,一个测温点就要占掉一片板子。产品要做小、要做便宜,这些都得砍。而砍掉它们的前提,是有人把这条链路整个折叠起来。
2.3 单总线方案省掉了哪几段路
DS18B20 这类数字温度传感器的思路,就是把整条模拟链路折叠进一颗三脚芯片里。它内部有敏感元件、有 A/D、有一块存放换算结果的寄存器,还有一套数字通信接口。对外它不交电压,直接交一个数字——你读到的就是温度值,只不过单位是“十六分之一摄氏度”的整数。
这样一折叠,省掉的东西很具体:外部放大器不用了,外部基准不用了,外部 A/D 不用了,大部分标定工作也不用做了。单片机这边只要会按时序把线拉低、拉高,会读位,就能拿到温度。整个测温点的外围只剩一颗上拉电阻,这是它最讨人喜欢的地方。
代价也很清楚。第一是速度慢,12 位分辨率下一次转换典型要 750 毫秒,比起模拟链路微秒级的响应差了几个量级,测緩变温度无所谓,测快速变化就力不从心。第二是时序严格,单总线没有时钟线,收发双方全靠“低电平持续多久”对表,延时函数稍微不准,读回来就是一串乱码。第三是线长和节点数受限,总线上挂的器件越多、线拉得越长,分布电容越大,波形上升沿越缓,通信越容易出错。
| 对比项 | 模拟链路(热电偶/热电阻 + A/D) | 单总线数字传感器 |
|---|---|---|
| 输出形式 | 电压或阻值,需换算 | 数字量,直读 |
| 外围器件 | 放大器、基准、A/D、滤波 | 一颗上拉电阻 |
| 标定工作量 | 每个通道单独标 | 基本免标定 |
| 响应速度 | 微秒到毫秒级 | 百毫秒级(12 位约 750 毫秒) |
| 精度上限 | 取决于所选器件,可以很高 | 由器件本身决定,典型 ±0.5℃ |
| 成本结构 | 器件多,成本高 | 单颗低廉 |
这张表说明的不是“数字方案全面胜出”,而是两条路各自适用的边界:要绝对精度和速度,模拟链路仍然不可替代;要低成本、小体积、免标定,数字方案几乎是最优解。
3. 一颗传感器加一颗单片机:数字温度计的内部骨架
整套系统的结构其实很简单:一颗传感器负责把温度变成数字,一颗单片机负责把数字变成人能看懂的东西。中间靠一根数据线连着,两头各有一个上拉电阻和一段软件时序。系统框图摊开来,就是主控、复位、时钟、显示、按键、报警、传感器这么几块,不多不少。
3.1 0.0625℃ 是怎么算出来的:传感器输出的其实是一个整数
很多人第一次看到“分辨率 0.0625℃”会觉得这是仪器的测量误差,其实它只是刻度间隔。这颗传感器内部有一个 12 位的模数转换器,把感温元件的结果量化成整数,一个整数刻度对应 1/16 摄氏度,也就是 0.0625℃。它并不直接输出“25.0”这样的浮点数,输出的是一串二进制位。
分辨率是可以调的,位数越少转换越快:
| 分辨率设置 | 最低有效位 | 满量程刻度数 | 单次转换典型时间 |
|---|---|---|---|
| 9 位 | 0.5℃ | 512 | 93.75 毫秒 |
| 10 位 | 0.25℃ | 1024 | 187.5 毫秒 |
| 11 位 | 0.125℃ | 2048 | 375 毫秒 |
| 12 位 | 0.0625℃ | 4096 | 750 毫秒 |
读回来的两个字节要拼成一个 16 位有符号整数。比如低字节是 0x91、高字节是 0x01,拼起来是 0x0191,换成十进制是 401,401 乘以 0.0625 等于 25.0625℃,这就是当前温度。负温度用补码表示,比如 -25.0625℃ 对应 0xFE6F,按有符号整数解释之后再乘 0.0625 就对了。
这里有个很实用的习惯:整条链路都用“摄氏度乘以 16”的整数运算,最后显示的时候再拆整数位和小数位,全程不碰浮点。51 系列单片机做一次浮点乘除要几百个机器周期,能省则省,而且整数运算不会引入额外的舍入误差。
3.2 AT89C52 在这套系统里到底做哪几件事
AT89C52 是 51 家族里很经典的一颗:8 位 CPU,片内 4KB 程序存储器,128 字节数据存储器,4 个 8 位并行 I/O 口,2 个 16 位定时/计数器,5 个中断源分两个优先级,1 个全双工串口,40 个引脚。这个配置放到今天看很朴素,但撑起一个数字温度计绰绰有余。
它干的活可以归成四件。第一件是造时序,单总线没有时钟线,复位脉冲拉低多久、写 1 时隙拉低多久、读时隙什么时候采样,全靠软件延时掰出来,12MHz 晶振下一个机器周期正好 1 微秒,用循环凑时间比较顺手。第二件是发命令,按协议依次送出跳过 ROM、启动转换、读暂存器这几个字节。第三件是换算和拆段码,把 16 位原始值变成摄氏度,再查表变成数码管要的七段码。第四件是比较和报警,拿当前值和设定的上下限比,超了就驱动蜂鸣器。
128 字节的数据存储器听起来少得可怜,实际上够用。整个程序不需要大数组,段码表放程序存储器,运行时变量只有当前温度、上下限、按键状态这么几个,几十字节就打住了。这也是这套方案成本能压下来的原因之一——不需要外扩任何存储器。
3.3 显示、按键、报警:人和机器打交道的那一段
显示部分用四位 LED 数码管,能显示“-55.0”到“125.0”这个范围,符号位和百位共用一位。数码管的段码如果直接用 I/O 口驱动,四位就要占掉十几个引脚,太浪费,所以常见做法是用移位寄存器串行驱动:单片机从两根线上把段码一位一位推出去,移位寄存器负责锁存和并行输出。好处是省引脚,坏处是刷新要靠软件不停循环,主循环里不能有太长的阻塞。
按键一般三个:设置、加、减。按一下设置进入上限设定,再按进入下限设定,加减键调数值,再按退出。机械按键有抖动,读一次不准,要在检测到电平变化后延时十到二十毫秒再确认一次,这就是软件消抖。这个延时不能太长,长了按键反应迟钝;也不能太短,短了消不干净。
报警部分最简单,单片机某个引脚输出高电平,通过一颗三极管驱动蜂鸣器。有的设计会在超限时让数码管闪烁,视觉和听觉双保险。如果想要更完整的信息展示,也可以换成字符型液晶模块,能同时显示当前温度、上限、下限三行内容,代价是要多占三根控制线,初始化的指令序列也长不少。
4. 一根线怎么传数据:单总线的时序、供电与那几个坑
单总线这个名字容易让人误解,以为一根线包打天下。实际上它至少还需要一根地线,供电可以走外部电源也可以从数据线上“偷”。真正让它特别的地方在于:没有时钟线,所有信息都编码在电平持续的时间长短里。
4.1 一根线上的三种状态
总线上的电平只有几种可能:主机把它拉低、两边都释放(由上拉电阻拉高)、从机把它拉低。这就是所谓的“线与”——任何一方拉低,总线就是低。所有通信都建立在这个基础上,谁拉低、拉多久,就代表什么含义。
复位是每一次对话的开场。主机把线拉低至少 480 微秒,然后释放;从机检测到这个长低电平后,等待 15 到 60 微秒,主动把线拉低 60 到 240 微秒作为应答,这叫存在脉冲。主机在这个窗口里采样,读到低电平就说明器件在线,读到高电平说明没接好或者坏了。这一步是后面所有操作的前提,读不到存在脉冲,后面全是白忙。
写一位分两种情况。写 1 是把线拉低一到十几微秒就赶紧释放,让上拉电阻把线拉回高;写 0 是把线拉低满 60 微秒以上再释放。区别只在于低电平持续的长短,从机在这个窗口里采样判断。读一位则由主机发起:主机先拉低至少 1 微秒,然后释放,从机如果要把 0 发出来就把线继续拉低,要发 1 就放任上拉拉高,主机在释放后 15 微秒内采样。
这些数字不是随便定的,是器件手册给的窗口。12MHz 晶振下延时函数好写,换成 11.0592MHz 为了串口波特率,延时常数就得重算。这就是为什么同一段代码换块板子就读不出数——看着像玄学,背后其实是时钟差了几个微秒。
4.2 寄生供电和外部供电,差的不只是一根线
这颗传感器有两种供电方式。一种是老老实实三根线:电源、地、数据。另一种叫寄生供电,电源脚直接接地,芯片从数据线上“偷电”——总线为高电平时,通过内部二极管给一个小电容充电,总线为低电平时靠这个电容撑着。
问题出在温度转换那一步。转换时芯片内部电路全开,电流会明显上升,小电容撑不住。这时候总线必须被强行拉高,通常用一个 MOS 管在转换期间把数据线直接接到电源上。忘了这一步,读回来的经常是 85℃,那是上电默认值,也是最经典的翻车现场——程序看着没问题,数字就是不对,查半天查不出所以然。
三线制多一根线,换来的是稳定。板子空间够、线不长,就用三线制,别为了省一根线给自己找麻烦。只有在探头要做得很小、引线必须极少的场合,寄生供电才值得考虑,而且要老老实实把强上拉电路加上。
提示:上拉电阻的取值通常在 4.7kΩ 左右。阻值太大,上升沿变缓,长线上容易读错;阻值太小,总线被拉低时灌电流过大,器件可能吃不消。
4.3 一次完整测温:从发命令到显示出数字
一次测温的流程是固定的:复位等应答,发跳过 ROM 命令,发启动转换命令,等转换完成,再复位,再跳过 ROM,发读暂存器命令,连读两个字节。跳过 ROM 是因为总线上只挂了一个器件,省掉 64 位地址匹配;如果挂多个,就得先读 ROM 或者指定地址匹配,否则所有器件会同时应答,数据在线上打架。
/* 12MHz 晶振,一个机器周期约 1us,延时常数按此估算 */ sbit DQ = P3^7; // 单总线数据线接 P3.7 /* 复位:拉低至少 480us,释放后等待从机拉低应答 */ bit ds_reset(void) { bit ack; DQ = 0; delay_us(480); // 复位脉冲宽度,不能短于 480us DQ = 1; // 释放总线,由上拉电阻拉高 delay_us(60); // 等从机拉低 60~240us 作为存在脉冲 ack = DQ; // 读到 0 表示器件在线 delay_us(420); // 补足整个时隙,避免影响下一次通信 return ack; } /* 写一个字节,低位先出 */ void ds_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; if (dat & 0x01) { // 写 1:拉低 1~15us 后尽快释放 delay_us(2); DQ = 1; delay_us(60); } else { // 写 0:拉低满 60us 以上再释放 delay_us(60); DQ = 1; delay_us(2); } dat >>= 1; } } /* 读一个字节,低位先出 */ unsigned char ds_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; DQ = 0; delay_us(2); // 拉低 1us 以上发起读时隙 DQ = 1; delay_us(10); // 释放后 15us 内采样 if (DQ) dat |= 0x80; delay_us(50); // 补足时隙 } return dat; } /* 读一次温度,返回摄氏度乘 16 的整数,全程避开浮点 */ int ds_read_temp_x16(void) { unsigned char tl, th; int raw; ds_reset(); ds_write_byte(0xCC); // 跳过 ROM:总线上只有一个器件 ds_write_byte(0x44); // 启动一次温度转换 delay_ms(750); // 12 位分辨率下的典型转换时间 ds_reset(); ds_write_byte(0xCC); ds_write_byte(0xBE); // 读暂存器 tl = ds_read_byte(); // 低字节先出 th = ds_read_byte(); // 高字节后出 raw = (th << 8) | tl; // 拼成 16 位有符号整数 return raw; // 乘以 1/16 即得摄氏度 }几个参数值得单独说明。延时函数delay_us和delay_ms必须按实际晶振校准,差一点点就可能错过采样窗口,这是整套代码里最不能想当然的部分。0xCC是跳过 ROM,单器件时用它最省事,多器件必须换成0x33读 ROM 或0x55匹配地址。0x44启动转换,0xBE读暂存器,暂存器前两个字节就是温度,低字节在前。返回值是“摄氏度乘以 16”,显示时先除以 16 得整数部分,余数再乘 625 得到小数部分,或者更简单——只取一位小数,用raw * 10 / 16就够了。
如果读回来是 85.0℃(raw 等于 0x0550),基本可以判定是上电默认值,说明转换没启动或者供电时序有问题。如果读回来是 -127℃,通常是总线被拉死或者器件没应答,先查接线,再查上拉电阻。
5. 易混概念辨析:分辨率、精度、量程与仿真到底差在哪
这套方案里有一批词,字面上很像,实际含义差得远。初学者最容易在面试或者答辩现场栽在这几个问题上,因为说错一句,后面整段解释都站不住。
5.1 分辨率不等于精度
最容易混的一点:看到“0.0625℃”就以为测量误差是 0.0625℃。分辨率是刻度间隔,精度是读数和真值的偏差,两回事。一把尺子可以刻到毫米,但尺子本身可能受热胀冷缩影响偏了半毫米,刻度细不等于量得准。
这颗器件的手册给出的典型误差在 -10℃ 到 +85℃ 区间大约是 ±0.5℃,全温区还要更宽。除此之外,实际误差还跟供电电压、布线、探头自热、安装位置有关。要谈精度,得看误差指标,不能看分辨率。这个区别在写论文的时候尤其要小心,把分辨率当精度写进去,是很典型的硬伤。
5.2 传感器量程不等于系统量程
器件本身能测 -55℃ 到 125℃,但很多设计里写的是 0℃ 到 100℃。为什么缩了?因为系统量程还受显示位数、按键设定范围、结构封装、供电条件限制。四位数字加一个符号位能显示 -55.0 到 125.0,但按键设定的报警上下限往往收窄到常用区间,超出这个区间的读数虽然能出来,指标却不一定保证。
工程上写 0 到 100℃ 的意思是“在这个范围内我承诺指标”,不是说超出就一定读不出来。这个区别在给客户看规格书的时候很重要,写宽了做不到,写窄了显得能力不够,得按实测数据来定。
5.3 数字输出不等于不用调试
很多人以为数字信号“要么对要么错”,不存在调不准的问题。实际上单总线是时序敏感协议,中断一打断,读时隙就错过了采样窗口。常见的现象是:平时读数正常,一开定时器中断就跳到 85℃,或者干脆变成 -127℃。这不是器件坏了,是时序被打乱了。
解决办法是把读写时序放进临界区,读的时候临时关中断,或者干脆用汇编精确控制周期数。另外上拉电阻取值、线长、分布电容都会影响上升沿,线拉长了,波形爬得慢,采样点就可能落在门槛电压附近,读出来时对时错。
5.4 仿真跑通不等于实物能用
仿真软件里的器件是理想模型,不会体现上拉电阻不足、电源纹波、寄生供电电流不够、探头引线的分布参数。仿真通过只说明逻辑是对的,时序在理想条件下成立,跟实物能不能用是两件事。
见过太多这样的情况:仿真里数码管跳得漂漂亮亮,焊出来一上电就是 85℃。拿示波器一测,复位脉冲根本不到 480 微秒,或者转换期间总线没被强上拉。所以实物调试这一步,示波器基本是必需品,光靠看显示数字猜原因,效率太低。
6. 这套方案什么时候用,什么时候该趁早换
判断标准其实不复杂,就看三件事:精度要求、响应速度、使用环境。三件事都不苛刻,这套方案几乎没有对手;任何一件苛刻,就得换思路。
适合用的场景很明确。单点或者少量点位的测温,线长在一两米以内,成本敏感,板面空间紧张,需要直接出数字给单片机处理,刷新率要求是秒级——比如室温监测、小家电面板、机箱过热保护、教学实验平台、毕设原型。这些场合里,一颗传感器加一颗单片机就能把活干完,外围只要一颗上拉电阻,标定工作基本为零,这是它最大的价值。
不适合的场景同样明确。宽温区高精度计量,比如需要 0.1℃ 甚至更高准确度的场合,得用铂电阻配高精度模数转换器,或者用经过单独标定的测温模块;强干扰的长距离工业现场,单总线的电平和时序都容易被干扰,应该用电流环或者差分总线;测快速变化的温度,比如气流、燃烧、电机绕组,750 毫秒的转换时间根本跟不上,热电偶才是正解;多点密集测温,总线上挂十几个器件,逐个转换加起来的刷新周期长得离谱,应该改用分布式采集。
| 场景 | 这套方案是否合适 | 更合适的做法 |
|---|---|---|
| 室温监测、面板显示 | 合适 | 直接采用 |
| 机箱、电池包过热保护 | 合适 | 直接采用,注意探头贴合 |
| 教学实验与原型验证 | 合适 | 直接采用,重点讲时序 |
| 高精度计量(0.1℃ 以内) | 不合适 | 铂电阻配高精度转换器,单独标定 |
| 长距离强干扰工业现场 | 不合适 | 电流环或差分总线传输 |
| 快速变化温度(毫秒级) | 不合适 | 热电偶配冷端补偿 |
| 几十点密集测温 | 不合适 | 分布式采集节点 |
| 防水防腐的液体测温 | 需改造 | 加金属探头封装,裸封装不能直接泡 |
最后说个自己的习惯。早年做这类小系统,我总想着先把代码写完再上电,结果每次都是显示 85℃ 或者 -127℃,然后拿着万用表一根线一根线量。后来改了顺序:焊完板子先不写业务代码,只写一个最小的复位加读存在脉冲的程序,用示波器确认复位脉宽够 480 微秒、存在脉冲能读到,再往上叠转换和显示。慢是慢了一点,但从那以后基本没在这类项目上返工过。每次动手前都强制走一遍这个最小验证,省下来的时间远比它花掉的多。希望帮到你。
本文还有配套的精品资源,点击获取