当前位置: 首页 > backend >正文

SD3302 驱动:轻量级模块化,一键集成,高效易用。

前言

SD3302 实时时钟(RTC)芯片以其高精度、硬件写保护、闹钟输出和温度补偿功能,成为可靠的计时解决方案,它通过标准 I²C(7 位地址) 总线与 MCU 通信,内部提供 122 字节寄存器(包括时间、闹钟、控制、电池状态、70 字节用户 SRAM 及 8 字节唯一 ID),并内建 谐振电容 + 数字温度补偿,无需外置晶振调校即可实现宽温自动校时;内置 定时/报警中断(最长可设 100 年),配合后备电池智能充电与欠压检测;更支持 SD2068/SD3068 软件兼容、管脚兼容 1302,轻松替换旧款。

然而,网上关于 SD3302/ SD2068/SD3068 的驱动资料极为零散,几乎找不到一份完整、结构清晰的示例。本文将一次性呈现完整的 SD3302 驱动及底层 I²C 驱动代码,并配以详尽注释,帮助读者无需跨文件即可快速上手。

芯片简介

为什么选择 SD3302?

  • 高精度温度补偿
    内置数字温度补偿与谐振电容,无需外部校准,走时更加稳定可靠。
  • 硬件写保护
    防止意外写入覆盖时钟寄存器,确保数据安全。
  • 硬件闹钟中断
    通过 INT 引脚触发中断,无需轮询即可唤醒 MCU,极大节省功耗。
  • 122 字节寄存器空间
    集成时间、闹钟、控制、电池状态、70 B 用户 SRAM 及唯一 8 B ID,一线全掌控。
  • 标准 I²C 接口
    7 位地址,兼容 STM32、Arduino、ESP32 等主流 MCU,即插即用。
  • 超长定时能力
    硬件闹钟可配置至 100 年,满足各类超长定时或定期唤醒需求。
  • 后备电池智能管理
    内置充电与欠压检测电路,断电续航、实时监测电量状态。
  • 向下兼容
    软件兼容 SD2068/SD3068,管脚与 1302 完全一致,升级零成本。

硬件连接与软件依赖

管脚定义

image-20250425110844376

硬件连接

目前我使用的MCU是普冉的PY32F030X8系列芯片。

SD3302 引脚功能MCU 接脚说明
VCC电源3.3V需加 0.1µF 陶瓷电容
GNDGND
SDAI²C 数据PA9(I2C1_SDA)配置上拉
SCLI²C 时钟PA8 (I2C1_SCL)配置上拉
INT中断输出PC2(EXTI2)下降沿触发
  • 别忘了给 SDA/SCL 上拉电阻,至少 4.7K,使 I²C 总线稳定。

寄存器映射示意图

下图为 SD3302 的寄存器地址分布图,便于查阅和开发过程中对功能字段的快速定位:

image-20250425110612767

image-20250425110643532

驱动框架设计

本驱动架构分为两大模块,便于分层开发与后期移植:

  1. SD3302 功能驱动:负责芯片寄存器读写与功能逻辑(包含 .h 声明与 .c 实现)。
  2. I²C 底层驱动:封装 HAL 的收发操作,并配置 MSP 初始化(包含 .h.c 和外设初始化代码)。

两者通过统一接口函数 APP_MasterTransmitAPP_MasterReceive 进行通信,既提高了可读性,也便于在不同平台或 I²C 控制器间快速移植与复用。

👉 总之就是“上层专心控制功能,下层专心传话”,互不打扰,合作愉快。😄

SD3302 驱动源码及说明事项

SD3302程序源码

