图片来源:《星空列车与白的旅行》CG及二创
1390 字
4 分钟
项目|基于单片机的《神的随波逐流》音乐播放器
1. 前言
大家好,今天给大家带来一个有趣的 Arduino 小项目——使用无源蜂鸣器和 OLED 屏幕,完整播放《神のまにまに》(神的随波逐流)这首经典歌曲。
项目核心功能:
- 🎵 通过 PWM 调波技术驱动无源蜂鸣器播放音乐
- 🖥️ OLED 屏幕同步显示歌曲封面
- 📝 完整乐谱数据,支持歌曲循环播放
演示视频:
2. 简介
本项目使用 Arduino 开发板驱动无源蜂鸣器播放《神のまにまに》(神的随波逐流),同时在 OLED 屏幕上显示歌曲封面图片。通过 PWM 调波技术实现音调控制,支持完整的歌曲循环播放结构。
2.1 功能特性
- 🎵 PWM 调波发声,
音准准确 - 🖥️ OLED 屏幕显示位图封面
- 🔁
支持复杂的循环跳转结构 - 📦
代码结构清晰,数据与逻辑分离 - 🔧
易于修改,可扩展其他曲目
Haha…
2.2 硬件清单
| 器件 | 规格 | 数量 |
|---|---|---|
| Arduino 主控板 | ESP8266 等 | 1 |
| 无源蜂鸣器 | 3~5V 无源 | 1 |
| OLED 屏幕 | 0.96寸 I2C 128×64 SSD1306 | 1 |
| 杜邦线 | 根据开发板而定 | 若干 |
2.3 接线说明
Arduino 无源蜂鸣器 GPIO 1 ──────────── Signal
Arduino OLED屏幕 GPIO 4 ──────────── SDA GPIO 5 ──────────── SCL 3V/5V ──────────── VCC GND ──────────── GND| OLED 引脚 | Arduino 引脚 |
|---|---|
| SDA | A4 (SDA) |
| SCL | A5 (SCL) |
| VCC | 3V/5V |
| GND | GND |
2.4 软件依赖
本项目使用以下开源库:
| 库名 | 版本 | 说明 |
|---|---|---|
| Adafruit GFX Library | ≥1.10 | 图形库核心 |
| Adafruit SSD1306 | ≥2.5 | OLED 驱动库 |
| SPI | 内置 | SPI 通信库 |
| Wire | 内置 | I2C 通信库 |
2.4.1 库安装方法
在 VS Code 中的 PlatformIO 插件搜索并安装:
Adafruit GFX Libraryby AdafruitAdafruit SSD1306by Adafruit
2.5 使用说明
- 硬件连接:按照上述接线图完成电路连接
- 安装库文件:安装上述软件依赖
- 编译上传:将
main.cpp文件烧录到 Arduino 开发板 - 启动播放:打开串口监视器(波特率 9600),发送任意字符开始播放
2.6 项目结构
├── README.md # 项目说明文档└── src └── main.cpp # 主程序文件3. 技术原理
3.1 PWM 调波发声原理
无源蜂鸣器需要外部电路提供交流脉冲信号才能发声。通过 Arduino 的 tone() 函数,我们可以:
- 生成指定频率的方波信号:不同频率对应不同音高
- PWM 占空比固定为 50%:确保信号稳定
- 音调与频率对应关系:
- 低音:262Hz (C4) ~ 494Hz (B4)
- 中音:523Hz (C5) ~ 988Hz (B5)
┌─────────────┐│ Arduino │ ──tone(440Hz)──▶ [蜂鸣器] ──▶ 发出 A4 音└─────────────┘3.2 乐谱数据结构设计
本项目采用双数组存储乐谱信息:
| 数组 | 作用 | 数据示例 |
|---|---|---|
musicSentenceTones | 音调序列 | -5, -6, 1, 2, 2, 1... |
musicSentenceTime | 时长序列 | 4, 4, 4, 3, 1... |
音调编码规则:
// 对照规则:低音 -1~-7; 中音 1~7; 高音 11~17; 2# 为 33int lowTones[8] = {0, 262, 294, 330, 349, 392, 440, 494}; // 低音频率int midTones[8] = {0, 523, 587, 659, 698, 784, 880, 988}; // 中音频率int mid2sTone = 622; // 中音 2# 频率3.3 OLED 显示原理
使用 Adafruit_SSD1306 库驱动 128×64 分辨率的 OLED 屏幕:
- 通信协议:I2C(只占用 2 个 GPIO 引脚)
- 显示方式:位图数组直接绘制
- 显存操作:双缓冲避免闪烁
OLED 引脚连接:┌─────────────┐│ Arduino ││ GPIO 5 ────▶ SCL (时钟线)│ GPIO 4 ────▶ SDA (数据线)└─────────────┘4. 代码展示
前置警告
⚠️ 前方高能警告!请谨慎观看! ⚠️
警告以下代码仅用于整活、娱乐、以及证明我能让单片机唱歌,不代表任何正确的工程规范、算法思想、代码审美或嵌入式最佳实践。
代码里充斥着:
- 暴力
delay()阻塞式播放 - 写死到 Flash 里的乐谱数组
- 人工计算的跳段行数
- 毫无封装的大段逻辑
- 能跑就行的低技术力写法
- 以及巨大的可优化空间
总之:能响,但完全不优雅。
再次警告如果你是算法大佬、嵌入式大神、代码洁癖患者,现在退出还来得及。
4.1 完整源代码
//《神のまにまに》音乐测试
//库引用#include <Arduino.h>#include <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>
//接口设置#define MUSIC 1 //音频信号接口GPIO//OLED屏SCL接口GPIO 5//OLED屏SDA接口GPIO 4
//oled相关设置#define SCREEN_WIDTH 128 //宽#define SCREEN_HEIGHT 64 //高#define OLED_RESET -1 //使用RESET引脚时设为-1#define SCREEN_ADDRESS 0x3C //I2C接口地址Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);//OLED声明
//音频常量设置#define SIXTEENTH_NOTE 111 //十六分音符时长修正参数 单位毫秒#define LENGTH_OF_ROWS 52 //数组行数#define LENGTH_OF_CULUMNS 19 //数组列数
//位图数组const unsigned char img [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xf7, 0xff, 0x7f, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xe3, 0xff, 0x7c, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xc3, 0x8e, 0x20, 0x0f, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe1, 0xff, 0xff, 0xfc, 0x43, 0x8e, 0x20, 0x0f, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xf1, 0x87, 0xff, 0xdc, 0xc3, 0xce, 0x12, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xf0, 0x01, 0xff, 0xf7, 0xc3, 0x7e, 0x1e, 0x7b, 0x1f, 0xff, 0xff, 0xfc, 0xff, 0xe0, 0x01, 0xfc, 0xe3, 0x82, 0x72, 0x0a, 0x19, 0x3f, 0xff, 0xff, 0xfc, 0x1f, 0xc0, 0x78, 0xfd, 0xfb, 0x83, 0xfe, 0x10, 0x19, 0x3f, 0xff, 0xff, 0xfc, 0x0f, 0xc0, 0x0c, 0xe7, 0x07, 0xc3, 0xce, 0x10, 0x67, 0x3c, 0x7f, 0xff, 0xfc, 0x0f, 0xc6, 0x04, 0x64, 0x02, 0xc7, 0xff, 0x18, 0x46, 0x3c, 0x1f, 0xff, 0xfc, 0x7d, 0xc4, 0x0c, 0x7c, 0x31, 0xc7, 0xff, 0x1e, 0x7e, 0x7b, 0x0f, 0xff, 0xfc, 0xf0, 0xe0, 0x1c, 0x39, 0x39, 0xc6, 0xfb, 0x1e, 0x7c, 0xe7, 0xff, 0xbf, 0xfc, 0x00, 0xf1, 0x90, 0x30, 0x78, 0xa7, 0x7f, 0x60, 0x7c, 0xe7, 0xf0, 0x3f, 0xfc, 0x00, 0xf1, 0x80, 0x32, 0x7c, 0xf3, 0xff, 0x80, 0x19, 0xff, 0xb0, 0x0f, 0xfc, 0x01, 0xf9, 0x01, 0xf2, 0x5c, 0xfb, 0x8f, 0x80, 0x01, 0x39, 0xff, 0x07, 0xfc, 0x31, 0xf8, 0x04, 0xf2, 0x5c, 0xfc, 0x39, 0x81, 0xc1, 0x38, 0xee, 0x7f, 0xfc, 0x63, 0xfc, 0x63, 0xf8, 0x79, 0xf1, 0x7c, 0xf3, 0xf1, 0xf8, 0x72, 0x7f, 0xfc, 0xe1, 0xfe, 0xe3, 0x38, 0xf3, 0xe1, 0x06, 0x3f, 0x33, 0xe4, 0x30, 0x3d, 0xfc, 0xe0, 0x7f, 0xf1, 0x3c, 0xf3, 0x81, 0x00, 0x0f, 0x3d, 0xe6, 0x30, 0x38, 0xfc, 0xc0, 0x3f, 0xf1, 0xfe, 0xff, 0x80, 0x00, 0x0d, 0xff, 0xff, 0x38, 0xf9, 0xfc, 0xc0, 0xff, 0x38, 0xe7, 0x9c, 0x00, 0xc0, 0x31, 0xe7, 0x3c, 0xf9, 0xf9, 0xfc, 0x8c, 0x7f, 0x38, 0xe7, 0xbc, 0x01, 0xfc, 0x11, 0xef, 0x3e, 0x09, 0xf9, 0xe4, 0x9e, 0x7f, 0xec, 0x7f, 0xfe, 0x01, 0xfe, 0x03, 0xff, 0xfe, 0x03, 0x73, 0xe0, 0x1f, 0x39, 0xe6, 0x3c, 0xf6, 0x03, 0x76, 0x02, 0x7d, 0xe6, 0x00, 0xf7, 0xf8, 0x3f, 0x39, 0xee, 0x3d, 0xff, 0x00, 0x72, 0x03, 0xf9, 0xf7, 0x80, 0x67, 0xfc, 0x7f, 0x9f, 0x3f, 0x27, 0x9f, 0x01, 0xfc, 0x03, 0xff, 0xbc, 0x8e, 0x0f, 0xfc, 0xff, 0x9f, 0x38, 0xff, 0xfc, 0x21, 0xfc, 0x01, 0xf7, 0xbf, 0xf3, 0x0f, 0xfc, 0xff, 0x8f, 0xff, 0xff, 0xf8, 0x01, 0xf8, 0x00, 0xff, 0xdf, 0xff, 0xdf, 0xdc, 0xff, 0xcb, 0xf7, 0xfe, 0xf9, 0x80, 0x70, 0x0c, 0xff, 0xff, 0xfc, 0x97, 0xcc, 0xff, 0xfb, 0xff, 0xfd, 0x2b, 0x26, 0x53, 0x86, 0xed, 0xfb, 0x9c, 0x97, 0xc4, 0xff, 0x7f, 0xff, 0xc3, 0x7f, 0x32, 0xd3, 0x32, 0xe6, 0x0f, 0xff, 0x9f, 0xe0, 0xff, 0xce, 0x3f, 0x82, 0x7e, 0x3e, 0x03, 0xf1, 0xfa, 0x0f, 0xff, 0xdf, 0xf0, 0xff, 0xcf, 0xff, 0x84, 0xfe, 0x4e, 0x03, 0xc9, 0xf9, 0x0f, 0xff, 0xff, 0xfc, 0xfe, 0xff, 0xff, 0x05, 0xff, 0x4c, 0x21, 0xcb, 0xff, 0x07, 0xfc, 0xf3, 0xfc, 0xff, 0xfb, 0xff, 0xed, 0xff, 0xfc, 0x71, 0xfb, 0xfd, 0x0f, 0x9c, 0x71, 0xfc, 0xfe, 0x3b, 0xff, 0xe9, 0xff, 0xf8, 0x70, 0xff, 0xfd, 0x7f, 0x9c, 0x7f, 0xfc, 0xfd, 0xc7, 0xff, 0xc9, 0xff, 0xf8, 0x70, 0xff, 0xff, 0xbf, 0xfb, 0x7f, 0xfc, 0xff, 0xff, 0xff, 0x87, 0xff, 0xf0, 0x20, 0xff, 0xfe, 0x1f, 0xfb, 0x7e, 0xfc, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xf0, 0x20, 0x7f, 0xff, 0xc3, 0xe7, 0xbf, 0xfc, 0xfe, 0x1b, 0xcd, 0xff, 0xff, 0xe0, 0x60, 0x3f, 0xff, 0xfb, 0xbf, 0xff, 0xfc, 0xff, 0xff, 0x93, 0xff, 0xff, 0xd9, 0x80, 0x7f, 0xff, 0xfc, 0xdf, 0xc0, 0xfc, 0xff, 0xff, 0x67, 0xff, 0xff, 0xda, 0x10, 0x5f, 0xff, 0xfe, 0x31, 0xe1, 0xfc, 0xff, 0xfc, 0xcf, 0xff, 0xff, 0x3c, 0x65, 0x6f, 0xff, 0xff, 0xdb, 0xfc, 0xfc, 0xf9, 0xfd, 0x1f, 0xff, 0xfe, 0xf9, 0x91, 0xf7, 0xff, 0xff, 0xc5, 0xf5, 0xfc, 0xff, 0xfd, 0x3f, 0xff, 0xfd, 0xef, 0x3f, 0xb9, 0xff, 0xff, 0xe6, 0x9f, 0xfc, 0xff, 0xfa, 0x3f, 0xff, 0xf3, 0xee, 0x66, 0xbc, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0xfe, 0x66, 0x7f, 0xff, 0xe7, 0xcf, 0x87, 0x1f, 0x3f, 0xff, 0xf1, 0x7f, 0xfc, 0x10, 0x0c, 0x8f, 0xff, 0x9f, 0xc9, 0x01, 0x8f, 0xcf, 0xff, 0xf9, 0x9c, 0x7c, 0x40, 0x00, 0xaf, 0xfe, 0x7f, 0x80, 0x05, 0x8f, 0xe3, 0xff, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x84, 0x73, 0x0f, 0xf8, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x03, 0xff, 0x83, 0xfe, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1f, 0xff, 0x00, 0xf8, 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x38, 0x03, 0xff, 0x80, 0x00, 0x00, 0x00, 0x01, 0x80, 0x20, 0x6f, 0xfe, 0x00, 0x00, 0x03, 0xff, 0x82, 0x00, 0x00, 0x00, 0x01, 0x80, 0x20, 0xff, 0xfc, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x80, 0x21, 0x7f, 0xfe, 0x00, 0x40, 0x03, 0xff, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x40, 0x20, 0x47, 0xfe, 0x10, 0x30, 0x03, 0xff, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x61, 0xa4, 0x43, 0xfc, 0x3f, 0x1a, 0x01, 0xff, 0xe0, 0x40, 0x00, 0x00, 0x00, 0x31, 0xa7, 0xc7, 0xf8, 0x1f, 0x01, 0x00, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x10, 0x87, 0xff, 0xf8, 0x1f, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x1e, 0xff, 0xf8, 0x1f, 0x06, 0x18, 0x0f, 0xfe, 0x00, 0x01, 0x00};
//声音频率寄存int lowTones[8] = {0,262,294,330,349,392,440,494}; //主低音PWM设定int midTones[8] = {0,523,587,659,698,784,880,988}; //主中音PWM设定int mid2sTone = 622; //中音2#PWM设定//int highTones[8] = {0,1047,1175,1319,1397,1568,1760,1976}; //主高音PWM设定 没用上哈哈
int breakPoint = 0; //循环标记
//乐句音调寄存 对照规则:低音-1~-7;中音1~7;高音11~17;2# 33 注释数字表示开始时对应的标记变量int musicSentenceTones[LENGTH_OF_ROWS][LENGTH_OF_CULUMNS] = { //前奏0 {-5,-6,1,2,2,1,1,2,3,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3,3}, {5,3,2,1,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3,3,2,3,2,1},
//A段a {0,-3,-5,-6,1,-7,-6,-5,-3,-1,-2,-1,-2,-3}, //接断点0 rows = 7 {0,-1,-2,-2,-1,-3,-2,-1,-2,-3,-5,-6,-3}, {0,-3,-5,-6,1,1,-6,1,2,33,2,2,33,2,1}, {0,-5,-6,1,0,-5,-6,1}, {0,-5,-6,1,1,2,-6,1},
//A段b {0,-3,-5,-6,1,-7,-6,-5,-3,-1,-2,-1,-2,-3}, {0,-1,-2,-2,-1,-3,-2,-1,-2,-3,-5,-6,-3}, {0,-3,-5,-6,1,1,-6,1,2,33,2,2,33,2,1}, {0,-5,-6,1,0,-5,3,1,2,1}, {0,2,1,1,1,-6,1,2,3,4,3,2,1,1,3}, {0,-6,3,2,1}, {1,1,-6,1,1,-6,1,2,3,-6,1,1,-6,1,2,3,4,33,1}, {0,-5,-5,-5,1,1,2,2},
//B段a {-5,-6,1,2,2,1,1,2,3,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3,3}, {5,33,2,1,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3,3,2,3,2,1},
{-5,-6,1,2,2,1,1,2,3,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3,3,5,33,2,1,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3}, {2,2,1,1,2,3,3,3},
//反复句1 断点0 {2,3,2,1},
//反复句2 断点1 {2,3,2,1,1,1,1,5,5,5,1,1,1,-7,1,2}, {1,-7,1,2,2,2,5,4,3,2,2,2,3,2,1}, {0,-6,-6,-7,1,1,-6,1,2,3,2,1,1,-6,1}, {3,3,4,33,1}, {0,5,4,1,2,2},
//结束句 断点2 {2,3,2,1}, {-5,-6,1,2,1,2,3,3}, {2,1,2,3,3}, {2,1,1,2,3,3,3,5,2,3,2,1,3}, {2,1,1,2,3,3}, {2,1,1,2,3,3}, {2,1,1,2,3,3,3,2,3,2,1}, {-5,-6,1,2,1,1,2,3,3}, {2,1,1,2,3,3}, {2,1,1,2,3,3,3,5,2,3,2,1,3}, {2,1,1,2,3,3}, {2,1,1,2,3,3}, {2,1,1,2,3,3,3,2,3,2,1}};
//乐句时长寄存 单位:_个十六分音符int musicSentenceTime[LENGTH_OF_ROWS][LENGTH_OF_CULUMNS] = { //前奏 7行 {4,4,4,3,1,3,1,2,2,4}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,2,2}, {2,2,2,2,8}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,2,2,2,1,1,16},
//A段a 5行 {6,2,2,2,4,4,2,2,2,2,2,2,2,6}, //接断点0 rows = 7 {2,2,4,3,1,2,2,2,2,2,2,2,2}, {2,2,2,2,4,3,1,2,2,2,2,2,2,2,2}, {2,2,2,2,2,2,2,2}, {2,2,2,2,2,2,2,2},
//A段b 8行 {2,2,2,2,4,4,2,2,2,2,2,2,2,6}, {2,2,4,3,1,2,2,2,2,2,2,2,2}, {2,2,2,2,4,3,1,2,2,2,2,2,2,2,2}, {2,2,2,6,2,2,3,3,2,8}, {2,2,4,4,3,1,2,2,2,4,4,4,2,2,10}, {2,2,2,6,6}, {2,2,2,4,3,1,2,2,2,2,4,3,1,2,2,2,4,4,6}, {2,1,1,2,4,6,2,22},
//B段a 7行+6行 {4,4,4,3,1,3,1,2,2,4}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,2,2}, {2,2,2,2,8}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,2,2,2,1,1,6},
{2,2,2,3,1,3,1,2,2,4}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,2,2,2,2,2,2,8}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,4}, {3,1,3,1,2,2,2,2}, //断点! rows = 32
//反复句1 断点0 {2,1,1,12}, //rows = 33
//反复句2 断点1 {2,1,1,6,2,2,2,4,4,2,2,2,2,2,4,4}, {2,2,2,4,2,2,2,2,2,2,2,1,1,2,2}, {2,2,2,2,4,2,2,2,2,2,4,2,1,1,4}, {2,2,4,4,14}, {2,2,2,2,2,22}, //rows = 38
//结束句 断点2 {2,1,1,6}, {2,2,2,4,4,2,2,4}, {4,4,2,2,4}, {4,3,1,2,2,2,2,2,1,1,2,2,8}, {4,3,1,2,2,4}, {4,3,1,2,2,4}, {4,3,1,2,2,2,2,2,1,1,6}, {2,2,2,4,3,1,2,2,4}, {4,3,1,2,2,4}, {4,3,1,2,2,2,2,2,1,1,2,2,8}, {4,3,1,2,2,4}, {4,3,1,2,2,4}, {4,3,1,2,2,2,2,2,1,1,16}};
void setup() { Serial.begin(9600); //开启串口通信
pinMode(MUSIC,OUTPUT); //音频接口设为输出模式
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS); //初始化OLED display.clearDisplay(); //清除显示缓存 display.drawBitmap(7, 0, img, 102, 64, WHITE); //显示 display.display();}
void loop() { if (Serial.available() > 0){ Serial.end(); for (int rows = 0; rows < LENGTH_OF_ROWS; rows ++){ //乐句遍历循环
for (int columns = 0; columns < LENGTH_OF_CULUMNS; columns ++){ //音符遍历循环
int thisTone = musicSentenceTones[rows][columns]; //当前音符 int thisTime = SIXTEENTH_NOTE * musicSentenceTime[rows][columns] - 40; //当前时长
if (thisTone == 0 && musicSentenceTones[rows][columns+1] == 0){ //判断该乐句是否结束,且不是休止符 break; //若结束,进入下一乐句
}else if (thisTone < 0){ //判断是否是低音 thisTone *= -1; //改成PWM tone(MUSIC,lowTones[thisTone]); //播放 delay(thisTime); noTone(MUSIC); //结束播放 delay(40);
}else if(thisTone == 33){ //判断是否是2# tone(MUSIC,mid2sTone); delay(thisTime); noTone(MUSIC); delay(40);
}else{ //那就是中音了 tone(MUSIC,midTones[thisTone]); //播放 delay(thisTime); //等待时长 noTone(MUSIC); delay(40); } } if (breakPoint == 0){ //在断点0,33行时执行反复 if (rows == 33){ delay(SIXTEENTH_NOTE * 4); //休止符 rows = 6; //反复 breakPoint += 1; delay(40); //速度快了,等一下 哈哈 }
}else if (breakPoint == 1){ //在断点1 if (rows == 32){ rows = 33; //32行时跳过第33行 } if (rows == 38){ rows = 19; //38行时反复 breakPoint = 2; //断点设为2 delay(40); //速度快了,等一下 哈哈 } }else{ //在断点2 if (rows == 32){ rows = 38; //32行时进入结束句! delay(40); //速度快了,等一下 哈哈 } } } for(;;); //结束程序 }
delay(100); //防卡死}4.2 音乐播放核心逻辑
为了提高可读性,这里做了部分删改。
void playMusic() { for (int rows = 0; rows < LENGTH_OF_ROWS; rows++) { for (int columns = 0; columns < LENGTH_OF_CULUMNS; columns++) {
int thisTone = musicSentenceTones[rows][columns]; int thisTime = SIXTEENTH_NOTE * musicSentenceTime[rows][columns] - 40;
// 休止符判断 if (thisTone == 0 && musicSentenceTones[rows][columns + 1] == 0) { break; } // 低音处理 else if (thisTone < 0) { thisTone *= -1; tone(MUSIC, lowTones[thisTone]); delay(thisTime); noTone(MUSIC); delay(40); } // 升音处理 else if (thisTone == 33) { tone(MUSIC, mid2sTone); delay(thisTime); noTone(MUSIC); delay(40); } // 中音处理 else { tone(MUSIC, midTones[thisTone]); delay(thisTime); noTone(MUSIC); delay(40); } } // 循环控制逻辑... }}5. 算法解读
5.1 循环控制机制
本项目实现了复杂的循环结构,通过 breakPoint 变量控制歌曲的反复段:
歌曲结构:├── 前奏(0-6行)├── A段(7-14行)├── B段(15-32行)├── 反复句1(33行)→ 回到前奏├── 反复句2(34-38行)→ 回到B段└── 结束句(39-51行)断点控制逻辑:
breakPoint = 0 → 到达第33行时,反复到第6行breakPoint = 1 → 到达第38行时,反复到第19行breakPoint = 2 → 到达第32行时,进入结束句5.2 时长计算公式
thisTime = SIXTEENTH_NOTE * musicSentenceTime[rows][columns] - 40;- 基准单位:十六分音符(111ms)
- 时长修正:乘以节拍数,再减去 40ms 作为音间间隔
- 为什么要减40ms:为下一个音符的
noTone()留出间隙
5.3 音调编码映射
| 编码值 | 实际音高 | 计算方式 |
|---|---|---|
-1 ~ -7 | 低音 1~7 | lowTones[abs(编码)] |
1 ~ 7 | 中音 1~7 | midTones[编码] |
33 | 中音 2# | 特殊值,查表 mid2sTone |
0 | 休止符 | 无操作(需配合下一音判断) |
6. 总结
6.1 项目亮点
其实是没有亮点,但是博主牵强附合(
- 资源占用少:仅使用 2 个 GPIO 引脚(I2C 通信)
- 代码结构清晰:数据与逻辑分离,便于维护和扩展
- 支持循环播放:通过断点机制实现复杂的歌曲结构
6.2 硬件清单
| 器件 | 数量 | 说明 |
|---|---|---|
| ESP8266 开发板 | 1 | 主控芯片 |
| 无源蜂鸣器 | 1 | 发声元件 |
| OLED 屏幕 (SSD1306) | 1 | 128×64 I2C 接口 |
| 杜邦线 | 若干 | 连接线路 |
6.3 可扩展方向
实际情况是博主技术力不够,实现不了
- 🔄 添加按钮实现”播放/暂停”功能
- 📊 增加音量调节
- 🎨 支持多首歌曲切换
- 📝 添加歌词同步显示功能
完整代码已上传至 GitHub,欢迎围观!
没事反正人少,丢人也丢不到哪去,哈哈
Waiting for api.github.com...
如果你想体验一下,不妨去下载代码。操作方法已经写入README.md文件里了。
如果有任何问题,欢迎在评论区留言交流~ 🎵
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
项目|基于单片机的《神的随波逐流》音乐播放器
https://luq-blog.xyz/posts/2026-05-28-beep-audio/ 部分信息可能已经过时
相关文章 智能推荐
1
DIY|电机/机械硬盘改音响
创意DIY 关于使用功放板让电机发声的原理及教程。
2
笔记|数据分析学习笔记05|ndarray的数据类型
理工研习 学习NumPy中ndarray支持的数据类型,包括布尔、整数、浮点数和复数类型。
3
日常|SCO-YSC日记10|又是跑代码的一天啊
生活随笔 模型终于训练的差不多啦!
4
笔记|数据分析学习笔记19|DataFrame的访问
理工研习 本节介绍 Pandas DataFrame 的数据访问方式,包括 loc、iloc、at、iat 索引器以及布尔索引、单列/多列获取、抽样等操作。
5
笔记|数据分析学习笔记20|DataFrame的常用方法
理工研习 本节介绍 Pandas DataFrame 的常用方法,包括统计描述、缺失值判断、去重排序、值替换和频率统计等操作。
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)