【Arduino Uno】使用DS1302时钟模块(不调用库)

目标:

        使用Arduino Uno开发板,在没有调用DS1302.h的头文件下对DS1302时钟模块进行时分秒的读写。

一、DS1302介绍

        DS1302是美国DALLAS推出的一款高性能、低功耗的日历时钟芯片。DS1302是一种串行接口的实时时钟,芯片内部具有可编程的日历时钟和31个字节的静态RAM,日历时钟可以自动进行闰年补偿,计时准确,接口简单,使用方便,工作电压范围宽(2.5~5.5V),芯片自身还具有对备用电池进行涓流充电功能,可有效延长备用电池的使用寿命。

        DS1302用于数据记录,能实现数据与该数据出现的时间同时记录,因此广泛应用于测量系统中。

        这里使用现成的DS1302模块,该模块带有一个纽扣电池,没有纽扣电池的不能断电计时:

c6185e9c6a6549d7b14c5cfa4186c5db.png4131ef6a301b4cd0a4a05dad46e7d812.png

二、 DS1302芯片的引脚

  • VCC2(VCC):接Arduino的5V引脚,电源正极;
  • VCC1:接纽扣电池的正极;一般来说VCC2引脚的电压小于VCC1引脚的电压,则模块自动选择VCC1纽扣电池进行供电;
  • GND:接Arduino的GND引脚,Arduino没有接的情况下,则内部与纽扣电池的负极导通;
  • X1、X2:接32768Hz的晶振,X1流入DS1302,X2流出DS1302;
  • CE(RST):使能端,CE为高时允许读写DS1302数据,为低时禁止读写。Arduino对这个引脚输出高电平1,则可以对DS1302寄存器进行读写;反之不能进行读写操作;
  • IO(DAT):双向输入引脚,这个引脚可以输入输出电平,对DS1302进行读写寄存器操作的引脚;
  • SCLK(CLK):串行时钟输入端,控制DS1302中寄存器的数据输入与输出;

三、接线表

Arduino UnoDS1302模块
5VVCC
GNDGND
2(可随机定义)RST
3(可随机定义)DAT
4(可随机定义)CLK

29f52e345f55445da9e3611974e25265.png

四、DS1302寄存器

        DS1302芯片中有31个静态RAM寄存器,以下是我们常用的“年月日时分秒”以及写保护寄存器

读寄存器

指令

写寄存器

指令

Bit7Bit6Bit5Bit4Bit3Bit2Bit1Bit0范围
0x810x80

CH

暂停

10秒00-59
0x830x8210分00-59
0x850x84

12/24

0

   10

AM/PM

1-12/0-24
0x870x860010日1-31
0x890x8800010月1-12
0x8B0x8A00000周(周日为1)1-7
0x8D0x8C10年1年00-99
0x8F0x8E

  WP

写保护

0000000——

   这里我们用到:

  • 秒寄存器:写0x80、读0x81;Bit7定义为时钟暂停标志(CH)。当该位置为1时,时钟振荡器停止,DS1302处于低功耗状态;当该位置为0时,时钟开始运行。
  • 分寄存器:写0x82、读0x83;
  •  时寄存器:写0x84、读0x85;Bit7用于定义DS1302是运行于12小时模式还是24小时模式,当为1时,选择12小时模式,此时BIT5为AM/PM位,在24小时模式时此位为小时数据位。
  • 写保护寄存器:写0x8E、读0x8F;Bit7是写保护位(WP),其它7位均为0。在任何对时钟或RAM读写操作之前,WP位必须为0。当WP位为1时,不能对任何时钟日历寄存器或RAM进行写操作。

五、DS1302读写数据时序图

5.1 读数据

