人们喜欢音乐。许多人知道如何演奏乐器。有些人尝试即兴创作甚至创作音乐。可以将电子乐器连接到您的计算机,以获得更多的创作可能性。这似乎很简单,但是大多数廉价的中文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适配器。
在撰写本文时,我知道几个有趣的项目:
- 南京勤恒微电子CH345芯片文档的图表。
- 使用USB协议的软件实现的Atmega微控制器上的旧项目。他们使用低速模式,Windows 7已弃用该模式,并且不支持该模式。
- Arduino板的MIDIUSB库,具有对USB接口(Atmega32u4,Cortex-M)以及Maple等硬件的支持。
根据MIDI标准的建议,所有项目的电气原理图都包含许多样本片段。因此,仍然需要选择一个支持USB全速模式的微控制器,找到一个PC900V光耦合器和一个DIN-5(MIDI)插座。
电路板布局
我的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接口。
图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代码。
项目代码分为几个功能部分
- 文件“ main.c”包含入口点,全局变量的声明,初始化外围设备的调用以及主程序循环。
- 文件“ init.c”包含时钟,端口,UART及其中断的设置。
- descriptors.c文件包含音频类设备的USB描述符。
- 文件“ midi.c”包含两个函数,用于将MIDI消息转换为USB事件,反之亦然。使用状态机。
- 文件“ 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
图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
。9 MIDI协议中的字节类型。
开玩笑说数字的数字容量
:
— , ?
— H, .
— , 11-22-33?
— H, 11-22-34.
— H ! , !
— , ?
— H, .
— , 11-22-33?
— H, 11-22-34.
— H ! , !
所有方案和源代码以及完成的固件都在我的git仓库中。MIT许可证。
软件
安装板后,应对微控制器进行编程。为此,您可以使用专有的/克隆SiLabs C2调试适配器,或J-Link v10 +(具有EFM8支持),或者使用出厂时已刷新的引导程序(修订版B),或者最后使用具有适当脚本的Arduino。对于检查和调试MIDI消息,MIDI-OX是一个很好的帮助。
图10 MIDI-OX程序界面
如果使用Cubase,则应安装Asio驱动程序,因为在使用DirectSound和DirectInput时,在按键和弹奏音符之间会有延迟。延迟与硬件无关,并且是操作系统实现的功能。通常,该设备可以在Casio CDP-100仪器上完美发挥其功能。
图11 Cubase 5界面。
实验固件生成了最大的音符和其他MIDI命令流。刺耳的声音很糟糕,但是一切都按预期进行。借助MuseScore 3.2,您可以录制和播放中间文件。
最后的笑话
1990-. . — . . :
— , !
— , ! — !
— ! !
— … . !
— ! !
. . , , , … . , . :
— , , ?
— , !
— , ! — !
— ! !
— … . !
— ! !
. . , , , … . , . :
— , , ?
工作成果
适配器有效!看来我设法制作了一个很好的MIDI至USB转换器。对于我的设备,我使用了一个外壳,一些中文适配器的零件和电缆。迷你USB连接器的外壳很深,我不得不重做USB电缆并使用文件。这些LED尽管呈一定角度,但仍紧密地插入孔中。对于中国案例,需要修改董事会。
数字:12.紧凑的微型USB插头。
使用8位EFM8UB20微控制器的决定似乎有些争议。当然,还有其他选项和控制器。另一种方法是在CH345转换器上选择纯硬件解决方案,然后根据中国人推荐的参考方案制造器件。但是我的版本是通用的,因为 允许您更改固件,添加所需的功能或修复发现的错误。最后,我从完成的项目中获得了知识,经验和道德上的满足感。最后,我完成了我的文章,而您也完成了阅读。
有用的链接
感谢您的关注。