DS1302 概述
时序图分析
理解说明:
先说单字节写时序,首先,字节序开始。SCLK = 低电平,RST = 0,接着RST = 1,这里要注意datasheet中的时间,不过对于72Mhz MCU,IO翻转速度也能够满足。开始后,IO就要准备发送的命令寄存器了,先发送低位再发送高位,可以看出,IO总是先准备好数据,接着SCLK上升沿,送入数据。 每次都是先发送命令寄存器,再去发送8bits数据。写入完毕后,SCLK = 0,RST = 0,表示字节序结束。
读时序:
首先,字节序开始。SCLK = 0,RST = 0,RST = 1。在SCLK的时钟信号下,IO发送给DS1302的字节序,低位先发送,高位最后,这里和写字节序同理,IO先准备好数据,然后在SCLK的上升沿时候送入数据。==Warning:==读字节序,是下降沿读取,而且在写字节序的最后一个时钟脉冲的下降沿,已经是读取第一位了,可以看出总共有15个时钟脉冲,这与写时序16个脉冲还有点区别。最后在下降沿下,依次读取8bits数据。
如果有不足之处,请欢迎改正。
读写时序
可以看出,向控制寄存器0x8E地址写入0x00(取消写保护),然后再从0x8E地址读取出来为0x00,表示取消了写保护,此时我们可以向DS1302的可写寄存器进行写入值了。以下是逻辑分析仪抓取的波形。注意:发送、接收数据都是低位先发送,低位先接收。
写保护寄存器读写操作:
读取星期寄存器:0x8B
读取小时寄存(0x85)器数据
寄存器表
重点寄存器描述
时钟暂停
AM-PM/12-24方式
写保护寄存器
比如,想要自己设置时间的时候或者朝RAM中写入东西前,bit7必须为0
慢速充电(Trickle charge)寄存器
这部分建议看手册,这部分可以通过设置寄存器来达到给备用电池充电的目的,电池可以是超级电容,也可以是微型锂电池。
其它部分
晶振和电源
BCD码转十进制计算
8421 BCD码是最基本和最常用的BCD码,它和四位自然二进制码相似,各位的权值为8、4、2、1,故称为有权BCD码。和四位自然二进制码不同的是,它只选用了四位二进制码中前10组代码,即用0000~1001分别代表它所对应的十进制数,余下的六组代码不用。
DS1302使用的是8421 BCD码,十进制范围是0~9。
举例:秒寄存器转为10进制值
第一种方法:
dstime.get_time.second = second/16*10+second%16;
第二种方法:
dstime.get_time.second = ((second & 0x70)>>4)*10 + (second & 0x0F);
简单点说就是,取出一个字节的高4位作为十进制的十位,取出字节中的低4位作为十进制的个位,然后相加组合成秒,同样,其它的日期也是同理。
完整的程序
平台和配置:
芯片:STM32F103C8T6
IDE:STM32Cubemx,CLion
下载:ST-Link V2
逻辑分析仪:某宝30块8通道,软件:Logic2.4.7
注意事项:CLion中printf重定向和Keil中不一样问题!
main.c
#include "ds1302.h"
int main(void)
{
ds1302_init();
while(1)
{
print_times();
HAL_Delay(500);
}
}
ds1302.h
//
// Created by lxp on 2024/3/29.
//
#ifndef CLOCK_DS1302_H
#define CLOCK_DS1302_H
#include "stm32f1xx.h"
#define DS1302_GPIO GPIOB
#define DS1302_DATA GPIO_PIN_12
#define DS1302_CLK GPIO_PIN_13
#define DS1302_RST GPIO_PIN_14
#define DS1302_DATA_IN { GPIOB->CRH &= 0xfff0ffff; GPIOB->CRH |= (uint32_t)(8<<16); }
#define DS1302_DATA_OUT { GPIOB->CRH &= 0xfff0ffff; GPIOB->CRH |= (uint32_t)(3<<16); }
#define DS1302_DATA_LOW HAL_GPIO_WritePin(DS1302_GPIO,DS1302_DATA,GPIO_PIN_RESET)
#define DS1302_DATA_HIGH HAL_GPIO_WritePin(DS1302_GPIO,DS1302_DATA,GPIO_PIN_SET)
#define DS1302_CLK_LOW HAL_GPIO_WritePin(DS1302_GPIO,DS1302_CLK,GPIO_PIN_RESET)
#define DS1302_CLK_HIGH HAL_GPIO_WritePin(DS1302_GPIO,DS1302_CLK,GPIO_PIN_SET)
#define DS1302_RST_LOW HAL_GPIO_WritePin(DS1302_GPIO,DS1302_RST,GPIO_PIN_RESET)
#define DS1302_RST_HIGH HAL_GPIO_WritePin(DS1302_GPIO,DS1302_RST,GPIO_PIN_SET)
typedef struct _time{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t week;
uint8_t hours;
uint8_t minute;
uint8_t second;
}_Time;
typedef struct _time_user{
_Time set_time;
_Time get_time;
}Time;
extern Time dstime;
//register addr
//写保护寄存器
#define WRITE_PROTECT 0x8e
#define WRITE_PROTECT_CLOSE 0x00
#define WRITE_PROTECT_OPEN 0x80
#define SECONDE_ADDR 0x80
#define MINUTE_ADDR 0x82
#define HOURS_ADDR 0x84
#define DAY_ADDR 0x86
#define MONTH_ADDR 0x88
#define WEEK_ADDR 0x8a
#define YEAR_ADDR 0x8c
#define CONTROL_ADDR 0x8e //写保护, value: 0x00: 关闭写保护,0x80:打开写保护
#define CHARGE_ADDR 0x90 //充电寄存器
#define CLOCK_MUTI_WAY_ADDR 0xbe //时钟多字节方式
#define PAUSE_CLOCK 0x80 //时钟暂停
#define RUNNING_CLOCK 0x00 //时钟运行
#define NO_CHARGE 0x00 //禁止充电
//RAM addr
#define RAM0_ADDR 0xc0
void delay(uint32_t time);
void ds1302_init(void);
void write_byte(uint8_t addr,uint8_t data);
uint8_t read_byte(uint8_t addr);
/**
* @brief: 获取时间,存储在结构体中
* @param: void
* @return: none
*/
void get_time(void);
/**
* @brief: 打印时间
* @param: none
* @return: none
*/
void print_times(void);
/**
* @brief: 设置时间
* @param: char *
* @return: none
*/
void set_time(void);
/**
* @brief: 设置时间,把十进制数转换为DS1302各个寄存器对应的数值
* @param year: 2000 ~ 2099
* @param month: 1~12
* @param day: 1~28/29,1~30,1~31
* @param hours: 只支持24h, 0~23
* @param minute: 0~59
* @param second: 0~59
* @return: none
*/
void set_time_params(uint16_t year,uint8_t month,uint8_t day,uint8_t week,uint8_t hours,uint8_t minute, uint8_t second);
#endif //CLOCK_DS1302_H
ds1302.c
//
// Created by lxp on 2024/3/29.
//
#include "ds1302.h"
#include "uart.h"
#include <stdio.h>
#include <string.h>
#include "retarget.h"
//日期: 年,月,日,时,分,秒
//2024年3月31日23:23:00
uint8_t set_time_value[7] = {0x24,0x03,0x31,0x33,0x23,0x00};
uint8_t day_ping_nia[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; //平年365天
uint8_t day_run_nia[12] = {31,29,31,30,31,30,31,31,30,31,30,31}; //闰年366天
//存储时间
Time dstime;
/**
* @brief: ds1302 init
* @param: none
* @return: none
*/
void ds1302_init(void)
{
GPIO_InitTypeDef ds1302_gpio_init;
uint8_t i = 0;
uint8_t write_protect_bit = 0;
uint8_t ram0 = 0;
//GPIO 初始化
__HAL_RCC_GPIOB_CLK_ENABLE();
ds1302_gpio_init.Pin = DS1302_CLK | DS1302_DATA | DS1302_RST;
ds1302_gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
ds1302_gpio_init.Pull = GPIO_PULLUP;
ds1302_gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DS1302_GPIO,&ds1302_gpio_init);
//复位
DS1302_DATA_LOW;
DS1302_RST_LOW;
DS1302_CLK_LOW;
//关闭芯片写保护位
write_byte(CONTROL_ADDR,WRITE_PROTECT_CLOSE);
//判断写保护位是否为关闭状态
write_protect_bit = read_byte(CONTROL_ADDR);
if(write_protect_bit == 0x00)
{
// printf("ds1302 is ok!\r\n");
}
else
{
// printf("ds1302 is error!\r\n");
}
// printf("rece write protect value = %02x.\r\n",write_protect_bit);
//检测RAM0是否可以使用
write_byte(RAM0_ADDR,0xfa);
ram0 = read_byte(RAM0_ADDR);
if(ram0 == 0xfa)
{
printf("ds1302 ram is ok!.\r\n");
}
printf("ram0 = %02x.\r\n",ram0);
//判断DS1302是否正在运行
// if(read_byte(SECONDE_ADDR) & 0x80)
// {
//设置时间
// set_time_params(2024,5,31,3,23,59,55);
set_time_params(2024,4,17,3,11,59,55);
// }
}
/**
* @brief: 向指定地址写入一个字节的数据
* @param addr
* @param data
*/
void write_byte(uint8_t addr,uint8_t data)
{
uint8_t i = 0;
//时序开始
DS1302_RST_LOW;
DS1302_CLK_LOW;
delay(1);
DS1302_RST_HIGH;
//设置DATA pin为输出
DS1302_DATA_OUT;
addr = addr & 0xfe; //写方式,最后一位为0
//发送命令及其地址字节
for (i = 0; i < 8; i++)
{
//准备好数据,先发送低位数据
if(addr & 0x01)
{
DS1302_DATA_HIGH;
}
else
{
DS1302_DATA_LOW;
}
//时序,从机上升沿接收到数据
DS1302_CLK_LOW;
DS1302_CLK_HIGH;
//右移一位
addr = addr >> 1;
}
//发送数据字节
for (i = 0;i < 8;i++)
{
if(data & 0x01)
DS1302_DATA_HIGH;
else
DS1302_DATA_LOW;
DS1302_CLK_LOW;
DS1302_CLK_HIGH;
data = data >> 1;
}
//时序结束操作
DS1302_CLK_LOW;
DS1302_RST_LOW;
}
/**
* @brief: 从指定地址读取一个字节的数据
* @param addr
* @return
*/
uint8_t read_byte(uint8_t addr)
{
uint8_t rec_data = 0x00;
uint8_t i = 0;
//时序开始操作
DS1302_RST_LOW;
DS1302_CLK_LOW;
delay(1);
DS1302_RST_HIGH;
//设置DATA pin为输出模式
DS1302_DATA_OUT;
//读方式,寄存器最后一位为1
addr = addr | 0x01;
//读时序,先发送要读的地址
for (i = 0; i < 8; i++)
{
DS1302_CLK_LOW;
if(addr & 0x01)
DS1302_DATA_HIGH;
else
DS1302_DATA_LOW;
DS1302_CLK_HIGH;
addr = addr >> 1;
}
//设置DATA pin为输入模式
DS1302_DATA_IN;
for (i = 0; i < 8; i++)
{
DS1302_CLK_LOW;
//rec_data = rec_data >> 1;
if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
{
//rec_data = rec_data | 0x80;
rec_data = rec_data | (1 << i);
}
else
{
rec_data = rec_data & (~(1 << i));
}
DS1302_CLK_HIGH;
}
//结束时序
DS1302_CLK_LOW;
DS1302_RST_LOW;
return rec_data;
}
//初略延时
void delay(uint32_t time)
{
uint32_t t = 0;
while(time--)
{
t++;
}
t = 0;
}
/**
* @brief: 设置时间,把十进制数转换为DS1302各个寄存器对应的数值
* @param year: 2000 ~ 2099
* @param month: 1~12
* @param day: 1~28/29,1~30,1~31
* @param hours: 只支持24h, 0~23
* @param minute: 0~59
* @param second: 0~59
* @return: none
*/
void set_time_params(uint16_t year,uint8_t month,uint8_t day,uint8_t week,uint8_t hours,uint8_t minute, uint8_t second)
{
memset(&dstime,0,sizeof(Time));
year -= 2000;
dstime.set_time.year = year/10*16+year%10;
dstime.set_time.month = month/10*16+month%10;
dstime.set_time.day = day/10*16+day%10;
dstime.set_time.week = week/10*16+week%10;
dstime.set_time.hours = hours/10*16+hours%10;
// dstime.set_time.hours = 0x33;
dstime.set_time.minute = minute/10*16+minute%10;
dstime.set_time.second = second/10*16+second%10;
// printf("%d-%d-%d : %d-%d-%d \r\n",year,month,day,hours,minute,second);
// printf("year = %02x \r\n",dstime.set_time.year);
// printf("month = %02x \r\n",dstime.set_time.month);
// printf("day = %02x \r\n",dstime.set_time.day);
// printf("hours = %02x \r\n",dstime.set_time.hours);
// printf("minute = %02x \r\n",dstime.set_time.minute);
// printf("second = %02x \r\n",dstime.set_time.second);
set_time();
}
/**
* @brief: 设置时间
* @param: none
* @return: none
*/
void set_time(void)
{
//关闭时钟
write_byte(SECONDE_ADDR,PAUSE_CLOCK);
//设置年,月,日,小时,分钟,秒
write_byte(YEAR_ADDR,dstime.set_time.year);
write_byte(MONTH_ADDR,dstime.set_time.month);
write_byte(DAY_ADDR,dstime.set_time.day);
write_byte(WEEK_ADDR,dstime.set_time.week);
write_byte(HOURS_ADDR,dstime.set_time.hours); //24h方式
write_byte(MINUTE_ADDR,dstime.set_time.minute);
write_byte(SECONDE_ADDR,dstime.set_time.second);
//设置禁止充电,因为我使用的是纽扣电池
write_byte(CHARGE_ADDR,NO_CHARGE);
}
/**
* @brief: 获取时间,存储在结构体中
* @param: void
* @return: none
*/
void get_time(void)
{
uint16_t year = 0;
uint8_t month = 0,day = 0,week = 0,hours = 0,minute = 0,second = 0;
//获取年,月,日,时,分,秒
year = read_byte(YEAR_ADDR);
month = read_byte(MONTH_ADDR);
day = read_byte(DAY_ADDR);
week = read_byte(WEEK_ADDR);
hours = read_byte(HOURS_ADDR);
minute = read_byte(MINUTE_ADDR);
second = read_byte(SECONDE_ADDR);
/*
printf("second = 0x%02x\r\n",second);
printf("minute = 0x%02x\r\n",minute);
printf("hours = 0x%02x\r\n",hours);
printf("day = 0x%02x\r\n",day);
printf("month = 0x%02x\r\n",month);
printf("year = 0x%02x\r\n",year);
*/
//转换为十进制数值
// dstime.get_time.year = 2000 + (year >> 4)*10 + (year & 0x0F);
// dstime.get_time.month = ((month & 0x10)>>4)*10 + (month & 0x0F);
// dstime.get_time.day = ((day & 0x30)>>4)*10 + (day & 0x0F);
// dstime.get_time.week = week & 0x0F;
// dstime.get_time.hours = ((hours & 0x20)>>5)*10 + ((hours & 0x10)>>4)*10 + (hours & 0x0F);
// dstime.get_time.minute = ((minute & 0x70)>>4)*10 + (minute & 0x0F);
// dstime.get_time.second = ((second & 0x70)>>4)*10 + (second & 0x0F);
//另一种计算方法
dstime.get_time.year = 2000 + year/16*10+year%16;
dstime.get_time.month = month/16*10+month%16;
dstime.get_time.day = day/16*10+day%16;
dstime.get_time.week = week/16*10+week%16;
dstime.get_time.hours = (hours/16*10)+hours%16;
dstime.get_time.minute = minute/16*10+minute%16;
dstime.get_time.second = second/16*10+second%16;
}
/**
* @brief: 打印时间
* @param: none
* @return: none
*/
void print_times(void)
{
get_time();
// printf("%d-%d-%d : %d-%d-%d : %d \r\n",dstime.get_time.year,dstime.get_time.month,dstime.get_time.day,dstime.get_time.hours,dstime.get_time.minute,dstime.get_time.second,dstime.get_time.week);
}
其它知识拓展
根据年月日返回星期几
/**
* @brief: 输入年月日,返回今天是周几,使用蔡勒公式
* @param year
* @param month
* @param day
* @return: 周几: 1~7
*/
int return_week_day(unsigned int year, unsigned int month, unsigned int day)
{
int week = 0;
unsigned int y = 0, c = 0, m = 0, d = 0;
if (month == 1 || month == 2) {
c = (year - 1) / 100;
y = (year - 1) % 100;
m = month + 12;
d = day;
} else {
c = year / 100;
y = year % 100;
m = month;
d = day;
}
week = y + y / 4 + c / 4 - 2 * c + 26 * ( m + 1 ) / 10 + d - 1; //蔡勒公式
week = week >= 0 ? ( week % 7 ) : ( week % 7 + 7 ); //week为负时取模
if ( week == 0 ) { //星期日不作为一周的第一天
week = 7;
}
return week;
}
根据年、月,返回当前月有多少天
/**
* @brief: 根据年、月,返回当前月多少天
* @param year:
* @param month: 1~12
* @return: 返回当前月多少天
*/
uint8_t get_days_of_month(int year, int month)
{
uint8_t days = 0; //该年该月天数
switch (month)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
days = 28; //平年
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) //闰年,29天
{
days = 29;
}
break;
}
return days;
}
遇到的问题
问题1
在对秒寄存器进行读操作,数据输出:00 ,00,01,01,02,02,03,03,04,04,08,08,09,09,0a …
此处是以十六进制输出的,任务内代码如下:
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
uint8_t second = 0;
for(;;)
{
second = read_byte(SECONDE_ADDR_R);
printf("second = %02x.\r\n",second);
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
osDelay(pdMS_TO_TICKS(1000));
}
/* USER CODE END 5 */
}
//设置DATA pin为输入模式
DS1302_DATA_IN;
for (i = 0; i < 8; i++)
{
DS1302_CLK_LOW;
if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
{
rec_data = rec_data | 0x80;
}
else
{
rec_data = rec_data & 0x7f;
}
DS1302_CLK_HIGH;
rec_data = rec_data >> 1; //问题在于这个地方!!!
}
可以看出,每次的第一位右移了8次(移没了!),第二位右移了7次。秒为1时候,低位先发为0x80 >> 8 = 0,问题就出现在这里,右移的次数不对!!!
改正后代码:
//设置DATA pin为输入模式
DS1302_DATA_IN;
for (i = 0; i < 8; i++)
{
DS1302_CLK_LOW;
rec_data = rec_data >> 1;
if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
{
rec_data = rec_data | 0x80;
}
else
{
rec_data = rec_data & 0x7f;
}
DS1302_CLK_HIGH;
}
改进后的方法2,使用置位、清除某位来操作:
//设置DATA pin为输入模式
DS1302_DATA_IN;
for (i = 0; i < 8; i++)
{
DS1302_CLK_LOW;
if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
{
rec_data = rec_data | (1 << i);
}
else
{
rec_data = rec_data & (~(1 << i));
}
DS1302_CLK_HIGH;
}
结果正常了。
问题2
问题描述: 时间在24小时模式,走到23-59-59秒,变成了24-00-00,日期没有任何变化也不进位。看到手册里面有写到闰年校准功能,并且对于小于31天的月份自动调整,这肯定和手册写的功能是相悖的。
我就是这个地方看的疏忽了,理解上出现了偏差,我认为的,在24h模式下,bit4为第一个10小时标志位,bit5为第二个10小时标志位,比如表示>20小时的时间,bit4、bit5都要为1,所以,我初始化时间为23h,朝这个寄存器写入0x33(这是一个非法值!!!),然后芯片虽然小时部分能跑,但日期部分已经无法正常工作了!!!写入0x23后,DS1302就正常工作了。困扰了我几天的问题,竟然因为这一小点的疏忽毁于一旦。。。
找问题过程:
人家手册寄存器值的范围就是112,或者023。怎么可能出现24?苦思冥想,手册看了一遍又一遍,最后还是没解决,最后还是在群里问,然后大佬解答说我写入23h,写入的值是0x33引起的。确实手册里面说:bit4是10标志位,bit5是20标志位。我想着23h不就是0x33嘛,这也体现了我的疏忽、不仔细,0x33这个算是一个非法值,写入后,都不能正常工作了!!!写入0x23后立马正常了。。。啊啊啊,困扰了我几天的问题,竟然因为这一小点的疏忽毁于一旦。。。
反思:由于写入时序、读取时序都是我自己写的,并且调通了,我想着反正锻炼自己,就把日期转换十进制转bcd码,读出的BCD码转为十进制,这个部分我也写了。重点:就是这部分出现的问题:因为理解的偏差,写入小时的位我理解错了!自己的转换写的挺麻烦,通过移位和位运算来实现的。而别人的是通过乘除、取余运算来实现的,看起来简洁许多。