2022暑假在家一起练
——基于RP2040实现电子沙漏制作
一.项目介绍
本项目依托2022暑假在家一起练,基于RP2040GameKit平台,实现了电子沙漏的制作。本项目通过LCD屏幕、2个按键和2块8*8 LED灯板实现了沙漏倒计时的功能,取得了良好的效果,具有一定的价值。
项目任务:
- 自行设计一个电子沙漏的物理结构,将提供的两个LED灯板和RP2040 Game Kit固定。
- 通过RP2040 Game Kit上的按键和LCD屏幕设定沙漏一个周期的时间,实现如上图中LED的效果。
- 通过RP2040 Game Kit上的姿态传感器来感知沙漏的方向变化,并开始新的沙漏操作。
二.设计思路
本项目设计围绕RP2040GAMEKIT,首先对RP2040的板载硬件进行实验使用,初步掌握板载的IO硬件以及RP2040能够实现的功能与实际性能。再根据硬件的具体功能,实现设计。设计步骤如下:
- 首先根据项目任务要求做好准备工作。连接8*8LED灯板与RP2040游戏机板,确定好GPIO口,从而实现在LED灯板上呈现沙漏图案的功能。由原理图和74HC595 8位寄存器的Micropython SPI库等资料可以得出,需要把灯板上DIN口接到RP2040的mosi口(Pin(27)),SRCLK口接到sck口(Pin(26)),把RCLK接到Pin(22),当解决好接口之后我们就可以顺利的在灯板上显示出我们想要的图案了。参考链接:https://www.eetree.cn/project/detail/1095
- 根据RP2040游戏机板上的三轴姿态传感器MMA7660来确定板子正置和倒置的模式,从而达到感知沙漏方向变化的要求。通过对水平仪项目的参考,得出了I2C总线的运作方式,在本项目中加以运用,配置出了当y<0时正置,y>0时倒置的特点。
- 设计两个不同时间的沙漏,在LED灯板上实现随着时间变化,沙子不断滴下的场景,最后完善RP2040GAMEKIT的页面。在10s漏斗和20s漏斗中均设置连续的沙子滴漏矩阵,随着时间变化,矩阵逐秒平移。设置开机页面,并且使得在开机的同时灯板全暗。
三.硬件介绍
- RP2040GameKit游戏机板
- 采用树莓派Pico核心芯片RP2040:
- 双核Arm Cortex M0+内核,可以运行到133MHz
- 264KB内存
- 性能强大、高度灵活的可编程IO可用于高速数字接口
- 片内温度传感器、并支持外部4路模拟信号输入,内部ADC采样率高达500Ksps、12位精度
- 支持MicroPython、C、C++编程
- 板上功能:
- 240*240分辨率的彩色IPS LCD,SPI接口,控制器为ST7789
- 四向摇杆 + 2个轻触按键 + 一个三轴姿态传感器MMA7660用做输入控制
- 板上外扩2MB Flash,预刷MicroPython的UF2固件
- 一个红外接收管 + 一个红外发射管
- 一个三轴姿态传感器MMA7660
- 一个蜂鸣器
- 双排16Pin连接器,有SPI、I2C以及2路模拟信号输入
- 可以使用MicroPython、C、C++编程
- USB Type C连接器用于供电、程序下载
2.8*8LED灯板
该板子的具体详情可以参考链接:
https://www.eetree.cn/project/detail/55
该灯板的LED阵列是由发光二极管与限流电阻有规律地排布而成。发光二极管的亮灭通过ROW与COL的电平关系决定,仅当ROW为HIGH且COL为HIGH时,发光二极管导通。
四.功能介绍
1.初始化开机界面:屏幕显示“设置沙漏”,按键A/B选择A.20s,B.10s
2.选择B.10s漏斗:因为10s时间不便于观察沙漏来回倒置,故设置10s为最简单的单向沙漏模式。沙漏开始时显示“已选择10s”沙漏,结束后显示已完成。
3.选择A.20s漏斗:实现姿态传感器来感知沙漏的方向变化,并开始新的沙漏操作。沙漏开始时显示“已选择10s”沙漏,结束后显示已完成。可以在沙漏滴漏过程中随时倒换方向.
4.当沙漏完成后,可点击A、B按钮再次开始漏斗
五.主要代码
1.因为开机页面选择黄色,为使得字体搭配,故在st7789.py中仿照原有的text()加入代码:
def textnew(self, font, text, x0, y0, color=BLACK, background=YELLOW):
"""
Draw text on display in specified font and colors. 8 and 16 bit wide
fonts are supported.
Args:
font (module): font module to use.
text (str): text to write
x0 (int): column to start drawing at
y0 (int): row to start drawing at
color (int): 565 encoded color to use for characters
background (int): 565 encoded color to use for background
"""
if font.WIDTH == 8:
self._text8(font, text, x0, y0, color, background)
else:
self._text16(font, text, x0, y0, color, background)
def textnew2(self, font, text, x0, y0, color=BLACK, background=WHITE):
"""
Draw text on display in specified font and colors. 8 and 16 bit wide
fonts are supported.
Args:
font (module): font module to use.
text (str): text to write
x0 (int): column to start drawing at
y0 (int): row to start drawing at
color (int): 565 encoded color to use for characters
background (int): 565 encoded color to use for background
"""
if font.WIDTH == 8:
self._text8(font, text, x0, y0, color, background)
else:
self._text16(font, text, x0, y0, color, background)
2.主要代码
import uos
import machine
import time
import st7789 as st7789
from fonts import vga2_8x8 as font1
from fonts import vga1_16x32 as font2
import framebuf
该部分为mma7660与st7789所对应库的声明
from mma7660_2 import MMA7660
from machine import I2C, Pin, ADC,RTC,SPI
import math
from sr_74hc595_spi import SR
import framebuf,array
因为灯板的每行每列都被芯片74HC595所控制,所以我们要点亮灯板只需要控制芯片74HC595输出的数据。这里驱动74HC595采用的是SPI总线的方式。这种方式可以精确的控制每一个LED点阵中的“像素”
st7789_res = 0
st7789_dc = 1
buttonB = 5
disp_width = 240
disp_height = 240
CENTER_Y = int(disp_width/2)
CENTER_X = int(disp_height/2)
spi_sck=machine.Pin(2)
spi_tx=machine.Pin(3)
spi0=machine.SPI(0,baudrate=4000000, phase=1, polarity=1, sck=spi_sck, mosi=spi_tx)
display = st7789.ST7789(spi0, disp_width, disp_width,
reset=machine.Pin(st7789_res, machine.Pin.OUT),
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
xstart=0, ystart=0, rotation=0)
display.fill(st7789.YELLOW)
display.textnew(font2, "Set the", 10, 10)
display.textnew(font2,"hourglass",25,55)
display.text(font2,"A.20s",36,116)
display.text(font2,"B.10s",146,116)
对st7789、mma7660以及RP2040对应的按键端口进行配置和初始化
#按键配置
buttonB = Pin(5,Pin.IN, Pin.PULL_UP)
buttonA = Pin(6,Pin.IN, Pin.PULL_UP)
#姿态传感器配置
i2c = I2C(1, scl=Pin(11), sda=Pin(10))
accel = MMA7660(i2c)
accel.on(True)
spi = SPI(1, 5000_000, sck=Pin(26), mosi=Pin(27))
rclk = Pin(22, Pin.OUT)
sr = SR(spi, rclk, 2) # chain of 2 shift registers
buffer1=bytearray(1) #
buffer0=bytearray(1) #
buffer=bytearray(8*5) #
buffer_clear=bytearray(1)
fbuf = framebuf.FrameBuffer(buffer,8*5,8,framebuf.MONO_VLSB)
def show(buf):
for y in range(8): #每循环控制所有灯板同一列
buffer1[0]=0x80 #选中行
buffer1[0] = buffer1[0]>> y
for i in range (5): #每循环控制一个灯板
buffer0[0] = buf[(5-i)*8-1-y] #选中列
spi.write(buffer0)
spi.write(buffer1)
sr.latch()
def move():#姿态捕捉
p = bytearray(3)
accel.getSample(p)
y = int(position(p[0])*2.7)
x = int(position(p[1])*2.7)
z = position(p[2])
return x, y, z
def position(position):#数值换算
sign_bit = 1 << 5
if position & sign_bit == 0 :
sign = 1
else :
sign=-1
if sign > 0 :
value = position & ~sign_bit
else:
value=sign * ((sign_bit << 1) - position)
return value
def ledfunction():
for i in range(250):
show(buffer)
for i in range (5*8):
buffer[i]=0x00
#B模式漏斗框
def Bedge():
fbuf.line(2,1,2,6,1)
fbuf.line(13,1,13,6,1)
fbuf.line(3,1,5,1,1)
fbuf.line(3,6,5,6,1)
fbuf.line(12,1,10,1,1)
fbuf.line(12,6,10,6,1)
fbuf.line(6,2,9,5,1)
fbuf.line(9,2,6,5,1)
#B模式漏斗初始化
def BedgeInit():
fbuf.fill_rect(4,2,2,4,1)
fbuf.fill_rect(6,3,1,2,1)
#执行B模式
def patternB(temp,start_time):
###漏斗框架
Bedge()
BedgeInit()
time.sleep(1)
rtc=RTC()
temp=rtc.datetime()
end_time=temp[5]*60+temp[6]
while (end_time-start_time < 11):
#画框
Bedge()
temp=rtc.datetime()
end_time=temp[5]*60+temp[6]
i=end_time-start_time
#fbuf.fill_rect(1+i,3,5,2,1)
if i<4:
fbuf.fill_rect(5,2,1,4,1)
fbuf.fill_rect(6,3,1,2,1)
fbuf.fill_rect(4,2+i,1,4-i,1)
fbuf.fill_rect(12,2,1,i,1)
if i<3:
fbuf.fill_rect(9+i,3,3,1,1)
elif i==4:
fbuf.fill_rect(5,2,1,4,1)
fbuf.fill_rect(6,3,1,2,1)
fbuf.fill_rect(12,2,1,4,1)
elif i>4 and i<8:
fbuf.fill_rect(6,3,1,2,1)
fbuf.fill_rect(12,2,1,4,1)
fbuf.fill_rect(5,i-4+2,1,4-(i-4),1)
fbuf.fill_rect(9+i-4,4,2,1,1)
fbuf.fill_rect(11,2,1,i-4,1)
elif i==8:
fbuf.fill_rect(12,2,1,4,1)
fbuf.fill_rect(11,2,1,4,1)
fbuf.fill_rect(6,3,1,2,1)
elif i==9:
fbuf.fill_rect(12,2,1,4,1)
fbuf.fill_rect(11,2,1,4,1)
fbuf.fill_rect(10,3,1,1,1)
fbuf.fill_rect(6,4,1,1,1)
elif i==10:
fbuf.fill_rect(11,2,2,4,1)
fbuf.fill_rect(10,3,1,2,1)
else:
break
ledfunction()
#A模式漏斗框
def Aedge():
fbuf.fill_rect(2,1,12,1,1)
fbuf.fill_rect(2,6,12,1,1)
fbuf.fill_rect(2,2,1,4,1)
fbuf.fill_rect(13,2,1,4,1)
#A模式漏斗正置初始化
def AedgeInit():
fbuf.fill_rect(3,2,5,4,1)
#A模式倒置初始化
def AedgeInitdown():
fbuf.fill_rect(8,2,5,4,1)
#执行倒置A模式
def patternAdown():
print("A倒置")
rtc=RTC()
display.fill(st7789.WHITE)
display.textnew2(font2, "You choose ", 10, 10)
display.textnew2(font2, "the 20-second",20, 70)
display.textnew2(font2,"funnel !",30,130)
time1=rtc.datetime()
start_time=time1[5]*60+time1[6]
reserve=[1,1,1,1,1,1,1,1,1,1,1,1,1,"12","13","14","15","16","17","18","19","20"]
x,y,z=move()
reserve[0]=y
temp=rtc.datetime()
end_time=temp[5]*60+temp[6]
settime=23
Aedge()
AedgeInitdown()
while (end_time-start_time < settime ):
temp=rtc.datetime()
end_time=temp[5]*60+temp[6]
endtemp=end_time
i=end_time-start_time
Aedge()
#AedgeInitdown()
if i<=5 and i>=0 and y>0 :
fbuf.fill_rect(8-i,2,5,1,1)
fbuf.fill_rect(8,3,5,3,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
patternAup()
break
elif i>5 and i<=10:
fbuf.fill_rect(8-i+5,3,5,1,1)
fbuf.fill_rect(3,2,5,1,1)
fbuf.fill_rect(8,4,5,2,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
patternAup()
break
elif i>10 and i<=15:
fbuf.fill_rect(8-i+10,4,5,1,1)
fbuf.fill_rect(3,2,5,2,1)
fbuf.fill_rect(8,5,5,1,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
patternAup()
break
elif i>15 and i<=20:
fbuf.fill_rect(3,2,5,3,1)
fbuf.fill_rect(8-i+15,5,5,1,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
patternAup()
break
else:
display.fill(st7789.WHITE)
display.textnew2(font2, "20s finished !",20,70)
display.textnew2(font2, "press A or B !",30,130)
break
ledfunction()
#执行正置A模式
def patternAup():
print("A正置")
rtc=RTC()
time1=rtc.datetime()
display.fill(st7789.WHITE)
display.textnew2(font2, "You choose ", 10, 10)
display.textnew2(font2, "the 20-second",20, 70)
display.textnew2(font2,"funnel !",30,130)
start_time=time1[5]*60+time1[6]
reserve=[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
x,y,z=move()
reserve[0]=y
temp=rtc.datetime()
Aedge()
AedgeInit()
end_time=temp[5]*60+temp[6]
settime=23
while (end_time-start_time < settime):
temp=rtc.datetime()
end_time=temp[5]*60+temp[6]
endtemp=end_time
i=end_time-start_time
Aedge()
if i<=5 and i>=0 and y<0 :
fbuf.fill_rect(3+i,2,5,1,1)
fbuf.fill_rect(3,3,5,3,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
Aedge()
patternAdown()
break
elif i>5 and i<=10:
fbuf.fill_rect(3+i-5,3,5,1,1)
fbuf.fill_rect(3,4,5,2,1)
fbuf.fill_rect(8,2,5,1,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
Aedge()
patternAdown()
break
elif i>10 and i<=15:
fbuf.fill_rect(8,2,5,2,1)
fbuf.fill_rect(3+i-10,4,5,1,1)
fbuf.fill_rect(3,5,5,1,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
Aedge()
patternAdown()
break
elif i>15 and i<=20:
fbuf.fill_rect(8,2,5,3,1)
fbuf.fill_rect(3+i-15,5,5,1,1)
x,y,z=move()
reserve[i+1]=y
re=reserve[i+1]*reserve[i]
if(re<0):
fbuf.fill(0)
Aedge()
patternAdown()
break
else :
display.fill(st7789.WHITE)
display.textnew2(font2, "20s finished !",20,70)
display.textnew2(font2, "press A or B!",30,130)
break
ledfunction()
def main():
x0=0
y0=0
z0=0
fbuf.fill(0)
ledfunction()
led(x0,y0,z0)
fbuf.fill(0)
ledfunction()
def led(x0,y0,z0):
rtc = RTC()
time =bytearray(8)
temp=bytearray(8)
while True:
if buttonB.value() == 0:
x,y,z=move()
y=147+(y-y0)
#display.clear()
print("B按下")
display.fill(st7789.WHITE)
display.textnew2(font2, "You choose ", 10, 10)
display.textnew2(font2, "the 10-second",20, 70)
display.textnew2(font2,"funnel !",30,130)
time=rtc.datetime()
start_time=time[5]*60+time[6]
patternB(temp,start_time)
display.fill(st7789.WHITE)
display.textnew2(font2, "10s finished !",20,70)
display.textnew2(font2, "press A or B!",30,130)
Bedge()
ledfunction()
elif buttonA.value() == 0:
x,y,z=move()
y=147+(y-y0)
#display.clear()
print("A按下")
display.fill(st7789.WHITE)
patternAup()
Aedge()
ledfunction()
main()
3.遇到的问题及说明
(1)第一个难题是对于姿态传感器的工作方式及初始化配置不明白,导致无法在RP2040上调用姿态传感器的功能。后期通过对I2C总线时序和传输方式的学习,参考了往次项目案例,了解了I2C总线的运作方式,并可以对姿态传感器进行配置和初始化。
(2)第二个难题是对于LED灯板如何定向操作某个灯不明白,导致不能展示出想要的图案。后不断查询资料,终于在MicroPython官网查到了“framebuf — 帧缓冲区操作”,调用函数FrameBuffer.fill_rect(x, y, w, h, c)等,成功实现了在LED灯板上定点绘图。
六.活动总结与未来计划
1.活动总结
通过本次的实践练习,我对树莓派RP2040和Micro Python的了解更加的深入,编程能力得到一定程度的提升,尤其是对原理图的使用有了更深刻的理解。在平时的学习生活中曾接触过树莓派4B的使用,故对本次的活动项目产生了极为浓厚的兴趣。但在编程的过程中,也意识到了自己的不足。首先是在电子方向上仍然存在着很多还未了解的专业知识,其次是代码的编写略显累赘,期望在下次的活动中有所改善。
2.未来计划
未来期望再次参加活动,了解并学习更多的知识,掌握更多的技能,开拓电子芯片在生活中运用的视野,改变思维模式,在专业上有既定的发展方向。