1.项目介绍
制作一个时钟,可以通过板上的12个彩色LED灯来指示时间,并通过蜂鸣器在整点以播放音乐的方式报时,板上的按键可以设置时间。
实现方式为程序通过定时器计数的方式来生成时、分、秒信号,并根据这些信号来改变12个彩色LED灯的亮灭,产生PWM信号驱动蜂鸣器播放定时长度的音乐。
2.设计思路
在Thonny平台上,首先用RTC将实时的时间传输到程序中,再在程序中分别设计出OLED屏显示年月和小人,12个LED灯显示时分秒,整点报时,状态机等程序。
3.简单的硬件介绍
该项目运用到的硬件包括了STEP Pico、树莓派Pico扩展板和Type-C 数据线。其中,STEP Pico是一块RP2040微控制芯片,搭载了ARM Cortex MO+处理器,内置了264kb SRAM和2MB闪存,同时有Type-C接口;树莓派Pico扩展板包含了12个WS2812B RGB三色灯,1个蜂鸣器,2个按键输入,8位R-2R电阻网络构成的DAC等。
4.完成的功能和达到的性能
4.1时间指示
在运行程序后,会默认在OLED显示屏上出现年、月和一个螺旋斜向上的小人,其中年、月显示在OLED屏的右侧,小人显示在OLED屏的左侧。同时,12个WS2812B RGB三色灯会亮起三个,其中红色表示时,绿色表示分,蓝色表示秒。
4.2报时时长设置
按一下按键输入k1,OLED屏就会切换到整点报时时长设置界面,在屏幕中心会显示报时的时长,范围为0-55秒。
进入设置界面后,初始会默认设置报时时长为0s,之后可通过按键输入k2来改变报时时长,按一下k2能增加5s的报时时长。另外当报时时长到达55s时,再按一下k2会使报时时长恢复初始化至0s。
4.3整点报时
报时时长设置完成后,按一下按键设置k1,OLED屏就会切换回正常时间的显示,并且会进行整点报时,即当切回正常时间显示后,每经过一定时间后,就会发生整点报时,蜂鸣器就会歌曲《我和我的祖国》的旋律。
5.实现过程
5.1程序流程图
5.2用RTC传输时间
在程序中用函数RTC()将实时的时间数据传输到STEP Pico的程序中,并将时间的数据以元组储存,然后再用函数oled_time.show_date(),ws2812_time.show_time(),分别将时间中的年月显示在OLED屏上,将时分秒用12个WS2812B RGB三色灯显示,具体代码为:
rtc = RTC()
while True:
oled.fill(0)
dt_tuple = rtc.datetime()
year = dt_tuple[0]
month = dt_tuple[1]
h = dt_tuple[4]
m = dt_tuple[5]
s = dt_tuple[6]
5.3OLED屏上显示年月和小人
在程序中定义类“OledTime()”,来在OLED屏上显示年月和小人。其中,又主要定义了函数show_date、draw,分别起显示年月、绘制动态小人的作用,具体如下:
class OledTime():
def __init__(self, oled, frames):
self.oled = oled
self.frames = frames
self.oled.fill(0)
self.i = len(frames) - 1
self.wri = Writer(oled, freesans20)
def show_date(self, year, month):
self.y = year
self.m = month
def draw(self):
if self.i == 0:
self.i = len(frames) - 1
else:
self.i = self.i - 1
fb=framebuf.FrameBuffer(self.frames[self.i],64,64,framebuf.MONO_HLSB)
self.oled.blit(fb, 0, 0, 0)
self.wri.set_textpos(oled, 10, 70)
self.wri.printstring(str(self.y))
self.wri.set_textpos(oled, 30, 85)
self.wri.printstring(str(self.m))
5.4用LED灯显示时、分、秒
在程序中定义类“PixelDisplay()”和“Ws2812Time()”来用12个WS2812B RGB三色灯指示时间。其中,前者其调用12个WS2812B RGB三色灯的作用,后者主要起计算时间的作用,具体如下:
class PixelDisplay():
def __init__(self, wb):
self.pixel_array = array.array("I", [0 for _ in range(12)])
def set_color(self, n, color):
self.pixel_array[(n - 1) % 12] = (color[1]<<16) + (color[0]<<8) + color[2]
def get_color(self, n):
v = self.pixel_array[(n - 1) % 12]
return ((v >> 8) & 0xff, (v >> 16) & 0xff, v & 0xff)
def fill(self, c):
a = self.pixel_array
for i in range(12):
a[i] = c
def pixels_show(self, brightness_input=1):
for ii,cc in enumerate(self.pixel_array):
r = int(((cc >> 8) & 0xFF) * brightness_input)
g = int(((cc >> 16) & 0xFF) * brightness_input)
b = int((cc & 0xFF) * brightness_input)
self.pixel_array[ii] = (g<<16) + (r<<8) + b
def render(self):
wb.state_mach.put(self.pixel_array, 8)
class Ws2812Time():
def __init__(self, pd):
self.pd = pd
self.h = 0
self.m = 0
self.s = 0
def show_time(self, h, m, s):
h_tmp = h
m_tmp = m
s_tmp = s
self.h = (h_tmp - 1) % 12 + 1
self.m = (int(m_tmp / 5) - 1) % 12 + 1
self.s = (int(s_tmp / 5) - 1) % 12 + 1
def draw(self):
pd.fill(0)
pd.set_color(self.h, (0xff, 0, 0))
pd.set_color(self.m, (0, 0xff, 0))
pd.set_color(self.s, (0, 0, 0xff))
5.5状态机/按k1切换状态
在程序中定义类“StateMachine”作为状态机。首先设定设置报时时间界面和显示标准时间界面分别为0,1。然后定义了函数update、showCurrentTime、settingAlarmTime,分别为传输状态机状态、切换到当前时间、切换到设置整点报时时长,具体如下:
class StateMachine():
SettingAlarmTime = 0
ShowCurrentTime = 1
def __init__(self, alarmTime):
self.state = StateMachine.ShowCurrentTime
self.old_state = self.state
self.alarmTime = alarmTime
def update(self, k1, k2, s):
self.old_state = self.state
if self.state == StateMachine.ShowCurrentTime:
self.showCurrentTime(k1)
elif self.state == StateMachine.SettingAlarmTime:
self.settingAlarmTime(k1, k2, s)
print(self.state)
def showCurrentTime(self, k1):
if k1 == False:
pass
elif k1 == True:
self.alarmTime.on = False
self.state = StateMachine.SettingAlarmTime
def settingAlarmTime(self, k1, k2, s):
if k1 == False:
if k2:
self.alarmTime.s = (self.alarmTime.s + 5) % 60
pass
if k1 == True:
self.alarmTime.ss = (s + self.alarmTime.s + 1) % 60
self.alarmTime.on = True
self.state = StateMachine.ShowCurrentTime
5.6整点报时
在程序中定义类“AlarmTime”,用来整点报时。其中,又定义了函数render、update、draw,分别用作调用音乐、传输数据和判定报时条件、OLED屏显示的作用,具体如下:
class AlarmTime:
def __init__(self, oled):
self.oled = oled
self.s = 0
self.ss = 0
self.on = False
self.wri = Writer(oled, freesans20)
def render(self):
if self.on and mymusic is not None:
mymusic.tick()
def update(self, s):
if (s - 10 - self.ss) % 60 == 10:
self.on = False
print(self.ss, s)
if self.on and self.ss < s:
music_toggle(True)
elif not self.on:
music_toggle(False)
def draw(self):
self.oled.fill(0)
self.wri.set_textpos(oled, 32, 64)
self.wri.printstring(str(self.s))
5.7调用音乐
在程序中定义函数“music_toggle()”调用音乐,具体如下:
def music_toggle(on):
global music_on, mymusic
if music_on and not on:
mymusic.stop()
music_on = False
elif not music_on and on:
mymusic = music(song, tempo = 1)
gc.collect()
mymusic.beat = 200
music_on = True
6.遇到的主要难题及解决方法
6.1整点报时的判定
在该项目中,整点报时的困难主要是报时开始和结束的判定。
目前,为判定报时开始,代码中加入了报警绝对时间ss的辅助量,其中ss等于当前设定的报时时长,加上设定后按下k1切换回指示时间界面的时间的秒数s。且当s等于ss+3时,即会发生报时。
在该项目中,当ss与s满足关系:s-10-ss对60取余等于10时,整点报时结束,音乐停止播放。
7.未来的计划建议
该项目已经成功实现了制作一个能够指示时间和整点报时的时钟,并达到了预期指标。然而该项目还有许多可以提升与扩展的地方:
(1)OLED屏上未显示时间中的时分秒,无法直观的指示时间;
(2)整点报时的时长只能以5秒为单位增加;
(3)该时钟只能完成单次整点报时,无法完成2次及以上次数的整点报时。