MIDI2USB-音乐连接了我们

俄中美MIDI到USB转换器。图1俄中美MIDI到USB转换器。图片由作者提供。



人们喜欢音乐。许多人知道如何演奏乐器。有些人尝试即兴创作甚至创作音乐。可以将电子乐器连接到您的计算机,以获得更多的创作可能性。这似乎很简单,但是大多数廉价的中文USB-MIDI适配器性能一般。谁在乎我如何制作MIDI2USB适配器,我邀请您阅读



问题的提法



几年前,我的侄子,正在学习音乐,开始即兴创作音乐。我希望他的作品不致遗失,但我设法只在录音机上录制了他的音乐作品。录制的质量不令人满意。我想直接在Cubase或MuseScore中记录笔记,然后对其进行编辑。为此,我决定购买一个中文USB到MIDI适配器(转换器)。



主题轶事
:

— , !

— , ?

— !


该适配器电缆很便宜,不能很好地工作。从合成器(电子钢琴)到计算机的数据传输不起作用。如果您用一根手指弹奏,则可以录制多个音符,并且在进行和弦或弹奏音阶时,适配器会挂起并变成砖头。另一个方向,即 将数据从计算机传输到合成器效果很好。在许多买家的评论中可以找到类似的故事。



完成中文适配器的方法



互联网上有许多有关如何改进修改中文适配器的讨论。在此适配器的某些版本中,提供了一个光耦合器,但未焊接,从而在计算机和合成器之间提供了电流隔离。 las,就我而言,修订很困难,因为代替了光耦合器,安装了两个NPN晶体管。请注意,MIDI标准直接指定使用光隔离器,例如PC900V或6N138。光耦合器H11L1M(DIP-8)或H11L1SM(SO-6)具有相似的特性。可以使用具有合适参数的其他组件。



拆卸过程中的中文适配器

图2。中文适配器正在拆除过程中。图片由作者提供。



照片显示,外壳中有足够的空间容纳光隔离器和相关元件。一些工匠焊接现有组件,并在其位置安装带有“主体套件”的光隔离器。显然,该操作不仅需要知识,还需要良好的手部动作技巧。



但是在乐器和计算机之间提供光隔离是不够的。还需要一个精密的晶体振荡器或谐振器,以确保串行UART的时钟符合MIDI标准。我买的中文适配器不仅缺少光耦合器,而且缺少石英谐振器。当然,有些微电路在工厂中已对时钟单元进行了校准,但是没有这样的电路。通常,该中文产品的性能较低。 CH345芯片上内置有适配器-SSOP-20封装中的USB到MIDI转换器,但这不是我的情况。 CH345芯片具有硬件USB标签供应商ID:1a86,产品ID:752d。但是,任何“左手”微电路都可以(并且确实)给出相同的标识符,甚至可以“假装”为任何东西。



我在中文适配器中发现的最后一个小缺陷是软件(固件)。更准确地说,这是端点(EndPoints)的较小缓冲区大小,只有8个字节。这足以传输按下的音符,因为通过USB接口的MIDI信息由4个字节(电缆号,命令号和2个数据字节)组成。但是任何扩展名(例如SysEx)都可以更大。



一段时间后,我购买了另一根适配器电缆,即“专业USB MIDI接口”。该适配器要贵得多,性能要好得多,但是仍然有错误。这体现在以下事实上:演奏合成器几分钟后,他突然开始错过击键,反之亦然-没意识到释放琴键。我对中文适配器的结果感到失望,并决定遵循以下建议:“如果您想做得好,那就自己动手。”



硬件部分



首先,有必要考虑未来设备的方案并学习其他工程师的经验。现有的适配器在外观上看起来非常好,因此我决定使用外壳,LED和屏蔽电缆。而且,在莫斯科,MIDI电缆比现成的中文适配器贵。我拔出了中文板,测量了它的尺寸,并开始研究MIDI标准和在公共领域中成功的MIDI项目。





图3带有电缆的USB-MIDI适配器。



在撰写本文时,我知道几个有趣的项目:



  1. 南京勤恒微电子CH345芯片文档的图表
  2. 使用USB协议的软件实现的Atmega微控制器上的旧项目。他们使用低速模式,Windows 7已弃用该模式,并且不支持该模式。
  3. Arduino板的MIDIUSB库,具有对USB接口(Atmega32u4,Cortex-M)以及Maple等硬件的支持。