2df45174a54442efa459d21d6605d798.png

        在读数据之前,我们要让使能CE引脚置为高电平,也就是要上使能,才能进行读写操作,在Arduino中可以使用digitalWrite(RST,1),RST就是CE,就相当于把CE引脚拉高,可以开始读写数据。

        在读数据的操作中,首先我们要明确我们要读的是什么数据(时分秒),这个数据存储在哪个寄存器里面,而读写寄存器的16进制指令是什么,这个16进制读写寄存器的指令就是我们所需要带入的参数。

        我们要创建一个uchar DS1302_Read_Data(uchar cmd)函数,只要代入对应的读写寄存器的指令,就能返回指定寄存器中的值。

        重点在于数据的发送和接收,在Arduino上,如果想要获取秒寄存器上的值,我们需要将0x81写入到DS1302,这样DS1302才知道我们要读秒寄存器,首先用pinMode定义DAT引脚为输出模式。DAT引脚只能输出高电平1或低电平0,所以要将16进制的地址发送给DS1302,我们要把16进制的地址转化成二进制(0和1)的形式:

        0x81 = 0b10000001

        但实际在程序中,我们不需要刻意去转化成二进制的形式,写成二进制为了更好理解一些。

        从上面的转化关系能够看出,一位16进制数等于4位二进制数,那么2位16进制数就等于8位二进制数,也就是一个字节的数据大小。

        转化成二进制数后,数据只有0和1组成,这时候就可以通过引脚的高电平和低电平来传输数据了,因为一次只能发送一个位(读数据也是一样),并且读和写数据,都是从最低位开始的,0b10000001,从最右边开始一位一位发。一个字节大小的指令,要发8次才能完成。

        我们可以写一个for循环,来完成8个位的数据发送,在发送之前需要对数据进行位分解,也就是把一大串的二进制数分解成一个个独立的位,然后赋值给一个bool变量,为什么要选择bool变量呢?是因为分解出来的位数值要直接代入digitlaWrite函数中,该函数的第二个参数要求的就是bool类型的参数。如果写成digitalWrtie(DAT,100);这样是会报错的。

        将数值和0x01进行按位与运算就可以得到独立的第一位数值,这时候再把数值右移一位,再次与0x01按位与运算,就可以得到第二位的数值。依次类推就可以将一个字节的数据都发送出去了。

        这时候把读指令发送出去后,我们就要接收DS1302返回给我们的数据了,这时候要重新定义DAT引脚模式,pinMode(DAT,INPUT);,变成开始读数据。原理也是类似,从最低位开始读,读过来之后我们要放在一个字节中的最高位,然后第二次读的时候右移一位,再把读到的第二位数填入到最高位中。这里用的按位或运算,自定义了data变量,一个字节大小,然后这个变量与0x80按位或运算,可以将读取到的每个位拼凑成完整的数值。这里定义了data_bit用来存储读到的每一个位数据。for循环读取8次,然后拼凑而成data就可以被函数返回了。详细说明可见程序内代码注释。

//DS1302读数据
//cmd 读寄存器指令
uchar DS1302_Read_Data(uchar cmd)
{
  //确保时序一开始都是从低电平开始的
  digitalWrite(RST,0);
  digitalWrite(CLK,0);
  //开始写时序操作(上使能)
  digitalWrite(RST,1);
  //写cmd数据
  //定义DAT引脚位输出模式,用于写数据
  pinMode(DAT,OUTPUT);
  for(uchar i=0; i<8; i++)
  {
    bool cmd_bit = 0;           //bool类型的数据只有0和1,用于带入到digitalWrite中
    digitalWrite(CLK,0);        //再一次确保CLK引脚是低电平的
    cmd_bit = cmd & 0x01;       //cmd数据位分解,用按位与的方式,提取出每一位二进制数
    digitalWrite(DAT,cmd_bit);  //将DAT引脚置高,相当于发了1,置低发0;
    digitalWrite(CLK,1);        //CLK引脚置高,DAT的位数据才能传入到DS1302
    cmd >>= 1;                  //cmd数据值右移一位
  }
  //读data数据
  //定义DAT引脚位输入模式,用于读数据
  pinMode(DAT,INPUT);
  uchar data = 0;  //定义data变量,用来存储读到的数据
  for(uchar i=0; i<8; i++)
  {
    digitalWrite(CLK,0);      //CLK置低,产生下降沿才能读数据,这一句执行了,下面就可以把DAT读进来
    data >>= 1;              //data数据值右移一位,注意!!这个右移一定不能放在下面if块中,或者if语句之后。因为最后一次循环结束后,还会再右移一位,这时候数据就是错误的。还要注意的是这里接收的值是bcd码,在主程序中再从bcd码转成普通数据,如果这时候这条指令放在if后面,输出的数据也就会奇怪了;就好比分钟设了25分,结果输出是12分,这就是data>>=1位置放错而导致的一系列错误
    bool data_bit = digitalRead(DAT);
    if(data_bit)      //判断DAT有没有获取到高电平,也就是数据1
      data = data | 0x80;    //数据是从最低位开始读的,最低位最后是在最右边。所以如果最低位为高电平,那么会被按位或运算0x80=0b10000000,把最高位置1,后面再右移1位,最后8个位读完,最低位也就到了最右边了;如果这个判断不执行,DAT就是0,那么直接跳过判断右移,相当于把最高位置0
    digitalWrite(CLK,1);     //CLK置1,回到高电平,以便下一次循环产生下降沿而读取数据
  }
  return data;  //返回读到的数据
}

