基于RP2350B实现音乐播放器
该项目使用了RP2350B核心板、综合技能训练板,实现了音乐播放器的设计,它的主要功能为:PWM播放音乐,按键切换曲目,OLED屏幕显示歌曲名。
标签
嵌入式系统
显示
开发板
FuShenxiao
更新2025-07-11
哈尔滨工业大学
15

项目介绍

该项目基于RP2350B核心板,在综合技能训练板上实现了歌曲名字在OLED屏幕上的显示以及蜂鸣器的播放,利用核心板上的按键实现了歌曲的翻页。

该核心板搭载了树莓派RP2350B的MCU,板载10颗单色LED灯,2颗三色彩灯,4个拨码开关,4个按键,2个数码管以及1个姿态传感器,该核心板外设丰富,便于进行相关的开发。

image.png

选用的扩展板为小脚丫FPGA综合技能训练平台,该扩展板具有丰富的AD/DA接口以及丰富的通信接口,便于项目的扩展设计。

image.png

其功能框图如下图所示。

image.png

该项目基于CircuitPython开发,实现了四首曲子的播放。

该项目器件之间的控制框图如下。其中,MCU利用ADC采样获取核心板上的按键信息;MCU与OLED屏幕利用模拟SPI通信,实现屏幕上的字符显示;MCU利用PWM信号控制蜂鸣器,实现不同音调和音律的信号输出。从而实现歌曲的切换与播放。

image.png

流程框图如下。由于该系统为死循环,即循环执行相关指令,因此无需退出循环(可通过Thonny中的Stop按键实现程序停止)

image.png

OLED屏幕显示

扩展板上的OLED接口对应着核心板的35-40扩展引脚

image.png

观察核心板上的35-40扩展引脚,可见对应RP2350B的GPIO41-GPIO45,以及+5V供电

image.png

然而,RP2350B的GPIO41-GPIO45并不与OLED屏幕对应的SPI接口对应。因此,需要通过GPIO高低电平模拟SPI通信实现OLED屏幕显示

image.png

扩展板上的OLED屏幕依靠SSD1306驱动,相关驱动代码可以参见GitHub仓库:GitHub - adafruit/Adafruit_CircuitPython_SSD1306: Adafruit CircuitPython framebuf driver for SSD1306 or SSD1305 OLED displays. Not for use with displayio. See README.

也可以参见附件adafruit_ssd1306.py

完成SSD1306底层驱动代码的编写后,需要设计一些用户API,例如屏幕初始化、屏幕刷新以及字符显示等,相关用户API可参见GitHub仓库:GitHub - adafruit/Adafruit_CircuitPython_framebuf: CircuitPython framebuf module, based on the Python framebuf module

也可以参见附件adafruit_framebuf.py

至此屏幕显示相关的驱动与现实函数就导入成功了

在主函数中,首先导入相关库函数,例如SSD1306的SPI控制以及字符现实API

from adafruit_ssd1306 import SSD1306_SPI
import adafruit_framebuf as framebuf

由于扩展板上的OLED屏幕依赖模拟SPI,因此编写模拟SPI初始化函数如下

class SoftSPI:
def __init__(self, sck, mosi, miso=None):
self.sck = digitalio.DigitalInOut(sck)
self.mosi = digitalio.DigitalInOut(mosi)
self.sck.direction = digitalio.Direction.OUTPUT
self.mosi.direction = digitalio.Direction.OUTPUT
self._locked = False

def configure(self, **kwargs):
return self

def try_lock(self):
if not self._locked:
self._locked = True
return True
return False

def unlock(self):
self._locked = False

def write(self, buffer):
for byte in buffer:
for i in [7,6,5,4,3,2,1,0]: # MSB优先
self.sck.value = False
self.mosi.value = (byte >> i) & 0x01
self.sck.value = True
# 初始化引脚
dc_pin = digitalio.DigitalInOut(board.GP42)
reset_pin = digitalio.DigitalInOut(board.GP43)
cs_pin = digitalio.DigitalInOut(board.GP41)

spi = SoftSPI(board.GP45, board.GP44) # sck, mosi

由于项目要求在屏幕上现实中文字符,其实现原理在于依照字符取模后的数组信息,将一个一个的像素点显示在屏幕上。编写中文字符显示函数如下

def draw_chinese_char(oled, font_data, x, y):
"""手动绘制字符(不使用framebuf)"""
for dy in range(16): # 16
byte1 = font_data[dy*2] # 高8
byte2 = font_data[dy*2+1] # 低8
for dx in range(16): # 16
pixel = 0
if dx < 8:
pixel = byte1 & (0x80 >> dx)
else:
pixel = byte2 & (0x80 >> (dx-8))
if pixel:
oled.pixel(x+dx, y+dy, 1)

在本项目中,我选择了四首歌,分别为《生日快乐》、《一闪一闪亮晶晶》、《超级马里奥》以及《哈工大之歌》

以上这些字符信息可以使用PCtoLCD2002软件实现

首先设置字模格式为C51格式,取模方式为逐行式(取模方式与程序中字符显示方式有关)

image.png

接着设置要显示的字体高度和宽度,以《哈工大之歌》为例,得到字模数组

image.png

在主程序中,首先定义四首歌的字模数组,这里以《哈工大之歌》为例

# 哈
FONT_HA = [
0x00,0x40,0x00,0x40,0x78,0xA0,0x49,0x10,
0x4A,0x08,0x4C,0x06,0x4B,0xF8,0x48,0x00,
0x48,0x00,0x4B,0xF8,0x7A,0x08,0x4A,0x08,
0x02,0x08,0x02,0x08,0x03,0xF8,0x02,0x08
]
# 工
FONT_GONG = [
0x00,0x00,0x00,0x00,0x7F,0xFC,0x01,0x00,
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
0x01,0x00,0xFF,0xFE,0x00,0x00,0x00,0x00
]
# 大
FONT_DA = [
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,
0x02,0x80,0x02,0x80,0x04,0x40,0x04,0x40,
0x08,0x20,0x10,0x10,0x20,0x08,0xC0,0x06
]
# 之
FONT_ZHI = [
0x02,0x00,0x01,0x00,0x01,0x00,0x00,0x00,
0x7F,0xF8,0x00,0x10,0x00,0x20,0x00,0x40,
0x00,0x80,0x01,0x00,0x02,0x00,0x04,0x00,
0x18,0x00,0x24,0x00,0x43,0xFE,0x00,0x00
]
# 歌
FONT_GE = [
0x00,0x20,0xFF,0x20,0x02,0x20,0x7A,0x7E,
0x4A,0x42,0x7A,0x84,0x00,0x10,0xFF,0x10,
0x02,0x10,0x7A,0x10,0x4A,0x28,0x4A,0x28,
0x7A,0x28,0x02,0x44,0x0A,0x44,0x04,0x82
]

接着设置播放每一首歌时OLED屏幕的显示字符

