mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7mobile wallpaper 8mobile wallpaper 9mobile wallpaper 10mobile wallpaper 11mobile wallpaper 12mobile wallpaper 13mobile wallpaper 14mobile wallpaper 15mobile wallpaper 16mobile wallpaper 17mobile wallpaper 18mobile wallpaper 19mobile wallpaper 20mobile wallpaper 21mobile wallpaper 22mobile wallpaper 23
1390 字
4 分钟
项目|基于单片机的《神的随波逐流》音乐播放器
2026-05-28
2026-06-21

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 SSD13061
杜邦线根据开发板而定若干

2.3 接线说明#

Arduino 无源蜂鸣器
GPIO 1 ──────────── Signal
Arduino OLED屏幕
GPIO 4 ──────────── SDA
GPIO 5 ──────────── SCL
3V/5V ──────────── VCC
GND ──────────── GND

OLED 引脚Arduino 引脚
SDAA4 (SDA)
SCLA5 (SCL)
VCC3V/5V
GNDGND

2.4 软件依赖#

本项目使用以下开源库:

库名版本说明
Adafruit GFX Library≥1.10图形库核心
Adafruit SSD1306≥2.5OLED 驱动库
SPI内置SPI 通信库
Wire内置I2C 通信库

2.4.1 库安装方法#

在 VS Code 中的 PlatformIO 插件搜索并安装:

  • Adafruit GFX Library by Adafruit
  • Adafruit SSD1306 by Adafruit

2.5 使用说明#

  1. 硬件连接:按照上述接线图完成电路连接
  2. 安装库文件:安装上述软件依赖
  3. 编译上传:将 main.cpp 文件烧录到 Arduino 开发板
  4. 启动播放:打开串口监视器(波特率 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# 为 33
int 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~7lowTones[abs(编码)]
1 ~ 7中音 1~7midTones[编码]
33中音 2#特殊值,查表 mid2sTone
0休止符无操作(需配合下一音判断)

6. 总结#

6.1 项目亮点#

其实是没有亮点,但是博主牵强附合(

  1. 资源占用少:仅使用 2 个 GPIO 引脚(I2C 通信)
  2. 代码结构清晰:数据与逻辑分离,便于维护和扩展
  3. 支持循环播放:通过断点机制实现复杂的歌曲结构

6.2 硬件清单#

器件数量说明
ESP8266 开发板1主控芯片
无源蜂鸣器1发声元件
OLED 屏幕 (SSD1306)1128×64 I2C 接口
杜邦线若干连接线路

6.3 可扩展方向#

实际情况是博主技术力不够,实现不了

  • 🔄 添加按钮实现”播放/暂停”功能
  • 📊 增加音量调节
  • 🎨 支持多首歌曲切换
  • 📝 添加歌词同步显示功能

完整代码已上传至 GitHub,欢迎围观!

没事反正人少,丢人也丢不到哪去,哈哈

Luquiescene
/
kami-no-manimani-music-player-with-Arduino
Waiting for api.github.com...
00K
0K
0K
Waiting...

如果你想体验一下,不妨去下载代码。操作方法已经写入README.md文件里了。

如果有任何问题,欢迎在评论区留言交流~ 🎵

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

项目|基于单片机的《神的随波逐流》音乐播放器
https://luq-blog.xyz/posts/2026-05-28-beep-audio/
作者
Luquiescene
发布于
2026-05-28
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录