MIDI2USB - a música nos conectou

Conversor de MIDI para USB russo-chinês-americano.Fig.1 Conversor russo-chinês-americano de MIDI para USB. Foto do autor.



As pessoas adoram música. Muitas pessoas sabem tocar instrumentos musicais. E alguns tentam improvisar e até compor música. Instrumentos musicais eletrônicos podem ser conectados ao seu computador para possibilidades mais criativas. Isso parece ser uma questão simples, mas a maioria dos adaptadores USB-MIDI chineses baratos têm um desempenho medíocre. Quem se importa como eu fiz meu adaptador MIDI2USB, eu convido você a ler



Formulação do problema



Há alguns anos meu sobrinho, que está estudando música, começou a improvisar e compor música. Queria que seu trabalho não se perdesse, mas consegui registrar seus estudos musicais apenas em um gravador. A qualidade desta gravação foi insatisfatória. Queria gravar notas diretamente no Cubase ou MuseScore e depois editá-las. Para isso, decidi comprar um adaptador (conversor) USB-para-MIDI chinês.



Anedota no assunto
:

— , !

— , ?

— !


Este cabo adaptador é barato e não funciona bem. A transferência de dados do sintetizador (piano elétrico) para o computador não funciona. Se você tocar com um dedo, poderá gravar várias notas e, ao pegar um acorde ou tocar escalas, o adaptador trava e vira um tijolo. Outra direção, ou seja, transferir dados do computador para o sintetizador funciona bem. Histórias semelhantes podem ser encontradas nas análises de muitos compradores.



Maneiras de finalizar o adaptador chinês



Existem muitas discussões na Internet sobre como melhorar ou modificar o adaptador chinês. Em algumas versões deste adaptador, um optoacoplador é fornecido, mas não soldado, o que fornece isolamento galvânico entre o computador e o sintetizador. Infelizmente, no meu caso, a revisão foi difícil, porque em vez de um optoacoplador, dois transistores NPN são instalados. Observe que o padrão MIDI especifica diretamente o uso de um opto-isolador, por exemplo, PC900V ou 6N138. Os optoacopladores H11L1M (DIP-8) ou H11L1SM (SO-6) têm características semelhantes. Outros componentes com parâmetros adequados podem ser usados.



Adaptador chinês em processo de desmontagem

Figura 2. Adaptador chinês em processo de desmontagem. Foto do autor.



A foto mostra que há espaço suficiente na caixa para acomodar o opto-isolador e os elementos relacionados. Alguns artesãos soldam os componentes existentes e instalam um opto-isolador com um "kit de corpo" em seu lugar. Obviamente, esta operação requer não apenas conhecimento, mas também boas habilidades motoras manuais.



Mas não é suficiente fornecer isolamento óptico entre o instrumento musical e o computador. Um oscilador ou ressonador de cristal de precisão também é necessário para garantir que o UART serial seja sincronizado de acordo com o padrão MIDI. O adaptador chinês que comprei não tem apenas um optoacoplador, mas também um ressonador de quartzo. Claro, existem microcircuitos nos quais as unidades de relógio são calibradas na fábrica, mas não há nada parecido. Em geral, o desempenho deste produto chinês é baixo. Existem adaptadores embutidos no chip CH345 - um conversor USB para MIDI no pacote SSOP-20, mas este não é o meu caso. O microchip CH345 tem tags USB de hardware ID do fornecedor: 1a86, ID do produto: 752d. No entanto, qualquer microcircuito "canhoto" pode (e dá) os mesmos identificadores e pode até "fingir" ser qualquer coisa.



A última pequena falha que encontrei no adaptador chinês é o software (firmware). Mais precisamente, este é um tamanho de buffer pequeno para endpoints (EndPoints), apenas 8 bytes. Isso é suficiente para transmitir as notas pressionadas, pois a mensagem MIDI via interface USB consiste em 4 bytes (número do cabo, número do comando e 2 bytes de dados). Mas quaisquer extensões, por exemplo SysEx, podem ser maiores.



Depois de um tempo, comprei outro cabo adaptador, que se chamava “Professional USB MIDI Interface”. Este adaptador era significativamente mais caro e tinha um desempenho significativamente melhor, mas ainda com erros. Isso se manifestou no fato de que, após alguns minutos tocando o sintetizador, ele repentinamente começou a perder as teclas pressionadas, ou vice-versa - não percebeu a liberação da tecla. Fiquei desapontado com os resultados dos adaptadores chineses e decidi seguir o conselho: "Se você quer fazer algo bem, faça você mesmo."



Parte de hardware



Primeiro, foi necessário pensar sobre o esquema do futuro dispositivo e estudar a experiência de outros engenheiros. O adaptador existente parecia muito bom por fora, então decidi usar um gabinete, LEDs e cabos blindados dele. Além disso, em Moscou, os cabos MIDI são mais caros do que um adaptador chinês pronto. Peguei a placa chinesa, medi suas dimensões e comecei a estudar o padrão MIDI e os projetos MIDI de sucesso em domínio público.





