
简介基于51单片机的DS1302时钟与18B20温度计综合实例面向单片机学习者和课程设计开发者解决时间显示与温度测量同步实现的问题。内含完整C语言工程、Proteus仿真相关文件及实验指导文档共23个文件核心代码采用模块化写法涵盖DS1302驱动、18B20驱动、液晶显示等关键部分附带的.hex烧录文件、.uv2工程文件及.lst/.obj等编译中间文件便于直接打开工程、编译并仿真验证。压缩包仅267KB轻量易用。已有383人学习下载适合初学者模仿练习也可在课程设计中快速移植应用。通过该实例可掌握单片机对时钟芯片与温度传感器的时序控制、数据处理和液晶显示方法同时理解Proteus环境下的联合调试流程。1. 12864DS130218B20组合在51单片机上的最小可行性把12864液晶、DS1302时钟芯片和DS18B20温度传感器同时挂到一块51单片机上是很多课程设计和电子制作的标准配置。这个组合的难点不在单一模块而是三个器件各有各的时序DS1302用3线串行读写DS18B20走单总线12864还要根据屏型号决定并行还是串行驱动。把这些时序理顺之后剩下的C语言代码其实都是体力活。这类项目最常见的坑反而在Proteus仿真阶段DS1302的晶振在仿真里经常不走时DS18B20的初始化偶尔拉不到位12864显示乱码时又分不清是数据线接错还是驱动时序不对。本篇文章把这三个器件从原理、代码到仿真排错完整串一遍给你一套拿到就能抄、抄完能跑通的最小方案。2. DS1302和18B20的时序基础与12864接口选型2.1 DS1302的3线读写在51单片机上的连续性要求DS1302是Dallas公司的涓流充电时钟芯片对外只有CE、SCLK、I/O三根线。和I2C的SCL/ SDA配合寻址机制不同的是DS1302每次通信需要先拉高CE然后在SCLK的上升沿逐位写入或读出8位数据。写入第一个字节是命令字命令字的bit0是读写标志bit1是地址递增标志高5位是寄存器地址。读取秒寄存器时的命令字是0x81写入是0x80。51单片机的IO操作需要注意SCLK连续性问题。寄存器地址写入和后续数据写入之间SCLK中间不能插入过长的延时否则DS1302内部状态机会误判。常见做法是把SCLK脉冲放在一个无延时的连续循环里或者把单字节读写函数做短避免在主循环里被中断打断。下面是一个通用的写字节函数void DS1302_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DS1302_IO dat 0x01; // 先输出低位 DS1302_SCLK 0; DS1302_SCLK 1; // 上升沿锁存 dat 1; } }这段代码的关键在于先置SCLK为0再置1产生上升沿。每次循环只改两个电平中间没有任何延时函数调用保证时序紧凑。如果51单片机主频只有12MHz这个循环大约耗时几微秒完全在DS1302允许的范围里。注意I/O方向在写数据时51引脚输出读数据时要切换成输入或高阻态。实际操作中把DS1302_IO定义成sbit后通过单独的控制位切换P3口的引脚状态或者干脆用准双向口直接读。DS1302的关键寄存器包括秒、分、时、日、月、星期、年全部以BCD码保存。每次读写后需要手动做BCD和十进制的互转因为51的C语言里没有现成的BCD指令。写保护寄存器0x8E的bit7必须在写入时间前清零否则所有写操作都会被拒绝。2.2 DS18B20单总线初始化时序和温度数据格式DS18B20比DS1302的时序更苛刻因为单总线只有一根数据线读写时序完全靠时间窗口区分。初始化时主机拉低480微秒以上然后释放总线等待DS18B20拉低60到240微秒作为存在脉冲。这个时序在Proteus仿真里通常能过但在真实硬件上对延时函数很敏感。推荐用while循环加NOP的方式做微秒延时不要依赖for循环的边界猜测。下面这段是常见的复位函数bit DS18B20_Reset(void) { bit presence; DS18B20_DQ 0; Delay480us(); DS18B20_DQ 1; Delay60us(); presence DS18B20_DQ; // 读取存在脉冲 Delay420us(); return presence; }返回值presence为0时表示检测到设备。这里有个容易踩的坑读存在脉冲的时机要在释放总线后的60微秒附近太早读到的是高电平太晚则错过低脉冲。仿真和实物调试时如果这个函数返回值一直为1先检查上拉电阻是否接上再看延时是否偏短。温度读取流程是复位、发0xCC跳过ROM匹配、发0x44启动转换等待750毫秒12位分辨率再次复位、发0xCC、发0xBE读取暂存器的低8位和高8位。温度值和实际温度的对应关系是16位数据左移3位后除以16。读到符号位为1时温度是负数需要取反加一。分辨率转换时间温度精度9位93.75 ms0.5℃10位187.5 ms0.25℃11位375 ms0.125℃12位750 ms0.0625℃实际工程中不需要每次都把分辨率配到12位。如果只是做室温显示11位足够375毫秒的转换时间也让主循环响应更快。如果项目里有多个DS18B20想区分就把0xCC换成对应ROM序列号这部分代码量会大很多。2.3 12864液晶的两种接口模式选择12864液晶按控制器分两类带ST7920控制器的屏同时支持并行和串行KS0108控制器的屏只有并行接口且需要左右两片驱动。Proteus仿真里最常用的是AMPIRE128X64模型对应ST7920控制器这两种模式都可以仿真通过。并行模式要接8根数据线加RS、RW、E三根控制线串行模式只需要SCLK、SID、CS三根线。51单片机引脚本身紧张建议优先选串行模式。ST7920串行时序类似SPI每个字节被拆成三个部分前5个1作为同步头然后是RS和RW组成的高位控制位最后是8位数据。每次对控制器写命令或写数据都要组装成24位的帧。void LCD12864_SendByte(unsigned char dat, bit isData) { unsigned char i; unsigned char tmp; tmp 0xF8; // 11111 RW0 RS0 if (isData) tmp | 0x02; // RS1 SendOneBit(tmp); // 先发送控制字节 SendOneBit(dat); // 低于控制字节中的显示字节 SendOneBit(dat 4); // 再从低四位发送一次 LCD12864_SCLK 1; LCD12864_SID 1; }这里的SendOneBit函数需要自己实现把数据按位送到SID线上每个位脉冲一个SCLK。注意ST7920的串行时序高位在前和DS18B20的LSB FIRST相反。刚接触时很容易在这里写反导致显示全乱。调试时可以先发送固定数据0x30初始化指令序列第一步就是0x30如果液晶正常显示两行字说明基本通信已经建立。3. 用51单片机C语言把三个器件组装成一个完整时钟温度计3.1 主循环结构刷新率和显示策略三个器件里DS18B20的转换时间最长真机上需要几百毫秒等待。如果把温度读取放在主循环里每次读一次温度显示会卡顿时钟秒表也会跳动不均匀。常见的方案是把温度转换启动放到一个定时器中断里主循环只负责刷新显示。主循环的节奏按100毫秒一个周期来安排每10个周期读一次DS18B20温度每500个周期刷新一次日期时间。这样秒数显示在12864上连续平滑温度数字每秒最多跳一次。3.2 DS1302的时间读写和时间初始化DS1302读取时间一般是一次连续读突发模式。命令字0xBF可以一次把秒、分、时、日、月、星期、年全部读完。连续读模式下地址自动递增省去了反复发送命令字的开销。void DS1302_ReadTime(void) { unsigned char i; DS1302_CE 1; DS1302_WriteByte(0xBF); for (i 0; i 7; i) { time_buf[i] DS1302_ReadByte(); } DS1302_CE 0; }time_buf数组的索引顺序是秒、分、时、日、月、星期、年全部是BCD码。显示前先做个BCD转十进制然后按20xx-xx-xx xx:xx:xx格式拼进12864的显示缓冲区。上电首次需要初始化时间。判断条件不能只看秒寄存器是否为0因为正常走时后秒寄存器也会出现非零值。合理的做法是增加一个标志位如果秒寄存器的bit7是1表示芯片处于暂停状态此时才执行写入时间。DS1302的秒寄存器bit7是时钟暂停标志置1时振荡器停振。每次上电后把这个位清零并写入当前时间就能避免每次复位后时间错乱。3.3 12864的中文字库显示和自定义字符ST7920控制器内置了8192个中文字符这是选用12864的关键优势。显示中文时调用0x80到0x9F的区位码代码里直接用字符串指针加数组偏移量就能把汉字显示到指定行列。比如想在第2行第1列显示“温度”可以先写位置指令0x90然后连续发送两个汉字的区位码。纯英文数字的显示速度更快因为ASCII码的存储区域和中文不重叠。温度值我用英文字符显示日期和时间也全部用ASCII拼出来。12864的显示坐标在串行模式下依然有效但需要注意行列地址映射Y轴分成左右两半0x80到0x87是左半屏第1行0x88到0x8F是右半屏第1行。当一行超过8个中文字符时需要主动切换地址否则会落到右半屏的起始位置。3.4 温度负值和零下显示的处理逻辑DS18B20读取到的16位温度数据如果不做符号判断负温度会显示成很大的正数。处理方式是把原始值先右移3位得到12位有效数据再判断符号位。符号位在bit11置1表示负温度。负温度对应的数值是按补码保存的需要取反加1再除以16才能得到正确的温度绝对值。显示时单独设置一个低温标记温度低于零度就在数字前面加一个负号。Proteus仿真不给你提供极端温度环境这个逻辑一定得通过手动修改DS18B20寄存器来验证。仿真里双击DS18B20元件在Temperature窗口填一个负值然后观察显示是否正确。这一步很容易被忽略实际做实物时冬天测试务必检查。3.5 完整的代码骨架和编译注意点下面给出项目主文件和头文件的组织方式// main.c #include reg51.h #include ds1302.h #include ds18b20.h #include lcd12864.h void main(void) { unsigned char temperature_h, temperature_l; LCD12864_Init(); DS1302_Init(); while (1) { DS1302_ReadTime(); DS18B20_ReadTemp(temperature_h, temperature_l); LCD12864_DisplayTime(time_buf); LCD12864_DisplayTemp(temperature_h, temperature_l); DelayMs(200); // 200ms刷新一次时间显示仍有连续感 } }编译时优先用Keil C51芯片选AT89C51即可。Proteus仿真时如果用的是AT89C52代码兼容性也没有任何问题。头文件里需要注意sbit定义引脚和Proteus原理图保持完全一致引脚错了的话程序跑得通但设备响应异常。例如DS1302的CE如果接P3.5头文件写成#define DS1302_CE P3_5Proteus就必须连到P3.5。4. Proteus仿真搭建、晶振不起振和hex加载问题4.1 元件清单和连接方式在Proteus 8 Professional里新建原理图AT89C51从Microprocessor库取DS1302从时钟库取DS18B20从传感器库取AMPIRE128X64从显示库取。连接DS18B20时数据线到VCC之间加一个4.7k上拉电阻。DS1302的X1和X2之间接32768Hz晶振两个引脚各接一个22pF电容到地。12864的串行模式下PSB引脚要接GNDCS接P3.4SCLK接P3.3SID接P3.2。元件Proteus库路径关键字常见仿真问题AT89C51Microprocessor ICs未设置晶振频率导致定时不准DS1302Texas Instruments晶振不起振不走时DS18B20Dallas Semiconductor未加上拉电阻读不到存在脉冲AMPIRE128X64DisplayPSB悬空导致串行不生效4.2 DS1302晶振不起振的解决办法Proteus仿真DS1302时最典型的问题就是时间不走。原因是Proteus对32.768kHz晶振的等效模型不够完善X1和X2之间的振荡幅度太小。手动加一个10M欧姆电阻跨接在X1和X2之间或者把两个22pF电容对调位置都能改善。更稳定的做法是给DS1302的VCC2接5V电源VCC1接备用电池的正极仿真中备用电池用3V直流源代替。还有一种极端情况是DS1302虽然初始化了但秒寄存器一直不动。这时候检查一下命令字是不是误用了写保护。初始化里必须调用DS1302_WriteByte(0x8E); DS1302_WriteByte(0x00);否则后面所有写入时间都被拒绝。同理写完时间后把写保护寄存器重新设成0x80防止主程序误改时间。4.3 hex文件加载和仿真运行Keil编译生成hex文件后在Proteus里双击AT89C51元件Program File路径指向生成的hex文件。这里有个常见低级错误Keil工程里没勾选Create HEX File编译成功后根本没有hex文件生成。工具栏的Options for Target里Output页勾上Create HEX File再重新编译。仿真开始后如果12864没有反应先检查单片机的EA引脚是否接高电平。AT89C51的EA引脚必须接VCC否则程序从头地址执行时会去访问外部存储器代码根本跑不起来。复位电路同样重要RST引脚接10uF电容到VCC、10k电阻到GND这个组合在Proteus里工作正常。Proteus里的DS18B20仿真速度和真实硬件不同温度转换完成时间比数据手册标称值要快。如果显示的温度比预期值高出几度可以从环境温度参数入手调低。双击DS18B20直接修改Temperature值比如设成28然后观察代码里的读取逻辑是否计算出28.0。5. 掉电走时维持与温度精度校准的小技巧DS1302同时支持VCC2和VCC1双电源VCC2正常供电时芯片从VCC2取电VCC2掉电后自动切换到VCC1。仿真里如果不接VCC1一旦主电源断开所有时间数据全部丢失。做一个简单的掉电保护电路在VCC2的供电回路里串联一个二极管到VCC然后VCC1直接接一个3V纽扣电池就能在拔掉USB后让时间继续走。实际项目中3V电池供电要关注DS1302的涓流充电功能。0x90命令字可以配置充电回路但51单片机这个项目里一般用不到。充电使能设置不当反而会反向充电烧坏电池建议初始化时把0x90寄存器清零。温度精度的校准方法是先准备一个标准水银温度计和12864上显示的温度做对比记录差值。DS18B20的出厂精度是±0.5℃如果偏差固定可以在软件里加上一个校准偏移量。比如读到的温度是25.5℃标准温度计是25.0℃就在显示前减去0.5。这个偏移量可以放到一个全局变量里方便后续调整。DS18B20在真实硬件上还有一个细节传感器通电后第一次读取的温度值经常是85℃这是芯片复位后的默认上电值不是真实温度。处理方法是在初始化后连续读两次显示第二次或第三次的值丢弃第一次。时间校准方面DS1302走时精度主要取决于外部晶振的负载电容。仿真没法验证晶振精度实物做好后如果发现每天快几秒可以微调两个负载电容值。22pF换成15pF通常能让频率走下坡换成27pF则略微加快具体取决于晶振本身的初始误差。这是个需要反复试验的过程但也是时钟项目里最有工程感的部分。最后给一个验证程序整体可靠性的方法把DS1302初始化函数里的时间设成一个整点比如23:59:55然后让程序连续跑5秒观察秒数是否从55走到00同时看12864是否在23:59:59后把日期进位。日期进位正常说明BCD码转换和显示字符串拼接没写错温度值在室温范围内连续波动不超过0.5说明DS18B20的上拉和采样逻辑都正确。这一整套跑通之后这个51单片机时钟温度计就具备出实物或交课程设计的基础了。本文还有配套的精品资源点击获取