# 生日快乐
def song1(oled):
oled.fill(0)
draw_chinese_char(oled, FONT_SHENG, 32, 8)
draw_chinese_char(oled, FONT_RI, 32 + 16, 8)
draw_chinese_char(oled, FONT_KUAI, 32 + 32, 8)
draw_chinese_char(oled, FONT_LE, 32 + 48, 8)
oled.show() # 更新显示
# 一闪一闪亮晶晶
def song2(oled):
oled.fill(0) # 清屏
draw_chinese_char(oled, FONT_YI, 8, 8)
draw_chinese_char(oled, FONT_SHAN, 8 + 16, 8)
draw_chinese_char(oled, FONT_YI, 8 + 32, 8)
draw_chinese_char(oled, FONT_SHAN, 8 + 48, 8)
draw_chinese_char(oled, FONT_LIANG, 8 + 64, 8)
draw_chinese_char(oled, FONT_JING, 8 + 80, 8)
draw_chinese_char(oled, FONT_JING, 8 + 96, 8)
oled.show() # 更新显示
# 超级马里奥
def song3(oled):
oled.fill(0)
draw_chinese_char(oled, FONT_CHAO, 24, 8)
draw_chinese_char(oled, FONT_JI, 24 + 16, 8)
draw_chinese_char(oled, FONT_MA, 24 + 32, 8)
draw_chinese_char(oled, FONT_LI, 24 + 48, 8)
draw_chinese_char(oled, FONT_AO, 24 + 64, 8)
oled.show() # 更新显示
# 哈工大之歌
def song4(oled):
oled.fill(0) # 清屏
draw_chinese_char(oled, FONT_HA, 24, 8)
draw_chinese_char(oled, FONT_GONG, 24 + 16, 8)
draw_chinese_char(oled, FONT_DA, 24 + 32, 8)
draw_chinese_char(oled, FONT_ZHI, 24 + 48, 8)
draw_chinese_char(oled, FONT_GE, 24 + 64, 8)
oled.show() # 更新显示

可以得到四首歌在OLED屏幕上的显示结果如下

image.png

image.png

image.png

image.png

按键控制

观察核心板上开关和按键电路,可以发现这种按键电路使用ADC采集到的电压值获取按键信息。相较于传统按键电路依靠高低电平控制,这种设计方式节省了许多IO口资源。除此之外,这种设计方式对于多个按键同时按下时的适配也能做得比较好。

image.png

本项目中只是用了轻触开关,即K1-K4,而不使用SW2的拨码开关。因此只需测量K1-K4按下时的输出电压即可。

第一种方案为在仿真软件中绘制电路图并仿真测试。这里以Multisim仿真为例,在K4按下的情况下,相当于K4这一路接地,可以利用探针读取输出点电压。

同理,可以分别得出K1-K4按下,以及两两组合、三三组合按下时的输出电压。

image.png

然而这种方式仅在理论上较为可行,在实际中由于器件选型等问题,器件并不能做到跟理想值完全相同,因此仿真得到的参数只能作为设计的大致参考。

为了解决理论与实际的差异,可以编写ADC采样程序,从而获取针对于实际电路的参数。

import time
import board
import analogio

# 初始化 ADC 引脚
adc_pin = board.GP47 # 使用 GP47 作为 ADC 输入
adc = analogio.AnalogIn(adc_pin)

def read_voltage(adc_input):
"""将 ADC 读数转换为电压值"""
return (adc_input.value * 3.3) / 65535

def read_temperature():
"""读取内部温度传感器(如果有)"""
if hasattr(board, 'TEMPERATURE'):
return analogio.AnalogIn(board.TEMPERATURE)
return None

try:
print("RP2350 ADC 读取示例")
print("按下 Ctrl+C 停止程序")

while True:
# 读取原始 ADC (0-65535)
raw_value = adc.value

# 转换为电压 (0-3.3V)
voltage = read_voltage(adc)

# 打印结果
print(f"原始值: {raw_value:5d} | 电压: {voltage:.3f}V")

# 如果有温度传感器,也读取温度
temp_sensor = read_temperature()
if temp_sensor:
temp_voltage = read_voltage(temp_sensor)
# RP2350 温度传感器公式: T = 27 - (ADC_voltage - 0.706)/0.001721
temperature = 27 - (temp_voltage - 0.706) / 0.001721
print(f"芯片温度: {temperature:.1f}°C")

time.sleep(1)

except KeyboardInterrupt:
print("程序结束")
adc.deinit()

由于RP2350的ADC输出值范围为0-65535,针对于我的开发板,得到按键按下时的ADC采样近似值参数如下表所示

按键状态

电压采集值

没有按键按下

60000

K1

30000

K2

45000

K3

52000

K4

56000

