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(&currentTime);
// 获取当前时间
if (DS3231_GetTime(&currentTime) != HAL_OK) {
    //当前时间已存入currentTime结构体中
}
    
// 获取温度
if (DS3231_GetTemperature(&temp) != HAL_OK) {
    //当前温度已存入temp结构体中
}
    

4. 结论

        本文系统介绍了 DS3231 芯片的寄存器配置方法及其使用说明,并通过实际驱动代码与实验,完成了 时间日期的写入 以及 时间、日期与温度的读取。通过这些实验,读者可以对 DS3231 的基本功能和 I²C 通信过程有直观的理解和实践体验。

        需要说明的是,本文暂未涉及 闹钟功能与中断应用。这是因为闹钟的完整实现通常需要结合更多外设与知识点,例如外部中断按键、LED 屏幕显示,以及通过 PWM 驱动无源蜂鸣器等。作者计划在后续课程中,将这些内容逐步讲解清楚,并最终设计一个 综合性的闹钟实验,其中会涵盖 I²C 通信、USART 通信、中断按键处理、PWM 信号输出、蜂鸣器驱动与显示控制 等知识,帮助读者完成从单一功能到系统集成的学习过程。

        此外,虽然 DS3231 提供了温度读取功能,但其分辨率与精度有限,且温度寄存器的更新频率为 1 分钟一次,因此即使连续读取,其变化也较慢。若实验中对温度精度要求较高,则需要引入 专业的温度传感器芯片。后续作者也会针对这一需求,设计更高精度的温度采集实验,供读者扩展学习。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