一、硬件配置与任务需求匹配
核心板选型
STEP_RP2350B核心板:基于RP2350B芯片,提供48个GPIO(相比RP2350A的30个GPIO扩展性更强),满足按键、OLED屏幕、蜂鸣器等多外设需求。
扩展板功能:
OLED屏幕:128×32分辨率,SPI接口,用于显示曲目名称(支持汉字)。
按键输入:4颗按键通过分压电阻接入ADC引脚,通过电压值区分按键状态(需消抖处理)。
蜂鸣器:通过PWM输出控制音调,生成音乐旋律。
关键外设连接
OLED屏幕:
SPI接口需手动映射GPIO(如SCK→GP45、MOSI→GP44),因硬件SPI引脚与扩展板不匹配,需使用软件SPI模拟。
使用U8g2库驱动屏幕,支持中文显示(需加载字库文件,如wqy16.ttf)。
按键输入:
通过ADC读取分压值(如按键1→56、按键2→52等),结合定时器中断实现消抖(连续3次采样一致才确认按键状态)。
PWM输出:
蜂鸣器连接至任意PWM引脚(如GP0),通过调整频率(如262Hz对应C4音调)和占空比(5%~10%)控制音高和音量。
二、开发环境搭建与问题解决
Arduino IDE配置
安装支持库:
RP2350B核心:通过Arduino Board Manager添加URL(如https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json),选择Raspberry Pi Pico/RP2040板卡(需确认兼容性)。
U8g2库:通过库管理器安装,用于OLED汉字显示。
引脚定义修正:
若PlatformIO仅支持RP2350A,需手动修改pins_arduino.h文件,将NUM_DIGITAL_PINS定义为48,并补充GPIO映射表。
或通过编译标志-D PICO_RP2350B=1临时扩展引脚访问(需验证稳定性)。
替代方案:MicroPython开发
环境搭建:
使用Thonny IDE,选择MicroPython解释器(Raspberry Pi Pico)。
拖放UF2固件至Pico 2(按住BOOTSEL键上电)。
三、环境搭建
参考着产品维库搭建Arduino环境,配置好Preferences窗口。

选择开发板

任务实现
方案框图:

实现流程图:

RP2350B的双核(Core 0和Core 1)可独立运行任务,通过硬件邮箱(Mailbox)或共享内存通信。本方案将高实时性任务(PWM生成、ADC检测)分配给Core 0,低实时性任务(OLED显示)分配给Core 1,避免屏幕刷新干扰音乐播放的稳定性。
代码
// oled管脚 #define OLED_MOSI 44 #define OLED_CLK 45 #define OLED_DC 42 #define OLED_CS 41 #define OLED_RESET 43 U8G2_SSD1306_128X32_UNIVISION_1_4W_SW_SPI u8g2(U8G2_R0, OLED_CLK, OLED_MOSI, OLED_CS, OLED_DC, OLED_RESET);
OLED使用的是SSD1306的I驱动,走SPI接口,使用了5个GPIO,根据电路图找出对应的管脚信息。
void setup1()
{
u8g2.begin();
u8g2.enableUTF8Print(); // 启用UTF8支持
}
void loop1()
{
u8g2.setFont(u8g2_font_wqy16_t_gb2312); // 设置中文字体
u8g2.setFontDirection(0);
u8g2.firstPage();
do
{
// u8g2.setCursor(0, 10);
// u8g2.print("汉字显示12345ABCder"); // 显示中文
u8g2.setCursor(40, 25);
// u8g2.print("中文,小学校");
switch (keyval.key)
{
case 1: // 小星星
u8g2.print(startsname);
break;
case 2: // 樱花
u8g2.print(flowname);
break;
case 3: // 生日歌
u8g2.print(birthdayname);
break;
}
} while (u8g2.nextPage());
// Serial.println(sizeof(pmusic));
delay(1000);
}按键读取。