K1 + K2

15900

K1 + K3

23000

K1 + K4

27000

K2 + K3

38000

K2 + K4

41900

K3 + K4

49000

为了使按键判断更加稳健,可以将这几种状态标注到坐标轴上,然后左右各设置缓冲区域,即电压采集值到达这个范围内,就认为这个状态的按键按下,例如

按键状态

电压采集值范围

没有按键按下

58000-65535

K1

29000-37500

K2

37500-48500

K3

48500-54000

K4

54000-58000

最后进行程序实现。在主函数中,首先初始化ADC引脚,之后根据采集到的ADC值获取哪个按键按下。在按键读取程序中,通过软件延时10ms实现消抖,在按键抬起时完成按键读取。

adc_pin = board.GP47  # 使用 GP47 作为 ADC 输入
adc = analogio.AnalogIn(adc_pin)

def read_voltage(adc_input):
return adc_input.value

def read_key(adc_input):
if adc_input.value >= 29000 and adc_input.value < 37500:
time.sleep(0.01)
if adc_input.value >= 29000 and adc_input.value < 37500:
while adc_input.value >= 29000 and adc_input.value < 37500:
pass
return 1
elif adc_input.value >= 37500 and adc_input.value < 48500:
time.sleep(0.01)
if adc_input.value >= 37500 and adc_input.value < 48500:
while adc_input.value >= 37500 and adc_input.value < 48500:
pass
return 2
elif adc_input.value >= 48500 and adc_input.value < 54000:
time.sleep(0.01)
if adc_input.value >= 48500 and adc_input.value < 54000:
while adc_input.value >= 48500 and adc_input.value < 54000:
pass
return 3
elif adc_input.value >= 54000 and adc_input.value < 58000:
time.sleep(0.01)
if adc_input.value >= 54000 and adc_input.value < 58000:
while adc_input.value >= 54000 and adc_input.value < 58000:
pass
return 4
else:
return 0

在该项目中,K1-K4按键的功能分别如下

按键

功能

K1

转到下一首歌

K2

转到上一首歌

K3

播放当前页面歌曲

K4

缺省

PWM音乐播放

不同的PWM频率可以使蜂鸣器产生不同的音调,在扩展板上,蜂鸣器的发声依靠三极管基极的开关。

image.png

在程序中,首先导入PWM输出的库函数

import pwmio

接着定义控制蜂鸣器的PWM引脚,同时设置播放指定频率音符的函数,对于休止符而言,使PWM占空比变为0,对于非休止符,不修改占空比,只修改PWM的频率。

# 初始化PWM输出
buzzer = pwmio.PWMOut(board.GP20, frequency=440, duty_cycle=0)
def play_tone(frequency, duration):
"""播放指定频率的音符"""
global buzzer # 声明为全局变量

if frequency == NOTE_REST:
buzzer.duty_cycle = 0
time.sleep(duration)
else:
buzzer.deinit() # 先释放PWM
buzzer = pwmio.PWMOut(board.GP20, duty_cycle=32768, frequency=frequency)
time.sleep(duration)
buzzer.duty_cycle = 0

基于C大调导入各个音符的频率值

# 定义音符频率(Hz) - 基于C大调
NOTE_D3 = 147
NOTE_E3 = 165
NOTE_FS3 = 185
NOTE_G3 = 196
NOTE_A3 = 220 # 低频la
NOTE_AS3 = 233 # 低频la#
NOTE_B3 = 247 # 低频si
NOTE_C4 = 262 # do
NOTE_CS4 = 277 # do#
NOTE_D4 = 294 # re
NOTE_DS4 = 311 # re#
NOTE_E4 = 330 # mi
NOTE_F4 = 349 # fa
NOTE_FS4 = 370 # fa#
NOTE_G4 = 392 # sol
NOTE_GS4 = 415 # sol#
NOTE_A4 = 440 # la
NOTE_AS4 = 466 # la#
NOTE_B4 = 494 # si
NOTE_C5 = 523 # 高音do
NOTE_CS5 = 554 # 高音do#
NOTE_D5 = 587 # 高音re
NOTE_DS5 = 622 # 高音re#
NOTE_E5 = 659 # 高音mi
NOTE_F5 = 698 # 高音fa
NOTE_FS5 = 740 # 高音fa#
NOTE_G5 = 784 # 高音sol
NOTE_A5 = 880 # 高音la
NOTE_B5 = 988 # 高音si
NOTE_C6 = 1047
NOTE_CS6 = 1109
NOTE_D6 = 1175
NOTE_E6 = 1319
NOTE_F6 = 1397
NOTE_G6 = 1568
NOTE_REST = 0 # 休止符

