[原创] 为树莓派添加 DS1302 实时时钟(硬件时钟)/ Add a DS1302 RTC for RPi

『1』软硬件环境
本文适用于:
Raspberry Pi:Model B/B+(已测),其他型号理论上也可以,只不过可能要修改一下后面说到的shell脚本中的端口号
OS:Arch Linux ARM

『2』实时时钟与树莓派的关系
树莓派为了节约成本以及减小体积,没有板载的实时时钟(real-time clockRTC),或者叫硬件时钟,因此,如果你没有配置过树莓派自动从网络同步时间的话,或者就算你配置好了自动从网络同步时间、但没有网络可用的话,那么,你设置好的系统时间,在重启树莓派之后就会丢失。而我们家用的电脑之所以在开机之后时间仍然正确,是因为电脑主板上有实时时钟。

因此,在某些场合下,如果树莓派无法联网,但是它上面运行的程序又和时间紧密相关,要求系统时间是正确的,在这种情况下,我们就可以为树莓派添加一个外部的RTC,使之重启之后也能保持时间正确。
其原理是:

  • 先将树莓派的系统时间通过网络对时,使系统时间正确
  • 首次使用RTC时,将系统时间写入RTC
  • 树莓派断网重启时,通过一个开机自动启动的程序,从RTC中读取出里面存储的时间,写回到树莓派的系统中

文章来源:http://www.codelast.com/
『3』实时时钟型号及硬件接线方法
(1)RTC概述
我使用的RTC是成本低廉、市场上遍地都是的DS1302模块,淘宝价一般是5元左右。之所以用DS1302最重要的原因不是因为它便宜,而是WiringPi已经自带了DS1302的驱动程序,稍微修改一下就可以用起来了。
DS1302模块自带一块CR2032纽扣电池,电池使用时间至少应该有1年以上。先来个特写:
DS1302 module
DS1302 module

(2)RTC接线说明
DS1302模块一共有5个外部接口:
VCC:接树莓派的 3.3V 输出
GND:接树莓派的 Ground(地)
CLK:接树莓派的 SCLK
DAT:接树莓派的 SDA0
RST:接树莓派的 CE0
文章来源:http://www.codelast.com/
这里有一个巨大的陷阱:网上某些文章只告诉你要按照上面的方法连接RTC和树莓派,却没有告诉你一个非常关键的事情:在RTC的VCCDAT两个针脚之间还要接一个上拉电阻!如果没有这个上拉电阻的话,你会发现用程序从RTC读出的时间非常不稳定,各种错乱。
我们知道,上拉电阻一般是指一端接电源,一端接芯片管脚的电阻,其作用是防止输入端的“floating”(浮动)状态,使电平可被稳定检测。
在这里,上拉电阻可以用10K~30K的,我试了10K20K的,RTC都可以稳定运行。
下图简单地展示了应该怎么接这个上拉电阻:
DS1302 pull-up resistor
文章来源:http://www.codelast.com/
另外需要提醒大家的是,尽管商家提供的这款DS1302模块的说明里写着VCC可以接5V,但是我劝大家不要这样玩,我亲测“毁”了一次树莓派(Model B+)——
VCC接5V的时候,我试验过,在Model B+上不接上拉电阻也可以 rtest 成功(Model B则不行),并且可以将系统时间正确写入RTC,并且还可以从RTC读出正确的时间,但是一旦你尝试把RTC中的时间写入系统时间,树莓派马上挂掉,任何命令都无法再执行,提示 ­bash: /usr/bin/ls: Input/output error。我觉得这不是偶然现象,而是可以复现的,只不过我不想再“毁”一次树莓派了。
所以,大家务必要把VCC接3.3V,并且接10K的上拉电阻。

(3)整体硬件接线图
下面展示一下RTC+树莓派的整体硬件接线电路图,先是Model B+的:
DS1302 with RPi Model B+
文章来源:http://www.codelast.com/
再是Model B的:
DS1302 with RPi Model B
文章来源:http://www.codelast.com/
『4』实时时钟控制软件使用说明
在接好了线之后,我们就要修改一下WiringPi自带的DS1302驱动程序来符合我们的要求了,文件位置在 WiringPi 安装包的 examples/ds1302.c,代码内容挺多,不需要仔细研究。如果你连看都不想看,那么可以直接使用我修改过的版本:https://github.com/codelast/raspberry-pi/tree/master/real-time-clock
使用方法举例:

./ds1302.sh:打印出 DS1302 模块中存储的时间
./ds1302.sh rtest:测试 DS1302 模块是否工作正常
./ds1302.sh sdsc:将Linux系统时间写入 DS1302 模块中
./ds1302.sh slc:将 DS1302 模块中的时间写入Linux系统
./ds1302.sh slc b:与上面命令效果相同,但只适用于树莓派model B(如果不提供第2个参数的话默认是model B+)

ds1302.sh脚本的作用就是编译ds1302.c并运行它。由于Model B和B+的某些WiringPi端口号不同,因此,ds1302.sh中根据不同的树莓派型号,分别设置了不同的端口号,并传给DS1302程序。

写本文的时候,我手上只有B和B+,所以无法对其他的型号进行测试,如果你要支持其他的型号,对ds1302.sh稍加修改即可。
ds1302.sh脚本中定义了三个变量:、

SCLK_PORT
SDA0_PORT
CE0_PORT

它们指的是WiringPi编号方式下,树莓派的SCLK、SDA0、CE0这三个针脚对应的GPIO端口号(GPIO端口分布图请参考这里)。RTC上的CLKDTARST三个针脚分别与这这三者一一对应。

其中,SCLK、CE0的WiringPi端口号对树莓派Model B和B+都是相同的,分别是14和10,但SDA0不同(Model B为8,B+为30)。
文章来源:http://www.codelast.com/
『5』实时时钟控制软件部分代码解析
这一段代码对不同的参数分别执行不同的逻辑:

if (strcmp(param, "-slc") == 0) {
  return setLinuxClock();
} else if (strcmp(param, "-sdsc") == 0) {
  return setDSclock();
} else if (strcmp(param, "-rtest") == 0) {
  return ramTest();
} else {
  printUsage();
  return EXIT_FAILURE ;
}

其中,setLinuxClock()用于将RTC时间写入系统,setDSclock()用于将系统时间写入RTC,ramTest()用于对DS1302模块进行测试。所以,最重要的逻辑就是在这3个函数里了。当然,可以不必对它们进行仔细的研究。
文章来源:http://www.codelast.com/
下面再来看看对WiringPi自带的原版ds1302.c,主要修改了哪些地方:
(1)DS1302端口设置代码修改

ds1302setup(012);

改为由外部传入参数。ds1302setup() 函数用于设置DS1302模块参数。三个参数依次为 SCLK、SDA0、CE0 三个针脚的WiringPi端口号。

(2)RTC时间设置函数修改

static int setDSclock(void)
{
  int clock[8];
  time_t now = time(NULL);
  struct tmt = localtime(&now);

  int second = t->tm_sec;        // seconds after the minute(0~61)                                                                                           
  int minute = t->tm_min;        // minutes after the hour(0~59)                                                                                             
  int hour = t->tm_hour;         // hours since midnight(0~23)                                                                                               
  int day = t->tm_mday;          // day of the month(1~31)                                                                                                   
  int month = t->tm_mon + 1;     // months since January(0~11 --> 1~12)                                                                                      
  int weekDay = t->tm_wday + 1;  // days since Sunday(0~6 --> 1~7)                                                                                           
  int year = t->tm_year - 100;   // years                                                                                                                    

  printf("Setting the clock in the DS1302 from Linux time [%d-%d-%d %d:%d:%d]...", year, month, day, hour, minute, second);

  clock[0] = dToBcd(second);    // seconds                                                                                                                   
  clock[1] = dToBcd(minute);    // mins                                                                                                                      
  clock[2] = dToBcd(hour);      // hours                                                                                                                     
  clock[3] = dToBcd(day);       // date                                                                                                                      
  clock[4] = dToBcd(month);     // months 0-11 --> 1-12                                                                                                      
  clock[5] = dToBcd(weekDay);   // weekdays (sun 0)                                                                                                          
  clock[6] = dToBcd(year);      // years                                                                                                                     
  clock[7] = 0;                 // W-Protect off

  //...
}