SD3302.h
/*** @file SD3302.h* @author cyWu (1917507415@qq.com)* @brief SD3302 实时时钟驱动框架头文件* @version 0.1* @date 2025-04-24**/
#ifndef __SD3302_H
#define __SD3302_H#include "main.h"/* I2C 设备地址 (7位) */
#define SD3302_I2C_ADDR 0x32/*------------------------ 寄存器地址定义 ------------------------*/
#define SEC_REG      0x00 // 秒寄存器
#define MIN_REG      0x01 // 分寄存器
#define HOUR_REG     0x02 // 时寄存器
#define WEEK_REG     0x03 // 星期寄存器
#define DAY_REG      0x04 // 日寄存器
#define MONTH_REG    0x05 // 月寄存器
#define YEAR_REG     0x06 // 年寄存器#define ALARM_SEC    0x07 // 报警秒寄存器
#define ALARM_MIN    0x08 // 报警分寄存器
#define ALARM_HOUR   0x09 // 报警时寄存器
#define ALARM_WEEK   0x0A // 报警星期寄存器
#define ALARM_DAY    0x0B // 报警日寄存器
#define ALARM_MONTH  0x0C // 报警月寄存器
#define ALARM_YEAR   0x0D // 报警年寄存器
#define ALARM_ENABLE 0x0E // 报警使能寄存器#define CTRL1_REG    0x0F // 控制寄存器 1
#define CTRL2_REG    0x10 // 控制寄存器 2
#define TEMP_REG     0x12 // 温度传感器寄存器
#define TD0_REG      0x13 // 定时器寄存器#define ID_BASE_REG 0x72 // 设备 ID 起始地址/*------------------------ 控制位定义 ------------------------*/
#define WRTC1        (1 << 7) // 写保护位 1
#define WRTC2        (1 << 2) // 写保护位 2
#define WRTC3        (1 << 7) // 写保护位 3
#define INTAE        (1 << 1) // 报警中断允许位
#define INTFE        (1 << 0) // 报警中断允许位
#define INTDE        (1 << 2) // 报警中断允许位
#define INTAF        (1 << 5) // 报警中断标志位
#define INTS1        (1 << 5) // INT中断输出设置1
#define INTS0        (1 << 4) // INT中断输出设置0
#define IM           (1 << 6) // 报警中断模式位
#define TEMP_COMP_EN (1 << 7) // 温度补偿使能/*------------------------ 结构体定义 ------------------------*/
typedef struct
{uint8_t seconds;uint8_t minutes;uint8_t hours;uint8_t week;uint8_t day;uint8_t month;uint8_t year;
} RTC_TimeTypeDef;typedef struct
{uint8_t sec;uint8_t min;uint8_t hour;uint8_t week;uint8_t day;uint8_t month;uint8_t year;uint8_t enable; // 报警使能位
} RTC_AlarmTypeDef;/* 函数声明 */
void SD3302_Init(void);
void RTC_GetTime(RTC_TimeTypeDef *time);
void RTC_SetTime(RTC_TimeTypeDef *time);
void RTC_SetAlarm(RTC_AlarmTypeDef *alarm);
void RTC_GetAlarm(RTC_AlarmTypeDef *alarm);
void Read_ChipID(uint8_t *id_buffer);
void Test_ReadChipID(void);
void Test_RTC_SetTime(void);
void Test_RTC_Alarm(void);
uint32_t rtcDateTimeToSeconds(RTC_TimeTypeDef *rtc);
int rtcDiffS(RTC_TimeTypeDef *start, RTC_TimeTypeDef *now);
int rtcDiffS_uint(uint32_t *startSec, uint32_t *nowSec);
uint32_t get_current_rtc_seconds(void);
#endif /* __SD3302_H */
SD3302.c
/*** @file SD3302.c* @author cyWu (1917507415@qq.com)* @brief SD3302 实时时钟驱动框架源文件* @version 0.1* @date 2025-04-24**/#include "sd3302.h"
#include "I2c.h"extern void APP_MasterTransmit(uint16_t DevAddress, uint8_t *pData, uint16_t Size);
extern void APP_MasterReceive(uint16_t DevAddress, uint8_t *pData, uint16_t Size);/*------------------------ 私有函数 ------------------------*/
/* 解锁写保护 */
static void WriteUnlock(void)
{uint8_t reg = SEC_REG;uint8_t buffer[17];APP_MasterTransmit(SD3302_I2C_ADDR, &reg, 1);APP_MasterReceive(SD3302_I2C_ADDR, buffer, 17);uint8_t data[2] = {CTRL2_REG, WRTC1 | buffer[16]};APP_MasterTransmit(SD3302_I2C_ADDR, data, 2);data[0] = CTRL1_REG;data[1] = WRTC2 | WRTC3 | buffer[15];APP_MasterTransmit(SD3302_I2C_ADDR, data, 2);
}/* 锁定写保护 */
static void WriteLock(void)
{uint8_t reg = SEC_REG;uint8_t buffer[17];APP_MasterTransmit(SD3302_I2C_ADDR, &reg, 1);APP_MasterReceive(SD3302_I2C_ADDR, buffer, 17);uint8_t data[2] = {CTRL1_REG, (~(WRTC2 | WRTC3)) & buffer[15]};APP_MasterTransmit(SD3302_I2C_ADDR, data, 2);data[0] = CTRL2_REG;data[1] = (~WRTC1) | buffer[16];APP_MasterTransmit(SD3302_I2C_ADDR, data, 2);
}/* BCD 转换为十进制 */
static uint8_t BCD_To_Dec(uint8_t bcd)
{return ((bcd >> 4) * 10) + (bcd & 0x0F);
}/* 十进制转换为 BCD */
static uint8_t Dec_To_BCD(uint8_t dec)
{return ((dec / 10) << 4) | (dec % 10);
}/*------------------------ 设备初始化 ------------------------*/
void SD3302_Init(void)
{SD3302_INT_GPIO_CLK_ENABLE();GPIO_InitTypeDef GPIO_InitStruct = {0};// INT 下降沿中断GPIO_InitStruct.Pin = GPIO_PIN_2;GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);HAL_NVIC_EnableIRQ(EXTI2_3_IRQn); /* Enable EXTI interrupt */HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0, 0);
}/*------------------------ 时间操作 ------------------------*/
void RTC_SetTime(RTC_TimeTypeDef *time)
{uint8_t data[8];data[0] = SEC_REG;data[1] = Dec_To_BCD(time->seconds);data[2] = Dec_To_BCD(time->minutes);data[3] = Dec_To_BCD(time->hours) | 0x80; // 强制 24 小时模式data[4] = Dec_To_BCD(time->week);data[5] = Dec_To_BCD(time->day);data[6] = Dec_To_BCD(time->month);data[7] = Dec_To_BCD(time->year);WriteUnlock();APP_MasterTransmit(SD3302_I2C_ADDR, data, 8);WriteLock();
}void RTC_GetTime(RTC_TimeTypeDef *time)
{uint8_t reg = SEC_REG;uint8_t buffer[7];APP_MasterTransmit(SD3302_I2C_ADDR, &reg, 1);APP_MasterReceive(SD3302_I2C_ADDR, buffer, 7);time->seconds = BCD_To_Dec(buffer[0]);time->minutes = BCD_To_Dec(buffer[1]);time->hours = BCD_To_Dec(buffer[2] & 0x3F);time->week = BCD_To_Dec(buffer[3] & 0x07);time->day = BCD_To_Dec(buffer[4]);time->month = BCD_To_Dec(buffer[5]);time->year = BCD_To_Dec(buffer[6]);
}/*------------------------ 报警配置 ------------------------*/
/*** @brief  设置 RTC 报警时间* @param  alarm: 需要设置的报警时间结构体指针* @retval None** @note   SD3302 的报警寄存器地址从 0x07 开始,依次存放 秒、分、时、星期、日、月、年、报警允许位。*         该函数先读取寄存器内容,然后修改相关报警寄存器值,再写回完整数据。*/
void RTC_SetAlarm(RTC_AlarmTypeDef *alarm)
{uint8_t reg = SEC_REG; // 读取的起始寄存器地址(从 0x00 读取完整的时间和报警数据)uint8_t buffer[18];    // 用于存储读取的寄存器数据,避免影响其他寄存器内容WriteUnlock(); // 解除写保护,允许修改 RTC 配置// 读取 RTC 当前时间和报警寄存器内容,避免修改不相关数据APP_MasterTransmit(SD3302_I2C_ADDR, &reg, 1);       // 发送要读取的寄存器地址APP_MasterReceive(SD3302_I2C_ADDR, &buffer[1], 17); // 读取 17 字节数据(完整 RTC 和报警数据)uint8_t data[11];data[0] = ALARM_SEC;                                                         // 报警秒寄存器data[1] = Dec_To_BCD(alarm->sec);                                            // 设置秒报警值data[2] = Dec_To_BCD(alarm->min);                                            // 设置分钟报警值data[3] = Dec_To_BCD(alarm->hour);                                           // 设置小时报警值data[4] = Dec_To_BCD(alarm->week);                                           // 设置星期报警值data[5] = Dec_To_BCD(alarm->day);                                            // 设置日期报警值data[6] = Dec_To_BCD(alarm->month);                                          // 设置月份报警值data[7] = Dec_To_BCD(alarm->year);                                           // 设置年份报警值data[8] = alarm->enable;                                                     // 报警允许寄存器(控制哪些字段启用报警)data[10] = ((buffer[17] | INTAE | INTS0 | IM) & (~(INTFE | INTDE | INTS1))); // 设置控制寄存器(启用报警中断,确保不会影响其它控制位)// 将修改后的数据写回 RTCAPP_MasterTransmit(SD3302_I2C_ADDR, data, 11);WriteLock(); // 重新启用写保护,防止误操作
}/*** @brief  读取 RTC 报警时间* @param  alarm: 存储读取到的报警时间信息* @retval None** @note   读取 SD3302 的报警寄存器数据(从 0x07 开始),并转换为十进制格式存入结构体*/
void RTC_GetAlarm(RTC_AlarmTypeDef *alarm)
{uint8_t reg = SEC_REG; // 读取的起始寄存器地址uint8_t buffer[17];    // 存储读取的数据// 读取报警时间寄存器(从 0x07 开始,共 8 字节报警相关数据)APP_MasterTransmit(SD3302_I2C_ADDR, &reg, 1);   // 发送要读取的寄存器地址APP_MasterReceive(SD3302_I2C_ADDR, buffer, 17); // 读取 17 字节数据// 解析报警时间数据alarm->sec = BCD_To_Dec(buffer[7]);    // 读取秒报警alarm->min = BCD_To_Dec(buffer[8]);    // 读取分钟报警alarm->hour = BCD_To_Dec(buffer[9]);   // 读取小时报警alarm->week = BCD_To_Dec(buffer[10]);  // 读取星期报警alarm->day = BCD_To_Dec(buffer[11]);   // 读取日期报警alarm->month = BCD_To_Dec(buffer[12]); // 读取月份报警alarm->year = BCD_To_Dec(buffer[13]);  // 读取年份报警alarm->enable = buffer[14];            // 读取报警允许寄存器(直接存储,不进行 BCD 转换)
}/*------------------------ 读取芯片 ID ------------------------*/
void Read_ChipID(uint8_t *id_buffer)
{uint8_t reg = ID_BASE_REG;APP_MasterTransmit(SD3302_I2C_ADDR, &reg, 1);APP_MasterReceive(SD3302_I2C_ADDR, id_buffer, 8);
}/*------------------------ 测试函数 ------------------------*/
// 读取芯片 ID
void Test_ReadChipID(void)
{uint8_t chipID[8] = {0}; // 存储读取的 IDprintf("Reading Chip ID...\n");Read_ChipID(chipID); // 读取芯片 ID// 检查是否成功读取if (chipID[0] == 0x00 || chipID[0] == 0xFF){printf("Failed to read Chip ID! Check I2C connection.\n");}else{printf("Chip ID: ");for (int i = 0; i < 8; i++){printf("%02X ", chipID[i]); // 以十六进制打印}printf("\n");}
}// 设置时间
void Test_RTC_SetTime(void)
{RTC_TimeTypeDef setTime;RTC_TimeTypeDef getTime;// 设置要写入的时间setTime.year = 24;    // 2024年setTime.month = 3;    // 3月setTime.day = 10;     // 10日setTime.week = 7;     // 星期天setTime.hours = 16;   // 14时setTime.minutes = 25; // 25分setTime.seconds = 50; // 36秒printf("Setting RTC Time...\n");// 写入 RTC 时间RTC_SetTime(&setTime);// 读取 RTC 时间进行验证RTC_GetTime(&getTime);// 检查设置的时间是否成功写入if (setTime.year == getTime.year &&setTime.month == getTime.month &&setTime.day == getTime.day &&setTime.week == getTime.week &&setTime.hours == getTime.hours &&setTime.minutes == getTime.minutes &&setTime.seconds == getTime.seconds){printf("RTC Time Set Successfully!\n");printf("RTC Time: %04d-%02d-%02d %02d:%02d:%02d (Weekday: %d)\n",getTime.year + 2000, getTime.month, getTime.day,getTime.hours, getTime.minutes, getTime.seconds,getTime.week);}else{printf("Failed to Set RTC Time! Check I2C connection.\n");printf("RTC Time: %04d-%02d-%02d %02d:%02d:%02d (Weekday: %d)\n",getTime.year + 2000, getTime.month, getTime.day,getTime.hours, getTime.minutes, getTime.seconds,getTime.week);}
}// 设置报警
void Test_RTC_Alarm(void)
{RTC_AlarmTypeDef alarm_set;RTC_AlarmTypeDef alarm_read;// 示例:设置报警时间为 08:30:00(只比较时和分,故其它字段置 0)alarm_set.sec = 0;   // 秒alarm_set.min = 30;  // 分alarm_set.hour = 8;  // 时alarm_set.week = 0;  // 星期不比较则置0alarm_set.day = 0;   // 日不比较则置0alarm_set.month = 0; // 月不比较则置0alarm_set.year = 0;  // 年不比较则置0// 报警使能:使能“分钟”(对应位 1)和“小时”(对应位 2),0x02 | 0x04 = 0x06alarm_set.alarm_enable = 0x06;// 设置报警RTC_SetAlarm(&alarm_set);// 读取报警配置验证是否正确RTC_GetAlarm(&alarm_read);if (alarm_set.sec == alarm_read.sec &&alarm_set.min == alarm_read.min &&alarm_set.hour == alarm_read.hour &&alarm_set.week == alarm_read.week &&alarm_set.day == alarm_read.day &&alarm_set.month == alarm_read.month &&alarm_set.year == alarm_read.year &&alarm_set.enable == alarm_read.enable){printf("Alarm Set Successfully!\n");}else{printf("Failed to Set Alarm! Check I2C connection.\n");}// printf("Alarm read back:\n");// printf("  Time: %02d:%02d:%02d\n", alarm_read.hour, alarm_read.min, alarm_read.sec);// printf("  Weekday: %d\n", alarm_read.week);// printf("  Day: %d, Month: %d, Year: %d\n", alarm_read.day, alarm_read.month, alarm_read.year);// printf("  Enable: 0x%02X\n", alarm_read.enable);
}

