Go y el hardware: ¿por qué no es una broma?
\nCuando escuchas la frase «desarrollo embebido», normalmente te imaginas C, ensamblador y una depuración dolorosa a través de un LED. Pero el mundo no se detiene. Hoy en día, Go (o Golang) está llamando cada vez con más confianza a la puerta del mundo de los microcontroladores. Y no llama sin razón, sino con gorutinas, recolector de basura e interfaces integradas.
\nEn este artículo, analizaremos cómo se ve el desarrollo embebido en Go, escribiremos código real para un microcontrolador hipotético y entenderemos si vale la pena. Spoiler: sí, si estás cansado de las fugas de memoria y te gusta el código legible.
\n\n¿Por qué Go y no C? Tres pilares
\nEl embebido clásico es C. Pero Go tiene tres características killer que hacen que los ingenieros reconsideren sus hábitos:
\n- \n
- Seguridad de memoria: nada de punteros salvajes que puedan volar al espacio. El compilador de Go es más estricto que el compilador de C. \n
- Gorutinas: hilos ligeros que se adaptan perfectamente a la arquitectura orientada a eventos de los microcontroladores. \n
- Multiplataforma: escribes código en Mac y compilas para ARM Cortex-M. Sin bailes con tambores. \n
Por supuesto, hay un detalle: Go nativo (gc) no puede compilar para MCU pequeños. Pero existe el proyecto TinyGo, un compilador que adapta Go para hardware con kilobytes de memoria. En eso nos centraremos.
\n\nConfiguración del entorno: TinyGo y tu placa
\nAntes de escribir código, preparemos el espacio de trabajo. Para el desarrollo embebido en Go necesitarás:
\n- \n
- Instalar TinyGo:
brew install tinygo(o descargarlo del sitio oficial). \n - Instalar la cadena de herramientas para tu placa (por ejemplo, ARM GCC para STM32). \n
- Conectar la placa por USB. \n
Verifiquemos que todo funciona:
\ntinygo version\ntinygo: 0.31.0 (linux/amd64)\n\nAhora puedes compilar código para cualquier placa compatible: tinygo build -target=arduino-nano -o firmware.hex main.go.
Primer parpadeo: LED en Go
\nEl clásico del género. Nos conectamos a un pin, movemos la pata. Así se ve la programación de microcontroladores en Go:
\npackage main\n\nimport (\n "machine"\n "time"\n)\n\nfunc main() {\n // El LED integrado suele estar en el pin 13 (o 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 y cargamos en la placa:
\ntinygo flash -target=arduino-nano -port=/dev/ttyUSB0 main.go\n\n¡Voilà! El LED parpadea. Observa: usamos el paquete machine, que es la abstracción de hardware de TinyGo. Oculta el trabajo con registros, pero proporciona una interfaz Go limpia.
Trabajo con GPIO e interrupciones
\nEn proyectos reales, no basta con solo parpadear. Hay que leer botones, sensores, reaccionar a eventos. En Go para sistemas empotrados, las interrupciones se implementan mediante canales y gorutinas. Ejemplo: coloquemos un manejador de pulsación de botón.
\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 // Esperamos la pulsación (pin == LOW, ya que tiene pull-up a alimentación)\n for {\n if !button.Get() {\n fmt.Println("¡Botón presionado!")\n }\n }\n}\n\nPero esto es un bucle ocupado (busy loop). ¿Queremos algo más eficiente? Usamos una interrupción a través de un 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,