任务介绍
本项目实现了硬禾RP2350B核心板评测活动的任务四——基于RP2350B开发板的PWM蜂鸣器音乐播放器。系统支持通过模拟按键选择歌曲,并控制蜂鸣器播放对应旋律,同时联动数码管和OLED显示屏,实时显示当前歌曲序号与名称。
硬件平台
本次评测使用的主控设备为 RP2350B 核心板,搭载 RP2350B 微控制器,它有独特的双核、双架构,搭载了双核 ARM Cortex-M33 处理器和双核 Hazard3 RISC-V 处理器,运行频率均高达 150MHz 灵活时钟。这种双核异构处理能力,使得它很适合高精度音频播放与多任务调度。
本次使用的外设模块包括:
- 模拟电压按键输入(通过 ADC 检测按键状态)
- 数码管显示模块(ShiftDisplay 驱动,共阴极)
- OLED 屏幕(SSD1306 显示驱动,U8g2 中文字体支持)
- 蜂鸣器输出(使用 Arduino tone API 实现 PWM 音频输出)
主控设备:RP2350B核心板
- 控制芯片:RP2350B
- 外设引脚资源丰富,支持多线程任务调度(Core 0/1)
- 使用 PIO 状态机实现精确音频波形输出
传感执行器扩展板
- 无源蜂鸣器 + GPIO 驱动电路
- 共阴极两位数码管(ShiftDisplay 库驱动)
- 128x32 SSD1306 OLED 屏幕(Adafruit_SSD1306 + U8g2 GFX 支持中文显示)
任务分析与实现
本系统实现了基于 RP2350B 的蜂鸣器音乐播放器,主要功能包括:
- 三首可选曲目(以及默认静音模式)
- 按键切换歌曲并即时播放
- OLED 显示当前歌曲中文名
- 数码管同步显示歌曲编号(00~03)
- Core 0 负责按键检测、显示刷新
- Core 1 独立运行音乐播放逻辑,避免阻塞主线程
方案框图:
代码详解
一、硬件初始化与传感器配置
系统上电后完成以下初始化操作:
- 初始化 OLED 显示屏,加载中文字体
- 设置数码管引脚与模式
- 启动 Core 1 音乐播放任务
- 初始化 MillisTaskManager 定时任务调度器
void setup() {
Serial.begin(115200);
analogReadResolution(ADC_BITS);
if (!displayOLED.begin(SSD1306_SWITCHCAPVCC)) {
for (;;); // 停止程序
}
u8g2.begin(displayOLED);
multicore_launch_core1(core1_playback_task);
taskManager.TaskRegister(0, keyDetectionTask, 400);
taskManager.TaskRegister(1, displayTask, 200);
taskManager.TaskRegister(2, displayTask2, 10);
}
二、双核任务调度机制
Core 0:负责按键扫描、OLED 刷新、数码管高频刷新
Core 1:独立运行 player.play() 函数,防止阻塞 UI 更新
void core1_playback_task() {
while (true) {
if (startPlayback) {
switch (songIndex) {
case 0: noTone(BUZZER_PIN); break;
case 1: player.play(melody_mario, notes_mario, tempo_mario); break;
case 2: player.play(melody_nokia, notes_nokia, tempo_nokia); break;
case 3: player.play(melody_birthday, notes_birthday, tempo_birthday); break;
}
}
delayMicroseconds(100);
}
}
三、按键扫描任务设计
采用模拟电压检测方式读取按键状态,结合防抖机制,实现稳定切换歌曲。
void keyDetectionTask() {
int raw = analogRead(ANALOG_PIN);
float voltage = raw * (VREF / ADC_MAX);
currentKeyState = (voltage > KEY_THRESHOLD) ? HIGH : LOW;
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY && currentKeyState == LOW) {
shouldStop = true;
songIndex = (songIndex + 1) % 4;
startPlayback = true;
shouldUpdateDisplay = true;
}
}
四、显示更新任务设计
将 OLED 与数码管显示分离,仅在内容变化时刷新 OLED,数码管每 10ms 高频刷新保持亮度。
void update7SegDisplay() {
if (songIndex != lastSongIndex) {
strcpy(segBuf, songs[songIndex].display);
display7seg.set(segBuf, ALIGN_RIGHT);
lastSongIndex = songIndex;
shouldUpdateDisplay = true;
}
display7seg.show();
}
void updateOLED() {
if (shouldUpdateDisplay) {
u8g2.print(songs[songIndex].name);
displayOLED.display();
shouldUpdateDisplay = false;
}
}
使用结构体统一管理歌曲名称与数码管显示值,提高可维护性。
struct SongInfo {
const char* name; // OLED 显示的中文名称
const char display[3]; // 数码管显示的两位字符串
};
const SongInfo songs[] = {
{"静音模式", "00"},
{"超级马里奥", "01"},
{"诺基亚铃声", "02"},
{"生日快乐歌", "03"}
};
效果展示
遇到的难题与解决办法
问题
tone() 阻塞播放导致按键扫描等其他任务无法执行。
解法
将 player.play() 移至 Core 1 执行,Core 0 继续运行按键和显示任务,实现非阻塞播放。
活动感想
通过本项目的实践,深入掌握了 RP2350 的双核编程技巧、PIO 波形生成原理以及多任务调度策略。OLED + 数码管双显联动提升了交互体验,也为后续多功能嵌入式项目打下基础。
特别感谢硬禾学堂组织本次活动,提供 RP2350 开发平台,让我得以全面锻炼嵌入式系统开发能力,祝硬禾的活动越办越好!