DS3231概述
常用的DS1302需要使用外置晶振,且没有温度补偿,误差较大。
DS3231内置晶振且有内部温度补偿,误差可做到1分钟每年。
项目地址:https://github.com/NorthernWidget/DS3231
-
工作电压:3.3V–5.5V
-
时钟芯片:高精度时钟芯片DS3231
-
时钟精度:0-40度范围内,精度2pm,年误差约1分钟
-
2个日历闹钟可编程输出,年月日有效时间到2100年,芯片内部自带温度传感器,精度正负3摄氏度
-
存储芯片:AT24c32(存储容量32k)
-
可编程方波输出
AT24C32级联
许多 DS3231 模块会集成一个 24C32 EEPROM 芯片(AT24C32 是一种常用的串行电可擦除可编程只读存储器(EEPROM)芯片),主要用于:
- 存储校准数据:DS3231 的时钟精度可通过校准参数优化,这些参数可存储在 24C32 中。
- 保存用户数据:例如闹钟设置、定时器配置等,断电后不丢失。
- 扩展存储容量:DS3231 内部仅有 32 字节 RAM,而 24C32 提供32KB 存储空间。
24C32 芯片的地址设置主要通过其 A0、A1、A2 引脚来实现。这三个引脚可以接高电平(VCC)或低电平(GND),通过不同的电平组合来设置芯片在 I²C 总线上的地址。
-
A0、A1、A2 引脚的电平状态决定了芯片地址的低三位。例如,当 A0 = 0、A1 = 0、A2 = 0 时,芯片地址的低三位为 000;当 A0 = 1、A1 = 0、A2 = 1 时,芯片地址的低三位为 101。
-
24C32 芯片的 I²C 地址通常为 7 位,高四位固定为 1010,低三位由 A0、A1、A2 引脚决定。比如,当 A0 = A1 = A2 = 0 时,芯片的 I²C 地址为 1010000(十六进制为 0x50);当 A0 = 1、A1 = 1、A2 = 1 时,芯片的 I²C 地址为 1010111(十六进制为 0x57)。
虽然 DS3231 不能级联,但通过 24C32 的级联可以实现:
1. 大幅扩展数据存储容量
每个 24C32 提供32KB 存储,级联多个后总容量倍增。例如:
- 2 个 24C32 → 64KB
- 4 个 24C32 → 128KB
适用于需要长期记录时间戳和传感器数据的场景(如环境监测、工业日志)。
2. 分离不同类型的数据
将时钟校准数据、用户配置、日志数据分别存储在不同的 24C32 中,避免数据覆盖风险。例如:
- 24C32-1(地址 0x50):存储时钟校准参数
- 24C32-2(地址 0x51):存储用户设置
- 24C32-3(地址 0x52):存储历史事件日志
3. 提高系统可靠性
当某个 24C32 出现故障时,其他芯片的数据不受影响。例如:
- 关键配置数据存储在多个 24C32 中,实现冗余备份。
- 按时间分片存储数据,便于故障排查(如只恢复最近 7 天的数据)。
4.示例
通过设置 A0/A1/A2 引脚的电平,可在同一 I2C 总线上连接最多 8 个 24C32:
#include <Wire.h>
// 定义多个24C32的地址
const uint8_t EEPROM_ADDR1 = 0x50; // A0=A1=A2=0
const uint8_t EEPROM_ADDR2 = 0x51; // A0=1, A1=A2=0
const uint8_t EEPROM_ADDR3 = 0x52; // A0=0, A1=1, A2=0
void setup() {
Wire.begin();
}
// 向指定地址的24C32写入数据
void writeEEPROM(uint8_t addr, uint16_t memoryAddress, uint8_t data) {
Wire.beginTransmission(addr);
Wire.write((memoryAddress >> 8) & 0xFF); // 高字节地址
Wire.write(memoryAddress & 0xFF); // 低字节地址
Wire.write(data);
Wire.endTransmission();
delay(5); // EEPROM写入需要时间
}
// 从指定地址的24C32读取数据
uint8_t readEEPROM(uint8_t addr, uint16_t memoryAddress) {
Wire.beginTransmission(addr);
Wire.write((memoryAddress >> 8) & 0xFF); // 高字节地址
Wire.write(memoryAddress & 0xFF); // 低字节地址
Wire.endTransmission();
Wire.requestFrom(addr, 1);
return Wire.read();
}
void loop() {
// 向不同的24C32写入数据
writeEEPROM(EEPROM_ADDR1, 0, millis() & 0xFF); // 存储时间戳
writeEEPROM(EEPROM_ADDR2, 0, analogRead(A0)); // 存储传感器数据
// 从不同的24C32读取数据
uint8_t data1 = readEEPROM(EEPROM_ADDR1, 0);
uint8_t data2 = readEEPROM(EEPROM_ADDR2, 0);
delay(1000);
}
多个RTC的级联
时钟芯片DS3231 I²C地址固定为0x68
,本身无法级联。若需要多个RTC的级联,可通过以下方法实现:
1. 使用I2C多路复用器(如 TCA9548A)
TCA9548A库
项目地址: https://github.com/WifWaf/TCA9548A
TCA9548A 是德州仪器(TI)推出的一款 8 通道 I²C 总线多路复用器 / 解复用器,主要用于扩展 I²C 总线的负载能力,解决单一主控制器与多个从设备通信时的地址冲突问题。
- 通道数量与切换
- 支持 8 个独立的 I²C 通道(通道 0~7),每个通道可连接一个或多个 I²C 从设备。
- 通过简单的寄存器配置,主控制器可选择任意一个通道与目标设备通信,实现 “单主多从” 的灵活扩展。
- 电气特性
- 宽电压范围:支持 1.65V~5.5V 的电源电压,兼容不同逻辑电平的主控制器(如 3.3V 或 5V 系统)。
- 双向电平转换:自动适配主控制器与从设备的电压电平,无需额外电平转换电路,简化设计。
- 高总线速度:支持标准模式(100kHz)、快速模式(400kHz)和快速模式 Plus(1MHz),满足高速 I²C 通信需求。
类成员函数
1. 构造函数 TCA9548A(uint8_t address = 0x70)
- 功能:创建
TCA9548A
类的一个实例,同时设定 TCA9548A 芯片的 I2C 地址。 - 参数:
address
:TCA9548A 芯片的 I2C 地址,默认值为0x70
。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70); // 创建一个 TCA9548A 实例,地址为 0x70
2. begin(TwoWire &inWire = Wire)
- 功能:对 I2C 总线进行初始化,并且把其关联到
TCA9548A
实例。 - 参数:
inWire
:TwoWire
类型的引用,代表 I2C 总线,默认值是Wire
。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70);
void setup() {
tca.begin(Wire); // 初始化 I2C 总线
}
void loop() {
// 主循环代码
}
3. openChannel(uint8_t channel)
- 功能:开启指定的通道。
- 参数:
channel
:要开启的通道编号,范围是 0 到 7。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70);
void setup() {
tca.begin(Wire);
tca.openChannel(0); // 开启通道 0
}
void loop() {
// 主循环代码
}
4. closeChannel(uint8_t channel)
- 功能:关闭指定的通道。
- 参数:
channel
:要关闭的通道编号,范围是 0 到 7。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70);
void setup() {
tca.begin(Wire);
tca.openChannel(0); // 开启通道 0
delay(1000); // 等待 1 秒
tca.closeChannel(0); // 关闭通道 0
}
void loop() {
// 主循环代码
}
5. writeRegister(uint8_t value)
- 功能:向 TCA9548A 芯片的控制寄存器写入指定的值。
- 参数:
value
:要写入寄存器的值。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70);
void setup() {
tca.begin(Wire);
tca.writeRegister(TCA_CHANNEL_0 | TCA_CHANNEL_1); // 开启通道 0 和通道 1
}
void loop() {
// 主循环代码
}
6. readRegister()
- 功能:从 TCA9548A 芯片的控制寄存器读取当前的值。
- 返回值:控制寄存器的当前值。
- 示例:
#include "TCA9548A.h"
#include <Serial.h>
TCA9548A tca(0x70);
void setup() {
Serial.begin(9600);
tca.begin(Wire);
}
void loop() {
byte regValue = tca.readRegister(); // 读取寄存器的值
Serial.print("Register value: ");
Serial.println(regValue, HEX);
delay(1000);
}
7. closeAll()
- 功能:关闭所有通道。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70);
void setup() {
tca.begin(Wire);
tca.openChannel(0); // 开启通道 0
tca.openChannel(1); // 开启通道 1
delay(1000); // 等待 1 秒
tca.closeAll(); // 关闭所有通道
}
void loop() {
// 主循环代码
}
8. openAll()
- 功能:开启所有通道。
- 示例:
#include "TCA9548A.h"
TCA9548A tca(0x70);
void setup() {
tca.begin(Wire);
tca.openAll(); // 开启所有通道
}
void loop() {
// 主循环代码
}
私有成员函数
1. write(uint8_t inData)
- 功能:通过 I2C 总线向 TCA9548A 芯片写入数据。
- 参数:
inData
:要写入的数据。
2. read()
- 功能:通过 I2C 总线从 TCA9548A 芯片读取数据。
- 返回值:读取到的