☰
AT89C51+DS18B20温度计设计:从原理到调试的完整工程实践
2026/10/15 6:04:58 网站建设 项目流程

简介:这份资源是面向电子信息、自动化等专业学生及单片机初学者的课程设计资料,围绕AT89C51单片机与DS18B20数字温度传感器,解决简易温度计从硬件搭建到软件编程的完整实现问题。压缩包内共1个doc文档,约602KB,内容涵盖主控制器、四位共阴极数码管显示、温度采集、复位、时钟与报警电路六大模块,并附有摘要、目录、硬件设计与软件流程分析,可直接作为课程设计报告或答辩参考。文档重点讲解DS18B20的数据采集过程、AT89C51的编程方法以及上下限报警温度的设定思路,读者能据此掌握温度采集、显示与超限报警的完整设计链路,理解各模块电路连接与程序判断逻辑。目前已有185人学习下载,适合需要快速完成单片机课程设计、撰写实验报告或进行仿真调试的读者参考借鉴。

1. 从一块“跑不起来”的板子说起:AT89C51+DS18B20 温度计到底能干什么

很多人第一次做单片机课程设计,选的就是“基于 AT89C51 单片机的温度计设计”这个题目。原因很直接:器件便宜、资料多、逻辑不复杂,看起来照着连线上电就能出数。但真正动手之后,翻车点往往集中在几个地方——DS18B20 读出来永远是 85℃、数码管显示乱码、负温度直接变成正数、报警阈值改不动。这套资源包解决的正是这些问题:它给出了一套完整的 AT89C51 + DS18B20 + 四位共阴数码管 + 蜂鸣器报警的软硬件实现,包含原理图、C 源码、Proteus 仿真和调试记录。适合正在做单片机课程设计的学生、需要快速验证单总线温度采集方案的嵌入式入门者,以及想拿一套可运行代码改造成自己项目的工程师。它不是“讲原理”的文档,而是一份能直接烧录、能仿真、能对照排查的工程底稿。

2. 硬件链路拆解:AT89C51 最小系统与 DS18B20 单总线怎么接才不翻车

2.1 主控与时钟复位:为什么 12MHz 晶振配 22pF 电容是默认答案

AT89C51 是整个系统的核心,负责存储程序、控制时序、处理温度数据并驱动显示和报警。它自带 4KB Flash、256 字节 RAM、32 根 I/O 口线、两个 16 位定时器和一个全双工串口,对于温度计这种量级的任务绰绰有余。时钟电路采用内部振荡方式,XTAL1 和 XTAL2 之间接 12MHz 晶振,两端各挂一只 22pF 瓷片电容到地。这个组合不是随便选的:12MHz 能让机器周期稳定在 1μs,方便用软件延时做 DS18B20 的微秒级时序;22pF 是晶振厂家推荐的负载电容匹配值,容值偏差太大会导致起振困难或频率漂移。

复位电路常见做法是上电复位加按键复位:10μF 电解电容正极接 VCC、负极接 RST,同时 RST 通过 10kΩ 电阻接地,再并联一个按键。上电瞬间电容充电使 RST 维持高电平足够时间,单片机完成复位;按下按键则手动拉高 RST 重新启动。这里有个容易忽略的点:电容极性接反会导致复位不可靠,表现为程序偶尔跑飞或根本不启动。

提示:如果手头没有 12MHz 晶振,11.0592MHz 也能用,但所有基于 1μs 机器周期计算的延时函数都要重新核算,否则 DS18B20 时序会偏。

2.2 DS18B20 单总线:一根线完成供电与通信的代价

DS18B20 是这套设计里最“娇气”的器件。它把温度传感器、ADC、寄存器全部集成在一个三脚封装里,测温范围 -55℃ 到 +125℃,分辨率可编程为 9 到 12 位,对应最小 0.0625℃ 的步进。数据通过单根 DQ 线串行输出,这意味着供电和通信复用同一根线——寄生电源模式下 VDD 必须接地,靠 DQ 线在空闲时给内部电容充电维持工作。

