基于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 482次下载 预览
attachment icon USB-MIDI.zip 357.11KB ZIP 455次下载

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和指令也都是一一对应的,按协议定义来解析即可~~~

来自:电子与无线电 / 电子技术
 
薛定谔的猫
4年10个月前
1楼
Nice XXXXXXXanks a lot for your efforts about MIDI Controller. It's much useful for the smart musical TC project。
回复
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

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

所属专业
所属分类
上级专业
同级专业
ry7740kptv
学者 机友 笔友
文章
88
回复
1542
学术分
5
2010/06/13注册,1 个月前活动
暂无简介
%7B%22isDisplay%22%3Atrue%7D
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{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}}
学术分隐藏
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

支持的图片格式:jpg, jpeg, png
插入公式
分享回复:{{shareId}}
加载中...
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

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

空空如也