Files
STM32F4-Base/Drivers/BSP/SD2506/sd2506.c
2026-07-21 00:30:59 +08:00

499 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称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 <string.h>
#include <stdio.h>
#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);
}
/*
* 函数功能十进制<E8BF9B>?BCD <20>? * 入口参数dec - 十进制数<E588B6>? uint8_t 0 - 99
* <20>?<3F>?值BCD 编码<E7BC96>? * 限定条件:输入值在 0 - 99 范围<E88C83>? * 函数说明:十位存入高 4 位个位存入<E5AD98>?4 <20>? */
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;
}
/* 上电重置充电寄存<E5AF84>?18H = 82H (开启充<E590AF>? 5K电阻)
* 手册强烈建议每次上电时重置此<E7BDAE>?*/
ret = sd2506_write_reg(SD2506_REG_CHARGE, 0x82U);
if (ret != SD2506_OK) {
return ret;
}
/* 配置: 24小时<E5B08F>? 自动复位使能
* 0FH = 0x20 (bit5=ARST=1, 其它标志位清<E4BD8D>?
* 写入时需注意: 写允许状态下 0FH <20>?WRTC 位必须为1
* 此处直接写入 0x20 即可 (ARST=1, <20>?WRTC <20>? */
ret = sd2506_write_reg(SD2506_REG_CTR1, SD2506_CTR1_WRITE_OFF);
if (ret != SD2506_OK) {
return ret;
}
return SD2506_OK;
}
/*
* 函数功能<EFBC9A>?RTC 时间日期
* 入口参数p_time - 时间结构体指针,包含要设置的时间
* 返回值0 - 成功<E68890>?2 - I2C 错误
* 限定条件sd2506_init() 已调<E5B7B2>?
* 函数说明<E8AFB4>?. 先开写保<E58699>?
* 2. 一次性写<E680A7>?7 字节时间数据 (00H~06H)
* 3. 关闭写保<E58699>?
*/
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 <20>? */
buf[0] = sd2506_dec_to_bcd(p_time->second); /* 00H: <20>?*/
buf[1] = sd2506_dec_to_bcd(p_time->minute); /* 01H: <20>?*/
buf[2] = sd2506_dec_to_bcd(p_time->hour) | 0x80U; /* 02H: <20>?(bit7=1, 24小时<E5B08F>? */
buf[3] = sd2506_dec_to_bcd(p_time->week); /* 03H: 星期 */
buf[4] = sd2506_dec_to_bcd(p_time->day); /* 04H: <20>?*/
buf[5] = sd2506_dec_to_bcd(p_time->month); /* 05H: <20>?*/
buf[6] = sd2506_dec_to_bcd(p_time->year - 2000U); /* 06H: <20>?*/
/* 开启写保护 */
ret = sd2506_write_enable();
if (ret != SD2506_OK) return ret;
/* 一次性写<E680A7>?7 字节时间数据 (00H~06H)
* 手册要求: 不可单独写某一个时间寄存器否则可能引起错误进<E8AFAF>?*/
ret = sd2506_write_regs(SD2506_REG_SEC, buf, 7);
if (ret != SD2506_OK) return ret;
/* 关闭写保<E58699>?*/
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;
}
/* 一次读<E6ACA1>?7 字节时间数据 (00H~06H)
* 手册说明: 读取时所有实时数据被锁存避免错<E5858D>?*/
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: <20>?*/
p_time->minute = sd2506_bcd_to_dec(buf[1] & 0x7FU); /* 01H: <20>?*/
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: <20>?*/
p_time->month = sd2506_bcd_to_dec(buf[5] & 0x1FU); /* 05H: <20>?*/
p_time->year = sd2506_bcd_to_dec(buf[6]) + 2000U; /* 06H: <20>?*/
return SD2506_OK;
}
/*
* 函数功能:读取内部温度
* 入口参数p_temp - 输出温度值指针 int8_t*(非空)
* 返回值0-成功2-I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* 函数说明:读取寄存器 0x16bit7 为符号位
* 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 为符号位, 其余为温度<E6B8A9>?*/
if (val & 0x80U) {
/* 负温<E8B49F>? 取补<E58F96>?*/
*p_temp = (int8_t)(val | 0xF0U);
} else {
/* 正温<E6ADA3>?*/
*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) <20>?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 <20>? bit8=BAT8_VAL(1AH bit7), bit7~0=BAT_VL(1BH)
* <20>?1AH=80H, 1BH=30H => 电压 = 0x130 = 304 => 3.04V => 3040mV */
uint16_t raw = ((val_high & 0x80U) << 1) | val_low;
*p_voltage = raw * 10; /* 转换为毫<E4B8BA>?(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 无需开写保护即可写<E58FAF>?(写保护仅<E68AA4>?00H~71H 有效, SRAM <20>?2CH~71H)
* 但为安全起见, SRAM 写入也走写保护流<E68AA4>?*/
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: 秒报<E7A792>?*/
buf[1] = sd2506_dec_to_bcd(p_time->minute) & 0x7FU; /* 08H: 分报<E58886>?*/
buf[2] = sd2506_dec_to_bcd(p_time->hour) & 0x3FU; /* 09H: 时报<E697B6>?(最高位始终<E5A78B>?) */
buf[3] = 0x00U; /* 0AH: 星期报警 */
buf[4] = sd2506_dec_to_bcd(p_time->day) & 0x3FU; /* 0BH: 日报<E697A5>?*/
buf[5] = sd2506_dec_to_bcd(p_time->month) & 0x1FU; /* 0CH: 月报<E69C88>?*/
buf[6] = sd2506_dec_to_bcd(p_time->year - 2000U); /* 0DH: 年报<E5B9B4>?*/
buf[7] = mask; /* 0EH: 报警允许 */
/* 开启写保护 */
ret = sd2506_write_enable();
if (ret != SD2506_OK) return ret;
/* 写入报警寄存<E5AF84>?(07H~0EH) */
ret = sd2506_write_regs(SD2506_REG_AL_SEC, buf, 8);
if (ret != SD2506_OK) return ret;
/* 使能报警中断: CTR2 <20>?INTAE=1, INTS1=0, INTS0=1, IM=1 (周期<E591A8>? */
ret = sd2506_write_reg(SD2506_REG_CTR2,
SD2506_CTR2_INTAE | SD2506_CTR2_INTS0 | SD2506_CTR2_IM);
if (ret != SD2506_OK) return ret;
/* 关闭写保<E58699>?*/
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 时自动清<E58AA8>?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);
}