资讯详情

资讯详情

基于AT89C52与DS18B20的单片机数字温度计设计与实现

简介这份面向电子信息、自动化及单片机课程毕业设计场景的文档围绕基于 AT89C52 单片机与 DS18B20 智能温度传感器的数字温度计展开可用于课程设计、毕业论文撰写与测温报警电路参考。压缩包内含 1 个 doc 文件大小约 301KB正文按论文结构组织涵盖课题背景与研究意义、Proteus 与 Keil 开发工具简介、系统总体方案、DS18B20 工作原理、AT89C52 功能应用以及温度测量、LED 显示和上下限报警的软硬件电路说明。文档还给出 0℃100℃测量范围、三线制连接、报警阈值设置等设计要点并附中英文摘要、关键词与目录便于读者快速把握论文框架、梳理硬件模块与程序流程。已有 157 人学习适合需要参考论文结构、功能论证与电路设计思路的本科或高职毕业设计人群。1. 一份毕业设计文档里的完整温度计先说它能干什么第一次翻到这份《毕业论文基于单片机的数字温度计.doc》多数人想的是把它改个封面交差。但把附录 B 的全部程序清单和正文第 4、5 章对着看一遍就会发现这是一套零件齐全的完整设计AT89C52 主控、DS18B20 单总线测温、74LS164 串行驱动四位数码管、按键设定上下限、超限蜂鸣器报警配 Proteus 仿真图与 Keil C51 工程编译出来直接能在仿真里跑出 26.5℃ 这样的读数。它适合两类人课程设计/毕设卡在选型和时序上、需要一份能跑通的参考实现的人以及想拿 51 单片机练手单总线协议、后面再往 DS1302、AT24C16、串口上传上扩展的人。整套东西不复杂但时序、换算、显示这三段稍不留神就翻车下面按我拆的顺序讲。2. 系统架构与工程目录主控、传感、显示、报警四块怎么分工先把文档里的章节目录翻译成工程视角。原文第 3 章画了系统框图第 4 章讲硬件第 5 章讲软件附录 A 是整体电路、附录 B 是全部程序清单——这个结构本身就是一份标准的三层设计信号采集层、控制处理层、人机交互层。看懂这三层怎么咬合后面改代码才知道该动哪个文件。2.1 系统框图拆解四个模块的信号流向系统只做一件事把某个位置的温度变成一个四位数码管上的数字再和一个可调的阈值比一比。拆开是四块。主控制器是 AT89C5240 引脚、4 个 8 位并行口、128B 片内 RAM、4KB 片内 Flash、2 个 16 位定时器、1 个全双工串口、5 个中断源。这套资源放在这儿是够用的片内 4KB Flash 装一个温度计程序绰绰有余128B RAM 存段码缓冲和上下限阈值也够。温度采集走 DS18B20单总线三线制VDD、GND、DQ 数据线DQ 接单片机一个 I/O文档里用 P3.7 这类普通口。它的价值在于省掉整套模拟前端——传统热电偶、热电阻测出来是电压要先放大、再 A/D、再标定一路下来外围器件一大堆。DS18B20 内部集成了感温元件、A/D、寄存器直接吐出 12 位数字量免掉外部 A/D 和运放这是这个方案成本能压下来的根本原因。显示用四位数码管但不走并行口——P3 口的 RXD、TXD 两根线接到 74LS164 的串行输入四片级联用串口方式移位输出段码。好处是省 I/OP3.0/P3.1 两根线就够剩下的口留给按键和蜂鸣器。报警回路最简单三个独立按键设定上限和下限温度越界就拉低某个引脚驱动蜂鸣器同时数码管上给出提示状态。四条信号线各走各的没有交叉这是这套设计能被一个学生独立调通的前提。我见过有人把显示改成并行接法一下吃掉 P0 整个口结果按键没地方接——口资源分配要在画原理图之前定死。2.2 Proteus 与 Keil 双工具链仿真工程与编译工程怎么对应两个软件各管一段。Keil C51 负责编译Proteus 负责把 .hex 加载进虚拟单片机跑电路仿真。真正落地时导出后的常见工程结构是这样DigitThermometer/ ├── doc/ │ └── 基于单片机的数字温度计.doc # 论文正文含框图与电路图 ├── src/ │ ├── main.c # 主循环、报警判断、按键扫描 │ ├── ds18b20.c # 单总线时序与温度读取 │ ├── ds18b20.h │ ├── display.c # 74LS164 移位驱动与段码表 │ ├── key.c # 上下限设置 │ └── config.h # 引脚定义、阈值默认值、晶振频率 ├── project/ │ ├── temp.uvproj # Keil 工程 │ └── temp.hex # 编译产物喂给 Proteus └── sim/ └── temp.dsn # Proteus 仿真工程对应关系必须记牢Keil 里改的引脚宏Proteus 里的连线要跟着改Keil 里的晶振频率设置和 Proteus 里双击单片机填的 Clock Frequency 必须一致否则所有基于延时的单总线时序全部偏移。这是最容易忽略的一步很多人调半天读不出温度最后发现是一边写 12MHz 一边填 11.0592MHz。编译产物路径也要对Keil 的 Output 选项卡里勾上 Create HEX File输出目录指向 project/Proteus 里双击单片机在 Program File 一栏选到那个 .hex。改了代码没重新编译就点仿真跑的还是上一版——这种低级错误我犯过不止一次。2.3 器件选型与引脚分配表把文档第 4 章的引脚说明整理成一张表写代码时直接照抄器件/功能型号连接引脚说明主控AT89C52—12MHz 晶振复位电路 10uF 10k温度传感器DS18B20P3.7DQ三线制DQ 需 4.7k 上拉数码管驱动74LS164 × 4P3.0RXD、P3.1TXD串行数据 移位时钟上限键轻触开关P1.0低电平有效下限键轻触开关P1.1低电平有效确认/切换键轻触开关P1.2低电平有效报警输出蜂鸣器 三极管P1.5低电平驱动引脚分配看着随意其实有讲究DS18B20 放在 P3 口是因为 P3 各脚都有第二功能但这里只用普通 I/O不冲突数码管占 P3.0/P3.1 是串口复用写的时候要先把这两位当普通口用或者干脆用串口方式 0 输出按键放 P1 是因为 P1 内部带上拉外接按键到地即可省外部电阻。有一点要提醒DS18B20 的 DQ 上拉电阻别省4.7k 到 VDD仿真里 Proteus 的 DS18B20 模型有时不严格依赖它但实物上少了这根上拉读回来的全是 0xFF。3. DS18B20 单总线时序整个项目最容易翻车的一段代码DS18B20 之所以便宜是因为它把复杂度全推到软件这边——一根线上既要供电寄生模式下又要传数据所有信息靠高低电平的持续时间来编码。这意味着延时函数的精度直接决定成败晶振、编译器优化等级、循环体里的变量类型任何一个变了时序就偏。这段是整个项目最该逐行抠的地方。3.1 复位脉冲与存在应答每次通信前必须先复位让器件回到已知状态。主机拉低总线至少 480us然后释放器件在 15~60us 后拉低总线作为应答主机读到这个低电平就说明器件在线。#include reg52.h sbit DQ P3^7; // 单总线数据引脚与硬件连线一致 sbit BEEP P1^5; // 报警输出 /* 微秒级延时12MHz 晶振下 while(t--) 一轮约 2us 实际使用时用示波器或逻辑分析仪校准别照抄 */ void delay_us(unsigned char t) { while (t--); } /* 复位并检测存在脉冲返回 0 表示器件应答正常 */ bit ds18b20_reset(void) { bit ack; DQ 0; // 主机拉低总线 delay_us(240); delay_us(240); // 合计约 480us满足最小复位宽度 DQ 1; // 释放总线靠 4.7k 上拉拉高 delay_us(30); // 等 15~60us 让器件拉低应答 ack DQ; // 读到 0 说明器件存在 delay_us(200); delay_us(200); // 等本次时隙走完避免影响下一次 return ack; }三个参数值得说清拉低时长取 480us 是数据手册的最小值可以给到 600us 更保险释放后等待 30us 是在应答窗口的中间位置采样太早器件还没反应太晚应答脉冲已经结束末尾那 400us 是给器件留的恢复时间省掉它会在连续两次复位时偶发失败。返回值定义成「1 表示无应答」的话调用侧判断要跟着反过来这种地方写错不报错只是永远读不到温度。3.2 写时隙与读时隙单总线的每个 bit 都是一个独立时隙写和读的时隙结构不同但都从主机拉低开始。写 1 是拉低约 2us 后立刻释放写 0 是持续拉低整个时隙60us 以上。读时隙更微妙主机拉低至少 1us 后释放器件会在之后把当前 bit 输出到总线上主机必须在释放后 15us 内采样否则器件已经撤销数据。/* 写一个字节低位先发 */ void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; delay_us(2); // 起始下拉至少 1us DQ dat 0x01; // 写 1 释放拉高写 0 继续拉低 delay_us(30); // 整个时隙凑够 60us 以上 DQ 1; // 恢复总线空闲态 delay_us(2); dat 1; // 低位在前这是 DS18B20 的规定 } } /* 读一个字节低位先收 */ unsigned char ds18b20_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { dat 1; DQ 0; delay_us(1); // 下拉产生读时隙 DQ 1; // 释放把总线交给器件驱动 delay_us(2); // 等器件输出稳定必须在 15us 内采样 if (DQ) dat | 0x80; // 读到 1 则置最高位配合右移实现低位先收 delay_us(30); // 走完剩余时隙 } return dat; }读函数里dat 1放在采样之前、置位用| 0x80这套写法看着别扭但正确每轮先把已收到的位移低新 bit 填到最高位8 轮下来顺序刚好是低位先收。如果写成左移、置| 0x01读回来的字节位序是反的温度值会变成完全离谱的数——这是新手最常见的翻车点之一。3.3 温度读取与 0.0625 换算时序通了以后读温度就是照着命令表发指令。DS18B20 的 ROM 命令 0xCC 是跳过 ROM总线上只挂一个器件时用功能命令 0x44 启动转换、0xBE 读暂存器。暂存器 9 个字节前两个就是温度值低字节在前。/* 读原始温度值单位 0.0625 摄氏度负温为补码 */ int ds18b20_read_temp(void) { unsigned char tl, th; int raw; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM单器件场景 ds18b20_write_byte(0x44); // 启动一次温度转换 delay_ms(750); // 12 位分辨率需要最长 750ms不能省 ds18b20_reset(); // 转换完必须重新复位再发读命令 ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 tl ds18b20_read_byte(); // 温度低字节 th ds18b20_read_byte(); // 温度高字节 raw (th 8) | tl; // 拼成 16 位原始值 return raw; }返回的raw不是摄氏度是 1/16 度的整数。要显示成 26.5 这种形式得做一次定点换算/* 原始值转成 0.1 摄氏度为单位返回如 265 表示 26.5℃ */ int raw_to_deci(int raw) { long v (long)raw * 625; // raw * 0.0625 * 10 * 1000 if (v 0) v -v; return (int)(v / 1000); }这里用long是必须的raw * 625在 12 位满量程附近能到 20 万16 位 int 直接溢出。负温度更要小心DS18B20 用补码表示零下-0.5℃的原始值是 0xFFF8直接右移会当成一个大正数得先判断符号位。分辨率先别急着改成 9 位。9 位转换快93.75ms但精度只有 0.5℃显示出来数字跳得厉害。毕设里默认 12 位就好750ms 的等待换来 0.0625℃ 的分辨率主循环里用定时器分段等也行但我一般就让它阻塞着反正温度变化本来就慢。4. 显示与报警74LS164 驱动数码管 上下限比较硬件上省下来的口资源软件这边要还回去。四个数码管只占两根线代价是所有段码都得一位一位移出去而且移错一位整排显示就全乱。这段逻辑不难但很容易写成「能动但不对」的状态。4.1 串行移位驱动四位数码管74LS164 是 8 位串入并出移位寄存器两片级联成 16 位四片就是 32 位——正好对应四个数码管的 8 段含小数点。数据从第一片的 A、B 脚串行进时钟接 CLK每来一个上升沿数据往高位挪一格最高位溢出到下一片。所以要显示的第 4 位数码管段码先发第 1 位最后发全发完一次刷新。sbit DAT P3^0; // 串行数据接第一片 74LS164 输入 sbit CLK P3^1; // 移位时钟 /* 共阴段码表0~9 对应 a~g、dp 段 */ unsigned char code seg_code[10] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; /* 移出一个字节高位先出 */ void send_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DAT (dat 0x80) ? 1 : 0; // 取最高位 dat 1; CLK 0; CLK 1; // 上升沿完成一次移位 } } /* 刷新四位数码管value 单位为 0.1℃如 265 显示 26.5 */ void display_temp(int value) { unsigned char buf[4]; buf[0] value / 1000 % 10; // 十位 buf[1] value / 100 % 10; // 个位 buf[2] value / 10 % 10; // 十分位 buf[3] value % 10; // 百分位 send_byte(seg_code[buf[3]]); // 最后一位先移走得最远 send_byte(seg_code[buf[2]] | 0x80);/* 小数点加在十分位前 */ send_byte(seg_code[buf[1]]); send_byte(seg_code[buf[0]]); }移位顺序是这段的核心先发的数据会一路被顶到最远端所以 buf[3] 必须先发。如果反过来从 buf[0] 开始发显示结果就是左右颠倒。共阴共阳的段码表也要和实际数码管对上共阳的段码是共阴取反搞错了显示出来全是乱字符或者干脆不亮。小数点通过最高位 dp 叠加| 0x80把它点亮。四位数字 26.5 只需要三位最高位 buf[0] 是 0 时可以考虑熄灭消隐做法是判断后发 0x00 而不是 seg_code[0]否则会显示成 026.5。4.2 按键设置上下限三个按键一个进入设置模式并切换要调的是上限还是下限另两个加减。教学项目的按键一般用软件消抖10~20ms 延时再读一次。#define TH_DEFAULT 300 /* 上限 30.0℃ */ #define TL_DEFAULT 100 /* 下限 10.0℃ */ int temp_hi TH_DEFAULT; int temp_lo TL_DEFAULT; unsigned char setting_mode 0; /* 0 正常运行1 调上限2 调下限 */ void key_scan(void) { if (K_SET 0) { delay_ms(15); // 消抖 if (K_SET 0) { setting_mode (setting_mode 1) % 3; while (K_SET 0); // 等松手防止连跳 } } if (K_UP 0) { delay_ms(15); if (K_UP 0) { if (setting_mode 1 temp_hi 1000) temp_hi 5; // 步进 0.5℃ if (setting_mode 2 temp_lo 1000) temp_lo 5; while (K_UP 0); } } if (K_DN 0) { delay_ms(15); if (K_DN 0) { if (setting_mode 1 temp_hi -500) temp_hi - 5; if (setting_mode 2 temp_lo -500) temp_lo - 5; while (K_DN 0); } } }阈值和温度统一用 0.1℃ 为单位是这里最省事的做法。上下限都用int存步进 5 就是 0.5℃ 一档比较时不用再换算。如果阈值用摄氏度整数、温度用 0.0625 单位比较逻辑里就得来回乘除很容易写成「29℃ 就报警」那种差一档的问题。while (K_xxx 0);这行是死等松手按键卡住的话程序会停在这里严谨点应该加超时退出。教学项目图省事可以留但要心里有数。4.3 报警输出与主循环主循环的顺序是读温度 → 换算 → 刷新显示 → 判断报警 → 扫按键。按键放最后是因为设置模式会改变阈值下一次循环才生效晚一秒没人察觉。void main(void) { int raw, deci; ds18b20_reset(); // 上电先复位一次确认器件在位 display_temp(0); // 上电显示初值避免数码管全灭 while (1) { raw ds18b20_read_temp(); deci raw_to_deci(raw); // 转成 0.1℃ 单位 if (setting_mode 0) { display_temp(deci); // 正常运行显示实测温度 } else { display_temp(setting_mode 1 ? temp_hi : temp_lo); } /* 越界报警回差 0.5℃ 防止临界点反复响 */ if (deci temp_hi || deci temp_lo) { BEEP 0; // 三极管驱动低电平导通 } else { BEEP 1; } key_scan(); } }回差那半度不是必需的但少了它温度停在阈值附近时蜂鸣器会哒哒哒地反复触发听着像故障。加个滞回判断只在上穿上限 0.5℃、下穿下限 -0.5℃ 时切换状态体验立刻正常。5. 避坑与排查仿真跑通不等于实物能跑这套设计在 Proteus 里点一下仿真就能出数看着很省心。但真拿万用板焊出来问题会一个接一个冒出来。下面几条都是这套方案里高频出现的按现象、原因、解决三段记。5.1 温度恒定 85℃转换指令与等待时间现象不管怎么调 DS18B20 模型上的温度滑块数码管永远显示 85.0。原因85℃ 是 DS18B20 上电复位后的默认暂存器值。写 0x44 启动转换后等转换完成才能读到新值等待时间不够或者根本没发 0x44读到的就是那个默认值。解决确认 0x44 之后有 750ms 的等待12 位分辨率且等待结束后重新执行了一次复位 0xCC 0xBE。有些实现图省事把等待砍到 100ms9 位分辨率勉强够12 位必然读到旧值。仿真里时间被拉快了也一样会这样。5.2 仿真正常实物读不到晶振与延时校准现象Proteus 里读数正常焊到板上 DQ 一直高读回来全是 0xFF。原因delay_us是空循环循环次数取决于晶振频率和编译器优化级别。Keil 里选了 12MHz 但板上焊的是 11.0592MHz 晶振或者工程从 Debug 切到 Release 优化等级变了循环被优化掉几条时序全偏。另外delay_us的参数声明成unsigned char传大于 255 的值会回绕成小值等待时间突然缩短。解决换晶振后重新校准延时函数或者干脆改用定时器做基准delay_us的参数用unsigned int优化等级固定下来别来回切。手边有逻辑分析仪的话抓一次复位波形480us 的宽脉冲一眼就能看出来对不对比盲调快得多。5.3 数码管乱码或缺段移位时钟与段码表现象显示的数字缺一笔或者四位数字位置错乱。原因两种可能。一是 74LS164 的移位时钟太快实物上导线稍长一点就建立不住仿真里完全看不出来二是段码表共阴共阳搞反了或者send_byte里取位方向写反应该高位先出。解决CLK 0; CLK 1;之间加一两个空操作拉长高电平时间段码表拿万用表逐段验证一遍共阳数码管把表里每个值按位取反先只发一个字节点亮一位确认单字节通路没问题再级联四位。5.4 报警一直在响单位不统一现象温度明明在设定范围里蜂鸣器就是不依不饶。原因阈值用摄氏度整数存比如 30温度用 0.0625 单位的原始值比较30 对上一个 480 左右的原始值永远判超限。或者反过来阈值存 0.1℃ 单位、温度取了整数部分低半度永远触发下限。解决全链路统一单位从raw_to_deci出来之后一路用 0.1℃阈值、显示、比较三处都不再换算。比较表达式写完之后打印一次中间值核对比盯代码快。5.5 编译通过但实物不跑最小系统三根线现象.hex 烧进去了单片机毫无反应数码管全灭。原因最小系统三根线没接对——EA 引脚必须接高电平片内 ROM 执行复位电路电容和电阻值不对导致一直处于复位或者晶振旁边那两个 22pF 电容省掉了不起振。解决先量 EA 是不是 5V再量 RST 在按下复位键时有没有高电平脉冲最后用示波器看 XTAL2 有没有波形。这三步按顺序来八成问题在前两步就能定位。6. 二次开发改造从单点测温到一套带时间戳的采集节点原设计的功能是封闭的测温、显示、报警数据出不去也存不下来。真正拿去做小项目第一件事就是把数据留下来。文档第 3 章其实已经给了方向加 DS1302 拿时间、AT24C16 存温度、MAX232 走串口上传三条扩展都留了口。串口上传是最容易先做的因为 AT89C52 自带 UART只需要初始化定时器 1 做波特率发生器/* 11.0592MHz 下 9600bpsTH1 0xFD */ void uart_init(void) { TMOD | 0x20; // 定时器 1 工作在方式 28 位自动重装 TH1 0xFD; // 波特率初值11.0592MHz 专用换晶振要重算 TL1 0xFD; TR1 1; // 启动定时器 1 SCON 0x50; // 串口方式 1允许接收 EA 1; ES 1; } /* 发送一个字节轮询方式够用 */ void uart_send(unsigned char dat) { SBUF dat; while (!TI); TI 0; }波特率这里有个坑11.0592MHz 是专门为串口挑的频率9600bps 能整除出精确值如果板上还是 12MHz波特率会有 8.5% 的误差通信时好时坏。要么换晶振要么降到 4800bps 并接受误差。DS1302 和 AT24C16 都是三线/两线串行器件接法和 DS18B20 类似读写靠时序拼字节。渐进的做法是先用 DS1302 拿到时、分、秒让数码管在温度和时间之间交替显示再每十分钟往 AT24C16 写一条「时间 温度」的四字节记录地址自增最后串口把这些记录按文本格式吐出去用上位机存成 CSV。改造时要盯住 RAM 占用。AT89C52 只有 128B 片内 RAM四个数码管的缓冲、时间结构体、串口收发缓冲、记录指针加起来很容易顶到边界。我的习惯是每加一个模块就翻一次编译输出的 .m51看 DATA 段还剩多少剩不到 20 字节就别再往上堆了改用外扩或者直接砍掉历史记录功能。验证方法建议按层来先断开上位机、只让串口把固定字符串发出来确认波特率对再接上 DS1302 打印时间确认走时正常最后才把温度拼进去。一次全接上出了故障分不清是哪个模块。从那以后我每次动这种小系统都强制先在纸上把引脚分配、单位约定、各模块的 RAM 开销写死再开始敲代码——这套温度计的几个坑全是我没提前写清这东西的时候踩出来的。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →