2023寒假在家练-使用STEP PICO制作简易音乐播放器
1. 利用板上的蜂鸣器,播放一首音乐 2. 利用板上的电位计调节电压从0-3.3V之间变化,在OLED显示屏上显示电压值,并使用这个电压调节音乐音量大小 3. 利用板上的12个彩色LED灯直观地展示音量大小
标签
嵌入式系统
数字逻辑
显示
MicroPython
2023寒假在家练
Jollies
更新2023-03-29
北京理工大学
1165

0.任务要求及效果展示

  1. 利用板上的蜂鸣器,播放一首音乐
  2. 利用板上的电位计调节电压从0-3.3V之间变化,在OLED显示屏上显示电压值,并使用这个电压调节音乐音量大小
  3. 利用板上的12个彩色LED灯直观地展示音量大小

      以下是本项目的设计思路框图:

      FsxDdJGurT2RWCBc1R0QTWoQ9vJI

     通过STEP Pico的ADC(模拟数字转换器)采集电位器输入的电压,并换算为实际电压与音量,并根据得到的电压和音量数值对蜂鸣器响度(调节PWM占空比)、WS2812B亮灭以及OLED屏幕显示数值进行控制,从而实现任务要求。

     以下是功能展示:

     Fl9uexkNmiWbR4jJH6NwU-1pRKCZ

    FvLhQQOLhEfi234hECYBO6gMP-ih

     其中SSD1306显示出了此时的音乐音量和电位器电压,同时,周围的LED灯也随着音乐音量变化而有规律地亮灭,蜂鸣器发出对应的音乐(见内嵌视频)

1.板卡详情和环境配置

  1. Thonny & Micropython

Thonny是一种简洁易用的Micropython IDE,开箱即用,且易于安装,只需将设备插入电脑,我们就可以在Thonny中看到设备的串口信息,同时上传程序和在线调试也变得十分方便。

     2. 基于STEP Pico的嵌入式系统学习平台