函数说明

  • WriteUnlock() / WriteLock()
    采用“读取 → 保留原值 → 修改目标位 → 写回”的标准流程,仅操作写保护相关位,确保其它控制位不被误改,保障写入操作安全可靠。
  • BCD_To_Dec() / Dec_To_BCD()
    实现 BCD(Binary-Coded Decimal)与十进制之间的双向转换,用于 RTC 时间寄存器的数据格式转换,使人类可读的时间与芯片格式无缝对接。
  • SD3302_Init()
    初始化 SD3302 的 INT 中断引脚,将其配置为下降沿触发的外部中断,并开启对应 NVIC 通道,使芯片具备报警中断输出能力。
  • RTC_SetTime() / RTC_GetTime()
    通过一次性写入或读取时间相关寄存器,实现对当前“时、分、秒、星期、日期、月份、年份”等字段的高效配置和获取,自动处理 24 小时格式位。
  • RTC_SetAlarm() / RTC_GetAlarm()
    采用安全的“读-改-写”流程,对闹钟时间字段和报警使能位进行配置与读取,保证仅修改需要的字段,不影响其他寄存器内容,防止潜在误操作。
  • Read_ChipID()
    读取芯片内置的 8 字节唯一 ID,可用于通信认证、版本标识或产品追踪,是识别 SD3302 芯片身份的重要手段。
  • Test_\*() 系列函数
    封装基础测试流程,例如设置/读取时间、闹钟或读取 ChipID,结合 printf() 输出,适用于快速验证驱动是否初始化成功、通信是否正常,方便开发调试阶段使用。

