STM32和VS1053制作电子琴

2026-11-101

技术图片1

做一把小型电子琴,最先遇到的往往不是按键数量,而是声音从哪里来。单片机自己合成波形,需要同时处理音色、复音和连续音频输出;把这些工作交给专用芯片,主控就能把精力放在按键、界面和演奏时序上。

这把十五键音乐键盘采用 STM32F103C8T6 最小系统板和 VS1053 模块,配合两只 EC11 编码器与 OLED。机械按键决定音符,旋钮调整音色、调性和八度,VS1053 负责合成声音。录制功能保存的是音符事件和时间间隔,音频仍由合成器在回放时重新产生。

发出声音之前先把音符送对地方

MIDI 描述的是演奏动作。按下一个键,需要发送某通道上的 Note On,并给出音符编号和力度;松开时再发送对应的 Note Off。它没有直接保存扬声器需要的连续采样值,因此事件数据很少,音色和复音的负担主要落在合成器一侧。

VS1053 在这里作为实时 MIDI 合成器使用。初始化需要完成软件复位、时钟与模式设置,再加载实时 MIDI 补丁,之后才能按对应驱动的数据封装发送演奏指令。仅把芯片上电,或者把它配置成普通音频文件解码模式,都不能代替这段初始化。

主控侧的循环把编码器处理、显示更新、矩阵扫描和演奏衔接起来。显示刷新应服从按键扫描的响应要求,不能让长时间的屏幕传输把松键事件拖住。合成器输出经过模块上的音频接口送到外接设备,整机本身没有配套扬声器功放,使用时应连接合适的有源音箱。3.5 mm 插孔的存在并不意味着可以直接驱动任意无源扬声器。

技术图片2

硬件采用模块拼装,接口定义也应以实际模块为准。原理图中 H1 引出了 PA8、PB12~PB15、PA9 和 PA10,但这些网络名不能单独说明每根线在替代模块上的功能。更换 VS1053 板卡时,需要逐项对照时钟、数据、片选、复位与就绪信号,连同模块供电和逻辑电平一起核查。

矩阵找出按键 状态变化决定何时发声

十五个按键组成三行五列矩阵,只需八条扫描线。电路中 PA0~PA2 接三条行线,PA3~PA7 接五条列线,每个开关串联一只 1N4148W。扫描时依次选通行线,再读取列线,才能把电平组合还原为某个按键。二极管限制电流绕行的方向,配合正确的扫描电平和未选通行状态,抑制多键按下时的鬼键路径。

技术图片3

电平扫描之后还有两件事要处理。机械触点的抖动需要消除,而持续按住的键不能在每轮循环反复触发 Note On。前者关系到输入是否稳定,后者关系到状态是否发生变化,二者不能互相替代。

弹奏逻辑保存了上一轮的 lastkeyboard。下面节选的是按下分支,进入条件同时检查当前和历史状态。

if (keyboard[i][j] == Pressed && lastkeyboard[i][j] != Pressed)
{
    note = (12 * param->Octave_Index + param->Major_Index)
           + Major_scale[(i * 5) + j];
    VS1053_MIDI_NoteOn(hvs, 0, note, 100);
}

Major_scale 把按键位置映射到自然大调的音阶偏移,八度项每次移动十二个半音,调性项再平移音高。十五个键按自然大调排列,不等于十五个连续半音。代码把力度固定为 100,普通机械开关也没有提供按压力度信息,因此这里的力度是发送参数,并非触键测量结果。

松开分支发送 Note Off,随后用当前矩阵覆盖历史矩阵。有一个容易忽略的边界,按下时发出的音符编号必须和松开时关闭的编号一致。如果按住期间旋转编码器改变八度,再用新参数计算 Note Off,就可能关闭另一个音,留下原音持续发声。改进时应为每个活动按键保存实际发出的通道和音符,释放时按这份记录关音,同时约束最终音符编号在 MIDI 的 0~127 范围内。

录制的是等待多久 再做哪个动作

保存波形会很快耗尽 F103 的片内空间。这里把每次动作压缩为一段时间间隔和一个音符字节,前面另存一份通道参数。记录时间的单位定义为 20 ms,按下或松开时取走 record_tick 并清零,得到距上一个事件经过的 tick 数。

音符字节的低七位保存编号,最高位区分打开或关闭。时间字段也以七位为一组,单字节最高位为零;需要两个字节时,第一个字节最高位置一,后面接低七位。时间与音符都使用最高位,但含义不同,解析器必须知道自己当前正处于哪个字段。

if (tick > 0x7F)
{
    buffer[0] = ((tick >> 7) & 0x7F) | 0x80;
    buffer[1] = (tick & 0x7F);
    ChannelX_MIDI_RecordFile.Data[write_idx++] = buffer[0];
    ChannelX_MIDI_RecordFile.Data[write_idx++] = buffer[1];
    ChannelX_MIDI_RecordFile.Data[write_idx++] = (note & 0x7F);
}

