1、实时时钟(Real Time Clock)
RTC,全称为实时时钟(Real Time Clock),是一种能够提供实时时间信息的电子设备。RTC通常包括一个计时器和一个能够记录日期和时间的电池。它可以独立于主控芯片工作,即使断电也能继续运行,并保持时间的精确度。
RTC一般用于需要准确时间的应用场合,如计算机系统的时间同步、数据采集系统的时间记录等。RTC可以提供秒、分、时、日、月、年等时间信息,还可以具备闹钟、定时器等功能。
在计算机系统中,RTC可以通过串行接口(如I2C、SPI接口)与主控芯片进行通信。主控芯片通过读取RTC的寄存器来获取当前时间,并可以通过写入寄存器来设置时间或功能。
除了计算机系统,RTC还可以用于其他电子设备中,如手机、电视等。
2、DS1302
(1)实时时钟芯片
DS1302是一款实时时钟芯片,由美国达拉斯半导体(Dallas Semiconductor)公司生产。它集成了时钟、日历和电池供电管理等功能。
DS1302通过3根线(数据线、时钟线和使能线)与外部控制器通信。它内部有一个32位的静态RAM,用于存储时间和日期等信息。它还有一个时钟输出引脚(CLKOUT),可以输出时钟信号。此外,DS1302还包括一个电池供电管理电路,可以在外部电源断开时维持时钟运行。
DS1302的主要特点如下:
- 时钟精度:±2分钟/月
- 工作电压:2V~5.5V
- 时钟频率:具有多个可选频率,最高可达到8MHz
- 时钟输出:可输出1Hz至32.768kHz的时钟信号
- 数据传输:采用串行方式传输,速率最高可达到2.048Mb/s
- 电源管理:具有电源失效检测和切换功能,可以自动切换到备用电池供电
DS1302广泛应用于各种需要实时时钟功能的设备,例如计算器、电子表、温度计、计步器、电子秤等。由于其低功耗和高精度的特点,它也常被用于嵌入式系统和物联网应用中。
(2)与树莓派接线
VCC:接树莓派的 3.3V 输出
GND:接树莓派的 Ground(地)
CLK:接树莓派的 GPIO21(BOARD 物理引脚编号40)
DAT:接树莓派的 GPIO20(BOARD 物理引脚编号38)
RST:接树莓派的 GPIO8(BOARD 物理引脚编号24)
(3)说明
DS1302的接线我特别说明下,我这里卡了一天,给我整懵了。我按照网上的接法接SLCK,ID_SD,CE0,运行程序老出问题,引脚功能报错,我估计接法不对,如果有同学整明白可以给我留言。我估计之前的接法是针对树莓派其他版本的。如果有时间还是要看下手册。
DS1302的接法还可以接到clk和IO引脚可以接树莓派SCL和SDA,不过不建议这样接,占用了OLED的引脚。如果接到这个IIC引脚上,通过sudo i2cdetect -y 1 命令也是查不到0x68这个1302器件ID。我这里也是搞了很长时间。
由于,DS1302的驱动在51和STM32都是通过IIC总线完成的,只要接到GPIO口,根据芯片手册的时序信号模拟IIC总线来发送与接收数据,主要有起始,结束、应答、非应答信号,再编写发送与接收字节函数,总之相比对程序员硬件软件要求比较高。在树莓派4B开发板中,DS1302也是基于IIC总线的,也有相应的C驱动和例程,但是我们还是用Pyhton来写。
3、DS1302驱动代码
import time
import RPi.GPIO
from datetime import datetime
# 使用物理编码
SCL = 40
IO = 38
RST = 24
# 数据读写的间隔
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)
# RST引脚设置为输出
RPi.GPIO.setup(RST, RPi.GPIO.OUT)
# IO引脚设置为输出
RPi.GPIO.setup(IO, RPi.GPIO.OUT)
# 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)
# RST引脚设置为输出
RPi.GPIO.setup(RST, RPi.GPIO.OUT)
# IO引脚设置为输出
RPi.GPIO.setup(IO, RPi.GPIO.OUT)
# 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)
主要有2个函数,一个是setDatetime(year, month, day, hour, minute, second, dayOfWeek),一个是getDatetime()。日期参数可以删掉,也可以随便给一个。在主程序中如果要引用这个驱动文件.py,使用 from ds1302 import * 。
4、主程序
from datetime import datetime
from ds1302 import *
import time
# 初始化程序
def datetime_setup():
print ('')
print ('')
print (getDatetime()) # 获取时间信息
print ('')
print ('')
ds_a = input( "Do you want to setup date and time?(y/n/c)\n c:Set the current time to the system time\n")
# 是否更新时间
if ds_a == 'y' or ds_a == 'Y': # 重新更新时间
ds_date = input("Input date:(YYYY MM DD) ") # 输入年月日
ds_time = input("Input time:(HH MM SS) ") # 输入时分秒
ds_date = list(map(lambda x: int(x), ds_date.split())) # 判断格式
ds_time = list(map(lambda x: int(x), ds_time.split())) # 判断格式
print ('')
print ('')
setDatetime(ds_date[0], ds_date[1], ds_date[2], ds_time[0], ds_time[1], ds_time[2],33) # 设置时间
dt = getDatetime() # 获取当前时间
print ("You set the date and time to:", dt) # 打印出当前时间
if ds_a == 'c' or ds_a == 'C':
current_datetime()
print ("current time is:", getDatetime())
# 循环函数
def datetime_loop():
while True:
dt= getDatetime() # 获取时间
print (dt) # 打印出时间
time.sleep(1) # 延时1S
# 释放资源
def resource_destory():
endDS1302() # 释放资源
#获取当前时间写入ds1302
def current_datetime():
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)
# 程序入口
if __name__ == '__main__':
datetime_setup()
try:
datetime_loop() # 循环函数
except KeyboardInterrupt: # 当按下Ctrl+C时,将执行destroy()子程序。
resource_destory() # 释放资源
启动后可以不设置时间n,可以设置时间y,也可以读取系统时间设置到DS1302(C)。设置后,每隔一秒读取寄存器日期时间值,打印到屏幕上面。也可以显示到OLED上面。
显示到OLED上面需要把之前的显示封装成函数,在主程序中获取日期时间后调用。
5、问题
具体显示的结果参考之前的接线实物图,但是OLED显示会出现闪烁问题,就是用time.sleep(1)导致的,这个和C中的delay函数一样,对于这个问题,解决办法,是将获取时间函数直接放到OLED显示函数里面。
def Oled_display(x,y):
global device
device = load_device()
font = ImageFont.truetype('STKAITI.TTF',16)
while True:
with canvas(device) as draw:
draw.rectangle(device.bounding_box, outline=0, fill=0)
draw.text((x,y),str(getDatetime()),font=font, fill='white')