目录
基于esp32和ws2812的彩色像素时钟,目前网上有许多类似作品,但大多使用的为esp32原生的编程语言,鉴于Python的简洁易懂,我打算使用本文记录一次使用micropython编程的像素时钟过程。
首先确保你的esp32已经刷了micropython,如果你还没有,请查阅其他相关资料教程,这里不再赘述,我使用的编程软件是thonny,可以很方便的对esp32进行程序调试
thonny官网:Thonny, Python IDE for beginners
如果你无法打开github,可以使用镜像站下载(该链接为我写文章时thonny最新版本,如果thonny有更新的版本,可以将下载链接的“github.com”替换为“hub.nuaa.cf”)https://hub.nuaa.cf/thonny/thonny/releases/download/v4.1.4/thonny-4.1.4.exe
接线如下图(此处散热片是我之前做别的东西时装的,已经拆不下来了):
接线如下图:
首先是需要初始化的一些东西
from machine import Pin
from ds1302 import DS1302
from machine import RTC
import machine, neopixel, time, network, ntptime
wlan = network.WLAN(network.STA_IF)
n = 32*8
p = 13 #ws2812信号输入
ds = DS1302(clk = Pin(25), dio = Pin(26), cs = Pin(27))
np = neopixel.NeoPixel(machine.Pin(p), n)
从网上找到了ds1302的驱动程序
保存为ds1302.py
from machine import Pin
DS1302_REG_SECOND = (0x80)
DS1302_REG_MINUTE = (0x82)
DS1302_REG_HOUR = (0x84)
DS1302_REG_DAY = (0x86)
DS1302_REG_MONTH = (0x88)
DS1302_REG_WEEKDAY= (0x8A)
DS1302_REG_YEAR = (0x8C)
DS1302_REG_WP = (0x8E)
DS1302_REG_CTRL = (0x90)
DS1302_REG_RAM = (0xC0)
class DS1302:
def __init__(self, clk, dio, cs):
self.clk = clk
self.dio = dio
self.cs = cs
self.clk.init(Pin.OUT)
self.cs.init(Pin.OUT)
def _dec2hex(self, dat):
return (dat//10) * 16 + (dat % 10)
def _hex2dec(self, dat):
return (dat//16) * 10 + (dat % 16)
def _write_byte(self, dat):
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):
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):
self.cs.value(1)
self._write_byte(reg)
t = self._read_byte()
self.cs.value(0)
return t
def _set_reg(self, reg, dat):
self.cs.value(1)
self._write_byte(reg)
self._write_byte(dat)
self.cs.value(0)
def _wr(self, reg, dat):
self._set_reg(DS1302_REG_WP, 0)
self._set_reg(reg, dat)
self._set_reg(DS1302_REG_WP, 0x80)
#开启
def start(self):
t = self._get_reg(DS1302_REG_SECOND + 1)
self._wr(DS1302_REG_SECOND, t & 0x7f)
#关闭
def stop(self):
t = self._get_reg(DS1302_REG_SECOND + 1)
self._wr(DS1302_REG_SECOND, t | 0x80)
#秒
def second(self, second=None):
if second == None:
return self._hex2dec(self._get_reg(DS1302_REG_SECOND+1)) % 60
else:
self._wr(DS1302_REG_SECOND, self._dec2hex(second % 60))
#分
def minute(self, minute=None):
if minute == None:
return self._hex2dec(self._get_reg(DS1302_REG_MINUTE+1))
else:
self._wr(DS1302_REG_MINUTE, self._dec2hex(minute % 60))
#时
def hour(self, hour=None):
if hour == None:
return self._hex2dec(self._get_reg(DS1302_REG_HOUR+1))
else:
self._wr(DS1302_REG_HOUR, self._dec2hex(hour % 24))
#工作日
def weekday(self, weekday=None):
if weekday == None:
return self._hex2dec(self._get_reg(DS1302_REG_WEEKDAY+1))
else:
self._wr(DS1302_REG_WEEKDAY, self._dec2hex(weekday % 8))
#天
def day(self, day=None):
if day == None:
return self._hex2dec(self._get_reg(DS1302_REG_DAY+1))
else:
self._wr(DS1302_REG_DAY, self._dec2hex(day % 32))
#月
def month(self, month=None):
if month == None:
return self._hex2dec(self._get_reg(DS1302_REG_MONTH+1))
else:
self._wr(DS1302_REG_MONTH, self._dec2hex(month % 13))
#年
def year(self, year=None):
if year == None:
return self._hex2dec(self._get_reg(DS1302_REG_YEAR+1)) + 2000
else:
self._wr(DS1302_REG_YEAR, self._dec2hex(year % 100))
#获取或设置时间
def date_time(self, dat=None):
if dat == 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):
if dat == None:
return self._get_reg(DS1302_REG_RAM + 1 + (reg % 31)*2)
else:
self._wr(DS1302_REG_RAM + (reg % 31)*2, dat)
对于ws2812,micropython有内置的库neopixel,因此我们不需要额外添加驱动
neopixel需要灯的序号来给ws2812发送数据,对于点阵屏来说是及其不方便的,因此这里我写了一个函数用于通过坐标点亮指定的灯
def set_pixel(x,y,color):
if x<0 or x>=32 or y<0 or y>=8: #判断是否超出范围
raise ValueError("x,y are out of range")
index = x*8+y #坐标转换序号
np[index] = color
#np.write()
不难看出,我以点阵屏右上角为坐标原点,向下为y轴正方向,向左为x轴正方向建的坐标系。对于为何将np.write()注释掉,稍候我会解释
这里我写了一个函数,输入指定的数字和坐标(数字为3*5,坐标定位数字右上角)即可显示数字在对应位置
def get_xy(i):
if i>7 or i<1:
raise ValueError("i is out of range")
if i==1:
return [(2,0),(2,1),(2,2)]
elif i==2:
return [(2,2),(2,3),(2,4)]
elif i==3:
return [(2,0),(1,0),(0,0)]
elif i==4:
return [(2,2),(1,2),(0,2)]
elif i==5:
return [(2,4),(1,4),(0,4)]
elif i==6:
return [(0,0),(0,1),(0,2)]
elif i==7:
return [(0,2),(0,3),(0,4)]
^为了减少代码量,先将“8”字(类似数码管)每个笔画都写在一个函数里?
def set_figure(f,x,y,color):
ze = get_xy(3)+get_xy(1)+get_xy(2)+get_xy(5)+get_xy(6)+get_xy(7) #数字0
on = [(1,0), (1,1), (2,1), (1,2), (1,3), (1,4),(2,4),(0,4)] #数字1
tw = get_xy(3)+get_xy(6)+get_xy(4)+get_xy(2)+get_xy(5) #数字2
th = get_xy(3)+get_xy(6)+get_xy(4)+get_xy(7)+get_xy(5) #数字3…
fo = get_xy(1)+get_xy(4)+get_xy(6)+get_xy(7)
fi = get_xy(3)+get_xy(1)+get_xy(4)+get_xy(7)+get_xy(5)
si = get_xy(3)+get_xy(1)+get_xy(4)+get_xy(2)+get_xy(7)+get_xy(5)
se = get_xy(3)+get_xy(6)+get_xy(7)
ei = get_xy(3)+get_xy(1)+get_xy(2)+get_xy(4)+get_xy(5)+get_xy(6)+get_xy(7)
ni = get_xy(3)+get_xy(1)+get_xy(6)+get_xy(4)+get_xy(7)+get_xy(5)
for i in ei : #清空屏幕
set_pixel(i[0]+x,i[1]+y,(0,0,0))
for i in on :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
if f==0:
for i in ze :
set_pixel(i[0]+x,i[1]+y,color)
elif f==1:
for i in on :
set_pixel(i[0]+x,i[1]+y,color)
elif f==2:
for i in tw :
set_pixel(i[0]+x,i[1]+y,color)
elif f==3:
for i in th :
set_pixel(i[0]+x,i[1]+y,color)
elif f==4:
for i in fo :
set_pixel(i[0]+x,i[1]+y,color)
elif f==5:
for i in fi :
set_pixel(i[0]+x,i[1]+y,color)
elif f==6:
for i in si :
set_pixel(i[0]+x,i[1]+y,color)
elif f==7:
for i in se :
set_pixel(i[0]+x,i[1]+y,color)
elif f==8:
for i in ei :
set_pixel(i[0]+x,i[1]+y,color)
elif f==9:
for i in ni :
set_pixel(i[0]+x,i[1]+y,color)
这里由于切换数字要先清除之前的数字,因此先写入“8”的数字颜色为(0,0,0)和1的颜色为(0,0,0)(由于数字“1”与其他数字不一样,因此只用“8”来清空不够(看成果就知道了))
下面进行时间的显示
def print_time(x,y,color):
global printtime
thetime = rtc.datetime()[4:7]
set_figure(thetime[0]//10,x-3,y,color) #小时十位
set_figure(thetime[0]-10*(thetime[0]//10),x-7,y,color) #小时个位
set_figure(thetime[1]//10,x-13,y,color) #分钟十位
set_figure(thetime[1]-10*(thetime[1]//10),x-17,y,color) #分钟个位
set_figure(thetime[2]//10,x-23,y,color) #秒钟十位
set_figure(thetime[2]-10*(thetime[2]//10),x-27,y,color) #秒钟个位
#左冒号 :
set_pixel(19,2,color)
set_pixel(19,4,color)
#右冒号 :
set_pixel(9,2,color)
set_pixel(9,4,color)
np.write()
到这里,已经可以显示基本的时间信息了,但我们需要校准时间并使时间在断电后依旧可以走时
所以这里我写了一个函数,先检查网络连接,若有网则使用网络校准时间并校准ds1302,没有网则使用ds1302校准时间
这里我已经在boot.py内配置好了WIFI如果你还没有配置,添加下面的语句到boot.py
SSID = '(你的WIFI名称)'
PASSWORD = '(你的WIFI密码)'
def connect_wifi(ssid, password, timeout=10):
start = time.time()
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect(ssid, password)
while not wlan.isconnected():
if time.time() - start > timeout:
return False
pass
print('network config:', wlan.ifconfig())
return True
if not connect_wifi(SSID, PASSWORD):
print('Failed to connect WiFi')
校准时间函数:(小时+8是因为ntp服务器默认的时间为UTC0)
def set_time():
if wlan.isconnected(): #如果有网络连接,尝试网络校准,没有则使用ds1302校准
try:
ntptime.host = 'ntp1.aliyun.com'
ntptime.settime()
rtc.datetime(rtc.datetime()[0:4]+(int(rtc.datetime()[4])+8,)+rtc.datetime()[5:8])
ds.date_time(rtc.datetime()[0:7])
except:
rtc.datetime(ds.date_time()+[0])
else:
rtc.datetime(ds.date_time()+[0])
此时,基本的时间显示已经可以运行了,是时候测试一下了(注意,颜色不要调的过亮,否则可能会发热严重)
set_time()
while(True):
print_time(28,1,(20,0,20))
time.sleep(0.5)
但是,好景不长,我发现每循环一次时,就会闪烁一下,非常不好看(此时我还没有注释掉坐标转换函数内的np.write()语句
我反复思考,怎么让它不会闪烁呢,显然,闪烁是因为每次更新数字时,都要擦掉原来的数字再重新显示,因此我想了老长时间,想出一个麻烦但貌似可行的办法,每次更新时,不擦全部数字,而是擦掉与原来数字不一样的部分,为此,我改动了时间显示函数,将数字显示里的先擦除所有数字语句删掉,然后添加了一个用于擦除不一样的像素的函数
改动的时间显示函数:
def print_time(x,y,color):
global printtime
thetime = rtc.datetime()[4:7]
#print(thetime[4]," ",thetime[5]," ",thetime[6])
#print(thetime[4]//10,thetime[4]-thetime[4]//10,thetime[5]//10,thetime[5]-thetime[4]//10,thetime[6]//10,thetime[6]-thetime[4]//10)
if printtime[0] != thetime[0]//10: #此处是判断哪个地方需要更换数字
printtime[0] = thetime[0]//10
smooth_clear(printtime[0],x-3,y) #clear
set_figure(printtime[0],x-3,y,color)
if printtime[1] != thetime[0]-10*(thetime[0]//10):
printtime[1] = thetime[0]-10*(thetime[0]//10)
smooth_clear(printtime[1],x-7,y)
set_figure(printtime[1],x-7,y,color)
if printtime[2] != thetime[1]//10:
printtime[2] = thetime[1]//10
smooth_clear(printtime[2],x-13,y) #clear
set_figure(printtime[2],x-13,y,color)
if printtime[3] != thetime[1]-10*(thetime[1]//10):
printtime[3] = thetime[1]-10*(thetime[1]//10)
smooth_clear(printtime[3],x-17,y)#clear
set_figure(printtime[3],x-17,y,color)
if printtime[4] != thetime[2]//10:
printtime[4] = thetime[2]//10
smooth_clear(printtime[4],x-23,y) #clear
set_figure(printtime[4],x-23,y,color)
if printtime[5] != thetime[2]-10*(thetime[2]//10):
printtime[5] = thetime[2]-10*(thetime[2]//10)
smooth_clear(printtime[5],x-27,y)
set_figure(printtime[5],x-27,y,color)
#set_figure(thetime[0]//10,x-3,y,color)
#set_figure(thetime[0]-10*(thetime[0]//10),x-7,y,color)
#set_figure(thetime[1]//10,x-13,y,color)
#set_figure(thetime[1]-10*(thetime[1]//10),x-17,y,color)
#set_figure(thetime[2]//10,x-23,y,color)
#set_figure(thetime[2]-10*(thetime[2]//10),x-27,y,color)
添加的擦除函数:
def smooth_clear(later,x,y):
if later==0:
cl = [(1,2)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==1:
cl = [(2,0), (0,0), (0,1), (0,2), (0,3), (2,3), (2,2)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==2:
cl = [(2,1), (1,1), (1,3)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==3:
cl = [(2,3)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==4:
cl = [(1,0),(2,4), (1,4)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==5:
cl = [(0,1)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==6:
pass
elif later==7:
cl = [(1,2), (2,1), (2,2), (2,3), (2,4), (1,4)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
elif later==8:
pass
elif later==9:
cl = [(2,3)]
for i in cl :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
np.write()
是不是非常麻烦,等我写完静下来想了一会之后,突然发现这些根本没必要,因为neopixel是先设置颜色信息,再用np.write()来发送数据,所以我只需要将擦除数字和设置数字全部放在np.write()前就可以了,于是我删除了这一大堆代码,复原函数之后注释掉了坐标转换函数中的np.write()(白忙活了老长时间)
丰富一下时钟,我又添加了日期显示功能,不过是通过像素形状和数目来实现的
实现函数:
def print_day():
day = rtc.datetime()[1:4]
for i in range(day[1]):
set_pixel(31-i//8,i-(i//8)*8,get_color(2))
#clear week
for i in range(2):
set_pixel(26-i,6,get_color(3))
for i in range(2):
set_pixel(22-i,6,get_color(3))
for i in range(2):
set_pixel(18-i,6,get_color(3))
for i in range(2):
set_pixel(14-i,6,get_color(3))
for i in range(2):
set_pixel(10-i,6,get_color(3))
for i in range(2):
set_pixel(6-i,6,get_color(3))
for i in range(2):
set_pixel(2-i,6,get_color(3))
#set week
for i in range(2):
set_pixel(26-i-day[2]*4,6,(15,13,13))
#clear month
for i in range(12):
set_pixel(20-i,7,get_color(4))
#set month
set_pixel(21-day[0],7,(15,13,13))
np.write()
?这里的get_color()函数是我写的渐变色函数,它会随着日期或者时间逐渐变化颜色(如正午是偏红色,深夜是深蓝色)
def get_color(mod):
day = rtc.datetime()[1:4]
thetime = rtc.datetime()[4:7]
now = thetime[0]*60+thetime[1]
if mod==1:#realtime
if now>720: #这里算法有点问题,早上5-6点就已经发红了,我已经更改,等我一会更新一下文章
i = now-720
return (30-int(30*(i/720)),int(10-8*(i/720)),int(15*(i/720)))
else:
i = 720-now
return (30-int(30*(i/720)),int(10-8*(i/720)),int(15*(i/720)))
elif mod==2:#mday
return (10,2,10)
elif mod==3:#week
return (day[2]*2,12-day[2]*2,0)
elif mod==4:#month
return (0,day[0],12-day[0])
from machine import Pin
from ds1302 import DS1302
from machine import RTC
import machine, neopixel, time, random, _thread, network, ntptime
wlan = network.WLAN(network.STA_IF)
n = 32*8
p = 13
printtime = [10,10,10,10,10,10]
ds = DS1302(clk = Pin(25), dio = Pin(26), cs = Pin(27))
np = neopixel.NeoPixel(machine.Pin(p), n)
rtc = RTC()
def set_pixel(x,y,color):
if x<0 or x>=32 or y<0 or y>=8:
raise ValueError("x,y are out of range")
index = x*8+y
np[index] = color
#np.write()
def set_figure(f,x,y,color):
ze = get_xy(3)+get_xy(1)+get_xy(2)+get_xy(5)+get_xy(6)+get_xy(7)
on = [(1,0), (1,1), (2,1), (1,2), (1,3), (1,4),(2,4),(0,4)]
tw = get_xy(3)+get_xy(6)+get_xy(4)+get_xy(2)+get_xy(5)
th = get_xy(3)+get_xy(6)+get_xy(4)+get_xy(7)+get_xy(5)
fo = get_xy(1)+get_xy(4)+get_xy(6)+get_xy(7)
fi = get_xy(3)+get_xy(1)+get_xy(4)+get_xy(7)+get_xy(5)
si = get_xy(3)+get_xy(1)+get_xy(4)+get_xy(2)+get_xy(7)+get_xy(5)
se = get_xy(3)+get_xy(6)+get_xy(7)
ei = get_xy(3)+get_xy(1)+get_xy(2)+get_xy(4)+get_xy(5)+get_xy(6)+get_xy(7)
ni = get_xy(3)+get_xy(1)+get_xy(6)+get_xy(4)+get_xy(7)+get_xy(5)
for i in ei :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
for i in on :
set_pixel(i[0]+x,i[1]+y,(0,0,0))
if f==0:
for i in ze :
set_pixel(i[0]+x,i[1]+y,color)
elif f==1:
for i in on :
set_pixel(i[0]+x,i[1]+y,color)
elif f==2:
for i in tw :
set_pixel(i[0]+x,i[1]+y,color)
elif f==3:
for i in th :
set_pixel(i[0]+x,i[1]+y,color)
elif f==4:
for i in fo :
set_pixel(i[0]+x,i[1]+y,color)
elif f==5:
for i in fi :
set_pixel(i[0]+x,i[1]+y,color)
elif f==6:
for i in si :
set_pixel(i[0]+x,i[1]+y,color)
elif f==7:
for i in se :
set_pixel(i[0]+x,i[1]+y,color)
elif f==8:
for i in ei :
set_pixel(i[0]+x,i[1]+y,color)
elif f==9:
for i in ni :
set_pixel(i[0]+x,i[1]+y,color)
def get_xy(i):
if i>7 or i<1:
raise ValueError("i is out of range")
if i==1:
return [(2,0),(2,1),(2,2)]
elif i==2:
return [(2,2),(2,3),(2,4)]
elif i==3:
return [(2,0),(1,0),(0,0)]
elif i==4:
return [(2,2),(1,2),(0,2)]
elif i==5:
return [(2,4),(1,4),(0,4)]
elif i==6:
return [(0,0),(0,1),(0,2)]
elif i==7:
return [(0,2),(0,3),(0,4)]
def set_time():
if wlan.isconnected():
try:
ntptime.host = 'ntp1.aliyun.com'
ntptime.settime()
rtc.datetime(rtc.datetime()[0:4]+(int(rtc.datetime()[4])+8,)+rtc.datetime()[5:8])
ds.date_time(rtc.datetime()[0:7])
except:
rtc.datetime(ds.date_time()+[0])
else:
rtc.datetime(ds.date_time()+[0])
def print_time(x,y,color):
global printtime
thetime = rtc.datetime()[4:7]
set_figure(thetime[0]//10,x-3,y,color)
set_figure(thetime[0]-10*(thetime[0]//10),x-7,y,color)
set_figure(thetime[1]//10,x-13,y,color)
set_figure(thetime[1]-10*(thetime[1]//10),x-17,y,color)
set_figure(thetime[2]//10,x-23,y,color)
set_figure(thetime[2]-10*(thetime[2]//10),x-27,y,color)
#left :
set_pixel(19,2,color)
set_pixel(19,4,color)
#right :
set_pixel(9,2,color)
set_pixel(9,4,color)
np.write()
def get_color(mod):
day = rtc.datetime()[1:4]
thetime = rtc.datetime()[4:7]
now = thetime[0]*60+thetime[1]
if mod==1:#realtime
if now>720:
i = now-720
return (30-int(30*(i/720)),int(10-8*(i/720)),int(15*(i/720)))
else:
i = 720-now
return (30-int(30*(i/720)),int(10-8*(i/720)),int(15*(i/720)))
elif mod==2:#mday
return (10,2,10)
elif mod==3:#week
return (day[2]*2,12-day[2]*2,0)
elif mod==4:#month
return (0,day[0],12-day[0])
def print_day():
day = rtc.datetime()[1:4]
for i in range(day[1]):
set_pixel(31-i//8,i-(i//8)*8,get_color(2))
#clear week
for i in range(2):
set_pixel(26-i,6,get_color(3))
for i in range(2):
set_pixel(22-i,6,get_color(3))
for i in range(2):
set_pixel(18-i,6,get_color(3))
for i in range(2):
set_pixel(14-i,6,get_color(3))
for i in range(2):
set_pixel(10-i,6,get_color(3))
for i in range(2):
set_pixel(6-i,6,get_color(3))
for i in range(2):
set_pixel(2-i,6,get_color(3))
#set week
for i in range(2):
set_pixel(26-i-day[2]*4,6,(20,0,0))
#clear month
for i in range(12):
set_pixel(20-i,7,get_color(4))
#set month
set_pixel(21-day[0],7,(15,13,13))
np.write()
set_time()
t=0
while(True):
if t%180000 == 0:
set_time()
t = 0
print_time(28,1,get_color(1))
print_day()
time.sleep(0.1)
t += 1
我觉得贴上一层纸会更好(因为没粘上所以有的地方纸翘起来导致有点糊)
中午效果图(纸已粘好)
由于学业原因,暂时只做了这个简陋的像素时钟初版,还有许多想加的功能没有添加?,如翻页显示功能,温湿度显示功能等等,等学业不紧张后,后面的版本会一一尝试实现。
写文章时有些匆忙,后续会不定时更新一下。一些代码写得比较乱望海涵。如果你觉得还不错的话,求点个赞,你的支持是我创作的最大动力
感谢你的阅读