一起玩转树莓派(22)——DS1302硬件时钟实践-阿里云开发者社区

开发者社区> 珲yy少> 正文

一起玩转树莓派(22)——DS1302硬件时钟实践

简介: 不知你是否有发现,我们在使用计算机时,除了第一次启动需要同步下时间外,即是没有联网,断电重启后,计算机的时间依然是准确的。这是因为在计算机主机内部有一个自带电源的硬件时钟模块,在同步时间时将当前的时间写入模块后,此硬件时钟模块会自动的维护准确的当前时间。树莓派内部本身没有硬件时钟模块,但是在某些非联网的需求场景中,我们需要准确的记录当前的日期时间,比如之前我们介绍过许多有关气象相关的传感器,在记录气象数据时,也需要记录当时的准确时间。
+关注继续查看

一起玩转树莓派(22)——DS1302硬件时钟实践

不知你是否有发现,我们在使用计算机时,除了第一次启动需要同步下时间外,即是没有联网,断电重启后,计算机的时间依然是准确的。这是因为在计算机主机内部有一个自带电源的硬件时钟模块,在同步时间时将当前的时间写入模块后,此硬件时钟模块会自动的维护准确的当前时间。树莓派内部本身没有硬件时钟模块,但是在某些非联网的需求场景中,我们需要准确的记录当前的日期时间,比如之前我们介绍过许多有关气象相关的传感器,在记录气象数据时,也需要记录当时的准确时间。

1. DS1302模块简介

DS1302是一款涓流充电计时芯片。其拥有实时时钟可以计算年,月,日,时,分,秒,星期等信息,可使用的时间间隔为2000年到2100年之间。除此之外,其还拥有一个31*8位的通用暂存RAM,用来存储一些临时的逻辑数据。DS1302采用同步串行通信的方式,简化了处理器的接口,理论上除了电源之外,主需要连接三根线即可实现通信功能,即CE线,I/O线和SCLK串行时钟线。

DS1302芯片有8个引脚,工作电路结构如下图所示:

每个引脚的作用如下:

DS1302采用的是8位的指令命令字,在每次通信前,都需要使用命令字来启动,命令字结构如下:

如上图所示,其中第7位是写入有效控制位,为0时写入无效,为1时允许写入,每次进行数据传输时,必须将此位设置为1,一般我们在使用指令时,此为强制设置为1。

第6位控制芯片功能,为0时表示使用时钟数据,为1时表示使用RAM数据。

第1位到第5位为寄存器选择位,选择要操作的寄存器。

第0位是读写控制位,为0时表示要写数据到寄存器,为1时表示要从寄存器读数据。

了解了DS1302的指令结构,要使用它我们还需要对寄存器的功能做简单的了解,本次实验我们主要使用DS1302的时钟功能,与时钟功能相关的寄存器有7个,芯片手册中给出了示例,如下:

下面我们介绍下这些寄存器的功能和用法。

首先,指令中有5位来标识要操作的寄存器的地址,上图中直接给我了操作寄存器的读写指令。

第1个寄存器的地址为0b0,因此其读指令为0b1000 0001,写指令为0b1000 0000,转换成16进制,即上图中的0x81与0x80。此寄存器的最高位CH是一个标志位,每次上电后,我们可以读取一下这一位,如果是1表示断电后始终系统已经不工作了,我们需要重新校准时间,如果是0表示备用电源正常,始终持续正常运行。第4到第6位用来表示时间秒的十位数据,第0到第3位用来表示秒的个位数据,因为秒的十位数据最大为5,3个二进制位足够使用。

第2个寄存器的地址为0b1,其最高位为保留位,目前无用,第4位到第6位用来藐视分钟的十位,第0位到第3位表示分钟的个位,同样分钟的十位数据最大为5,3个二进制位足够使用。

第3个寄存器的地址为0b10,其最高位为1表示当前使用的是12小时制,最高位是0表示使用的是24小时制。第6位固定为0,没有意义。第5位在12小时制的模式下,为1表示的是下午,为0表示的是上午。在24小时制的模式下,第4位和第5位一起表示小时的十位。第0位到第3位表示了小时的个位。

第4个寄存器的地址为0b11,其第7位和第6位为固定的0,没有意义。第5位和第4位一起表示了日期的十位,第0位到第3位表示了日期的个位。

