低成本智能桌面时钟:基于 Pico 的 MicroPython 实时时钟与环境监测系统

本文详细介绍如何使用 Raspberry Pi Pico 搭建一款多功能桌面时钟。项目基于 MicroPython 开发,结合 DS1302 实时时钟模块提供精准时间,SSD1306 OLED 屏幕清晰显示日期、星期与时间,同时集成 DHT11 温湿度传感器实时监测环境数据。
1
准备材料






1234
2
电路图




12
DTH11 | PICO |
VCC | |
GND | |
DATA | GPIO0 |
SSD1306 | PICO |
VCC | |
GND | |
SCL | GPIO3 |
SDA | GPIO2 |
DS1302 | PICO |
VCC | |
GND | |
CLK | GPIO12 |
DAT | GPIO13 |
RST | GPIO14 |
3
使用内置RTC函数实现时钟

"""
Raspberry Pi Pico + MicroPython 多功能信息屏
功能:
- 显示 DS1302 实时时钟(年月日、星期、时分秒)
- 显示 DHT11 温湿度数据
- 每 300ms 自动刷新一次(使用硬件定时器)
"""
from machine import Pin, I2C, Timer
from ssd1306 import SSD1306_I2C
import dht
from DS1302 import DS1302
import time
# ===== 全局变量初始化 =====
# 初始化 I2C 总线(使用 I2C1,scl=GPIO3, sda=GPIO2)
i2c = I2C(1, scl=Pin(3), sda=Pin(2))
# 初始化 OLED 屏幕(128x64,I2C 地址通常为 0x3C)
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)
# 星期映射表(DS1302 返回 1~7,其中 1=Sunday)
week = ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'] # 索引 0~6
# 初始化 DHT11 传感器(数据引脚接 GPIO0)
d = dht.DHT11(Pin(0))
# 初始化 DS1302 RTC(CLK=12, DIO=13, CS=14)
ds = DS1302(clk=Pin(12), dio=Pin(13), cs=Pin(14))
# 启动 DS1302 时钟(确保 CH 位已清除)
ds.start()
# ===== 定时刷新回调函数 =====
def clock_time(timer):
"""
定时器回调函数:每 300ms 被调用一次
功能:读取时间 & 温湿度,并刷新 OLED 显示
"""
try:
# 读取当前时间(返回 [年,月,日,星期,时,分,秒])
timee = ds.DateTime()
# 清屏
oled.fill(0)
# === 第一行:日期 ===
oled.text("Date:", 0, 0)
weekday_index = (timee[3] - 1) % 7 # 确保在 0~6 范围内
weekday_str = week[weekday_index]
# 格式化日期(补零对齐,更美观)
date_str = "{:04d}-{:02d}-{:02d}".format(timee[0], timee[1], timee[2])
oled.text(date_str, 0, 10)
oled.text(weekday_str, 90, 10) # 星期显示在右侧
# === 第二行:时间(时:分:秒)===
time_str = "{:02d}:{:02d}:{:02d}".format(timee[4], timee[5], timee[6])
oled.text(time_str, 0, 20)
# === 第三部分:温湿度 ===
try:
d.measure() # 触发 DHT11 测量
temp = d.temperature()
hum = d.humidity()
oled.text("Temp: {} C".format(temp), 0, 40)
oled.text("Humi: {} %".format(hum), 0, 50)
except Exception as e:
# DHT11 读取失败时显示错误(避免程序崩溃)
oled.text("Sensor Error", 0, 40)
oled.text(str(e)[:16], 0, 50) # 截断过长错误信息
# 刷新屏幕
oled.show()
except Exception as e:
# 兜底异常处理(防止定时器回调崩溃)
oled.fill(0)
oled.text("System Error", 0, 20)
oled.text(str(e)[:16], 0, 30)
oled.show()
# ===== 启动定时器 =====
# 创建一个硬件定时器
tim = Timer(-1)
# 每 300 毫秒触发一次回调
tim.init(period=300, mode=Timer.PERIODIC, callback=clock_time)
4
使用DS1302实现时钟