上面这一段代码先获取当前时间,然后写入RTC(部分省略),WiringPi的原版代码是:

static int setDSclock (void)
{
  struct tm t ;
  time_t now ;
  int clock [8] ;

  printf ("Setting the clock in the DS1302 from Linux time... ") ;

  now = time (NULL) ;
  gmtime_r (&now, &t) ;

  clock [ 0] = dToBcd (t.tm_sec) ;      // seconds                                                                                                           
  clock [ 1] = dToBcd (t.tm_min) ;      // mins                                                                                                              
  clock [ 2] = dToBcd (t.tm_hour) ;     // hours                                                                                                             
  clock [ 3] = dToBcd (t.tm_mday) ;     // date                                                                                                              
  clock [ 4] = dToBcd (t.tm_mon + 1) ;  // months 0-11 --> 1-12                                                                                              
  clock [ 5] = dToBcd (t.tm_wday + 1) ; // weekdays (sun 0)                                                                                                  
  clock [ 6] = dToBcd (t.tm_year - 100) ;       // years                                                                                                     
  clock [ 7] = 0 ;                      // W-Protect off

  //...
}

可见,WiringPi的原版代码使用的是UTC时间,而不是本地时间,这样的话,set到RTC里的时间就不对了。所以,修改的代码使用了localtime()来获取本地时间。
文章来源:http://www.codelast.com/
『6』配置树莓派的系统
在使用RTC的程序之前,首先要配置树莓派系统的时区,否则RTC将无法正常使用。
编辑 /etc/rc.conf 文件,添加如下内容:

LOCALE="en_US.UTF-8"
DAEMON_LOCALE="no"
HARDWARECLOCK="localtime"
TIMEZONE="Asia/Shanghai"

然后:

ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

并网络同步一次系统时间(我配置了自动同步服务,在此不详述)。
文章来源:http://www.codelast.com/
『7』测试
软硬件都就绪的情况下,就要对RTC进行测试了。运行如下命令:

./ds1302.sh rtest

如果接线接错了,会打印出一堆类似于下面这样的信息:

DS1302 RAM TEST
DS1302 RAM Failure: Address:  0, Expected: 0x00, Got: 0xFF
DS1302 RAM Failure: Address:  1, Expected: 0x00, Got: 0xFF
DS1302 RAM Failure: Address:  2, Expected: 0x00, Got: 0xFF
...
DS1302 RAM Failure: Address: 28, Expected: 0x1C, Got: 0xFF
DS1302 RAM Failure: Address: 29, Expected: 0x1D, Got: 0xFF
DS1302 RAM Failure: Address: 30, Expected: 0x1E, Got: 0xFF
-- DS1302 RAM TEST FAILURE. 465 errors.

注意,前面说了必须要接一个上拉电阻,如果你没有接,也会报错。
如果一切正常,程序会提示“DS1302 RAM TEST: OK”。
文章来源:http://www.codelast.com/
然后将系统时间写入RTC:

./ds1302.sh sdsc

输出类似于:

Setting the clock in the DS1302 from Linux time [15-11-8 13:40:9]...OK

再从RTC中读取出其存储的时间,看是否正确:

./ds1302.sh

输出类似于:

0:   13:41:34  8/11/2015
1:   13:41:34  8/11/2015
2:   13:41:34  8/11/2015
3:   13:41:35  8/11/2015
4:   13:41:35  8/11/2015
5:   13:41:35  8/11/2015
6:   13:41:35  8/11/2015
7:   13:41:35  8/11/2015
8:   13:41:36  8/11/2015

最后测试一下将RTC中的时间写入树莓派的系统:

./ds1302.sh slc

输出类似于:

Setting the Linux Clock from the DS1302... Sun Nov  8 13:43:03 CST 2015

再看看系统时间是否正确:

date

搞定。
文章来源:http://www.codelast.com/
『8』开机启动
在上面的测试一切正常的情况下,只需要让系统启动时执行 ./ds1302.sh slc 命令,即可在树莓派断网的情况下也能保持正确走时了。

文章来源:https://www.codelast.com/
➤➤ 版权声明 ➤➤ 
转载需注明出处:codelast.com 
感谢关注我的微信公众号(微信扫一扫):

wechat qrcode of codelast

发表评论