2021暑假一起练-用树莓派PICO设计电子相册
利用树莓派PICO扩展板实现了电子相册和音乐播放功能,过程中发现了PICO以及micropython的一些特性。
标签
嵌入式系统
MicroPython
树莓派PICO
987518529
更新2021-09-07
1949

硬禾学堂树莓派PICO扩展板制作的电子相册

  • 初识树莓派PICO以及项目目标Fg6gesESvY0_ip3V2CN9ZDl4avY7

树莓派PICO是树莓派基金会推出的微控制器,支持c++和micropython编程,采用双核心双核 ARM Cortex M0 +,运行频率高达133Mhz(在micropython环境下默认为125mhz)。与之前的arduino uno为代表的avr架构MCU相比是强大了很多,作为树莓派的老粉丝我当然要买来玩玩。

于是我便参加了此次硬禾学堂的项目《树莓派PICO的扩展功能板》,在给定的三个任务中,我选择了第二个,制作电子相册的任务。对于不太熟悉micropython的我来说,第二个任务比较容易完成。

设计一个带有背景音乐的数码相框

1.将多张照片保存在SD卡中,能够在240*240的LCD屏幕上以至少3种不同的切换模式轮流播放照片,模式的切换由按键控制

2.播放照片的同时,播放背景音乐,通过蜂鸣器或耳机插孔输出

3.利用姿态传感器,旋转板卡,照片可以自动旋转,保证无论板卡是什么方向,照片的方向都是正的

  • 几经修改的实现过程

首先要观察树莓派PICO扩展板的原理图。原理图对于一个电子设备非常重要,没有的话就无法下手开发。(在这里强烈谴责一下某些店铺销售电子模块不提供原理图的行为。)

结合上面的PICO引脚功能图和原理图,可以得到如下结论:

1,LCD240x240连接在SPI0接口上,LCD的reset引脚接在GPIO0,CS片选引脚接地,一直选中。

2,旋转编码器以及几个按钮连接的引脚有4.7k上拉电阻,编程时候应该检测低电平。

3,SD卡的连接有一些奇怪,不是SPI接法,似乎也不是SDIO,最终我决定软件模拟SPI来读取SD卡。

4,蜂鸣器接在GPIO16,支持PWM输出。注意到了三极管开关电路和续流二极管。

5,MMA7660加速度传感器挂载在IIC总线上,地址为0x4c。

6,红外接收部分没焊接。

 

FogYMldkmOTFuqPhM0CvfKqd9wZ-

然后下载资料,从raspberrypi官网,先后下载了几本关于PICO和micropython的手册。

此外还有st7789屏幕驱动芯片的手册和MMA7660加速度计的资料。

FgxDS6J_6ZpBfX1vB1g1x-Fui0_vFikz6p7d56SZA0BB2_iq2-7_bT89

关于micropython的学习,只要有python的基础,其实不难,只需要看看micropython官网的Quick reference for the RP2(RP2的速查手册)即可掌握IO外设,总线的调用函数。

Fj4m5IYctAdXNezEaH7CbeRX_IUV

能够基本使用PICO开发板,我做的第一件事是搞定屏幕驱动。感谢漂移君的ST7789py.py,让我可以轻松在屏幕上画像素点,划线,画矩形,填充颜色等。

图片显示部分,我在st7789py的基础上加了三个方法,来实现显示bmp图片。只需要传入一个文件指针即可。可以实现三种不同的显示风格。

部分代码如下:

# 修改库函数,实现整个屏幕bmp显示
    # 传入一个文件指针
    def showMyBmp(self,fid):
        # 跳过10字节
        rm=fid.read(10)
        rm=0
        p1=fid.read(4)
        p1=struct.unpack('<I',p1)   # 小端方式读取像素开始位置 
        p1=int(p1[0])
        
        fid.seek(0,p1)  # 移动指针到像素数据位置
        
        self._set_window(0,0,240,240)
        RGB=[0,0,0]
        for iii in range(240*240/_BUFFER_SIZE):
            for ii in range(_BUFFER_SIZE):
                # 读像素
                for i in range(3):
                    rgb=struct.unpack('B',fid.read(1))
                    RGB[i]=int(rgb[0])
                # 像素转为rgb565
                color=self.color565(RGB[0],RGB[2],RGB[1])
                # 像素转为二进制
                pixel=_encode_pixel(color)
                self.dc.on()
                self._write(None, pixel)