第5个寄存器的地址为0b100,其第5位到第7位为固定的0,没有意义。第4位表示了月的十位,第0到第3位表示月的个位。

第6个寄存器的地址为0b101,其高5位固定为0,没有意义。第3位表示了星期。

第7个寄存器的地址为0b110,高4位表示了年的十位,第4位表示了年的个位,可以表示0-99之间的值,即2000年到2099年。

第8个寄存器的地址为0b111,这是一个控制寄存器,在写数据时,这个寄存器的最高位WP必须是0,如果此位为1,则给其他寄存器的写操作都将禁止,用来做保护。

如果要使用到RAM功能,则指令的前两位必须是11,因此RAM的第一个寄存器的读写指令为0b1100 0000或0b1100 0001 ,即0xC0和0xC1。RAM存储寄存器有31个,地址为0x00到0x1F,对应的命令字从0xC0到0xFF。

无论是时钟模式,还是RAM模式,DS1302都支持采用脉冲串的方式来读写数据,时钟脉冲串指令为0xBF与0xBE,RAM脉冲串为0xFF与0xFE。数据会按照寄存器的顺序依次写入和读出。

明白了指令与寄存器的使用逻辑,下面还剩下一点需要明确,那就是如何进行数据的输入和读取。RST引脚用来控制数据读写,RTS从低电平变成高电平触发一次数据传输过程。

2. 实验连线

本次实验,我们使用的是封装好的DS1302模块,如下图所示:

其中CLK对应SCLK引脚,DAT是IO功能引脚,RST对应CE功能引脚。我们选用树莓派中的16,18和22引脚(物理编码,分别对应BCM编码的23,24和25),连线如下:

DS1302树莓派
VCC3.3V
GNDGND
CLKGPIO23(BCM编码)
DATGPIO24(BCM编码)
RSTGPIO25(BCM编码)

3.实验编码

现在,我们只需要按照前面介绍的模块用法进行编码即可,示例代码如下:

#coding:utf-8

import time
import RPi.GPIO
from datetime import datetime

# 使用物理编码
SCL = 16
IO = 18
RST = 22

# 数据读写的间隔
CLK_PERIOD = 0.00001

# 关闭GPIO警告
RPi.GPIO.setwarnings(False)
# 配置树莓派GPIO接口 使用物理编码
RPi.GPIO.setmode(RPi.GPIO.BOARD)

# 写入一个字节的数据
def writeByte(Byte):
    for Count in range(8):
        # 将SCL置为低电平 开启一次传输
        time.sleep(CLK_PERIOD)
        RPi.GPIO.output(SCL, 0)
        # 取一位数据进行写入
        Bit = Byte % 2
        Byte = int(Byte / 2)
        # 通过IO引脚进行写入
        time.sleep(CLK_PERIOD)
        RPi.GPIO.output(IO, Bit)
        # 将SCL置为高电平 结束一次传输
        time.sleep(CLK_PERIOD)
        RPi.GPIO.output(SCL, 1)

# 读取一个字节的数据
def readByte():
    # 将IO引脚设置为输入
    RPi.GPIO.setup(IO, RPi.GPIO.IN, pull_up_down=RPi.GPIO.PUD_DOWN)
    Byte = 0
    for Count in range(8):
        # 先将SCL重置为高电平 
        time.sleep(CLK_PERIOD)
        RPi.GPIO.output(SCL, 1)
        # 将SCL置为低电平 开启一次传输
        time.sleep(CLK_PERIOD)
        RPi.GPIO.output(SCL, 0)
        # 读取一位数据
        time.sleep(CLK_PERIOD)
        Bit = RPi.GPIO.input(IO)
        Byte |= ((2 ** Count) * Bit)
    return Byte

# 重置一些数据
def resetDS1302():
    # SCL引脚设置为输出
    RPi.GPIO.setup(SCL, RPi.GPIO.OUT, initial=0)
    # RST引脚设置为输出
    RPi.GPIO.setup(RST, RPi.GPIO.OUT, initial=0)
    # IO引脚设置为输出
    RPi.GPIO.setup(IO, RPi.GPIO.OUT, initial=0)
    # SCL和IO都置为低电平
    RPi.GPIO.output(SCL, 0)
    RPi.GPIO.output(IO, 0)
    time.sleep(CLK_PERIOD)
    # RST置为高电平
    RPi.GPIO.output(RST, 1)

