MIDI协议与Arduino编程
好久没写Arduino了,忘的都差不多了
偏偏实验要用(
参考资料:
课程文档
——————————————————
MIDI协议
MIDI协议的基础传输单元为一个字节
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 
字节最高位为
1:表示该字节为状态字节字节最高位为
0:表示该字节为数据字节
状态字节
命令字段(第6、5、4位)
命令类型  | 字段  | 数据长度  | 第一个字节  | 第二个字节  | 
音符关闭  | 000  | 2  | 音符序号  | 力度  | 
音符启动  | 001  | 2  | 音符序号  | 力度  | 
音符触键压力  | 010  | 2  | 音符序号  | 压力  | 
控制变更  | 011  | 2  | 地址  | 值  | 
程序变更  | 100  | 1  | 程序序号  | 无  | 
通道触键压力  | 101  | 1  | 压力  | 无  | 
弯音  | 110  | 2  | 低7位  | 高7位  | 
系统设置  | 111  | 可变  | 可变  | 可变  | 
通道字段(第0、1、2、3位)
表示通道序号
MIDI通道可理解为一条独立的指令传输轨道或一个虚拟的乐器席位,目的是实现“一发多收”
数据字节
第0~7位表示0~127
命令举例
| 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 
| 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 
第一个字节是一个状态字节,选择了通道0,命令类型为音符启动,后需跟2个数据字节,
第二个字节是一个数据字节,音符编号60(0111100)
第三个字节是一个数据字节,力度64
音符编号0~127分别代表了十二平均律中从C1到G9的127个音符
如果多次传输中状态信息相同,则可以省略后续的状态信息,即一个状态字节+2n个数据字节。
Arduino编程

从这张图可以看出,Arduino的作用是将MIDI传输的数据转换成GATE信号和CV信号
与MIDI通信
使用ESP32自带的USB虚拟串口HWCDC来接收PC上MIDI转串口软件(HairlessMIDI)产生的MIDI串行数据,并在Arduino中下载MIDI Library库,用来解析MIDI协议
MIDI初始化代码
MIDI_CREATE_INSTANCE(HWCDC,Serial,MIDI);
void setup() {// put your setup code here, to run once:MIDI.begin();    //启动并初始化之前创建的MIDI实例Serial.begin(115200);    //启动ESP32的USB串口通信,并设置波特率为115200
}
MIDI_CREATE_INSTANCE(HWCDC, Serial, MIDI);
MIDI_CREATE_INSTANCE:这是MIDI库提供的一个宏,用于快速创建MIDI实例。
HWCDC:这是第一个参数,指定了要使用的硬件接口类型。
HWCDC代表 Hardware CDC (Communications Device Class)。对于ESP32-S2/S3/C3等,这是指它们原生的USB硬件端口。这明确表示你将使用开发板的 USB接口来进行MIDI通信,而不是传统的串口(UART)引脚。
Serial:这是第二个参数,指定了具体的串行对象。
在支持USB的ESP32上,
Serial对象默认就指向这个USB CDC端口。当你通过USB线将开发板连接到电脑时,电脑识别出的串口就是这个Serial。
MIDI:这是第三个参数,是你为这个实例起的名字。
这里你创建了一个名为
MIDI的MIDI实例。在后续代码中,你将通过MIDI.begin(),MIDI.read()等来操作它。
几个会使用到的MIDI库函数
- MIDI.read():读取新接收到的MIDI数据包,返回True / False
 - MIDI.getType():返回MIDI包的指令字节,类型为midi::MidiType
 - MIDI.getData1():返回MIDI包的第一个数据字节,类型为midi::DataByte
 - MIDI.getData2():返回MIDI包的第二个数据字节,类型为midi::DataByte
 
其中,MIDI指令类型为midi::MidiType,包括midi::NoteOn和midi::NoteOff等;MIDI数据类型为midi::DataByte,其本质上为字节类型。
输出GATE信号
使用GPIO实现
pinMode(pin_number, OUTPUT); // 将引脚设置为输出模式
digitalWrite(pin_number, HIGH); // 设置其输出高电平输出CV信号
利用MCP4728芯片产生
在Arduino下载Adafruit_MCP4728的库,配合Wire库的i2c外设实现功能

检查开发板设置

MCP4728初始化代码
#include <Adafruit_MCP4728.h>   
Adafruit_MCP4728 mcp;    // 创建一个MCP4728对象,代表MCP4728芯片
TwoWire i2c_dev = TwoWire(0);    // 创建一个I2C总线对象,并指定ESP32的I2C端口0
void setup() {// put your setup code here, to run once:i2c_dev.begin(20,21);    // 初始化之前创建的I2C总线,并明确指定SDA引脚(GPIO20)和SCL引脚(GPIO21)if(!mcp.begin(0x60,&i2c_dev)) {    //初始化MCP4728模块Serial.println("Error!!!");}
}初始化结束后,可使用以下函数设置通道A的电压
MCP4728为12-bit DAC,参考电压为5V
mcp.setChannelValue(MCP4728_CHANNEL_A, 1025)