注意事项

细心的同学可能注意到,在获取闹钟寄存器的值时,程序中并不是直接从闹钟秒寄存器(ALARM_SEC)发起读取,而是先从秒寄存器(SEC_REG)一并读出连续寄存器。原因在于,SD3302 在接收到读取命令后不会自动释放总线(也就是MCU不发送 STOP 位),因为我使用的是硬件 I²C ,使用的是HAL库的 I²C 驱动,而现有的 I²C 驱动在每次接收完毕后都会强制发出 STOP 条件,。如果从 ALARM_SEC 单独发起读取,STOP 信号就会中断当前事务,导致只能得到秒寄存器的数据。

查看数据手册中可以发现,从制定的内部地址中读取数据是没有STOP位的

image-20250425111954322

而以下是我的程序发送数据时抓取到的数据波形,硬件 I²C 在发送设置内部地址的时候总是会自带一个STOP位,导致读取到的数据时从寄存器00开始读的,也就是获取到的数据是秒寄存器的值

image-20250425112635123

为了解决这一问题,我们采用另外一种获取方式,连续读取整个寄存器区块:从 SEC_REG 开始,一次性获取秒、分、时以及闹钟字段中的所有数据。

image-20250425113222099

I²C 驱动代码

I²C.h
#ifndef __I2C_H
#define __I2C_H#include "main.h"#define I2C_ADDRESS      0xA0               //本机I2C地址
#define I2C_SPEEDCLOCK   100000             //传输速率
#define I2C_DUTYCYCLE    I2C_DUTYCYCLE_16_9 //占空比void i2c_init(void);
void APP_MasterTransmit(uint16_t DevAddress, uint8_t *pData, uint16_t Size);
void APP_MasterReceive(uint16_t DevAddress, uint8_t *pData, uint16_t Size);
#endif
I²C.c
#include "I2c.h"I2C_HandleTypeDef I2cHandle;void i2c_init(void)
{/* I2C initialization */I2cHandle.Instance = I2C;                                 /* I2C */I2cHandle.Init.ClockSpeed = I2C_SPEEDCLOCK;               /* I2C communication speed */I2cHandle.Init.DutyCycle = I2C_DUTYCYCLE;                 /* I2C Duty cycle */I2cHandle.Init.OwnAddress1 = I2C_ADDRESS;                 /* I2C address */I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; /* Disable general call */I2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;     /* Enable clock stretching */if (HAL_I2C_Init(&I2cHandle) != HAL_OK){APP_ErrorHandler();}HAL_Delay(10);
}/*** @brief  I2C Send data* @param  DevAddress:Slave address* @param  pData:pData pointer to Send buffer* @param  Size:Size Amount of data to be sent* @retval None*/
void APP_MasterTransmit(uint16_t DevAddress, uint8_t *pData, uint16_t Size)
{if(HAL_I2C_Master_Transmit(&I2cHandle, (DevAddress << 1), (uint8_t *)pData, Size, 1000) != HAL_OK){Log("I2C Send data error");}
}/*** @brief  I2C receive data* @param  DevAddress:Slave address* @param  pData:pData pointer to receive buffer* @param  Size:Size Amount of data to be received* @retval None*/
void APP_MasterReceive(uint16_t DevAddress, uint8_t *pData, uint16_t Size)
{if(HAL_I2C_Master_Receive(&I2cHandle, (DevAddress << 1) | 0x01, (uint8_t *)pData, Size, 1000) != HAL_OK){Log("I2C receive data error");}
}

