基于STM32的USB MIDI协议栈设计

Q:为什么要使用USB MIDI?

A:传统MIDI接口使用异步串行方式通信,波特率31.25kbps,而一般一条指令的长度为3字节,所以换算出来最快1ms传输一条指令,而USB MIDI(工作在Full Speed模式)在每1ms的SOF帧后可搭载n个数据包,所以带来的演奏延迟是远小于传统接口。

Q:USB MIDI与传统MIDI协议区别大么?

A:USB MIDI采用4字节对齐的事件帧作为最小传输单元,第1字节为Cable Num+CIN,第2~4字节为原MIDI协议内容,对于长度大于3字节的MIDI数据被拆分为若干个事件,而小于3字节的后面以0补齐。


附件附USB MIDI 1.0协议原本和源码(MDK v5.14工程),源码在STM32F103C8T6核心板上调试通过,模拟了一个USB转传统MIDI接口的适配器,使用USART1作为传统MIDI接口。


attachment icon midi10.pdf 175.85KB PDF 244次下载 预览
attachment icon USB-MIDI.zip 357.11KB ZIP 254次下载

USB部分没有太多要讲的,毕竟都是用例程改的(这里是拿ST官方的Joystick例程来修改),修改好设备描述符和端点配置就可以了。例程有休眠唤醒支持,实际调试时发现有问题,遂注释掉相关代码,还没有测试实际在主机休眠后再唤醒USB是否能继续正常工作。


这里来分析下USB MIDI事件与MIDI指令互相转换的代码:

<code>static void uart_send(uint8_t *buf, uint8_t len)
{
	uint8_t i;
	
	USART_ClearFlag(USART1, USART_FLAG_TC);
	for(i = 0; i < len; i ++){
		USART_SendData(USART1, buf[i]);
		while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
	}
}

static void usb_send(uint8_t *buf, uint8_t len)
{
	while(!USB_TC);
	USB_TC = 0;
	USB_SIL_Write(ENDP1, buf, len);
	SetEPTxValid(ENDP1);
}

int main(void)
{
	uint8_t i, m, buff[4];
	
	Set_System();
	USB_Interrupts_Config();
	Set_USBClock();
	USB_Init();

	while(1){
		if(bDeviceState == CONFIGURED){
			/*****  UART->USB Routine  ***/
			if(MIDI_SendLen){
				if(MIDI_SendBuff[0] == 0xF0 && 
						MIDI_SendBuff[MIDI_SendLen - 1] == 0xF7){ /* SysEx */
					m = (MIDI_SendLen - 1) / 3;
					for(i = 0; i <= 1 2 3 m; i ++){ if(i="=" m) switch(midi_sendlen % 3){ case 0: * sysex ends with following bytes buff[0]="0x07;" break; 1: 2: } else starts or continues buff[1]="MIDI_SendBuff[i" 3]; buff[2]="MIDI_SendBuff[1" + buff[3]="MIDI_SendBuff[2" usb_send(buff, 4); for(i="0;" < midi_sendlen; ++) midi_sendbuff[i]="0;" }else general switch(midi_sendbuff[0] & 0xf0){ 0xc0: program change 0xd0: channel if(midi_sendlen>= 2){
								buff[0] = MIDI_SendBuff[0] >> 4;
								buff[1] = MIDI_SendBuff[0];
								buff[2] = MIDI_SendBuff[1];
								buff[3] = 0;
								MIDI_SendLen = 0;
								usb_send(buff, 4);
							}
							break;
							
						case 0x80: /* Note-off */
						case 0x90: /* Note-on */
						case 0xA0: /* Poly-KeyPress */
						case 0xB0: /* Control Change */
						case 0xE0: /* PitchBend Change */
							if(MIDI_SendLen >= 3){
								buff[0] = MIDI_SendBuff[0] >> 4;
								buff[1] = MIDI_SendBuff[0];
								buff[2] = MIDI_SendBuff[1];
								buff[3] = MIDI_SendBuff[2];
								MIDI_SendLen = 0;
								usb_send(buff, 4);
							}
							break;
							
						default: MIDI_SendLen = 0;
					}
			}
			/***  USB->UART Routine  ***/
			if(RecvBufNE){
				i = 0;
				do
					switch(MIDI_RecvBuff[i]){
						case 0x05:
						case 0x0F:
							uart_send(&MIDI_RecvBuff[i + 1], 1);
							i += 2;
							break;
						
						case 0x06:
						case 0x0C:
						case 0x0D:
							uart_send(&MIDI_RecvBuff[i + 1], 2);
							i += 3;
							break;
						
						case 0x04:
						case 0x07:
						case 0x08:
						case 0x09:
						case 0x0A:
						case 0x0B:
						case 0x0E:
							uart_send(&MIDI_RecvBuff[i + 1], 3);
							i += 4;
							break;
						
						default: i ++; /* ignore 1 byte */
					}
				while(i < MIDI_RecvLen);
				RecvBufNE = 0;
			}
		}
	}
}
</=></code>

比较麻烦的地方是SysEx要与其他指令分开对待,因为SysEx的长度是不确定的,以F0开始以F7结束,而USB MIDI对SysEx进行了拆分,一帧SysEx会被拆分为多个事件帧,并且前面的若干帧和最后一帧通过CIN区分,最后上位机软件根据CIN来把被拆分的SysEx进行组装,还原回原SysEx帧。具体CIN的定义可以参考USB MIDI V1.0协议第4部分。剩下的0x8~0xE的指令就比较好对待啦,长度都是固定的,CIN和指令也都是一一对应的,按协议定义来解析即可~~~

来自:电子信息 / 电子技术
 

想参与大家的讨论?现在就 登录 或者 注册

ry7740kptv
专家 学者 机友 笔友
文章
88
回复
1542
学术分
5
2010/06/13注册,14 小时前活动
暂无简介
%7B%22isDisplay%22%3Atrue%7D

仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。

插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
处理中..
处理失败
插入表情
我的表情
共享表情
Emoji
上传
注意事项
最大尺寸100px,超过会被压缩。为保证效果,建议上传前自行处理。
建议上传自己DIY的表情,严禁上传侵权内容。
点击重试等待上传{{s.progress}}%处理中...已上传
空空如也~
草稿箱
加载中...
此处只插入正文,如果要使用草稿中的其余内容,请点击继续创作。
{{fromNow(d.toc)}}
{{getDraftInfo(d)}}
标题:{{d.t}}
内容:{{d.c}}
继续创作
删除插入插入
{{forum.displayName}}
{{forum.countThreads}}
篇文章,
{{forum.countPosts}}
条回复
{{forum.description || "暂无简介"}}
ID: {{user.uid}}
学术分隐藏
{{submitted?"":"投诉"}}
请选择违规类型:
{{reason.description}}
支持的图片格式:jpg, jpeg, png