
做嵌入式这些年我越来越发现一个有意思的现象当你想给产品加个背景音乐、提示音或者游戏音效时很多人的第一反应是去放音频文件比如WAV、MP3而不是考虑MIDI。可一旦你真正接触过像SAM2695这样的单芯片MIDI合成方案很可能会改变固有印象。这颗芯片可以把一个MIDI文件“翻译”成真实的乐器声音而且不需要MCU承担复杂的音频解码任务也不需要外挂Flash存大尺寸音频文件只需要几条串口指令就能让它自己演奏。这篇文章就围绕SAM2695展开聊聊这种低成本单芯片MIDI合成器能做什么、适合谁用、硬件和软件怎么搭以及在Linux下编辑MIDI、获取MIDI乐谱资源、甚至把MIDI转成简谱的完整思路。如果你正在做单片机项目、树莓派DIY乐器、桌面小音响或者智能玩具那这篇文章可能会帮你省下不少弯路。1. 把MIDI变成声音这枚芯片到底干了什么1.1 MIDI不是音频而是一张“演奏乐谱”很多人第一次接触MIDI时都会困惑为什么MIDI文件这么小几十KB甚至几KB的音乐听起来却有完整旋律原因在于MIDI文件里存的根本就不是“声音”而是“演奏指令”。它记录的是“什么时间、用哪个通道、按下哪个音、按多大力、松开”类似一张电子乐谱。真正让这些音符变成声音的是合成器。合成器内部有一个音色库波表里面预先存放了各种乐器的采样或者数学模型。当它收到Note On指令时就去音色库里找对应编号的音色按照力度、音高等参数实时合成出音频信号。SAM2695就是这样一颗把“MIDI指令”变成“模拟音频输出”的专用芯片内部集成了General MIDIGM音色库、DSP音效处理、混音器和数模转换电路。MCU只需要把MIDI数据字节发给它剩下的发声工作全部由芯片完成。1.2 单芯片的意义把“发声”和“控制”彻底分开在常见的嵌入式音频方案里很多开发者习惯用MCU去解码音频文件。比如STM32播放WAV需要读文件、解压、填DMA、配DAC或I2S一旦音乐稍长Flash和RAM就捉襟见肘。用MP3也一样需要占用大量算力去解码还要考虑解码芯片或软件库的授权成本。SAM2695的思路完全不同。它把音色库、DSP、混合合成、数模转换全部封装在一颗芯片里MCU只干一件事通过UART或SPI发送MIDI事件字节。这就像你请了一个专业乐手他手里有整套乐器你只需要告诉他“现在弹中央C、用钢琴音色、力度强一点”剩下的演奏他全包。对于产品开发来说这意味着音乐部分几乎不消耗主控的宝贵资源程序里没有复杂的解码库也没有大片音频数据占存储音乐素材可以是一首只有十几KB的MIDI曲目。1.3 与同类方案的横向对比我刚接触这类芯片时也纠结过到底是用VS1053、SAM2695还是直接用树莓派跑软件合成器。这类选择没有绝对好坏重点看项目约束。方案优点缺点适合场景SAM2695 单芯片MIDI合成成本低、电路简单、音色库内建、功耗低音质约等于入门级电子琴有相对软波表略显合成味提示音、玩具、音乐盒、低成本产品VS1053 解码芯片支持MIDI、MP3、WAV等功能多片内Flash空余音色不如专用MIDI芯片初始化配置略复杂需要同时兼顾多种音频格式的设备树莓派Fluidsynth音色库可换音质上限高扩展性强成本高、功耗大、启动慢、不适合超低功耗产品独立音乐播放器、智能设备原型通用MCUDAC播放PCM音质完全可控占用Flash/RAM大曲目稍长就放不下短音效、语音提示、固定提示音SAM2695的优势很明确中间态。它比纯软件方案便宜、省事比纯音频文件方案体积小、存储压力低。如果产品只是需要播放几首背景音乐、做成音效反馈或者做一个电子琴/音乐盒原型这颗芯片是很有性价比的选择。2. 核心细节解析与实操要点2.1 硬件上的几个关键引脚和外围电路SAM2695的硬件连接并不复杂但有几个细节决定了能不能一次成功。首先看电源。它通常工作在3.3V环境所以如果主控是5V系统一定要注意电平匹配。电源引脚旁边需要加去耦电容而且越靠近芯片越好我习惯在VCC和GND之间放一个10μF钽电容再加一个0.1μF陶瓷电容的小并联组合能有效滤掉高频噪声。还有音频输出部分容易被忽视合成器芯片输出的是模拟信号如果直接接耳机引脚声音可能偏小或者有直流分量。稳妥的做法是加一个串联电容做交流耦合再根据负载决定是否需要放大电路。然后是时钟问题。我踩过一次大坑焊好板子后发现芯片完全没有声音折腾了大半天才发现是主时钟没有给上。SAM2695这类合成器芯片内部有数字信号处理模块必须有时钟才能工作但不同封装和配置可能要求外部晶振或者外部MCLK信号。具体用哪种方式、频率多少一定要以对应型号的数据手册为准。很多参考设计会标注“12.288MHz晶振”或者“24.576MHz晶振”这种频率选值跟音频采样率相关不能随手换一个16MHz晶振。第三个关键是控制接口。常见的有UART、SPI等我建议新手从UART入手因为串口调试很直观接两根线就能跑起来。SPI适合在需要高速、密集MIDI事件流时使用但调试起来没有UART那么方便。2.2 控制接口UART与SPI怎么选UART方式下SAM2695默认往往以标准MIDI波特率31250bps接收数据但有些板上默认可能是38400或者57600这个一定要看手册确认。如果你用的单片机串口波特率跟它不一致发过去的全是乱码芯片自然不会发声。我用UART时一般会先用一根USB转TTL模块单独连芯片电脑上用串口助手发送几个字节测试。比如发送“0x90 0x3C 0x64”如果能听到一个短促的钢琴音说明通信链路没问题。这比一上来就接MCU程序调试要快得多。SPI方式更适合大量MIDI事件同时涌来的场景比如一首复杂的管弦乐MIDI同一时刻可能有好几个通道的音符事件需要发送。SPI时钟速率可以做到几MHz甚至更高UART则受限于波特率。不过SPI的时序、片选、寄存器读写配置比UART稍微复杂一些我个人的建议是做原型和简单播放器用UART做正式产品或者播放复杂曲目再考虑SPI。2.3 音色库与GM规范为什么同一个MIDI文件播放效果千差万别“同一个MIDI文件为什么在这个设备上好听在那个设备上就难听”这个问题经常被问到。答案基本都出在GM音色库兼容性上。GMGeneral MIDI是一种标准定义了128种乐器音色编号比如0号钢琴、40号小提琴、56号小号等。MIDI文件里通过Program Change消息来切换音色。SAM2695内置的是GM兼容音色库也就是说理论上你拿一个标准GM格式的MIDI文件喂给它应该能得到基本正常的乐器编配。但“兼容”不等于“音色完全一致”。不同芯片的采样源、音色引擎、效果器算法都不一样即使音色编号相同实际听感也可能有差异。另一个常见问题是有些MIDI文件在最开始写了Bank Select消息用于切换音色库如果你的合成器不支持或者代码里没考虑这个乐器就可能“串台”钢琴音色变成吉他弦乐变成铜管。所以如果产品需要播放固定几首曲目我强烈建议拿到MIDI文件后先在目标硬件上听一遍把有问题的音色手动修正比如把Bank Select消息去掉或者用Program Change换成最接近的音色编号。工作中我会写一个小工具去扫描MIDI文件里的音色信息在硬件播放前先做“清洁”。这个环节看起来不起眼但对最终听感的提升非常明显。3. 实操过程与核心环节实现3.1 最小系统搭建MCU SAM2695我们先搭一个最小系统。假设主控是STM32F103或者Arduino/UnoSAM2695模块是独立小板。连接方式参考这样一个表格SAM2695引脚接主控/外部说明VCC3.3V注意与主控电平域一致GND公共地所有GND必须连在一起TX/RX或SPI主控UART TX / RX或SPI这里是数据通道不是音频输出AOUT/耳机输出音频输出端接有源音箱、耳机或后级功放MCLK/XTAL外部晶振或主控时钟必须按手册配置否则无声音复位GPIO或RC电路上电复位确保稳定接线完成后先不接任何程序直接用万用表确认供电电压正常再用示波器或逻辑分析仪看看晶振有没有起振。很多“没声音”的问题在这一步就能现出原形。3.2 第一步裸机发送一条Note On通信基础打好后写一个最简单的程序让SAM2695发出一个中央C音符。在C语言环境下UART初始化后发送三字节uint8_t note_on[] {0x90, 0x3C, 0x64}; // 通道0 Note On中央C力度100 uint8_t note_off[] {0x80, 0x3C, 0x40}; // 通道0 Note Off中央C力度64 HAL_UART_Transmit(huart1, note_on, 3, 100); HAL_Delay(1000); HAL_UART_Transmit(huart1, note_off, 3, 100);0x90表示在0号通道上按下音符0x3C是中央C的MIDI音高编号0x64是力度。1秒后发送0x80也就是该通道的Note Off音符才停止。如果你只发Note On不发Note Off声音会一直持续下去这在合成器里很常见也是新手容易忽略的点。3.3 第二步把整个MIDI文件喂给芯片能发单独音符后自然想播放完整曲目。MIDI文件本质是一个包含了大量指令的二进制文件里面除了音符事件还有速度、拍号、音色、控制变化等元信息。播放时我们需要逐个读取这些事件然后把MIDI事件字节通过串口发给SAM2695。在树莓派或Linux主机上用Python处理这件事很高效。mido库可以直接遍历MIDI文件pyserial负责串口发送import mido import serial import time port serial.Serial(/dev/ttyUSB0, 31250, timeout0.1) mid mido.MidiFile(song.mid) for msg in mid.play(): if msg.is_meta: continue data msg.bytes() port.write(data)mid.play()内部已经按照MIDI文件里的delta time做了时间调度所以播放节奏是对的。但这里有个隐藏问题如果MIDI文件里某个时间点有多个音符事件同时发送串口发送时间短还好万一串口缓冲被填满后续数据就可能丢失。简单经验是如果曲目不复杂直接发送即可如果曲目节奏密集可以在每批数据后加一个极短的延时比如sleep(0.001)给芯片留出处理时间更稳的方案是用流控或者按事件时间片批量发送不要让缓冲区瞬间灌满。如果要在单片机上实现思路也类似解析MIDI文件提取出所有MIDI事件然后根据delta time延时并发送。因为单片机RAM有限通常不会一次加载整个文件而是边读边发或者把MIDI文件转成自定义的二进制事件表存放在Flash里。3.4 资源获取从MIDI文件到简谱的完整链条有了硬件和播放代码马上会面临一个实际需求到哪里去找MIDI音乐素材一个好的MIDI文件是好听效果的前提。这里我分享几条比较可靠且需要注意版权问题的途径。古典音乐类IMSLP国际乐谱图书馆项目、Musopen等平台提供大量公有领域乐谱和音频资源其中也有MIDI文件可以放心学习使用。社区分享类部分音乐爱好者和作曲者在自己的主页或社区分享原创或授权的MIDI文件可以下载练手但商用前一定要确认授权范围。自制MIDI如果手上只有旋律也可以用MuseScore这类软件自己写谱并导出MIDI文件完全自主可控。在Linux下编辑MIDI我常用MuseScore它本质是一个打谱软件可以导入MIDI、查看五线谱、修改音符后再导出MIDI。Rosegarden和Qtractor也是不错的选择它们更偏向MIDI音序器。命令行下还有midicsv这类工具可以把MIDI转成CSV文本方便写脚本批量处理。至于“MIDI转简谱”这是一个比较接地气的需求。如果你想快速看一下某首MIDI的旋律走向可以用Python简单解析。大致思路是读取MIDI文件找到含音符最多的旋律轨道按时间顺序输出音高和时值再把音高映射成简谱的1-7用点表示高八度、逗号表示低八度。下面是一个演示轮廓import mido def note_to_jianpu(note): scale [0, 1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0] step scale[(note - 60) % 12] octave_shift (note - 60) // 12 if step 0: return None if octave_shift 0: return * octave_shift str(step 1) else: return str(step 1) , * (-octave_shift) mid mido.MidiFile(song.mid) for track in mid.tracks: if any(msg.type note_on and msg.velocity 0 for msg in track): for msg in track: if msg.type note_on and msg.velocity 0: jp note_to_jianpu(msg.note) if jp: print(jp, end ) break这个脚本没有处理时值和拍号属于“能看个大概”的程度。更完整的工具可以配合pretty_midi库提取音符起止时间再结合节拍信息生成PDF简谱。不过从工程角度如果只是给产品调试用能输出一串简谱就够用了。4. 常见问题与排查技巧实录做这类单芯片音频项目最让人头疼的就是“明明照着手册做了怎么还没声”。我把自己和身边朋友踩过的坑整理成一个速查表希望能帮你少买几次教训。现象可能原因排查与解决上电后完全无声主时钟未起振、电源没供上、音频输出没接有效负载先查供电再用示波器看晶振波形最后用耳机测试输出串口发送数据但芯片无反应波特率不匹配、TX/RX接反、电平不匹配用USB转TTL单独连芯片用串口助手发0x90 0x3C 0x64测试有声音但音色全错MIDI文件里带了Bank Select或者Program Change音色编号不兼容用支持MIDI查看的工具打开文件手动去掉Bank Select或修正音色编号声音持续不停Note Off没发或者力度为0的Note On被当成Note On检查程序是否在所有音符结束位置发送了Note Off音质杂音明显电源纹波大地线处理不当在电源上加强去耦电容音频输出线尽量短模拟地数字地单点连接播放到一半卡住或丢音串口缓冲溢出或者时间调度不精确降低发送速率增加等待时间或改用SPI接口只听到单声道或者音量偏小音频输出可能是差分单端需要正确接地和耦合检查数据手册中的输出引脚定义用交流耦合电容连接负载4.1 音符一直长鸣最容易被忽视的Note Off我见过不少第一次用合成器芯片的人最大的疑惑就是“为什么音符弹了一下就一直响”。因为它只发了Note On没有发Note Off。在MIDI世界里按下琴键只是指令松开琴键是另一条指令。你即使把系统关了重开音符也未必停得下来因为芯片内部的合成通道还处于“按下”状态。解决方式很简单在任何音符结束后必须在同一通道发送对应音高的Note Off0x80或力度为0的Note On0x90 0x00两种写法等价。4.2 电平不一样5V单片机的尴尬很多开发板供电是5V而SAM2695通常跑在3.3V。如果直接把5V的串口TX连到芯片的RX引脚长时间工作可能损坏芯片。最稳妥的方式是加一个电平转换电路或者用3.3V的MCU开发板比如STM32的3.3V供电引脚、树莓派GPIO、ESP32的3.3V。我在调试时偷懒过直接用两个电阻分压把5V降到3.3V短时间测功能没问题但批量产品绝对不建议这么干。4.3 发送太快也会出问题MIDI事件本身是流式的芯片需要一字节一字节地解析。如果一上来就以几Mbps的速度灌几百个事件芯片内部处理不过来就会出现“丢指令、音色错、音符丢失”之类的诡异现象。解决办法是在大批量事件中主动加一点间隔或者换用SPI并在硬件上做FIFO缓冲。播放简单曲目时UART加微延时已经完全够用。4.4 电源噪声真的是音质杀手SAM2695内置了DAC所以对电源噪声比较敏感。如果你用开关电源供电音频输出里很可能有咝咝声或者嗡嗡声。我通常在电源树里增加一级LDO给音频芯片单独供电音频输出的地线也单独走避免和数字电路的地线混在一起。对于便携设备用锂电池供电或者加一个电荷泵负压方案也会改善听感。5. 应用场景与经验心得5.1 那些适合SAM2695的场景第一个典型场景是智能玩具。玩具不需要高保真音质但需要丰富的声音反馈、音乐旋律、互动音效。传统做法是把多段WAV文件烧进Flash声音素材一大成本和存储都上去了。用SAM2695只需把曲子写成MIDIMCU通过串口控制播放素材体积可以压缩到几KB效果却像“真的会演奏乐器”一样。第二个场景是HMI人机界面和工业设备提示音。很多设备需要开机音乐、操作反馈音、异常报警音。用合成器芯片的好处是声音不像普通蜂鸣器那样刺耳还能做到多段旋律。我之前做过一个医用小设备的项目就用SAM2695播放几段提示音音量和旋律可以随时用MIDI指令切换非常灵活。第三个场景是复古游戏和迷你音乐播放器。很多开发者在树莓派或者ESP32上做复古游戏机想播放游戏背景音乐。经典游戏机的音乐很多以MIDI或音乐事件形式存在用SAM2695播放比直接用PCM模拟更有“那个味道”。配上几个按键或者编码器控制音量、切歌一个桌面复古音乐盒就出来了。另外还有一类场景是“离线简谱播放器”。你可以把MIDI文件转成简谱显示在OLED屏上同时让SAM2695播放旋律非常适合作为音乐教学、练琴辅助工具。配合Linux下的MIDI编辑工具可以把网上找来的MIDI整理成自己想要的曲目再烧进设备里播放。5.2 我对这类单芯片方案的体会我做过的项目里真正把音频方案做复杂的往往不是“发声”本身而是“素材管理”和“体验打磨”。一颗SAM2695芯片虽然看起来很简单但它带来的最大价值是把发声这件事从主控里抽离出来让开发者可以把精力放在交互、显示、功能逻辑上。如果产品只需要“放个音乐/音效”没必要非用大容量Flash去存音频文件也没必要在MCU里跑庞大的解码库一颗几块钱的MIDI合成芯片就能干得漂亮。关于音质我也想说句实在话不要拿它和手机里的无损音乐比SAM2695的优势从来不是音质而是“成本可控、体积小、开发快、素材轻”。在玩具、提示音、互动音乐这些场景里“能听、够用、还不占存储”就已经是满分了。而且它的播放效果可以用MIDI文件本身来调整编配越简单干净听感反而越好。如果让我给新手一句建议我会说先把USB转TTL和芯片通过串口连通让芯片响一声再往工程和封装方向做。这个“响一声”的成就感是后面所有调试工作的起点。等你真的把一首完整的MIDI曲目在芯片上流畅播放出来你就会理解为什么这种“上古的MIDI技术”在今天的嵌入式产品里依然有它不可替代的位置。