Go e hardware: por que isso não é brincadeira?
\nQuando se ouve a expressão "desenvolvimento embarcado", geralmente se imagina C, assembly e uma depuração dolorosa através de um LED. Mas o mundo não para. Hoje, Go (ou Golang) está batendo cada vez mais forte na porta do mundo dos microcontroladores. E não está batendo à toa, mas com goroutines, coletor de lixo e interfaces integradas.
\nNeste artigo, vamos entender como é o desenvolvimento embarcado em Go, escrever código real para um microcontrolador hipotético e descobrir se vale a pena. Spoiler: sim, se você está cansado de vazamentos de memória e adora código legível.
\n\nPor que Go e não C? Três pilares
\nO embarcado clássico é C. Mas Go tem três killer-features que fazem os engenheiros repensarem seus hábitos:
\n- \n
- Segurança de memória: sem ponteiros selvagens que podem voar para o espaço. O compilador Go é mais rigoroso que o compilador C. \n
- Goroutines: threads leves que se encaixam perfeitamente na arquitetura orientada a eventos dos microcontroladores. \n
- Multiplataforma: você escreve código no Mac e compila para ARM Cortex-M. Sem complicações. \n
Claro, há uma ressalva: o Go nativo (gc) não sabe compilar para MCUs pequenos. Mas existe o projeto TinyGo — um compilador que adapta o Go para hardware com kilobytes de memória. É nele que vamos focar.
\n\nConfiguração do ambiente: TinyGo e sua placa
\nAntes de escrever código, vamos preparar o ambiente de trabalho. Para desenvolvimento embarcado em Go, você precisará de:
\n- \n
- Instalar o TinyGo:
brew install tinygo(ou baixar do site oficial). \n - Instalar a toolchain para sua placa (ex: ARM GCC para STM32). \n
- Conectar a placa via USB. \n
Vamos verificar se tudo funciona:
\ntinygo version\ntinygo: 0.31.0 (linux/amd64)\n\nAgora podemos compilar código para qualquer placa suportada: tinygo build -target=arduino-nano -o firmware.hex main.go.
Primeiro blink: LED em Go
\nO clássico do gênero. Conectamos ao pino, alternamos o sinal. Veja como é a programação de microcontroladores em Go:
\npackage main\n\nimport (\n "machine"\n "time"\n)\n\nfunc main() {\n // O LED embutido geralmente está no pino 13 (ou LED_BUILTIN)\n led := machine.LED\n led.Configure(machine.PinConfig{Mode: machine.PinOutput})\n\n for {\n led.High()\n time.Sleep(500 * time.Millisecond)\n led.Low()\n time.Sleep(500 * time.Millisecond)\n }\n}\n\nCompilamos e gravamos na placa:
\ntinygo flash -target=arduino-nano -port=/dev/ttyUSB0 main.go\n\nVoilà! O LED pisca. Observe: usamos o pacote machine — uma abstração de hardware do TinyGo. Ele esconde o trabalho com registradores, mas fornece uma interface Go limpa.
Trabalhando com GPIO e interrupções
\nEm projetos reais, não basta apenas piscar. É preciso ler botões, sensores, reagir a eventos. Em Go para sistemas embarcados, as interrupções são implementadas através de canais e goroutines. Exemplo: vamos criar um handler para o pressionamento de um botão.
\npackage main\n\nimport (\n "machine"\n "fmt"\n)\n\nfunc main() {\n button := machine.D2\n button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})\n\n // Aguarda o pressionamento (pin == LOW, pois há pull-up para VCC)\n for {\n if !button.Get() {\n fmt.Println("Botão pressionado!")\n }\n }\n}\n\nMas isso é um busy loop. Quer algo mais eficiente? Use interrupção através de canal:
\nfunc main() {\n button := machine.D2\n button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})\n\n // Canal para eventos\n ch := make(chan bool, 1)\n button.SetInterrupt(machine.PinFalling,