DS1302驱动补充——突发模式
几个月前我发布了一篇关于讲解DS1302驱动方式的文章,其中无论是写数据还是读取数据都采用的是单字节传输方式。
然而在项目中实际使用时却遇到了一个问题——跳时,分析这个bug出现的原因如下。
在实际项目中,会每隔30秒从DS1302获取一下时间,这个获取时间的函数内容为
void DS1302_Read_Time()
{
if (SysGetLapseTick(g_rtctick) > 30000)
{
SysSetCurrentTick(&g_rtctick);
TimeData_t.second = DS1302_Read_rig(0x81); //读秒
TimeData_t.minute = DS1302_Read_rig(0x83); //读分
TimeData_t.hour = DS1302_Read_rig(0x85); //读时
TimeData_t.day = DS1302_Read_rig(0x87); //读日
TimeData_t.month = DS1302_Read_rig(0x89); //读月
TimeData_t.week = DS1302_Read_rig(0x8B); //读星期
TimeData_t.year = DS1302_Read_rig(0x8D); //读年
}
}
即依次读取DS1302各个寄存器的值,并将读到的值赋值给结构体。
这看起来没有什么问题,只要时序不出差错总能读到正确的时间,然而实际上由于主芯片和DS1302并非一个串行系统,即主芯片在读取数据时,DS1302也是在工作的。
这将导致一个问题,假设主芯片先读取到秒寄存器的数据为59,当它再去读取分寄存器时,由于DS1302一直时处于工作状态,这个时候时间可能已经从59s变成了1min,即主芯片读到的分寄存器的值为1.
这样就导致了主芯片认为现在的时间为1min59s,然而实际的DS1302时间却为1min00s。
这里只是以秒分为例,实际上其他寄存器也有这样的问题,当处于一个临界时间时,主芯片读取到的时间和DS1302的时间将会有巨大的偏差,直到第二次读取时间才会被矫正。
虽然由于主芯片超高的运行速度,出现这个问题的概率极小,我在每秒读取一次时间的情况下测试了24小时,共出现过一次这种现象,但是对某些项目来说,时间出错可能会带来很大问题。
为了彻底杜绝这个现象,建议采用突发模式/多字节方式来读取数据。
突发模式描述:
如下是数据手册中,对突发模式的描述
乍一看有些难以理解,我们一句一句分析找出想要的内容:
如图是主机向DS1302发送命令的格式,
结合这个我们再来看
通过对31(十进制)位地址寻址(地址/命令位于1至5=逻辑1),可以把时钟/日历或RAM寄存器规定为多字节方式
也就是说,如果我们想要实现多字节方式传输,需要通过31这个地址。
这里的31为十进制表达,化为二进制则是0001 1111,不过DS1302规定从bit1到bit5才是地址即A4-A0的位置,也就是说我们需要把他往前移移。
现在命令为 1x11 111x,其中x是未知,需要从别的语句中获取它们的含义。
位6规定时钟或RAM
根据上面的图片可以看到,bit6为1时操控的是RAM,为0时操控时钟,由于我们是要操作时钟,所以将bit6设置为0 ——1011 111x
位0规定读或写
这里我们需要读取时间,所以bit0 为1 ———1011 1111(0XBF)
知道了要发送的指令后,剩下的就是水到渠成的事,多字节的传输由地址0开始,即从秒开始发送,我们需要定义一个数组来接收这些数据。
代码
void ds1302_burst_read(uint8_t *_buf)
{
uint8_t i, j, suf, temp, value;
temp = 0xBF;
CE_H; // RET=1;
// 写地址
DS1302_DATAOUT_Init(); // 配置IO为输出,恢复正常状态
for (i = 0; i < 8; i++)
{
if (temp & 0x01)
{
DATA_H;
} // IO=1;
else
{
DATA_L;
} // IO=0;
temp = temp >> 1;
Delay_us(5);
SCLK_H; // CLK=1;
Delay_us(5);
if (i == 7)
{
DS1302_DATAINPUT_Init(); // 配置IO为输入
}
SCLK_L; // CLK=0;
}
// 读数据
for (j = 0; j < 8; j++)
{
for (i = 0; i < 8; i++)
{
Delay_us(5);
suf = suf >> 1; // 读数据变量
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12)) // IO=1
{
suf = suf | 0x80;
}
else // IO=0
{
suf = suf & 0x7f;
}
SCLK_H; // CLK=1;
Delay_us(5);
SCLK_L; // CLK=0;
}
_buf[j] = suf;
suf = 0;
}
CE_L; // RET=0;
DS1302_DATAOUT_Init(); // 配置IO为输出,恢复正常状态
}
uint8_t timedata[8]={0};
uint8_t hextodec(uint8_t _hex)
{
uint8_t mm, nn, value;
// 数据处理转化十进制
mm = _hex / 16;
nn = _hex % 16;
value = mm * 10 + nn;
return value;
}
void DS1302_Read_Time()
{
uint32_t temp;
static uint32_t pretemp;
static uint8_t flag = 0;
if (SysGetLapseTick(g_rtctick) > 1000)
{
SysSetCurrentTick(&g_rtctick);
ds1302_burst_read(timedata);
TimeData_t.second = hextodec(timedata[0]); // 读秒
TimeData_t.minute = hextodec(timedata[1]); // 读分
TimeData_t.hour = hextodec(timedata[2]); // 读时
TimeData_t.day = hextodec(timedata[3]); // 读日
TimeData_t.month = hextodec(timedata[4]); // 读月
TimeData_t.week = hextodec(timedata[5]); // 读星期
TimeData_t.year = hextodec(timedata[6]); // 读年
}
}