Fig. 3 Adaptador USB-MIDI na caixa e com cabos.



No momento em que este artigo foi escrito, conheço vários projetos interessantes:



  1. Diagrama da documentação do chip CH345 da Nanjing Qinheng Microelectronics.
  2. Projetos antigos em microcontroladores Atmega com implementação de software do protocolo USB. Eles usam o modo de baixa velocidade, que está obsoleto e não é compatível com o Windows 7.
  3. Biblioteca MIDIUSB para placas Arduino com suporte de hardware para interface USB (Atmega32u4, Cortex-M), bem como Maple, etc.


Os diagramas esquemáticos elétricos em todos os projetos contêm muitos fragmentos de amostra com base nas recomendações do padrão MIDI . Portanto, faltou escolher um microcontrolador com suporte para o modo USB Full Speed, encontrar um optoacoplador PC900V e um soquete DIN-5 (MIDI) à venda.



Layout da placa
MIDI2USB Schematics




O coração do meu adaptador MIDI2USB é um microcontrolador EFM8UB20F64G de 8 bits da Silicon Laboratories. Eu realmente gosto e uso sempre que posso. Este controlador é o sucessor (após rebranding) do controlador C8051F380, que substituiu o lendário C8051F320 - um desenvolvimento de sucesso do Cygnal, que foi comprado pela SiLabs em 2003.



Vou listar meus argumentos a favor do microcontrolador EFM8UB20F64:



  • conveniência de desenvolvimento de software, que se expressa na presença de GPIO, SPI, UART, USB, PCA rápidos e fáceis de usar;
  • 8051-core aprimorado (1-2 ciclos por instrução, 48MIPS), mudança de frequência "em tempo real";
  • regulador de tensão embutido, tolerância de saída de + 5V, corrente de até 100 mA;
  • gerador de relógio preciso integrado com calibração do host USB (± 0,25%);
  • disponibilidade de USBXpress, VCPXpress, bibliotecas USB Device API e exemplos para início rápido;
  • errata pura.


É um prazer programar este controlador como existem poucos registros e você pode se concentrar na resolução de um problema aplicado. Infelizmente, as operações aritméticas (especialmente as de 32 bits) são lentas, mas fora isso, EFM8 é bom. O desenvolvimento de software para dispositivos USB não é uma tarefa fácil. E aqui a principal vantagem dos controladores SiLabs são as bibliotecas USBXpress, VCPXpress e USB Device API. Até mesmo a Texas Instruments usa controladores C8051F320 em suas placas SmartRF.



O optoacoplador é o segundo componente mais importante do adaptador. Decidi usar o Sharp PC900V porque é exatamente o que está especificado no diagrama de especificação MIDI recomendado. A peculiaridade deste optoacoplador são os tempos de liga e desliga rápidos (1μs e 2μs), assim como a presença de uma saída digital. Mas também há desvantagens - o grande tamanho do microcircuito (7x10 mm) e queima em 50% após 5 anos de operação. As dimensões do optoacoplador não permitiram a marcação de todos os componentes em um dos lados da placa. Eu também não queria abrir mão do conector MIDI, que ocupava muito espaço.



Parte traseira da placa com optoacoplador e LEDs.

Fig. 4 Parte traseira da placa com optoacoplador PC900V e LEDs. Foto do autor.



O estágio de saída é montado de acordo com o esquema padrão recomendado em um chip lógico 74LVC2G04, composto por dois inversores. O objetivo principal deste componente é converter os níveis de sinal lógico de 3 V => 5 V e fornecer uma corrente de saída de pelo menos 10 mA.



Outra anedota
:

- , , , , , …

. :

— - ?

— , !


Os demais componentes desempenham funções auxiliares e não afetam significativamente a operação do dispositivo. Resistores, capacitores, diodos e LEDs podem ser substituídos razoavelmente. Em vez de um conector mini-USB, você pode colocar um micro-USB ou fazer um conector de pino para soldar o cabo, como os chineses fazem. O conector MIDI ocupa muito espaço e não cabe na caixa, por isso só é usado na versão do adaptador sem caixa. Os sinais MIDI-IN e MIDI-OUT são roteados para o cabeçalho do pino para a fiação do cabo. Em geral, a localização dos LEDs e conectores deve ser ajustada para seu posicionamento ideal na caixa.





Fig. 5 Versões de depuração e em caixa do adaptador MIDI2USB. Foto do autor.



A corrente de consumo total não excede 50 mA. Consiste nas seguintes partes:



  • microcontrolador, 15mA;
  • três LEDs, 15mA (3x5mA);
  • microcircuito 74LVC2G04, 10 mA;
  • optoacoplador PC900V, 10 mA.


