AVR no WSL: toolchain, USBasp e o blink do ATmega328P

O ATmega328P é o chip que muita gente conhece só pelo recorte Arduino. Eu tratei o repositório avr_mcu como o contrário disso: toolchain GNU no WSL, gravador USBasp, avrdude e um blink em C cru. Sem bootloader obrigatório, sem setup()/loop(), sem biblioteca que esconda DDRB. O exercício é pequeno. A disciplina é que importa — a mesma que, no painel, separa quem só “carrega o CLP” de quem entende ciclo, imagem de processo e I/O.

Este texto descreve o que o repositório de fato documenta. Não inflo o blink em “plataforma IoT” e não vendo o 328P como ECU de airbag.

O que está no disco

A raiz tem README de dependências, pasta src/blink (blink.c + makefile) e pasta arduino_nano com um pinout do Nano. Linguagem dominante no GitHub: Makefile. Isso já é um recado: o artefato central não é um sketch; é o fluxo compile → hex → gravar → limpar.

O README fixa o ambiente da época do estudo:

  • No Windows: usbipd-win 2.3.0, com usbipd wsl list, attach e detach por --busid.
  • No WSL: linux-tools + hwdata, alternativa do usbip, e o pacote clássico Debian/Ubuntu: avr-libc, binutils-avr, gcc-avr, avrdude.
  • Checagem: lsusb e sudo avrdude -c usbasp -p m328P -vvv.
  • No blink: sudo make “for usb permition”.

Nada além disso. Quem esperar RTOS, UART, ADC ou shield no mesmo repo vai se decepcionar — e isso é saudável.

Por que WSL + usbipd em vez da IDE pronta

Gravar AVR no Windows com uma IDE fechada funciona até o dia em que você precisa do mesmo fluxo no Linux, no CI ou num notebook de campo. O usbipd-win resolve o problema chato: o USBasp aparece no Windows e precisa ser anexado à distro WSL para o avrdude nativo enxergar o bus.

Na bancada a ordem que não falha é:

  1. Conectar o USBasp.
  2. usbipd wsl list — anotar o busid.
  3. usbipd wsl attach --busid …
  4. No WSL, lsusb até ver o gravador.
  5. Só então avrdude ou make.

O detach existe para devolver o dispositivo ao Windows. Esquecer o attach é a causa número um de “avrdude: expected signature”. A causa número dois é cabo ruim ou MOSI/MISO/SCK/RESET trocados. A causa número três é o 328P sem clock válido (fusebit) — e fusebit eu não trato neste artigo porque o makefile do repo não toca em -U lfuse.

Nota de bancada. sudo make no README é honesto e incompleto: resolve permissão USB naquele instante e ensina um vício. O caminho adulto é regra udev para o USBasp (grupo plugdev ou uaccess) e make sem root. No painel eu também não deixo o notebook de comissionamento “rodar de administrador” por preguiça de ACL.

O blink: três linhas que valem uma aula de I/O

O blink.c é deliberadamente curto. Define F_CPU 16000000UL, LED_PIN 0, inclui avr/io.h e util/delay.h. No main:

  • DDRB |= (1<<LED_PIN) — PB0 como saída.
  • laço infinito com PORTB ^= (1<<LED_PIN) — toggle por XOR.
  • _delay_ms(1000) — espera ocupada.

O comentário ao lado do delay diz “busy wait, 500ms”. O argumento é 1000. Deixo o descompasso visível: comentário e código divergem, e isso é mais didático do que um blink perfeito. Em firmware de produção (e em ladder comentado de painel) esse tipo de resto vira diagnóstico errado daqui a dois anos.

Três princípios que o arquivo ensina sem discurso:

  1. DDR antes de PORT. Mesma lição do TRIS no PIC. Sem direção, o toggle não é saída; é desejo.
  2. XOR no registrador em vez de digitalWrite. Quem lê um bit de entrada no mesmo PORT precisa lembrar do read-modify-write. No 328P o toggle por PINx write também existe; o código usa XOR em PORTB, que é explícito e suficiente para um LED isolado.
  3. F_CPU e _delay_ms. A rotina de delay do avr-libc é calculada em compile time. Se o cristal é 16 MHz e o define mente 8 MHz, o LED “pisca errado” e o técnico troca o hardware. No CLP o análogo é o time base da tarefa cíclica mal configurado.

Busy-wait de 1 s trava a CPU. Serve para LED. Não serve para ler encoder, não serve para timeout de comunicação, não serve para nada que conviva com outro evento. O próximo passo natural — que este repo não implementa — seria timer + interrupção, como eu fiz no PIC18. Não invento esse passo no avr_mcu.

Makefile: o produto real do repositório

O makefile declara FILENAME=blink, DEVICE=atmega328p, PROGRAMER=usbasp (grafia do arquivo), BAUD=115600 e COMPILE=avr-gcc -Wall -Os -mmcu=$(DEVICE). Há um PORT=/dev/bus/usb/001/004 que o alvo upload não usa: o avrdude é invocado só com -p, -c e -U flash:w:blink.hex:i. Variável morta é variável morta; registro o fato.

A cadeia padrão é compile upload clean:

  • avr-gcc -c.o
  • link → .elf
  • avr-objcopy -j .text -j .data -O ihex.hex
  • avr-size --format=avr --mcu=… → tamanho na Flash/SRAM/EEPROM
  • avrdude -v grava
  • rm dos intermediários

Isso é o mesmo espírito de um build reproduzível de firmware de CLP ou de um make de STM32: uma linha, um artefato, um gravador. -Os e -Wall são escolhas corretas para 32 KB de Flash. O clean no alvo default apaga evidência depois do upload; para debug de map file eu quebraria o default. Para aula de “foi e gravou”, funciona.

O USBasp fala ISP (MOSI, MISO, SCK, RESET, VCC, GND). No Nano, o 328P já vem com bootloader UART na maioria das placas de hobby; este fluxo não depende dele. Por isso a pasta arduino_nano com pinout faz sentido: o Nano é o hardware barato, o fluxo é AVR-GCC. Não misturar as duas religiões é o ponto.

Ponte com o painel e com o PIC

Bancada AVR Analogia no CLP / painel Analogia no PIC do outro repo
DDRB / PORTB Imagem de processo, %QX / %IX TRIS / LAT
_delay_ms TON na rotina errada (trava ciclo) __delay_ms no XC8
makefile + avrdude Download do runtime + cartão de memória MPLAB X + PICkit
USBasp ISP Não tem equivalente USB-barato no CLP médio; o parente é JTAG/SWD ICSP
usbipd + WSL Notebook de campo com VM e dongle Menos comum; o MPLAB ainda é Windows-nativo

O técnico que já comissionou inversor por USB e sofreu com driver no Windows reconhece o usbipd. O tecnólogo que já piscou RB0 no 18F4550 reconhece o DDRB. A ponte não é marketing: é o mesmo bit.

O 328P também aparece em I/O barato de máquina — relé, encoder lento, HMI serial de gambiarra. Isso não o torna controlador de segurança. Numa linha com NR-12, a função de segurança continua no relé de segurança, no CLP de segurança ou no drive com STO. O AVR pode piscar a lâmpada de “ciclo em curso”. Pode gravar um datalogger. Não assina o corte da potência.

O que o repo ensina versus produção ASIL

ISO 26262 e ISO 13849 não se impressionam com LED em PB0. O repositório ensina toolchain, permissão USB, assinatura do chip (-p m328P), geração de Intel HEX e o custo de um delay bloqueante. Não ensina:

  • requisitos e HARA;
  • watchdog com janela e supervisor externo;
  • diagnóstico de clock, de RAM e de fluxo de programa;
  • compilador/qualificação de ferramenta;
  • cobertura de teste, MISRA, partição ASIL.

O ATmega328P sequer é o MCU que se escolhe hoje para item ASIL em veículo. Usá-lo na bancada é correto. Prometer continuidade até airbag é desonesto.

Há um detalhe de bancada que eu não fecho neste artigo porque o repo não documenta: fusebits, brown-out, clock interno versus cristal de 16 MHz do Nano. O F_CPU assume 16 MHz. Se a placa for um 328P “nu” em protoboard com RC interno, o período do LED mente. Medir ou ler a assinatura com avrdude -vvv (comando que o README já traz) é o começo da investigação.

Pitfalls da toolchain WSL (a parte que o README não dramatiza)

O fluxo usbipd → WSL → avrdude falha de formas previsíveis. Catalogar falha é mais útil que repetir o happy path.

O USBasp some depois do sleep do Windows

Notebook fecha a tampa, USB suspende, o attach do usbipd morre. Sintoma: lsusb limpo, avrdude com “could not find USB device”. Remédio: usbipd wsl list, attach de novo, confirmar VID/PID do USBasp. Em comissionamento de painel o análogo é o dongle USB-RS485 que “some” depois do Windows Update — mesma higiene mental: dispositivo ≠ sessão.

Permissão: sudo make versus udev

sudo make grava hoje e ensina root amanhã. Regra udev típica (conceito; ajuste o idVendor/idProduct do seu USBasp):

# /etc/udev/rules.d/99-usbasp.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", MODE="0664", GROUP="plugdev"

Depois udevadm control --reload e replug. O make volta a rodar como usuário. Em fábrica, a mesma disciplina aparece em quem pode flashear IHM ou gravar CLP — privilégio mínimo.

Assinatura errada e o -p m328P

avrdude -p m328P espera a assinatura do ATmega328P. Placa errada, chip morto, ISP invertido ou RESET sem pull-up geram “Expected signature … Invalid”. O -vvv do README não é enfeite: ele imprime o diálogo ISP. Ler o log antes de culpar o makefile.

