mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7mobile wallpaper 8
1372 字
4 分钟
项目|基于单片机的《神的随波逐流》音乐播放器
2026-05-28

前言#

大家好,今天给大家带来一个有趣的 Arduino 小项目——使用无源蜂鸣器和 OLED 屏幕,完整播放《神のまにまに》(神的随波逐流)这首经典歌曲。

项目核心功能:

  • 🎵 通过 PWM 调波技术驱动无源蜂鸣器播放音乐
  • 🖥️ OLED 屏幕同步显示歌曲封面
  • 📝 完整乐谱数据,支持歌曲循环播放

演示视频


简介#

本项目使用 Arduino 开发板驱动无源蜂鸣器播放《神のまにまに》(神的随波逐流),同时在 OLED 屏幕上显示歌曲封面图片。通过 PWM 调波技术实现音调控制,支持完整的歌曲循环播放结构。

功能特性#

  • 🎵 PWM 调波发声,音准准确
  • 🖥️ OLED 屏幕显示位图封面
  • 🔁 支持复杂的循环跳转结构
  • 📦 代码结构清晰,数据与逻辑分离
  • 🔧 易于修改,可扩展其他曲目

Haha…

硬件清单#

器件规格数量
Arduino 主控板ESP8266 等1
无源蜂鸣器3~5V 无源1
OLED 屏幕0.96寸 I2C 128×64 SSD13061
杜邦线根据开发板而定若干

接线说明#

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

软件依赖#

本项目使用以下开源库:

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

库安装方法#

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

  • Adafruit GFX Library by Adafruit
  • Adafruit SSD1306 by Adafruit

使用说明#

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

总结#

项目亮点#

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

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

硬件清单#

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

可扩展方向#

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

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

完整代码已上传至 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

部分信息可能已经过时

目录