'''
DS1302 RTC 驱动模块
功能:支持设置/读取时间、日期,以及控制 DS1302 芯片
'''
from machine import Pin
# DS1302 寄存器地址定义(写地址为偶数,读地址 = 写地址 | 0x01)
DS1302_REG_SECOND = 0x80 # 秒寄存器(写)
DS1302_REG_MINUTE = 0x82 # 分寄存器
DS1302_REG_HOUR = 0x84 # 时寄存器
DS1302_REG_DAY = 0x86 # 日寄存器
DS1302_REG_MONTH = 0x88 # 月寄存器
DS1302_REG_WEEKDAY = 0x8A # 星期寄存器(1=周日, 2=周一, ..., 7=周六)
DS1302_REG_YEAR = 0x8C # 年寄存器(仅低两位,如 24 表示 2024)
DS1302_REG_WP = 0x8E # 写保护寄存器(0x80 启用保护)
DS1302_REG_CTRL = 0x90 # 控制寄存器(涓流充电等)
DS1302_REG_RAM = 0xC0 # RAM 起始地址(共31字节)
class DS1302:
def __init__(self, clk, dio, cs):
"""
初始化 DS1302 对象
:param clk: 时钟引脚(Pin 对象或编号)
:param dio: 数据引脚(Pin 对象或编号)
:param cs: 片选引脚(Pin 对象或编号)
"""
self.clk = clk if isinstance(clk, Pin) else Pin(clk, Pin.OUT)
self.dio = dio if isinstance(dio, Pin) else Pin(dio, Pin.OUT)
self.cs = cs if isinstance(cs, Pin) else Pin(cs, Pin.OUT)
self.clk.init(Pin.OUT)
self.cs.init(Pin.OUT)
self.cs.value(0) # 初始拉低片选
def DecToHex(self, dat):
"""将十进制 BCD 值转换为十六进制格式(用于写入 DS1302)"""
return (dat // 10) * 16 + (dat % 10)
def HexToDec(self, dat):
"""将 DS1302 读出的十六进制 BCD 值转为十进制"""
return (dat // 16) * 10 + (dat % 16)
def write_byte(self, dat):
"""向 DS1302 写入一个字节(低位先传)"""
self.dio.init(Pin.OUT)
for i in range(8):
self.dio.value((dat >> i) & 1) # 逐位输出
self.clk.value(1)
self.clk.value(0) # 下降沿锁存
def read_byte(self):
"""从 DS1302 读取一个字节(低位先传)"""
d = 0
self.dio.init(Pin.IN)
for i in range(8):
d |= (self.dio.value() << i) # 逐位读取
self.clk.value(1)
self.clk.value(0)
return d
def getReg(self, reg):
"""
读取指定寄存器的值
:param reg: 寄存器写地址(如 0x80)
:return: 寄存器内容(BCD 格式)
"""
self.cs.value(1)
self.write_byte(reg | 0x01) # 读操作:地址最低位置1
t = self.read_byte()
self.cs.value(0)
return t
def setReg(self, reg, dat):
"""
向指定寄存器写入数据(不处理写保护)
:param reg: 寄存器写地址(如 0x80)
:param dat: 要写入的数据(BCD 格式)
"""
self.cs.value(1)
self.write_byte(reg) # 写操作:使用偶数地址
self.write_byte(dat)
self.cs.value(0)
def wr(self, reg, dat):
"""
安全写入寄存器:先关闭写保护,写入后再启用保护
:param reg: 寄存器写地址
:param dat: 数据(十进制,内部自动转 BCD)
"""
# 关闭写保护(WP=0)
self.setReg(DS1302_REG_WP, 0)
# 写入目标寄存器(注意:dat 应为 BCD 格式)
self.setReg(reg, dat)
# 重新启用写保护(WP=0x80)
self.setReg(DS1302_REG_WP, 0x80)
def start(self):
"""启动 DS1302 时钟(清除秒寄存器的 CH 位)"""
sec = self.getReg(DS1302_REG_SECOND)
self.wr(DS1302_REG_SECOND, sec & 0x7F) # CH=0 表示运行
def stop(self):
"""停止 DS1302 时钟(设置秒寄存器的 CH 位)"""
sec = self.getReg(DS1302_REG_SECOND)
self.wr(DS1302_REG_SECOND, sec | 0x80) # CH=1 表示停止
# --- 时间/日期读写方法 ---
def Second(self, second=None):
if second is None:
return self.HexToDec(self.getReg(DS1302_REG_SECOND)) % 60
else:
self.wr(DS1302_REG_SECOND, self.DecToHex(second % 60))
def Minute(self, minute=None):
if minute is None:
return self.HexToDec(self.getReg(DS1302_REG_MINUTE))
else:
self.wr(DS1302_REG_MINUTE, self.DecToHex(minute % 60))
def Hour(self, hour=None):
if hour is None:
return self.HexToDec(self.getReg(DS1302_REG_HOUR))
else:
self.wr(DS1302_REG_HOUR, self.DecToHex(hour % 24))
def Weekday(self, weekday=None):
if weekday is None:
return self.HexToDec(self.getReg(DS1302_REG_WEEKDAY))
else:
# 星期范围通常为 1~7,确保不为0
wd = max(1, min(7, weekday % 8))
self.wr(DS1302_REG_WEEKDAY, self.DecToHex(wd))
def Day(self, day=None):
if day is None:
return self.HexToDec(self.getReg(DS1302_REG_DAY))
else:
self.wr(DS1302_REG_DAY, self.DecToHex(max(1, min(31, day % 32))))
def Month(self, month=None):
if month is None:
return self.HexToDec(self.getReg(DS1302_REG_MONTH))
else:
self.wr(DS1302_REG_MONTH, self.DecToHex(max(1, min(12, month % 13))))
def Year(self, year=None):
if year is None:
return self.HexToDec(self.getReg(DS1302_REG_YEAR)) + 2000
else:
self.wr(DS1302_REG_YEAR, self.DecToHex(year % 100))
def DateTime(self, dat=None):
"""
一次性设置或读取完整时间
:param dat: [年, 月, 日, 星期, 时, 分, 秒],如 [2026, 2, 2, 1, 10, 30, 0]
:return: 若 dat=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, dat=None):
"""
读写 DS1302 内置的 31 字节 RAM
:param reg: RAM 地址(0~30)
:param dat: 要写入的数据(0~255),若为 None 则读取
:return: 读取的数据(0~255)
"""
addr = DS1302_REG_RAM + (reg % 31) * 2 # 每个 RAM 单元占两个地址(写/读)
if dat is None:
return self.getReg(addr)
else:
self.wr(addr, dat & 0xFF)
def SetTime(self, YEAR, MONTH, DAY, WEEK, HOUR, MINUTE, SECOND):
"""
兼容旧接口:设置完整时间(推荐使用 DateTime([...] ))
"""
self.DateTime([YEAR, MONTH, DAY, WEEK, HOUR, MINUTE, SECOND])
# 初始化 DS1302
rtc = DS1302(clk=18, dio=19, cs=20)
# 第一次使用需设置时间
rtc.DateTime([2026, 2, 2, 1, 10, 45, 0]) # 2026年2月2日 周一 10:45:00
# 启动时钟
rtc.start()
# 读取当前时间
print(rtc.DateTime()) # 输出: [2026, 2, 2, 1, 10, 45, 30]
# 读取秒
print(rtc.Second())
0
0
0
qq空间
微博
复制链接
分享 更多相关项目
猜你喜欢
评论/提问(已发布 0 条)
0