根据MIDI标准建议,所有项目的电气原理图都包含许多样本片段因此,仍然需要选择一个支持USB全速模式的微控制器,找到一个PC900V光耦合器和一个DIN-5(MIDI)插座。



电路板布局
MIDI2USB Schematics




我的MIDI2USB适配器的核心是Silicon Laboratories的8位EFM8UB20F64G微控制器我真的很喜欢它,我会尽可能使用它。该控制器是C8051F380控制器的后继产品(更名后),它取代了传奇的C8051F320-Cygnal的成功开发,该产品于2003年被SiLabs收购。



我将列出支持EFM8UB20F64微控制器的论点:



  • 易于开发的软件,表现为快速,易于使用的GPIO,SPI,UART,USB,PCA;
  • 改进的8051内核(每条指令1-2个周期,48MIPS),“实时”改变频率;
  • 内置稳压器,输出公差为+ 5V,电流高达100 mA;
  • 内置的精确时钟发生器可通过USB主机校准(±0.25%);
  • 提供USBXpress,VCPXpress,USB设备API库和快速入门示例;
  • 纯勘误表。


将此控制器编程为 寄存器很少,您可以专注于解决应用问题。arithmetic,算术运算(尤其是32位运算)很慢,但其他情况下EFM8则不错。为USB设备开发软件并非易事。SiLabs控制器的主要优点是USBXpress,VCPXpress,USB设备API库。甚至德州仪器(TI)在其SmartRF板上也使用C8051F320控制器。



光耦合器是适配器中第二重要的组件。我决定使用Sharp PC900V,因为它恰好在推荐的MIDI规格表中指明。该光耦合器的独特之处在于其开/关时间(1μs和2μs)以及数字输出均很快。但是也有缺点-微电路的大尺寸(7x10mm)和运行5年后的50%烧毁。光耦合器的尺寸不允许在电路板的一侧标记所有组件。我也不想放弃占用大量空间的MIDI接口。



板的背面带有光耦合器和LED。

图4带有PC900V光耦合器和LED的板的背面。图片由作者提供。



根据推荐的标准方案,将输出级组装在逻辑芯片74LVC2G04上,该逻辑芯片由两个反相器组成。该组件的主要目的是将逻辑信号电平从3V => 5V转换为至少10 mA的输出电流。



另一个轶事
:

- , , , , , …

. :

— - ?

— , !


其余组件将执行辅助功能,并且不会显着影响设备的运行。电阻,电容,二极管和LED可以合理更换。您可以像中国人一样,使用微型USB或制作用于焊接电缆的引脚连接器,而不是微型USB连接器。MIDI连接器占用大量空间,并且不适合放入外壳,因此仅在适配器版本中使用,而没有外壳。MIDI-IN和MIDI-OUT信号被路由到插针头以进行电缆接线。通常,应根据情况调整LED和连接器的位置,以使其达到最佳位置。





图5 MIDI2USB适配器的调试版和盒装版本。图片由作者提供。



总消耗电流不超过50 mA。它由以下部分组成:



  • 微控制器,15mA;
  • 三个LED,15mA(3x5mA);
  • 微电路74LVC2G04,10 mA;
  • 光耦合器PC900V,10 mA。


2层PCB由美国人在OSH Park制造,厚1.6mm,0.035mm铜,FR-4材料。



软件部分



设备软件的开发是开发的重要而关键的阶段。幸运的是,所有现代操作系统都有USB MIDI设备的驱动程序。任务减少了,您只需要为适配器编写固件。



我通常将Keil uVision PK51与配置向导2结合使用,有时还与IAR Embedded Workbench结合使用,而很少使用SiLabs Simplicity Studio。每种环境都有优点和缺点。在这个项目中,我决定使用IAR,因为我想拥有“带有类的C”。另外,IAR编译器提供对系统寄存器所有位的访问。例如,P2_bit.B0 = 1;PCA0MD_bit.WDTE = 0;