因为图片要储存在sd卡中,我需要读取SD卡中的bmp文件。使用softSPI和os.mount可以将SD卡挂载到文件系统上,直接访问。Os.chdir()和os.listdir()可以修改目录和显示目录下的文件,至于打开文件,直接使用with open 即可。

SD卡部分代码:

# 硬件初始化部分---------------------------------------------
# 挂载sd卡
# 灵魂接线,我不得不使用了软件SPI!!!
sd_spi=machine.SoftSPI(0,sck=machine.Pin(17,machine.Pin.OUT),mosi=machine.Pin(18,machine.Pin.OUT),miso=machine.Pin(19,machine.Pin.IN))
sd=sdcard.SDCard(sd_spi,cs=machine.Pin(22))
sd.init_spi(40_000_000)
sd.init_card()

 

MMA7660加速度传感器的工作原理是检测重力在XYZ轴方向的分量,从而计算当前的姿态。通过阅读手册,我发现只需要向地址xxxx写入1即可启动传感器,同时读取地址xxxx的数据,就可以判断不同姿态。

FjHwgmeeq4TByQw43qVrVu1o34GA

写入数据和读取数据的代码如下:

i2c=machine.SoftI2C(scl=MMA7660.sclpin,sda=MMA7660.sdapin,freq=400000)

        print('MMA7660 address={}'.format(i2c.scan()[0]))

        i2c.writeto_mem(76,7,b'1')

        time.sleep_ms(10)

        print('MMA7660 init')

为了方便使用MMA7660,我仿照st7789py.py也编写了MMA7660py.py。使用pythn中面向对象编程的方法,新建了MMA7660这个类,以及读取XYZ轴加速度和姿态的方法。

# 读MMA7660三轴加速度传感器数据
# 2021/7/31/1/20
import machine
import struct
import time
class MMA7660:
    sclpin=machine.Pin(11)
sdapin=machine.Pin(10)

    def init(self):
        global i2c
        i2c=machine.SoftI2C(scl=MMA7660.sclpin,sda=MMA7660.sdapin,freq=400000)
        print('MMA7660 address={}'.format(i2c.scan()[0]))
        # 使能
        i2c.writeto_mem(76,7,b'1')
        time.sleep_ms(10)
        print('MMA7660 init')
                
    def close(self):
        i2c.writeto_mem(76,7,b'0')
        print('MMA7660 closed')
    
    def _bit2num(self,x):
        xsign=(x&32)>>5  # 取符号位
        x=x&31     # 取数值
        if xsign==1:
            x=-x
        return x       
    def readxyz(self):
        x=i2c.readfrom_mem(76,0,8)
        y=i2c.readfrom_mem(76,1,8)
        z=i2c.readfrom_mem(76,2,8)        
        # 数据转换
        # 6bit结果输出,移位操作
        x=struct.unpack('<B',x)[0]
        y=struct.unpack('<B',y)[0]
        z=struct.unpack('<B',z)[0]        
        # 调用函数,6位转为数字
        x=MMA7660.bit2num(self,x)
        y=MMA7660.bit2num(self,y)
        z=MMA7660.bit2num(self,z)
    return x,y,z

    def readtilt(self):       
        tilt_data=i2c.readfrom_mem(76,3,8)
        tilt_data=struct.unpack('<B',tilt_data)
        tilt_data=int(tilt_data[0])       
        # Bafro 00 unknown 01 Front 10 Back
        Bafro=(tilt_data<<6)>>6
        # PoLa 000 unkonwn 001 Left 010 Right 101 Down 110 Up
        PoLa=(tilt_data&28)>>2
        pos=0
        if PoLa==0:
            pos='unknown!'
        elif PoLa==1:
            pos='Left'
        elif PoLa==2:
            pos='Rignt'
        elif PoLa==5:
            pos='Down'
        elif PoLa==6:
            pos='Up'
        return pos

 

需要注意的是,在python中(micropython也一样),BYTE类型(二进制)需要使用struct包进行转换,转换为整型,浮点型的数据。此外,对int型使用左移指令时候,会在右边自动补0使数字变大,因此不推荐左移位,推荐使用按位逻辑运算来实现一些数据的处理。

音乐播放部分,只需要输出特定频率的pwm'波即可模拟音符,我选择了一首欢乐颂。

