- Organiza, em um só fluxo, o que é necessário ao criar um dispositivo USB pela primeira vez, indo de fiação física e PCB até velocidades do USB 2.0, camadas de protocolo e prática com STM32
- A conexão básica do USB 2.0 usa quatro fios: +5V, GND, D+ e D-, e
D+ e D- formam um par diferencial que transmite 1 bit em conjunto
- Em PCBs, são importantes o casamento de comprimento, a proximidade e a impedância do par diferencial, mas um protótipo full speed de 12 Mbit/s pode funcionar com tolerância relativamente alta
- Na prática, em uma NUCLEO-F103RB, configura-se
PA12 como USB_DP e PA11 como USB_DM, cria-se um dispositivo USB serial reconhecido como porta COM virtual CDC e acende-se o LED ao receber a entrada 1
- A abordagem com STM32CubeIDE traz o peso da geração de código baseada em UI e do acoplamento com boilerplate, enquanto uma implementação de dispositivo USB baseada em Linux pode oferecer APIs mais padronizadas e uma estrutura mais separada
Escopo de uma primeira implementação USB
- O objetivo é construir até o fim, na forma mais simples possível, um dispositivo USB que seja reconhecido ao ser conectado ao computador
- Aqui, dispositivo significa um periférico que expande as funções do computador, e o computador é chamado de host
- O escopo inclui desde a conexão física entre o dispositivo USB e o host até uma aplicação simples no host para interagir com o dispositivo USB
- Em vez de uma explicação autoritativa da especificação, o texto se aproxima mais de um projeto E2E para criar o dispositivo USB mais simples possível e de um índice de materiais de referência
Conceitos básicos de USB
- USB é um padrão industrial que permite troca de dados e fornecimento de energia entre vários dispositivos eletrônicos
- USB é um barramento serial, então os bits são transmitidos um a um no barramento, e não em paralelo
- Os barramentos modernos são, em geral, seriais, e o USB pode ser entendido como uma forma de trocar bits em série entre host e device
- USB inclui não só a especificação física para conectar dois dispositivos e trocar bits, mas também o protocolo de comunicação para transferência de dados e energia
- O foco da explicação está no USB 2.0
Fiação USB e par diferencial
- Uma conexão USB 2.0 comum é descrita com no mínimo quatro fios
+5 V: fio pelo qual o host fornece energia ao device
D-, D+: par diferencial em que os dois fios trabalham juntos para transmitir 1 bit
GND: terra
- Algumas conexões podem ter pinos adicionais, como
ID, mas a prática cobre apenas esses quatro fios
-
Observações sobre USB-C
- O USB-C também usa pares diferenciais internamente, mas difere de uma explicação USB 2.0 comum em pontos como a necessidade de funcionar mesmo com o conector invertido
- Usar um conector USB-C não determina por si só a velocidade nem a versão do USB
- Mesmo um dispositivo com USB-C pode ser USB 2.0 ou um dispositivo mais moderno, como USB 3.0
- A prática não volta a tratar de USB-C depois disso
-
Como o par diferencial reduz ruído
- Um fio único expressa o valor do bit comparando sua tensão com
GND, mas em conexões com fio mais longas o efeito do ruído pode aumentar
- Um par diferencial usa dois fios: um transmite
V e o outro -V
- O lado receptor observa a diferença de tensão entre os dois fios
- Se simplificarmos dizendo que o mesmo ruído
Vn é somado aos dois fios, então (V + Vn) - (-V + Vn) = 2V, o que faz com que o ruído seja cancelado
- Esse modelo é uma explicação bastante simplificada, e para um entendimento mais profundo de pares diferenciais pode-se consultar o vídeo do YouTube da Altium com Zach Peterson
- Video 15
Trabalhando com USB em uma PCB
- Se você usar uma placa de desenvolvimento em vez de criar o hardware diretamente, pode pular a seção de PCB, mas os princípios básicos ao colocar um conector USB na PCB ainda são úteis
- Os componentes de biblioteca para conectores USB incluem os pinos mencionados antes, e o par diferencial deve ser roteado até os pinos adjacentes correspondentes do microcontrolador ou SoC
- Há três princípios básicos no roteamento de pares diferenciais
- Igualar o comprimento da trilha que vai de
D+ até o pino plus do chip e da outra trilha
- Manter as duas trilhas muito próximas entre si
- Considerar a impedância adequada ao sinal
- Como isso faz com que os dois fios passem por ambientes quase idênticos, torna-se razoável assumir, no modelo simplificado anterior, que o ruído nos dois fios é o mesmo
- O cálculo de impedância pode ser feito usando calculadoras do fabricante, inserindo a impedância-alvo, a distância até o plano de terra, a distância entre os fios do par diferencial etc., para obter a largura necessária das trilhas
- Vídeos de Zach Peterson sobre par diferencial e roteamento USB estão incluídos como referência
Velocidades do USB 2.0
- USB 2.0 não significa uma única velocidade fixa
- O USB 2.0 pode ter, por exemplo, dois níveis de velocidade
- full speed: 12 Mbit/s
- high speed: 480 Mbit/s
- Device e host precisam decidir, ao se conectarem, qual velocidade será usada
- Para atingir high speed de forma estável em uma PCB, é importante casar a impedância
- Em um protótipo básico, 12 Mbit/s full speed pode ser suficiente e, se as trilhas entre o conector USB e o chip forem curtas, pode haver tolerância relativamente alta quanto à largura das trilhas etc.
Protocolo e camadas de software
- USB envolve não só hardware, mas também as camadas de software tanto do host quanto do device
- Um vídeo de cerca de 45 minutos explicando USB da perspectiva do Linux está incluído como material recomendado
- Esse vídeo aborda frames USB, endpoint, configuration e como um único device pode executar várias funções USB
- É importante a perspectiva de pensar no USB como uma rede de dispositivos
- Como seria inviável para o sistema operacional do host implementar individualmente drivers para cada dispositivo USB, o sistema operacional reconhece várias classes de dispositivos USB
- mass storage device
- serial device
- várias outras classes
- O dispositivo da prática funciona, do ponto de vista do host, como um dispositivo de porta serial
Criando um dispositivo de porta serial USB com STM32
- O dispositivo da prática é um dispositivo USB simples que acende um LED quando recebe uma solicitação do host
- O host reconhece esse dispositivo como um serial port device
- Há duas grandes abordagens de implementação
- Usar um microcontrolador com suporte a USB
- Usar um SoC capaz de rodar Linux para que o kernel faça boa parte do trabalho
- A prática usa a abordagem com microcontrolador por simplicidade
-
Placa NUCLEO-F103RB
- A placa de desenvolvimento usada é a NUCLEO-F103RB
- Nos EUA, ela pode ser comprada em lugares como a Digikey por pouco mais de 10 dólares
- Essa placa tem uma estrutura formada por duas partes conectadas
- A parte menor é a área do programmer; a parte maior é a área do MCU principal que será realmente programado
- A conexão USB padrão da placa não está ligada ao MCU principal, mas ao MCU do lado do programmer
- Esse programmer usa o protocolo ST-LINK para trocar mensagens USB com o computador e depois programar o MCU principal
- Se você montar sua própria PCB baseada em STM32, pode deixar apenas um chip na placa, programá-lo via USB e depois usar essa mesma porta USB para a lógica do dispositivo
- Vídeos do Phil’s Lab sobre PCB baseada em STM32 e programação via USB estão incluídos como referência
- Video 19
- Video 20
Fiação da porta USB real
- Como a conexão USB padrão da placa Nucleo não está ligada ao MCU principal, é preciso conectar diretamente a porta USB do MCU principal ao host
- Depois de gravar o programa, o programmer ST-LINK deve ser desconectado do computador, e o MCU principal deve ser alimentado pela porta USB real
- Para escrever o software, usa-se o STM32CubeIDE, e para gravar na placa, o STM32CubeProgrammer
-
Pinos e configuração necessários
- São necessários quatro pinos para criar um dispositivo USB 2.0
- Alimentação de 5V vinda do host
GND
D+
D-
- Para alimentar a NUCLEO-F103RB pela porta USB do lado do MCU principal, é preciso mudar o jumper
JP5 para que a placa receba alimentação external 5 V
- No STM32CubeIDE, os pinos são configurados assim
PA12 → USB_DP
PA11 → USB_DM
- O chip dessa placa precisa de um resistor pull-up externo de 1.5 kΩ para a conexão USB
- O resistor faz pull-up de
PA12 para 3.3V
- O circuito foi montado em uma breadboard
- Para conectar um MacBook Pro aos pinos individuais, foi usado um USB breakout cable da Amazon; removeu-se o terminal block para expor os pinos USB e, em seguida, fez-se a conexão com jumper wires até a placa Nucleo e a breadboard
- Mesmo sem seguir rigidamente o casamento de comprimento do par diferencial e o controle de impedância, foi possível obter, nessa prática, uma conexão de 12 Mbit/s
Escrevendo o software USB CDC
- Ao configurar os pinos USB no CubeIDE, aparece um aviso sobre configuração de clock, e é possível escolher a opção para que o CubeIDE resolva isso automaticamente
- Em
Pinout & Configuration, dentro de Middleware and Software Packs, configura-se USB_DEVICE
- O modo importante na prática é o seguinte
Communication Device Class (Virtual Port Com)
- Com essa configuração, a placa Nucleo funciona, do ponto de vista do host, como um dispositivo de porta serial CDC
- Com base nessa informação de classe, o host pode configurar o driver adequado para se comunicar com o device customizado
- O CubeIDE gera código C, e o
main.c inclui a seguinte chamada de inicialização
MX_USB_DEVICE_Init();
- Para acender o LED, adiciona-se o seguinte código à rotina
CDC_Receive_FS
/* USER CODE BEGIN 6 */
if (Buf[0] == '1') {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
}
- Essa chamada HAL acende o LED onboard conectado ao pino 5 da porta A
- O vídeo de referência passo a passo é o seguinte
Gravação e execução
- Depois de compilar o arquivo ELF, grava-se o código na placa com o CubeProgrammer
- Em seguida, desconecta-se o programmer e faz-se a fiação da placa usando alimentação externa de 5V, como explicado antes
- Quando a placa estiver ligada, ela deve aparecer no gerenciador de dispositivos do sistema operacional como algo parecido com uma porta COM ou porta serial
- Se você alterar o valor de
Device Descriptor no menu de middleware USB_DEVICE do CubeIDE, poderá definir o nome do dispositivo customizado exibido no gerenciador de dispositivos do sistema operacional
- No Mac OS, o novo dispositivo aparece no sistema de arquivos
/dev
- Exemplo de caminho:
/dev/tty.usbmodem497A0F6739561
- No Linux, ele pode aparecer com um nome como
/dev/ttyUSB0
- Um exemplo de conexão ao dispositivo serial com o Minicom é o seguinte
minicom --device /dev/tty.usbmodem497A0F6739561
- Após a conexão, ao digitar
1 no teclado, o LED verde da placa Nucleo acende
Diferenças de software entre a abordagem STM32 e a abordagem Linux
- O resultado da prática é a criação de um dispositivo de porta serial USB reconhecido por sistemas operacionais mainstream
- A abordagem baseada em STM32CubeIDE tem algumas desvantagens do ponto de vista de engenharia de software
- É preciso clicar em menus da UI para gerar muito boilerplate
- Não é um modelo de usar, no código, uma biblioteca flexivelmente parametrizada como
InitUsbDevice(UsbClass.CDC)
- O código gerado pode ficar fortemente acoplado ao código do usuário, dificultando code review
- Não está claro como atualizar o boilerplate quando surgirem novas versões
- A configuração atual fica fortemente presa ao ecossistema STM32
- A forma como o Linux atua como dispositivo USB é vista como uma abordagem mais limpa
- A API do Linux é mais robusta e padronizada
- Ela pode se basear em interações com pseudo-files e system calls
- Há separação entre user space e kernel space
- O Linux pode ser visto como uma camada HAL
- Ainda assim, se o objetivo for um dispositivo USB leve, barato e fácil de produzir, um SoC com Linux pode ser pesado demais e excessivo para muitos casos de uso
- A conclusão é que seria desejável haver um framework para desenvolvimento bare metal de dispositivos USB que fosse mais portátil e menos opinionated
1 comentários
Opiniões no Hacker News
É um ótimo texto de introdução a USB, mas ele é bem enviesado para o uso de microcontroladores ST; em comparação com formas mais recentes, como no ecossistema ESP32, em que é fácil acoplar e usar dispositivos USB, parece envolver muito mais etapas e uma cadeia de ferramentas bem maior
Além disso, falando como alguém que já fez várias placas USB, pares diferenciais não são algo com que iniciantes precisem se preocupar tanto; isso passa a ser importante principalmente em operações de alta velocidade
Como muitos ICs controladores USB comumente usados em Arduino/ESP cuidam de muitos detalhes, fazer quem está criando seu primeiro dispositivo até calcular isso parece um exagero
Foi um projeto rápido e divertido para iniciantes, e a parte mais chata foi esculpir o case de madeira
Quase nunca fiz os cálculos na prática, e o software ECAD que uso oferece ferramentas de roteamento de pares diferenciais e análise de sinais de impedância
Mesmo assim, se você mantiver as trilhas bem curtas, normalmente não haverá grandes problemas
Normalmente usei apenas microcontroladores com USB integrado
Há muito tempo fiz testes de conformidade USB, e um dos itens em que eu via problemas com frequência era o teste de corrente de partida
Basicamente, era quando havia capacitores de bypass demais no lado de 5 V, e não me parece que o texto tenha abordado essa parte
É fácil se concentrar no projeto digital de alta velocidade, mas, em conformidade, às vezes são as partes menos glamourosas que atrapalham
Não sei como isso funciona nos padrões mais recentes, mas parece que o teste ainda existe, e o texto em si foi bom
https://compliance.usb.org/index.asp?UpdateFile=Electrical#:...
Dá para fazer um limitador de corrente com alguns transistores, mas deve haver soluções integradas melhores, com recursos como proteção térmica
Para complementar o texto sobre USB-C, os pinos CC precisam ser ligados aos resistores adequados
Caso contrário, é bem provável que não funcione
E roteamento diferencial e impedância não são um problema tão grande em USB 2.0
Basta deixar os comprimentos das trilhas parecidos, conectá-las diretamente de forma razoável e, se possível, mantê-las próximas uma da outra
Não é preciso se preocupar demais com ajustes finos de comprimento e largura de trilha, controle de impedância ou boas práticas de RF; é só conectar as nets
Muitos MCUs também exigem resistores em série entre os pinos do USB PHY e o conector
Provavelmente você não vai precisar refazer a placa só porque deveria ter usado trilhas de 20 mil em vez de 24 mil, mas, mesmo que o layout de pares diferenciais em USB 2.0 seja relativamente pouco exigente, esforçar-se para fazê-lo corretamente é uma boa prática
Se a preocupação forem componentes difíceis de soldar, como processadores ARM, não é obrigatório usar um componente tão grande
STM32 é bom quando você precisa de desempenho, mas, para tarefas pequenas, um controlador menor pode ser melhor
Por exemplo, VUSB é uma biblioteca que faz bit banging de USB em pequenos microcontroladores Atmel: https://www.obdev.at/products/vusb/index.html
Também há o esquemático de uma placa de exemplo que usei para ensinar programação de módulos do kernel Linux a estudantes: https://gitlab.cs.fau.de/i4/passt/passtboard-v2
O firmware está em http://www.poempelfox.de/ds1820tousb/ e https://gitlab.cs.fau.de/i4/passt/ds1820tousb
Se você prefere programação no estilo Arduino, há muitas placas que podem ser usadas como dispositivos USB com apenas algumas linhas de bibliotecas incluídas; por exemplo, https://www.az-delivery.de/en/products/digispark-board
Um exemplo é a NUCLEO-F429ZI: https://www.st.com/en/evaluation-tools/nucleo-f429zi.html
Ela é muito parecida com a NUCLEO-F103RB usada pelo autor, mas, além do conector USB superior para o programador/depurador integrado, também há na parte inferior um conector USB ligado diretamente ao microcontrolador
Se quiser consultar escolhas como proteção contra ESD, também é possível baixar o esquemático da placa
É um TSSOP-20 com USB em hardware, e o preço unitário é de cerca de US$ 0,81
Só que o lado de software pode ser um pouco mais complicado
https://www.wch-ic.com/products/CH32V203.html?
Já escrevi código USB bare-metal em um MCU e, comparado a protocolos digitais simples como SPI ou I2C, foi um choque considerável
As camadas física e de enlace de dados não chegam a ser muito mais complexas que CAN, mas logo depois você dá de cara com a barreira de descritores, endpoints e configuração de drivers
USB foi projetado desde o início como um ecossistema plug and play para PCs, e essa característica é bem marcante
Sempre que possível, usar ao máximo o software fornecido pelo fabricante é claramente o melhor caminho
Uma dica de que me lembro vagamente: para alta vazão, use transferências bulk e nem olhe para transferências isócronas
USB é um protocolo mestre/escravo, então, se você não atinge a vazão máxima, geralmente a causa está no lado do PC, que é o host
Se a licença LGPL for compatível com seus requisitos, a libusb é bem fácil de usar
Se você não for usar o driver do fabricante, um analisador de protocolo USB de hardware é realmente útil, e USB in a NutShell é uma boa referência web para entender o protocolo: https://www.beyondlogic.org/usbnutshell/usb1.shtml
Aproveitando o assunto de USB no STM32, queria saber se alguém sabe como dar suporte a recebimento acima de 64 bytes em um único frame
Hoje estou tratando frames de 64 bytes repetidamente no software, mas sei que dá para ir além
Acho que talvez fosse até 1 MB
O problema é que, no Reference Manual, essas configurações aparecem não como registradores comuns, mas como uma espécie de pseudo-registradores
Queria saber se existe alguma solução de contorno fácil
Estou falando do periférico USB que não é OTG
Não sei de qual STM32 você está falando, mas o stm32g4 só oferece suporte a full-speed
Uso principalmente ESP32, mas existe um hack barato que serve para vários projetos
Se você precisa de um controlador customizado muito rápido e fácil, vale considerar remover a placa controladora de um teclado USB descartado
Em vez de soldar, basta prender fios aos contatos com adesivo condutivo e fixar bem com cola quente
Já fiz um controle de jogo de 1 botão baseado em botão de arcade que envia a tecla espaço; ficou barato, mas bem resistente, o debounce e todo o resto já vêm resolvidos, e não precisa de código
Gostaria de saber se existem placas de desenvolvimento com suporte a USB 3
Quero prototipar um sink de monitor USB-C, mas está difícil encontrar uma placa com desempenho suficiente para receber DisplayPort via USB
Partindo do pressuposto de que você já consegue receber o sinal DisplayPort ou roteá-lo para um monitor externo
Basta implementar um dispositivo USB Billboard e sinalizar o modo alternativo correto
Isso é opcional para a operação em si, mas, se bem me lembro, é exigido pela especificação
Aí o sinal DisplayPort sai no plugue USB-C, e basta conectar as linhas AUX corretas ao conector DisplayPort
O preço também parece razoável
Quando procurei algo parecido há 2 anos, era uma ordem de grandeza mais caro
Estou mantendo um Raspberry Pi conectado a um PC e criando um dispositivo USB virtual com https://github.com/xairy/raw-gadget
No momento, estou usando isso para emular uma câmera MTP a fim de enganar um software proprietário
Prototipei alguns dispositivos USB com o Raspberry Pi Zero e o USB composto do kernel Linux
Pelo menos dispositivos de armazenamento e seriais foram bem fáceis de fazer funcionar
Você precisa de algo como um script shell para inicializar o módulo composto do kernel no Raspberry Pi, e o código boilerplate pode ser encontrado na documentação do kernel
O pikvm também é um projeto interessante
Quando conectado a um PC, a conexão USB não só funciona como teclado e mouse, como também pode virar um drive USB usado para inicializar o sistema
É uma abordagem bem interessante para instalações