基于树莓派PICO的带有背景音乐的数码相框
本项目是基于电子森林平台提供的树莓派PICO扩展板,完成了带有背景音乐的数码相框的设计。主要内容包括sd卡的读取、mma7660姿态传感器的使用、st7789显示、利用蜂鸣器播放音乐、按键中断等。
标签
嵌入式系统
ST7789
蜂鸣器
mma7660
sd卡
Haoami
更新2021-09-05
1472

本项目是基于电子森林平台提供的树莓派PICO扩展板,完成了带有背景音乐的数码相框的设计。主要内容包括sd卡的读取、mma7660姿态传感器的使用、st7789显示、利用蜂鸣器播放音乐、按键中断等。

实现了以下功能:

  1. 将多张照片保存在SD卡中,能够在240*240的LCD屏幕上以至少3种不同的切换模式轮流播放照片,模式的切换由按键控制
  2. 播放照片的同时,播放背景音乐,通过蜂鸣器或耳机插孔输出
  3. 利用姿态传感器,旋转板卡,照片可以自动旋转,保证无论板卡是什么方向,照片的方向都是正的

img

第一部分:st7789显示屏的使用

在PICO中导入st7789库(控制显示屏),新建fonts文件夹,在其中导入vga1_16x32和vga2_8x8两个库(字体)。我们接下来的操作就需要用到这三个库。下面是显示部分代码(由于我编写了一些动画所以显示部分代码略长)。

为了显示图片,必须要将240*240的图片转码成Binary RGB565 Swap文件,通过sd卡读入,每次通过display.blit_buffer()函数刷新一部分。推荐一个图片转码网站:https://lvgl.io/tools/imageconverter

我们发现了一个问题,就是图片刷新频率始终上不去。我们从SD卡中读取图片像素信息,并将其存入树莓派的内存中,再通过spi将数据写入st7789显示屏以显示图像。可能限制刷新速率的因素有sd卡读取速度,树莓派的内存大小(决定一次读入的数据量),st7789的写入速率。首先我们计算一张图片需要多大的存储空间,我们采用240*240像素的图片(显示屏的尺寸),每个像素点由16bit数据储存,一张图片就是112.5Kb。SD卡和st7789通过SPI通信(SPI的理论最大通信速率很快,但还是受到CPU的空闲情况和处理能力的影响),每次向st7789写入一个满屏图片约需200ms,这个速度比较慢,但还不是决定刷新频率的主要因素。

树莓派的内存大小限制了每次不能完全读取一整张照片,经过调试后我决定每次只读取48行的图片信息,再写入st7789,如此重复5次,完成一整张照片的读取,这造成了刷新一张照片需要3s左右,非常缓慢。

我想可以这样提升速度:提高SPI速率;在完成一张照片的写入后马上进行下一张照片的读取,并在用户按下切换按钮后刷新显示,这样省下了sd卡的读取时间;采用DMA技术每次直接从sd卡读取数据写入st7789,不需要树莓派作为储存中介。欢迎大家尝试并分享。

import sdcard
from fonts import vga2_8x8 as font1
from fonts import vga1_16x32 as font2
​
image_file = "/sd/hk02.bin" #图片文件地址
switch_method=1 #图片切换方式 1 2 3
​
# 连接屏幕
st7789_res = 0
st7789_dc = 1
disp_width = 240
disp_height = 240
spi_sck=machine.Pin(2)
spi_tx=machine.Pin(3)
spi0=machine.SPI(0,baudrate=4000000, phase=0, 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)
#画整圆
def draw_circle(xpos0, ypos0, rad, col=st7789.color565(255, 255, 255)):
   x = rad-1
   y = 0
   dx = 1
   dy = 1
   err = dx - (rad << 1)
   while x >= y:
       display.pixel(xpos0 + x, ypos0 + y, col)
       display.pixel(xpos0 + y, ypos0 + x, col)
       display.pixel(xpos0 - y, ypos0 + x, col)
       display.pixel(xpos0 - x, ypos0 + y, col)
       display.pixel(xpos0 - x, ypos0 - y, col)
       display.pixel(xpos0 - y, ypos0 - x, col)
       display.pixel(xpos0 + y, ypos0 - x, col)
       display.pixel(xpos0 + x, ypos0 - y, col)
       if err <= 0:
           y += 1
           err += dy
           dy += 2
       if err > 0:
           x -= 1
           dx += 2
           err += dx - (rad << 1)
