Nivel incluso más bajo (avr-vusb): habr.com/ru/post/460815
USB en registros: STM32L1 / STM32F1
USB en registros: extremo masivo usando el ejemplo de almacenamiento masivo
USB en registros: punto final de interrupción en el ejemplo de HID
Hoy, veamos el último tipo de punto final, isócrono. Está diseñado para transferir datos críticos para el tiempo de entrega, pero no garantiza su éxito. El ejemplo más clásico son los dispositivos de audio: altavoces, micrófonos.
Curiosamente, este tipo de punto final resultó ser el que más bombea el cerebro (¡y esto es después de todo lo que he visto con stm'kami!). Sin embargo, hoy haremos un dispositivo de audio y al mismo tiempo terminaremos ligeramente el núcleo de la biblioteca USB. Como de costumbre, los códigos fuente están disponibles:
github.com/COKPOWEHEU/usb/tree/main/4.Audio_L1
github.com/COKPOWEHEU/usb/tree/main/4.Audio_F1
Refinamiento de kernel
Es necesario refinar el kernel porque STM solo puede tener puntos isócronos con doble búfer, es decir, en términos generales, es imposible hacer que 0x01 sea isócrono y 0x81. Es decir, por supuesto, es posible escribir esto en el descriptor USB, pero esto no cambiará el interior del controlador, y la dirección real del punto simplemente diferirá de la visible desde el exterior. Lo cual, por supuesto, aumentará el riesgo de errores, por lo que no seremos pervertidos en esta dirección.
Cabe señalar que el almacenamiento en búfer doble se produce no solo para puntos isócronos, sino también para el volumen, y se enciende y funciona de manera diferente. Si los puntos isócronos habilitan el almacenamiento en búfer automáticamente, simplemente porque no pueden hacer lo contrario, entonces, para la configuración de volumen correspondiente, debe usar el bit especial USB_EP_KIND, que debe establecerse junto con la configuración del tipo de punto real.
Por sí mismo, el almacenamiento en búfer significa que si antes un punto correspondía a un búfer para transmisión y otro para recepción, ahora ambos búferes funcionarán para transmisión o recepción, y solo funcionarán juntos. Como resultado, la configuración de un punto en búfer es muy diferente del habitual, porque necesita configurar no un búfer, sino dos. Por lo tanto, no esculpiremos condiciones innecesarias en la inicialización habitual, sino que crearemos una función separada usb_ep_init_double () basada en ella.
La recepción y transmisión de paquetes no difiere tanto, aunque tomó mucho más tiempo primero tratar de entender cómo debería funcionar de acuerdo con la lógica ST, luego ajustar el hechizo de Internet para que funcione. Como se mencionó anteriormente, si para un punto ordinario dos búferes son independientes y difieren en la dirección de intercambio, entonces para uno con búfer son iguales y solo difieren en el desplazamiento. Así que cambiemos un poco las funciones usb_ep_write y usb_ep_read para que no acepten un número de punto, sino un número de compensación. Es decir, si antes estas funciones asumían la existencia de ocho puntos dobles, ahora - 16 simples. En consecuencia, el número de la nueva "media línea" para escribir es solo el número de lo habitual, multiplicado por dos, y para usb_ep_read también se debe agregar uno (ver la asignación de búferes en el PMA). De hecho,esto se hace mediante las funciones en línea usb_ep_write y usb_ep_read para puntos regulares. Pero echemos un vistazo más de cerca a la lógica almacenada en búfer.
Según la documentación, un búfer de dicho punto está disponible para hardware y el segundo para software. Luego cambian y nuevamente no interfieren entre sí. Para el punto OUT, el indicador en el lado del hardware es el bit USB_EP_DTOG_RX, que debe leerse para comprender cuál de los búferes acaba de terminar de escribir y desde dónde puede leer el software, respectivamente. Cuando lee su búfer, necesita sacudir el bit USB_EP_DTOG_TX, que en realidad cambia los búferes. No estoy seguro de si esto es lo que se quiere decir, pero al menos funciona.
Una situación simétrica debería haber sido con puntos IN. Pero en la práctica, resultó que necesita verificar y extraer USB_EP_DTOG_RX. ¿Por qué no TX todavía no entiendo ... Gracias al usuario kuzulis por el enlace a github.com/dmitrystu/libusb_stm32/edit/master/src/usbd_stm32f103_devfs.c
Debido a la función en línea, no se agregó ninguna sobrecarga especial, aparte de la inicialización. Pero puede, si lo desea, tirarlo con las banderas del enlazador. O no es necesario que lo deseche: no ocupa tanto espacio y solo se llama durante la inicialización. Esto no es un HAL para usted, donde las funciones no solo son pesadas, sino que también se llaman entre sí todo el tiempo.
Como resultado, los endpoints han aprendido a funcionar en modo de búfer ... si no les da demasiada fuerza.
Para el usuario, la diferencia es pequeña: en lugar de usb_ep_init, use usb_ep_init_double, y en lugar de usb_ep_write y usb_ep_read, use usb_ep_write_double y usb_ep_read_double, respectivamente.
Dispositivo de dispositivo de audio
Y ahora, cuando descubrimos el rastrillo técnico, pasemos a lo más interesante: configurar un dispositivo de audio.
Según el estándar USB, un dispositivo de audio es un conjunto de entidades conectadas entre sí en una determinada topología, a través de las cuales pasa la señal de audio. Cada entidad tiene su propio número único (bTerminalID, también conocido como UnitID), mediante el cual otras entidades o puntos finales pueden conectarse a ella, el host también lo usa si desea cambiar algunos parámetros. Y se le considera la única salida de esta entidad. Pero puede que no haya ninguna entrada (si es un terminal de entrada), o puede haber más de una (bSourceID). En realidad, al escribir los números de las entidades de las que la actual recibe una señal de audio en la matriz bSourceID, describimos la topología completa, que como resultado puede resultar muy rápido. Por ejemplo, daré la topología de una tarjeta de sonido USB comprada (los números muestran bTerminalID / UnitID):
lsusb y su descifrado
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 a hacer algo más simple (tomé el espacio en blanco de aquí ):
Aquí puede ver dos ramas de propagación de señal independientes: desde USB a través de una "función" a un "altavoz", o desde un "micrófono" a través de otra "función". ”A USB. El micrófono y el altavoz no solo están entre comillas: no están en mi placa de depuración, por lo que en lugar del sonido en sí, usaremos botones y LED. Sin embargo, nada nuevo. Las "características" en mi caso no hacen nada y se agregan más por la belleza.
Debe aclararse de inmediato que se considera que la señal en este modelo está compuesta por uno o más canales lógicos. Es decir, si, por ejemplo, cambio un altavoz mono a uno estéreo, la topología en sí no cambiará, solo cambiará el formato de la señal.
No profundicé en las diferencias entre los tipos de "características" y otras entidades, pero no desdeñaré volver a contar una pieza de documentación.
1. Terminal de entrada
Como sugiere el nombre, es a través de él que la señal de audio ingresa al dispositivo de audio. Puede ser USB, puede ser un micrófono normal, un micrófono de diadema, incluso un conjunto de micrófonos.
2. Terminal de salida
También es bastante obvio - aquel a través del cual el sonido sale de nuestro dispositivo. Puede ser el mismo USB, puede ser un parlante, un auricular, un parlante en el monitor, parlantes de varias frecuencias y muchos otros dispositivos.
3. Unidad mezcladora
Toma varias señales de entrada, amplifica cada una en una cantidad predeterminada y agrega el resultado al canal de salida. Si lo desea, puede establecer la ganancia en cero, lo que la reducirá a la siguiente entidad.
4. Unidad selectora
Toma múltiples señales de entrada y redirige una de ellas a la salida.
5. Filtro (Unidad de
funciones ) Toma una sola señal de entrada, cambia los parámetros de sonido (volumen, tono, etc.) y la envía a la salida. Naturalmente, todos estos parámetros se aplican a toda la señal de la misma forma, sin la interacción de canales lógicos dentro de ella.
6. Unidad de procesamiento
Pero esto ya le permite manipular canales lógicos individuales dentro de cada entrada. Además, le permite hacer que el número de canales lógicos en la salida no sea igual al número en la entrada.
7. Unidad de extensión
Todo el conjunto de entidades no estándar, por lo que la fantasía enfermiza de los fabricantes de equipos era gratuita. En consecuencia, tanto el comportamiento como el entorno dependerán de esta misma fantasía.
Algunas entidades tienen parámetros como ganancia o número de canal, que pueden ser influenciados por el anfitrión usando consultas setFeature / getFeature sobre el número de entidad. Pero aquí, para ser honesto, realmente no entiendo cómo verificarlo. Probablemente, necesite algún tipo de software especial, que yo no tengo. Bueno, está bien, de todos modos me metí en eso solo para revisar todo tipo de puntos ... en mi cabeza ...
Rastrillo en el descriptor
A diferencia de los dispositivos USB anteriores, el descriptor aquí es complejo, de varias capas y tiende a asustar a Windows en BSOD. Como vimos anteriormente, la topología de un dispositivo autólogo puede ser bastante compleja y extendida. Una interfaz completa destaca por su descripción. Obviamente, no contendrá puntos finales, pero contendrá una lista de descriptores de entidad y descripciones de a qué están conectadas sus entradas. No veo mucho sentido aquí, es más fácil mirar el código y la documentación. Solo señalaré el rastrillo principal: aquí se describe qué interfaces con los puntos finales correspondientes se refieren específicamente a este dispositivo. Por ejemplo, si quieres cambiar mi configuración y quitar el altavoz de allí, no solo tendrás que eliminar la mitad de las entidades (gracias a las macros, al menos no habrá problema con calcular la longitud del descriptor), sino también reduzca el campo bInCollection a 1,luego elimine el número de la interfaz adicional de la matriz bInterfaceNr que le sigue.
Además, existen interfaces responsables del intercambio de datos. En mi caso, la primera interfaz es responsable del micrófono y la segunda del altavoz. Vale la pena prestar atención aquí, en primer lugar, a dos variantes de cada una de estas interfaces. Uno con bAlternateSetting igual a 0, el segundo con 1. Se diferencian en la presencia de un punto final. Es decir, si nuestro dispositivo no está actualmente en uso, el host simplemente cambia a esa interfaz alternativa, que no está equipada con un punto final, y ya no desperdicia el ancho de banda del bus en él.
La segunda característica de las interfaces de datos es el formato de la señal de audio. El descriptor correspondiente especifica el tipo de codificación, el número de canales, la resolución y la frecuencia de muestreo (que se especifica mediante un número de 24 bits). Hay bastantes opciones de codificación, pero usaremos la más simple: PCM. De hecho, es solo una secuencia de valores del valor instantáneo de la señal sin ninguna codificación, y el valor se considera un entero con signo . La resolución de la señal se establece en dos lugares (no está claro por qué): el campo bSubFrameSize especifica el número de bytes y bBitResolution especifica el número de bits... Probablemente se pueda señalar que el rango de nuestra tarjeta de sonido no llega al rango completo del tipo de datos, digamos int16_t y es solo de 10 bits.
Y finalmente, el descriptor del punto final real. También se diferencia ligeramente de las habituales, ya que proporciona, en primer lugar, varias opciones de sincronización, y en segundo lugar, el número de la entidad a la que está asociado este punto (bTerminalLink) . Las opciones de sincronización están escritas en bits de orden superior directamente en el tipo de punto final (por lo que el punto isócrono se ha movido a la rama predeterminada en la función de inicialización), pero no me he ocupado de sus detalles, así que no puedo decirte cualquier cosa interesante. En lugar de sincronización, usaremos un temporizador de controlador regular, que generará interrupciones aproximadamente a la frecuencia deseada.
Ah, sí, casi me olvido de mencionar otro grupo de BSOD al probar los descriptores incorrectos. Permítame recordarle nuevamente: el número de interfaces de datos debe corresponder al número de bInCollection, y sus números deben corresponder a la matriz que le sigue.
Texto oculto
, , . --.
La lógica del dispositivo
Como ya dije, para las pruebas no tiene sentido colocar componentes con bisagras en la placa de depuración, por lo que todas las pruebas se realizarán con lo que ya se ha instalado: botones y LED. Sin embargo, en este caso, esto no constituye un problema: el "micrófono" puede simplemente generar una sinusoide con una frecuencia de, digamos, 1 kHz, y el "altavoz" enciende el LED cuando se excede el valor del umbral de sonido (digamos , por encima de 10,000: con los 16 bits especificados de resolución, que corresponde al rango -32768 ... +32767, esto es aproximadamente un tercio).
Pero con las pruebas, surgió un pequeño problema: no encontré una manera fácil de redirigir la señal del micrófono al stdin de algún programa. Parece que antes esto se hacía simplemente leyendo / dev / dsp, pero ahora algo está roto. Sin embargo, nada crítico, porque hay todo tipo de bibliotecas para la interacción con multimedia: SDL, SFLM y otras. De hecho, en SFML escribí una sencilla utilidad para leer desde un micrófono y, si es necesario, visualizar la señal.
Prestaré especial atención a las limitaciones de nuestro dispositivo de audio: hasta donde tengo entendido, una solicitud de ENTRADA isócrona se envía una vez por milisegundo (pero puede haber muchas SALIDAS), lo que limita la frecuencia de muestreo. Digamos que el tamaño del punto final es de 64 bytes (teniendo en cuenta el búfer, toma 128 bytes en la memoria, pero el host no lo sabe), la resolución es de 16 bits, es decir, se pueden enviar 32 muestras a la vez. . Dado un intervalo de 1 ms, obtenemos un límite teórico de 32 kHz para un canal. La forma más sencilla de evitar esto es aumentar el tamaño del punto final. Pero aquí debemos recordar que el tamaño del búfer PMA total es de solo 512 bytes. Menos la tabla de distribución de puntos, menos ep0, obtenemos un máximo de 440 bytes, es decir, 220 bytes por punto único, teniendo en cuenta el almacenamiento en búfer. Y este es el límite teórico.
Pero el hecho de que el host pueda enviar varias solicitudes OUT en una trama sugiere que el dispositivo puede hacer lo mismo. Queda por entender cómo. Quizás esto se resuelva mediante una configuración de sincronización competente. Pero para mí esta pregunta ya no es de interés: los puntos isócronos funcionan, los puntos en búfer funcionan, el dispositivo de audio funciona, la tarea está completa.
Conclusión (común para el ciclo)
Bueno, nos familiarizamos con el dispositivo USB en los controladores STM32F103 y STM32L151 (y otros con una implementación similar), nos sorprendió la lógica de algunas soluciones arquitectónicas (me impresionó especialmente el registro USB_EPnR, sin embargo, el doble búfer tampoco se está quedando atrás detrás), examinó todos los tipos de puntos finales y los verificó, construyendo los dispositivos apropiados. Entonces podemos decir que esta serie de artículos ha llegado a una conclusión lógica. Aunque esto, por supuesto, no significa que voy a abandonar los controladores o USB: en los planes lejanos, todavía tengo que lidiar con dispositivos compuestos (hasta ahora parece fácil, pero los puntos isócronos tampoco auguraban nada bueno) y USB en los controladores. de otras familias.