# 结束操作
def endDS1302():
    # SCL引脚设置为输出
    RPi.GPIO.setup(SCL, RPi.GPIO.OUT, initial=0)
    # RST引脚设置为输出
    RPi.GPIO.setup(RST, RPi.GPIO.OUT, initial=0)
    # IO引脚设置为输出
    RPi.GPIO.setup(IO, RPi.GPIO.OUT, initial=0)
    # SCL和IO都置为低电平
    RPi.GPIO.output(SCL, 0)
    RPi.GPIO.output(IO, 0)
    time.sleep(CLK_PERIOD)
    # RST置为低电平
    RPi.GPIO.output(RST, 0)


# 进行时间校准
def setDatetime(year, month, day,  hour, minute, second, dayOfWeek):
    # 引脚重置
    resetDS1302()
    # 设置写始终数据脉冲指令
    writeByte(int("10111110", 2))

    # 开始依次写数据
    # 写入秒数据,*16的作用是把十位右移4位 下面同
    writeByte((second % 10) | int(second / 10) * 16)
    # 写入分钟数据
    writeByte((minute % 10) | int(minute / 10) * 16)
    # 写入小时数据
    writeByte((hour % 10) | int(hour / 10) * 16)
    # 写入日期数据
    writeByte((day % 10) | int(day / 10) * 16)
    # 写入月份数据
    writeByte((month % 10) | int(month / 10) * 16)
    # 写入星期数据
    writeByte(dayOfWeek)
    # 写入年份数据
    writeByte((year % 100 % 10) | int(year % 100 / 10) * 16)
    # 结束数据写入
    writeByte(int("00000000", 2))
    # 结束任务
    endDS1302()

# 获取DS1302硬件时钟实践
def getDatetime():
    # 重置引脚
    resetDS1302()
    # 0xBF指令,开始时钟脉冲串读取数据
    writeByte(int("10111111", 2))

    Data = ""
    
    # 依次读取
    # 先读出秒数据
    Byte = readByte()
    second = (Byte % 16) + int(Byte / 16) * 10
    # 分钟数据
    Byte = readByte()
    minute = (Byte % 16) + int(Byte / 16) * 10
    # 小时数据
    Byte = readByte()
    hour = (Byte % 16) + int(Byte / 16) * 10
    # 日期数据
    Byte = readByte()
    day = (Byte % 16) + int(Byte / 16) * 10
    # 月份数据
    Byte = readByte()
    month = (Byte % 16) + int(Byte / 16) * 10
    # 星期数据
    Byte = readByte()
    day_of_week = (Byte % 16)
    # 年数据
    Byte = readByte()
    year = (Byte % 16) + int(Byte / 16) * 10 + 2000

    # 结束任务
    endDS1302()
    return datetime(year, month, day, hour, minute, second)

# 时间格式化
def format_time(dt):
    if dt is None:
        return ""
    fmt = "%m/%d/%Y %H:%M"
    return dt.strftime(fmt)
    
def parse_time(s):
    fmt = "%m/%d/%Y %H:%M"
    return datetime.strptime(s, fmt)



# 初始化工作

resetDS1302()

# 写保护已关闭。
writeByte(int("10001110", 2))
# 结束指令执行
writeByte(int("00000000", 2))
# 涓流充电模式被关闭。
writeByte(int("10010000", 2))
# 结束指令执行
writeByte(int("00000000", 2))
#结束任务
endDS1302()
        

current = datetime.now()

year = current.year
month = current.month
day = current.day
hour = current.hour
minute = current.minute
second = current.second
week = current.weekday()

setDatetime(year,month,day,hour,minute,second,week)

while True:
    time.sleep(1)
    dt = getDatetime()
    print(dt)

上面示例代码中,无论是读数据还是写数据,我们都采用的时钟脉冲串的通信模式,这样我们只需要设置一次指令,即可按需读出所需数据,非常方便。在树莓派上运行上面的代码,效果如下图所示:

4.思考一下

仿照上面的思路,是否可以改成非时钟脉冲串的通信方式来获取日期时间信息呢?试试吧!

专注技术,懂的热爱,愿意分享,做个朋友

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9497 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
11214 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
9055 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13186 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6895 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
21912 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4012 0
+关注
594
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载