MicroPython 嵌入式项目实战:Pico DS1302 RTC 数字闹钟实现指南
1.使用 DS1302 RTC 时钟实现简易数字闹钟
以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\51 RTC_DS1302 文件夹找到。
1.1 自定义 DS1302 类的实现
在以下代码中,我们使用自定义 DS1302 类实现了获取和设置时间、日期以及使用内置的 RAM 存储自定义数据等功能,示例代码如下:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/10/3 下午2:41
# @Author : 李清水
# @File : ds1302.py
# @Description : 自定义DS1302类控制芯片
# ======================================== 导入相关模块 ========================================
# 导入硬件相关模块
from machine import Pin
# 导入MicroPython相关模块
from micropython import const
# ======================================== 全局变量 ============================================
# ======================================== 功能函数 ============================================
# ======================================== 自定义类 ============================================
# 自定义DS1302芯片控制类
class DS1302:
"""
DS1302类,用于通过三线接口(CLK、DIO、CS)操作DS1302实时时钟芯片。
该类封装了对DS1302芯片的通信,提供了读取和设置时间、日期、星期等功能。
支持启动和停止时钟,以及读写芯片内部的RAM寄存器。
Attributes:
clk (Pin): 时钟引脚,用于产生时钟信号。
dio (Pin): 数据引脚,用于读写数据。
cs (Pin): 片选引脚,用于使能芯片通信。
Methods:
__init__(self, clk: Pin, dio: Pin, cs: Pin) -> None:
初始化DS1302类实例。
_dec2hex(self, dat: int) -> int:
将十进制数转换为十六进制数。
_hex2dec(self, dat: int) -> int:
将十六进制数转换为十进制数。
_write_byte(self, dat: int) -> None:
写入一个字节到DS1302。
_read_byte(self) -> int:
从DS1302读取一个字节。
_get_reg(self, reg: int) -> int:
获取指定寄存器的值。
_set_reg(self, reg: int, dat: int) -> None:
设置指定寄存器的值。
_wr(self, reg: int, dat: int) -> None:
写入数据到寄存器,并设置写保护。
start(self) -> None:
启动时钟。
stop(self) -> None:
停止时钟。
second(self) -> int:
获取当前秒数。
second(self, value: int) -> None:
设置秒数。
minute(self) -> int:
获取当前分钟数。
minute(self, value: int) -> None:
设置分钟数。
hour(self) -> int:
获取当前小时数。
hour(self, value: int) -> None:
设置小时数。
weekday(self) -> int:
获取当前星期数。
weekday(self, value: int) -> None:
设置星期数。
day(self) -> int:
获取当前日期。
day(self, value: int) -> None:
设置日期。
month(self) -> int:
获取当前月份。
month(self, value: int) -> None:
设置月份。
year(self) -> int:
获取当前年份。
year(self, value: int) -> None:
设置年份。
date_time(self, dat: list[int] | None = None) -> list[int] | None:
获取或设置完整的日期和时间。
ram(self, reg: int, dat: int | None = None) -> int | None:
读取或写入RAM寄存器的值。
"""
# 类变量:定义DS1302芯片寄存器地址
DS1302_REG_SECOND = const(0x80) # 秒寄存器
DS1302_REG_MINUTE = const(0x82) # 分寄存器
DS1302_REG_HOUR = const(0x84) # 时寄存器
DS1302_REG_DAY = const(0x86) # 日寄存器
DS1302_REG_MONTH = const(0x88) # 月寄存器
DS1302_REG_WEEKDAY = const(0x8A) # 星期寄存器
DS1302_REG_YEAR = const(0x8C) # 年寄存器
DS1302_REG_WP = const(0x8E) # 写保护寄存器
DS1302_REG_CTRL = const(0x90) # 控制寄存器
DS1302_REG_RAM = const(0xC0) # RAM寄存器
def __init__(self, clk: Pin, dio: Pin, cs: Pin) -> None:
"""
初始化DS1302类。
Args:
clk (Pin): 时钟引脚。
dio (Pin): 数据引脚。
cs (Pin): 片选引脚。
Returns:
None
"""
self.clk = clk
self.dio = dio
self.cs = cs
# 初始化时钟引脚为输出
self.clk.init(Pin.OUT)
# 初始化片选引脚为输出
self.cs.init(Pin.OUT)
def _dec2hex(self, dat: int) -> int:
"""
将十进制转换为十六进制。
Args:
dat (int): 要转换的十进制数。
Returns:
int: 转换后的十六进制数。
"""
return (dat // 10) * 16 + (dat % 10)
def _hex2dec(self, dat: int) -> int:
"""
将十六进制转换为十进制。
Args:
dat (int): 要转换的十六进制数。
Returns:
int: 转换后的十进制数。
"""
return (dat // 16) * 10 + (dat % 16)
def _write_byte(self, dat: int) -> None:
"""
写入一个字节到DS1302。
Args:
dat (int): 要写入的字节。
Returns:
None
"""
# 设置数据引脚为输出
self.dio.init(Pin.OUT)
# 写入8位数据
for i in range(8):
# 写入每一位,上升沿写入数据
self.dio.value((dat >> i) & 1)
# 产生时钟信号
self.clk.value(1)
self.clk.value(0)
def _read_byte(self) -> int:
"""
从DS1302读取一个字节。
Returns:
int: 读取到的字节。
"""
# 初始化临时变量
d = 0
# 设置数据引脚为输入
self.dio.init(Pin.IN)
# 依次读取八位数据
for i in range(8):
# 读取每一位,下降沿读取数据
d = d | (self.dio.value() << i)
# 产生时钟信号
self.clk.value(1)
self.clk.value(0)
return d
def _get_reg(self, reg: int) -> int:
"""
获取指定寄存器的值。
Args:
reg (int): 寄存器地址。
Returns:
int: 寄存器的值。
"""
# 启动通信
self.cs.value(1)
# 写入寄存器地址
self._write_byte(reg)
# 读取数据
t = self._read_byte()
# 结束通信
self.cs.value(0)
return t
def _set_reg(self, reg: int, dat: int) -> None:
"""
设置指定寄存器的值。
Args:
reg (int): 寄存器地址。
dat (int): 要写入的数据。
Returns:
None
"""
# 启动通信
self.cs.value(1)
# 写入寄存器地址
self._write_byte(reg)
# 写入数据
self._write_byte(dat)
# 结束通信
self.cs.value(0)
def _wr(self, reg: int, dat: int) -> None:
"""
写入数据到寄存器,并设置写保护。
Args:
reg (int): 寄存器地址。
dat (int): 要写入的数据。
Returns:
None
"""
# 禁用写保护
self._set_reg(DS1302.DS1302_REG_WP, 0)
# 写入数据
self._set_reg(reg, dat)
# 启用写保护
self._set_reg(DS1302.DS1302_REG_WP, 0x80)
def start(self) -> None:
"""
启动时钟。
Args:
None
Returns:
None
"""
# 读取秒寄存器
t = self._get_reg(DS1302.DS1302_REG_SECOND + 1)
# 取消停止位
self._wr(DS1302.DS1302_REG_SECOND, t & 0x7f)
def stop(self) -> None:
"""
停止时钟。
Args:
None
Returns:
None
"""
# 读取秒寄存器
t = self._get_reg(DS1302.DS1302_REG_SECOND + 1)
# 设置停止位
self._wr(DS1302.DS1302_REG_SECOND, t | 0x80)
@property
def second(self) -> int:
"""
获取秒。
Args:
None
Returns:
int: 当前秒。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_SECOND + 1)) % 60
@second.setter
def second(self, value: int) -> None:
"""
设置秒。
Args:
value (int): 要设置的秒数。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_SECOND, self._dec2hex(value % 60))
@property
def minute(self) -> int:
"""
获取分。
Args:
None
Returns:
int: 当前分。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_MINUTE + 1))
@minute.setter
def minute(self, value: int) -> None:
"""
设置分。
Args:
value (int): 要设置的分钟数。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_MINUTE, self._dec2hex(value % 60))
@property
def hour(self) -> int:
"""
获取时。
Args:
None
Returns:
int: 当前时。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_HOUR + 1))
@hour.setter
def hour(self, value: int) -> None:
"""
设置时。
Args:
value (int): 要设置的小时数。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_HOUR, self._dec2hex(value % 24))
@property
def weekday(self) -> int:
"""
获取星期。
Args:
None
Returns:
int: 当前星期。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_WEEKDAY + 1))
@weekday.setter
def weekday(self, value: int) -> None:
"""
设置星期。
Args:
value (int): 要设置的星期数。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_WEEKDAY, self._dec2hex(value % 8))
@property
def day(self) -> int:
"""
获取日。
Args:
None
Returns:
int: 当前日。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_DAY + 1))
@day.setter
def day(self, value: int) -> None:
"""
设置日。
Args:
value (int): 要设置的日。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_DAY, self._dec2hex(value % 32))
@property
def month(self) -> int:
"""
获取月。
Args:
None
Returns:
int: 当前月。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_MONTH + 1))
@month.setter
def month(self, value: int) -> None:
"""
设置月。
Args:
value (int): 要设置的月。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_MONTH, self._dec2hex(value % 13))
@property
def year(self) -> int:
"""
获取年。
Args:
None
Returns:
int: 当前年。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_YEAR + 1)) + 2000
@year.setter
def year(self, value: int) -> None:
"""
设置年。
Args:
value (int): 要设置的年。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_YEAR, self._dec2hex(value % 100))
def date_time(self, dat: list[int] | None = None) -> list[int] | None:
"""
获取或设置日期和时间。
Args:
dat (list[int] | None): 日期和时间数据 [年, 月, 日, 星期, 时, 分, 秒]。如果为 None,则获取当前时间。
Returns:
list[int] | None: 如果 dat 为 None,返回 [年, 月, 日, 星期, 时, 分, 秒];否则返回 None。
"""
if dat is None:
return [self.year, self.month, self.day, self.weekday, self.hour, self.minute, self.second]
else:
self.year = dat[0]
self.month = dat[1]
self.day = dat[2]
self.weekday = dat[3]
self.hour = dat[4]
self.minute = dat[5]
self.second = dat[6]
def ram(self, reg: int, dat: int | None = None) -> int | None:
"""
读取或写入RAM寄存器。
Args:
reg (int): 寄存器地址。
dat (int | None): 要写入的数据。如果为 None,则读取数据。
Returns:
int | None: 如果 dat 为 None,返回读取的数据;否则返回 None。
"""
if dat is None:
return self._get_reg(DS1302.DS1302_REG_RAM + 1 + (reg % 31) * 2)
else:
self._wr(DS1302.DS1302_REG_RAM + (reg % 31) * 2, dat)
# ======================================== 初始化配置 ==========================================
# ======================================== 主程序 ============================================
首先,我们定义了一系列常量,表示 DS1302 芯片的寄存器地址:
# 自定义DS1302芯片控制类
class DS1302:
# 类变量:定义DS1302芯片寄存器地址
DS1302_REG_SECOND = const(0x80) # 秒寄存器
DS1302_REG_MINUTE = const(0x82) # 分寄存器
DS1302_REG_HOUR = const(0x84) # 时寄存器
DS1302_REG_DAY = const(0x86) # 日寄存器
DS1302_REG_MONTH = const(0x88) # 月寄存器
DS1302_REG_WEEKDAY = const(0x8A) # 星期寄存器
DS1302_REG_YEAR = const(0x8C) # 年寄存器
DS1302_REG_WP = const(0x8E) # 写保护寄存器
DS1302_REG_CTRL = const(0x90) # 控制寄存器
DS1302_REG_RAM = const(0xC0) # RAM寄存器
在初始化方法中设置时钟引脚(clk)、数据引脚(dio)和片选引脚(cs)。
实现 DS1302 操作的底层方法为:
- 单字节写入
_write_byte方法:首先将 DIO 引脚设置为输出模式,通过循环,依次写入每一位数据,并在发送每一位数据时产生一个时钟脉冲(高电平然后低电平)。 - 单字节读取
_read_byte方法:将 DIO 引脚设置为输入模式,通过循环,依次读取每一位数据,并将读取到的完整字节返回。
寄存器读写相关方法就是通过调用 _write_byte() 和 _read_byte() 方法进行实现的:

- 寄存器值获取
_get_reg方法:首先将 CS 引脚置高,写入目标寄存器地址并读取 DS1302 芯片返回的数据,然后将 CS 引脚置低结束通信会话。 - 寄存器值写入
_set_reg方法:首先将 CS 引脚置高,写入目标寄存器地址并写入需要写入的数据,然后将 CS 引脚置低结束通信会话。 - WP 寄存器写入
_wr方法:该方法向 WP 写保护寄存器写入数据,首先将写保护寄存器WP设置为0来禁用写保护,然后向目标寄存器写入数据,最后将写保护寄存器 WP 设置为0x80以启用写保护
这些方法封装了低级的寄存器读写操作,在其他方法中通过调用这些方法即可实现控制 DS1302 芯片,例如:
-
启动时钟
start方法:首先读取秒寄存器的当前值,然后清除最高位(STOP位),使DS1302开始计时,调用关系如下图所示:![1.png]()
-
停止时钟
stop方法:首先读取秒寄存器的当前值,然后设置最高位(STOP位),使DS1302停止计时,调用关系如下图所示:

在对时间数据的获取和设置的 second、minute、hour、weekday、day、month、year 方法中主要是通过读写对应寄存器实现的:
@property
def second(self) -> int:
"""
获取秒。
Args:
None
Returns:
int: 当前秒。
"""
return self._hex2dec(self._get_reg(DS1302.DS1302_REG_SECOND + 1)) % 60
@second.setter
def second(self, value: int) -> None:
"""
设置秒。
Args:
value (int): 要设置的秒数。
Returns:
None
"""
self._wr(DS1302.DS1302_REG_SECOND, self._dec2hex(value % 60))
- 读取时间:通过
_get_reg方法读取对应寄存器的值,并使用_hex2dec将十六进制数据转换为十进制时间数据; - 写入时间:通过
_wr方法将十进制数据转换为十六进制后写入对应寄存器,从而设置时间。
并且,我们通过使用 Python 的 @property 装饰器,还为 DS1302 的各个时间和日期组件(秒、分、时、星期、日、月、年)提供了获取和设置的方法,我们将类的内部数据(如 DS1302 的寄存器值)封装起来,避免外部代码直接访问和修改这些内部数据;并且,外部代码能够以属性访问的方式调用方法,使得代码更加直观和易读,类似于访问普通的对象属性而非调用方法。
为方便用户使用,我们还有一个用于获取或设置完整日期和时间的 date_time 方法,如果不传参数,返回当前完整的日期和时间列表 [年, 月, 日, 星期, 时, 分, 秒],反之,如果传入一个包含日期和时间的列表,依次设置 DS1302 各个时间组件:
def date_time(self, dat: list[int] | None = None) -> list[int] | None:
"""
获取或设置日期和时间。
Args:
dat (list[int] | None): 日期和时间数据 [年, 月, 日, 星期, 时, 分, 秒]。如果为 None,则获取当前时间。
Returns:
list[int] | None: 如果 dat 为 None,返回 [年, 月, 日, 星期, 时, 分, 秒];否则返回 None。
"""
if dat is None:
return [self.year, self.month, self.day, self.weekday, self.hour, self.minute, self.second]
else:
self.year = dat[0]
self.month = dat[1]
self.day = dat[2]
self.weekday = dat[3]
self.hour = dat[4]
self.minute = dat[5]
self.second = dat[6]
在对 DS1302 芯片的内部 RAM 访问中,我们提供了读写 RAM 寄存器的 ram 方法:
- 读取
RAM: 如果不传入dat参数,读取指定RAM地址的数据 - 写入
RAM: 如果传入dat参数,向指定RAM地址写入数据
def ram(self, reg: int, dat: int | None = None) -> int | None:
"""
读取或写入RAM寄存器。
Args:
reg (int): 寄存器地址。
dat (int | None): 要写入的数据。如果为 None,则读取数据。
Returns:
int | None: 如果 dat 为 None,返回读取的数据;否则返回 None。
"""
if dat is None:
return self._get_reg(DS1302.DS1302_REG_RAM + 1 + (reg % 31) * 2)
else:
self._wr(DS1302.DS1302_REG_RAM + (reg % 31) * 2, dat)
这里,内部 RAM 起始写地址为 0xC0,而起始读地址为 0xC1 ,(reg % 31) * 2 确保寄存器索引在 0 到 30 范围内,并根据寄存器索引计算地址偏移量。
1.2 实验现象
在下面实验中,我们需要将风雅一号板-环境与存储采集板插入到风雅一号板-通用兼容扩展板上面,并打开风雅一号板-环境与存储采集板上 SWITCH2 拨码开关上 CLK、DIO 和 CS 选项:


然后将风雅一号板-通用兼容扩展板插入到风雅一号板-OLED显示与交互扩展板上,同时打开风雅一号板-OLED显示与交互扩展板 上 SWITCH1 拨码开关的 I2C_SDA、I2C_SCL 和 BUZZER 选项:
除此之外,请在 OLED_ADDR 拨码开关上选择一个地址,本示例将拨码置于 0x3C,因此 OLED 屏幕的 I²C 地址为 0x3C:

这里,各个模块与树莓派 Pico 连接如下所示:

在以下代码中,我们使用自定义 DS1302 类,通过 DS1302 实时时钟模块和 SSD1306 OLED 显示屏,实现一个具有闹钟功能的时钟系统,系统能够显示当前的日期和时间,并在预设的闹钟时间到达时,通过蜂鸣器发出提示。
示例代码如下:
# Python env : MicroPython v1.23.0
# -*- coding: utf-8 -*-
# @Time : 2024/10/3 下午2:41
# @Author : 李清水
# @File : main.py
# @Description : 外置RTC类实验,使用DS1302实现闹钟
# ======================================== 导入相关模块 ========================================
# 导入硬件相关模块
from machine import Pin, I2C, Timer, PWM, RTC
# 导入时间相关模块
import time
# 导入外部RTC实时时钟模块
from ds1302 import DS1302
# 从SSD1306模块中导入SSD1306_I2C类
from SSD1306 import SSD1306_I2C
# ======================================== 全局变量 ============================================
# OLED屏幕地址
OLED_ADDRESS = 0
# ======================================== 功能函数 ============================================
def display_time(timer: Timer) -> None:
"""
定时器回调函数,用于在OLED屏幕上显示当前时间。
该函数通过定时器周期性调用,从DS1302实时时钟芯片获取当前时间,
并在OLED屏幕上居中显示日期和时间。同时,检查当前时间是否与设置的闹钟匹配,
如果匹配则触发闹钟,否则停止闹钟蜂鸣器。
Args:
timer (Timer): Timer对象,用于周期性调用该函数。
Returns:
None
"""
# 声明全局变量
global ds1302, oled, alarm_clock
# 获取当前时间
current_time = ds1302.date_time()
# 清空标题区域
oled.fill_rect(0, 0, 128, 16, 0)
# 显示标题
title = "RTC Clock"
title_width = len(title) * 8
title_x = (128 - title_width) // 2
oled.text(title, title_x, 0)
# 格式化日期字符串
date_str = "{:04}-{:02}-{:02}".format(current_time[0], current_time[1], current_time[2])
# 计算位置以居中显示日期
date_width = len(date_str) * 8
date_x = (128 - date_width) // 2
# 格式化时间字符串
time_str = "{:02}:{:02}:{:02}".format(current_time[4], current_time[5], current_time[6])
# 计算位置以居中显示时间
time_width = len(time_str) * 8
time_x = (128 - time_width) // 2
# 清空日期和时间区域
oled.fill_rect(0, 16, 128, 32, 0)
# 显示日期和时间
oled.text(date_str, date_x, 16)
oled.text(time_str, time_x, 32)
# 检查闹钟
if alarm_clock.check_alarms(current_time):
# 停止定时器
timer.deinit()
# 重新初始化定时器
timer.init(period=100, mode=Timer.PERIODIC, callback=display_time)
else:
# 停止闹钟蜂鸣器
alarm_clock.stop_alarm()
# 刷新屏幕
oled.show()
# ======================================== 自定义类 ============================================
# 自定义闹钟类
class AlarmClock:
"""
AlarmClock类,用于管理闹钟功能,支持设置、删除和触发闹钟。
该类通过OLED显示屏显示闹钟状态,并通过蜂鸣器发出闹钟提示音。
支持设置多个闹钟,并在指定时间触发闹钟。
Attributes:
oled (SSD1306_I2C): OLED实例,用于显示闹钟状态。
buzzer (PWM): 蜂鸣器控制引脚,用于发出提示音。
alarms (list): 存储闹钟时间的列表,格式为[(小时, 分钟), ...]。
Methods:
__init__(self, oled: SSD1306_I2C, buzzer_pin: Pin) -> None:
初始化AlarmClock类实例。
set_alarm(self, hours: int, minutes: int) -> None:
设置闹钟。
delete_alarm(self, hours: int, minutes: int) -> None:
删除指定的闹钟。
check_alarms(self, current_time: tuple[int, int, int]) -> None:
检查当前时间是否与设定的闹钟时间匹配。
trigger_alarm(self) -> None:
触发闹钟,打开蜂鸣器。
stop_alarm(self) -> None:
停止闹钟,关闭蜂鸣器。
"""
def __init__(self, oled: SSD1306_I2C, buzzer_pin: Pin) -> None:
"""
初始化闹钟类。
Args:
oled (SSD1306_I2C): OLED实例,用于显示闹钟状态。
buzzer_pin (Pin): 蜂鸣器控制引脚。
"""
# 保存OLED实例
self.oled = oled
# 初始化蜂鸣器引脚为PWM输出,占空比为0
self.buzzer = PWM(buzzer_pin, freq=1000, duty_u16=0)
# 存储闹钟时间的列表
self.alarms = []
def set_alarm(self, hours: int, minutes: int) -> None:
"""
设置闹钟。
Args:
hours (int): 小时,范围为0-23。
minutes (int): 分钟,范围为0-59。
Returns:
None
Raises:
ValueError: 如果小时或分钟不在合法范围内。
"""
# 验证小时和分钟是否在合法范围内
if (hours < 0 or hours > 23) or (minutes < 0 or minutes > 59):
# 抛出异常
raise ValueError("Invalid time for alarm")
# 添加闹钟时间到列表
self.alarms.append((hours, minutes))
def delete_alarm(self, hours: int, minutes: int) -> None:
"""
删除指定的闹钟。
Args:
hours (int): 小时。
minutes (int): 分钟。
Returns:
None
"""
try:
# 从列表中删除指定的闹钟
self.alarms.remove((hours, minutes))
except ValueError:
# 如果闹钟未找到,打印提示信息
print("Alarm not found")
def check_alarms(self, current_time: tuple[int, int, int]) -> None:
"""
检查当前时间是否与设定的闹钟时间匹配。
Args:
current_time (tuple[int, int, int]): 当前时间的元组(小时,分钟,秒)。
Returns:
None
"""
# 获取当前小时和分钟
current_hours, current_minutes = current_time[4], current_time[5]
# 遍历所有设置的闹钟
for alarm in self.alarms:
# 如果闹钟时间到达
if alarm[0] == current_hours and alarm[1] == current_minutes:
# 触发闹钟
self.trigger_alarm()
# 清空屏幕
self.oled.fill(0)
# 在屏幕上显示“Alarm!”提示
self.oled.text("Alarm!", 0, 0)
# 刷新屏幕
self.oled.show()
def trigger_alarm(self) -> None:
"""
触发闹钟,打开蜂鸣器。
Args:
None
Returns:
None
"""
# 打开蜂鸣器
self.buzzer.duty_u16(32000)
def stop_alarm(self) -> None:
"""
停止闹钟,关闭蜂鸣器。
Returns:
None
"""
# 关闭蜂鸣器
self.buzzer.duty_u16(0)
# ======================================== 初始化配置 ==========================================
# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: Implement an alarm clock using DS1302")
# 创建DS1302对象
ds1302 = DS1302(clk=Pin(10), dio=Pin(11), cs=Pin(12))
# 在烧录程序之前,务必使用 mpremote rtc --set 命令设置内置RTC实时时钟时间为电脑主机时间
# 接着打开REPL,输入下面命令:
# from machine import Pin, RTC
# from ds1302 import DS1302
# rtc = RTC()
# year, month, day, weekday, hour, minute, second, _ = rtc.datetime()
# ds1302 = DS1302(clk=Pin(10), dio=Pin(11), cs=Pin(12))
# ds1302.date_time([year, month, day, weekday, hour, minute, second])
# 然后退出REPL模式,重启设备:mpremote reset
# 创建硬件I2C的实例,使用I2C1外设,时钟频率为400KHz,SDA引脚为6,SCL引脚为7
i2c = I2C(id=1, sda=Pin(6), scl=Pin(7), freq=400000)
# 开始扫描I2C总线上的设备,返回从机地址的列表
devices_list = i2c.scan()
print('START I2C SCANNER')
# 若devices_list为空,则没有设备连接到I2C总线上
if len(devices_list) == 0:
print("No i2c device !")
# 若非空,则打印从机设备地址
else:
print('i2c devices found:', len(devices_list))
# 便利从机设备地址列表
for device in devices_list:
# 判断从机设备地址是否为0x3c或0x3d(即OLED SSD1306的地址)
if device == 0x3c or device == 0x3d:
print("I2C hexadecimal address: ", hex(device))
OLED_ADDRESS = device
# 创建SSD1306 OLED屏幕的实例,宽度为128像素,高度为64像素,不使用外部电源
oled = SSD1306_I2C(i2c, OLED_ADDRESS, 128, 64,False)
# 创建闹钟实例
alarm_clock = AlarmClock(oled, buzzer_pin=Pin(9))
# 设置闹钟示例
alarm_clock.set_alarm(18, 3)
# 创建软件定时器实例
timer = Timer(-1)
# 每100ms调用一次display_time函数更新显示
timer.init(period=100, mode=Timer.PERIODIC, callback=display_time)
# ======================================== 主程序 ============================================
# 无限循环检查闹钟是否到达
while True:
# 获取当前时间
current_time = ds1302.date_time()
# 延时1s
time.sleep(1)
# 打印当前时间
print("Current Time: {:02}:{:02}:{:02}".format( current_time[4], current_time[5], current_time[6]))
这里,我们在自定义 AlarmClock 类中管理闹钟的设置、删除和触发功能,当前时间达到任一闹钟设置时间时,触发蜂鸣器并在 OLED 显示提示信息,各个方法含义如下:

- 类初始化
init 方法:首先保存 OLED 实例并初始化蜂鸣器,然后初始化闹钟列表self.alarms = [],用于存储多个闹钟时间。 - 设置闹钟
set_alarm方法:通过指定小时和分钟,将闹钟时间添加到闹钟列表中,首先进行参数验证确保hours在0-23之间,minutes在0-59之间,然后将(hours, minutes)元组添加到闹钟列表。 - 删除闹钟
delete_alarm方法:根据提供的小时和分钟,从闹钟列表中移除对应的闹钟时间;如果指定的闹钟时间不存在于列表中,捕获ValueError异常,并打印提示信息。 - 检查闹钟
check_alarms方法:比较当前时间的小时和分钟与闹钟列表中的每个闹钟时间是否匹配,如果匹配,触发闹钟,在 OLED 屏幕上显示提示信息并开启蜂鸣器,返回True;如果所有闹钟都未匹配,返回False。
然后,我们创建 DS1302 类的实例和 AlarmClock 类实例,接着,初始化一个软件定时器,设置其定时周期为 100 毫秒,以周期性模式调用 display_time 函数。在 display_time 函数中,会从 ds1302 获取当前时间,更新 OLED 屏幕上的标题(“RTC Clock”)、日期和时间,并调用 alarm_clock.check_alarms 检查闹钟是否触发:
- 如果当前时间匹配某个设定的闹钟时间,会触发闹钟(启动蜂鸣器),同时在
OLED上显示 “Alarm!”; - 如果不匹配,会停止蜂鸣器(
PWM占空比设为 0)。
而在主循环中,每秒则通过 ds1302.date_time() 获取当前时间,并将其以 “时:分: 秒” 的格式打印到终端上。
整体时序流程如下图所示:

需要注意的是,在烧录代码之前我们还需要进行一项工作,即同步外部 RTC 实时时钟和电脑时间。
首先用 mpremote rtc --set 命令将树莓派 Pico 内置 RTC 时间同步为电脑主机时间:

如果不提前同步内置 RTC,Pico 的内置 RTC 在断电后会丢失时间(它没有备用电池,断电后时间会重置为初始值,比如 2021-01-01 00:00:00)。此时代码读取到的内置 RTC 时间是错误的,写入 DS1302 后,整个闹钟系统的时间就会从错误的初始值开始运行(比如显示 “2021 年”),导致闹钟功能完全失效。
接着,还需打开 REPL 并依次输入以下命令,将内置 RTC 的时间同步到外部 DS1302 实时时钟:
from machine import Pin, RTC
from ds1302 import DS1302
rtc = RTC()
year, month, day, weekday, hour, minute, second, _ = rtc.datetime()
ds1302 = DS1302(clk=Pin(10), dio=Pin(11), cs=Pin(12))
ds1302.date_time([year, month, day, weekday, hour, minute, second])

待上述的命令执行完毕后,我们退出 REPL 模式,最后通过 mpremote reset 命令重启设备,确保时间配置完全生效。
烧录代码,打开终端显示如下:

可以看到,终端不断更新当前时间,而 OLED 屏幕上同样如此:
当闹钟设定时间到达时,蜂鸣器间断发出声响,同时 OLED 屏幕上显示提示信息:



以下代码可以在我们提供的资料包中 `elegance-devkit v1\Demo\51 RTC_DS1302` 文件夹找到。

浙公网安备 33010602011771号