#画半圆
def draw_halfcircle(xpos0, ypos0, rad, col=st7789.color565(255, 255, 255),pos="Down"):
   x = rad-1
   y = 0
   dx = 1
   dy = 1
   err = dx - (rad << 1)
   while x >= y:
       if pos=="Down":
           display.pixel(xpos0 + x, ypos0 + y, col)
           display.pixel(xpos0 + y, ypos0 + x, col)
           display.pixel(xpos0 - y, ypos0 + x, col)
           display.pixel(xpos0 - x, ypos0 + y, col)
       elif pos=="Up":
           display.pixel(xpos0 - x, ypos0 - y, col)
           display.pixel(xpos0 - y, ypos0 - x, col)
           display.pixel(xpos0 + y, ypos0 - x, col)
           display.pixel(xpos0 + x, ypos0 - y, col)
       elif pos=="Left":
           display.pixel(xpos0 - y, ypos0 + x, col)
           display.pixel(xpos0 - x, ypos0 + y, col)
           display.pixel(xpos0 - x, ypos0 - y, col)
           display.pixel(xpos0 - y, ypos0 - x, col)
       elif pos=="Right":
           display.pixel(xpos0 + x, ypos0 + y, col)
           display.pixel(xpos0 + y, ypos0 + x, col)
           display.pixel(xpos0 + y, ypos0 - x, col)
           display.pixel(xpos0 + x, ypos0 - y, col)
       if err <= 0:
           y += 1
           err += dy
           dy += 2
       if err > 0:
           x -= 1
           dx += 2
           err += dx - (rad << 1)
           
# 显示图片
def display_image():
   global image_file
   global switch_method
   mySong.stop()
   f_image = open(image_file, 'rb')
   if switch_method==1: # 刷新图片方法1
       for column in range(0,240,40):
           buf=f_image.read(480*40)
           display.blit_buffer(buf, 0, column, 240, 40)
   elif switch_method==2:  # 刷新图片方法2
       rows=6
       for i in range(0,48/rows):
           f_image.seek(480*rows*i,0)
           buf=f_image.read(480*rows)
           display.blit_buffer(buf, 0, rows*i, 240, rows)
           f_image.seek(480*48*2+480*rows*i,0)
           buf=f_image.read(480*rows)
           display.blit_buffer(buf, 0, 48*2+rows*i, 240, rows)
           f_image.seek(480*48*4+480*rows*i,0)
           buf=f_image.read(480*rows)
           display.blit_buffer(buf, 0, 48*4+rows*i, 240, rows)
       utime.sleep(0.5)
       for i in range(0,48/rows):
           f_image.seek(480*48*1+480*rows*i,0)
           buf=f_image.read(480*rows)
           display.blit_buffer(buf, 0, 48*1+rows*i, 240, rows)
           f_image.seek(480*48*3+480*rows*i,0)
           buf=f_image.read(480*rows)
           display.blit_buffer(buf, 0, 48*3+rows*i, 240, rows)
   elif switch_method==3: # 刷新图片方法3
       for y in range(0,5):
           for x in range(0,5):
               if (x+y)%2==0:
                   display.fill_rect(48*x,48*y,48,48,color=st7789.WHITE)
                   utime.sleep(0.2)
       for column in range(0,240,48):
           buf=f_image.read(480*48)
           display.blit_buffer(buf, 0, column, 240, 48)      
   
   f_image.close()
   mySong.stopped = False
   return