接着导入各个乐曲的旋律,这些旋律可以在网上很容易地找到

例如《哈工大之歌》

学习强国

# 生日快乐歌旋律
birthday_melody = [
NOTE_G4, NOTE_G4, NOTE_A4, NOTE_G4, NOTE_C5, NOTE_B4,
NOTE_G4, NOTE_G4, NOTE_A4, NOTE_G4, NOTE_D5, NOTE_C5,
NOTE_G4, NOTE_G4, NOTE_G5, NOTE_E5, NOTE_C5, NOTE_B4, NOTE_A4,
NOTE_F5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_C5
]

# 一闪一闪亮晶晶旋律
twinkle_melody = [
NOTE_C4, NOTE_C4, NOTE_G4, NOTE_G4, NOTE_A4, NOTE_A4, NOTE_G4,
NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_D4, NOTE_C4,
NOTE_G4, NOTE_G4, NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4,
NOTE_G4, NOTE_G4, NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4,
NOTE_C4, NOTE_C4, NOTE_G4, NOTE_G4, NOTE_A4, NOTE_A4, NOTE_G4,
NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_D4, NOTE_C4
]

# 超级马里奥旋律
mario_melody = [
NOTE_E5, NOTE_E5, NOTE_REST, NOTE_E5, NOTE_REST, NOTE_C5, NOTE_E5, NOTE_REST,
NOTE_G5, NOTE_REST, NOTE_REST, NOTE_G4, NOTE_REST, NOTE_REST,
NOTE_C5, NOTE_REST, NOTE_REST, NOTE_G4, NOTE_REST, NOTE_E4, NOTE_REST,
NOTE_A4, NOTE_REST, NOTE_B4, NOTE_REST, NOTE_AS4, NOTE_A4, NOTE_REST,
NOTE_G4, NOTE_E5, NOTE_G5, NOTE_A5, NOTE_REST, NOTE_F5, NOTE_G5, NOTE_REST,
NOTE_E5, NOTE_REST, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_REST,
NOTE_C5, NOTE_REST, NOTE_REST, NOTE_G4, NOTE_REST, NOTE_E4, NOTE_REST,
NOTE_A4, NOTE_REST, NOTE_B4, NOTE_REST, NOTE_AS4, NOTE_A4, NOTE_REST,
NOTE_G4, NOTE_E5, NOTE_G5, NOTE_A5, NOTE_REST, NOTE_F5, NOTE_G5, NOTE_REST,
NOTE_E5, NOTE_REST, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_REST
]