从电路图可知,四个按键经电阻分压使DVout端电压随按键按下而变化。代码中启用定时器,以5ms为周期触发中断,每次中断时通过ADC读取DVout电压值。为消除机械按键抖动,当检测到电压变化时启动计数器,仅当连续3次中断(15ms)内电压保持稳定,才判定按键有效,并将对应键值赋给变量。此方法兼顾了实时性与抗干扰性,适用于嵌入式系统低功耗场景。
// 在每次时间中断中,检查ADC的值
struct repeating_timer segtimer, pwmtimer; // 两个定时器 1个用来控制数码官,一个用来控制pwm播放
bool segdisp_timer_callback(struct repeating_timer *t)
{
uint adcval;
Tube.displayInt(keyval.key);
// 将读取到的值缩小1000倍,拨玛开关全关闭情况下 大约为 59:无按键0 56:按键1 52:按键2 45:按键3 30:按键4
adcval = analogRead(ADCBTN) / 1000; // 从47口读取电压
// 消抖:当前读取的ADC值与当前按键值作比较,相同超过指定次数,就赣边keyval的值
if (adcval >= 58)
{
// 无按键
if (keyval.curkeyval == 0)
{
//keyval.val++;
}
else
{
keyval.curkeyval = 0;
keyval.val = 0;
}
}
else if (adcval >= 55) // 按键1
{
if (keyval.curkeyval == 1)
keyval.val++;
else
{
keyval.curkeyval = 1;
keyval.val = 0;
}
}
else if (adcval >= 51) // 按键2
{
if (keyval.curkeyval == 2)
keyval.val++;
else
{
keyval.curkeyval = 2;
keyval.val = 0;
}
}
else if (adcval >= 44) // 按键3
{
if (keyval.curkeyval == 3)
keyval.val++;
else
{
keyval.curkeyval = 3;
keyval.val = 0;
}
}
else if (adcval >= 29) // 按键4
{
if (keyval.curkeyval == 4)
keyval.val++;
else
{
keyval.curkeyval = 4;
keyval.val = 0;
}
}
if (keyval.val > 2)
{
keyval.key = keyval.curkeyval == 0 ? keyval.key : keyval.curkeyval;
keyval.isChange = keyval.curkeyval == 0 ? false:true;
}
return true;
}核心功能实现PWM库适配采用RP2040官方hardware_pwm库,经实测在RP2350上稳定运行,支持动态频率与占空比调节。曲谱存储与频率映射将3首曲目的简谱转换为频率数组(如C4=262Hz,D4=294Hz),存储于程序Flash中:const uint16_t song_freq[] = {262, 294, 330, 349}; // 示例:《小星星》片段定时器驱动播放通过硬件定时器(如1ms周期中断)逐次读取频率数组,动态更新PWM输出:void timer_isr() {
static uint8_t note_idx = 0;
if (note_idx >= SONG_LENGTH) note_idx = 0; // 循环播放
pwm_set_freq_duty(song_freq[note_idx++], 8); // 更新频率与占空比
}PWM参数优化占空比选择测试范围:1%~10%占空比下,蜂鸣器音量随占空比线性增强;饱和阈值:超过10%后音量提升不明显,且蜂鸣器发热严重;最终设定:固定占空比为8%,平衡音量与功耗。频率精度控制PWM时钟源配置为125MHz,通过分频器实现1Hz级频率调节(如262Hz需分频系数=125M/262≈477096);支持无源蜂鸣器音高准确还原。
uint musicoffset = 0;
bool pwmmusic_timer_callback(struct repeating_timer *t)
{
float freq;
switch (keyval.key)
{
case 1: //小星星
if (musicoffset >= sizeof(starts)/4)
musicoffset = 0;
freq = MUSICFREQ[starts[musicoffset]];
PWM_Instance->setPWM(BEEP, freq, starts[musicoffset] == nu ? 0 : PWMVAL);
break;
case 2: //樱花
if (musicoffset >= sizeof(flower)/4)
musicoffset = 0;
freq = MUSICFREQ[flower[musicoffset]];
PWM_Instance->setPWM(BEEP, freq, flower[musicoffset] == nu ? 0 : PWMVAL);
break;
case 3: //生日歌
if (musicoffset >= sizeof(birthday)/4)
musicoffset = 0;
freq = MUSICFREQ[birthday[musicoffset]];
PWM_Instance->setPWM(BEEP, freq, birthday[musicoffset] == nu ? 0 : PWMVAL);
break;
}
musicoffset++;
return true;
}
void setup()
{
Serial.begin(115200); //
pinMode(LED, OUTPUT);
analogReadResolution(16); // 调整ADC分辨率为16位
PWM_Instance = new RP2040_PWM(BEEP, 1000, 3);
delay(100);
// PWM_Instance->disablePWM();
add_repeating_timer_ms(20, segdisp_timer_callback, NULL, &segtimer); // 每5ms调用一次
add_repeating_timer_ms(MUSICTIME, pwmmusic_timer_callback, NULL, &pwmtimer); // 指定周期调用一次
}
void loop()
{
switch (keyval.key)
{
case 1: // 小星星
if (keyval.isChange)
{
keyval.isChange = false;
musicoffset = 0;
Serial.println(sizeof(starts));
// add_repeating_timer_ms(startsspeed, pwmmusic_timer_callback, NULL, &pwmtimer); //指定周期调用一次
}
break;
case 2: // 樱花
if (keyval.isChange)
{
keyval.isChange = false;
musicoffset = 0;
Serial.println(sizeof(flower));
// add_repeating_timer_ms(flowerspeed, pwmmusic_timer_callback, NULL, &pwmtimer); //指定周期调用一次
}
break;
case 3: // 生日歌
if (keyval.isChange)
{
keyval.isChange = false;
musicoffset = 0;
Serial.println(sizeof(birthday));
// add_repeating_timer_ms(birthdayspeed, pwmmusic_timer_callback, NULL, &pwmtimer); //指定周期调用一次
}
break;
}
}数码管显示。
STEP_RP2350B核心板上还有个很漂亮的数码管,这里已将其驱动起来,用来显示当前的按键选择。

从电路图可以看出,数码管使用两颗74HC595级联。可以使用3个GPIO,用串行方式来驱动数码管的显示。这里写了个Tube595的驱动。显示时,数码管点亮一小会,利用肉眼错觉来显示具体的数字。
代码
#ifndef TUBE595_cpp
#define TUBE595_cpp
#include "Tube595.h"
unsigned char Tube595::LED_MODEL[17] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F -
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40
};
unsigned char Tube595::LedData[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Tube595::Tube595(int dataPin, int sclkPin, int rclkPin) {
DIO = dataPin;
SCLK = sclkPin;
RCLK = rclkPin;
pinMode(SCLK, OUTPUT);
pinMode(RCLK, OUTPUT);
pinMode(DIO, OUTPUT);
digitalWrite(DIO, LOW);
digitalWrite(SCLK, LOW);
digitalWrite(RCLK, LOW);
}
// display a int number
// 整形显示,point 0 第0位的小数点 1 第1位的小数点 2 两个一起亮
void Tube595::displayInt(int num, int point) {
char string[9] = { 0 };
sprintf(string, "%8d", num);
for (int i = 7; i >= 0; i--) {
switch (string[i]) {
case ' ':
break;
case '-':
LedData[7 - i] = LED_MODEL[16];
break;
default:
LedData[7 - i] = LED_MODEL[string[i] - '0'];
// LedData[7-i] = LED_MODEL[1];
break;
}
}
if (point == 1) setPoint(0);
else if (point == 2) setPoint(1);
else if (point == 3) {
setPoint(0);
setPoint(1);
}
update();
}
void Tube595::setNoPoint(int place) {
LedData[place] &= 0x80;
}
void Tube595::setPoint(int place) {
LedData[place] |= 0x80;
}
void Tube595::closeDisplay() {
for (int i = 0; i < 8; i++)
LedData[i] = 0x7F;
update();
}
void Tube595::update() {
// 仅有两个数码管
for (int i = 0; i < 2; i++) {
shiftOut(DIO, SCLK, MSBFIRST, 1 << i);
shiftOut(DIO, SCLK, MSBFIRST, LedData[i]);
digitalWrite(RCLK, LOW);
digitalWrite(RCLK, HIGH);
delayMicroseconds(2000); //数据锁存 显示2ms
}
shiftOut(DIO, SCLK, MSBFIRST, 0);
digitalWrite(RCLK, LOW);
digitalWrite(RCLK, HIGH);
}
#endif作品展示
使用简谱写了三首常见的音乐。通过按键切换不同的曲子。



我要赚赏金
