第三十四章 RTC实时时钟LCD显示实验
本章在讲什么
上一章我们造好了一个通用 I2C 驱动模块,本章立刻兑现它的复用价值:换一颗芯片——RTC 实时时钟 PCF8563(板上焊接的是兼容的国产 AT8563),把芯片里的实时时间读出来,显示在 RGB LCD 屏幕上。
PCF8563 是一款低功耗时钟/日历芯片,内部自带 32.768KHz 晶振计时,即使系统断电(有后备电池时)也能持续走时。它有 16 个 8 位寄存器:00H/01H 是控制/状态寄存器,02H~08H 是秒、分、时、日、月、年计时寄存器——本章只需要打交道的就是这 7 个。
实验任务:FPGA 先给 PCF8563 写入初始时间,然后不断读取当前时间,在 LCD 屏上分两行显示"年月日"和"时分秒",秒在不停跳动。
本章最大的学习点是看懂同一套 I2C 驱动如何通过改参数适配新芯片,以及 BCD 码数据如何直接映射到字符显示。
核心概念拆解
34.1 PCF8563 的三个关键特性
① 器件地址 7'h51:注意和 EEPROM(7'h50)不同!两者挂在同一条 I2C 总线上,全靠地址区分。顶层模块中:
parameter SLAVE_ADDR = 7'b101_0001; // PCF8563 器件地址(EEPROM 是 101_0000)
parameter BIT_CTRL = 1'b0; // 字地址 8 位(EEPROM 是 16 位)
② BCD 码存储:时间寄存器中的数值用 8421 BCD 码存储——4 个二进制位表示 1 位十进制数。比如 23 秒存的是 8'b0010_0011(0x23),而不是二进制的 0x17。这个特性带来巨大便利:读出的高 4 位就是十位、低 4 位就是个位,可以直接当字符数组下索引字模,不需要做二进制-十进制转换。
③ 上电要等 8ms:芯片手册规定,上电到第一个起始信号之间需等待 8ms 消除不稳定状态。代码里用 WAIT_TIME = 13'd8_000(dri_clk 为 1MHz,8000 个周期 = 8ms)实现。
另外注意秒寄存器的最高位 VL 是"数据有效性标志"(VL=1 表示时钟数据不保证准确),所以代码读秒时用 i2c_data_r[6:0] 而不是整个字节;月寄存器的最高位是世纪位 C,读月时取 i2c_data_r[4:0]。
34.2 系统架构:I2C 驱动零改动复用
top_rtc_lcd
├── pcf8563_ctrl (新增:初始化 + 循环读时间,输出 sec/min/hour/day/mon/year)
├── i2c_dri (原封不动复用上一章的驱动!)
└── lcd_rgb_char (由字符显示实验改造:lcd_display 模块重写显示内容)
对比上一章,i2c_dri 一行代码都没改——只是顶层参数从 SLAVE_ADDR=7'h50, BIT_CTRL=1 变成了 SLAVE_ADDR=7'h51, BIT_CTRL=0。这正是上一章把器件地址、字地址位数做成参数的回报。
34.3 pcf8563_ctrl:一条"写6个寄存器→循环读6个寄存器"的状态流
业务流程:上电等 8ms → 依次写秒(02h)、分(03h)、时(04h)、天(05h)、月(07h)、年(08h)寄存器 → 拉高 i2c_rh_wl 切读模式 → 从 flow_cnt=1 重新开始循环读。用 flow_cnt[3:0] 编排,每两个状态处理一个寄存器(一个触发、一个等完成):
4'd1: begin // 触发写/读秒寄存器
i2c_exec <= 1'b1;
i2c_addr <= 16'h02;
flow_cnt <= flow_cnt + 4'b1;
i2c_data_w <= TIME_INIT[7:0]; // 初始时间的秒字段
end
4'd2: begin // 等本次操作完成
if(i2c_done == 1'b1) begin
sec <= i2c_data_r[6:0]; // 写阶段存初始值,读阶段存实时值
flow_cnt<= flow_cnt + 4'b1;
end
end
初始时间通过参数传入:TIME_INIT = 48'h24_01_01_09_30_00,从高到低依次是年、月、日、时、分、秒(即 2024 年 01 月 01 日 09:30:00)。年只写 0~99 两位,显示时千位百位固定为"20"。
巧妙之处在于同一组状态既用于写也用于读:写阶段 i2c_data_w 从 TIME_INIT 取值,读阶段同一个 sec <= i2c_data_r[6:0] 语句自动接收实时数据;年寄存器读完时 i2c_rh_wl <= 1'b1 并把 flow_cnt 拉回 1,实现无限循环读——因为时钟在走,必须持续刷新。
初始化只执行一次:PCF8563 会从初始时间开始自行累计计时,之后每次读到的都是最新时间。
34.4 LCD 显示:BCD 直接索引字模数组
显示方案:每个字符 8×16 像素,第一行从 (1,1) 起显示"20XX XX XX"(年月日),第二行从 (17,17) 起显示"XX XX XX"(时分秒),黑字白底。
数字 0~9 的字模由取模软件生成,存成 10 个 128 位的数组(16 行 × 8 列,一行占 8 位):
reg [127:0] char [9:0];
char[2] <= 128'h0000003C4242420404081020427E0000; // "2"的字模
每个像素位置判断的核心公式(以第一行第一个字符"2"为例):
else if((pixel_xpos >= CHAR_POS_X_1 - 11'd1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*1 - 11'd1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
if(char[2][(CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos - (CHAR_POS_X_1-11'd1)) % 8) - 11'd1])
pixel_data <= BLACK; // 字模位为1 → 黑色
else
pixel_data <= WHITE; // 为0 → 白色背景
end
理解这段代码的关键是把 128 位字模看成 16×8 的二维点阵:
(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8:算出当前是第几行(每换一行跨 8 位);(pixel_xpos - 起点) % 8:算出当前是这一行的第几列;- 最终索引到 128 位中的某一位,为 1 涂黑、为 0 涂白。
显示年月日/时分秒数值时,只需把字面量 char[2] 换成变量索引:年的十位用 char[year[7:4]]、个位用 char[year[3:0]]——BCD 码的高 4 位/低 4 位恰好就是十位/个位的数值,直接当下标取字模,一行转换代码都不用写。这就是 34.1 说的 BCD 红利。
图片对照

图:PCF8563 原理图——与 EEPROM 挂在同一条 I2C 总线,OSCI/OSCO 接 32.768KHz 晶振

图:系统架构——I2C 驱动模块原样复用,只新增 pcf8563_ctrl 控制模块

图:LCD 显示效果——第一行年月日、第二行时分秒

图:上板现象——屏幕时间持续走动
初学者容易踩的坑
- 器件地址没换:直接复制 EEPROM 工程却忘了把
SLAVE_ADDR从7'h50改成7'h51,PCF8563 永远不应答。同样BIT_CTRL要从 1 改成 0(PCF8563 寄存器地址是单字节 02H~08H)。 - 忘了 8ms 上电等待:跳过
WAIT_TIME直接配置寄存器,芯片还没准备好,初始化可能失败或数据错乱。 - 把 BCD 当二进制处理:如果对读出的秒做二进制运算(如
sec + 1),得到的结果显示出来会乱跳。记住寄存器里 0x59 表示 59 秒;想显示就直接拆 nibble 索引字模,想做算术才需要 BCD→二进制转换。 - 寄存器保留位没屏蔽:秒寄存器 bit7 是 VL 标志、月寄存器 bit7 是世纪位、bit6~5 保留,直接用整个字节索引字模会越界(索引到 10 以上的字模数组)。必须按位截取:
[6:0]、[4:0]等。 - 时间不刷新或一直重新初始化:读循环必须回到"读秒"状态无限往复;如果不小心让 flow_cnt 每圈都走写路径,屏幕时间会永远停在初始值。区分点就是
i2c_rh_wl只在年寄存器读完那一次被拉高。 - 字模数组索引公式抄错:
(x % 8)、行偏移乘 8、起点坐标减 1,三处细节错一处,字符就会显示成乱码或镜像。建议先用固定字符"2"验证公式,再替换成变量索引。
小结 & 下一步
本章的核心收获不是新协议,而是复用与适配的方法论:
- 驱动复用:上一章参数化的 i2c_dri 零改动跑在新芯片上——好的分层设计会让"换器件"变成"换参数";
- 器件适配三要素:换一颗 I2C 芯片只需要改三件事——器件地址、字地址位数、寄存器布局(地址+含义+保留位);
- BCD 码的显示红利:拆 nibble 直接索引字模,省去进制转换;
- 持续轮询模型:对"数据自己在变"的器件(时钟),业务层是无限循环读,而不是 EEPROM 那种"一次测试就结束"。
下一步:第三十五章将进入测量仪器领域——等精度频率计。将学习如何用 FPGA 精确测量信号频率,涉及闸门时间、等精度测量法如何消除 ±1 误差等经典测频技术。