简介这是一份面向嵌入式初学者与DIY爱好者的12864液晶多功能电子时钟项目源码包围绕时间显示、温度采集与节日提醒三大功能展开适合用来练习单片机编程、LCD驱动与传感器接口设计。压缩包共20个文件约62KB以C语言源文件与头文件为核心包含lcd12864.c及ds1302、18b20等驱动头文件另有Keil工程文件uv2、opt、plg、编译产物hex、obj、lst、m51和启动代码STARTUP.A51完整覆盖从源码到可烧录固件的流程。项目涉及12864屏幕初始化与数据写入、DS1302时钟芯片走时、DS18B20温度读取以及闰年判断、12/24小时制切换和节日算法等知识点硬件上可参考I2C、SPI或GPIO连接方式。目前已有127人学习下载读者可据此理清时钟类项目的模块划分与调试思路并在此基础上扩展界面布局与低功耗设计。1. 从一份 LCD.rar 说起12864 上同时跑温度与时钟到底难在哪很多人第一次拿到「LCD.rar_12864_12864 温度_温度与时钟_电子时钟」这类工程包第一反应是解压、编译、烧录然后盯着屏幕等它亮。结果往往是屏亮了字是花的字正常了温度不动温度动了时钟走两分钟就飘。问题不在某一个模块而在于 12864 这块屏、DS18B20 这类温度传感器、以及 RTC 时钟三者共享同一套时序和显示资源任何一个环节的时序没对齐整机就表现为「玄学故障」。这个标题对应的是一套典型的 12864 电子时钟方案用 ST7920 或 KS0108 驱动的 12864 液晶同时显示实时时钟和温度常见于单片机课程设计、桌面摆件、以及带温度补偿的工业面板。它解决的是「一块低分辨率单色屏上如何稳定地同时呈现时间与温度」这件事。适合正在做单片机综合实验、想把手里的 12864 用起来、或者被时钟走时和温度读数反复折磨的嵌入式从业者。下面按「先立住原理、再动手复现、最后避坑」的顺序拆开讲。2. 12864 显示与温度时钟的底层账先算清资源再动手2.1 12864 的两种驱动选错后面全白干12864 是 128×64 点阵的单色液晶市面上主流两种控制芯片ST7920 和 KS0108。ST7920 自带字库支持并行和串行两种接口串行只要 3 根线CS、SID、CLK就能驱动适合引脚紧张的单片机KS0108 没有字库必须自己取模但刷新逻辑更直接分左右两个半屏各 64×64 控制。选型上我一般这样判断如果 MCU 引脚少于 20 个可用 IO优先 ST7920 串行模式如果要做自定义图形、波形、大字号KS0108 更灵活因为你可以完全控制每个像素。标题里带「温度与时钟」说明显示内容以字符为主ST7920 的字库能省掉大量取模工作是更省事的选择。关键参数先记住ST7920 串行模式下每个字节要分两个周期发送先发高 4 位再发低 4 位每次传输在 CLK 上升沿采样。这个「先高后低」的顺序如果写反屏幕会显示乱码而不是不亮这是新手最容易翻车的地方。2.2 温度采集DS18B20 的时序比代码本身更重要温度部分常见用 DS18B20单总线一根线完成供电和数据。它的难点不在读寄存器而在严格的时序窗口复位脉冲要拉低至少 480μs存在脉冲检测要在 60~240μs 内完成写 0 和写 1 靠拉低持续时间区分写 0 拉低 60μs 以上写 1 拉低 1~15μs 后释放。很多人的温度读数偶尔跳成 85℃这不是传感器坏了而是复位时序被中断打断DS18B20 返回了上电默认值 0x0550换算出来正好是 85。解决办法是在复位和读写期间关中断或者用硬件定时器精确控制延时。温度换算公式原始值是 16 位有符号数低 4 位是小数部分分辨率默认 0.0625℃。整数部分取高 12 位小数部分乘 0.0625。如果配置成 9 位分辨率转换时间只要 93.75ms12 位则要 750ms这个时间必须等够否则读到的是上一次的残值。2.3 时钟源RTC 芯片和 MCU 内部 RTC 的取舍时钟部分有两条路外挂 DS1302/DS3231 这类 RTC 芯片或者用 MCU 内部 RTC 加 32.768kHz 晶振。DS3231 精度高自带温度补偿年误差在分钟级DS1302 便宜但精度差而且对晶振负载电容敏感走时飘是常态。如果标题里的方案用的是内部 RTC那就要注意很多 MCU 的内部 RTC 在低功耗模式下才准全速运行时如果时钟源配置不对一天差几分钟很正常。常见做法是外挂一颗 32.768kHz 晶振配 12.5pF 负载电容走时精度能到 ±20ppm 以内。这里涉及一个跨时钟域的问题RTC 走秒是异步于主循环的如果主循环在刷屏时读时间可能读到「秒」正在进位的中途值。稳妥做法是读两次时间如果两次秒值相同才采用或者用 RTC 的秒中断来触发刷新。2.4 最小可跑通的资源清单在动手写代码前先把这套方案的资源账算清楚避免中途发现引脚不够或内存爆掉资源典型占用说明IO 引脚3ST7920 串行 1DS18B20 2I2C 接 DS3231共 6 根留出调试串口Flash8~16KB字库占大头ST7920 内置字库可省RAM512B~1KB显示缓冲区 128×64/8 1KB可只开半屏定时器1 个用于 DS18B20 微秒延时和刷新节拍中断1 个RTC 秒中断或定时器中断提示如果 MCU 的 RAM 小于 1KB不要开全屏缓冲直接写屏代价是刷新时可能有轻微闪烁但能跑起来比跑不起来强。3. 把温度与时钟同时显示到 12864从接线到刷屏的完整步骤3.1 硬件接线与上电自检先按 ST7920 串行模式接线CS 接任意 IOSID 接 MOSI 或普通 IOCLK 接 SCK 或普通 IO。DS18B20 的 DQ 接一个 IO同时接 4.7kΩ 上拉到 VCC。DS3231 的 SDA/SCL 接 I2C 引脚同样需要上拉。上电后先做两件事一是给 12864 发复位指令二是读 DS18B20 的 ROM 是否存在。如果屏幕全亮或全暗先查对比度调节引脚V0ST7920 的 V0 通常接一个 10kΩ 电位器中点调不到合适对比度就什么都看不见。// ST7920 串行写一个字节先高4位后低4位 void lcd_write_byte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { LCD_SID (dat 0x80) ? 1 : 0; // 取最高位 LCD_CLK 0; delay_us(1); LCD_CLK 1; // 上升沿采样 delay_us(1); dat 1; } } // 发送指令CS拉高发送0xF8同步字再发指令码 void lcd_send_cmd(uint8_t cmd) { LCD_CS 1; lcd_write_byte(0xF8); // 同步字固定 lcd_write_byte(cmd 0xF0); // 高4位 lcd_write_byte((cmd 4) 0xF0); // 低4位 LCD_CS 0; }这段代码里0xF8是 ST7920 串行模式的同步字写指令和写数据分别用0xF8和0xFA。lcd_write_byte里先发最高位循环 8 次每次在 CLK 上升沿让芯片采样。延时 1μs 是保守值实际 ST7920 能跑到 1MHz 以上但新手先用慢速跑通再提速。3.2 DS18B20 温度读取的完整函数温度读取分三步复位、跳过 ROM、启动转换、等待、读暂存器。下面是一个不依赖库的完整实现// 复位 DS18B20返回 0 表示存在 uint8_t ds18b20_reset(void) { uint8_t presence; DS_DQ 0; // 拉低 delay_us(480); // 至少480us DS_DQ 1; // 释放 delay_us(60); // 等15~60us presence DS_DQ; // 读存在脉冲0表示存在 delay_us(420); // 等复位完成 return presence; } // 读温度返回摄氏度×100的整数避免浮点 int16_t ds18b20_read_temp(void) { uint8_t tl, th; int16_t 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; return (int16_t)((int32_t)raw * 625 / 100); // 0.0625℃转成×100 }0xCC是跳过 ROM单传感器场景下省去匹配 ROM 的步骤。0x44启动转换后必须等够 750ms这是 12 位分辨率的硬性要求。最后raw * 625 / 100是把 0.0625 的步进放大 100 倍用整数运算避免浮点返回 2537 就代表 25.37℃。如果读到 8500说明复位失败检查上拉电阻和延时。3.3 时钟读取与显示格式化以 DS3231 为例走 I2C 读秒、分、时寄存器BCD 码转十进制后格式化成字符串。DS3231 的寄存器 0x00 是秒0x01 是分0x02 是时都是 BCD 格式。// BCD转十进制 uint8_t bcd2dec(uint8_t b) { return (b 4) * 10 (b 0x0F); } // 读时间并显示到12864指定位置 void show_time(void) { uint8_t sec, min, hour; char buf[9]; i2c_read(DS3231_ADDR, 0x00, sec, 1); i2c_read(DS3231_ADDR, 0x01, min, 1); i2c_read(DS3231_ADDR, 0x02, hour, 1); sec bcd2dec(sec 0x7F); // 去掉CH位 min bcd2dec(min); hour bcd2dec(hour 0x3F); // 24小时制 sprintf(buf, %02d:%02d:%02d, hour, min, sec); lcd_display_string(0, 0, buf); // 第0行第0列 }sprintf在单片机上占 Flash如果空间紧张可以用手动查表转字符。sec 0x7F是去掉最高位的时钟停止位DS3231 上电时这一位可能是 1不清掉会读到 80 秒这种非法值。3.4 主循环的刷新节拍与显示布局12864 分辨率 128×64一行 16 个汉字或 32 个字符共 4 行。典型布局第一行显示日期第二行显示时间第三行显示温度第四行留空或显示星期。刷新节拍不要跟着主循环跑否则 DS18B20 的 750ms 等待会把刷屏卡死。正确做法是用定时器每 200ms 置一个标志主循环检测标志再刷屏温度每 1s 更新一次时间每 200ms 更新一次。这样屏幕不会闪温度也不会因为刷新太频繁而反复触发转换。volatile uint8_t refresh_flag 0; // 定时器中断里 void timer_isr(void) { static uint8_t cnt 0; if (cnt 20) { // 200ms cnt 0; refresh_flag 1; } } // 主循环 while (1) { if (refresh_flag) { refresh_flag 0; show_time(); if (temp_cnt 5) { // 每1s读一次温度 temp_cnt 0; show_temp(); } } }4. 温度与时钟联调时最容易翻车的五个地方4.1 温度偶尔跳 85℃现象温度显示大部分时间正常偶尔蹦出 85.00。原因DS18B20 复位时被中断打断存在脉冲没检测到后续读到的 0x0550 就是 85℃。解决在ds18b20_reset和读写函数前后关总中断或者把延时改成硬件定时器精确延时确保 480μs 不被拉长。4.2 时钟每走几分钟就慢几秒现象DS1302 方案走时明显偏慢一天差几分钟。原因32.768kHz 晶振的负载电容不匹配或者晶振走线太长引入寄生电容。解决换 6pF 或 12.5pF 电容试晶振尽量靠近芯片走线包地。如果用的是内部 RTC检查时钟源是不是被主频分频影响。4.3 屏幕显示乱码但能亮现象上电后屏幕有显示但全是乱码或错位。原因ST7920 串行模式同步字写错或者先发了低 4 位。解决确认写指令用 0xF8、写数据用 0xFA且每个字节先发高 4 位。另外检查初始化序列里有没有发 0x30 进入基本指令集。4.4 温度刷新时屏幕闪烁现象每次温度更新屏幕整体闪一下。原因温度读取的 750ms 等待期间没有刷屏或者刷屏时清了整个缓冲区再重写。解决温度读取放到定时器标志里异步做刷屏只更新变化的区域不要全屏清。4.5 断电后时间归零现象重新上电时间从 00:00:00 开始。原因RTC 芯片的备用电池没接或没电或者 MCU 内部 RTC 没有 VBAT 供电。解决DS3231 接 CR2032 纽扣电池测电池电压是否高于 2.5V内部 RTC 方案检查 VBAT 引脚是否接了 3V 电池。5. 让这套 12864 温度时钟更耐用的三个进阶习惯第一个习惯是给温度加滑动平均。DS18B20 本身有 ±0.5℃ 的误差单次读数跳变很正常。我一般连续读 5 次去掉最高最低剩下 3 次取平均显示就稳很多。代价是刷新周期从 1s 变成 5s但桌面时钟不需要那么快。int16_t temp_filter(void) { int16_t buf[5]; uint8_t i, j; int16_t tmp, sum 0; for (i 0; i 5; i) { buf[i] ds18b20_read_temp(); delay_ms(800); } // 冒泡排序 for (i 0; i 4; i) for (j 0; j 4 - i; j) if (buf[j] buf[j1]) { tmp buf[j]; buf[j] buf[j1]; buf[j1] tmp; } for (i 1; i 4; i) sum buf[i]; // 去掉最小和最大 return sum / 3; }第二个习惯是给时钟加校准接口。DS3231 有老化偏移寄存器如果发现每月固定快或慢几秒可以往这个寄存器写补偿值。具体是读 0x10 寄存器按每 LSB 约 0.1ppm 调整一年能校准到分钟级以内。这个操作不需要拆机通过串口发命令就能做。第三个习惯是显示布局留余量。12864 只有 4 行如果以后想加湿度、气压现在就把温度和时间压缩到两行第三行留给扩展。我见过太多方案一开始占满四行后来想加功能只能重画界面。进阶项代价收益滑动平均刷新变慢温度不跳变老化校准需要串口工具月误差降到秒级布局留余量当前显示略空后期扩展不用改板这套方案我前后做过三版第一版死在 DS18B20 的 85℃ 上第二版死在 DS1302 走时飘第三版才把温度滤波和 RTC 校准都加上。血泪经验就一句先把单模块跑稳再联调别一上来就三合一。希望帮到你。本文还有配套的精品资源点击获取