硬件连接上,DQ 引脚接 AT89C51 的某个 I/O 口(常见是 P3.7 或 P2.x),同时必须接一只 4.7kΩ 上拉电阻到 VCC。这个电阻不是可选项:单总线在空闲时靠上拉电阻拉高,主机和从机都通过开漏输出拉低来发送数据。没有上拉电阻,DQ 线永远处于浮空状态,读回来全是随机值。很多“读出来一直是 85℃”的案例,根源就是上拉电阻漏焊或者阻值选得太大。

DS18B20 内部有 64 位光刻 ROM、温度寄存器、TH/TL 报警触发器和配置寄存器。上电默认分辨率是 12 位,转换时间最长 750ms。如果程序在发出温度转换命令后没有给足等待时间就去读暂存器,读到的就是上一次的旧值或者上电默认的 85℃。这是新手最常踩的坑之一。

2.3 四位共阴数码管与报警电路:显示和报警的硬件边界

显示部分用四位一体共阴数码管,段选线并联、位选线独立。AT89C51 的 P0 口通常接段选(需要外接上拉电阻,因为 P0 是开漏输出),P2 口的部分引脚接位选。共阴数码管的公共端接地,段选给高电平点亮对应段。动态扫描方式下,每位数码管轮流点亮,利用人眼视觉暂留形成稳定显示。扫描频率不能太低,否则会看到闪烁;也不能太高,否则亮度不足。常见做法是每位数码管点亮 1 到 3ms,四位数总刷新周期控制在 10ms 以内。

报警电路由蜂鸣器和 LED 指示灯组成。当测量温度超过设定的上限或低于下限时,单片机输出信号驱动蜂鸣器发声、LED 点亮。蜂鸣器分有源和无源两种:有源蜂鸣器给高电平就响,无源蜂鸣器需要输出方波驱动。资源里的代码用的是无源蜂鸣器方案,通过定时翻转 I/O 口产生方波。如果换成有源蜂鸣器,这部分代码要相应简化,否则声音会断断续续。

注意:共阴数码管的位选信号是低电平有效,共阳则是高电平有效。买器件时一定要确认型号,接反了要么不亮,要么显示乱码。

3. 软件时序与数据处理:DS18B20 驱动和温度换算的代码级实现

3.1 单总线复位与读写时序:微秒级延时为什么不能随便改

DS18B20 的所有通信都由复位脉冲开始。主机拉低 DQ 线至少 480μs,然后释放,等待 15 到 60μs 后检测从机是否存在。如果从机在线,会在 60 到 240μs 内拉低 DQ 线作为应答。这个时间窗口很窄,延时函数必须准确。

// 单总线复位:拉低至少480us,然后释放等待应答 void ow_reset(void) { char presence = 1; while (presence) { while (presence) { DQ = 1; _nop_(); _nop_(); DQ = 0; delay(50); // 拉低约500us,满足复位脉冲宽度 DQ = 1; delay(6); // 释放后等待约60us presence = DQ; // 检测从机是否拉低应答 } delay(45); // 等待应答脉冲结束 presence = ~DQ; } DQ = 1; }

这段代码里delay(50)和delay(6)的参数是基于 12MHz 晶振和特定延时函数实现标定的。如果换晶振或者换编译器优化等级,这两个值必须重新用示波器抓波形确认。我一般会先用示波器看复位脉冲宽度是否落在 480 到 960μs 之间,再看应答窗口是否在 60 到 240μs 内。没有示波器的话,至少保证延时函数本身是可靠的——用定时器做基准比空循环更稳。

写命令函数write_byte负责向 DS18B20 发送一个字节。每个 bit 的写入窗口是:拉低 DQ 线 1 到 15μs,然后在 15 到 60μs 内根据写入的是 0 还是 1 决定是否释放总线。写 0 时保持低电平至少 60μs,写 1 时在 15μs 内释放。代码里用DQ = val & 0x01取出最低位,然后val >>= 1移位,循环 8 次完成一个字节。