O PCB de 2 camadas foi feito por americanos em OSH Park , com 1,6 mm de espessura, 0,035 mm de cobre, material FR-4.



Parte de software



A criação de software para equipamentos é uma etapa importante e crucial do desenvolvimento. Felizmente, todos os sistemas operacionais modernos possuem drivers para dispositivos USB MIDI. A tarefa é reduzida e você só precisa escrever o firmware para o adaptador.



Eu geralmente uso o Keil uVision PK51 em conjunto com o Configuration Wizard 2, às vezes IAR Embedded Workbench e muito raramente o SiLabs Simplicity Studio. Cada ambiente tem vantagens e desvantagens. Neste projeto decidi usar o IAR porque queria ter "C com classes". Além disso, o compilador IAR fornece acesso a todos os bits dos registros do sistema. Por exemplo, P2_bit.B0 = 1; ou PCA0MD_bit.WDTE = 0;



Não há necessidade de usar as "constantes mágicas" ou expressões de bits de vários níveis que estão cheias de CMSIS ou "SI_EFM8UB2_Register_Enums.h". Infelizmente, toda essa funcionalidade é declarada no arquivo ioEFM8UB20F64G.h, que se revelou incompatível com as bibliotecas si_toolchain.h (por exemplo, a macro B0..B3). Não traduzi o projeto para o Keil uVision PK51, mas simplesmente escrevi código C compatível para todos os ambientes de desenvolvimento.



O código do projeto é dividido em várias partes funcionais



  1. O arquivo "main.c" contém o ponto de entrada, declarações de variáveis ​​globais, uma chamada para inicializar periféricos e o loop principal do programa.
  2. O arquivo "init.c" contém configurações de clock, portas, UART e suas interrupções.
  3. O arquivo descriptors.c contém descritores USB para o dispositivo Audio Class.
  4. O arquivo "midi.c" contém duas funções para converter mensagens MIDI em eventos USB e vice-versa. Uma máquina de estado é usada.
  5. O arquivo "usbconfig.h" contém macros e definições (#define) para configurar os modos de operação da biblioteca API de dispositivos USB.


Vamos dar uma olhada na função main () com as portas, periféricos e loop principal.



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
		}
	}
}


A biblioteca do SiLabs para dispositivos USB consiste em um conjunto de sub-rotinas que são compiladas e incluídas no projeto dependendo das configurações do arquivo “usbconfig.h”. Isso é muito semelhante à biblioteca "libusb, V-USB" encontrada no código para microcontroladores da Atmel (agora Microchip). Deve-se notar que o SiLabs tem uma biblioteca boa e conveniente do ponto de vista do programador.



Os descritores (descritores) do dispositivo, configuração e interfaces desempenham um papel importante na operação de qualquer dispositivo USB. Usando esses descritores, o dispositivo informa ao host (computador) sobre seus requisitos, capacidades, parâmetros, etc. Uma função para lidar com solicitações de descritores é normalmente encontrada em todas as bibliotecas USB, e o programador só precisa preencher corretamente as estruturas de dados que contêm esses descritores.



Código com descritores
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
};


Todos os descritores, topologia e terminologia são detalhados e detalhados no padrão "Universal Serial Bus Device Class Definition para MIDI Devices" . E para um rápido início e imersão no assunto, basta estudar as informações fornecidas pelos programas "usbview.exe" do Windows Driver Kit 7600 ou "USB Descriptor Dumper" . Algo que você pode até copiar para seu programa.





Fig.6 Informações sobre os descritores do programa "usbview.exe" Os



descritores e os arranjos e estruturas correspondentes estão localizados na memória flash do microcontrolador (segmento de código), pois esses dados não mudam (constantes). Armazenar constantes na memória flash é um truque típico de programação que permite salvar RAM.



Preste atenção aos campos Vendor_ID e Product_ID na estrutura do descritor do dispositivo. Este é um par de números para identificar exclusivamente o dispositivo USB. Para obter esse número para o seu dispositivo, você precisa pagar à organização USB-IF ou enviar uma solicitação ao proprietário do Vendor_ID existente (fabricante do microcontrolador) e obter o Product_ID. E você pode, por exemplo, como o chinês para usar VID e PID mais adequado de outras pessoas. Para projetos de código aberto, há uma opção para obter um Product_ID gratuito .



Outro ponto a se prestar atenção ao desenvolver dispositivos USB da classe de áudio MIDI Streaming são os conectores (Jack). Conectores são entidades imaginárias (virtuais) para descrever a topologia e as conexões entre um dispositivo e um host. Eles são de entrada (In Jack) e saída (Out Jack), internos (Embedded) e externos (Externos). Cada conector possui um Jack_Id exclusivo (número de 0 a 15). Os conectores de saída contêm o número de identificação da fonte, ou seja, número do conector para conexão. Finalmente, os pontos finais de áudio (EP) funcionam no topo dos canais formados (fluxos de entrada e saída). Esses são EPs em massa quase comuns que têm informações de ligação do conector em seus descritores.



