项目介绍:
本项目在STEP Pico上,通过对应midi音符的解析,控制蜂鸣器播放对应的声音,实现midi音乐的播放,同时根据当前播放的音符,点亮对应数量和颜色WS2812B灯珠。
播放过程中,如果按K1按键,则暂停播放;如果按K2按键,则继续播放。
设计思路:
要实现本项目,需要做到四点:
- 音符的解析
- 音符的播放
- WS2812B灯珠的控制
- 按键状态的获取
通过了解,midi音符可以转换为频率和时长进行,通过蜂鸣器发生呈现。
而每个音符,对应了不同的数字,根据数字,选择对应的颜色,来点亮对应数量的WS2812B灯珠,从而实现项目预设的效果。
音符与频率的对照关系,参考下图:
国际标准音高包含了哆啦咪法嗦来嘻(表中用1234567)的多个音高的频率,为了尽可能的还原音乐,这里模拟了其中9个频率的音高,下表为音高对应的频率:
在实际使用中,只需要将midi音符通过查表,找到对应的频率,再驱动蜂鸣器,播放指定的时长即可。
而每个音符,本身对应了1-7,通过1-7,可以选择不同的预设颜色,来驱动WS2812B灯珠进行显示。
而按键状态的获取,则直接通过数字IO读取按键状态即可。
硬件介绍:
本项目使用了Step pico及其对应的扩展板。在该扩展板上,提供了12灯珠的WS2812B,以及一个蜂鸣器。
根据官方提供的功能及管脚映射表,可以得到控制WS2812B和蜂鸣器的GPIO:
从上图可知:
- WS2812B对应GPIO18
- 蜂鸣器对应GPIO19
- K1对应GPIO12
- K2对应GPIO13
因为扩展板提供了上述外设完整的连接和控制线路,所以无需其他辅助,仅使用STEP pico+扩展板就可以完成全部工作。
实现功能:
STEP pico为RP2040核心,可以使用SDK开发,也可以使用Arduino开发,还可以使用micropython和circuitpython进行开发。本项目使用circuitpython进行开发。
通过circuitpython,最终实现的功能如下:
- 根据midi音乐的音符数据,驱动蜂鸣器进行播放
- 播放的同时,点亮指定颜色的WS2812B灯珠,实现灯珠跟随音乐的效果
- 播放时,按K1,则暂停播放
- 暂停播放时,按K2,则继续播放
代码说明:
最终编写的代码如下:
import board
import simpleio
import digitalio
import neopixel
import time
# 按键IO设置
k1 = digitalio.DigitalInOut(board.GP12)
k1.direction = digitalio.Direction.INPUT
k1.pull = digitalio.Pull.UP
k2 = digitalio.DigitalInOut(board.GP13)
k2.direction = digitalio.Direction.INPUT
k2.pull = digitalio.Pull.UP
# WS2812B设置
pixel_pin = board.GP18
num_pixels = 12
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
colors = (RED, YELLOW, GREEN, CYAN, BLUE, PURPLE)
def ws2812b_set(color, num, wait):
for i in range(num_pixels):
if i < num:
pixels[i] = color
else:
pixels[i] = (0,0,0)
if wait>0:
time.sleep(wait)
pixels.show()
# time.sleep(0.5)
# 蜂鸣器设置
PIEZO_PIN = board.GP19
#simpleio.tone(PIEZO_PIN, 440, duration=0.1)
# 音符频率
pitch_freqs = (
# 1 2 3 4 5 6 7
(16.4, 18.4, 20.6, 21.8, 24.5, 27.5, 30.9 ), # 0
(32.7, 36.7, 41.2, 43.7, 49.0, 55.0, 61.7 ), # 1
(65.4, 73.4, 82.4, 87.3, 98.0, 110, 123.5 ), # 2
(130.8, 146.8, 164.8, 174.6, 196.0, 220, 246.9 ), # 3
(261.6, 293.7, 329.6, 349.2, 392.0, 440, 493.9 ), # 4
(523.3, 587.3, 659.3, 698.5, 784.0, 880, 987.8 ), # 5
(1046.5,1174.7, 1318.5, 1396.9, 1568.0, 1760, 1975.6 ), # 6
(2093.1,2349.4, 2637.1, 2793.9, 3136.0, 3520.1, 3951.2 ), # 7
(4186.1,4698.8, 5274.2, 5587.8, 6272.1, 7040.2, 7902.3 ) # 8
)
# 要播放的音乐
music=(
24,89,
4,5,12,4,5,6,5,5,6,
7,5,36,5,5,12,4,5,12,
5,5,6,7,5,6,5,5,12,
4,5,12,2,5,72,2,5,12,
2,5,6,4,5,6,5,5,36,
7,5,12,4,5,12,2,6,12,
2,6,6,1,6,6,7,5,12,
1,6,72,7,5,12,1,6,12,
2,6,12,2,6,12,1,6,12,
2,6,6,1,6,6,7,5,36,
5,5,6,7,5,6,1,6,12,
1,6,12,1,6,6,7,5,6,
0
)
# 音乐基础信息获取
i = 2
km = music[0] #//获取divisions值
beats_per_min = music[1] #//获取曲子每分钟拍子数
pitch_quarter_time = int(1000*((60/beats_per_min)/4)+0.5) #//根据每分钟节拍数,求解四分之一音符保持时长,四舍五入单位为1ms
pitch_quarter_time = int(1000*(60/beats_per_min/km))
print(km, beats_per_min, pitch_quarter_time)
is_stop = False
while True:
# 检查按键状态
if not k1.value and not is_stop:
print("press k1")
is_stop = True
if not k2.value and is_stop:
print("press k1")
is_stop = False
if is_stop:
continue
# 音符信息获取
m1 = music[i+1] #//获取音符对应计数值所在行下标
m2 = music[i]-1 #//获取音符对应计数值所在列下标
n = music[i+2] #//获取音符保持时长值
# 点亮WS2812B
ws2812b_set(colors[music[i]%6], music[i]%12, 0)
# 音符播放
freq = pitch_freqs[m1][m2]
dt = pitch_quarter_time * n
print(m1, m2, n, freq, dt)
simpleio.tone(PIEZO_PIN, freq, duration=dt/1000)
i = i+3 #//读下一个音符
if music[i]==0:
i=2 #//如果到末尾则回到开头,循环播放
# break
在上述代码中,使用的库说明如下:
- board:circuitpython自带的板子驱动库
- simpleio:驱动GPIO的功能库,这里使用其驱动蜂鸣器
- digitalio:数字IO库,这里使用其获取按键状态
- neopixel:WS2812B控制库
- time:python时间库
代码中:
1. 获取按键状态的部分较为简单,通说初始化按键对应的数字IO口,然后获取其状态即可。需要注意的是,默认为高电平,按下为低电平,所以检测的时候,使用了not k1.value和not k2.value来进行。
2. WS2812B控制的部分,主要为:
- 初始化:pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
- 点亮指定的灯珠:ws2812b_set(colors[music[i]%6], music[i]%12, 0)
初始化部分,需要指定好GPIO以及总的灯珠数量。
点亮部分,实际上是给对应的灯珠,设置一个(R, G, B)的颜色值即可。
3. 音符播放部分,主要为:
- 给出了音符频率表:pitch_freqs
- 给出了要播放音乐的数据表
- 实际播放时:
- 通过音乐数据表,计算当前要播放的音乐的节拍信息,以及每个节拍对应的时间
- 通过音乐数据表,查找当前要播放的音符在音符频率表中对应的频率值
- 然后使用simpleio.tone(PIEZO_PIN, freq, duration=dt/1000),用对应的频率来驱动蜂鸣器鸣叫指定的时间
操作步骤:
- 通过thonny安装circuitpython:
- thonny连接pico:
- 下载扩展库:
- Libraries (circuitpython.org):https://circuitpython.org/libraries
- 拷贝扩展库:
- 代码编写:
- 运行代码,就能播放音乐,点亮WS2812B,然后可以按键进行控制