Cabo e pinagem ISP

MOSI↔MOSI, MISO↔MISO, SCK↔SCK, RESET, VCC, GND. Cabo jumper frouxo no protoboard é a segunda causa mais comum depois do attach esquecido. No Nano, o conector ICSP de 2×3 existe por isso — use-o. Alimentar o alvo só pelo USBasp em placa sedenta (muitos LEDs + shield) às vezes falha; nesse caso alimente a placa e compartilhe GND.

Baud no makefile

O arquivo declara BAUD=115600 e não usa no upload ISP. Isso não quebra o blink; polui o raciocínio. Em ISP clássico o clock do programador importa mais que baud de UART. Limpar variável morta evita o próximo técnico “ajustar baud” à toa.

F_CPU, cristal e a mentira do delay

_delay_ms do avr-libc é calculado em compile time a partir de F_CPU. Três cenários reais:

Hardware Clock real F_CPU no código Sintoma
Nano clássico 16 MHz cristal 16000000UL LED ~1 s (ok)
328P bare com RC interno ~8 MHz ~8 MHz 16000000UL LED ~2 s (parece “bug de delay”)
Fuses mudados para divisor clock/2 16000000UL período dobra

Antes de trocar LED, conferir fusebits e osciloscópio no XTAL ou no pino CKOUT se habilitado. O README não documenta leitura/gravação de fuse — então o artigo para no aviso. Gravar fuse errado (clock externo sem cristal) “tijola” o chip até HV programmer. Na bancada industrial: não mexa em fuse sem procedimento escrito.

O comentário “500ms” com argumento 1000 no código original é o segundo tipo de mentira: documentação podre. Em ladder de painel, comentário “TON 2s” com preset 5s gera a mesma classe de acidente de manutenção.

Do blink ao próximo exercício (sem inventar commit)

O repo para no LED. A trilha natural — se você for continuar em outro experimento — seria:

  1. Timer0/1 overflow no lugar de _delay_ms — libera a CPU para polling.
  2. UART 115200 para telemetria mínima (eco de registrador).
  3. ADC numa entrada analógica (potenciômetro) — paralelo com 4–20 mA só no conceito de escala.
  4. Watchdog — reset se o laço travar; ainda longe de ASIL, mas muda o hábito.

Nada disso está no avr_mcu hoje. Listar a trilha evita transformar o README em fanfic.

Ponte mais fina: CLP, PIC e AVR no mesmo cérebro

Quem monta painel já pensa em:

  • ciclo de scan versus busy-wait;
  • imagem de processo versus leitura direta de pino no meio do algoritmo;
  • download de aplicação versus avrdude -U flash:w:….

O AVR ensina o registrador. O PIC do outro repositório ensina a mesma coisa com outro mapa (TRIS/LAT). O CLP esconde o registrador atrás de simbolismo (%I, %Q) e de um runtime certificado. Pular do Arduino IDE direto para “edge AI no Jetson” sem passar por DDR/PORT é o atalho que cria técnico que não debuga GPIO.

Tabela de decisão rápida:

Preciso… Caminho sensato
Aprender bit, makefile, ISP avr_mcu / pic_mcu
Protótipo rápido com shield Arduino (aceitando o bootloader)
Percepção + Linux Jetson / SBC
Segurança de máquina CLP/relé de segurança, não 328P

Checklist de bancada (reproduzível)

  1. Windows: usbipd-win instalado; USBasp conectado.
  2. usbipd wsl listattach --busid …
  3. WSL: lsusb vê o gravador; usuário no grupo certo (ou udev).
  4. avrdude -c usbasp -p m328P -vvv responde assinatura.
  5. cd src/blink && make (sem sudo, se udev ok).
  6. LED em PB0 pisca; se período errado, suspeitar F_CPU/cristal antes do LED.
  7. usbipd wsl detach ao terminar se o Windows precisar do USB.

O que não misturar na cabeça

  • Bootloader UART do NanoISP USBasp. Ambos gravam flash; o caminho elétrico e a ferramenta mudam.
  • Arduino coreavr-gcc cru. O core é produtivo; o cru é didático e portátil.
  • Hobby 328PMCU automotivo ASIL. O primeiro ensina. O segundo exige processo.
  • WSL ≠ “Linux de produção embutida”. É ambiente de desenvolvimento no notebook Windows.

Fechamento

avr_mcu documenta um caminho chato e verdadeiro: Windows ↔ WSL ↔ USB ↔ ISP ↔ HEX — a mesma família mental de gravar HMI ou baixar aplicação de CLP em campo. Se o LED pisca no tempo certo com F_CPU honesto e make sem root eterno, a bancada cumpriu o papel. O próximo salto (timer, UART, ADC) é outro commit, não padding neste texto.