基于RP2350B的PWM实现的音乐播放器
该项目使用了RP2350B,实现了PWM音乐播放器的设计,它的主要功能为:通过PWM播放驱动蜂鸣器播放不同的音乐。
标签
PWM
音乐播放器
RP2350B
Wang Chong
更新2025-07-08
26

项目介绍

项目购买的综合技能训练板 + RP2350B来实现的PWM音乐播放器功能,通过将不同的音调频率保存为数组,然后根据不同音乐的曲子驱动不同的音调从而实现音乐的播放。 同时支持音乐名称以中文的方式显示在屏幕上, 和使用ADC的功能来读取按键识别按键状态

硬件介绍

  • 硬禾RP2350B开发板是一块搭载了RP2350B芯片的和小脚丫开发板引脚引出大部分兼容的树莓派核心开发板其搭载2MB的Flash。
  • 小脚丫FPGA的综合技能训练平台,具备任意波形/信号发生器ADC数据采集传感器信息输入OLED图形化信息显示蜂鸣器输出UART通信的功能。 是学生学习实验的不二选择。

方案框图和项目设计思路介绍

未命名绘图 (1).png

程序的主要的难点在于屏幕的驱动和固件的选配。因为目前MPY官方并没有非常适合的固件针对RP2350B, 因此部分现有 的固件无法对多余的PIN进行驱动, 在群友的帮助下选用了CPY作为开发环境进行开发。 在开发屏幕显示部分的时候发现RP2350B和综合技能训练板上的SPI PIN不对应, 因此无法固定在开发板上直接使用硬件SPI来驱动OLED屏幕。 因此选择了使用杜邦线进行连接。


软件流程图和关键代码介绍

未命名绘图 (2).png

根据官方的核心板原理图得知, 开发板上的所有的按键输入都是使用的一个ADC的IN, 因此只需要根据不同按键按下时对应的读取到的对应的ADC的值即可判断出当前是哪个按键被按下了。

def isButtonPressed():
"""
This function is used to determine if the button is pressed
"""
raw_value = adc.value
voltage = get_voltage(adc)
#print(f"Raw ADC: {raw_value}, Voltage: {voltage:.2f} V")
# 按键消抖
if 30500 < raw_value < 32000:
time.sleep(0.02)
raw_value2 = adc.value
if 30500 < raw_value2 < 32000:
print("SWD button pressed")
return True
return False

如上代码所示, 其使用的BUTTON为SWD上方的按钮, 因此我取名使用了SWD按钮。之后程序便可以根据定义的歌曲索引的状态来进行菜单的切换。

################################# 菜单显示和刷新开始 #################################
def show_menu(index):
splash = displayio.Group()
if index == 0:
splash.append(displayio.TileGrid(bitmap_huan, pixel_shader=palette, x=10, y=10))
splash.append(displayio.TileGrid(bitmap_le, pixel_shader=palette, x=40, y=5))
splash.append(displayio.TileGrid(bitmap_song, pixel_shader=palette, x=70, y=5))
elif index == 1:
splash.append(displayio.TileGrid(bitmap_xiao, pixel_shader=palette, x=10, y=5))
splash.append(displayio.TileGrid(bitmap_xing, pixel_shader=palette, x=40, y=5))
splash.append(displayio.TileGrid(bitmap_xing, pixel_shader=palette, x=70, y=5))
elif index == 2:
splash.append(displayio.TileGrid(bitmap_liang, pixel_shader=palette, x=10, y=5))
splash.append(displayio.TileGrid(bitmap_zhi, pixel_shader=palette, x=40, y=5))
splash.append(displayio.TileGrid(bitmap_lao, pixel_shader=palette, x=70, y=5))
splash.append(displayio.TileGrid(bitmap_hu, pixel_shader=palette, x=100, y=5))
display.root_group = splash
################################# 菜单显示和刷新结束 #################################

需要注意一点的是,这里的菜单全部使用的是bit map的方式保存为变量在程序运行期间读取的。 我之前也是在网上找了一些取模工具,但是由于是在MAC平台上开发的,因此有些工具不可用,因此最后只能自己一个一个点把文字信息给扣了出来。 比如说小星星, 如下所示