5.2 写数据

3b0ec424b8dc4e71bcc245e8be5caf69.png

原理跟读数据是一样的,这里不再赘述。

//DS1302写数据
//cmd 写寄存器指令
//data 要写入的数据
void DS1302_Write_Data(uchar cmd, uchar data)
{
  //确保时序一开始都是从低电平开始的
  digitalWrite(RST,0);
  digitalWrite(CLK,0);
  //开始执行写时序操作
  digitalWrite(RST,1);
  //定义DAT引脚为输出模式,用于写数据
  pinMode(DAT,OUTPUT);
  //写cmd数据
  for(uchar i=0; i<8; i++)  //每次写一位,循环8次,写一个字节的数据
  {
    bool cmd_bit = 0;       //定义一个bool变量用来存储分解后的位数据
    digitalWrite(CLK,0);    //CLK引脚设置电平位0,用以后面置1时能够产生上升沿
    cmd_bit = cmd & 0x01;   //cmd数据位分解,用按位与的方式,提取出每一位二进制数
    digitalWrite(DAT,cmd_bit); //从cmd数据的最低位开始发送
    digitalWrite(CLK,1);         //CLK置高电平,DAT上的数据0或1就发送出去了
    cmd >>= 1;               //cmd数据值右移一位
  }
  //写data数据
  for(uchar i=0; i<8; i++)   //每次写一位,循环8次,写一个字节的数据
  {
    bool data_bit = 0;          //定义一个bool变量用来存储分解后的位数据
    digitalWrite(CLK,0);        //CLK引脚设置电平位0,用以后面置1时能够产生上升沿
    data_bit = data & 0x01;     //cmd数据位分解,用按位与的方式,提取出每一位二进制数
    digitalWrite(DAT,data_bit); //从cmd数据的最低位开始发送
    digitalWrite(CLK,1);        //CLK置高电平,DAT上的数据0或1就发送出去了
    data >>= 1;                 //data数据值右移一位
  }
}

六、BCD数据转换

        这时候我们已经定义好了读写数据的函数了,还有需要注意的是,DS1302除了指令外,读写的数据都是BCD的数据。

        假设我要写入一个15的数据,那么这个数据要先被转换成BCD码的形式:

        先把这个十进制15,拆成两位1 5,然后拆开的每一位十进制数都用4位二进制表示

        0001 0101

        这时候得到了一个8位二进制数,然后再转化成十进制就是:21

        可以知道这普通的十进制和BCD码还是不一样的,这就有点类似于16进制转10进制。所以我们还需要定义BCD码的转化函数的。

6.1 数据转BCD

计算方法:

data = 15;

data1 = 15/10 = 1

data2 = 15%10 = 5

bcd = data1*16 + data2  =  1*16 + 5 = 21


//数据转BCD码
//十进制转BCD码,就相当于把这个十进制数看成十六进制,然后再转换成二进制数,十六进制的10相当于十进制的16;所以后面求出来的商要乘16
uchar data_bcd(uchar data)
{
  uchar data1 = data/10;
  uchar data2 = data%10;
  uchar bcd = data1*16 + data2;
  return bcd;
}

6.2 BCD转数据

计算方法:

bcd = 21;

bcd1 = 21/16 = 1

bcd2 = 21%16 = 5

data = bcd1*16 + bcd2  =  1*10 + 5 = 15

//BCD码转数据
//十进制转BCD码,就相当于把这个十进制数看成十六进制,然后再转换成二进制数,十六进制的10相当于十进制的16;所以后面求出来的商要乘16
uchar bcd_data(uchar bcd)
{
  uchar bcd1 = bcd/16;
  uchar bcd2 = bcd%16;
  uchar data = bcd1*10 + bcd2;
  return data;
}

七、设置时分秒,读取时分秒

        时分秒寄存器上一旦给了数值,只要没有给暂停指令,那么时间就会自己走起来。

        不管在设置时间还是读取时间,都需要先进行关闭写保护,设置好时间或者读取完时间后,在恢复写保护的状态。

