/* * 模块名称:SD2506API-G RTC Driver * 模块功能:SD2506API-G 高精度温补实时时钟驱动,I2C 接口 * 适用平台:STM32F407ZGTx + SD2506API-G 实时时钟芯片(I2C1: PB6-SCL, PB7-SDA) * 作者:王建锋 * 创建日期:2026-07-17 * 修改记录: * 2026-07-17 王建锋 创建初始版本,参考 SD2506API-G Ver2.0 手册 */ #include #include #include "sd2506.h" #include "i2c.h" extern I2C_HandleTypeDef hi2c1; /* ======================== 内部辅助函数 ======================== */ /* * 函数功能:BCD 码转十进制数 * 入口参数:bcd - BCD 编码值 uint8_t 0x00 - 0x99 * 返回值:十进制数值 uint8_t * 限定条件:输入为合法 BCD 编码(高 4 位和低 4 位均 <= 9) * 函数说明:高 4 位为十位,低 4 位为个位,分别乘 10 和加后返回 */ uint8_t sd2506_bcd_to_dec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0FU); } /* * 函数功能:十进制�?BCD �? * 入口参数:dec - 十进制数�? uint8_t 0 - 99 * �?�?值:BCD 编码�? * 限定条件:输入值在 0 - 99 范围�? * 函数说明:十位存入高 4 位,个位存入�?4 �? */ uint8_t sd2506_dec_to_bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); } /* * 函数功能:写单字节寄存器 * 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79 * val - 写入数据 uint8_t 0x00 - 0xFF * 返回值:0-成功,2-I2C 错误 int * 限定条件:I2C 外设已初始化 * 函数说明:通过 I2C 向指定地址写入 1 字节数据 * 限定条件:I2C 外设已初始化 * 函数说明:通过 I2C 向指定地址写入 1 字节 */ static int sd2506_write_reg(uint8_t reg, uint8_t val) { if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, SD2506_I2C_TIMEOUT_MS) != HAL_OK) { return SD2506_I2C_ERROR; } return SD2506_OK; } /* * 函数功能:读单字节寄存器 * 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79 * p_val - 读取值指针 uint8_t*(非空) * 返回值:0-成功,2-I2C 错误 int * 限定条件:I2C 外设已初始化,p_val 为非空指针 * 函数说明:通过 I2C 从指定地址读取 1 字节数据 * 限定条件:I2C 外设已初始化,val 为非空指针函数说明:通过 I2C 从指定地址读取 1 字节 */ static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) { if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, I2C_MEMADD_SIZE_8BIT, p_val, 1, SD2506_I2C_TIMEOUT_MS) != HAL_OK) { return SD2506_I2C_ERROR; } return SD2506_OK; } /* * 函数功能:写多字节寄存器(连续写) * 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79 * p_data - 数据缓冲区指针 uint8_t*(非空) * len - 数据长度 uint8_t 1 - N * 返回值:0-成功,2-I2C 错误 int * 限定条件:I2C 外设已初始化,p_data 为非空指针 * 函数说明:从 reg 开始连续写入 len 字节数据 * 限定条件:I2C 外设已初始化,data 为非空指针 */ static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, I2C_MEMADD_SIZE_8BIT, (uint8_t *)p_data, len, SD2506_I2C_TIMEOUT_MS) != HAL_OK) { return SD2506_I2C_ERROR; } return SD2506_OK; } /* * 函数功能:读多字节寄存器(连续读) * 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79 * p_data - 数据缓冲区指针 uint8_t*(非空) * len - 数据长度 uint8_t 1 - N * 返回值:0-成功,2-I2C 错误 int * 限定条件:I2C 外设已初始化,p_data 为非空指针 * 函数说明:从 reg 开始连续读取 len 字节数据 * 限定条件:I2C 外设已初始化,data 为非空指针 */ static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, I2C_MEMADD_SIZE_8BIT, p_data, len, SD2506_I2C_TIMEOUT_MS) != HAL_OK) { return SD2506_I2C_ERROR; } return SD2506_OK; } /* * 函数功能:开启写保护(允许写入 00H~71H 寄存器) * 入口参数:无 * 返回值:0 - 成功 2 - I2C 错误 * 限定条件:I2C 外设已初始化 * 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1 */ static int sd2506_write_enable(void) { int ret = 0; /* 先置 WRTC1=1 (bit6=1), 其它位参考手册0x84 */ ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); if (ret != SD2506_OK) return ret; /* 再置 WRTC2=1(bit5) + WRTC3=1(bit4): 0x8C */ ret = sd2506_write_reg(SD2506_REG_CTR2, 0x8CU); if (ret != SD2506_OK) return ret; return SD2506_OK; } /* * 函数功能:关闭写保护(禁止写入 00H~71H 寄存器) * 入口参数:无 * 返回值:0 - 成功 2 - I2C 错误 * 限定条件:I2C 外设已初始化 * 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=0 */ static int sd2506_write_disable(void) { int ret = 0; /* 先清 WRTC2=0, WRTC3=0: 0x00 */ ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U); if (ret != SD2506_OK) return ret; /* 再清 WRTC1=0, 同时 ARST=1 使能自动复位: 0x20 */ ret = sd2506_write_reg(SD2506_REG_CTR1, SD2506_CTR1_WRITE_OFF); if (ret != SD2506_OK) return ret; return SD2506_OK; } /* ======================== 公共 API 实现 ======================== */ /* * 函数功能:初始化 SD2506API-G RTC * 入口参数:无 * 返回值:0-成功,2-I2C 错误 int * 限定条件:CubeMX 已完成 I2C1 初始化(PB6-SCL, PB7-SDA) * 函数说明:1. 读取芯片 ID 验证通信 * 2. 上电重置充电寄存器 (0x18=0x82) * 3. 配置 24 小时制、开自动复位 * 限定条件:CubeMX 已完成I2C1 初始化 * 函数说明:读取芯片 ID 验证通信 * 2. 上电重置充电寄存器18H=82H * 3. 配置 24 小时制、开自动复位 */ int sd2506_init(void) { int ret = 0; uint8_t a_id[8] = {0}; /* 验证 I2C 通信: 尝试读取 8 字节 ID */ ret = sd2506_read_regs(SD2506_REG_ID_START, a_id, SD2506_ID_SIZE); if (ret != SD2506_OK) { return ret; } /* 上电重置充电寄存�?18H = 82H (开启充�? 5K电阻) * 手册强烈建议每次上电时重置此�?*/ ret = sd2506_write_reg(SD2506_REG_CHARGE, 0x82U); if (ret != SD2506_OK) { return ret; } /* 配置: 24小时�? 自动复位使能 * 0FH = 0x20 (bit5=ARST=1, 其它标志位清�? * 写入时需注意: 写允许状态下 0FH �?WRTC 位必须为1 * 此处直接写入 0x20 即可 (ARST=1, �?WRTC �? */ ret = sd2506_write_reg(SD2506_REG_CTR1, SD2506_CTR1_WRITE_OFF); if (ret != SD2506_OK) { return ret; } return SD2506_OK; } /* * 函数功能:设�?RTC 时间日期 * 入口参数:p_time - 时间结构体指针,包含要设置的时间 * 返回值:0 - 成功�?2 - I2C 错误 * 限定条件:sd2506_init() 已调�? * 函数说明�?. 先开写保�? * 2. 一次性写�?7 字节时间数据 (00H~06H) * 3. 关闭写保�? */ int sd2506_set_time(const sd2506_time_t *p_time) { int ret = 0; uint8_t buf[7] = {0}; if (p_time == NULL) { return SD2506_ERROR; } /* 组装 7 字节时间数据 (BCD �? */ buf[0] = sd2506_dec_to_bcd(p_time->second); /* 00H: �?*/ buf[1] = sd2506_dec_to_bcd(p_time->minute); /* 01H: �?*/ buf[2] = sd2506_dec_to_bcd(p_time->hour) | 0x80U; /* 02H: �?(bit7=1, 24小时�? */ buf[3] = sd2506_dec_to_bcd(p_time->week); /* 03H: 星期 */ buf[4] = sd2506_dec_to_bcd(p_time->day); /* 04H: �?*/ buf[5] = sd2506_dec_to_bcd(p_time->month); /* 05H: �?*/ buf[6] = sd2506_dec_to_bcd(p_time->year - 2000U); /* 06H: �?*/ /* 开启写保护 */ ret = sd2506_write_enable(); if (ret != SD2506_OK) return ret; /* 一次性写�?7 字节时间数据 (00H~06H) * 手册要求: 不可单独写某一个时间寄存器,否则可能引起错误进�?*/ ret = sd2506_write_regs(SD2506_REG_SEC, buf, 7); if (ret != SD2506_OK) return ret; /* 关闭写保�?*/ ret = sd2506_write_disable(); if (ret != SD2506_OK) return ret; return SD2506_OK; } /* * 函数功能:读取 RTC 时间日期 * 入口参数:p_time - 输出时间结构体指针 sd2506_time_t*(非空) * 返回值:0-成功,2-I2C 错误 int * 限定条件:sd2506_init() 已调用成功 * 函数说明:一次读取 7 字节时间数据 (00H~06H),所有实时数据被锁存避免错误 * note: sd2506_init() must be called first */ int sd2506_get_time(sd2506_time_t *p_time) { int ret = 0; uint8_t buf[7] = {0}; if (p_time == NULL) { return SD2506_ERROR; } /* 一次读�?7 字节时间数据 (00H~06H) * 手册说明: 读取时所有实时数据被锁存,避免错�?*/ ret = sd2506_read_regs(SD2506_REG_SEC, buf, 7); if (ret != SD2506_OK) { return ret; } p_time->second = sd2506_bcd_to_dec(buf[0] & 0x7FU); /* 00H: �?*/ p_time->minute = sd2506_bcd_to_dec(buf[1] & 0x7FU); /* 01H: �?*/ p_time->hour = sd2506_bcd_to_dec(buf[2] & 0x7FU); /* 02H: 屏蔽 bit7 (12/24标志) */ p_time->week = sd2506_bcd_to_dec(buf[3] & 0x07U); /* 03H: 星期 */ p_time->day = sd2506_bcd_to_dec(buf[4] & 0x3FU); /* 04H: �?*/ p_time->month = sd2506_bcd_to_dec(buf[5] & 0x1FU); /* 05H: �?*/ p_time->year = sd2506_bcd_to_dec(buf[6]) + 2000U; /* 06H: �?*/ return SD2506_OK; } /* * 函数功能:读取内部温度 * 入口参数:p_temp - 输出温度值指针 int8_t*(非空) * 返回值:0-成功,2-I2C 错误 int * 限定条件:sd2506_init() 已调用成功 * 函数说明:读取寄存器 0x16,bit7 为符号位 * return: 0=OK, -2=I2C error * note: reads register 0x16 */ int sd2506_get_temperature(int8_t *p_temp) { int ret = 0; uint8_t val = 0; if (p_temp == NULL) { return SD2506_ERROR; } ret = sd2506_read_reg(SD2506_REG_TEMP, &val); if (ret != SD2506_OK) { return ret; } /* bit7 为符号位, 其余为温度�?*/ if (val & 0x80U) { /* 负温�? 取补�?*/ *p_temp = (int8_t)(val | 0xF0U); } else { /* 正温�?*/ *p_temp = (int8_t)(val & 0x7FU); } return SD2506_OK; } /* * 函数功能:读取电池电压(毫伏) * 入口参数:p_voltage - 输出电压指针 uint16_t*(非空) * 返回值:0-成功,2-I2C 错误 int * 限定条件:sd2506_init() 已调用成功 * 函数说明:组合 9 位值 (bit8=BAT8_VAL, bit7~0=BAT_VL),转换为毫伏 * return: 0=OK, -2=I2C error * note: combines 9-bit value */ int sd2506_get_battery_voltage(uint16_t *p_voltage) { int ret = 0; uint8_t val_high = 0; uint8_t val_low = 0; if (p_voltage == NULL) { return SD2506_ERROR; } /* 读取 1AH bit7 (BAT8_VAL) �?1BH (BAT_VL) */ ret = sd2506_read_reg(SD2506_REG_CTR5, &val_high); if (ret != SD2506_OK) return ret; ret = sd2506_read_reg(SD2506_REG_BAT_VAL, &val_low); if (ret != SD2506_OK) return ret; /* 组合 9 �? bit8=BAT8_VAL(1AH bit7), bit7~0=BAT_VL(1BH) * �?1AH=80H, 1BH=30H => 电压 = 0x130 = 304 => 3.04V => 3040mV */ uint16_t raw = ((val_high & 0x80U) << 1) | val_low; *p_voltage = raw * 10; /* 转换为毫�?(raw 单位 0.01V) */ return SD2506_OK; } /* * 函数功能:读取 8 字节芯片 ID * 入口参数:p_id - 输出缓冲区指针 uint8_t[8](非空) * 返回值:0-成功,2-I2C 错误 int * 限定条件:sd2506_init() 已调用成功 * 函数说明:读取寄存器 0x72~0x79 * return: 0=OK, -2=I2C error * note: reads registers 0x72-0x79 */ int sd2506_get_id(uint8_t p_id[8]) { if (p_id == NULL) { return SD2506_ERROR; } return sd2506_read_regs(SD2506_REG_ID_START, p_id, SD2506_ID_SIZE); } /* * 函数功能:读取 SRAM 数据 * 入口参数:addr - 起始地址 uint8_t 0-69 * p_buf - 输出缓冲区指针 uint8_t*(非空) * len - 读取长度 uint8_t 1-N * 返回值:0-成功,-1=参数错误,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70) * 函数说明:SRAM 范围 0x2C~0x71 * return: 0=OK, -1=param err, -2=I2C err * note: SRAM range 0x2C-0x71 */ int sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len) { if (p_buf == NULL || len == 0 || addr >= SD2506_SRAM_SIZE) { return SD2506_ERROR; } if (addr + len > SD2506_SRAM_SIZE) { return SD2506_ERROR; } return sd2506_read_regs(SD2506_REG_SRAM_START + addr, p_buf, len); } /* * 函数功能:写入 SRAM 数据 * 入口参数:addr - 起始地址 uint8_t 0-69 * p_buf - 输入数据指针 uint8_t*(非空) * len - 写入长度 uint8_t 1-N * 返回值:0-成功,-1=参数错误,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70) * 函数说明:SRAM 写入需走写保护流程(虽 SRAM 本身无需) * return: 0=OK, -1=param err, -2=I2C err */ int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { if (p_buf == NULL || len == 0 || addr >= SD2506_SRAM_SIZE) { return SD2506_ERROR; } if (addr + len > SD2506_SRAM_SIZE) { return SD2506_ERROR; } /* SRAM 无需开写保护即可写�?(写保护仅�?00H~71H 有效, SRAM �?2CH~71H) * 但为安全起见, SRAM 写入也走写保护流�?*/ int ret = 0; ret = sd2506_write_enable(); if (ret != SD2506_OK) return ret; ret = sd2506_write_regs(SD2506_REG_SRAM_START + addr, p_buf, len); if (ret != SD2506_OK) return ret; ret = sd2506_write_disable(); return ret; } /* * 函数功能:设置闹钟中断 * 入口参数:p_time - 报警时间结构体指针 sd2506_time_t*(非空) * mask - 报警匹配掩码 uint8_t * 返回值:0-成功,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功 * 函数说明:写入 8 字节报警数据 (07H~0EH),使能 CTR2.INTAE+INTS0+IM * return: 0=OK, -2=I2C error */ int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { int ret = 0; uint8_t buf[8] = {0}; if (p_time == NULL) { return SD2506_ERROR; } /* 组装 8 字节报警数据 (07H~0EH) */ buf[0] = sd2506_dec_to_bcd(p_time->second) & 0x7FU; /* 07H: 秒报�?*/ buf[1] = sd2506_dec_to_bcd(p_time->minute) & 0x7FU; /* 08H: 分报�?*/ buf[2] = sd2506_dec_to_bcd(p_time->hour) & 0x3FU; /* 09H: 时报�?(最高位始终�?) */ buf[3] = 0x00U; /* 0AH: 星期报警 */ buf[4] = sd2506_dec_to_bcd(p_time->day) & 0x3FU; /* 0BH: 日报�?*/ buf[5] = sd2506_dec_to_bcd(p_time->month) & 0x1FU; /* 0CH: 月报�?*/ buf[6] = sd2506_dec_to_bcd(p_time->year - 2000U); /* 0DH: 年报�?*/ buf[7] = mask; /* 0EH: 报警允许 */ /* 开启写保护 */ ret = sd2506_write_enable(); if (ret != SD2506_OK) return ret; /* 写入报警寄存�?(07H~0EH) */ ret = sd2506_write_regs(SD2506_REG_AL_SEC, buf, 8); if (ret != SD2506_OK) return ret; /* 使能报警中断: CTR2 �?INTAE=1, INTS1=0, INTS0=1, IM=1 (周期�? */ ret = sd2506_write_reg(SD2506_REG_CTR2, SD2506_CTR2_INTAE | SD2506_CTR2_INTS0 | SD2506_CTR2_IM); if (ret != SD2506_OK) return ret; /* 关闭写保�?*/ ret = sd2506_write_disable(); if (ret != SD2506_OK) return ret; return SD2506_OK; } /* * 函数功能:清除闹钟中断标志 * 入口参数:无 * 返回值:0-成功,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功;ARST=1 时自动清除 INTAF * 函数说明:读取 CTR1 即可清除 INTAF 标志 * return: 0=OK, -2=I2C error * note: reading CTR1 clears INTAF */ int sd2506_clear_alarm(void) { int ret = 0; uint8_t val = 0; /* 读取 CTR1, ARST=1 时自动清�?INTAF */ ret = sd2506_read_reg(SD2506_REG_CTR1, &val); if (ret != SD2506_OK) { return ret; } return SD2506_OK; } /* * 函数功能:读取控制寄存器 1 (CTR1) * 入口参数:p_val - 输出值指针 uint8_t*(非空) * 返回值:0-成功,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功 * return: 0=OK, -2=I2C error */ int sd2506_read_ctr1(uint8_t *p_val) { if (p_val == NULL) { return SD2506_ERROR; } return sd2506_read_reg(SD2506_REG_CTR1, p_val); }