基于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接口。


midi10.pdf
176k
PDF
102次下载

USB-MIDI.zip
357k
ZIP
134次下载

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


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

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 <= m; i ++){
						if(i == m)
							switch(MIDI_SendLen % 3){
								case 0: /* SysEx ends with following 3 bytes */
									buff[0] = 0x07;
									break;
								case 1: /* SysEx ends with following 1 bytes */
									buff[0] = 0x05;
									break;
								case 2: /* SysEx ends with following 2 bytes */
									buff[0] = 0x06;
									break;
							}
						else /* SysEx starts or continues */
							buff[0] = 0x04;
						buff[1] = MIDI_SendBuff[i * 3];
						buff[2] = MIDI_SendBuff[1 + i * 3];
						buff[3] = MIDI_SendBuff[2 + i * 3];
						usb_send(buff, 4);
					}
					for(i = 0; i < MIDI_SendLen; i ++)
						MIDI_SendBuff[i] = 0;
				}else /* General */
					switch(MIDI_SendBuff[0] & 0xF0){
						case 0xC0: /* Program Change */
						case 0xD0: /* Channel Change */
							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;
			}
		}
	}
}

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

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

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

ry7740kptv
专家 学者 机友 笔友
文章
88
回复
1537
学术分
5
2010/06/13注册,4 天前活动
暂无简介
插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
{{forum.displayName}}
{{forum.countThreads}}
篇文章,
{{forum.countPosts}}
条回复
{{forum.description}}
ID: {{user.uid}}
{{submitted?"":"投诉"}}
请选择违规类型:
{{reason.description}}
支持的图片格式:jpg, jpeg, png