基于RP2350B实现简单的音乐播放器
该项目使用了硬禾RP2350开发板、circuit python,实现了音乐播放器的设计,它的主要功能为:通过PWM驱动蜂鸣器发声,能够播放多首不同的曲子,并支持用户通过按键进行曲目切换和控制播放暂停。曲目的名称通过OLED屏幕显示,支持中文汉字显示。系统具备按键消抖功能,保证按键输入稳定可靠。
标签
嵌入式系统
显示
开发板
接口
starry-m
更新2025-07-11
5

项目介绍

本项目基于硬禾RP2350开发板,实现了通过PWM驱动蜂鸣器发声,能够播放多首不同的曲子,并支持用户通过按键进行曲目切换和控制播放暂停。曲目的名称通过OLED屏幕显示,支持中文汉字显示。系统具备按键消抖功能,保证按键输入稳定可靠。
本项目满足以下功能要求:

  • 利用PWM产生不同频率的音调,驱动蜂鸣器发出声音;
  • 支持至少三首歌曲的播放与切换;
  • 通过板载按键控制歌曲切换与播放状态,含按键消抖处理;
  • OLED屏幕显示当前播放曲目的中文名称。

硬件说明

本项目所使用的硬件平台为硬禾RP2350开发板,核心特点包括:

  • RP2350 MCU,支持丰富GPIO资源和PWM输出;
  • 板载蜂鸣器,通过PWM口驱动;
  • 128×32分辨率的OLED显示屏,使用SPI通信;
  • 多个模拟输入用于按键电压检测,实现多按键功能;
  • 按键连接通过ADC测量电压分压实现多键检测。

方案说明

本项目设计主要包含以下几个模块:

  • PWM音频播放模块:通过PWM输出控制蜂鸣器发声,利用预定义音符频率表实现音调变化;
  • OLED显示模块:使用SPI驱动SSD1306 OLED屏幕,结合自定义中文字库显示当前曲目名称;
  • 按键输入模块:通过ADC读取多级电压检测按键状态,实现多键功能,并做按键消抖;
  • 主控制逻辑:根据按键输入实现播放、暂停、上一首、下一首等控制。

软件说明

软件采用CircuitPython在Thonny下开发,使用circuit python官方提供的rp2350固件
系统框图如下

模块划分及工作流程如下:

PWM与频谱

  • 通过pwmio.PWMOut驱动蜂鸣器;
  • 预定义音符频率表(NOTE_FREQ字典)对应音符到频率的映射;
  • 通过控制PWM频率和占空比发出对应频率的音符,实现歌曲播放。

OLED显示与中文字模

  • 采用adafruit_ssd1306库驱动OLED屏幕;
  • 中文字模数据以二进制文件存储,16×16像素点阵;
  • 读取字模数据,通过自定义函数Decode16x16_Bytes_To_Pixel渲染到屏幕;
  • 通过计算字符总宽度实现水平居中显示。

查看原理图可见RP2350与OLED之间的连接使用SPI通信,但是并没有接上硬件SPI的SCK,和MOSI,因此需要使用软件SPI驱动,这一部分cpy也有提供对应的库:

import bitbangio
spi = bitbangio.SPI(microcontroller.pin.GPIO45, MOSI=microcontroller.pin.GPIO44)

SSD1306具体的驱动使用adafruit的库,在adafruit-circuitpython-bundle-9.x-mpy-20250625中有提供绝大多数模块的库与使用示例。
本次任务显示相关的要求只有显示中文的歌曲名字,因此需要自己做字库解码,这里我参考的这个博客树莓派pico入坑笔记,ssd1306使用,他里面提到了一个生成字库的网站,借助博主的解码方式,我实现了中文解码。
但是如果把字库直接当作数组放到代码中的话容易出现爆栈的情况,以及不够灵活,因此我选择将字库放到bin文件,然后读取显示,我这里使用的字体是免费的得意黑
在电脑的python环境(我这里是3.12)下运行,得到中文字bin。

#pip install pillow
from PIL import Image, ImageFont, ImageDraw
def render_char_to_bytes(ch, font_path="simhei.ttf", size=16):
# 创建黑白图像
img = Image.new("1", (size, size), color=0)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(font_path, size)
# 计算字符实际的 bbox(左上角、右下角)
bbox = font.getbbox(ch)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 居中绘制(特别是垂直方向)
x_offset = (size - text_width) // 2 - bbox[0]
y_offset = (size - text_height) // 2 - bbox[1]
draw.text((x_offset, y_offset), ch, fill=1, font=font)
# 提取每行像素,打包成位
data = []
for y in range(size):
for byte_index in range(2): # 每行2字节
byte = 0
for bit in range(8):
x = byte_index * 8 + bit
if x < size and img.getpixel((x, y)) != 0:
byte |= (1 << (7 - bit))
data.append(byte)
return bytes(data)

chars = "硬禾学堂"
font_path = "SmileySans-Oblique.ttf" # 中文字体,需手动提供
with open("yinghexuetang.bin", "wb") as f:
for ch in chars:
bytestr = render_char_to_bytes(ch, font_path)
f.write(bytestr)

随后的读取文件,解码显示比较简单,在附件中可以看到。

按键

  • 按键通过ADC采样多级电压值判断按键状态,实现多按键功能;
  • 设计了容差范围tol判断电压对应的按键;
  • 按键状态变化触发播放控制逻辑;
  • 按键输入包含去抖动逻辑,仅在按键释放时触发事件。

效果展示与遇到的问题

效果展示

预定义歌曲:《小星星》、《超级玛丽》、《两只老虎》、《生日快乐》、《送别》具体见视频。
从左到右,按键功能:
1:上一首
2:播放/暂停
3:下一首
4:重播

主界面

开始播放

暂停播放


遇到的问题

  • 一开始就是奔着体验circuit python来的,因为之前就没怎么写过python,所以有点无从下手,还好cpy官方网站资料很多,再加上语言上手方便,后面用起来还是挺顺的。
  • 实时按键切换曲子本来想像以前那样用中断回调的,但是查阅资料发现cpy好像是不支持中断和多线程的,因此在单个字符之间做了频繁的读取按键。
  • 播放的曲子想着直接读取wav文件解释成预定义的音调。但是看看剩下的不到1M的flash空间,只能放弃,或者可以把开发板当作声卡,在电脑这边做解析(还未实现)。

心得体会

  • 通过本项目的设计与实现,虽然这次的功能比较简单,还没有完全发挥rp2350的强大,但是终于真正的使用了一回circuit python,体验到了python的快乐。
  • 不知道rp2350的双核M33和双核risc-v怎么玩,有时间再看看c-sdk。
附件下载
fujian.zip
cpy代码
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号