图片来源:《星空列车与白的旅行》CG及二创
1372 字
4 分钟
项目|基于单片机的《神的随波逐流》音乐播放器
前言
大家好,今天给大家带来一个有趣的 Arduino 小项目——使用无源蜂鸣器和 OLED 屏幕,完整播放《神のまにまに》(神的随波逐流)这首经典歌曲。
项目核心功能:
- 🎵 通过 PWM 调波技术驱动无源蜂鸣器播放音乐
- 🖥️ OLED 屏幕同步显示歌曲封面
- 📝 完整乐谱数据,支持歌曲循环播放
演示视频:
简介
本项目使用 Arduino 开发板驱动无源蜂鸣器播放《神のまにまに》(神的随波逐流),同时在 OLED 屏幕上显示歌曲封面图片。通过 PWM 调波技术实现音调控制,支持完整的歌曲循环播放结构。
功能特性
- 🎵 PWM 调波发声,
音准准确 - 🖥️ OLED 屏幕显示位图封面
- 🔁
支持复杂的循环跳转结构 - 📦
代码结构清晰,数据与逻辑分离 - 🔧
易于修改,可扩展其他曲目
Haha…
硬件清单
| 器件 | 规格 | 数量 |
|---|---|---|
| Arduino 主控板 | ESP8266 等 | 1 |
| 无源蜂鸣器 | 3~5V 无源 | 1 |
| OLED 屏幕 | 0.96寸 I2C 128×64 SSD1306 | 1 |
| 杜邦线 | 根据开发板而定 | 若干 |
接线说明
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 |
软件依赖
本项目使用以下开源库:
| 库名 | 版本 | 说明 |
|---|---|---|
| Adafruit GFX Library | ≥1.10 | 图形库核心 |
| Adafruit SSD1306 | ≥2.5 | OLED 驱动库 |
| SPI | 内置 | SPI 通信库 |
| Wire | 内置 | I2C 通信库 |
库安装方法
在 VS Code 中的 PlatformIO 插件搜索并安装:
Adafruit GFX Libraryby AdafruitAdafruit SSD1306by Adafruit
使用说明
- 硬件连接:按照上述接线图完成电路连接
- 安装库文件:安装上述软件依赖
- 编译上传:将
main.cpp文件烧录到 Arduino 开发板 - 启动播放:打开串口监视器(波特率 9600),发送任意字符开始播放
项目结构
├── README.md # 项目说明文档└── src └── main.cpp # 主程序文件技术原理
1. PWM 调波发声原理
无源蜂鸣器需要外部电路提供交流脉冲信号才能发声。通过 Arduino 的 tone() 函数,我们可以:
- 生成指定频率的方波信号:不同频率对应不同音高
- PWM 占空比固定为 50%:确保信号稳定
- 音调与频率对应关系:
- 低音:262Hz (C4) ~ 494Hz (B4)
- 中音:523Hz (C5) ~ 988Hz (B5)
┌─────────────┐│ Arduino │ ──tone(440Hz)──▶ [蜂鸣器] ──▶ 发出 A4 音└─────────────┘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. OLED 显示原理
使用 Adafruit_SSD1306 库驱动 128×64 分辨率的 OLED 屏幕:
- 通信协议:I2C(只占用 2 个 GPIO 引脚)
- 显示方式:位图数组直接绘制
- 显存操作:双缓冲避免闪烁
OLED 引脚连接:┌─────────────┐│ Arduino ││ GPIO 5 ────▶ SCL (时钟线)│ GPIO 4 ────▶ SDA (数据线)└─────────────┘代码展示
前置警告
⚠️ 前方高能警告!请谨慎观看! ⚠️
警告以下代码仅用于整活、娱乐、以及证明我能让单片机唱歌,不代表任何正确的工程规范、算法思想、代码审美或嵌入式最佳实践。
代码里充斥着:
- 暴力
delay()阻塞式播放 - 写死到 Flash 里的乐谱数组
- 人工计算的跳段行数
- 毫无封装的大段逻辑
- 能跑就行的低技术力写法
- 以及巨大的可优化空间
总之:能响,但完全不优雅。
再次警告如果你是算法大佬、嵌入式大神、代码洁癖患者,现在退出还来得及。
完整源代码
//《神のまにまに》音乐测试
//库引用#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); //防卡死}音乐播放核心逻辑
为了提高可读性,这里做了部分删改。
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); } } // 循环控制逻辑... }}算法解读
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行时,进入结束句2. 时长计算公式
thisTime = SIXTEENTH_NOTE * musicSentenceTime[rows][columns] - 40;- 基准单位:十六分音符(111ms)
- 时长修正:乘以节拍数,再减去 40ms 作为音间间隔
- 为什么要减40ms:为下一个音符的
noTone()留出间隙
3. 音调编码映射
| 编码值 | 实际音高 | 计算方式 |
|---|---|---|
-1 ~ -7 | 低音 1~7 | lowTones[abs(编码)] |
1 ~ 7 | 中音 1~7 | midTones[编码] |
33 | 中音 2# | 特殊值,查表 mid2sTone |
0 | 休止符 | 无操作(需配合下一音判断) |
总结
项目亮点
其实是没有亮点,但是博主牵强附合(
- 资源占用少:仅使用 2 个 GPIO 引脚(I2C 通信)
- 代码结构清晰:数据与逻辑分离,便于维护和扩展
- 支持循环播放:通过断点机制实现复杂的歌曲结构
硬件清单
| 器件 | 数量 | 说明 |
|---|---|---|
| ESP8266 开发板 | 1 | 主控芯片 |
| 无源蜂鸣器 | 1 | 发声元件 |
| OLED 屏幕 (SSD1306) | 1 | 128×64 I2C 接口 |
| 杜邦线 | 若干 | 连接线路 |
可扩展方向
实际情况是博主技术力不够,实现不了
- 🔄 添加按钮实现”播放/暂停”功能
- 📊 增加音量调节
- 🎨 支持多首歌曲切换
- 📝 添加歌词同步显示功能
完整代码已上传至 GitHub,欢迎围观!
没事反正人少,丢人也丢不到哪去,哈哈
Waiting for api.github.com...
如果你想体验一下,不妨去下载代码。操作方法已经写入README.md文件里了。
如果有任何问题,欢迎在评论区留言交流~ 🎵
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
项目|基于单片机的《神的随波逐流》音乐播放器
https://luq-blog.xyz/posts/2026-05-28-beep-audio/ 部分信息可能已经过时
相关文章 智能推荐
1
DIY|电机/机械硬盘改音响
创意DIY 关于使用功放板让电机发声的原理及教程。
2
项目|Python文件夹自动整理脚本
理工研习 用 Python 写一个自动分类文件的脚本,告别杂乱的文件夹。
3
笔记|Markdown Tutorial
理工研习 Markdown教程。原作者:emn178。原文采用Unlicense协议。
4
笔记|数据分析学习笔记02|Jupyter在VSCode中的使用
理工研习 本节介绍如何在 VS Code 中安装和使用 Jupyter,包括扩展安装、创建文件和基本操作。
5
笔记|数据分析学习笔记01|开发环境的配置
理工研习 进行 Python 数据分析之前,需要先搭建好开发环境。本节将介绍 Anaconda 的安装与 Jupyter Notebook 的基本使用方法。