# c调音符的频率

list_freqC=[60000,261,293,329,349,392,440,493]

 

  • 遇到的坑和修复

在我最初的计划中,要使用_thread库实现调用双核,分别运行图片显示程序以及音乐播放程序,使用定时中断来查询姿态变化并且调整图片方向。但是计划看起来很美,事实很残酷。在实际运行当中,图片显示程序运行一行后就卡住了,音乐播放也会变成糟糕的噪声。尽管官方例程运行很顺利。

Fg5AiP3df18Cef_j-EMJS4QAV-qe

在论坛查询到很多同样的问题,也有人反映给官方去修改。

Fp3zkKcCgJSH3_MmECFN43sLmzcdFkjqI8VXy9Pvaed8PFG8zgjqw9L9

但是我肯定来不及等官方修正错误了,所以我尝试单核心运行,同时使用定时器中断来实现所有功能。主进程是图片刷新,中断负责音符播放和姿态检测。

以下代码通过在中断函数中改变中断的设置实现音乐播放和空节拍。

def interp1(timer):
    global mpos,mlength
    if mpos>=mlength:
        mpos=0
    pwm.freq(list_freqC[list_music[mpos][0]])
    pwm.duty_u16(volume)
    timer1.init(freq=list_music[mpos][1],callback=interp1_2)

def interp1_2(timer):
    pwm.duty_u16(0)
    global mpos
    mpos=mpos+1
    timer1.init(freq=100,callback=interp1)

# 定时器2通过2秒的中断查询姿态
mma7660=MMA7660py.MMA7660()
mma7660.init()
# 初始方向
global pic_up
pic_up=2
def interp2(timer):
    global pic_up
    result=mma7660.readtilt()
    # print(result)
    if result=='unknown!':
        pic_up=2   # 默认正方向
    elif result=='Left':
        pic_up=3
    elif result=='Rignt':
        pic_up=1
    elif result=='Up':
        pic_up=2
    elif result=='down':
        pic_up=0
timer1=machine.Timer() # 定时器1
timer1.init(freq=1,mode=machine.Timer.PERIODIC,callback=interp1)
timer2=machine.Timer() # 定时器2
timer2.init(freq=0.5,mode=machine.Timer.PERIODIC,callback=interp2)
  • 完整代码

FrZd2MBLWIIsHYAlV4swFQCpjaw_

 

 

 

 

 

 

 

 

最后放上来本次项目的完整代码,包括主函数,st7789修改的部分,以及自己写的MMA7660py。
# 实现电子相册的全部功能
# 注意啊,pico的双线程模式存在问题,等官方修吧
import machine
import os
import sdcard
import st7789py as st7789
import time
import struct
import _thread
import MMA7660py
# 硬件初始化部分---------------------------------------------
# 挂载sd卡
# 灵魂接线,我不得不使用了软件SPI!!!
sd_spi=machine.SoftSPI(0,sck=machine.Pin(17,machine.Pin.OUT),mosi=machine.Pin(18,machine.Pin.OUT),miso=machine.Pin(19,machine.Pin.IN))
sd=sdcard.SDCard(sd_spi,cs=machine.Pin(22))
sd.init_spi(40_000_000)
sd.init_card()
x=os.mount(sd,'/sd')
os.chdir('sd/bmpfile')
print(os.listdir())

# 定义按键 旋转编码器的按键
thekey=machine.Pin(7,machine.Pin.IN)

# 定义lcd_spi引脚
sck=machine.Pin(2,machine.Pin.OUT)
mosi=machine.Pin(3,machine.Pin.OUT)
rst=machine.Pin(0,machine.Pin.OUT)
dc=machine.Pin(1,machine.Pin.OUT)

# 定义屏幕信息
width=240
height=240
CENTER_Y = int(width/2)
CENTER_X = int(height/2)
# 初始化lcd_spi
lcd_spi=machine.SPI(0,baudrate=40000000,polarity=1, phase =0,sck=sck,mosi=mosi)
print(lcd_spi)
# 实例化display对象
display=st7789.ST7789(lcd_spi,width,height,reset=rst,dc=dc,xstart=0,ystart=0,rotation=0)
bmplist=os.listdir()
print(bmplist)
# def playmusic():
#     music.play()
# 
# _thread.start_new_thread(playmusic,())
global pwm
pwm=machine.PWM(machine.Pin(16))