# 哈工大之歌主旋律
hit_melody = [
NOTE_G4, NOTE_F4, NOTE_E4, NOTE_C4, NOTE_G4, NOTE_F4, NOTE_E4, NOTE_C4,
NOTE_E4, NOTE_E4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_G4,
NOTE_A4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_F4, NOTE_E4, NOTE_D4,
NOTE_G4, NOTE_F4, NOTE_E4, NOTE_C4, NOTE_G4, NOTE_F4, NOTE_E4, NOTE_C4,
NOTE_F4, NOTE_F4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_A4,
NOTE_G4, NOTE_C4, NOTE_C4, NOTE_D4, NOTE_D4, NOTE_E4, NOTE_C4,
NOTE_E4, NOTE_A4, NOTE_E4, NOTE_E4, NOTE_C4, NOTE_A3, NOTE_E4, NOTE_E4,
NOTE_A4, NOTE_E4, NOTE_E4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_G4, NOTE_E4,
NOTE_E4, NOTE_A4, NOTE_A4, NOTE_E4, NOTE_C4, NOTE_C4, NOTE_D4, NOTE_E4, NOTE_D4,
NOTE_D4, NOTE_C4, NOTE_A4, NOTE_C4, NOTE_D4, NOTE_D4, NOTE_E4, NOTE_E4,
NOTE_E4, NOTE_A4, NOTE_E4, NOTE_D4, NOTE_C4, NOTE_B3, NOTE_A3, NOTE_E4,
NOTE_A4, NOTE_E4, NOTE_E4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_G4, NOTE_E4,
NOTE_E4, NOTE_A4, NOTE_E4, NOTE_C4, NOTE_C4, NOTE_G4, NOTE_D4,
NOTE_G4, NOTE_D4, NOTE_D4, NOTE_D4, NOTE_G4, NOTE_A4, NOTE_B4
]

接着设置每个音调的节拍长度

# 生日快乐歌每个音符的节拍(4=四分音符,8=八分音符)
birthday_rhythm = [
8, 8, 4, 4, 4, 2,
8, 8, 4, 4, 4, 2,
8, 8, 4, 4, 4, 4, 4,
8, 8, 4, 4, 4, 2
]

# 一闪一闪亮晶晶每个音符的节拍(4=四分音符,2=二分音符)
twinkle_rhythm = [
4, 4, 4, 4, 4, 4, 2,
4, 4, 4, 4, 4, 4, 2,
4, 4, 4, 4, 4, 4, 2,
4, 4, 4, 4, 4, 4, 2,
4, 4, 4, 4, 4, 4, 2,
4, 4, 4, 4, 4, 4, 2
]

# 每个音符的节拍(8=八分音符,4=四分音符,2=二分音符)
mario_rhythm = [
8, 8, 8, 8, 8, 8, 8, 8,
4, 8, 4, 4, 8, 4,
4, 8, 4, 4, 8, 4, 8,
4, 8, 4, 8, 8, 8, 8,
4, 8, 8, 4, 8, 8, 8, 8,
4, 8, 4, 8, 4, 4,
4, 8, 4, 4, 8, 4, 8,
4, 8, 4, 8, 8, 8, 8,
4, 8, 8, 4, 8, 8, 8, 8,
4, 8, 4, 8, 4, 2
]

# 每个音符的节拍(4=四分音符,2=二分音符)
hit_rhythm = [
2, 8, 4, 4, 2, 8, 4, 4,
4, 16, 8, 8, 2, 8,
4, 4, 16, 8, 8, 4, 1,
2, 8, 4, 4, 2, 8, 4, 4,
4, 16, 8, 8, 2, 8,
4, 4, 16, 8, 8, 4, 1,
4, 4, 16, 4, 4, 4 / 3, 4, 16,
4, 4, 16, 8, 8, 8, 8, 1,
4, 4, 16, 4, 4, 16, 8, 8, 4 / 3,
4, 4, 16, 4, 4, 16, 4, 4 / 3,
4, 4, 4, 16, 8, 8, 4 / 3, 4,
4, 4, 16, 8, 8, 8, 8, 1,
4, 4, 4, 4, 16, 4, 4 / 3,
4, 4, 16, 4, 8, 8, 1
]

最后设置音乐播放的函数,分别包括四首歌,每一首歌根据旋律和节拍的数组不断更新PWM频率,从而播放出不同音调。其中tempo为音乐播放的速度,如果需要加速音乐播放速度,可以将tempo值调大。

def play_song1():
"""播放生日快乐歌"""
tempo = 1.2 # 播放速度

