STM32CubeMX + HAL 库:用硬件IIC接口实现DS3231 实时时钟读取与设置实验
本实验基于STM32微控制器的硬件I²C接口,实现对DS3231高精度实时时钟芯片的通信与功能验证。实验内容包括时间/日期读写、闹钟配置与中断处理,详细介绍了DS3231的寄存器结构、BCD编码格式及中断输出特性。通过CubeMX配置I²C外设,并提供了完整的驱动代码实现时间设置、温度读取等功能。实验结果表明,系统能准确完成时间数据交换和温度监测。文章还对比了不同RTC芯片的性能参数,指出DS32
1 概述
1.1 实验目的
本实验旨在通过 STM32 微控制器的硬件 I²C 接口,对 DS3231 高精度实时时钟(RTC)芯片进行通信与功能验证。实验演示了时间/日期的读写操作、闹钟功能配置与触发、中断信号的检测与响应等核心应用。通过完整的驱动代码与配置过程,帮助读者深入理解 STM32 硬件 I²C 通信机制以及 RTC 外设在嵌入式系统中的应用场景。
通过本实验,读者不仅能够掌握 I²C 总线的实际使用方法,还能熟悉 DS3231 的寄存器结构、时间数据存储格式(BCD 编码)、闹钟触发方式及中断输出特性。这对于日后在嵌入式系统中实现 精准时钟管理(如定时任务、掉电保持时间、事件提醒等) 提供了技术积累和实践基础,具有重要的工程应用价值和教学意义。
1.2 芯片介绍

| 型号/芯片 | 类型 | 精度(典型值) | 供电电压 | 接口方式 | 备用电源支持 | 温度补偿 | 使用广泛度 | 备注 |
|---|---|---|---|---|---|---|---|---|
| DS3231 | 外部 RTC | ±2ppm(约±1分钟/年) | 2.3–5.5V | I²C | 有(VBAT 引脚) | ✅ 内置晶振+温补 | ★★★★★ | 高精度,常用于高可靠场景 |
| DS3232 | 外部 RTC | ±2ppm | 2.3–5.5V | I²C/SPI | 有 | ✅ | ★★★★☆ | 带 SRAM,功能更多 |
| DS12C887 | 外部 RTC | ±20ppm(依赖晶振) | 5V | 并行总线 | 有 | ❌ | ★★☆☆☆ | 老式 PC 主板常用,体积大 |
| DS1307 | 外部 RTC | ±20ppm(依赖外部 32.768kHz 晶振) | 4.5–5.5V | I²C | 有 | ❌ | ★★★★★ | 经典款,低成本 |
| DS1302 | 外部 RTC | ±20ppm(依赖外部晶振) | 2.0–5.5V | 串行接口(SPI-like) | 有 | ❌ | ★★★★★ | 超低功耗,常见于学习和小项目 |
| STM32F103 内置 RTC | MCU 内置 RTC 外设 | 依赖外部 32.768kHz 晶振,典型 ±20ppm | 1.8–3.6V | 内部寄存器(软件访问) | 有(VBAT 引脚) | ❌ | ★★★★☆ | MCU 集成,节省硬件,但精度一般 |
1.3 DS3231引脚说明