​
try:
   # 初始动画
   for i in range(80):      
       display.pixel(3*i,i*3,st7789.color565(255,255,255))
       display.pixel(240-3*i,3*i,st7789.color565(255,255,255))
       utime.sleep(0.05)
   display.text(font2, "V", 88, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
   display.text(font2, "I", 88+16, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
   display.text(font2, "E", 88+16*2, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
   display.text(font2, "W", 88+16*3, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
​
   draw_halfcircle(120,120,40,col=st7789.WHITE,pos="Left")
   draw_halfcircle(120,120,40,col=st7789.WHITE,pos="Up")
   utime.sleep(0.4)
   draw_halfcircle(120,120,50,col=st7789.WHITE,pos="Right")
​
   display.text(font1, "Listen to the wind", 120-9*8, 210, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
   display.text(font1, "wait for the flowers to bloom", 4, 222, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(1)
   
   display.fill(st7789.BLACK)
   display_image()
   while True:
       if posture()==1:
           display_image()
       mySong.tick()
       utime.sleep(0.04)
       
except KeyboardInterrupt:
   #结束动画
   print("QUIT")
   display.fill(st7789.BLACK)
   for i in range(80):      
       display.pixel(3*i,i*3,st7789.color565(255,255,255))
       display.pixel(240-3*i,3*i,st7789.color565(255,255,255))
       utime.sleep(0.005)
   display.text(font2, "B", 96, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
   display.text(font2, "Y", 96+16, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
   display.text(font2, "E", 96+16*2, 104, color=st7789.WHITE, background=st7789.BLACK)
   utime.sleep(0.2)
​
   draw_halfcircle(120,120,40,col=st7789.WHITE,pos="Right")
   draw_halfcircle(120,120,40,col=st7789.WHITE,pos="Up")
   utime.sleep(0.4)
   draw_halfcircle(120,120,50,col=st7789.WHITE,pos="Left")
   
   mySong.stop()
   
   display.text(font1, "2021-8-19", 84, 210, color=st7789.WHITE, background=st7789.BLACK)
   display.text(font1, "Completed by ZH", 60, 222, color=st7789.WHITE,, background=st7789.BLACK)

第二部分:mma7660姿态传感器的读取和使用

mma7660采用I2C与树莓派通信,在使用前一定要详细阅读其技术手册,其内部寄存器0x03内储存的是姿态信息,对其读取就可知道板卡是正立的、倒立的还是倾斜的。读取姿态信息后就传给st7789显示屏以改变其显示方向。最终达到无论怎么旋转板卡,显示的图片都是正向的效果。

不足之处在于,本项目中采取在主函数中不断循环读取mma7660的信息,这样占用了处理器。仔细观察发现mma7660的INT端接入了PICO的GPIO09端口,通过改变mma7660的设置可以使传感器在姿态改变时发出一个中断信号,此时再读取姿态信息和改变显示器。

from machine import Pin,SPI,I2C,SoftSPI
​
last_pose=0 #记录上一次的位置状态
pose=0 #记录当前位置信息
​
# mma7660配置
mma7660_SDA=machine.Pin(10)
mma7660_SCL=machine.Pin(11)
mma7660 = I2C(1, scl=mma7660_SCL, sda=mma7660_SDA, freq=4000000)
mma7660.writeto_mem(76, 7, b'11000001') # 向寄存器 0x07写入 1 ,开启mma7660的Active Mode
​
# 姿态的读取
def posture():
   global last_pose
   global pose
   TILT=mma7660.readfrom_mem(76, 3, 8, addrsize=8) # 姿态数据读取
   if (TILT[0]==0b00000000 or TILT[0]==0b00000001 or TILT[0]==0b00000010):
       pose=0 # 位置状态Unkown,则默认为Up
       display.rotation(0)
       display.xstart = 0
       display.ystart = 0
   if (TILT[0]==0b00011000 or TILT[0]==0b00011001 or TILT[0]==0b00011010):
       pose=0 # 位置状态Up
       display.rotation(0)
       display.xstart = 0
       display.ystart = 0
   if (TILT[0]==0b00001000 or TILT[0]==0b00001001 or TILT[0]==0b00001010):
       pose=3 # 位置状态Right
       display.rotation(3)
       display.xstart = 80
       display.ystart = 0
   if (TILT[0]==0b00010100 or TILT[0]==0b00010101 or TILT[0]==0b00010110):
       pose=2 # 位置状态Down
       display.rotation(2)
       display.xstart = 0
       display.ystart = 80
   if (TILT[0]==0b00000100 or TILT[0]==0b00000101 or TILT[0]==0b00000110):
       pose=1 # 位置状态Left
       display.rotation(1)
       display.xstart = 0
       display.ystart = 0
   
   if last_pose!=pose:
       last_pose=pose
       if (switch_method==1 or switch_method==2):
           display.fill(st7789.BLACK)
       return 1
   else:
       return 0

第三部分:SD卡的读取

sd卡通过软件SPI与树莓派通信。需要读取其中文件时,必须创建一个文件指针打开目标读取文件。

from machine import Pin,SPI,I2C,SoftSPI
import sdcard
import uos
​
# 连接SD卡
sd_spi= machine.SoftSPI(baudrate=1000000,polarity=0,bits=8,firstbit=SPI.MSB,sck=Pin(17), mosi=Pin(18), miso=Pin(19))
sd= sdcard.SDCard(spi=sd_spi,cs=machine.Pin(22,machine.Pin.OUT))
sd.init_card()
# 新建并切换到sd卡目录
uos.mount(sd,'/sd')
uos.chdir('sd/')
print("SD_Listdir:",uos.listdir()) #显示sd卡内的内容
​
# 读取图片文件并显示图片示例
image_file = "/sd/hk02.bin" #图片文件地址
f_image = open(image_file, 'rb')
for column in range(0,240,40):
   buf=f_image.read(480*40)
   display.blit_buffer(buf, 0, column, 240, 40)
image_file = "/sd/hk02.bin" #图片文件地址

第四部分:蜂鸣器播放音乐

这一部分我借鉴了电子森林中其它同学的代码,没有做过多改动。首先要向PICO中拷入buzzer_music库,以控制蜂鸣器。

网站https://onlinesequencer.net/可以将自己的MIDI文件变成如下所示的音符信息字符串,还可以自己写音乐并实时播放,随时可以导出,搭配buzzer_music库文件十分好用。

from buzzer_music import music
​
song = '4 C#5 1 0;8 D#5 1 0;12 C5 1 0;16 C#5 1 0;18 C5 1 0;20 A#4 1 0;22 C5 1 0;24 G#4 1 0;30 G#4 1 0;31 A4 1 0;32 A#4 1 0;36 C#5 1 0;40 A#4 1 0;42 A#5 1 0;44 G#5 1 0;46 F#5 1 0;48 F5 1 0;50 F#5 1 0;52 G#5 1 0;54 F5 1 0;56 D#5 1 0;0 F5 1 0;68 C#5 1 0;72 D#5 1 0;76 C5 1 0;80 C#5 1 0;82 C5 1 0;84 A#4 1 0;86 C5 1 0;88 G#4 1 0;94 G#4 1 0;95 A4 1 0;96 A#4 1 0;100 C#5 1 0;104 A#4 1 0;106 A#5 1 0;108 G#5 1 0;110 F#5 1 0;64 F5 1 0;112 G#5 1 0;114 A#5 1 0;116 C6 1 0;118 C#6 1 0;120 D#6 1 0;128 C#6 1 0;134 F6 1 0;140 C#6 1 0;144 C6 1 0;150 D#6 1 0;156 C6 1 0;158 G#5 1 0;159 A5 1 0;160 A#5 1 0;166 C#6 1 0;172 A#5 1 0;176 C6 1 0;180 D#6 1 0;182 G#5 1 0;184 A#5 1 0;186 B5 1 0;188 C6 1 0;192 C#6 1 0;198 F6 1 0;204 C#6 1 0;208 C6 1 0;214 D#6 1 0;220 C6 1 0;222 G#5 1 0;223 A5 1 0;224 A#5 1 0;230 C#6 1 0;236 A#5 1 0;240 C6 1 0;244 D#6 1 0;246 F6 1 0;248 D#6 1 0;250 C#6 1 0;252 C6 1 0;254 G#5 1 0'
mySong = music(song)
​
while True:
   if posture()==1:
       mySong.tick() #播放音乐
       utime.sleep(0.04)
except KeyboardInterrupt:
   mySong.stop() #停止播放

第五部分:按键中断

按键的中断函数使用关键在于Pin.irq()函数的使用,https://docs.singtown.com/micropython/zh/latest/openmvcam/library/machine.Pin.html?highlight=irq#machine.Pin.irq中有很详细的介绍中断函数的用法。

K1键 切换图片切换方式,K2键切换图片(自动切换图片的代码在源文件可以正常使用,但是已被注释)

from machine import Pin,SPI,I2C,SoftSPI
​
image_file = "/sd/hk02.bin" #图片文件地址
num=1 #当前图片序号
​
# 连接key1(切换图片切换方式)
def switch_methodSwitch():
   global switch_method
   switch_method=switch_method+1
   if switch_method>3:
       switch_method=1
   print("switch_method",switch_method)
   return
key1=Pin(7,Pin.IN)
key1.irq(handler=lambda pin:switch_methodSwitch(),trigger=Pin.IRQ_FALLING)
​
# 连接key2(切换图片)
def ImageSwitch():
   global image_file
   global num
   num=num+1
   if num>5:
       num=1
   print(num)
   if num==1:
       image_file="/sd/hk01.bin"
   elif num==2:
       image_file="/sd/hk02.bin"
   elif num==3:
       image_file="/sd/hk03.bin"
   elif num==4:
       image_file="/sd/hk04.bin"
   else:
       image_file="/sd/hk05.bin"
   display_image()
   return
key2=Pin(8,Pin.IN)
key2.irq(handler=lambda pin:ImageSwitch(),trigger=Pin.IRQ_FALLING)

Tips:

源代码、库文件和技术文档均已经贴在附件中,要理解各部分的用法和工作原理,必须要多阅读器件的技术手册和树莓派PICO官方给的文档,还有Miropython的使用介绍https://docs.singtown.com/micropython/zh/latest/openmvcam/index.html

附件下载
Code.rar
资料.rar
main.py
团队介绍
浙江大学信电学院
团队成员
Haoami
浙江大学信电学院大二学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号