for i in range(len(birthday_melody)):
note_duration = 1.0 / birthday_rhythm[i]
play_tone(birthday_melody[i], note_duration * tempo)
time.sleep(note_duration * 0.1) # 音符间短暂停顿
def play_song2():
"""播放一闪一闪亮晶晶"""
tempo = 1.2 # 播放速度

for i in range(len(twinkle_melody)):
note_duration = 1.0 / twinkle_rhythm[i]
play_tone(twinkle_melody[i], note_duration * tempo)
time.sleep(note_duration * 0.1) # 音符间短暂停顿
def play_song3():
"""播放超级马里奥"""
tempo = 0.9 # 播放速度

for i in range(len(mario_melody)):
note_duration = 1.0 / mario_rhythm[i]
play_tone(mario_melody[i], note_duration * tempo)
time.sleep(note_duration * 0.1) # 音符间短暂停顿
def play_song4():
"""播放哈工大之歌"""
tempo = 1.2 # 播放速度

for i in range(len(hit_melody)):
note_duration = 1.0 / hit_rhythm[i]
play_tone(hit_melody[i], note_duration * tempo)
time.sleep(note_duration * 0.1) # 音符间短暂停顿

项目综合

在主函数中,需要完成的内容包括软件SPI初始化、OLED屏幕的显示以及蜂鸣器歌曲的播放。

对于OLED屏幕显示以及蜂鸣器歌曲的播放,采用状态机的思想,即四首歌曲四个状态,每一种对应状态对于OLED屏幕不同的显示以及蜂鸣器不同歌曲的播放。

最终实现结果可以参见我的B站视频。

if __name__ == "__main__":
# 初始化OLED
oled = SSD1306_SPI(
width=128, # 参数1: 屏幕宽度
height=32, # 参数2: 屏幕高度
spi=spi, # 参数3: SPI对象
dc=dc_pin, # 参数4: DC引脚
cs=cs_pin, # 参数5: CS引脚
reset=reset_pin # 参数6: RST引脚
)
cnt = 1

while(True):
key = read_key(adc)
if key == 1:
cnt += 1
if cnt > 4:
cnt = 1
elif key == 2:
cnt -= 1
if cnt < 1:
cnt = 4
if cnt == 1:
song1(oled)
elif cnt == 2:
song2(oled)
elif cnt == 3:
song3(oled)
elif cnt == 4:
song4(oled)
if key == 3:
if cnt == 1:
play_song1()
elif cnt == 2:
play_song2()
elif cnt == 3:
play_song3()
elif cnt == 4:
play_song4()

总结

RP2350作为树莓派家族的一款新品MCU,兼顾了低功耗与高性能的优点,在微控制领域有着重要作用。

在本次项目中,我第一次尝试使用CircuitPython进行嵌入式开发。相较于我常用的C编程而言,CircuitPython的编写更加方便快捷,通过调用相关库函数完成许多在C编程中繁琐的操作。然而,在本次项目中,也出现了一些我无法解决的难题,例如中断。对于本次项目使用的RP2350核心板,其按键采用的是ADC扩展IO的方式,因此很难通过上下沿的方式实现中断触发,由于我无法解决以上中断触发的问题,因此在按键控制代码中我采用的方式为软件延时消抖并在按键抬起时返回对应的值,这种方法虽然代码实现简单,但是会导致CPU死等,大大降低了系统的实时性。

希望未来在对CircuitPython的使用过程中能对其有进一步了解,解决目前无法解决的一些问题。

附件下载
main.py
程序主函数
adafruit_ssd1306.py
SSD1306驱动程序
adafruit_framebuf.py
屏幕显示API函数
font5x8.bin
英文字库
adc.py
核心板按键电压测试程序
key.ms14
核心板按键电压Multisim仿真
团队介绍
傅沈骁是哈尔滨工业大学(威海)自动化专业本科大四学生
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号