基于STM32的USB MIDI协议栈设计
ry7740kptv2017/02/02电子技术 IP:山东

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 886次下载 预览
attachment icon USB-MIDI.zip 357.11KB ZIP 810次下载

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 <= 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;
			}
		}
	}
}
</=>

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

来自:电子信息 / 电子技术
2
 
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
薛定谔的猫
7年11个月前 IP:陕西
830028
Nice XXXXXXXanks a lot for your efforts about MIDI Controller. It's much useful for the smart musical TC project。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

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

所属专业
所属分类
上级专业
同级专业
ry7740kptv
学者 机友 笔友
文章
89
回复
1542
学术分
5
2010/06/13注册,1年8个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}