I²C 实时时钟(RTC)
15.1 概述
如今,我们所有的计算机都有一个听起来理所当然的关键功能:实时时钟,即 RTC。这个硬件组件让计算机能够跟踪当前日期,即使断电后也能保持。
在 70 年代末和 80 年代的大多数复古计算机上,都没有配备 RTC。虽然这看起来不是一个非常关键的功能,但一旦我们尝试将其与现代计算机接口,就可能会引发一些问题。例如,常见的文件系统都有一个日期字段,记录文件或目录的创建和/或修改日期。因此,如果我们想让 8 位计算机为新文件生成这些信息,就需要一种方法来持续跟踪当前日期和时间。
15.2 硬件实现
在 Zeal 8-bit Computer 上,RTC 由 DS1307 集成电路实现,在 PCB 上标记为 U13。选择该电路是因为其外形小巧且编程简单,因为它也使用 I²C 协议。
这个 RTC 需要两个外部组件才能保持时间追踪:
- PCB 上的晶振,标记为 32.768KHz,电容为 12.5pF
- PCB 上的 3V 备用电池,用于在主电源关闭时保持计时
安装在 Zeal 8-bit Computer 上的备用电池座(标记为 BT1)兼容任何标准 CR20xx 电池,包括 CR2016、CR2025 和 CR2032。这些电池之间的唯一区别是厚度和容量。
当计算机通电时,备用电池不会被 RTC 使用。实际上,它内部有一个内置电路,可以检测电源故障,并且仅当主电源不再连接时自动切换到备用电池。
以下是该集成电路的引脚排列:
信号说明如下:
- $XTAL1$ 和 $XTAL2$:连接到 PCB 上标记为
Y1的 32.768KHz 晶振 - $V_{BAT}$:来自备用电池连接器的正极
- $Vcc$ 和 $Gnd$:主电源(5V)
- $SQW$:开漏方波信号,查看附加功能章节了解更多信息
- $SCL$ 和 $SDA$:分别为 I²C 时钟和数据信号,更多信息请查看内部集成电路(I²C)章节
RTC 没有任何用于配置其从设备地址的引脚。事实上,其 7 位地址被硬编码为 0x68。
⚠️ 警告
RTC 要求 $V_{BAT}$ 始终设置为已知值,绝不能悬空。因此,在未安装电池时与 RTC 通信可能导致错误。 在这种情况下且没有电池时,在 $V_{BAT}$ 和 $Gnd$ 之间连接一个电阻(约 20kΩ)。
15.3 与 RTC 通信
寄存器
RTC 内部围绕 7 个寄存器组织,分别表示秒、分、时、星期、日、月和年。所有这些寄存器包含的值以二进制编码十进制(BCD)表示。
二进制编码十进制是一种表示十进制数的方法(即我们日常使用的数字表示方式),通过将每个数字转换为其十六进制等效值来实现。 换句话说,不是将整个十进制数转换为十六进制,而是将每个数字分别转换为十六进制并将它们连接起来。
例如,十进制数 59 的 BCD 表示为 0x59(而不是 0x3b)
这 7 个寄存器如下表所述:
表 2 计时寄存器
| 地址 | 位 7 | 位 6 | 位 5 | 位 4 | 位 3 | 位 2 | 位 1 | 位 0 | 名称 | 范围 |
|---|---|---|---|---|---|---|---|---|---|---|
| 0x00 | HALT1 | 10 秒 | 秒 | 秒 | 00-59 | |||||
| 0x01 | 0 | 10 分 | 分 | 分 | 00-59 | |||||
| 0x02 | 0 | 12/24 模式2 | 10 时 或 下午/上午 | 10 时 | 时 | 时 | 1–12+上午/下午 24 00–23 | |||
| 0x03 | 0 | 0 | 0 | 0 | 0 | 星期 | 星期 | 01-07 | ||
| 0x04 | 0 | 0 | 10 日 | 日 | 日 | 01-31 | ||||
| 0x05 | 0 | 0 | 0 | 10 月 | 月 | 月 | 01-12 | |||
| 0x06 | 10 年 | 年 | 年 | 00-99 | ||||||
关于以下位的小注:
HALT$^1$:当此位为 1 时,时钟暂停,这降低了功耗但停止了内部计时。12/24 模式$^2$:当此位为 1 时,启用 12 小时制模式。在此模式下,位 5 为 1 表示PM(下午),为 0 表示AM(上午)。当此位为 0 时,启用 24 小时制模式。在此模式下,位 5 与位 4 一起表示小时(范围从 00 到 23)。每当模式切换时,必须手动更新时间。
读取和写入寄存器
与 RTC 通信包括检索或写入这些寄存器。与我们之前看到的 EEPROM 类似,有三种方法与 RTC 通信:
- 通过 I²C 写入事务进行写入
- 通过 I²C 写-读事务进行随机地址模式读取
- 通过 I²C 读取事务进行当前地址模式读取
例如,要启用时钟,我们需要清除寄存器 0 的位 7。为此,我们需要执行如下 I²C 写入事务:
- 生成 START 条件
- 发送设备地址
0x68,后跟写入位(0) - 确认设备已应答(SDA 被拉低)
- 发送要写入的寄存器地址,此处为
0x00 - 确认设备已应答
- 发送寄存器的新值,再次为
0x00,这也会将秒重置为 0 - 确认设备已应答
- 生成 STOP 条件
这也是在数据丢失或时钟停止后设置或更新时钟值的方法。
要读取 RTC 的值,我们可以直接执行读取而不指定要读取的寄存器,或者执行写-读来指定开始读取的寄存器。在这两种情况下,都可以通过单个事务读取多个寄存器。
这里我们只考虑第二种情况。例如,如果我们想读取从 0 开始的所有 7 个寄存器,我们需要执行如下 I²C 写-读事务:
- 生成 START 条件
- 发送设备地址
0x68,后跟写入位(0) - 确认设备已应答(SDA 被拉低)
- 发送要开始读取的寄存器地址,此处为
0x00 - 确认设备已应答
- 再次生成(RE)START 条件(不带 STOP 条件!)
- 发送设备地址
0x68,后跟读取位(1) - 确认设备已应答
- 接收第一个字节,即寄存器 0 的值,并通过在一个时钟周期内将 SDA 拉低来应答
- 对寄存器 1、2、3、4、5 执行相同操作
- 接收第六个字节,即寄存器 6 的值,但不要应答,保持 SDA 在一个时钟周期内为高电平
- 生成 STOP 条件
这些事务的图示可在前面的 I²C EEPROM 章节中找到,查看它以获取更多信息。
⚠️ 警告
RTC 可能对 SDA 和 SCL 时序非常敏感。即使整体频率符合数据手册给出的限制,它在发送从设备地址后也可能不会以 ACK 位响应。这可能是由于芯片在 SCL 变为高电平时内部锁存 SDA 线的时序所致。遗憾的是,这一点没有文档说明。
要解决此问题,请确保 SCL 时钟信号具有 50% 的占空比(低电平持续时间与高电平持续时间相同)。更重要的是,确保 SDA 线在 SCL 线变为高电平之前(而不是同时)设置为所需状态。
15.4 附加功能
RTC 集成电路提供了一些可选用的附加功能。
NVRAM
第一个功能是 RAM,该芯片提供 56 个可自由使用的字节。其主要目的是存储即使在复位后也能保留的数据,因为它也由电池供电。因此,只要电池仍有电,这部分就可以被视为非易失性存储器。
NVRAM 从地址 0x08 开始,一直到 0x3F(含)。与此部分 RAM 通信的方式与上一段描述的其他寄存器相同。
方波发生器
RTC 芯片提供的另一个功能是方波发生器。如前文引脚图所示,该电路可以在 $SQW$ 引脚上输出方波信号。
根据软件的配置方式,此引脚也可以强制为 0 或 1。寄存器 0x07 负责此功能,其组织如下:
| 位 7 | 位 6 | 位 5 | 位 4 | 位 3 | 位 2 | 位 1 | 位 0 |
|---|---|---|---|---|---|---|---|
| OUT | 0 | SQWE | 0 | 速率选择 | |||
当 SQWE 设置为 1 时,方波生成被启用,频率取决于 速率选择 的值:
| 速率选择值 | 输出频率 |
|---|---|
| 0 | 1Hz |
| 1 | 4.096KHz |
| 2 | 8.192kHz |
| 3 | 32.768kHz |
在这种情况下,OUT 位将被忽略。
当 SQWE 设置为 0 时,方波生成被禁用,$SQW$ 上的输出与 OUT 位相同:OUT 为 1 时为高电平,OUT 为 0 时为低电平。
需要注意的是,$SQW$ 引脚采用开漏配置!换句话说,当它为 0 时被拉到 $Gnd$,但当它为 1 时处于悬空状态。
得益于此,可以利用此引脚制作硬件定时器。实际上,通过连接 Zeal 8-bit Computer 上标记为 JP2 的跳线引脚,$SQW$ 引脚将连接到 PIO 的 H-SYNC IN 信号。该信号也是一个开漏信号,通过标记为 RN3 的电阻网络上拉。
因此,每当方波信号变为低电平时,PIO 可以请求中断,从而软件可以跟踪时间,以防需要执行某些基准测试或仅评估两次操作之间的经过时间。
⚠️ 警告
如果在视频端口上连接了视频板且
JP4跳线已连接以接收来自视频板的 H-blank 信号,则不建议连接JP2跳线。虽然由于开漏配置,这不会带来任何硬件短路风险,但在软件中可能难以确定 H-SYNC IN 信号中断的真实来源,因为它将在 RTC 定时器和外部视频板之间共享。