USB en registros: punto final isócrono usando el ejemplo de un dispositivo de audio

imagen<imagen con placa y auriculares>

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

      
      









imagen



Vamos a hacer algo más simple (tomé el espacio en blanco de aquí ):



imagen



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.



All Articles