Sem motor, sem placa, sem desculpa: o firmware agora é provado em emulação a cada commit
O hardware de bancada ainda está em trânsito. Mesmo assim, a cada commit, o binário real do firmware bota num STM32F767 emulado (Renode) e precisa provar: boot + telemetria, console respondendo byte-exato, atuadores travados em LOW sem sync, sincronização com crank 32-2 e ADC com injeção de falha caindo em modo de segurança. Vermelho = não mergeia.
O hardware de bancada do Pistonix — Nucleo, sondas, display, ferramentas — ainda está em trânsito. A pergunta óbvia: como saber que o firmware funciona se a placa não chegou?
A resposta que adotamos: emular o microcontrolador inteiro. Usamos Renode, um emulador de sistemas embarcados que roda o mesmo ELF que vai pro flash — não uma versão “de teste”, não um mock do hardware. O binário de produção bota num STM32F767ZI simulado, com USARTs, GPIOs e ADC virtuais, e precisa se comportar.
E desde esse arco, isso não é um experimento de canto de mesa: é gate de CI. Cenário vermelho = commit não entra.
Os quatro cenários
Cada commit que toca o firmware passa por quatro cenários de emulação:
1. Boot + telemetria. O firmware bota, o heartbeat pisca na frequência certa e a serial de telemetria emite CSV válido. O básico — mas é o básico assertado por máquina, não por “rodou na minha bancada”.
2. Console respondendo, byte a byte. Injetamos os bytes de um PING e de um GET_INFO na USART do console e assertamos a resposta exata — PONG e INFO, frame completo, CRC32 incluído. Se um refactor mudar um byte do protocolo sem mudar a spec, o CI acusa antes de qualquer humano perceber.
3. O invariante de segurança: sem sync, atuador nenhum liga. Esse é o cenário que me deixa dormir. Pinos de bobina e injetor são vigiados por “LED testers” do emulador que seguram a asserção pelo tempo todo, não só amostram no final: enquanto a ECU não sincronizou com o motor, todos os 4 pinos de atuador ficam em LOW. Sempre. Faísca fora de hora em V-twin de alta compressão é kickback, é biela, é motor — esse “nunca” agora é uma propriedade verificada a cada commit, não uma esperança de code review.
4. Crank sintético + ADC com falha injetada. Um gerador de sinal 32-2 (o padrão de roda fônica das Harley) alimenta o decoder a 1500 RPM: a ECU precisa sincronizar em até 3 voltas e — fechando o ciclo com o cenário 3 — só começar a agendar eventos de ignição depois do sync. No ADC, injetamos contagens nos canais de TPS/MAP/ECT e assertamos a conversão de engenharia; depois injetamos uma falha e assertamos que a ECU cai em modo de segurança em vez de seguir confiando num sensor morto.
O bug que a emulação pegou antes do hardware
O melhor argumento a favor desse setup foi um bug que teria mordido na bancada. O driver de ADC “de prateleira” do nosso framework assíncrono espera a conversão num loop sem limite. Num chip saudável, funciona. Num ADC que não responde — falha de hardware, clock errado, pino flutuando — esse loop trava a task pra sempre, o executor inteiro engasga e o watchdog reseta a ECU em loop infinito.
Em bancada, isso seria uma tarde perdida com a placa resetando sem explicação. Em emulação, apareceu de graça. Escrevemos um driver próprio com timeout limitado em cada espera: ADC morto vira DTC + modo de segurança, não ECU possuída. Filtragem por mediana de 5 amostras completa o caminho do sensor.
O gerador de crank vira ferramenta de bancada
Detalhe que me agrada: o gerador de sinal 32-2 usado no cenário de
emulação não é código descartável de teste. É um módulo no_std,
livre de drift, que vai rodar numa segunda placa na bancada como
simulador de sinal de crank quando o hardware chegar. O mesmo código que
prova o decoder no CI vai alimentar o decoder na mesa. Ferramenta de
teste com dupla cidadania.
O que a emulação não prova
Honestidade técnica: emulação não substitui bancada. Ela não vê o front-end analógico real, o condicionamento do sinal VR do sensor de crank, ruído elétrico, EMI de bobina, nem o timing fino de interrupção sob carga real. Tudo isso continua sendo trabalho de bancada e depois de moto.
O que muda é o que sobra pra bancada descobrir. Boot, protocolo, lógica de sync, conversão de sensores, caminhos de falha, o invariante de segurança — isso chega pré-provado. O dia 1 com hardware vira confirmação, não descoberta.
Onde isso se encaixa
Por baixo desses cenários tem uma mudança estrutural que merece menção: o loop de controle que roda no microcontrolador é o mesmo código que roda no nosso simulador de motor host-side — extraído pra biblioteca core compartilhada, com 500 testes e traces dourados que garantem que nenhum refactor muda o comportamento sem ninguém perceber.
Simulador de motor prova a física. Emulação prova o chip. Bancada vai provar o mundo real. Três camadas, cada uma pegando o que a anterior não pega — e as duas primeiras já rodam, automáticas, em cada commit.