/*=============================================*/
typedef  unsigned  char uchar;   //将unsigned char数据类型简写成uchar
/*=============================================*/
int RST = 2;  //引脚名称定义
int DAT = 3;  //引脚名称定义
int CLK = 4;  //引脚名称定义
/*=============================================*/
//DS1302读数据
//cmd 读寄存器指令
uchar DS1302_Read_Data(uchar cmd)
{
  //确保时序一开始都是从低电平开始的
  digitalWrite(RST,0);
  digitalWrite(CLK,0);
  //开始写时序操作(上使能)
  digitalWrite(RST,1);
  //写cmd数据
  //定义DAT引脚位输出模式,用于写数据
  pinMode(DAT,OUTPUT);
  for(uchar i=0; i<8; i++)
  {
    bool cmd_bit = 0;           //bool类型的数据只有0和1,用于带入到digitalWrite中
    digitalWrite(CLK,0);        //再一次确保CLK引脚是低电平的
    cmd_bit = cmd & 0x01;       //cmd数据位分解,用按位与的方式,提取出每一位二进制数
    digitalWrite(DAT,cmd_bit);  //将DAT引脚置高,相当于发了1,置低发0;
    digitalWrite(CLK,1);        //CLK引脚置高,DAT的位数据才能传入到DS1302
    cmd >>= 1;                  //cmd数据值右移一位
  }
  //读data数据
  //定义DAT引脚位输入模式,用于读数据
  pinMode(DAT,INPUT);
  uchar data = 0;  //定义data变量,用来存储读到的数据
  for(uchar i=0; i<8; i++)
  {
    digitalWrite(CLK,0);      //CLK置低,产生下降沿才能读数据,这一句执行了,下面就可以把DAT读进来
    data >>= 1;              //data数据值右移一位,注意!!这个右移一定不能放在下面if块中,或者if语句之后。因为最后一次循环结束后,还会再右移一位,这时候数据就是错误的。还要注意的是这里接收的值是bcd码,在主程序中再从bcd码转成普通数据,如果这时候这条指令放在if后面,输出的数据也就会奇怪了;就好比分钟设了25分,结果输出是12分,这就是data>>=1位置放错而导致的一系列错误
    bool data_bit = digitalRead(DAT);
    if(data_bit)      //判断DAT有没有获取到高电平,也就是数据1
      data = data | 0x80;    //数据是从最低位开始读的,最低位最后是在最右边。所以如果最低位为高电平,那么会被按位或运算0x80=0b10000000,把最高位置1,后面再右移1位,最后8个位读完,最低位也就到了最右边了;如果这个判断不执行,DAT就是0,那么直接跳过判断右移,相当于把最高位置0
    digitalWrite(CLK,1);     //CLK置1,回到高电平,以便下一次循环产生下降沿而读取数据
  }
  return data;  //返回读到的数据
}

//DS1302写数据
//cmd 写寄存器指令
//data 要写入的数据
void DS1302_Write_Data(uchar cmd, uchar data)
{
  //确保时序一开始都是从低电平开始的
  digitalWrite(RST,0);
  digitalWrite(CLK,0);
  //开始执行写时序操作
  digitalWrite(RST,1);
  //定义DAT引脚为输出模式,用于写数据
  pinMode(DAT,OUTPUT);
  //写cmd数据
  for(uchar i=0; i<8; i++)  //每次写一位,循环8次,写一个字节的数据
  {
    bool cmd_bit = 0;       //定义一个bool变量用来存储分解后的位数据
    digitalWrite(CLK,0);    //CLK引脚设置电平位0,用以后面置1时能够产生上升沿
    cmd_bit = cmd & 0x01;   //cmd数据位分解,用按位与的方式,提取出每一位二进制数
    digitalWrite(DAT,cmd_bit); //从cmd数据的最低位开始发送
    digitalWrite(CLK,1);         //CLK置高电平,DAT上的数据0或1就发送出去了
    cmd >>= 1;               //cmd数据值右移一位
  }
  //写data数据
  for(uchar i=0; i<8; i++)   //每次写一位,循环8次,写一个字节的数据
  {
    bool data_bit = 0;          //定义一个bool变量用来存储分解后的位数据
    digitalWrite(CLK,0);        //CLK引脚设置电平位0,用以后面置1时能够产生上升沿
    data_bit = data & 0x01;     //cmd数据位分解,用按位与的方式,提取出每一位二进制数
    digitalWrite(DAT,data_bit); //从cmd数据的最低位开始发送
    digitalWrite(CLK,1);        //CLK置高电平,DAT上的数据0或1就发送出去了
    data >>= 1;                 //data数据值右移一位
  }
}

