- Resume de principio a fin el flujo necesario para crear un dispositivo USB por primera vez, desde el cableado físico hasta el PCB, las velocidades de USB 2.0, las capas de protocolo y una práctica con STM32
- La conexión básica de USB 2.0 usa cuatro líneas: +5V, GND, D+ y D-, y
D+ y D- forman un par diferencial que transmite 1 bit en conjunto
- En un PCB son importantes el ajuste de longitud del par diferencial, su cercanía y la impedancia, pero un prototipo full speed de 12 Mbit/s puede funcionar con bastante tolerancia
- La práctica configura
PA12 como USB_DP y PA11 como USB_DM en una NUCLEO-F103RB, crea un dispositivo USB serial reconocido como puerto COM virtual CDC y enciende un LED al ingresar 1
- El enfoque con STM32CubeIDE implica generación de código basada en UI y acoplamiento con boilerplate, mientras que una implementación de dispositivos USB basada en Linux puede ofrecer una API más estandarizada y una estructura más separada
Alcance al implementar USB por primera vez
- El objetivo es construir de punta a punta, en su forma más simple, un dispositivo USB que sea reconocido al conectarlo a una computadora
- Aquí, dispositivo se refiere a un periférico que amplía las funciones de la computadora, y a la computadora se le llama host
- El alcance incluye desde la conexión física entre el dispositivo USB y el host hasta una aplicación sencilla en el host para interactuar con el dispositivo USB
- Más que una explicación autoritativa de la especificación, se parece a un proyecto E2E muy simple de creación de un dispositivo USB y a un índice de materiales de referencia
Conceptos básicos de USB
- USB es un estándar industrial que permite el intercambio de datos y la alimentación eléctrica entre varios dispositivos electrónicos
- USB es un bus serial, así que los bits no se transmiten en paralelo sino uno por uno sobre el bus
- Los buses modernos son principalmente seriales, y USB puede entenderse como un mecanismo de intercambio serial de bits entre host y device
- USB incluye no solo la especificación física para conectar dos equipos y enviar bits, sino también protocolos de comunicación para transferir datos y energía
- La explicación se enfoca en USB 2.0
Cableado USB y par diferencial
- Una conexión típica de USB 2.0 se explica con un mínimo de cuatro líneas
+5 V: línea por la que el host alimenta al device
D-, D+: un par diferencial en el que ambas líneas trabajan juntas para transmitir 1 bit
GND: tierra
- Algunas conexiones pueden incluir pines adicionales como
ID, pero la práctica solo usa estas cuatro líneas
-
Nota sobre USB-C
- USB-C también usa pares diferenciales internamente, pero difiere de la explicación general de USB 2.0 en aspectos como que debe funcionar sin importar la orientación del conector
- Usar un conector USB-C no determina por sí mismo la velocidad ni la versión de USB
- Incluso un dispositivo con USB-C puede ser USB 2.0, o puede ser un dispositivo más moderno USB 3.0
- En la práctica posterior no se profundiza más en USB-C
-
Cómo el par diferencial reduce el ruido
- Una sola línea expresa el valor de un bit comparando su voltaje con
GND, pero en conexiones cableadas largas el ruido puede afectar más
- Un par diferencial usa dos líneas: una lleva
V y la otra -V
- El lado receptor observa la diferencia de voltaje entre ambas líneas
- Si se simplifica suponiendo que el mismo ruido
Vn se suma a ambas, (V + Vn) - (-V + Vn) = 2V, por lo que el ruido se cancela
- Este modelo es una explicación muy simplificada; para una comprensión más profunda del par diferencial se puede consultar el video de Zach Peterson en el canal de Altium en YouTube
- Video 15
Manejo de USB en PCB
- Si se usa una placa de desarrollo en lugar de fabricar el hardware directamente, se puede saltar la sección de PCB, pero los principios básicos al colocar un conector USB en un PCB siguen siendo útiles
- Las piezas de biblioteca para conectores USB tienen los pines mencionados antes, y el par diferencial debe rutearse hacia los pines adyacentes correspondientes del microcontrolador o SoC
- Hay tres principios básicos para rutear un par diferencial
- Igualar la longitud de la traza que va de
D+ al pin plus del chip y la de la otra traza
- Colocar ambas trazas muy cerca entre sí
- Tener en cuenta la impedancia adecuada para la señal
- Como se busca que ambas líneas atraviesen un entorno casi idéntico, en el modelo simplificado anterior se puede asumir que el ruido en ambas es igual
- El cálculo de impedancia puede resolverse introduciendo en la calculadora del fabricante la impedancia objetivo, la distancia al plano de tierra, la separación entre el par diferencial y otros parámetros para obtener el ancho de traza necesario
- Se incluyen como referencia videos de Zach Peterson sobre pares diferenciales y ruteo USB
Velocidades de USB 2.0
- Que algo sea USB 2.0 no significa que la velocidad esté fijada a un solo valor
- USB 2.0 puede tener, por ejemplo, dos niveles de velocidad
- full speed: 12 Mbit/s
- high speed: 480 Mbit/s
- El device y el host deben decidir qué velocidad usar al conectarse
- Para alcanzar high speed de forma estable en un PCB, es importante ajustar la impedancia
- En un prototipo básico, 12 Mbit/s full speed puede ser suficiente y, si las trazas del conector USB al chip son cortas, puede haber bastante tolerancia en aspectos como el ancho de traza
Protocolo y capas de software
- USB no solo involucra hardware, sino también capas de software tanto del lado del host como del device
- Se incluye como material recomendado un video de unos 45 minutos que explica USB desde la perspectiva de Linux
- Ese video cubre USB frames, endpoint, configuration y la manera en que un solo device puede realizar varias funciones USB
- Es importante pensar USB como una red de dispositivos
- Como sería poco realista que el sistema operativo del host implementara por separado drivers para cada dispositivo USB, el sistema operativo reconoce varias clases de dispositivos USB
- mass storage device
- serial device
- y muchas otras clases
- El dispositivo de la práctica funciona como un serial port device desde la perspectiva del host
Crear un dispositivo USB serial port con STM32
- El dispositivo de la práctica es un dispositivo USB simple que enciende un LED al recibir una solicitud del host
- El host reconoce este dispositivo como un serial port device
- En términos generales, hay dos formas de implementarlo
- usar un microcontrolador con soporte USB
- usar un SoC capaz de ejecutar Linux para que el kernel haga gran parte del trabajo
- La práctica usa el enfoque con microcontrolador por simplicidad
-
Placa NUCLEO-F103RB
- La placa de desarrollo usada es la NUCLEO-F103RB
- En Estados Unidos puede comprarse por poco más de 10 dólares en sitios como Digikey
- Esta placa tiene una estructura con dos secciones unidas
- La parte pequeña es la zona del programmer y la parte grande es la zona del MCU principal que realmente se programa
- La conexión USB predeterminada de la placa no va al MCU principal, sino al MCU del lado del programmer
- Este programmer usa el protocolo ST-LINK para intercambiar mensajes USB con la computadora y luego programar el MCU principal
- Si se construyera un PCB propio basado en STM32, se podría dejar solo un chip en la placa, programarlo por USB y luego usar ese mismo puerto USB para la lógica del dispositivo
- Se incluyen videos de Phil’s Lab como referencia sobre PCB basados en STM32 y programación por USB
- Video 19
- Video 20
Cableado del puerto USB real
- Como la conexión USB predeterminada de la placa Nucleo no está conectada al MCU principal, hay que conectar directamente el puerto USB del MCU principal al host
- Después de cargar el programa, se desconecta el programmer ST-LINK de la computadora y se alimenta el MCU principal por medio del puerto USB real
- Para escribir el software se usa STM32CubeIDE y para cargarlo a la placa se usa STM32CubeProgrammer
-
Pines y configuración necesarios
- Para crear un dispositivo USB 2.0 se necesitan cuatro líneas
- alimentación de 5V proveniente del host
GND
D+
D-
- En la NUCLEO-F103RB, para alimentar desde el puerto USB del lado del MCU principal, hay que cambiar el jumper
JP5 para que la placa reciba 5 V externos
- En STM32CubeIDE se configuran los pines así
PA12 → USB_DP
PA11 → USB_DM
- El chip de esta placa requiere una resistencia pull-up externa de 1.5 kΩ para la conexión USB
- La resistencia hace pull-up de
PA12 a 3.3V
- El circuito se armó en un breadboard
- Para conectar pines individuales a una MacBook Pro se usó un USB breakout cable de Amazon; se retiró el terminal block para exponer los pines USB y luego se conectaron con jumper wires a la placa Nucleo y al breadboard
- Aunque no se respetaron estrictamente el ajuste de longitud del par diferencial ni el control de impedancia, en esta práctica sí fue posible una conexión de 12 Mbit/s
Escritura del software USB CDC
- Al configurar los pines USB en CubeIDE aparece una alerta relacionada con la configuración del clock, y se puede elegir la opción para que CubeIDE lo resuelva automáticamente
- En
Pinout & Configuration, bajo Middleware and Software Packs, se configura USB_DEVICE
- El modo importante en la práctica es el siguiente
Communication Device Class (Virtual Port Com)
- Con esta configuración, la placa Nucleo funciona como un dispositivo CDC serial port desde la perspectiva del host
- Con base en esta información de clase, el host puede configurar el driver adecuado para comunicarse con el dispositivo custom
- CubeIDE genera código en C, y en
main.c se incluye la siguiente llamada de inicialización
MX_USB_DEVICE_Init();
- Para encender el LED se agrega el siguiente código a la rutina
CDC_Receive_FS
/* USER CODE BEGIN 6 */
if (Buf[0] == '1') {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
}
- Esta llamada HAL enciende el LED onboard conectado al pin 5 del puerto A
- El video de referencia paso a paso es el siguiente
Flasheo y ejecución
- Después de compilar el archivo ELF, el código se carga a la placa con CubeProgrammer
- Luego se desconecta el programmer y se cablea la placa para usar alimentación externa de 5V como se explicó antes
- Cuando la placa se enciende, debería aparecer en el administrador de dispositivos del sistema operativo como algo similar a un puerto COM o Serial port
- Si se cambia el valor de
Device Descriptor en el menú middleware USB_DEVICE de CubeIDE, se puede definir el nombre del dispositivo custom que aparecerá en el administrador de dispositivos del sistema operativo
- En Mac OS, el nuevo dispositivo aparece bajo el sistema de archivos
/dev
- Ruta de ejemplo:
/dev/tty.usbmodem497A0F6739561
- En Linux puede verse con un nombre como
/dev/ttyUSB0
- Un ejemplo para conectarse al serial device con Minicom es el siguiente
minicom --device /dev/tty.usbmodem497A0F6739561
- Después de conectarse, si se ingresa
1 desde el teclado, se enciende el LED verde de la placa Nucleo
Diferencias de software entre el enfoque STM32 y el enfoque Linux
- El resultado de la práctica es crear un dispositivo USB serial port reconocido por sistemas operativos mainstream
- El enfoque basado en STM32CubeIDE tiene varias incomodidades desde la perspectiva de la ingeniería de software
- hay que hacer clic en menús de UI para generar mucho boilerplate
- no funciona como usar una biblioteca flexible y parametrizada en código, como
InitUsbDevice(UsbClass.CDC)
- el código generado puede quedar fuertemente acoplado al código del usuario, lo que dificulta el code review
- no está claro cómo actualizar el boilerplate cuando salga una nueva versión
- la configuración actual queda muy atada al ecosistema STM32
- La forma en que Linux actúa como USB device se evalúa como un enfoque más limpio
- la API de Linux es más robusta y estandarizada
- puede basarse en la interacción con pseudo-files y system calls
- user space y kernel space están separados
- Linux puede verse como una capa HAL
- Aun así, si se necesita un dispositivo USB liviano, barato y fácil de producir, un SoC con Linux puede ser demasiado pesado y exagerado para muchos casos de uso
- La conclusión es que sería bueno contar con un framework para desarrollo bare metal de dispositivos USB que fuera más portable y menos opinionated
1 comentarios
Comentarios de Hacker News
Como artículo introductorio sobre USB es muy bueno, pero está muy sesgado hacia el uso de microcontroladores ST, y comparado con formas más recientes de conectar dispositivos USB con facilidad, como en el ecosistema ESP32, parece requerir muchos más pasos y una cadena de herramientas bastante más compleja.
Además, desde la perspectiva de alguien que ya ha hecho varias placas USB por su cuenta, el tema de los pares diferenciales no es algo por lo que un principiante deba preocuparse demasiado; se vuelve importante sobre todo en trabajos de alta velocidad.
Como los IC controladores USB que se usan comúnmente con Arduino/ESP se encargan de muchos detalles, pedirle a alguien que hace su primer dispositivo que también haga cálculos se siente excesivo.
Fue un proyecto rápido y divertido para principiantes, y la parte más molesta fue tallar la carcasa de madera.
En la práctica casi nunca he hecho cálculos, y el software ECAD que uso ofrece herramientas de ruteo de pares diferenciales y análisis de señales de impedancia.
Aun así, si mantienes las pistas muy cortas, normalmente no hay grandes problemas.
Normalmente he usado solo microcontroladores con USB integrado.
Hace mucho hice pruebas de conformidad USB, y uno de los puntos donde solía ver problemas era la prueba de corriente de irrupción.
Básicamente ocurría cuando había demasiados capacitores de bypass del lado de 5 V, y en el artículo no me pareció ver esa parte.
Es fácil concentrarse en el diseño digital de alta velocidad, pero en conformidad a veces las partes menos llamativas son las que te hacen tropezar.
No sé cómo funciona en los estándares actuales, pero parece que la prueba todavía existe; el artículo en sí estuvo bueno.
https://compliance.usb.org/index.asp?UpdateFile=Electrical#:...
Se podría hacer un limitador de corriente con algunos transistores, pero imagino que debe haber soluciones integradas mejores con funciones como protección térmica.
Para complementar lo que dice el artículo sobre USB-C, hay que conectar los pines CC a las resistencias adecuadas.
Si no, lo más probable es que no funcione.
Y el ruteo diferencial y la impedancia no son tan críticos en USB 2.0.
Basta con hacer que las longitudes de las pistas sean similares, conectarlas de forma razonablemente directa y mantenerlas juntas si es posible.
No hace falta obsesionarse con ajustar finamente longitudes y anchos de pista, controlar la impedancia ni seguir todas las mejores prácticas de RF; simplemente conecta las nets.
Muchos MCU también necesitan resistencias en serie entre los pines del USB PHY y el conector.
Probablemente no tengas que rehacer la placa solo porque debías usar pistas de 20 mil en vez de 24 mil, pero aunque el layout del par diferencial de USB 2.0 sea de baja exigencia, intentar hacerlo bien es una buena práctica.
Si te preocupan los componentes difíciles de soldar, como un procesador ARM, no tienes por qué usar una pieza tan grande.
STM32 es buena opción cuando necesitas rendimiento, pero para tareas pequeñas puede convenir más un controlador más chico.
Por ejemplo, VUSB es una biblioteca para hacer bit-banging de USB en microcontroladores Atmel pequeños: https://www.obdev.at/products/vusb/index.html
También hay un esquema de una placa de ejemplo que usé para enseñar programación de módulos del kernel de Linux a estudiantes: https://gitlab.cs.fau.de/i4/passt/passtboard-v2
El firmware está en http://www.poempelfox.de/ds1820tousb/ y https://gitlab.cs.fau.de/i4/passt/ds1820tousb
Si prefieres programar al estilo Arduino, también hay muchas placas que se pueden usar como dispositivos USB con solo unas líneas de bibliotecas incluidas; por ejemplo, https://www.az-delivery.de/en/products/digispark-board.
Por ejemplo, la NUCLEO-F429ZI: https://www.st.com/en/evaluation-tools/nucleo-f429zi.html
Es muy similar a la NUCLEO-F103RB que usó el autor, pero además del conector USB superior para el programador/depurador integrado, también tiene en la parte inferior un conector USB conectado directamente al microcontrolador.
Si quieres tomar como referencia decisiones como la protección ESD, también puedes descargar el esquema de la placa.
Es un TSSOP-20 con USB por hardware, y el precio unitario ronda los 0.81 dólares.
Eso sí, el lado de software puede ser un poco más complicado.
https://www.wch-ic.com/products/CH32V203.html?
Escribí código USB bare-metal en un MCU, y comparado con protocolos digitales simples como SPI o I2C, fue bastante impactante.
La capa física y la capa de enlace de datos no son mucho más complejas que CAN, pero después de eso uno se topa de inmediato con el muro de los descriptores, endpoints y la configuración de drivers.
USB fue diseñado desde el principio como un ecosistema plug-and-play para PC, y esa característica se nota mucho.
Si es posible, definitivamente lo mejor es usar al máximo el software provisto por el fabricante.
Como consejo que recuerdo vagamente: para alto rendimiento conviene usar transferencias bulk y ni siquiera mirar las transferencias isócronas.
USB es un protocolo maestro/esclavo, así que si no se alcanza el rendimiento máximo, por lo general la causa suele estar del lado de la PC, que es el host.
Si la licencia LGPL encaja con los requisitos, libusb es bastante fácil de usar.
Si no se usan drivers del fabricante, un analizador de protocolo USB por hardware resulta realmente útil, y USB in a NutShell es una buena referencia web para entender el protocolo: https://www.beyondlogic.org/usbnutshell/usb1.shtml
Ya que estamos hablando de USB en STM32, me pregunto si alguien sabe cómo soportar la recepción de más de 64 bytes en un solo frame.
Ahora estoy procesando repetidamente frames de 64 bytes en software, pero sé que se puede más grande.
Creo que tal vez era hasta 1 MB.
El problema es que en el Reference Manual esas configuraciones aparecen no como registros normales, sino como una especie de pseudorregistros.
Me pregunto si existe algún workaround sencillo.
Esto es para el periférico USB que no es OTG.
No sé a qué STM32 te refieres, pero el stm32g4 solo soporta full-speed.
Uso principalmente ESP32, pero hay un método barato de hackeo que sirve para varios proyectos.
Si necesitas un controlador personalizado muy rápido y fácil, vale la pena considerar quitar la placa controladora de un teclado USB desechado.
En vez de soldar, puedes pegar cables a los contactos con adhesivo conductor y fijarlos firmemente con una pistola de silicón.
Hice un controlador de juegos de 1 botón basado en un botón arcade que envía la entrada de la barra espaciadora; fue barato pero bastante resistente, todo el debouncing queda resuelto y no hace falta código.
Me pregunto si existe alguna placa de desarrollo con soporte USB 3.
Quiero prototipar un sink para monitor USB-C, pero me está costando encontrar una placa con suficiente rendimiento para recibir DisplayPort por USB.
Eso supone que ya puedes recibir la señal DisplayPort o enrutarla a un monitor externo.
Implementas un dispositivo USB billboard y señalizas el modo alternativo correcto.
Para el funcionamiento en sí es opcional, pero según recuerdo, la especificación lo requiere.
Entonces la señal DisplayPort sale por el conector USB-C, y basta con conectar las líneas AUX adecuadas al conector DisplayPort.
El precio también parece razonable.
Cuando busqué algo parecido hace 2 años, costaba un orden de magnitud más.
Tengo una Raspberry Pi conectada a la PC y estoy creando un dispositivo USB virtual con https://github.com/xairy/raw-gadget.
Ahora la estoy usando para emular una cámara MTP y engañar a software propietario.
Prototipé varios dispositivos USB con una Raspberry Pi Zero y el USB compuesto del kernel de Linux.
Al menos los dispositivos de almacenamiento y seriales fueron bastante fáciles de hacer funcionar.
Se necesita algo como un script de shell que inicialice el módulo de kernel compuesto en la Raspberry Pi, y el código boilerplate se puede encontrar en la documentación del kernel.
pikvm también es un proyecto interesante.
Al conectarlo a una PC, la conexión USB no solo funciona como teclado y mouse, sino que también puede convertirse en una unidad USB desde la que se puede arrancar el sistema.
Es una forma bastante interesante para tareas de instalación.