这是录制按下事件时的长间隔分支,松开事件将音符字节最高位置一。比如 0x83 0x05 解出 389 tick,对应 7.78 s;后接 0x40 就打开编号 64 的音符,后接 0xC0 则关闭它。这样的数据不能直接当作标准 MIDI 文件播放,需要由本机解析器恢复成 MIDI 指令。

技术图片4

每次事件占两到三个字节,6000 字节数据区的容量取决于事件密度,不能固定换算成几分钟。一次完整按键通常包含按下和松开两个事件,和弦、快速重复按键都会增加消耗。两个时间字节最多表达 16383 tick,约 327.66 s,超过这个间隔需要扩展编码或拆分等待。

写入前还应预留当前事件和结束标记的容量。上面的分支直接递增 write_idx,仅凭这段代码不能确认整个程序已经处理数据区溢出。计数读取和清零也要与 中断 协调,避免中断恰好插入两次操作之间,把一个 tick 丢掉。上述边界属于后续完善点,不能用 volatile 代替原子性处理。

Flash 录音区必须和程序分开

录音暂存在 RAM,保存时再写入内部 Flash。四组存储起点采用以下定义,地址按 6 KiB 间距排列。

#define Recordable_bytes 6000
#define PAGE36  0x08009000
#define PAGE42  0x0800A800
#define PAGE48  0x0800C000
#define PAGE54  0x0800D800

首个起点距离 Flash 基址 36 KiB,意味着应用程序必须在此之前结束。这个边界应由 链接 脚本或构建检查守住,而不只是下载前看一次代码体积。链接器若仍把整个 Flash 当成程序空间,后续增加字库、界面或驱动,都可能让代码与录音区重叠。

技术图片5

6000 字节只计算事件 数组 ,实际保存还包括 sizeof(MIDI_Parameters)。单组槽位是否够用,应把参数头、有效事件、结束标记及必要对齐一起核算。不能因为数组小于 6144 字节,就认定任何参数结构都一定放得下。

保存函数按页推进 Flash 地址,回放初始化则让参数指针指向槽位起点,让事件指针跳过参数结构。擦页和编程的细节封装在 Flash_WritePage 中,适配时需要确认其擦除范围、写入粒度和错误返回。更稳妥的持久化格式还应包含长度、格式版本、校验和有效标记,避免断电留下半份内容却被回放器当成完整录音。适配时需要逐项实现并验证这些保护,不能只依赖一组固定地址。

回放时序和装配空间都要留余量

回放先还原通道参数,再由定时器驱动每个通道的倒计数。以第一通道为例,计数减到零时才解析下一步,解析函数的返回值成为下一段等待时间。

if (CH1_Chosen){
    if (--ch1_tick == 0)
    {
        ch1_tick = Replay_Step(&Channel1_MIDI_ReplayFile);
    }
}

解析器交替读取时间与音符,并在相邻间隔为零时继续处理事件,避免同一时刻的和弦音符被强行分散到多个 tick。与此对应,整个等待时间也存在 20 ms 量化,不能将它理解为任意精度的演奏记录。初始倒计数被设置为 1,若把它误改成 0,无符号预减就会下溢,零间隔必须交给解析器内部的连续处理逻辑。

调试应沿着输入、事件、存储和回放逐层验证,而不是只听到一次声音就认定录音链路完整。

检查动作 需要确认的结果
长按后松开一个键 每次状态变化只产生一次事件,松开后原音停止
按住时切换八度或调性 释放时仍关闭按下时发出的音符
同时按下多个键 矩阵无误键,同 tick 事件不会被逐个延后一拍
写满录音区后重启 有明确的容量与有效性判断,不越界读取 Flash

供电上,充放电模块向最小系统板和 VS1053 模块提供 5 V,电路另有 VCC 网络给低压侧使用,不能把两条电源混为一谈。EC11 两相各有 10 kΩ 上拉到 5 V 和 100 nF 对地电容,形成约 1 ms 的 RC 时间尺度;它能缓和边沿,也要求软件正确识别相位变化。迁移到别的主控时,应重新核对这些输入引脚的耐压与工作模式。

机械装配也有实际约束,铜柱与螺钉位置会和部分键帽发生干涉,键帽、OLED 排针高度和外壳间隙应放在一起检查。这套结构适合分析与迭代,不宜未经校验就照搬外壳尺寸。只有扫描频率、显示刷新和模块传输都留出余量,界面操作才不会打断演奏。

这把小键盘把 声音合成 交给 VS1053,把输入和时序留给 STM32,片内 Flash 则保存能重新演奏的事件。沿着一次按下、一次松开和一段等待去检查,既能看清矩阵电路怎样参与发声,也能找出转调挂音、事件越界和录音区覆盖这些真正影响使用的问题。把每个事件的身份、时间和存储边界守住,十五个按键才能可靠地连成一段旋律。

创作许可协议

本项目采用 CC BY(署名) 进行许可。

评论(0)
- 没有更多了 -