读命令函数read_byte的逻辑类似:主机拉低 DQ 线至少 1μs,然后释放,在 15μs 内采样 DQ 线的电平。采样到高电平就是 1,低电平就是 0。读操作的时序窗口比写更紧,因为从机在主机释放总线后很快就会把数据放到线上,主机必须在 15μs 内完成采样。

3.2 温度读取与负温度处理:85℃ 和负数是怎么来的

温度读取的完整流程是:复位 → 跳过 ROM(0xCC)→ 启动转换(0x44)→ 等待转换完成 → 复位 → 跳过 ROM → 读暂存器(0xBE)→ 读两个字节的温度数据。

// 读取温度:返回原始16位数据 int read_temp(void) { ow_reset(); delay(200); // 等待转换完成,12位分辨率下最长750ms write_byte(0xCC); // 跳过ROM,单器件时可用 write_byte(0x44); // 启动温度转换 ow_reset(); delay(1); write_byte(0xCC); write_byte(0xBE); // 读暂存器 temp_data[0] = read_byte(); // 低字节 temp_data[1] = read_byte(); // 高字节 temp = temp_data[1]; temp <<= 8; temp = temp | temp_data[0]; return temp; }

这里delay(200)是等待转换完成。如果这个延时不够,读到的就是上一次的转换结果或者上电默认的 85℃。85℃ 是 DS18B20 的上电默认值,很多“读出来永远是 85℃”的问题,要么是转换等待时间不够,要么是根本没发出 0x44 命令。

负温度的处理在work_temp函数里。DS18B20 输出的 16 位数据中,高 5 位是符号位。如果温度是负的,高字节的高 5 位全是 1。代码里用if (tem > 6348)来判断:6348 对应的是 +125℃ 的原始值,超过这个值说明是负数。判断为负后,用tem = 65536 - tem取补码得到绝对值,同时置标志位n = 1。显示时在最高位数码管显示负号(0x0b 对应段码)。

// 温度数据处理:分离整数和小数,处理负温度 uchar work_temp(uint tem) { uchar n = 0; if (tem > 6348) { // 判断是否为负温度 tem = 65536 - tem; // 取补码得到绝对值 n = 1; } display[4] = tem & 0x0f; // 小数部分 display[0] = ditab[display[4]]; // 查表得到小数段码 display[4] = tem >> 4; // 整数部分 display[3] = display[4] / 100; // 百位 display[1] = display[4] % 100; display[2] = display[1] / 10; // 十位 display[1] = display[1] % 10; // 个位 r = display[1] + display[2] * 10 + display[3] * 100; if (!display[3]) { // 百位为0时消隐 display[3] = 0x0a; if (!display[2]) { display[2] = 0x0a; // 十位也为0时消隐 } } if (n) { display[3] = 0x0b; // 显示负号 } return n; }

这段代码里ditab是小数段码表,0x0a是消隐码(数码管不显示),0x0b是负号段码。消隐逻辑的作用是:当温度是 5.5℃ 时,百位和十位不显示,只显示“5.5”;当温度是 25.5℃ 时,百位不显示,显示“25.5”。这个细节在仿真里看不出来,但实际做板子的时候如果没做消隐,会看到前导零一直亮着,观感很差。

3.3 报警逻辑与主循环:阈值怎么设、蜂鸣器怎么驱动

报警函数BEEP的逻辑很简单:如果当前温度r大于 27℃,就翻转蜂鸣器引脚产生方波;否则关闭蜂鸣器。

// 报警:温度超过阈值时蜂鸣器发声 void BEEP(void) { if (r > 27) { beep = !beep; // 翻转产生方波驱动无源蜂鸣器 } else { beep = 0; // 关闭蜂鸣器 } }

