STM32F103C8T6 · HAL库开发 · 3线串行通信
DS1302 是 DALLAS 公司推出的低功耗实时时钟/日历芯片,内置一个实时时钟/日历和 31 字节静态 RAM。它通过一个简单的 3 线串行接口与微控制器通信:
DS1302 内部时钟/日历寄存器使用 BCD(Binary Coded Decimal) 格式存储数据。每个时间单位(秒、分、时、日、月、周、年)占用一个寄存器,高低 4 位分别存储十位和个位。例如 0x26 表示 26(年 2026),0x11 表示 11(时 11点)。
芯片内置涓流充电器,可外接备份电池(如 CR2032),在主电源断电时自动切换到备份电源,保证时钟持续运行。晶振使用 32.768kHz 标准时钟晶振。
提供秒、分、时、日、月、周、年完整时间信息,自动处理闰年、大小月等日历逻辑,无需MCU干预。
内置电源检测电路,主电源断电时自动切换至备份电池,确保时钟持续运行,数据不丢失。
内置31字节静态RAM,可用于存储用户数据,通过相同的3线串行接口读写,断电后由备份电池保持。
内置可编程涓流充电器,支持对备份电池进行充电管理,可选择二极管和电阻配置充电电流。
| 引脚号 | 名称 | 功能 | STM32连接 |
|---|---|---|---|
| 1 | VCC | 主电源正极 (2.0V~5.5V) | 5V |
| 2 | X1 | 32.768kHz 晶振引脚1 | 外部晶振 |
| 3 | X2 | 32.768kHz 晶振引脚2 | 外部晶振 |
| 4 | GND | 电源地 | GND |
| 5 | RST/CE | 复位/使能信号 | PB5 |
| 6 | DSIO | 双向数据 I/O | PB4 |
| 7 | SCLK | 串行时钟 | PB3 |
| 8 | BAT | 备份电池正极 | CR2032 3V |
引脚对应关系: DS1302 模块的 SCLK → STM32 PB3,DSIO → STM32 PB4,RST/CE → STM32 PB5。 OLED 显示屏通过软件 I2C 驱动,SCL → PB15,SDA → PB14。
DS1302 使用 3 个 GPIO 引脚进行通信,均配置为推挽输出模式(DSIO 在读取时动态切换为输入模式)。引脚通过宏定义映射,便于移植。
//---定义DS1302使用的IO口---// #define DSIO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define RST_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SCLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define DSIO_Pin GPIO_PIN_4 #define DSIO_GPIO_Port GPIOB #define RST_Pin GPIO_PIN_5 #define RST_GPIO_Port GPIOB #define SCLK_Pin GPIO_PIN_3 #define SCLK_GPIO_Port GPIOB //---GPIO操作宏---// #define DSIO(a) if(a){HAL_GPIO_WritePin(DSIO_GPIO_Port,DSIO_Pin,GPIO_PIN_SET);}\ else{HAL_GPIO_WritePin(DSIO_GPIO_Port,DSIO_Pin,GPIO_PIN_RESET);} #define RST(a) if(a){HAL_GPIO_WritePin(RST_GPIO_Port,RST_Pin,GPIO_PIN_SET);}\ else{HAL_GPIO_WritePin(RST_GPIO_Port,RST_Pin,GPIO_PIN_RESET);} #define SCLK(a) if(a){HAL_GPIO_WritePin(SCLK_GPIO_Port,SCLK_Pin,GPIO_PIN_SET);}\ else{HAL_GPIO_WritePin(SCLK_GPIO_Port,SCLK_Pin,GPIO_PIN_RESET);} #define DSIO_IN HAL_GPIO_ReadPin(DSIO_GPIO_Port,DSIO_Pin)
void DS1302_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; SCLK_GPIO_CLK_ENABLE(); RST_GPIO_CLK_ENABLE(); DSIO_GPIO_CLK_ENABLE(); // RST (PB5) - 推挽输出 GPIO_InitStruct.Pin = RST_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(RST_GPIO_Port, &GPIO_InitStruct); // SCLK (PB3) - 推挽输出 GPIO_InitStruct.Pin = SCLK_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(SCLK_GPIO_Port, &GPIO_InitStruct); }
// 设置DSIO为输入模式(读取数据时使用) void IO_INPUT(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DSIO_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(DSIO_GPIO_Port, &GPIO_InitStruct); } // 设置DSIO为输出模式(写入数据时使用) void IO_OUTPUT(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; HAL_GPIO_WritePin(DSIO_GPIO_Port, DSIO_Pin, GPIO_PIN_SET); GPIO_InitStruct.Pin = DSIO_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DSIO_GPIO_Port, &GPIO_InitStruct); }
DS1302 的通信时序:首先拉低 SCLK,拉高 RST 开始通信;然后发送 8 位地址命令(LSB优先),数据在 SCLK 上升沿被 DS1302 读取;接着写入或读取 8 位数据。通信结束后拉低 RST。
void Ds1302Write(uchar addr, uchar dat) { uchar n; RST(0); delay_us(1); SCLK(0); delay_us(1); // SCLK先置低 RST(1); delay_us(1); // RST拉高,开始通信 IO_OUTPUT(); for (n=0; n<8; n++) { // 发送8位地址命令 (LSB优先) if(addr & 0x01){DSIO(1);} else{DSIO(0);} addr >>= 1; SCLK(1); delay_us(1); // 上升沿,DS1302读取数据 SCLK(0); delay_us(1); } for (n=0; n<8; n++) { // 写入8位数据 if(dat & 0x01){DSIO(1);} else{DSIO(0);} dat >>= 1; SCLK(1); delay_us(1); SCLK(0); delay_us(1); } RST(0); delay_us(1); // 通信结束 }
uchar Ds1302Read(uchar addr) { uchar n, dat, dat1; RST(0); delay_us(1); SCLK(0); delay_us(1); RST(1); delay_us(1); IO_OUTPUT(); for(n=0; n<8; n++) { // 发送8位地址命令 if(addr & 0x01){DSIO(1);} else{DSIO(0);} addr >>= 1; SCLK(1); delay_us(1); SCLK(0); delay_us(1); // 下降沿,DS1302准备数据 } delay_us(1); IO_INPUT(); // 切换为输入模式 for(n=0; n<8; n++) { // 读取8位数据 (LSB优先) if(DSIO_IN == 1){dat1=1;} else{dat1=0;} dat = (dat>>1) | (dat1<<7); // 组装数据 SCLK(1); delay_us(1); SCLK(0); delay_us(1); } IO_OUTPUT(); RST(0); return dat; }
DS1302 的时钟寄存器共 7 个字节,分别对应秒、分、时、日、月、周、年。读地址最低位为 1,写地址最低位为 0。数据以 BCD 码格式存储。
//---读取地址命令: 秒 分 时 日 月 周 年---// uchar READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d}; //---写入地址命令: 秒 分 时 日 月 周 年---// uchar WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c}; //---初始时间: 2026年08月20日 星期四 11:00:00 (BCD码)---// // 存储顺序: 秒 分 时 日 月 周 年 uchar TIME[7] = {0, 0, 0x11, 0x20, 0x08, 0x04, 0x26};
| 时间单位 | BCD码 | 计算公式 | 十进制值 |
|---|---|---|---|
| 秒 (TIME[0]) | 0x00 | 0x10*10 + 0x00 = 00 | 0 |
| 分 (TIME[1]) | 0x00 | 0x10*10 + 0x00 = 00 | 0 |
| 时 (TIME[2]) | 0x11 | 0x01*10 + 0x01 = 11 | 11 |
| 日 (TIME[3]) | 0x20 | 0x02*10 + 0x00 = 20 | 20 |
| 月 (TIME[4]) | 0x08 | 0x00*10 + 0x08 = 08 | 8 |
| 周 (TIME[5]) | 0x04 | 0x00*10 + 0x04 = 04 | 4 (周四) |
| 年 (TIME[6]) | 0x26 | 0x02*10 + 0x06 = 26 | 2026 |
主循环每 500ms 读取一次 DS1302 时间数据,将 BCD 码转换为十进制,然后在 OLED 上显示日期、时间和星期。系统时钟配置为 72MHz(HSE 8MHz × PLL9)。
while (1) { if(HAL_GetTick() - Time_Now >= 500) // 500ms更新一次 { Ds1302ReadTime(); // 读取DS1302时间数据 // BCD码转十进制 Year = 2000 + TIME[6]/0x10*10 + TIME[6]%0x10; Week = TIME[5]/0x10*10 + TIME[5]%0x10; Mon = TIME[4]/0x10*10 + TIME[4]%0x10; Day = TIME[3]/0x10*10 + TIME[3]%0x10; Hour = TIME[2]/0x10*10 + TIME[2]%0x10; Min = TIME[1]/0x10*10 + TIME[1]%0x10; Sec = TIME[0]/0x10*10 + TIME[0]%0x10; OLED_Clear(); // 清屏 // 显示日期和时间 OLED_Printf(0, 0, OLED_8X16, "%04d.%02d.%02d", Year, Mon, Day); OLED_Printf(0, 16, OLED_8X16, "%02d:%02d:%02d", Hour, Min, Sec); // 显示星期 switch(Week) { case 1: OLED_Printf(104, 0, OLED_8X16, "Mon"); break; case 2: OLED_Printf(104, 0, OLED_8X16, "Tue"); break; case 3: OLED_Printf(104, 0, OLED_8X16, "Wed"); break; case 4: OLED_Printf(104, 0, OLED_8X16, "Thu"); break; case 5: OLED_Printf(104, 0, OLED_8X16, "Fri"); break; case 6: OLED_Printf(104, 0, OLED_8X16, "Sat"); break; case 7: OLED_Printf(104, 0, OLED_8X16, "Sun"); break; } OLED_Update(); // 更新显示 Time_Now = HAL_GetTick(); // 更新时间戳 } }
void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // HSE 8MHz 外部晶振 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz × 9 = 72MHz HAL_RCC_OscConfig(&RCC_OscInitStruct); // SYSCLK = PLLCLK = 72MHz, APB1 = 36MHz, APB2 = 72MHz RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); }
DS1302 通信需要微秒级延时,利用 SysTick 计数器实现精确延时。SYSCLK 宏定义为 64(对应 72MHz/1000 的近似值),通过读取 SysTick->VAL 计数差实现延时。
#define SYSCLK 64 // 系统时钟参数 void delay_us(uint16_t udelay) { uint32_t startval, tickn, delays, wait; startval = SysTick->VAL; tickn = HAL_GetTick(); delays = udelay * SYSCLK; if(delays > startval) { while(HAL_GetTick() == tickn) {} wait = SYSCLK*1000 + startval - delays; while(wait < SysTick->VAL) {} } else { wait = startval - delays; while(wait < SysTick->VAL && HAL_GetTick() == tickn) {} } }