<imagem com placa e fones de ouvido>
Nível ainda mais baixo (avr-vusb): habr.com/ru/post/460815
USB em registros: STM32L1 / STM32F1
USB em registros: endpoint em massa usando o exemplo de armazenamento
em massa USB em registros: ponto de extremidade de interrupção no exemplo HID
Hoje, vamos olhar para o último tipo de ponto de extremidade, isócrono. Ele é projetado para transferir dados críticos para o tempo de entrega, mas não garante seu sucesso. O exemplo mais clássico são os dispositivos de áudio: alto-falantes, microfones.
Estranhamente, esse tipo de ponto final acabou sendo o que mais bombeia o cérebro (e isso depois de tudo que vi com stm'kami!). No entanto, hoje faremos um dispositivo de áudio e ao mesmo tempo terminaremos ligeiramente o núcleo da biblioteca USB. Como de costume, os códigos-fonte estão disponíveis:
github.com/COKPOWEHEU/usb/tree/main/4.Audio_L1
github.com/COKPOWEHEU/usb/tree/main/4.Audio_F1
Refinamento do kernel
É necessário refinar o kernel porque STM só pode ter pontos isócronos com buffer duplo, ou seja, grosso modo, é impossível tornar 0x01 isócrono e 0x81 controle. Ou seja, é claro que é possível escrever isso no descritor USB, mas isso não mudará o interior do controlador, e o endereço real do ponto simplesmente será diferente daquele visível do lado de fora. O que, é claro, aumentará o risco de erros, para não sermos pervertidos nessa direção.
Deve-se notar que o buffer duplo ocorre não apenas para pontos isócronos, mas também para bulk, e ele liga e funciona de forma diferente. Se os pontos isócronos habilitarem o buffer automaticamente, simplesmente porque eles não podem fazer de outra forma, para a configuração em massa correspondente, você deve usar o bit USB_EP_KIND especial, que deve ser definido junto com a configuração do tipo de ponto real.
Por si só, buffering significa que se antes um ponto correspondia a um buffer de transmissão e outro de recepção, agora ambos os buffers funcionarão tanto para transmissão quanto para recepção, e só funcionarão juntos. Como resultado, a configuração de um ponto de buffer é muito diferente do usual, porque você precisa configurar não um buffer, mas dois. Portanto, não esculpiremos condições desnecessárias na inicialização usual, mas criaremos uma função separada usb_ep_init_double () com base nela.
A recepção e a transmissão de pacotes não diferem tanto, embora tenha levado muito mais tempo primeiro para tentar entender como deveria funcionar de acordo com a lógica ST, depois ajustar o feitiço da Internet para fazê-lo funcionar. Como mencionado anteriormente, se para um ponto comum dois buffers são independentes e diferem na direção da troca, para um com buffer eles são iguais e diferem apenas no deslocamento. Portanto, vamos mudar um pouco as funções usb_ep_write e usb_ep_read para que aceitem não um número de ponto, mas um número de deslocamento. Ou seja, se antes essas funções assumiam a existência de oito pontos duplos, agora - 16 pontos únicos. Assim, o número da nova “meia-linha” para escrita é apenas o número do usual, multiplicado por dois, e para usb_ep_read deve-se também somar um (veja a alocação de buffers no PMA). Na realidade,isso é feito pelas funções inline usb_ep_write e usb_ep_read para pontos regulares. Mas vamos dar uma olhada mais de perto na lógica do buffer.
De acordo com a documentação, um buffer de tal ponto está disponível para hardware, o segundo para software. Em seguida, eles mudam e novamente não interferem um com o outro. Para o ponto OUT, o sinalizador no lado do hardware é o bit USB_EP_DTOG_RX, que precisa ser lido para entender qual dos buffers acabou de escrever e de onde o software pode ler, respectivamente. Quando ele lê seu buffer, você precisa empurrar o bit USB_EP_DTOG_TX, que na verdade muda os buffers. Não tenho certeza se é isso que significa, mas pelo menos funciona.
Uma situação simétrica deveria ser com pontos IN. Mas, na prática, descobriu-se que você precisa verificar e puxar USB_EP_DTOG_RX. Por que não TX ainda não entendi ... Obrigado ao usuário kuzulis pelo link para github.com/dmitrystu/libusb_stm32/edit/master/src/usbd_stm32f103_devfs.c
Devido à função inline, nenhuma sobrecarga especial foi adicionada, além da inicialização. Mas você pode, se desejar, jogá-lo fora com os sinalizadores do vinculador. Ou você não precisa jogá-lo fora: ele não ocupa muito espaço e é chamado apenas durante a inicialização. Este não é um HAL para você, onde as funções não são apenas pesadas, mas também ligam umas para as outras o tempo todo.
Como resultado, os terminais aprenderam a trabalhar no modo de buffer ... se você não respirar muito com eles.
Para o usuário, a diferença é pequena: em vez de usb_ep_init, use usb_ep_init_double, e em vez de usb_ep_write e usb_ep_read, use usb_ep_write_double e usb_ep_read_double, respectivamente.
Dispositivo AudioDevice
E agora, quando descobrimos o rake técnico, vamos passar para a coisa mais interessante - configurar um dispositivo de áudio.
De acordo com o padrão USB, um dispositivo de áudio é um conjunto de entidades conectadas entre si em uma determinada topologia, por onde passa o sinal de áudio. Cada entidade tem seu próprio número exclusivo (bTerminalID, também conhecido como UnitID), pelo qual outras entidades ou terminais podem se conectar a ela, o host também o usa se quiser alterar alguns parâmetros. E ele é considerado a única saída desta entidade. Mas pode não haver nenhuma entrada (se for um terminal de entrada), ou pode haver mais de um (bSourceID). Na verdade, ao escrever os números das entidades das quais a atual recebe um sinal de áudio para o array bSourceID, descrevemos toda a topologia, que como resultado pode ser muito rápida. Por exemplo, darei a topologia de uma placa de som USB adquirida (os números mostram bTerminalID / UnitID):
lsusb e sua descriptografia
Bus 001 Device 014: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller
#
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0d8c C-Media Electronics, Inc.
idProduct 0x013c CM108 Audio Controller
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerial 0
bNumConfigurations 1
#
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x00fd
bNumInterfaces 4 #
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
# 0 -
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 1 Control Device
bInterfaceProtocol 0
iInterface 0
AudioControl Interface Descriptor:
bLength 10
bDescriptorType 36
bDescriptorSubtype 1 (HEADER)
bcdADC 1.00
wTotalLength 0x0064
bInCollection 2 # ! (2)
baInterfaceNr(0) 1 #
baInterfaceNr(1) 2 #
##### #####
# 1 InputTerminal (USB, )
AudioControl Interface Descriptor:
bLength 12
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 1 #
wTerminalType 0x0101 USB Streaming
bAssocTerminal 0
bNrChannels 2 #
wChannelConfig 0x0003 # -
Left Front (L)
Right Front (R)
iChannelNames 0
iTerminal 0
# 2 InputTerminal ()
AudioControl Interface Descriptor:
bLength 12
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 2
wTerminalType 0x0201 Microphone
bAssocTerminal 0
bNrChannels 1
wChannelConfig 0x0001
Left Front (L)
iChannelNames 0
iTerminal 0
# 6 OutputTerminal (), 9
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (OUTPUT_TERMINAL)
bTerminalID 6
wTerminalType 0x0301 Speaker
bAssocTerminal 0
bSourceID 9 #
iTerminal 0
# 7 OutputTerminal (USB), 8
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (OUTPUT_TERMINAL)
bTerminalID 7
wTerminalType 0x0101 USB Streaming
bAssocTerminal 0
bSourceID 8
iTerminal 0
# 8 Selector, 10
AudioControl Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 5 (SELECTOR_UNIT)
bUnitID 8
bNrInPins 1 #
baSourceID(0) 10 #
iSelector 0
# 9 Feature, 15
AudioControl Interface Descriptor:
bLength 10
bDescriptorType 36
bDescriptorSubtype 6 (FEATURE_UNIT)
bUnitID 9
bSourceID 15
bControlSize 1
bmaControls(0) 0x01
Mute Control
bmaControls(1) 0x02
Volume Control
bmaControls(2) 0x02
Volume Control
iFeature 0
# 10 Feature, 2
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 6 (FEATURE_UNIT)
bUnitID 10
bSourceID 2
bControlSize 1
bmaControls(0) 0x43
Mute Control
Volume Control
Automatic Gain Control
bmaControls(1) 0x00
iFeature 0
# 13 Feature, 2
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 6 (FEATURE_UNIT)
bUnitID 13
bSourceID 2
bControlSize 1
bmaControls(0) 0x03
Mute Control
Volume Control
bmaControls(1) 0x00
iFeature 0
# 15 Mixer, 1 13
AudioControl Interface Descriptor:
bLength 13
bDescriptorType 36
bDescriptorSubtype 4 (MIXER_UNIT)
bUnitID 15
bNrInPins 2 #
baSourceID(0) 1 #
baSourceID(1) 13
bNrChannels 2
wChannelConfig 0x0003
Left Front (L)
Right Front (R)
iChannelNames 0
bmControls(0) 0x00
iMixer 0
##### #####
# 1 () -
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
# 1 () -
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 1
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
AudioStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (AS_GENERAL)
bTerminalLink 1
bDelay 1 frames
wFormatTag 0x0001 PCM
AudioStreaming Interface Descriptor:
bLength 14
bDescriptorType 36
bDescriptorSubtype 2 (FORMAT_TYPE)
bFormatType 1 (FORMAT_TYPE_I)
bNrChannels 2
bSubframeSize 2
bBitResolution 16
bSamFreqType 2 Discrete
tSamFreq[ 0] 48000
tSamFreq[ 1] 44100
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 9
Transfer Type Isochronous
Synch Type Adaptive
Usage Type Data
wMaxPacketSize 0x00c8 1x 200 bytes
bInterval 1
bRefresh 0
bSynchAddress 0
AudioStreaming Endpoint Descriptor:
bLength 7
bDescriptorType 37
bDescriptorSubtype 1 (EP_GENERAL)
bmAttributes 0x01
Sampling Frequency
bLockDelayUnits 1 Milliseconds
wLockDelay 0x0001
# 2 () -
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
# 2 ()
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 1
bNumEndpoints 1
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
AudioStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (AS_GENERAL)
bTerminalLink 7
bDelay 1 frames
wFormatTag 0x0001 PCM
AudioStreaming Interface Descriptor:
bLength 14
bDescriptorType 36
bDescriptorSubtype 2 (FORMAT_TYPE)
bFormatType 1 (FORMAT_TYPE_I)
bNrChannels 1
bSubframeSize 2
bBitResolution 16
bSamFreqType 2 Discrete
tSamFreq[ 0] 48000
tSamFreq[ 1] 44100
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 9
Transfer Type Isochronous
Synch Type Adaptive
Usage Type Data
wMaxPacketSize 0x0064 1x 100 bytes
bInterval 1
bRefresh 0
bSynchAddress 0
AudioStreaming Endpoint Descriptor:
bLength 7
bDescriptorType 37
bDescriptorSubtype 1 (EP_GENERAL)
bmAttributes 0x01
Sampling Frequency
bLockDelayUnits 0 Undefined
wLockDelay 0x0000
##### #####
# 3 " " ( )
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 3
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.00
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 60
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x87 EP 7 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 2
Vamos fazer algo mais simples (peguei o branco a partir daqui ):
Aqui você pode ver dois ramos de propagação de sinal independentes: ou de USB por um "recurso" para um "alto-falante" ou de um "microfone" por outro "recurso ”Para USB. O microfone e o alto-falante não são apenas colocados entre aspas: eles não estão na minha placa de depuração, então, em vez do próprio som, usaremos botões e LEDs. Porém, nada de novo. "Recursos" no meu caso não fazem nada e são adicionados mais para a beleza.
Deve ser esclarecido de imediato que o sinal neste modelo é considerado composto por um ou mais canais lógicos. Ou seja, se, por exemplo, eu mudar um alto-falante mono para um estéreo, a topologia em si permanecerá inalterada, apenas o formato do sinal mudará.
Não investiguei profundamente as diferenças entre os tipos de "recursos" e outras entidades, mas não desdenharei recontar uma parte da documentação.
1. Terminal de entrada
Como o nome sugere, é através dele que o sinal de áudio entra no dispositivo de áudio. Pode ser USB, pode ser um microfone comum, um microfone tipo headset ou até mesmo um conjunto de microfones.
2. Terminal de saída
Também é bastante óbvio - aquele através do qual o som sai do nosso dispositivo. Pode ser o mesmo USB, pode ser um alto-falante, um fone de ouvido, um alto-falante no monitor, alto-falantes de várias frequências e vários outros dispositivos.
3. Unidade Misturadora
Ele pega vários sinais de entrada, amplifica cada um em uma quantidade predeterminada e adiciona o resultado ao canal de saída. Se desejar, você pode definir o ganho para zero, o que o reduzirá para a próxima entidade.
4. Unidade seletora
Recebe vários sinais de entrada e redireciona um deles para a saída.
5. Filtro (unidade de recurso)
Recebe um único sinal de entrada, altera os parâmetros de som (volume, tom, etc.) e o envia para a saída. Naturalmente, todos esses parâmetros são aplicados a todo o sinal da mesma maneira, sem a interação de canais lógicos dentro dele.
6. Unidade de processamento
Mas isso já permite que você manipule canais lógicos individuais dentro de cada entrada. Além disso, permite que você faça com que o número de canais lógicos na saída não seja igual ao número na entrada.
7. Unidade de extensão
Todo o conjunto de entidades fora do padrão, de modo que a fantasia doentia dos fabricantes de equipamentos era livre. Conseqüentemente, tanto o comportamento quanto as configurações dependerão dessa mesma fantasia.
Algumas entidades têm parâmetros como ganho ou número de canal, que podem ser influenciados pelo host usando consultas setFeature / getFeature no número da entidade. Mas aqui, para ser honesto, eu realmente não entendo como verificar isso. Provavelmente, você precisa de algum tipo de software especial, que eu não tenho. Bem, ok, de qualquer maneira eu entrei nisso apenas para verificar todos os tipos de pontos ... na minha cabeça ...
Rake no descritor
Ao contrário dos dispositivos USB anteriores, o descritor aqui é complexo, com várias camadas e tende a assustar o Windows e torná-lo BSOD. Como vimos acima, a topologia de um dispositivo autólogo pode ser bastante complexa e extensa. Uma interface inteira se destaca por sua descrição. Obviamente, ele não conterá terminais, mas conterá uma lista de descritores de entidade e descrições de a que suas entradas estão conectadas. Não vejo muito sentido aqui, é mais fácil olhar o código e a documentação. Vou apenas observar o rake principal: aqui é descrito quais interfaces com os pontos de extremidade correspondentes se referem especificamente a este dispositivo. Por exemplo, se você quiser mudar minha configuração e remover o alto-falante de lá, você não terá apenas que deletar metade das entidades (graças às macros, pelo menos não haverá problema em calcular o comprimento do descritor), mas também reduza o campo bInCollection para 1,em seguida, remova o número da interface extra do array bInterfaceNr que o segue.
Além disso, existem interfaces responsáveis pela troca de dados. No meu caso, a 1ª interface é responsável pelo microfone e a 2ª pelo alto-falante. Vale a pena prestar atenção aqui, em primeiro lugar, a duas variantes de cada uma dessas interfaces. Um com bAlternateSetting igual a 0, o segundo com 1. Eles diferem na presença de um ponto final. Ou seja, se nosso dispositivo não estiver em uso, o host simplesmente muda para aquela interface alternativa, que não está equipada com um ponto de extremidade, e não desperdiça mais largura de banda do barramento nele.
O segundo recurso das interfaces de dados é o formato do sinal de áudio. O descritor correspondente especifica o tipo de codificação, número de canais, resolução e taxa de amostragem (que é especificada por um número de 24 bits). Existem algumas opções de codificação, mas usaremos a mais simples - PCM. Na verdade, é apenas uma sequência de valores do valor instantâneo do sinal sem nenhuma codificação, e o valor é considerado um número inteiro com sinal . A resolução do sinal é definida em dois lugares (não está claro o porquê): o campo bSubFrameSize especifica o número de bytes e bBitResolution especifica o número de bits... Provavelmente, pode ser apontado que a faixa de nossa placa de som não vai até a faixa completa do tipo de dados, digamos int16_t e tem apenas 10 bits.
E, finalmente, o descritor do terminal real. Também difere ligeiramente dos habituais, pois disponibiliza, em primeiro lugar, várias opções de sincronização e, em segundo lugar, o número da entidade à qual este ponto está associado (bTerminalLink) . As opções de sincronização são escritas em bits de alta ordem diretamente no tipo de endpoint (é por isso que o ponto isócrono foi movido para a ramificação padrão na função de inicialização), mas eu não lidei com seus detalhes, então não posso te dizer alguma coisa interessante. Em vez de sincronização, usaremos um temporizador de controlador regular, que gerará interrupções aproximadamente na frequência desejada.
Ah, sim, quase esqueci de mencionar outro monte de BSODs ao testar os descritores errados. Deixe-me lembrá-lo novamente: o número de interfaces de dados deve corresponder ao número de bInCollection, e seus números devem corresponder ao array seguinte!
Texto oculto
, , . --.
A lógica do dispositivo
Como já disse, para testes não faz sentido colocar componentes articulados na placa de depuração, portanto, todos os testes serão realizados com o que já está instalado - botões e LEDs. No entanto, neste caso, isso não constitui um problema: o "microfone" pode simplesmente gerar uma sinusóide com uma frequência de, digamos, 1 kHz, e o "alto-falante" liga o LED quando o valor do limiar de som é excedido (digamos , acima de 10.000: com os 16 bits especificados de resolução, que corresponde ao intervalo -32768 ... +32767, isso é cerca de um terço).
Mas com o teste, surgiu um pequeno problema: não encontrei uma maneira fácil de redirecionar o sinal do microfone para o stdin de algum programa. Parece que antes isso era feito simplesmente lendo / dev / dsp, mas agora algo está quebrado. Porém, nada crítico, pois existem todos os tipos de bibliotecas para interação com multimídia - SDL, SFLM e outras. Na verdade em SFML eu escrevi um utilitário simples para ler de um microfone e, se necessário, visualizar o sinal.
Vou prestar atenção especial às limitações do nosso dispositivo de áudio: até onde eu entendo, uma solicitação IN isócrona é enviada uma vez por milissegundo (mas pode haver muitos OUTs), o que limita a taxa de amostragem. Digamos que o tamanho do endpoint seja de 64 bytes (levando em consideração o buffer, ele ocupa 128 bytes na memória, mas o host não sabe disso), a resolução é de 16 bits, ou seja, 32 amostras podem ser enviadas por vez . Dado um intervalo de 1 ms, obtemos um limite teórico de 32 kHz para um canal. A maneira mais fácil de contornar isso é aumentar o tamanho do ponto de extremidade. Mas aqui devemos lembrar que o tamanho do buffer PMA total é de apenas 512 bytes. Sem a tabela de distribuição de pontos, sem ep0, obtemos no máximo 440 bytes, ou seja, 220 bytes por ponto único, levando em consideração o buffer. E esse é o limite teórico.
Mas o fato de o host poder enviar várias solicitações OUT em um quadro sugere que o dispositivo pode fazer o mesmo. Resta entender como. Talvez isso seja resolvido por uma configuração de sincronização competente. Mas para mim esta questão já não me interessa: os pontos isócronos funcionam, os pontos armazenados funcionam, o dispositivo de áudio funciona - a tarefa está concluída.
Conclusão (comum para o ciclo)
Bem, conhecemos o dispositivo USB nos controladores STM32F103 e STM32L151 (e outros com uma implementação semelhante), ficamos surpresos com a lógica de algumas soluções arquitetônicas (fiquei especialmente impressionado com o registro USB_EPnR, no entanto, o buffer duplo também não está atrasado atrás), examinou todos os tipos de endpoints e os verificou, criando os dispositivos apropriados. Portanto, podemos dizer que esta série de artigos chegou a uma conclusão lógica. Embora isso, é claro, não signifique que vou abandonar os controladores ou USB: nos planos distantes, ainda tenho que lidar com dispositivos compostos (até agora parece fácil, mas pontos isócrons também não augura nada de bom) e USB em controladores de outras famílias.