//数据转BCD码
//十进制转BCD码,就相当于把这个十进制数看成十六进制,然后再转换成二进制数,十六进制的10相当于十进制的16;所以后面求出来的商要乘16
uchar data_bcd(uchar data)
{
  uchar data1 = data/10;
  uchar data2 = data%10;
  uchar bcd = data1*16 + data2;
  return bcd;
}

//BCD码转数据
//十进制转BCD码,就相当于把这个十进制数看成十六进制,然后再转换成二进制数,十六进制的10相当于十进制的16;所以后面求出来的商要乘16
uchar bcd_data(uchar bcd)
{
  uchar bcd1 = bcd/16;
  uchar bcd2 = bcd%16;
  uchar data = bcd1*10 + bcd2;
  return data;
}


/*=============================================*/


void setup()
{
  Serial.begin(9600);  //串口初始化,波特率为9600
  pinMode(RST,OUTPUT);  //引脚初始化,RST为输出模式
  pinMode(DAT,OUTPUT);  //引脚初始化,DAT为输出模式
  pinMode(CLK,OUTPUT);  //引脚初始化,CLK为输出模式

  //设置DS1302时钟日历寄存器
  //关闭写保护,0x8e是DS1302中寄存器的写保护寄存器地址,置0,则关闭写保护;读的是0x8f
  DS1302_Write_Data(0x8e,0);
  
  //写秒,秒的写寄存器是0x80,秒的读寄存器是0x81;由于DS1302的写入的数据都是BCD码,需要先用data_bcd()函数对普通的十进制数进行处理,返回后的值再代入 DS1302_Write_Data 函数中
  DS1302_Write_Data(0x80,data_bcd(30));   //30秒
  //写分钟,分钟的写寄存器是0x82,分钟的读寄存器是0x83;由于DS1302的写入的数据都是BCD码,需要先用data_bcd()函数对普通的十进制数进行处理,返回后的值再代入 DS1302_Write_Data 函数中
  DS1302_Write_Data(0x82,data_bcd(15));  //15分
  //写小时,小时的写寄存器是0x84,小时的读寄存器是0x85;由于DS1302的写入的数据都是BCD码,需要先用data_bcd()函数对普通的十进制数进行处理,返回后的值再代入 DS1302_Write_Data 函数中
  DS1302_Write_Data(0x84,data_bcd(19));  //19时

  //开启写保护,0x8e是DS1302中寄存器的写保护寄存器地址,最高位置1,也就是0x80,则开启写保护;读的是0x8f
  DS1302_Write_Data(0x8e,0x80);
}

void loop()
{
  //定义 秒、分、时 三个变量
  uchar second, minute, hour;
  while(1)                                        //无限循环
  {
    DS1302_Write_Data(0x8e,0);                    //关闭写保护
    second = bcd_data(DS1302_Read_Data(0x81));    //读取当前秒,DS1302_Read_Data(0x81)返回的值是BCD码,要转化成普通的十进制数才看得懂,外面再套上bcd_data函数
    minute = bcd_data(DS1302_Read_Data(0x83));    //读取当前分,DS1302_Read_Data(0x83)返回的值是BCD码,要转化成普通的十进制数才看得懂,外面再套上bcd_data函数
    hour = bcd_data(DS1302_Read_Data(0x85));      //读取当前时,DS1302_Read_Data(0x85)返回的值是BCD码,要转化成普通的十进制数才看得懂,外面再套上bcd_data函数
    DS1302_Write_Data(0x8e,0x80);                 //打开写保护
    Serial.print(hour);                          //打印当前秒
    Serial.print(":");                           //打印分隔符
    Serial.print(minute);                        //打印当前分
    Serial.print(":");                           //打印分隔符
    Serial.println(second);                      //打印当前时,并且println比print多了'\n'换行符,
    delay(1000);                                  //延时1秒
  }
}

        在Arduino IDE上的串口监视器上就能看见设置的时间,并且时间在变化;

71a2391584bc4a76a32d368e28eeee6c.png

        需要注意的是,串口监视器关闭后重新打开,就相当于是把整个程序又重新执行了一遍,如果代码里有设置时间的这一部分,那么就相当于时间不会没有被断电保持。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值