Go und Hardware: Warum das kein Scherz ist?
\nWenn man den Begriff „Embedded-Entwicklung“ hört, stellt man sich normalerweise C, Assembler und mühsames Debuggen über eine LED vor. Aber die Welt steht nicht still. Heute klopft Go (oder Golang) immer selbstbewusster an die Tür der Mikrocontroller-Welt. Und es klopft nicht ohne Grund, sondern mit Goroutinen, Garbage Collector und eingebauten Interfaces.
\nIn diesem Artikel schauen wir uns an, wie Embedded-Entwicklung mit Go aussieht, schreiben echten Code für einen hypothetischen Mikrocontroller und verstehen, ob sich der Aufwand lohnt. Spoiler: Ja, wenn Sie Speicherlecks satt haben und lesbaren Code lieben.
\n\nWarum Go und nicht C? Drei Säulen
\nKlassisches Embedded ist C. Aber Go hat drei Killer-Features, die Ingenieure dazu bringen, ihre Gewohnheiten zu überdenken:
\n- \n
- Speichersicherheit: Keine wilden Zeiger, die ins All abheben können. Der Go-Compiler ist strenger als ein C-Compiler. \n
- Goroutinen: Leichtgewichtige Threads, die sich perfekt in die ereignisgesteuerte Architektur von Mikrocontrollern einfügen. \n
- Plattformunabhängigkeit: Sie schreiben Code auf einem Mac und kompilieren für ARM Cortex-M. Ohne großen Aufwand. \n
Natürlich gibt es einen Haken: Natives Go (gc) kann nicht für kleine MCUs kompilieren. Dafür gibt es das Projekt TinyGo – einen Compiler, der Go für Hardware mit Kilobyte-Speicher anpasst. Darauf konzentrieren wir uns.
\n\nEinrichtung der Umgebung: TinyGo und Ihr Board
\nBevor wir Code schreiben, bereiten wir den Arbeitsplatz vor. Für die Embedded-Entwicklung mit Go benötigen Sie:
\n- \n
- TinyGo installieren:
brew install tinygo(oder von der offiziellen Website herunterladen). \n - Das Toolchain für Ihr Board installieren (z. B. ARM GCC für STM32). \n
- Das Board über USB anschließen. \n
Überprüfen, ob alles funktioniert:
\ntinygo version\ntinygo: 0.31.0 (linux/amd64)\n\nJetzt können Sie Code für jedes unterstützte Board kompilieren: tinygo build -target=arduino-nano -o firmware.hex main.go.
Erster Blink: Eine LED mit Go
\nDer Klassiker. Wir verbinden uns mit einem Pin und lassen ihn blinken. So sieht Mikrocontroller-Programmierung mit Go aus:
\npackage main\n\nimport (\n "machine"\n "time"\n)\n\nfunc main() {\n // Die eingebaute LED ist normalerweise an Pin 13 (oder 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\nKompilieren und auf das Board flashen:
\ntinygo flash -target=arduino-nano -port=/dev/ttyUSB0 main.go\n\nVoilà! Die LED blinkt. Beachten Sie: Wir verwenden das Paket machine – die Hardware-Abstraktion von TinyGo. Sie verbirgt die Arbeit mit Registern, bietet aber eine saubere Go-Schnittstelle.
Arbeit mit GPIO und Interrupts
\nIn echten Projekten reicht bloßes Blinken nicht. Man muss Taster, Sensoren lesen und auf Ereignisse reagieren. In Go für eingebettete Systeme werden Interrupts über Kanäle und Goroutinen realisiert. Beispiel: Wir hängen einen Handler für einen Tastendruck an.
\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 // Warten auf Tastendruck (pin == LOW, da Pullup zur Versorgung)\n for {\n if !button.Get() {\n fmt.Println("Taste gedrückt!")\n }\n }\n}\n\nDas ist aber eine Busy-Loop. Wollen wir effizienter sein? Verwenden wir einen Interrupt über einen Kanal:
\nfunc main() {\n button := machine.D2\n button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})\n\n // Kanal für Ereignisse\n ch := make(chan bool, 1)\n button.SetInterrupt(machine.PinFalling,