函数说明

  • i2c_init()
    完成 I²C 总线的初始化,包括:
    1. 配置实例:I2cHandle.Instance = I2C1
    2. 设置通信参数:ClockSpeed(速率)、DutyCycle(占空比)、OwnAddress1(本机地址)、AddressingMode(7 位)、DualAddressModeGeneralCallModeNoStretchMode
    3. 调用 HAL_I2C_Init(&I2cHandle) 启动外设,若返回错误,则进入 APP_ErrorHandler()
    4. 初始化完成后加一个短延时(HAL_Delay(10))以确保总线稳定。
  • APP_MasterTransmit(uint16_t DevAddress, uint8_t \*pData, uint16_t Size)
    主机模式下向从机发送数据:
    1. 将 7 位从机地址左移 1 位(DevAddress << 1)构造 8 位寻址格式;
    2. 调用 HAL_I2C_Master_Transmit(&I2cHandle, addr, pData, Size, 1000) 发送 Size 字节数据,最后一个参数为超时时间(ms);
    3. 如果返回值不是 HAL_OK,则调用 Log("I2C Send data error") 打印错误信息,便于定位问题。
  • APP_MasterReceive(uint16_t DevAddress, uint8_t \*pData, uint16_t Size)
    主机模式下从从机接收数据:
    1. 将 7 位从机地址左移 1 位并在最低位置加 | 0x01 标记读操作;
    2. 调用 HAL_I2C_Master_Receive(&I2cHandle, addr, pData, Size, 1000) 接收 Size 字节数据;
    3. 若返回值非 HAL_OK,则调用 Log("I2C receive data error") 打印错误,帮助快速排查通信故障。