| 引脚编号 | 引脚名称 | 功能描述 | 关键细节 |
|---|---|---|---|
| 1 | 32KHZ | 32.768kHz频率输出(50%占空比) | 漏极开路输出,需外部上拉电阻(如10kΩ);若不使用可悬空。 |
| 2 | VCC | 主电源直流输入 | 推荐使用3.3V电源,需0.1μF至1.0μF电容去耦;若不使用应接地。 |
| 3 | INT/SQW | 低电平有效中断或方波输出 | 漏极开路输出,需外部上拉电阻;功能由控制寄存器(0Eh)的INTCN位决定: - INTCN=0时输出1Hz方波; - INTCN=1时触发中断(需匹配闹钟寄存器)。 |
| 4 | RST | 低电平有效复位引脚 | 漏极开路输入/输出,用于电源故障报警或手动复位: - VCC低于阈值时拉低RST; - 复位后保持低电平约250ms(tREC)以确保电源稳定。 |
| 5-12 | N.C. | 无连接 | 必须接地,避免悬空。 |
| 13 | GND | 接地引脚 | 确保电气连接稳定。 |
| 14 | VBAT | 备用电源输入 | 用于主电源掉电时的时钟保持,需0.1μF至1.0μF低泄漏电容去耦;若不使用应接地。 |
| 15 | SDA | 串行数据输入/输出 | I2C总线数据引脚,需接上拉电阻(如10kΩ),支持双向通信。 |
| 16 | SCL | 串行时钟输入 | I2C总线时钟引脚,需接上拉电阻(如10kΩ),提供时钟信号。 |
1.4 日期时间读写
| 地址 | 功能 | 说明 | 读写 | 样例数据 (BCD) |
|---|---|---|---|---|
| 0x00 | 秒(Seconds) | 低7位为秒 (00–59),最高位 CH=0 启动振荡器 | R/W | 0x25 → 25秒 |
| 0x01 | 分(Minutes) | 分钟数 (00–59) | R/W | 0x45 → 45分 |
| 0x02 | 时(Hours) | 支持24小时/12小时制,bit6=0为24小时制 | R/W | 0x13 → 13点 |
| 0x03 | 星期(Day) | 1–7(用户自定义,通常1=周日) | R/W | 0x05 → 周五 |
| 0x04 | 日(Date) | 日期 (01–31) | R/W | 0x18 → 18号 |
| 0x05 | 月(Month) | 1–12,bit7表示世纪位(0=20xx,1=19xx/21xx) | R/W | 0x08 → 8月 |
| 0x06 | 年(Year) | 00–99(需结合世纪位) | R/W | 0x24 → 2024年 |
1.5 闹钟匹配设置
1.5.1 闹钟匹配地址
| 地址 | 功能 | 说明 | 读写 | 样例数据 (BCD) |
|---|---|---|---|---|
| 0x07 | 闹钟1 秒(Alarm1 Seconds) | 支持秒匹配,最高位 A1M1 控制匹配模式 | R/W | 0x30 → 秒=30 |
| 0x08 | 闹钟1 分(Alarm1 Minutes) | 同上,A1M2 控制匹配模式 | R/W | 0x15 → 分=15 |
| 0x09 | 闹钟1 时(Alarm1 Hours) | 支持12/24小时制,A1M3 控制匹配 | R/W | 0x08 → 8点 |
| 0x0A | 闹钟1 日/星期(Alarm1 Day/Date) | 由 DY/DT 位决定按“日”还是“星期”比较,A1M4 控制匹配 | R/W | 0x11 → 日期=11 |
| 0x0B | 闹钟2 分(Alarm2 Minutes) | 仅分、时、日匹配(无秒),A2M2 控制匹配模式 | R/W | 0x20 → 分=20 |
| 0x0C | 闹钟2 时(Alarm2 Hours) | 同上,A2M3 控制匹配 | R/W | 0x09 → 9点 |
| 0x0D | 闹钟2 日/星期(Alarm2 Day/Date) | DY/DT 位决定按日还是星期,A2M4 控制匹配 | R/W | 0x15 → 日期=15 |
注: BCD(Binary-Coded Decimal)码不同于十六进制。BCD 码的每一位表示一个十进制数字,因此BCD码转换为十进制时,只需十位乘以 10 加个位即可,而不是乘以 16。
1.5.2 闹钟日期匹配(0x0A位展开)
| 场景 | [7]A2M4 | [6]DY/DT | [5:0]Day/Date(BCD) | 0x0D 写入值 | 说明 |
|---|---|---|---|---|---|
| 忽略“日/星期”字段(例如“每天 09:30”只看分/时) | 1 | X | X | 0x80 | A2M4=1 即可;DY/DT/BCD 无关,常写 0x80 |
| 每月 21 日触发 | 0 | 0 | 0x21 | 0x21 | BCD=0x21 → 十进制 21 |
| 每月 1 日触发 | 0 | 0 | 0x01 | 0x01 | 按“日期”匹配 1 号 |
| 每周日触发 | 0 | 1 | 0x01 | 0x41 | 0x40(DY/DT=1) + 0x01(周日) |
| 每周一触发 | 0 | 1 | 0x02 | 0x42 | 0x40 + 0x02(周一) |
| 每周三触发 | 0 | 1 | 0x04 | 0x44 | 0x40 + 0x04(周三) |
注:Alarm2 的分钟/小时匹配由 0x0B(A2M2)与 0x0C(A2M3)决定;0x0D 仅决定“按日期/按星期/是否忽略此字段”。Alarm1同理,其数据最高位代表是否忽略,如果为0则参与匹配
1.6 闹钟触发设置
| 地址 | 功能 | 说明 | 读写 | 样例数据 (BCD) |
|---|---|---|---|---|
| 0x0E | 控制寄存器(Control) | 控制方波、闹钟中断、32kHz 输出 | R/W | 0x0C → 使能闹钟中断 |
| 0x0F | 状态寄存器(Status) | 监控闹钟标志、振荡器状态等 | R/W | 0x02 → A2F=1 表示闹钟2触发 |
| 0x10 | 老化寄存器(Aging Offset) | 校准时钟频率 | R/W | - |
| 0x11~0x12 | 温度寄存器(Temperature) | 高位为整数,低位为小数(分辨率0.25℃) | R | 0x19 0xC0 → 25.75℃ |
1.6.1 控制寄存器(0x0E位展开)
| 位号 | 位名 | 读写 | 作用说明 |
|---|---|---|---|
| [7] | EOSC | R/W | Enable Oscillator(置 1 关闭 32 kHz 时钟振荡器,通常应为 0 以运行;在仅电池供电时可置 1 节能)。 |
| [6] | BBSQW | R/W | Battery-Backed Square-Wave:当仅由 VBAT 供电时,允许 SQW 方波输出(需 INTCN=0)。 |
| [5] | CONV | R/W | 手动触发一次温度转换(写 1 触发;转换期间 BSY 会置 1;完成后硬件自动清 0)。 |
| [4] | RS2 | R/W | 方波频率选择位(与 RS1 组合):00=1 Hz,01=1.024 kHz,10=4.096 kHz,11=8.192 kHz。 |
| [3] | RS1 | R/W | 同上(与 RS2 一起决定 SQW 输出频率;仅在 INTCN=0 时有效)。 |
| [2] | INTCN | R/W | Interrupt Control:1=INT/SQW 作为中断输出(用于 Alarm1/2);0=输出 SQW 方波。 |
| [1] | A2IE | R/W | Alarm2 Interrupt Enable:允许 Alarm2 触发时拉低 INT/SQW(需 INTCN=1)。 |
| [0] | A1IE | R/W | Alarm1 Interrupt Enable:允许 Alarm1 触发时拉低 INT/SQW(需 INTCN=1)。 |
1.6.2 状态寄存器(0x0F位展开)
| 位号 | 位名 | 读写 | 作用说明 |
|---|---|---|---|
| [7] | OSF | R/W | Oscillator Stop Flag:振荡器曾停止过(上电/掉电/晶振异常)。写 0 可清除。 |
| [6] | — | R | 保留(读作 0)。 |
| [5] | — | R | 保留(读作 0)。 |
| [4] | — | R | 保留(读作 0)。 |
| [3] | EN32kHz | R/W | 使能 32kHz 固定输出引脚(若芯片/封装提供)。1=使能,0=关闭。 |
| [2] | BSY | R | 温度转换忙标志:1=正在转换,由硬件在完成后清 0。 |
| [1] | A2F | R/W | Alarm2 Flag:Alarm2 命中置 1。必须写 0 清除(否则 INT 会保持低电平)。 |
| [0] | A1F | R/W | Alarm1 Flag:Alarm1 命中置 1。必须写 0 清除。 |
1.6.3 闹钟设置步骤
| 阶段 | 操作主体 | 操作内容 | 说明 |
|---|---|---|---|
| 设置闹钟 | MCU / 用户 | 写控制寄存器 0x0E | 配置闹钟相关寄存器 |
| INTCN=1 → 将 INT/SQW 引脚设为中断输出(非方波输出) | 设置输出模式为中断 | ||
| A1IE/A2IE=1 → 使能闹钟1/闹钟2中断 | 启用对应闹钟中断 | ||
| 设置闹钟匹配条件(见 1.5) | 配置触发条件,如秒、分、时、日/星期等 | ||
| 闹钟条件匹配 | 硬件(DS3231) | 自动置位状态寄存器 0x0F 的 A1F/A2F | 硬件标记哪个闹钟触发 |
| INT/SQW 引脚拉低(开漏输出,需要外部上拉电阻) | 中断信号输出,保持低电平 | ||
| 中断处理 | MCU | 检测 INT/SQW 引脚中断 | MCU 捕获中断信号 |
| 读取 0x0F 判断触发的闹钟(A1F 或 A2F) | 确认触发源 | ||
| 清除标志 | MCU | 写 0 清除对应的 A1F / A2F | 清除状态寄存器标志,INT/SQW 自动恢复高电平 |
注:当闹钟条件匹配时,DS3231 芯片会自动将中断输出引脚拉低,该引脚通常连接到单片机的外部中断端口,通知 MCU 闹钟已触发。MCU 在中断服务函数中读取状态寄存器,判断是哪个闹钟引发的中断,并清除对应的中断标志位,使中断引脚恢复高电平。
流程概览:
闹钟匹配 → 中断引脚拉低 → MCU 捕获中断 → 读取状态寄存器 → 清除标志 → 引脚恢复高电平
2. stm32CubeMX配置
2.1 SYS配置

