一起玩转树莓派(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 |
树莓派 |
VCC |
3.3V |
GND |
GND |
CLK |
GPIO23(BCM编码) |
DAT |
GPIO24(BCM编码) |
RST |
GPIO25(BCM编码) |
3.实验编码
现在,我们只需要按照前面介绍的模块用法进行编码即可,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
import time import RPi.GPIO from datetime import datetime
SCL = 16 IO = 18 RST = 22
CLK_PERIOD = 0.00001
RPi.GPIO.setwarnings(False)
RPi.GPIO.setmode(RPi.GPIO.BOARD)
def writeByte(Byte): for Count in range(8): time.sleep(CLK_PERIOD) RPi.GPIO.output(SCL, 0) Bit = Byte % 2 Byte = int(Byte / 2) time.sleep(CLK_PERIOD) RPi.GPIO.output(IO, Bit) time.sleep(CLK_PERIOD) RPi.GPIO.output(SCL, 1)
def readByte(): RPi.GPIO.setup(IO, RPi.GPIO.IN, pull_up_down=RPi.GPIO.PUD_DOWN) Byte = 0 for Count in range(8): time.sleep(CLK_PERIOD) RPi.GPIO.output(SCL, 1) 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(): RPi.GPIO.setup(SCL, RPi.GPIO.OUT, initial=0) RPi.GPIO.setup(RST, RPi.GPIO.OUT, initial=0) RPi.GPIO.setup(IO, RPi.GPIO.OUT, initial=0) RPi.GPIO.output(SCL, 0) RPi.GPIO.output(IO, 0) time.sleep(CLK_PERIOD) RPi.GPIO.output(RST, 1)
def endDS1302(): RPi.GPIO.setup(SCL, RPi.GPIO.OUT, initial=0) RPi.GPIO.setup(RST, RPi.GPIO.OUT, initial=0) RPi.GPIO.setup(IO, RPi.GPIO.OUT, initial=0) RPi.GPIO.output(SCL, 0) RPi.GPIO.output(IO, 0) time.sleep(CLK_PERIOD) RPi.GPIO.output(RST, 0)
def setDatetime(year, month, day, hour, minute, second, dayOfWeek): resetDS1302() writeByte(int("10111110", 2))
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()
def getDatetime(): resetDS1302() 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.思考一下
仿照上面的思路,是否可以改成非时钟脉冲串的通信方式来获取日期时间信息呢?试试吧!
专注技术,懂的热爱,愿意分享,做个朋友
QQ:316045346