本次寒假练活动中使用了Raspberry Pi Pico兼容版STEP Pico开发板,配合硬禾学堂出品的基于STEP Pico的嵌入式系统学习平台(详情:https://www.eetree.cn/project/detail/584),我得以完成指定的一系列任务要求。

Fr4bJoOd0UUlyxLAQOrlCzbWTp9d

2.程序实现

所有程序使用Micropython编写,本来打算使用熟悉的Arduino配合C/C++进行编写,但由于操作更加繁琐,故使用更加简单、更易在线调试的Micropython+Thonny组合。

1.模块与库介绍

1.SSD1306——128*64 OLED显示屏

本项目中,显示屏模块选用了SSD1306模块,这是一块128*64分辨率的单色OLED显示屏,由电子森林发布的官方资料,我们可以找到它对应的引脚接线情况。

支持驱动SSD1306这块屏幕的库有很多,此处参考了GitHub上的ssd1306.py并导入至Pico中,这样我们就可以通过导入ssd1306库中的SSD1306_SPI方法对SPI连接的SSD1306模块进行控制了。

以下是初始化配置:

from ssd1306 import SSD1306_SPI
import framebuf
oled = SSD1306_SPI(128, 64, spi, Pin(9),Pin(8), Pin(12))

以下是使用函数例:

oled.text("Raspberry Pi", 15, 0)
oled.text("Music Player",15,15) #oled.text()可以在对应坐标显示对应字符串

oled.show() #oled.show()可以将oled.text()设定的所需显示的内容显示出来
oled.fill(0) #清屏

2.WS2812B——串联全彩LED

本项目中,LED模块选用了WS2812B,它是一种可以串联连接并控制的全彩LED,其RGB分量各自可以设置在0~255之间,此处我们使用NeoPixel库对其进行控制。

以下是初始化配置:

from Neopixel import Neopixel
led = Neopixel(12,0,18,"RGB")

以下是使用函数例:

R=0
G=255
B=128
led[0:12]=(R,G,B) #将0-11号LED以R,G,B格式输出对应颜色
led.brightness(10) #将LED亮度统一设置为10(范围为0-255)

3.蜂鸣器

蜂鸣器是一种简单易用的发声单元,在其正负极加上不同的电压即可发出不同频率的声音,而通过PWM,我们可以输出不同的电压,所以,只要我们将蜂鸣器正极接在支持PWM的管脚上,负极接地,在程序中设定PWM输出的频率和占空比即可使蜂鸣器发出指定频率的声音和音量大小。

此处输出频率由事先设定的乐谱决定,而占空比可以由板卡自带的可调电位计进行调节。

以下是初始化配置:

from machine import Pin,PWM
pwm = PWM(Pin(19, Pin.OUT)) #创建PWM对象

以下是使用函数例:

s=50
pwm.freq(523) #设定PWM频率为523Hz
pwm.duty_u16(s) #设定占空比

2.开发过程与整体实现

  1. 利用板上的蜂鸣器,播放一首音乐

首先我需要找到音乐和频率之间的对应关系,我们可以预先将音符和频率的对应关系提前编入代码中,然后只需将乐谱输入至程序中即可。

此处我使用了国际歌的旋律,以下是它的简谱表示:国际歌简谱

FrNhdkAbSo9Q_C8Q6zUyNWBuF9F_

将简谱的音高和节奏按照对应关系输入至程序中:

C5 = 523
CS5 = 554
D5 = 587
DS5 = 622
E5 = 659
F5 = 698
FS5 = 740
G5 = 784
GS5 = 831
A5 = 880
AS5 = 932
B5 = 988
C6 = 1047
CS6 = 1109
D6 = 1175
DS6 = 1245
E6 = 1319
F6 = 1397
FS6 = 1480
G6 = 1568
GS6 = 1661
A6 = 1760
AS6 = 1865
B6 = 1976


internationale = [
    F5,0,0,0,
    AS5,0,0,0,0,0,A5,0,C6,0,AS5,0,F5,0,D5,0,
    G5,0,0,0,0,0,G5,0,DS5,0,0,0,10,0,G5,0,
    C6,0,0,0,0,0,AS5,0,A5,0,G5,0,F5,0,DS5,0,
    D5,0,0,0,0,0,0,0,0,0,0,0,F5,0,0,0,
    AS5,0,0,0,0,0,A5,0,C6,0,AS5,0,F5,0,D5,0,
    G5,0,0,0,0,0,0,0,DS5,0,G5,0,C6,0,AS5,0,
    A5,0,0,0,C6,0,0,0,DS6,0,0,0,A5,0,0,0,
    AS5,0,0,0,0,0,0,0,0,0,10,0,D6,0,C6,0,
    A5,0,0,0,0,0,0,0,G5,0,A5,0,AS5,0,G5,0,
    A5,0,0,0,0,0,0,0,F5,0,F5,0,E5,0,F5,0,
    G5,0,0,0,0,0,G5,0,C6,0,0,0,0,0,AS5,0,
    A5,0,0,0,0,0,0,0,0,0,10,0,C6,0,0,0,
    C6,0,0,0,0,0,A5,0,F5,0,F5,0,E5,0,F5,0,
    D6,0,0,0,0,0,0,0,AS5,0,G5,0,A5,0,AS5,0,
    A5,0,0,0,C6,0,0,0,AS5,0,0,0,G5,0,0,0,
    F5,0,0,0,0,0,0,0,0,0,10,0,D6,0,C6,0,
    AS5,0,0,0,0,0,0,0,F5,0,0,0,0,0,D5,0,
    G5,0,0,0,0,0,0,0,DS5,0,10,0,C6,0,0,AS5,
    A5,0,0,0,0,0,0,0,G5,0,0,0,F5,0,0,0,
    F5,0,0,0,0,0,0,0,0,0,10,0,F5,0,0,0,
    D6,0,0,0,0,0,0,0,C6,0,0,0,F5,0,0,0,
    AS5,0,0,0,0,0,0,0,A5,0,0,0,0,0,A5,0,
    G5,0,0,0,0,0,FS5,0,G5,0,0,0,C6,0,0,0,
    C6,0,0,0,0,0,0,0,0,0,10,0,D6,0,0,C6,
    AS5,0,0,0,0,0,0,0,F5,0,0,0,0,0,D5,0,
    G5,0,0,0,0,0,0,0,DS5,0,10,0,C6,0,0,AS5,
    A5,0,0,0,0,0,0,0,G5,0,0,0,F5,0,0,0,
    D6,0,0,0,0,0,0,0,0,0,0,0,D6,0,0,0,
    F6,0,0,0,0,0,0,0,DS6,0,0,0,D6,0,0,0,
    C6,0,0,0,0,0,D6,0,DS6,0,0,0,10,0,DS6,0,
    D6,0,0,0,0,0,D6,0,C6,0,0,0,0,0,C6,0,
    AS5,0,0,0,0,0,0,0,0,0,0,0,
]

然后对蜂鸣器进行初始化设置,首先确定蜂鸣器的正极管脚,此处它和开发板的19号引脚相连,故此处对19号创建一个PWM对象:

pwm = PWM(Pin(19, Pin.OUT))

PWM对象有两个参数,freq和duty_u16,前者为PWM频率,和乐曲对应的频率一致,后者为占空比,决定了乐曲的音量。我们可以遍历乐谱以获得需要播放的频率,可以通过读取电位器电压来获取需要播放的音量。代码如下:

while True:
    for note in song:
        print("freq:{}".format(note))
        duty=int(s/16)   
        if note:
            pwm.freq(note)
        pwm.duty_u16(duty)
        sleep_ms(170)
        pwm.duty_u16(0)

     2. 利用板上的电位计调节电压从0-3.3V之间变化,在OLED显示屏上显示电压值,并使用这个电压调节音乐音量大小

板上电位计的电压范围是0-3.3V,对应到内部ADC采集到的电压数值则为0-65535,0对应0V,65535对应3.3V,中间的值以正比例作映射即可,代码如下:

from machine import Pin,SPI,ADC,PWM
adc=ADC(Pin(28))

while True:
    s=adc.read_u16()
    q=str(int(100*s/65535))
    voltage=str(round(3.3*s/65535,3))

在得到电压后,我们将电压值传入oled.text()即可实时的显示出电压大小。

我们还需要使用电位计控制音乐音量大小,通常音量大小和蜂鸣器PWM占空比成对应关系,因此我们只需要得出电位计电压占最大电压(3.3V)的百分比,即可得到所需的占空比和音量大小

在得到音量大小后,我们将值传入oled.text()即可实时的显示出音量百分比,代码如下:

while True:
    s=adc.read_u16()   
    q=str(int(100*s/65535))
    voltage=str(round(3.3*s/65535,3))
    oled.text("Voltage: "+voltage+"V",8,28)
    oled.text("Volume: "+q+"%", 15, 40)
    oled.text("Internationale", 8, 50)
    led.show()
    oled.show()
    oled.fill(0)
    sleep_ms(100)

以下是OLED屏幕显示出的电压大小和音量大小效果图:

FmOjVOatXF7Oqs8NFuk2y508jkI3

     3. 利用板上的12个彩色LED灯直观地展示音量大小

得到音量大小后,我们可以将最大音量平均分为12个范围,当音量大小落入其中一个范围时,对应范围的LED点亮,其它LED熄灭,这样我们就可以用LED直观地表示音量大小了。

while True:
    s=adc.read_u16()
    Vol=int(100*s/65535)
    Segment=100/12
    ColorSeg=int(255/12)
    for i in range(0,12):
        led[i]=(255-i*ColorSeg,i*ColorSeg,0)
    if Vol==0:
        led[0:12]=(0,0,0)
    elif Vol<Segment and Vol>0:
        led[0]=(255,0,0)
        led[1:12]=(0,0,0)
    elif Vol<2*Segment and Vol>=Segment:
        led[1]=(255-ColorSeg,ColorSeg,0)
        led[2:12]=(0,0,0)
    elif Vol<3*Segment and Vol>=2*Segment:
        led[2]=(255-2*ColorSeg,2*ColorSeg,0)
        led[3:12]=(0,0,0)
    elif Vol<4*Segment and Vol>=3*Segment:
        led[3]=(255-3*ColorSeg,3*ColorSeg,0)
        led[4:12]=(0,0,0)
    elif Vol<5*Segment and Vol>=4*Segment:
        led[4]=(255-4*ColorSeg,4*ColorSeg,0)
        led[5:12]=(0,0,0)
    elif Vol<6*Segment and Vol>=5*Segment:
        led[5]=(255-5*ColorSeg,5*ColorSeg,0)
        led[6:12]=(0,0,0)
    elif Vol<7*Segment and Vol>=6*Segment:
        led[6]=(255-6*ColorSeg,6*ColorSeg,0)
        led[7:12]=(0,0,0)
    elif Vol<8*Segment and Vol>=7*Segment:
        led[7]=(255-7*ColorSeg,7*ColorSeg,0)
        led[8:12]=(0,0,0)
    elif Vol<9*Segment and Vol>=8*Segment:
        led[8]=(255-8*ColorSeg,8*ColorSeg,0)
        led[9:12]=(0,0,0)
    elif Vol<10*Segment and Vol>=9*Segment:
        led[9]=(255-9*ColorSeg,9*ColorSeg,0)
        led[10:12]=(0,0,0)
    elif Vol<11*Segment and Vol>=10*Segment:
        led[10]=(255-10*ColorSeg,10*ColorSeg,0)
        led[11]=(0,0,0)
    elif Vol<=12*Segment and Vol>=11*Segment:
        led[11]=(255-11*ColorSeg,11*ColorSeg,0)

最后呈现的效果如下图所示:

FpWWN7AOm8i_Dx__IzMtvpR2BDll

     4. 使用双线程编程将以上功能合成一个程序

在实际实验过程中,我发现,如果将上述所有功能全部使用单一线程进行处理,则音乐播放和信息显示都将出现不可忽视的延时和卡顿,而这一点是不可接受的,因此我产生了一种想法——RP2040是一个双核MCU,其支持双线程工作,那么我们可以使用_thread库,将三个功能分为两个线程进行处理:音乐播放单独占用一个线程,OLED信息显示、灯光控制和电位器读取占用一个线程,两个线程之间的变量交流使用全局变量即可。

以下是音乐播放线程:

_thread.start_new_thread(voltage_thread,())

while True:
    for note in song:
        print("freq:{}".format(note))
        duty=int(s/16)   
        if note:
            pwm.freq(note)
        pwm.duty_u16(duty)
        sleep_ms(170)
        pwm.duty_u16(0)
    sleep_ms(1000)

以下是另一个线程:

led.brightness(10)
song = internationale
def voltage_thread():
    global s
    while True:
        s=adc.read_u16()
        oled.text("Raspberry Pi", 15, 0)
        oled.text("Music Player",15,15)
        Vol=int(100*s/65535)
        Segment=100/12
        ColorSeg=int(255/12)
        for i in range(0,12):
            led[i]=(255-i*ColorSeg,i*ColorSeg,0)
        if Vol==0:
            led[0:12]=(0,0,0)
        elif Vol<Segment and Vol>0:
            led[0]=(255,0,0)
            led[1:12]=(0,0,0)
        elif Vol<2*Segment and Vol>=Segment:
            led[1]=(255-ColorSeg,ColorSeg,0)
            led[2:12]=(0,0,0)
        elif Vol<3*Segment and Vol>=2*Segment:
            led[2]=(255-2*ColorSeg,2*ColorSeg,0)
            led[3:12]=(0,0,0)
        elif Vol<4*Segment and Vol>=3*Segment:
            led[3]=(255-3*ColorSeg,3*ColorSeg,0)
            led[4:12]=(0,0,0)
        elif Vol<5*Segment and Vol>=4*Segment:
            led[4]=(255-4*ColorSeg,4*ColorSeg,0)
            led[5:12]=(0,0,0)
        elif Vol<6*Segment and Vol>=5*Segment:
            led[5]=(255-5*ColorSeg,5*ColorSeg,0)
            led[6:12]=(0,0,0)
        elif Vol<7*Segment and Vol>=6*Segment:
            led[6]=(255-6*ColorSeg,6*ColorSeg,0)
            led[7:12]=(0,0,0)
        elif Vol<8*Segment and Vol>=7*Segment:
            led[7]=(255-7*ColorSeg,7*ColorSeg,0)
            led[8:12]=(0,0,0)
        elif Vol<9*Segment and Vol>=8*Segment:
            led[8]=(255-8*ColorSeg,8*ColorSeg,0)
            led[9:12]=(0,0,0)
        elif Vol<10*Segment and Vol>=9*Segment:
            led[9]=(255-9*ColorSeg,9*ColorSeg,0)
            led[10:12]=(0,0,0)
        elif Vol<11*Segment and Vol>=10*Segment:
            led[10]=(255-10*ColorSeg,10*ColorSeg,0)
            led[11]=(0,0,0)
        elif Vol<=12*Segment and Vol>=11*Segment:
            led[11]=(255-11*ColorSeg,11*ColorSeg,0)
        q=str(int(100*s/65535))
        voltage=str(round(3.3*s/65535,3))
        oled.text("Voltage: "+voltage+"V",8,28)
        oled.text("Volume: "+q+"%", 15, 40)
        oled.text("Internationale", 8, 50)
        led.show()
        oled.show()
        oled.fill(0)
        sleep_ms(100)

3. 总结、参考文献

通过这一个项目,我对i2c、spi等通信总线有了进一步的理解与体会,加强了micropython编程能力,也让我能够初步的了解嵌入式开发的流程。

以下是参考文献和使用的开源库链接:

RP2 快速参考 — MicroPython中文版 1.17文档 (86x.org)

 

(137条消息) 树莓派4B ubuntu20.04 python控制pwm 蜂鸣器播放歌曲 教程_树莓派4b pwm输出音频_ourkix的博客-CSDN博客

 

(137条消息) 树莓派Pico迷你开发板MicroPython多线程编程实践_micropython 多线程_袁易学的博客-CSDN博客

 

(137条消息) MicroPython-On-ESP8266——有请蜂鸣器带来歌曲“小星星”_小星星蜂鸣器代码microbit_嗑烤队长的博客-CSDN博客

 

(137条消息) Micropython——关于Pico中I2C和SPI的实际应用示例(OLED屏幕显示)_Xa_L的博客-CSDN博客

 

blaz-r/pi_pico_neopixel: Pi Pico library for NeoPixel led-strip written in MicroPython. Works with ws2812b (RGB) and sk6812 (RGBW). (github.com)

 

micropython/ssd1306.py at master · 01studio-lab/micropython (github.com)

附件下载
源代码和库.zip
包含了项目源代码和使用的SSD1306、NeoPixel库
团队介绍
2023寒假在家练单人参加活动 普通通信工程专业本科生,对嵌入式开发感兴趣
团队成员
Jollies
北京理工大学2020级通信工程专业
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号