2.2 RCC配置


2.3 IIC配置

2.4 USART配置

3. VSCode代码
3.1 DS3231驱动代码
#include "ds3231.h"
extern I2C_HandleTypeDef DS3231_I2C;
static I2C_HandleTypeDef *hi2c = &DS3231_I2C; // 内部使用的I2C句柄
// 初始化DS3231
HAL_StatusTypeDef DS3231_Init(void) {
// 检查设备是否响应
uint8_t data = 0;
HAL_StatusTypeDef status = HAL_I2C_Mem_Read(hi2c, DS3231_ADDRESS << 1, DS3231_REG_SECONDS, 1, &data, 1, HAL_MAX_DELAY);
return status;
}
// 设置时间
HAL_StatusTypeDef DS3231_SetTime(DS3231_Time *time) {
uint8_t buffer[7];
// 准备要写入的数据 (转换为BCD格式)
buffer[0] = DS3231_BinToBcd(time->seconds);
buffer[1] = DS3231_BinToBcd(time->minutes);
buffer[2] = DS3231_BinToBcd(time->hours);
buffer[3] = DS3231_BinToBcd(time->day);
buffer[4] = DS3231_BinToBcd(time->date);
buffer[5] = DS3231_BinToBcd(time->month);
buffer[6] = DS3231_BinToBcd(time->year);
// 写入时间寄存器
return HAL_I2C_Mem_Write(hi2c, DS3231_ADDRESS << 1, DS3231_REG_SECONDS, 1, buffer, 7, HAL_MAX_DELAY);
}
// 获取时间
HAL_StatusTypeDef DS3231_GetTime(DS3231_Time *time) {
uint8_t buffer[7];
HAL_StatusTypeDef status;
// 读取时间寄存器
status = HAL_I2C_Mem_Read(hi2c, DS3231_ADDRESS << 1, DS3231_REG_SECONDS, 1, buffer, 7, HAL_MAX_DELAY);
if (status != HAL_OK) {
return status;
}
// 转换为二进制格式
time->seconds = DS3231_BcdToBin(buffer[0] & 0x7F); // 忽略CH位
time->minutes = DS3231_BcdToBin(buffer[1]);
time->hours = DS3231_BcdToBin(buffer[2]);
time->day = DS3231_BcdToBin(buffer[3]);
time->date = DS3231_BcdToBin(buffer[4]);
time->month = DS3231_BcdToBin(buffer[5] & 0x1F); // 忽略世纪位
time->year = DS3231_BcdToBin(buffer[6]);
return HAL_OK;
}
// 获取温度
HAL_StatusTypeDef DS3231_GetTemperature(DS3231_Temperature *temp) {
uint8_t buffer[2];
HAL_StatusTypeDef status;
// 读取温度寄存器
status = HAL_I2C_Mem_Read(hi2c, DS3231_ADDRESS << 1, DS3231_REG_TEMP_MSB, 1, buffer, 2, HAL_MAX_DELAY);
if (status != HAL_OK) {
return status;
}
// 温度整数部分是有符号的
temp->integer = (int8_t)buffer[0];
// 温度小数部分 (每个LSB = 0.25°C)
temp->decimal = (buffer[1] >> 6) * 25;
return HAL_OK;
}
// 将BCD码转换为二进制
uint8_t DS3231_BcdToBin(uint8_t value) {
return ((value >> 4) * 10) + (value & 0x0F);
}
// 将二进制转换为BCD码
uint8_t DS3231_BinToBcd(uint8_t value) {
return ((value / 10) << 4) | (value % 10);
}
#ifndef __DS3231_H__
#define __DS3231_H__
#include "stm32f1xx_hal.h" // 根据您的STM32系列修改
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
// I2C配置
#define DS3231_I2C hi2c1 // 修改为您的I2C句柄
#define DS3231_ADDRESS 0x68 // DS3231 I2C地址 (7位地址)
// DS3231寄存器地址
#define DS3231_REG_SECONDS 0x00
#define DS3231_REG_MINUTES 0x01
#define DS3231_REG_HOURS 0x02
#define DS3231_REG_DAY 0x03
#define DS3231_REG_DATE 0x04
#define DS3231_REG_MONTH 0x05
#define DS3231_REG_YEAR 0x06
#define DS3231_REG_CONTROL 0x0E
#define DS3231_REG_STATUS 0x0F
#define DS3231_REG_AGING 0x10
#define DS3231_REG_TEMP_MSB 0x11
#define DS3231_REG_TEMP_LSB 0x12
// 时间结构体
typedef struct {
uint8_t seconds; // 0-59
uint8_t minutes; // 0-59
uint8_t hours; // 1-12 或 0-23 (取决于格式)
uint8_t day; // 1-7 (1=星期日, 2=星期一, ..., 7=星期六)
uint8_t date; // 1-31
uint8_t month; // 1-12
uint8_t year; // 0-99 (2000-2099)
} DS3231_Time;
// 温度结构体
typedef struct {
int8_t integer; // 整数部分
uint8_t decimal; // 小数部分 (0.25度增量)
} DS3231_Temperature;
// 初始化DS3231
HAL_StatusTypeDef DS3231_Init(void);
// 设置时间
HAL_StatusTypeDef DS3231_SetTime(DS3231_Time *time);
// 获取时间
HAL_StatusTypeDef DS3231_GetTime(DS3231_Time *time);
// 获取温度
HAL_StatusTypeDef DS3231_GetTemperature(DS3231_Temperature *temp);
// 将BCD码转换为二进制
uint8_t DS3231_BcdToBin(uint8_t value);
// 将二进制转换为BCD码
uint8_t DS3231_BinToBcd(uint8_t value);
#endif /* __DS3231_H__ */
3.2 测试代码
DS3231_Time currentTime;
DS3231_Temperature temp;
// 初始化DS3231
if (DS3231_Init() != HAL_OK) {
while (1);
}
currentTime.day = 6;
currentTime.date = 1;
currentTime.hours = 22;
currentTime.month = 8;
currentTime.minutes = 4;
currentTime.year = 25;
DS3231_SetTime(¤tTime);
// 获取当前时间
if (DS3231_GetTime(¤tTime) != HAL_OK) {
//当前时间已存入currentTime结构体中
}
// 获取温度
if (DS3231_GetTemperature(&temp) != HAL_OK) {
//当前温度已存入temp结构体中
}
4. 结论
本文系统介绍了 DS3231 芯片的寄存器配置方法及其使用说明,并通过实际驱动代码与实验,完成了 时间日期的写入 以及 时间、日期与温度的读取。通过这些实验,读者可以对 DS3231 的基本功能和 I²C 通信过程有直观的理解和实践体验。
需要说明的是,本文暂未涉及 闹钟功能与中断应用。这是因为闹钟的完整实现通常需要结合更多外设与知识点,例如外部中断按键、LED 屏幕显示,以及通过 PWM 驱动无源蜂鸣器等。作者计划在后续课程中,将这些内容逐步讲解清楚,并最终设计一个 综合性的闹钟实验,其中会涵盖 I²C 通信、USART 通信、中断按键处理、PWM 信号输出、蜂鸣器驱动与显示控制 等知识,帮助读者完成从单一功能到系统集成的学习过程。
此外,虽然 DS3231 提供了温度读取功能,但其分辨率与精度有限,且温度寄存器的更新频率为 1 分钟一次,因此即使连续读取,其变化也较慢。若实验中对温度精度要求较高,则需要引入 专业的温度传感器芯片。后续作者也会针对这一需求,设计更高精度的温度采集实验,供读者扩展学习。
更多推荐



所有评论(0)