第四讲DS1302模块

以后看到 DS1302,你只需要脑补出三个搬运工、一种怪异的语言、一本带锁的密码本。


1. 硬件层:三个搬运工 (The Pins)

DS1302 与单片机对话只靠三根线,各司其职:

  • RST (CE) —— 老板: 平时躺平 (0),站起来 (1) 就代表“要开会了”,通信开始。
  • SCLK (SCK) —— 鼓手: 负责打拍子。
    • 上升沿 (0→1):单片机把数据写进芯片。
    • 下降沿 (1→0):芯片把数据吐给单片机。
  • I/O (SDA) —— 搬运工: 唯一的数据通道。数据排成队,一位一位地过独木桥(串行通信)。

2. 软件层:通信暗号 (The Protocol)

想要数据传得对,必须遵守这三个规矩:

  • 低位先行 (LSB First): 发数据时,先发第 0 位(脚底板),最后发第 7 位(头顶)。
    • 写代码技巧: temp & 0x01 (取最低位) 配合 temp >>= 1 (右移)。
    • 读代码技巧: temp >>= 1 (腾位置) 配合 temp |= 0x80 (填最高位)。
  • 时序反转:
    • 上升沿干活(拍照)。
    • 下降沿干活(吐货)。

3. 数据层:怪异的语言 (BCD Code)

DS1302 不讲普通的二进制,它讲 BCD 码

  • 特征: 用 16 进制的样子存 10 进制的数。
    • 比如 45秒,它存的是 0x45,而不是 0x2D
  • 翻译方法(给数码管看):
    • 切高位(十位): 数据 / 16
    • 割低位(个位): 数据 % 16

4. 逻辑层:带锁的密码本 (Register Map)

所有的操作都是在读写“寄存器”(小房间)。

  • 地址规律:

    • 写地址:84 → 分 82 → 秒 80 (每次减 2)。
    • 读地址:85 → 分 83 → 秒 81 (每次减 2)。
    • 写保护 (WP): 地址 8E
      • 写之前必须 0x00 解锁
      • 写完最好 0x80 上锁
  • 代码实战公式:
    利用地址减 2 的规律,用 for 循环批量处理:

    // 比如写时间:
    Write_Byte(0x84 - i*2, time_array[i]); 
    // 注意数组顺序必须是:[时, 分, 秒]
    

5. 避坑指南 (Checklist)

如果你写完代码发现时间不走或者乱码,请检查:

  1. 有没有解锁? (操作 0x8E 寄存器了吗?)
  2. 秒的最高位 (CH) 是不是 1? (如果是 1,时钟就暂停了,要写 0)。
  3. 读写有没有搞反? (读是下降沿,写是上升沿)。
  4. 数码管显示有没有 /16? (必须把 BCD 码拆解才能显示)。

这就好比是**“拿着地图走迷宫”**。

左边是地图(DS1302 的寄存器表),右边是怎么走(代码实现)。

这两段代码非常聪明,它没有笨笨地写三行“写秒、写分、写时”,而是利用了地址的数学规律,用一个循环搞定了。

我们来破解其中的奥秘:


6. 你需要的代码编写

请仔细观察左侧表格里 秒、分、时 的地址:

  1. 写地址 (WRITE):

    • 时 (Hour): 84h
    • 分 (Min): 82h
    • 秒 (Sec): 80h
    • 规律: 从“时”开始,每往下一级,地址就 减 2 (84 -> 82 -> 80)。
  2. 读地址 (READ):

    • 时 (Hour): 85h
    • 分 (Min): 83h
    • 秒 (Sec): 81h
    • 规律: 同样是每往下一级,地址就 减 2 (85 -> 83 -> 81)。

二、 看右边的代码(利用规律)

1. 设置时间函数 Set_Rtc

for(i=0; i<3; i++)
    Write_Ds1302_Byte(0x84-i*2, ucRtc[i]);

这个 0x84 - i*2 简直是神来之笔。让我们把 i = 0, 1, 2 带进去算一下:

  • 当 i = 0 时:
    • 地址:0x84 - 0 = 0x84 (查表:这是写小时)。
    • 动作:把 ucRtc[0] 写进小时寄存器。
  • 当 i = 1 时:
    • 地址:0x84 - 2 = 0x82 (查表:这是写分钟)。
    • 动作:把 ucRtc[1] 写进分钟寄存器。
  • 当 i = 2 时:
    • 地址:0x84 - 4 = 0x80 (查表:这是写秒)。
    • 动作:把 ucRtc[2] 写进秒寄存器。

重要结论:
这段代码默认你的数组 ucRtc 的顺序必须是:{ 时, 分, 秒 }。如果你按“秒分时”存,时间就全乱了。


2. 读取时间函数 Read_Rtc

for(i=0; i<3; i++)
    ucRtc[i] = Read_Ds1302_Byte(0x85-i*2);

逻辑完全一样,只是起始点变成了 读地址

  • i = 0: 0x85 → 读 → 存入 ucRtc[0]
  • i = 1: 0x83 → 读 → 存入 ucRtc[1]
  • i = 2: 0x81 → 读 → 存入 ucRtc[2]

三、 那个奇怪的开关锁 0x8e

Set_Rtc 函数里:

  1. 开头: Write_Ds1302_Byte(0x8e, 0);

    • 对应表格倒数第二行 WP (Write Protect)
    • 0 是为了 关闭写保护,否则 DS1302 拒绝任何修改。
  2. 结尾: Write_Ds1302_Byte(0x8e, 1);

    • 这里是为了 重新打开写保护
    • (注意一个小细节): 严格来说,根据表格,WP 是第 7 位(最高位)。标准的写法应该是写入 0x80 (二进制 1000 0000) 来打开 WP。
    • 截图里的代码写的是 1 (二进制 0000 0001)。这在有些比赛代码里能跑通可能是因为逻辑不严谨,或者仅仅是想表达“非零值”,但严谨的写法推荐用 0x80。不过在蓝桥杯比赛中,只要能锁住就行,有时候写 1 也能混过去(取决于具体芯片实现,但大概率这一行在截图代码里没起到真正的“写保护”作用,不过不影响时间设置功能)。

总结

这两段代码就是**“查表数学题”**:

  1. 写时间:0x84 (时) 开始,每次减 2。
  2. 读时间:0x85 (时) 开始,每次减 2。
  3. 数组顺序: 必须是 [时, 分, 秒]