# 利用定时器中断放音乐
# c调音符的频率
list_freqC=[60000,261,293,329,349,392,440,493]
#音符列表,音符和演奏时间
# 欢乐颂
list_music=([3,2],[3,2],[4,2],[5,2],
            [5,2],[4,2],[3,2],[2,2],
            [1,2],[1,2],[2,2],[3,2],
            [3,2],[2,2],[2,2],[0,2],
            
            [3,2],[3,2],[4,2],[5,2],
            [5,2],[4,2],[3,2],[2,2],
            [1,2],[1,2],[2,2],[3,2],
            [2,2],[1,2],[1,2],[0,2],
            
            [2,2],[2,2],[3,2],[1,2],
            [2,2],[3,2],[3,2],[1,2],
            [2,2],[3,2],[3,2],[2,2],
            [1,2],[2,2],[-5,2],[3,2],
            
            [3,2],[3,2],[4,2],[5,2],
            [5,2],[4,2],[3,2],[2,2],
            [1,2],[1,2],[2,2],[3,2],
            [2,2],[1,2],[1,2],[0,2]
            
            )
# 播放需要的变量,定时器1通过多次中断播放音乐
global mpos,volume,mlength
mpos=0
mlength=len(list_music)
volume=0


def interp1(timer):
    global mpos,mlength
    if mpos>=mlength:
        mpos=0
    pwm.freq(list_freqC[list_music[mpos][0]])
    pwm.duty_u16(volume)
    timer1.init(freq=list_music[mpos][1],callback=interp1_2)
    

def interp1_2(timer):
    pwm.duty_u16(0)
    global mpos
    mpos=mpos+1
    timer1.init(freq=100,callback=interp1)
    
# 定时器2通过2秒的中断查询姿态
mma7660=MMA7660py.MMA7660()
mma7660.init()
# 初始方向
global pic_up
pic_up=2

def interp2(timer):
    global pic_up
    
    result=mma7660.readtilt()
    # print(result)
    if result=='unknown!':
        pic_up=2   # 默认正方向
    elif result=='Left':
        pic_up=3
    elif result=='Rignt':
        pic_up=1
    elif result=='Up':
        pic_up=2
    elif result=='down':
        pic_up=0
       
timer1=machine.Timer() # 定时器1
timer1.init(freq=1,mode=machine.Timer.PERIODIC,callback=interp1)
timer2=machine.Timer() # 定时器2
timer2.init(freq=0.5,mode=machine.Timer.PERIODIC,callback=interp2)

# 显示图片
# 图片方向:0 下  2 上 3 右 4 下
#time.sleep(2) # 等待姿态传感器确定方向

#按键中断,按下改变图片刷新方式!
pic_method=0  # 显示图片方法参数0,1,2三种

def keyint(irq):   # 手册可没说要加irq参数
    # display.fill(st7789.RED)
    global pic_method
    pic_method=pic_method+1
    if pic_method>2:
        pic_method=0
    print('pic_method==',pic_method)
    time.sleep_ms(100)
    irq_flag=1
        
thekey.irq(keyint,machine.Pin.IRQ_FALLING)

# 死循环中显示图片和改变显示方向
while True:
    for xx in bmplist:
        fid=open(xx,'rb')
        # 图片显示方向选择
        display.rotation(pic_up)
        # 图片显示方式选择
        if pic_method==0:
            display.showMyBmp(fid)
        elif pic_method==1:
            display.showMyBmp2(fid)
        elif pic_method==2:
            display.showMyBmp3(fid)