无需使用“魔术常数”或充满CMSIS或“ SI_EFM8UB2_Register_Enums.h”的多级位表达式。las,所有这些功能都在ioEFM8UB20F64G.h文件中声明,该文件与si_toolchain.h库(例如,宏B0..B3)不兼容。我没有将项目翻译成Keil uVision PK51,而只是为所有开发环境编写了兼容的C代码。



项目代码分为几个功能部分



  1. 文件“ main.c”包含入口点,全局变量的声明,初始化外围设备的调用以及主程序循环。
  2. 文件“ init.c”包含时钟,端口,UART及其中断的设置。
  3. descriptors.c文件包含音频类设备的USB描述符。
  4. 文件“ midi.c”包含两个函数,用于将MIDI消息转换为USB事件,反之亦然。使用状态机。
  5. 文件“ usbconfig.h”包含用于配置USB设备API库的操作模式的宏和定义(#define)。


我们来看一下端口,外围设备和主循环的main()函数。



int main( void )
{
	WDT_Init();                             // Disable WDTimer (not used)
	PORT_Init();                            // Initialize ports (UART, LEDs)
	SYSCLK_Init();                          // Set system clock to 48MHz
	UART0_Init();                           // Initialize UART0 @31250, 8-N-1
	USBD_Init( &usbInitStruct );            // Initialize USB, clock calibrate
	LED_IN  = 1;                            // Blink LED
	LED_OUT = 1;                            // Blink LED
	IE_EA   = 1;                            // Global enable IRQ

	while(1)
	{
		//--- MIDI => USB
		if( nMidiCount > 0 )
		{
			IE_EA  = 0;                     // Begin: Critical section
			if( USB_STATUS_OK==USBD_Write(EP1IN,aMidiBuffer,nMidiCount,false) )
			{
				nMidiCount = 0;             // Reset MIDI data byte counter
			}
			IE_EA  = 1;                     // End of: Critical section
			LED_IN = 0;                     // Turn off input LED
		}

		//--- USB => MIDI
		if( nUsbCount )
		{
			uint8_t i;
			LED_OUT = 1;                    // Turn on Led on New packet
			for(i = 0; i < nUsbCount; i++)  // Process every data byte
			{
				USB2MIDI( aUsbBuffer[i] );  // Convert USB packet into MIDI
			}
			nUsbCount = 0;                  // Reset counter
			USBD_Read(EP2OUT, aUsbBuffer, sizeof(aUsbBuffer), true);
			LED_OUT = 0;                    // Turn off Led, when done
		}
	}
}


用于USB设备的SiLabs库由一组子例程组成,这些子例程根据“ usbconfig.h”文件中的设置被编译并包含在项目中。这与Atmel(现为Microchip)的微控制器代码中的“ libusb,V-USB”库非常相似。应该注意的是,从程序员的角度来看,SiLabs具有良好而便捷的库。



设备,配置和接口的描述符(描述符)在任何USB设备的操作中都起着重要作用。设备使用这些描述符,将其要求,功能,参数等通知主机(计算机)。通常在每个USB库中都有处理描述符请求的功能,并且仅要求程序员正确填写包含这些描述符的数据结构。



带描述符的代码
SI_SEGMENT_VARIABLE
(usbDeviceDesc[], const USB_DeviceDescriptor_TypeDef, SI_SEG_CODE) =
{
	USB_DEVICE_DESCSIZE,               // bLength, 18 bytes
	USB_DEVICE_DESCRIPTOR,             // bDescriptorType, 1
	htole16(0x0110),                   // bcdUSB Ver, 1.10
	0x00,                              // bDeviceClass, 0 for Audio
	0x00,                              // bDeviceSubClass, 0 for Audio
	0x00,                              // bDeviceProtocol, 0 for Audio
	SLAB_USB_EP1IN_MAX_PACKET_SIZE,    // bMaxPacketSize0, 64 bytes
	htole16(0x1209),                   // idVendor, Free GPL (SiLabs 0x10C4)
	htole16(0x7522),                   // idProduct
	htole16(0x0100),                   // bcdDevice, 1.00
	0x01,                              // iManufacturer string
	0x02,                              // iProduct string
	0x03,                              // iSerialNumber (no serial string)
	0x01                               // bNumConfigurations
};


所有描述符,拓扑和术语在“ MIDI设备的通用串行总线设备类定义”标准中进行了详细介绍。为了快速入门并沉浸于该主题中,足以研究Windows Driver Kit 7600或“ USB Descriptor Dumper”中“ usbview.exe”程序提供的信息。您甚至可以将某些内容复制到程序中。





图6程序“ usbview.exe”中有关描述符的信息描述



符以及相应的数组和结构位于微控制器的闪存(代码段)中,因为这些数据不变(常数)。将常量存储在闪存中是一种典型的编程技巧,可让您节省RAM。



请注意设备描述符结构中的Vendor_ID和Product_ID字段。这是一对数字,用于唯一标识USB设备。要为您的设备获取一个这样的号码,您需要向USB-IF组织付款或向现有Vendor_ID(微控制器制造商)的所有者发送请求,并获取Product_ID。例如,您可以像中文一样使用其他人最合适的VID和PID。对于开源项目,可以选择获取免费的Product_ID



开发MIDI流音频类的USB设备时,要注意的另一点是连接器(Jack)。连接器是虚拟的(虚拟的)实体,用于描述设备和主机之间的拓扑和连接。它们是输入(输入插孔)和输出(输出插孔),内部(嵌入式)和外部(外部)。每个连接器都有一个唯一的Jack_Id(数字从0到15)。输出连接器包含源ID号,即 用于连接的连接器编号。最后,音频端点(EP)在形成的通道(输入和输出流)的顶部工作。这些几乎是普通的Bulk EP,在其描述符中具有连接器绑定信息。



嵌入式和外部插孔

数字:7插孔和虚拟流至USB(MIDI类)。



MIDI杰克描述符
	// EMB:  IN Jack #1 <-----> EXT: OUT Jack #4
	// EMB: OUT Jack #3 <-----> EXT:  IN Jack #2

	//--- Class-Specific MS Interface Header Descriptor, p.40
	USB_MIDI_INTERFACE_DESCSIZE,       // bLength, 7 bytes
	USB_CS_INTERFACE_DESCRIPTOR,       // bDescriptorType, 0x24
	MIDI_CS_IF_HEADER,                 // bDescriptorSubtype, 0x01
	0x00,                              // bcdADC(LSB)
	0x01,                              // bcdADC(MSB), 0x0100 (version)
	0x41,                              // wTotalLength(LSB), 65 bytes
	0x00,                              // wTotalLength(MSB)

	//--- MIDI IN JACK EMB(it connects to the USB OUT Endpoint), p.40
	USB_IN_JACK_DESCSIZE,              // bLength, 6 bytes
	USB_CS_INTERFACE_DESCRIPTOR,       // bDescriptorType, 0x24
	MIDI_CS_IF_IN_JACK,                // bDescriptorSubtype, 0x02
	MIDI_JACK_TYPE_EMB,                // bJackType, 0x01 (embedded)
	1,                                 // bJackID, #1
	0,                                 // Jack string descriptor, unused
	//--- MIDI IN JACK EXT, p.40
	USB_IN_JACK_DESCSIZE,              // bLength, 6 bytes
	USB_CS_INTERFACE_DESCRIPTOR,       // bDescriptorType, 0x24
	MIDI_CS_IF_IN_JACK,                // bDescriptorSubtype, 0x02
	MIDI_JACK_TYPE_EXT,                // bJackType, 0x02 (external)
	2,                                 // bJackID, #2
	0,                                 // Jack string descriptor, unused

	//--- MIDI OUT JACK EMB (connects to IN Endpoint), p.41
	USB_OUT_JACK_DESCSIZE,             // bLength, 9 bytes
	USB_CS_INTERFACE_DESCRIPTOR,       // bDescriptorType, 0x24
	MIDI_CS_IF_OUT_JACK,               // bDescriptorSubtype, 0x03
	MIDI_JACK_TYPE_EMB,                // bJackType, 0x01
	3,                                 // bJackID
	1,                                 // bNrInputPins
	2,                                 // baSourceID, this <=> Jack #2
	1,                                 // baSourcePin
	0,                                 // iJack, unused
	//--- MIDI OUT JACK EXT, p.41
	USB_OUT_JACK_DESCSIZE,             // bLength, 9 bytes
	USB_CS_INTERFACE_DESCRIPTOR,       // bDescriptorType, 0x24
	MIDI_CS_IF_OUT_JACK,               // bDescriptorSubtype, 0x03
	MIDI_JACK_TYPE_EXT,                // bJackType, 0x02
	4,                                 // bJackID
	1,                                 // bNrInputPins
	1,                                 // baSourceID, this <=> Jack #1
	1,                                 // baSourcePin
	0,                                 // iJack, unused




USB MIDI类音频设备中的数据交换包括32位数据包(USB-MIDI事件数据包)的传输。从MIDI设备接收到1、2或3个字节的消息。通过USB传输时,带有电缆号和命令代码的起始字节被添加到这些字节中。如果数据包少于4个字节,则将其填充为0。在当前固件版本中,直到32位边界,我都不会填充零。有用。问题仍然悬而未决。



例如,在电缆#1中,按Note On键(传输时间960us)的命令将转换为以下数据包:

MIDI:0x90 0x60 0x7f => USB:0x19 0x90 0x60 0x7f


USB-MIDI事件包

图8 USB规范中的USB-MIDI事件包方案。



typedef union
{
	struct PACKET
	{
		uint8_t  cable : 4;            // Cable Number (we use #0)
		uint8_t  cin   : 4;            // Code Index Number (cmd: 0x08)
		uint8_t  cmd;                  // MIDI command (status byte)
		uint8_t  data1;                // MIDI data byte #1
		uint8_t  data2;                // MIDI data byte #2
	};
	uint8_t buffer[sizeof(struct PACKET)];
} MIDI_EVENT_PACKET;


通过MIDI2USB()和USB2MIDI()函数执行直接和反向转换在这些功能中,使用状态机,当输入数据到达时,功能从空闲状态(IDLE)变为接收命令的状态(STATUS),然后变为接收数据的状态(DATA),最后发送数据并返回其原始状态期望。



在MIDI协议中,数据字节本质上是7位(0..127)。它们总是将最高有效位设置为0。相反,命令(状态字节)总是将最高有效位设置为1,即 值从128到255



MIDI字节的类型

9 MIDI协议中的字节类型。



开玩笑说数字的数字容量
:

— , ?

— H, .

— , 11-22-33?

— H, 11-22-34.

— H ! , !


所有方案和源代码以及完成的固件都在我的git仓库中MIT许可证。



软件



安装板后,应对微控制器进行编程。为此,您可以使用专有的/克隆SiLabs C2调试适配器,或J-Link v10 +(具有EFM8支持),或者使用出厂时已刷新的引导程序(修订版B),或者最后使用具有适当脚本的Arduino。对于检查和调试MIDI消息,MIDI-OX是一个很好的帮助



MIDI-OX

图10 MIDI-OX程序界面



如果使用Cubase,则应安装Asio驱动程序,因为在使用DirectSound和DirectInput时,在按键和弹奏音符之间会有延迟。延迟与硬件无关,并且是操作系统实现的功能。通常,该设备可以在Casio CDP-100仪器上完美发挥其功能。



Cubase MIDI配置

图11 Cubase 5界面。



实验固件生成了最大的音符和其他MIDI命令流。刺耳的声音很糟糕,但是一切都按预期进行。借助MuseScore 3.2,您可以录制和播放中间文件。



最后的笑话
1990-. . — . . :

— , !

— , ! — !

— ! !

— … . !

— ! !

. . , , , … . , . :

— , , ?


工作成果



适配器有效!看来我设法制作了一个很好的MIDI至USB转换器。对于我的设备,我使用了一个外壳,一些中文适配器的零件和电缆。迷你USB连接器的外壳很深,我不得不重做USB电缆并使用文件。这些LED尽管呈一定角度,但仍紧密地插入孔中。对于中国案例,需要修改董事会。



迷你USB电缆

数字:12.紧凑的微型USB插头。



使用8位EFM8UB20微控制器的决定似乎有些争议。当然,还有其他选项和控制器。另一种方法是在CH345转换器上选择纯硬件解决方案,然后根据中国人推荐的参考方案制造器件。但是我的版本是通用的,因为 允许您更改固件,添加所需的功能或修复发现的错误。最后,我从完成的项目中获得了知识,经验和道德上的满足感。最后,我完成了我的文章,而您也完成了阅读。



有用的链接





感谢您的关注。



All Articles