这里的 27 是硬编码的上限阈值。实际使用时可以根据需要改成变量,通过按键或者串口动态设置。下限报警的逻辑类似,只是判断条件改成r < 下限值。资源里的代码只做了上限报警,如果要加上下限,在BEEP函数里加一个else if分支即可。

主函数的结构是:初始化 → 启动一次温度转换 → 循环读取温度、处理数据、刷新显示、执行报警判断。

void main() { beep = 0; dm = 0x00; w0 = 0; w1 = 0; w2 = 0; w3 = 0; for (h = 0; h < 4; h++) { display[h] = 0; } ow_reset(); write_byte(0xCC); write_byte(0x44); // 上电先启动一次转换 for (h = 0; h < 50; h++) { xianshi(); // 等待转换期间刷新显示 } while (1) { if (temp1 == 0) { work_temp(read_temp()); // 读取并处理温度 xianshi(); // 刷新数码管 BEEP(); // 报警判断 } } }

主循环里temp1是一个标志位,用来控制读取频率。如果每次循环都去读 DS18B20,转换时间不够,读到的数据会不稳定。常见做法是用定时器中断置位标志,主循环检测到标志后再执行读取。资源里的代码用了一个简单的计数方式,实际项目中建议改成定时器驱动。

4. Proteus 仿真与硬件调试:从原理图到实际板子的排查清单

4.1 Proteus 仿真:元件库、HEX 加载和联合调试

Proteus 是这套设计里最常用的仿真工具,优势在于能同时仿真模拟电路、数字电路和单片机。搭建仿真的步骤是:从元件库调出 AT89C51、DS18B20、四位共阴数码管、蜂鸣器、电阻电容晶振等 → 按原理图连线 → 在 Keil 里编译生成 HEX 文件 → 双击 AT89C51 在属性里加载 HEX → 运行仿真。

DS18B20 在 Proteus 里的模型行为跟实物有差异。仿真中温度值可以通过属性面板手动设置,但单总线时序的容错性比实物高——也就是说,仿真能跑通的代码,烧到实物上不一定能跑。常见差异包括:仿真里对延时要求没那么严格,实物上差几个微秒就读不到应答;仿真里上拉电阻可以省略,实物上不接就一定读不到数据。所以仿真通过只是第一步,实物调试才是真正的考验。

提示:Proteus 仿真时可以把 DS18B20 的温度值设成 -10℃ 和 +50℃ 分别测试,验证负温度显示和报警逻辑是否正常。

4.2 硬件调试:上电前必须做的三件事

硬件调试的第一步不是上电,而是断电检查。用万用表蜂鸣档测 VCC 和 GND 之间是否短路,测每个芯片的电源引脚是否接对,测晶振两端是否与地短路。这一步能避免上电烧片。

第二步是检查 DS18B20 的上拉电阻。4.7kΩ 电阻一端接 DQ 线,一端接 VCC。如果漏焊或者阻值不对,读出来的数据全是乱的。我一般会先用万用表测 DQ 线对 VCC 的电阻,确认在 4.7kΩ 左右。

第三步是检查数码管的段选和位选接线。共阴数码管的公共端接地,段选线接 P0 口(带上拉电阻),位选线接 P2 口。如果段选和位选接反了,显示会完全乱掉。可以用一个简单的测试程序:让所有段全亮,确认每一位都能正常点亮。

上电之后,先用示波器看 DS18B20 的 DQ 线。复位脉冲应该是 500μs 左右的低电平,然后释放,从机应答拉低 60 到 240μs。如果看不到应答脉冲,说明从机没工作或者接线有问题。如果应答脉冲正常但读出来的温度不对,检查转换等待时间和数据处理逻辑。

4.3 常见问题排查:85℃、乱码和负温度异常

现象一:读出来永远是 85℃。原因通常是三个:没有发出 0x44 启动转换命令、转换等待时间不够、或者 DS18B20 根本没有应答。解决方法是先用示波器确认复位和应答脉冲是否正常,再检查代码里 0x44 命令是否发出,最后把delay(200)加大到 1000 试一下。

现象二:数码管显示乱码或者闪烁。原因可能是段选和位选接反、消隐逻辑没做、或者扫描频率不对。解决方法是先用静态显示测试每一位数码管,确认段码表正确;然后检查动态扫描的延时,每位点亮时间控制在 1 到 3ms。

现象三:负温度显示成正数。原因是符号位判断逻辑有问题。DS18B20 的负温度用补码表示,高 5 位全是 1。代码里用tem > 6348判断,如果阈值设错了或者判断条件写反了,负温度就会被当成很大的正数。解决方法是打印原始 16 位数据,确认负温度时高字节的值,然后对照数据手册调整判断阈值。

现象四:报警阈值改了但不起作用。原因是阈值是硬编码的,改的时候只改了显示部分没改判断部分,或者改了判断部分没重新编译烧录。解决方法是把阈值定义成宏或者变量,统一在一处修改。

现象五:实物上电后蜂鸣器一直响。原因可能是蜂鸣器驱动引脚初始状态不对,或者报警判断逻辑写反了。解决方法是上电先把蜂鸣器引脚置低,确认硬件没问题后再加报警逻辑。

5. 进阶改造与验证:把这套温度计变成能用的模块

5.1 用定时器中断替代软件延时

资源里的延时函数是空循环实现的,优点是简单,缺点是占用 CPU 且不精确。实际项目中我一般会用定时器中断来做延时基准。具体做法是:配置定时器 0 为 1μs 或 10μs 中断,在中断服务程序里维护一组软件计数器,需要延时的时候读取计数器差值。这样主循环可以腾出来做其他事情,比如按键扫描或者串口通信。

改造后的延时函数大致长这样:

volatile unsigned int tick = 0; void timer0_isr(void) interrupt 1 { tick++; // 每次中断tick加1 TH0 = 0xFC; // 重新加载初值,1ms中断一次 TL0 = 0x18; } void delay_ms(unsigned int ms) { unsigned int start = tick; while ((tick - start) < ms); // 等待tick差值达到ms }

这个方案的好处是延时精度由晶振决定,不受编译器优化影响。缺点是所有依赖微秒级延时的单总线操作仍然需要单独的短延时,因为 1ms 的中断粒度太粗。常见做法是微秒级延时用_nop_()拼,毫秒级延时用定时器。

5.2 加上下限设置和掉电保存

DS18B20 内部有 TH 和 TL 两个报警触发器,可以写入上下限温度值,掉电后仍然保存在 EEPROM 里。利用这个特性,可以把报警阈值写到 DS18B20 内部,而不是存在单片机 RAM 里。具体命令是:写暂存器(0x4E),依次写入 TH、TL 和配置寄存器,然后发复制暂存器命令(0x48)把数据存到 EEPROM。

这样做的代价是每次修改阈值都要走一遍单总线写时序,而且 DS18B20 的 EEPROM 写入次数有限(典型值 50 万次),不适合频繁修改。如果阈值需要经常调整,还是存在单片机的外部 EEPROM 或者 Flash 里更合适。

5.3 验证方法:怎么确认这套代码真的能用

验证分三层。第一层是仿真验证:在 Proteus 里把温度设成 -10℃、0℃、25℃、50℃、85℃ 五个点,确认显示值、负号、消隐和报警逻辑都正确。第二层是实物静态验证:用冰水混合物(0℃)和沸水(100℃)做参考点,确认测量误差在 ±0.5℃ 以内。第三层是长时间运行验证:让系统连续跑 24 小时,观察是否有死机、显示漂移或者报警误触发。

我自己的习惯是:每次改完代码,先在仿真里跑一遍五个温度点,确认逻辑没问题再烧到实物。实物上电后先用示波器看单总线波形,确认时序没问题再看显示。这套流程走下来,基本能覆盖 90% 以上的问题。

从那以后我每次做单总线器件调试,都强制先用示波器抓一遍复位和应答波形,确认物理层没问题再去看代码逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询