“小”点阵

xiao_20x20 = [
"00000000100000000000",
"00000000100000000000",
"00000000100000000000",
"00000000100000000000",
"00000000100000000000",
"00000000100000000000",
"00000000100000000000",
"00000000100000000000",
"00001000100010000000",
"00010000100001000000",
"00100000100000100000",
"00000000100000010000",
"00000000100000000000",
"00001000100000000000",
"00000100100000000000",
"00000010100000000000",
"00000000100000000000",
"00000000000000000000",
"00000000000000000000",
"00000000000000000000"
]


“星” 点阵(可以复用, 因为小星星中星字出现了两次)

xing1_20x22 = [
"0000000000000000000000",
"0000000000000000000000",
"0001111111111111000000",
"0001000010000001000000",
"0001111111111111000000",
"0001000010000001000000",
"0001111111111111000000",
"0000000010000000000000",
"0001000001000000000000",
"0001111111111111000000",
"0010000010000000000000",
"0100000010000000000000",
"0001111111111111000000",
"0000000010000000000000",
"0011111111111111111100",
"0000000000000000000000",
"0000000000000000000000",
"0000000000000000000000",
"0000000000000000000000",
"0000000000000000000000"
]

然后等按钮切换的之后只需要播放事先定义好的音频数组即可。

while True:
print(f"played:", played)
pressed = isButtonPressed()
if pressed and not button_prev_state:
song_index = (song_index + 1) % 3
print(song_index)
played = False
show_menu(song_index)
if(song_index == 0 and played == False):
play_song(melody_joy)
played =True
elif(song_index == 1 and played == False):
play_song(melody_star)
played =True
elif(song_index == 2 and played == False):
play_song(melody_tiger)
played =True
button_prev_state = pressed
time.sleep(0.1)

音频数组也是事先定义好的, 比如说小星星的音频数组。


# 音符频率(Hz)
notes = {
'C4': 262, 'D4': 294, 'E4': 330, 'F4': 349,
'G4': 392, 'A4': 440, 'B4': 494,
'C5': 523, 'D5': 587, 'E5': 659, 'F5': 698,
'G5': 784, ' ': 0
}

# 小星星
melody_star = [
'C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4', ' ',
'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4', ' ',
'G4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4', ' ',
'G4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4', ' ',
'C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4', ' ',
'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4', ' '
]

实物功能展示图及说明

两只老虎菜单显示

ab8009cd99e081da31b8848cdf9229a.jpg


欢乐颂菜单显示

d8b9cf66a8d3d25fe6affa2151d53d2.jpg


小星星菜单显示

70830625f44013a665ff7cb701551ab.jpg

项目中遇到的难题和解决方法

对我而言本次项目开发中还是蛮顺利的, 两晚上的功夫就搞定了当前的项目。 程序的编写过程并不复杂, 主要就是在驱动SPI OLED屏幕的时候浪费了很多时间, 因为这个训练板和核心板上的PIN不对应,但是刚开始自己并不清楚,所以做了很多尝试。 还有一点就是在选择固件的时候比较浪费时间, 原本我以为芯片一样,不管RP2350A和B都可以使用一样的固件,但是实际上并不是。在MPY官方仓库中,PICO2的PIN定义比较少, B的定义应该更多一点才对。 所以在使用A的固件的时候便会出现无法使用超过PIN30的情况。解决办法到最后还是使用的CPY。

对本次活动的心得体会

这次活动让我更深刻的学习到了使用CPY的编程技巧, 同时对MPY的固件编译又有了更深刻的认知。感谢EETREE提供的参加活动的机会。不仅能让我从中学习到软件知识,硬件设计也真的是受益匪浅(ADC键盘输入)!

附件下载
adafruit-circuitpython-solderparty_rp2350_stamp_xl-zh_Latn_pinyin-9.2.7.uf2
CPY固件
project.zip
工程代码
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号