ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STC89C52+DS18B20+1602:数字温度计完整工程参考与避坑指南

STC89C52+DS18B20+1602:数字温度计完整工程参考与避坑指南 简介一份基于STC89C52单片机的数字温度计完整设计PDF面向单片机入门者与电子课程设计学生系统讲解从器件选型到软硬件实现的全部环节。文档对比了STC89C52与ATmega8主控方案选用1602LCD与DS18B20温度传感器详细给出了51单片机最小系统、电源供电、LCD显示与温度检测电路的设计要点并配合温度数据读取流程图、Keil编程思路和Proteus仿真界面帮助读者快速建立单片机测温系统的整体认知。内容涵盖绪论、方案选择、最小系统设计、电源与显示电路、温度检测电路、软件流程图、Keil与Proteus仿真调试等章节结构完整便于按需查阅。压缩包内仅包含一个PDF格式的文档文件整体大小为473KB轻量便于随时查阅。目前已有98人学习浏览。附录部分还提供完整原理图与程序清单可作为课程设计报告撰写和实物调试的参考模板适合需要完成类似课题的初学者参考借鉴。1. 基于 STC89C52 的数字温度计一份能直接复现的工程参考把「基于 STC89C52 的数字温度计」做成能跑起来的东西卡住大多数人的往往不是 C 语言而是 DS18B20 的单总线时序和 1602 液晶的接口对接。这套资料同时给了方案论证、原理图、完整源码和 Proteus 仿真从 Keil 编译到 hex 烧录是闭环的适合课程设计、毕业设计也适合想快速搭建温度采集原型的工程师参考。实际复现时要注意三处陷阱复位电路文字与常规接法矛盾、DS18B20 延时依赖 Keil 优化等级、P0 口驱动 1602 必须加上拉电阻。下面按选型、电路、时序、显示、仿真验证的顺序把每一处的判断依据和改动说明白。2. 主控与传感器的选型逻辑和最小系统设计2.1 为什么最终选了 STC89C52 而不是 ATmega8资料里把 STC89C52RC 和 ATmega8 放在一起比较结论是 STC 系列更适合这次设计。STC89C52RC 是 8051 内核片内 8KB Flash支持 ISP 在线编程指令系统兼容 MCS-51零售价通常在 5 元以内ATmega8 的存储容量和硬件接口更强属于 AVR 高端系列的低引脚版本但对一个只做温度采集和字符显示的项目来说性能优势发挥不出来反而要面对一套不同的寄存器风格和工具链。选型的原则实际是「够用 熟悉」而不是「参数最强」。对比项STC89C52RCATmega8内核/指令8051兼容 MCS-51AVR RISC程序存储8KB FlashISP 在线编程8KB FlashISP 在线编程最高工作频率资料标称最高 80MHz常用 12M/11.0592M16MHz上手成本低教材和例程多中寄存器风格差异大本次结论采用不采用对温度计这个场景12MHz 主频足够I/O 只用了 P0 数据口、P2.0/P2.1 控制线、P3.7 单总线8KB Flash 放 1602 驱动、DS18B20 时序和中断刷新还有富余。后续做复杂项目时可以再评估 AVR 或 STM32但作为教学和原型验证STC89C52 的生态优势非常明显。2.2 显示与测温模块的取舍显示部分在数码管和 1602 液晶之间选。数码管亮度高、成本低但多位显示需要动态扫描扫描频率不合适会明显闪烁而且每个数码管要占用段选和位选 I/O整机功耗也偏大。1602 是字符型液晶两行 16 字符接口简单内部自带显示控制器写命令和数据就能显示功耗比数码管低还能输出字母和提示语。资料中选 1602价格约 10 元内带背光5V 驱动正好和 STC89C52 共电源。测温部分比较了热敏电阻加 ADC 和 DS18B20 两种方案。热敏电阻方案需要额外的 A/D 转换电路感温器件本身存在非线性软件上还要做查表或拟合校准工作量大。DS18B20 是 DALLAS 公司的单总线数字温度传感器测温范围 -55125°C不需要外部器件数据直接以 16 位数字量读出省掉整条模拟链路。最终组合确定为 STC89C52 DS18B20 1602三样东西加在一起成本很低但覆盖了嵌入式设计最典型的几个环节最小系统、外设驱动、时序协议、中断刷新。2.3 最小系统要过的三道关最小系统以 STC89C52 为核心12MHz 晶振配两只 30pF 起振电容。晶振和电容的位置要尽量靠近单片机 XTAL1/XTAL2 引脚引线过长可能导致无法起振。复位部分资料文字写的是「按键低电平复位」后面又写「按下时 RET 端为高电平」这两句自相矛盾。实际常见做法是按键高电平复位10uF 电解电容并 10k 电阻到地按键并联在电容两端按下按键 RST 被拉高高电平持续约 10ms远大于 4us 的复位要求。EA 引脚必须接 VCC否则单片机从外部程序存储器取指内部 Flash 不生效这个引脚漏接是最容易被忽略的问题之一。环节器件/参数常见问题时钟12MHz 晶振 30pF 电容 ×2引线过长不起振复位10uF 电容 10k 电阻 按键RST 高电平时间不足使能EA 接 VCC无法执行内部程序供电5V100uF 0.1uF 滤波纹波大导致复位异常端口规划在这套源码里是这样定义的#include reg52.h // 8051 系列寄存器定义 #define uchar unsigned char #define uint unsigned int sbit rs P2^0; // 1602 寄存器选择0命令1数据 sbit lcden P2^1; // 1602 使能信号高脉冲有效 sbit DATA P3^7; // DS18B20 单总线数据线sbit是 Keil C51 的位寻址语法把某个引脚映射为位变量。P2.0/P2.1 作为控制线、P3.7 作为单总线数据线都在可位寻址区域内后续代码直接对rs、lcden、DATA赋值即可。注意这里和资料正文「LCD 的 RS 接 P1.0、E 接 P1.1」的描述不一致附录代码以 P2.0/P2.1 为准实际接线要看编译进 hex 的这份代码而不是看前面的文字描述。3. 供电电路、1602 接口与单总线测温电路的实物要点3.1 电源链路LM2940 与滤波电容资料用 4 节 1.5V 电池串联得到 6V经 LM2940 稳压到 5V 给系统供电。选 LM2940 而不是 7805关键在压差和静态电流。7805 典型压差约 2V6V 输入时输出可能只有 4V 左右接近 51 单片机的复位门限系统会不稳定LM2940 是低压差稳压器压差约 0.5V6V 输入能稳出 5V静态电流也比 7805 小电池供电场景下更合适。稳压器输入输出两端分别并 100uF 和 0.1uF 电容100uF 吸收低频纹波0.1uF 滤高频噪声两只电容尽量贴近稳压器引脚。D1 是电源指示灯串联限流电阻按 1k 左右取值避免 LED 过流。电池电压降到约 5.5V 以下时LM2940 输出开始跌落DS18B20 的转换结果会出现跳变。实作时先用万用表确认电源与地之间不短路再插芯片避免烧片。这一步是资料总结里特别强调的也是很多实物失败的直接原因。3.2 1602 接线与时序文字描述和代码以哪个为准1602 是 5V 驱动、两行 16 字符的字符型液晶。1/2 脚接电源3 脚 V0 通过一只 10k 电位器调对比度4 脚 RS、5 脚 RW、6 脚 E15/16 脚是背光背光串一只 1k 电阻限流。资料正文说 RS 接 P1.0、E 接 P1.1但附录程序里sbit rsP2^0; sbit lcdenP2^1;两者冲突。以附录代码为准因为它是真正编译进 hex 的版本如果按正文的 P1.0/P1.1 接线显示不会有任何反应。RW 端在这套设计里始终是写状态直接接地。另一个容易踩的坑是 P0 口。代码用 P0 作为 1602 的 8 位数据总线但 STC89C52 的 P0 是开漏结构内部没有上拉输出高电平时要靠外部上拉电阻拉到 5V。常见做法是在 P0 口接一只 10k 排阻到 VCC。Proteus 仿真往往不加上拉也能通过实物则会随机乱码这是「仿真能跑、实物必挂」的典型原因。/* 液晶写命令com 为命令字 */ void write_lcd_com(uchar com) { rs 0; // 命令模式 lcden 0; // E 置低准备写入 P0 com; // 命令字送到数据总线 delay_ms(1); // 等待总线稳定 lcden 1; // E 拉高完成锁存 delay_ms(1); // 保持高电平时间 lcden 0; // E 拉低结束写周期 } /* 液晶写数据date 为要显示的字符编码 */ void write_lcd_date(uchar date) { rs 1; // 数据模式 lcden 0; P0 date; delay_ms(1); lcden 1; delay_ms(1); lcden 0; }命令模式时rs0数据模式时rs1E 引脚产生一个高脉冲1602 在 E 下降沿把 P0 上的内容锁存进去。两次delay_ms(1)一是保证数据建立时间二是给出命令执行时间1602 的写命令周期在微秒级1ms 已经足够宽。初始化时依次发送 0x388 位数据、两行、5×7 点阵、0x0C开显示、关光标、0x06写数据后地址指针自动加 1、0x01清屏lcd_init()按这个顺序调用即可。3.3 温度检测电路上拉、供电与走线DS18B20 有三个引脚GND、DQ、VDD。DQ 接 P3.7单总线是开漏结构必须在 DQ 与 VCC 之间接 4.7k10k 上拉电阻总线空闲时保持高电平。DS18B20 支持寄生供电即只接 DQ 和 GND由总线高电平给内部电容充电但这种模式下上拉电阻不能太大否则转换期间电压跌落会读回 85°C 或随机值。实作建议直接把 VDD 接 5V省去寄生供电的麻烦。功能模块单片机引脚说明1602 RSP2.00命令1数据1602 EP2.1写使能高脉冲1602 D0~D7P0.0~P0.78 位数据总线需外部上拉DS18B20 DQP3.7单总线数据需外部上拉复位按键RST高电平复位晶振XTAL1/XTAL212MHz 30pF 电容 ×2注意这里的delay()是软件循环延时与 Keil 优化等级强相关。调试时优先用逻辑分析仪看 DQ 波形而不是凭延时函数推算。DQ 走线尽量短避免与 1602 背光电源线长距离平行。背光电流变化会在 DQ 上耦合出毛刺严重时直接让时序错乱。第 4 章的时序分析和这里说的物理层问题会互相印证。4. DS18B20 单总线时序协议与驱动实现4.1 先理解时隙再写驱动DS18B20 只有一根数据线所有操作都靠拉低和释放的时间长度来表达。协议分三层初始化复位脉冲加存在脉冲、ROM 命令、功能命令。初始化时主机把 DQ 拉低至少 480us 后释放DS18B20 检测到上升沿后等待 1560us再把总线拉低 60240us 作为存在脉冲。写 0 时隙是主机拉低总线并保持 60120us写 1 时隙是主机拉低 115us 后立即释放由外部上拉把总线拉高相邻两个时隙之间至少间隔 60us。读时隙由主机发起拉低 1us 后释放并在 15us 内采样电平传感器输出 0 时会把总线继续拉低输出 1 时总线保持高。操作主机动作关键时间参数复位拉低后释放低电平 ≥480us释放后等 15~60us写 0拉低保持60us~120us写 1拉低后释放低电平 1~15us时隙总长 ≥60us读拉低后释放并采样低电平 ≥1us15us 内采样这里最常见的问题不是理解协议而是延时不准。delay(uint num){ while(num--); }是纯软件循环延时长度由 Keil 优化等级和晶振频率共同决定同一个函数在不同工程里时间可以差一倍。资料里delay(80)注释是「大于 480us」但在 12MHz、12T 模式下经过编译器优化后实际可能只有 200400us不满足复位要求。Proteus 仿真对时序有一定宽容度过了仿真不代表实物一定过建议把参数改到delay(200)或更长实测时用示波器确认 DQ 低电平时间确实超过 480us。4.2 初始化、读一字节、写一字节的源码拆解核心驱动三件套如下/* DS18B20 初始化拉低总线产生复位脉冲等待存在脉冲 */ void Init_DS18B20(void) { uchar x 0; DATA 1; // 先拉高总线 delay(10); // 空闲一段时间 DATA 0; // 主机拉低开始复位 delay(200); // 保持低电平确保超过 480us DATA 1; // 释放总线等待传感器响应 delay(20); // 等待存在脉冲出现 x DATA; // 采样传感器会拉低总线表示响应 delay(30); // 等完存在脉冲剩余时间 }复位后的低电平时间由delay(200)承担这段延时建议实测确认。采样xDATA读到 0 说明 DS18B20 已经拉低总线初始化成功读到 1 则检查接线、上拉电阻或延时长度。/* 读一个字节低位在前高位在后 */ uchar ReadOneChar(void) { uchar i 0; uchar dat 0; for (i 8; i 0; i--) { DATA 0; // 启动读时隙 dat 1; // 先把旧位右移 DATA 1; // 释放总线 if (DATA) // 15us 内采样 dat | 0x80; delay(8); // 等待时隙结束 } return dat; } /* 写一个字节每次写一个位先写低位 */ void WriteOneChar(uchar dat) { uchar i 0; for (i 8; i 0; i--) { DATA 0; // 启动写时隙 if (dat 0x01) DATA 1; // 写 1短暂拉低后释放 else DATA 0; // 写 0保持低电平 delay(10); // 保持时隙长度 DATA 1; // 结束当前位 dat 1; } delay(8); }读字节时每一位先dat1再采样DQ 为高就把最高位置 18 位结束后低位在前、高位在后的顺序正好对应 DS18B20 温度寄存器的布局。写字节时先判断当前位写 1 时总线只被短暂拉低然后靠上拉电阻拉高写 0 时总线保持低电平 10 个延时单位时隙宽度足够。这里的delay(10)在 12MHz 下约为几微秒到十几微秒处于写 0 时隙和写 1 时隙的重叠范围内实测兼容性较好。4.3 温度读取的完整命令序列与返回值处理正常测温流程中两次初始化之间插入命令先发 0xCC 跳过 ROM总线上只有一个传感器时再发 0x44 启动温度转换12 位分辨率下转换时间最长约 750ms之后重新初始化发 0xCC 和 0xBE 读暂存器连续读两个字节得到温度低字节和高字节。命令字命令名功能0xCCSkip ROM跳过 64 位序列号匹配适合单点测温0x44Convert T启动一次温度转换0xBERead Scratchpad从暂存器 0 地址开始读 9 个字节/* 读取温度返回值放大 10 倍例如 256 表示 25.6℃ */ int ReadTemperature(void) { uchar a 0, b 0; int t 0; float tt 0; Init_DS18B20(); // 复位 WriteOneChar(0xCC); // 跳过 ROM 匹配 WriteOneChar(0x44); // 启动温度转换 Init_DS18B20(); // 第二次复位 WriteOneChar(0xCC); WriteOneChar(0xBE); // 读取暂存器 a ReadOneChar(); // 温度低字节 b ReadOneChar(); // 温度高字节 t b; // 高字节先装入 t 8; t | a; // 拼成 16 位有符号数 if (b 0x80) // 符号位为 1说明是负温度 { t ~t 1; // 取补码的绝对值 flag_Negative_number 1; } else { flag_Negative_number 0; } tt t * 0.0625; // 12 位分辨率最低位代表 1/16 ℃ t tt * 10 0.5; // 放大 10 倍并四舍五入到 0.1℃ return t; }ReadTemperature 内部做了两次初始化第一次启动转换第二次读取结果中间必须留出转换时间。主程序每 3 秒调用一次读函数天然满足 750ms 的转换等待。返回值的处理分两步先乘 0.0625 得到实际温度再乘 10 并加 0.5 取整得到保留一位小数的整数结果方便后面逐位拆分显示。b0x80判断的是高字节最高位即温度寄存器补码的符号位负温度在寄存器里以补码存放必须先做~t1还原绝对值。5. 温度换算、1602 显示与 3 秒刷新机制环境上Keil C51 新建工程时器件选 AT89C52 即可Proteus 元件库里通常没有 STC89C52用 AT89C52 替代仿真引脚和指令完全兼容。编译时在 Options for Target 的 Output 页面勾选 Create HEX File生成的 hex 文件加载到 Proteus 原理图的单片机芯片里就能看到 1602 第一行显示 tempreture:第二行每 3 秒刷新一次温度。下面拆解这段流程里温度数据是怎么变成屏幕字符的。5.1 十六位温度数据的补码解析DS18B20 温度寄存器是 16 位补码低 4 位是小数位高 5 位是符号扩展位。12 位分辨率下每个最低位代表 0.0625°C所以代码里ttt*0.0625。几个典型值寄存器原始值十进制实际温度0x019140125.0625℃0x00D221013.125℃0xFC90-880-55.0℃0x07D02000125.0℃0xFC90 按无符号读是 64656但在 C51 的 16 位 int 里是 -880b0x80判断到符号位后取补得到 880再乘 0.0625 就是 55.0。如果不做符号处理直接按无符号数乘 0.0625会得到一个四千多度的离谱值。负号由flag_Negative_number记录显示时在最高位补一个 ASCII 负号 0x2D。5.2 显示拆分和 1602 的字符映射返回值temp是放大 10 倍的温度比如 25.6°C 对应 256。拆分时用bai_18b20temp%1000/100、shi_18b20temp%100/10、ge_18b20temp%10。变量名叫 bai、shi、ge实际对应的是整数十位、整数个位、小数十分位名字和含义对不上是原资料的遗留问题但不影响逻辑。显示位置选在第二行开头地址为 0x800x40之后连续写符号或数字、小数点、十分位、度符号、字母 C。情况显示内容正数且十位为 05.6°C正数且十位非 025.6°C负数且十位为 0-5.6°C负数且整数部分三位-125.0°C字符编码方面0x30数字是把数字转成 ASCII 码0 是 0x30加 n 就是字符 n0x2D 是负号 -0x2E 是小数点0x43 是字母 C。0xDF 在 1602 的常见字库中对应度符号 °很多 DS18B20 例程都这么写。不同批次 1602 的字符映射可能略有差异如果仿真或实物上 0xDF 显示的不是 °查一下屏的数据手册字模表或者改用自定义字符实现。5.3 主循环与定时器中断的刷新节奏整套程序不是靠死等循环刷新温度而是用定时器 0 产生 50ms 时基计数 60 次后置一个标志位主循环看到标志位才去读温度并更新显示。这样每 3 秒刷新一次避免连续读 DS18B20 长时间占用 CPU也让 1602 显示稳定不抖动。为什么不把读取直接放进中断因为单总线协议里的 480us 复位、750ms 转换等待都是相对漫长的过程在中断里做会拖垮其他任务。void main() { lcd_init(); // 1602 初始化 TMOD 0x01; // 定时器 0方式 116 位定时 TH0 (65536 - 50000) / 256; // 50ms 定时初值高字节 TL0 (65536 - 50000) % 256; // 低字节 TR0 1; // 启动定时器 ET0 1; // 开定时器 0 中断 EA 1; // 开总中断 write_lcd_com(0x80); // 第一行起始地址 for (num 0; num 11; num) write_lcd_date(table[num]); // 显示提示语 tempreture: while (1) { if (FLAG_DIS 1) // 3 秒时间到 { FLAG_DIS 0; dis_D18B20(); // 读取温度并刷新第二行 } } } void timer0() interrupt 1 { uchar count; TH0 (65536 - 50000) / 256; // 重装初值 TL0 (65536 - 50000) % 256; count; if (count 60) // 60 次 50ms合计 3s { FLAG_DIS 1; count 0; } }TMOD0x01 把定时器 0 配成 16 位定时模式初值取 65536-50000在 12MHz、12T 模式下机器周期为 1us50000 个机器周期正好 50ms。中断函数里每次进入 count 加 1到 60 时置位标志并清零。资料原样代码里 count 声明成局部变量C51 编译器通常会把它放到可覆盖段单个中断场景问题不大如果再开第二个中断且同样声明局部变量就有覆盖风险常见做法是直接提成全局变量行为最稳定。dis_D18B20()内部调用 ReadTemperature里面有浮点运算放在主循环里不会阻塞中断因为定时器中断优先级高于主循环普通代码。6. 让这套数字温度计更实用定点换算与抗干扰调整6.1 用整数运算替代浮点换算ReadTemperature 里ttt*0.0625和ttt*100.5引入了浮点运算Keil C51 的浮点库会额外占用几百字节 ROM每次转换还要走浮点指令。常见做法是全程整数低 4 位是 1/16 度放大 10 倍显示一位小数时直接对原始值做(raw * 10 8) 4加 8 是为了四舍五入。/* raw 为拼好的 16 位原始值返回放大 10 倍的温度 */ int raw_to_temp10(int raw) { int t raw; if (raw 0x8000) // 负数取补 { t ~raw 1; flag_Negative_number 1; } else { flag_Negative_number 0; } return (t * 10 8) 4; // 等效 t*10/16 四舍五入 }(t*108)4等价于t*10/16后再四舍五入没有浮点类型代码量和执行时间都明显下降。负数部分沿用补码还原逻辑显示分支完全不用改。如果你的 51 工程对 ROM 容量敏感这个替换可以直接用。6.2 分辨率、上拉和两个实物调试技巧DS18B20 配置寄存器的低两位决定分辨率9 位对应 0x1F10 位对应 0x3F11 位对应 0x5F12 位对应 0x7F。长线干扰导致偶尔读到 85°C 时可以把分辨率降到 11 位步进 0.25°C转换时间从 750ms 降到 375ms时序容限更好。写入配置寄存器的序列是复位 → 0xCC → 0x4E写暂存器→ 三个配置字节TH、TL、配置值。注意重新上电后配置会恢复默认 12 位需要随启动代码重新写入。分辨率步进配置值典型转换时间9 位0.5℃0x1F93.75ms10 位0.25℃0x3F187.5ms11 位0.125℃0x5F375ms12 位0.0625℃0x7F750ms实测调试时先用读出的原始值对照冰水约 0°C和沸水约 100°C做两点验证确认符号扩展和放大倍数没有写错再检查 DQ 上拉电阻尽量把 4.7k 电阻放在靠近传感器引脚的位置而不是单片机一侧。如果还有偶发跳变可以在 DQ 对地并联一只 100nF 电容滤高频噪声容量不能太大否则 1us 级别的读时隙上升沿变缓采样点偏移反而更容易出错。把上拉电阻缩短到传感器一侧这类长线场景下的温度跳动通常立刻就能改善。本文还有配套的精品资源点击获取
返回列表