科技爱好者博客

树莓派使用DS1302实现实时时钟功能

树莓派在设计时由于对成本和体积的考虑,没有实时时钟功能(RTC:real-time clock),树莓派在使用时只能通过网络来同步时间,如果树莓派未连接网络,则无法实现时间同步,所以本文为树莓派添加了一个DS1302时钟芯片,使得树莓派在离线的情况下从DS1302中同步时间,从而实现硬件实时时钟功能。

一、准备条件

二、DS1302介绍

DS1302 是DALLAS 公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和31 字节静态RAM ,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整。时钟操作可通过AM/PM 指示决定采用24 或12 小时格式。DS1302 与单片机之间能简单地采用同步串行的方式进行通信,仅需用到三个口线:(1)RES 复位(2)I/O 数据线(3)SCLK 串行时钟。时钟/RAM 的读/写数据以一个字节或多达31个字节的字符组方式通信。DS1302 工作时功耗很低保持数据和时钟信息时功率小于1mW。

DS1302具有以下特性:

三、DS1302模块介绍

我使用的是DS1302实时时钟模块,模块如下图。DS1302模块价格低廉,2元人民币就可以买到,并且附带一个CR2032纽扣电池,作为备份电源,当模块断电时也可以继续走时,保证时间信息不丢失。

模块是这样的:

DS1302时钟模块

模块共有5个引脚,引脚功能和与树莓派连接方式分别为:

VCC:模块的电源引脚,接树莓派的1号引脚(3.3V

GND:模块的地,接树莓派的6号引脚(Grand

CLK:SCLK 串行时钟,接树莓派wiringPi 0(物理编号:11号)

DAT:I/O 数据线,接树莓派wiringPi 1(物理编号:12号)

RST:复位引脚,接树莓派wiringPi 2(物理编号:13号)

我使用的是树莓派2B,在实际使用时没有在VCC和DAT之间接上拉电阻,发现也可以正常的读取时间信号,如果你使用其他版本的树莓派,并且发现读取的时间不正确的话,那么需要在VCC和DAT引脚之间并连接一个10K的上拉电阻。

四、硬件电路连接

DS1302实时时钟模块的引脚在上面介绍了,如果你不清楚树莓派的wiringPi编号以及gpio编号,可以参考树莓派gpio引脚对照表

DS1302和树莓派的整体连接方式如下图。

连接实物图:

树莓派与DS1302连接实物图

五、程序设计

ds1302的驱动程序可以在wiringPi的examples目录下找到,我这里修改了源文件,源文件在将linux时间设置到DS1302中时使用了UTC时间,这样与我们的CST时间有偏差,所以使用localtime将CST当地时间写入到DS1302中,修改后的文件,大家可以将原来的ds1302.c文件覆盖。

修改后的ds1302.c文件。

 


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#include <wiringPi.h>
#include <ds1302.h> 

// Register defines

#define RTC_SECS         0
#define RTC_MINS         1
#define RTC_HOURS        2
#define RTC_DATE         3
#define RTC_MONTH        4
#define RTC_DAY          5
#define RTC_YEAR         6
#define RTC_WP           7
#define RTC_TC           8
#define RTC_BM          31


static unsigned int masks [] = { 0x7F, 0x7F, 0x3F, 0x3F, 0x1F, 0x07, 0xFF } ;


/*
 * bcdToD: dToBCD:
 *      BCD decode/encode
 *********************************************************************************
 */

static int bcdToD (unsigned int byte, unsigned int mask)
{
  unsigned int b1, b2 ;
  byte &= mask ;
  b1 = byte & 0x0F ;
  b2 = ((byte >> 4) & 0x0F) * 10 ;
  return b1 + b2 ;
}

static unsigned int dToBcd (unsigned int byte)
{
  return ((byte / 10) << 4) + (byte % 10) ;
}


/*
 * ramTest:
 *      Simple test of the 31 bytes of RAM inside the DS1302 chip
 *********************************************************************************
 */

static int ramTestValues [] =
  { 0x00, 0xFF, 0xAA, 0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0xF0, 0x0F, -1 } ;

static int ramTest (void)
{
  int addr ;
  int got ;
  int i = 0 ;
  int errors = 0 ;
  int testVal ;

  printf ("DS1302 RAM TEST\n") ;

  testVal = ramTestValues [i] ;

  while (testVal != -1)
  {
    for (addr = 0 ; addr < 31 ; ++addr)
      ds1302ramWrite (addr, testVal) ;

    for (addr = 0 ; addr < 31 ; ++addr)
      if ((got = ds1302ramRead (addr)) != testVal)
      {
        printf ("DS1302 RAM Failure: Address: %2d, Expected: 0x%02X, Got: 0x%02X\n",
                addr, testVal, got) ;
        ++errors ;
      }
    testVal = ramTestValues [++i] ;
  }

  for (addr = 0 ; addr < 31 ; ++addr)
    ds1302ramWrite (addr, addr) ;

  for (addr = 0 ; addr < 31 ; ++addr) if ((got = ds1302ramRead (addr)) != addr) { printf ("DS1302 RAM Failure: Address: %2d, Expected: 0x%02X, Got: 0x%02X\n", addr, addr, got) ; ++errors ; } if (errors == 0) printf ("-- DS1302 RAM TEST: OK\n") ; else printf ("-- DS1302 RAM TEST FAILURE. %d errors.\n", errors) ; return 0 ; } /* * setLinuxClock: * Set the Linux clock from the hardware ********************************************************************************* */ static int setLinuxClock (void) { char dateTime [20] ; char command [64] ; int clock [8] ; printf ("Setting the Linux Clock from the DS1302... ") ; fflush (stdout) ; ds1302clockRead (clock) ; // [MMDDhhmm[[CC]YY][.ss]] sprintf (dateTime, "%02d%02d%02d%02d%02d%02d.%02d", bcdToD (clock [RTC_MONTH], masks [RTC_MONTH]), bcdToD (clock [RTC_DATE], masks [RTC_DATE]), bcdToD (clock [RTC_HOURS], masks [RTC_HOURS]), bcdToD (clock [RTC_MINS], masks [RTC_MINS]), 20, bcdToD (clock [RTC_YEAR], masks [RTC_YEAR]), bcdToD (clock [RTC_SECS], masks [RTC_SECS])) ; sprintf (command, "/bin/date %s", dateTime) ; system (command) ; return 0 ; } /* * setDSclock: * Set the DS1302 block from Linux time ********************************************************************************* */ static int setDSclock (void) { int clock[8] ; time_t now = time(NULL) ; struct tm* t = localtime(&now) ; int sec = t -> tm_sec ;
  int min = t -> tm_min ;
  int hour = t -> tm_hour ;
  int mday = t -> tm_mday ;
  int mon = t -> tm_mon ;
  int wday = t -> tm_wday ;
  int year = t -> tm_year ;
  printf ("Setting the clock in the DS1302 from Linux time... ") ;

  clock [ 0] = dToBcd (sec) ;   // seconds
  clock [ 1] = dToBcd (min) ;   // mins
  clock [ 2] = dToBcd (hour) ;  // hours
  clock [ 3] = dToBcd (mday) ;  // date
  clock [ 4] = dToBcd (mon + 1) ;       // months 0-11 --> 1-12
  clock [ 5] = dToBcd (wday + 1) ;      // weekdays (sun 0)
  clock [ 6] = dToBcd (year - 100) ;       // years
  clock [ 7] = 0 ;                      // W-Protect off

  ds1302clockWrite (clock) ;

  printf ("OK\n") ;

  return 0 ;
}




int main (int argc, char *argv [])
{
  int i ;
  int clock [8] ;

  wiringPiSetup () ;
  ds1302setup   (0, 1, 2) ;

  if (argc == 2)
  {
    /**/ if (strcmp (argv [1], "-slc") == 0)
      return setLinuxClock () ;
    else if (strcmp (argv [1], "-sdsc") == 0)
      return setDSclock () ;
    else if (strcmp (argv [1], "-rtest") == 0)
      return ramTest () ;
    else
    {
      printf ("Usage: ds1302 [-slc | -sdsc | -rtest]\n") ;
      return EXIT_FAILURE ;
    }
  }

  for (i = 0 ;; ++i)
  {
    printf ("%5d:  ", i) ;

    ds1302clockRead (clock) ;
    printf (" %2d:%02d:%02d",
        bcdToD (clock [2], masks [2]), bcdToD (clock [1], masks [1]), bcdToD (clock [0], masks [0])) ;

    printf (" %2d/%02d/%04d",
        bcdToD (clock [3], masks [3]), bcdToD (clock [4], masks [4]), bcdToD (clock [6], masks [6]) + 2000) ;

    printf ("\n") ;

    delay (200) ;
  }

  return 0 ;
}

六、同步时间

编译ds1302.c

将ds1302.c修改完成后,在examples目录下编译ds1302.c。

make ds1302.c

编译成功则显示:

[CC] 

ds1302.c[link]

如果出现错误,按照屏幕提示进行修改,然后再次编译,直到成功编译,会在当前目录下生成可执行文件 ds1302

测试电路是否连接正确

将硬件电路连接完毕后开始测试,首先测试电路是否连接正确。在生成的可执行文件ds1302目录下执行测试命令。

sudo ./ds1302 -rtest

如果成功则出现:

DS1302 RAM TEST

-- DS1302 RAM TEST: OK

如果测试失败,则需要检查线路连接是否正确。

将树莓派的当前时间写入

sudo ./ds1302 -sdsc

提示写入成功:

Setting the clock in the DS1302 from Linux time... OK

查看DS1302当前的时间

直接执行ds1302就可以查看DS1302实时时钟模块的当前时间,命令和输出如下:

pi@raspberrypi:~/wiringPi/examples $ sudo ./ds1302 
 0: 15:38:38 12/07/2016
 1: 15:38:38 12/07/2016
 2: 15:38:38 12/07/2016
 3: 15:38:38 12/07/2016
 4: 15:38:39 12/07/2016
 5: 15:38:39 12/07/2016
 6: 15:38:39 12/07/2016
 7: 15:38:39 12/07/2016
 8: 15:38:39 12/07/2016
 9: 15:38:40 12/07/2016
 10: 15:38:40 12/07/2016
 11: 15:38:40 12/07/2016
 12: 15:38:40 12/07/2016

可以看到已经将树莓派的系统时间写入到了DS1302实时时钟模块中了,这样在以后树莓派未连接网络时,就可以从DS1302实时时钟中读取时间同步。

将树莓派时间与DS1302时钟模块的时间同步

在执行ds1302文件时,在后面加入参数-slc 可以将DS1302模块的时间写入树莓派。示例如下:

pi@raspberrypi:~/wiringPi/examples $ sudo ./ds1302 -slc
Setting the Linux Clock from the DS1302... 2016年 07月 12日 星期二 15:42:29 CST

七、开机同步时间

将树莓派的时间与网络同步后,就可以写入到DS1302中,这样DS1302具有准确的当前时间,就算掉电也不会丢失,在树莓派离线时,可以在每次开机时将DS1302的时间写入到树莓派中,从而使得树莓派实现了实时时钟的功能。

获得可执行文件ds1302的目录

pi@raspberrypi:~/wiringPi/examples $ pwd
/home/pi/wiringPi/examples

然后修改 /etc/rc.local 文件,在exit 0前面添加时间同步命令,这样在树莓派开机时就会自动将DS1302的时间同步到树莓派上。添加以下命令:

sudo /home/pi/wiringPi/examples/ds1302 -slc

保存后退出。我们将树莓派断网,然后树莓派关机,过一会开机,使用date命令读取时间,可以看到树莓派的走时和当前时间一致,说明时间自动同步成功!

原创文章,转载请注明: 转载自科技爱好者博客

本文链接地址: 树莓派使用DS1302实现实时时钟功能 (https://www.tujing.site/1995)

如果博客对您有帮助,请给我 赞助


退出移动版