IO初始化与闹钟外部触发(可选)

如果不需要使用 SD3302 的 INT 引脚触发闹钟中断,可在**SD3302_Init()**屏蔽掉闹钟配置 GPIO,以下是SDA和SCL的初始化。

void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_I2C1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/* PA8=SCL, PA9=SDA */GPIO_InitStruct.Pin       = GPIO_PIN_8 | GPIO_PIN_9;GPIO_InitStruct.Mode      = GPIO_MODE_AF_OD;GPIO_InitStruct.Pull      = GPIO_PULLUP;GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Alternate = GPIO_AF12_I2C; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* 复位 I2C */__HAL_RCC_I2C1_FORCE_RESET();__HAL_RCC_I2C1_RELEASE_RESET();
}void EXTI2_3_IRQHandler(void)
{if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_2)) {__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_2);// 在此可调用用户回调或标记闹钟事件}
}

使用示例

main.c

#include "sd3302.h"
#include "i2c.h"int main(void)
{HAL_Init();SystemClock_Config();  // 时钟树配置i2c_init();            // 初始化 I²CSD3302_Init();         // 初始化 GPIO 中断(可选)// 1. 读取并打印芯片 IDTest_ReadChipID();// 2. 设置并验证当前时间Test_RTC_SetTime();// 3. 设置并验证闹钟Test_RTC_Alarm();while (1) {// 在主循环中可处理闹钟标志或其他逻辑}
}