Tomadas embutidas e externas

Figura: 7 Jacks e fluxos virtuais para USB (classe MIDI).



Descritores MIDI Jack
	// 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




A troca de dados em um dispositivo de áudio de classe MIDI USB consiste na transmissão de pacotes de 32 bits (Pacote de eventos USB-MIDI). Mensagens de 1, 2 ou 3 bytes são recebidas do dispositivo MIDI. Ao transferir por USB, um byte principal com um número de cabo e código de comando é adicionado a esses bytes. Se o pacote tiver menos de 4 bytes, ele será preenchido com 0. Na versão atual do firmware, não preencho com zeros até a borda de 32 bits. Funciona. A questão permanece aberta.



Por exemplo, no cabo # 1, o comando para pressionar a tecla Note On (tempo de transmissão 960us) é convertido no seguinte pacote:

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


Pacote de Evento USB-MIDI

Fig. 8 Esquema do pacote de eventos USB-MIDI da especificação USB.



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;


A conversão direta e reversa é realizada pelas funções MIDI2USB () e USB2MIDI () . Nessas funções, uma máquina de estado é utilizada, quando, conforme os dados de entrada chegam, a função passa do estado inativo (IDLE) para o estado de recebimento de comandos (STATUS), e depois para o estado de recebimento de dados (DATA), e, por fim, o envio de dados com retorno ao estado original expectativas.



No protocolo MIDI, os bytes de dados são essencialmente de 7 bits (0..127). Eles sempre têm o 8º bit mais significativo definido como 0. Os comandos (bytes de status), pelo contrário, sempre vêm com o bit mais significativo definido como 1, ou seja, têm valores de 128 a 255.



Tipos de bytes MIDI

Fig. 9 Tipos de bytes no protocolo MIDI.



Piada sobre a capacidade dos dígitos dos números
:

— , ?

— H, .

— , 11-22-33?

— H, 11-22-34.

— H ! , !


Todos os esquemas e códigos-fonte, bem como o firmware finalizado, estão no meu repositório git . Licença do MIT.



Programas



Após a instalação da placa, o microcontrolador deve ser programado. Para fazer isso, você pode usar um adaptador de depuração SiLabs C2 da marca / clone ou J-Link v10 + (com suporte para EFM8) ou um bootloader atualizado na fábrica (revisão Rev-B) ou, finalmente, Arduino com um script apropriado. Para verificar e depurar mensagens MIDI, o MIDI-OX é uma grande ajuda .



MIDI-OX

Fig.10 Interface do programa MIDI-OX.



Se você trabalha com o Cubase, deve instalar os drivers Asio, porque ao usar DirectSound e DirectInput, há um atraso entre pressionar uma tecla e tocar uma nota. A latência não está relacionada ao hardware e é um recurso da implementação do sistema operacional. Em geral, o dispositivo executa suas funções perfeitamente com o instrumento Casio CDP-100.



Configuração de Cubase MIDI

Fig. 11 Interface do Cubase 5.



O firmware experimental gerou o maior fluxo possível de notas e outros comandos MIDI. A cacofonia foi terrível, mas tudo funcionou como planejado. E com o MuseScore 3.2 você pode gravar e reproduzir arquivos intermediários.



Última piada
1990-. . — . . :

— , !

— , ! — !

— ! !

— … . !

— ! !

. . , , , … . , . :

— , , ?


Resultados do trabalho



O adaptador funciona! Parece que consegui fazer um bom conversor de MIDI para USB. Para o meu aparelho, usei um case, algumas peças e cabos de um adaptador chinês. O conector mini-USB acabou ficando fundo no gabinete e tive que refazer o cabo USB e trabalhar com um arquivo. Os LEDs, embora em ângulo, se encaixam perfeitamente nos orifícios. A placa precisa ser modificada para o caso chinês.



Cabo mini USB

Figura: 12. Plugue mini-USB compacto desmontado.



A decisão de usar o microcontrolador EFM8UB20 de 8 bits pode parecer controversa para alguns. Existem outras opções e controladores, é claro. Uma alternativa é escolher uma solução puramente de hardware no conversor CH345 e fazer o dispositivo de acordo com o circuito de referência recomendado pelos chineses. Mas minha versão é universal, tk. permite que você altere o firmware, adicione a funcionalidade desejada ou corrija os erros encontrados. No final, ganhei conhecimento, experiência e satisfação moral com o projeto finalizado. E, finalmente, terminei meu artigo e você terminou de lê-lo.



Links Úteis





Obrigado pela atenção.



All Articles