修改的st7789部分:
# 修改库函数,实现整个屏幕bmp显示
    # 传入一个文件指针
    def showMyBmp(self,fid):
        # 跳过10字节
        rm=fid.read(10)
        rm=0
        p1=fid.read(4)
        p1=struct.unpack('<I',p1)   # 小端方式读取像素开始位置 
        p1=int(p1[0])
        
        fid.seek(0,p1)  # 移动指针到像素数据位置
        
        self._set_window(0,0,240,240)
        RGB=[0,0,0]
        for iii in range(240*240/_BUFFER_SIZE):
            for ii in range(_BUFFER_SIZE):
                # 读像素
                for i in range(3):
                    rgb=struct.unpack('B',fid.read(1))
                    RGB[i]=int(rgb[0])
                # 像素转为rgb565
                color=self.color565(RGB[0],RGB[2],RGB[1])
                # 像素转为二进制
                pixel=_encode_pixel(color)
                self.dc.on()
                self._write(None, pixel)
                
    # 第二种显示图片方法            
    def showMyBmp2(self,fid):
        # 跳过10字节
        rm=fid.read(10)
        rm=0
        p1=fid.read(4)
        p1=struct.unpack('<I',p1)   # 小端方式读取像素开始位置 
        p1=int(p1[0])
        fid.seek(0,p1)  # 移动指针到像素数据位置
        self.fill(RED)
        time.sleep_ms(500)
        self.fill(GREEN)
        time.sleep_ms(500)
        self.fill(BLUE)
        
        self._set_window(0,0,240,240)
        RGB=[0,0,0]
        for iii in range(240*240/_BUFFER_SIZE):
            for ii in range(_BUFFER_SIZE):
                # 读像素
                for i in range(3):
                    rgb=struct.unpack('B',fid.read(1))
                    RGB[i]=int(rgb[0])
                # 像素转为rgb565
                color=self.color565(RGB[0],RGB[2],RGB[1])
                # 像素转为二进制
                pixel=_encode_pixel(color)
                self.dc.on()
                self._write(None, pixel)
                
    def showMyBmp3(self,fid):
        for ii in range(9):
            R=random.randint(0,255)
            G=random.randint(0,255)
            B=random.randint(0,255)
            color=color565(R,G,B)
            self.fill_rect(0,240-24*ii,240,24,color)

MMA7660py部分:
# 读MMA7660三轴加速度传感器数据
# 2021/7/31/1/20
import machine
import struct
import time

class MMA7660:
    sclpin=machine.Pin(11)
    sdapin=machine.Pin(10)
    
    def init(self):
        global i2c
        i2c=machine.SoftI2C(scl=MMA7660.sclpin,sda=MMA7660.sdapin,freq=400000)
        print('MMA7660 address={}'.format(i2c.scan()[0]))
        # 使能
        i2c.writeto_mem(76,7,b'1')
        time.sleep_ms(10)
        print('MMA7660 init')
        
        
    def close(self):
        i2c.writeto_mem(76,7,b'0')
        print('MMA7660 closed')
        
        
    def _bit2num(self,x):
        xsign=(x&32)>>5  # 取符号位
        x=x&31     # 取数值
        if xsign==1:
            x=-x
        return x
        

    def readxyz(self):
        x=i2c.readfrom_mem(76,0,8)
        y=i2c.readfrom_mem(76,1,8)
        z=i2c.readfrom_mem(76,2,8)
        
        # 数据转换
        # 6bit结果输出,移位操作
        x=struct.unpack('<B',x)[0]
        y=struct.unpack('<B',y)[0]
        z=struct.unpack('<B',z)[0]
        
        # 调用函数,6位转为数字
        x=MMA7660.bit2num(self,x)
        y=MMA7660.bit2num(self,y)
        z=MMA7660.bit2num(self,z)

        return x,y,z

    def readtilt(self):
        
        tilt_data=i2c.readfrom_mem(76,3,8)
        tilt_data=struct.unpack('<B',tilt_data)
        tilt_data=int(tilt_data[0])
        
        # Bafro 00 unknown 01 Front 10 Back
        Bafro=(tilt_data<<6)>>6

        # PoLa 000 unkonwn 001 Left 010 Right 101 Down 110 Up
        PoLa=(tilt_data&28)>>2
        pos=0
        if PoLa==0:
            pos='unknown!'
        elif PoLa==1:
            pos='Left'
        elif PoLa==2:
            pos='Rignt'
        elif PoLa==5:
            pos='Down'
        elif PoLa==6:
            pos='Up'
        return pos
附件下载
doublethread.py
主程序
MMA7660py.py
MMA7660传感器驱动程序
st7789py.py
屏幕驱动程序(经过修改)
团队介绍
来自南京信息工程大学的电子爱好者,我们希望热爱能填平学习途中的沟壑,热情能战胜项目中的挫折。理论固然重要。但是只有动手才能真正的改变这个世界。熟练掌握ARDUINO开发,立创eda使用,以及51单片机。希望改变中国,改变世界的仍然是年轻人。
团队成员
987518529
电子爱好者一枚。。喵叽
徐玚
南京信息工程大学人工智能学院
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号