常见问题

  1. I²C 地址需左移一位
    HAL 接口中使用 DevAddress << 1 构造 8 位寻址格式,否则无法正确找到从机。
  2. SDA/SCL 未上拉或时序抖动
    如果缺少上拉电阻、线路过长或信号干扰,读写易失败,建议加装 4.7 K–10 K 上拉,并尽量缩短布线。
  3. 未调用 WriteUnlock() 即写寄存器
    SD3302 对时间和报警寄存器默认上锁,必须先执行解锁流程,否则写入操作会被芯片忽略。
  4. 多字节读取被 STOP 中断
    SD3302 不会自动发送 STOP,因此在读闹钟等多寄存器时务必使用 Repeated Start(“无 STOP”)连续读,否则只会得到首字节数据。
  5. BCD 转换错误导致时间异常
    如果 Dec_To_BCD()BCD_To_Dec() 实现有误,读写的时间字段会整体偏移,检查高低四位运算逻辑是否正确。
  6. 24 小时模式位(0x80)缺失
    写入时未在小时寄存器设置 0x80 强制 24 小时模式,导致读取到的小时数据不准确。
  7. 中断优先级未配置或冲突
    如果 EXTI2_3_IRQn 优先级与其他中断冲突,闹钟 INT 输出可能被屏蔽,需在 NVIC 中合理分配优先级。
  8. INT 引脚悬空抖动
    将 INT 引脚配置为上拉或保持定义的空闲电平,避免悬空导致误触发或抖动。
http://www.xdnf.cn/news/2083.html

相关文章:

  • PTC加热片详解(STM32)
  • kvm物理接口发现的脚本COLT_CMDB_KVM_IFACE.sh
  • Qt指ModbusTcp协议的使用
  • 潇洒郎:ssh 连接Windows WSL2 Linux子系统 ipv6地址转发到ipv4地址上
  • BTSRB德国交通标志数据集.csv文件提取数据转换成.json文件
  • UVM 寄存器模型中的概念
  • 国标GB28181视频平台EasyGBS视频监控平台助力打造校园安防智能化
  • 剖析经典二维动画的制作流程,汲取经验
  • SpringBoot集成LiteFlow实现轻量级工作流引擎
  • Java多线程同步有哪些方法?
  • 基于STM32、HAL库的ADS1256IDBR模数转换器ADC驱动程序设计
  • Python实验4 列表与字典应用
  • Apipost免费版、企业版和私有化部署详解
  • LeetCode 解题思路 44(Hot 100)
  • 蛋白质大语言模型ESM介绍
  • ​Stable Diffusion:Diffusion Model
  • 深度学习实战106-大模型LLM+股票MCP Server的股票分析和投资建议应用场景
  • 软件研发管理方法工具总结
  • 15.ArkUI Checkbox的介绍和使用
  • 【智能硬件】【CES 2025】Bhaptics TactSuit X40和TactGlove,带你走进真实的虚拟世界
  • 数据库-少库多表与多库少表理论
  • NHANES指标推荐:PLP
  • 零基础快速搭建AI绘画网站!用Gradio玩转Stable Diffusion
  • ⭐Unity_Demolition Media Hap (播放Hap格式视频 超16K大分辨率视频 流畅播放以及帧同步解决方案)
  • C++23 新特性深度落地与最佳实践
  • 迁移学习(基础)
  • AOP与IOC的详细讲解
  • Linux上安装Mysql、Redis、Nginx
  • 常用SQL整理
  • kvm网卡发现的采集信息脚本COLT_CMDB_KVM_NETDISC.sh