TinyGo: A Go Compiler for Micro-controllers

For micro-controllers like the Arduino or the BBC Micro:bit there are a choice of different programming languages, and now Go (or Golang) can be used for embedded solutions.

Go is an open source language that was developed and supported by Google. Go is a compiled language that runs on MS-Windows, MacOS and Linux.

The TinyGo project implements the exact same Go programming language, however TinyGo uses a different compiler that creates applications on a variety of different micro-controllers.

In this blog I wanted to document some of my notes on building an apps that ran on Arduino and a BBC Micro:bit hardware.

Some key take-aways are:

  • TinyGo is easy to install and flash packages to a micro-controller.
  • The same Go program can potentially run on totally different controllers (some comments on this below).
  • The TinyGo project is still in development so it’s missing some common libraries, full Wifi support being the most important one.

Getting Started

For your specific installation requirements see: https://tinygo.org/getting-started/install/

The typical TinyGo program will have three sections: 1) a definition of libraries used, 2) a setup of hardware and variables, and 3) an infinite loop to run some actions:

Arduino Uno 7 Segment Display Project

For a first project I wanted to show some numbers on a 7 segment display with an Arduino Uno.

The TinyGo site has a list of supported devices: https://tinygo.org/docs/reference/devices/. It’s important to realize that not all the libraries are support on all micro-controllers. There are examples for these drivers at: https://github.com/tinygo-org/drivers/tree/release/examples.

Unfortunately there aren’t examples for all the drivers, so in some cases I needed to look at the source code and reference examples done in Arduino C.

Below is the Go source for a program that will setup the tm1637 segment display on :

//
// Example program to write to a 7 segment display
//
package main

import (
    "machine"
    "time"
    "tinygo.org/x/drivers/tm1637"
)

func main() {
    // Define hardware, clk on pin D2, di (data) on pin D3
    mydisplay := tm1637.New(machine.D2, machine.D3, 7) 

    println("Starting...")
    val := 1

    for {       
        val = val + 1
        print("val: ")
        println(val)
        mydisplay.ClearDisplay()
        mydisplay.DisplayNumber(int16(val))
        time.Sleep(time.Millisecond * 1000)
    }
}

To run this example I needed to create a new directory with the source, initialize the project, get libraries and then build/flash to the controller:

# make a directory for my project
mkdir tm1637
cd tm1637
# initialize a Go project, this project is: tm1637_uno
go mod init tm1637_uno
# get tinygo.org/x/drivers/tm1637 for this app
go get tinygo.org/x/drivers/tm1637
# tidy up project file with tinygo.org/x/drivers/tm1637
go mod tidy
# build and flash project to Arduino 
tinygo flash  -target=arduino tm1637_uno.go  

There are some options on the tinygo compiler, for this example flash -target=arduino will download the application to any Arduino hardware (Uno, Mega, Nano etc.). Tinygo will find the controller so you don’t need to specific a port, however if you have multiple Arduino modules connected you can use the -port option to specify a port.

USB Serial Output

Typically you include USB Serial print statements for debugging. This output can be viewed using a number of different packages, like the Arduino IDE monitor option.

If you are working in a Linux shell, a couple of lines of bash can be used:

# in Linux the default port is: /dev/ttyACM0
# the Arduino Uno default speed is 9600
stty -F  9600 
while read line < /dev/ttyACM0; do echo "$line"; done

Another option is to write a Go USB Serial program. This option has the advantage in that a terminal based program can be used to create custom views of the micro-controller data (I’ll do this later and add some bar charts).

Below is the Go source code for a USB serial interface:

//
// USB Serial program to output data from a Micro-controller
//
package main

import "log"
import "github.com/tarm/serial"
import	"bufio"
// adjust port and speed for your setup
const SERIAL_PORT_NAME = "/dev/ttyACM0"
const SERIAL_PORT_BAUD = 9600

func main() {
	conf := &serial.Config{Name: SERIAL_PORT_NAME, Baud: SERIAL_PORT_BAUD}
	ser, err := serial.OpenPort(conf)
	if err != nil {
		log.Fatalf("serial.Open: %v", err)
	}
	scanner := bufio.NewScanner(ser)
	for scanner.Scan() {
          println(scanner.Text())
	}
	if scanner.Err() != nil {
	  log.Fatal(err)
	}
}

To build this project (source file is: main.go), the standard Go (not TinyGo) compiler is used :

# create a source directory and put main.go into this directory
mkdir USB_Serial
cd USB_Serial
# initialize the project
go mod init main
# get the serial library
go get github.com/tarm/serial
go mod tidy
# build the file, call the executable: usbserial
go build -o usbserial main.go
# run the file and see some sample output
./usbserial

val: 918
val: 919
val: 920
...

Micro:bit Sensor Project

For my next project I wanted to do something more complex. I connected to a BME280 temperature/pressure/humidity sensor and presented the results on 16×2 LCD display, and on a USB serial connection to a laptop.

Both BME280 sensor and the 16×2 LCD display used the I2C bus, so I used a small breadboard to connect the SDA and SCL pins together. It’s important to note that the Micro:bit only has 3.3V power so 5V LCD display units won’t work. My LCD display uses the hd44780i2c driver.

My project code was as follows:

//
// project1.go - show sensor data on a 2x16 LCD Display
//
package main

import (
	"machine"
	"strconv"
	"time"
	"tinygo.org/x/drivers/bme280"
    "tinygo.org/x/drivers/hd44780i2c"
)

func main() {
      // setup sensors
	  machine.I2C0.Configure(machine.I2CConfig{})
	  sensor := bme280.New(machine.I2C0)
	  sensor.Configure()

	connected := sensor.Connected()
	if !connected {
		println("BME280 not detected")
	}
	println("BME280 detected")
        // setup 2x16 screen
        mydisplay := hd44780i2c.New(machine.I2C0, 0x3F)
        mydisplay.Configure(hd44780i2c.Config{Width:16 , Height:2,})
        mydisplay.CursorOn(false)

	for {
		mtemp, _ := sensor.ReadTemperature()
        temp := int(mtemp/1000)

		mpress, _ := sensor.ReadPressure()
		press :=int(mpress/100000)

		mhum, _ := sensor.ReadHumidity()
        hum := int(mhum/100)

        mydisplay.ClearDisplay()
		msg :=  strconv.Itoa(temp) + " C  " + strconv.Itoa(press) + " hPa\n" + strconv.Itoa(hum) + " %"
        println(temp, press, hum)
        mydisplay.Print( []byte(msg) )
		time.Sleep(2 * time.Second)
	}
}

The serial output was sent as integers with no labels or units, I did this for the next step and that was to create a custom terminal program.

Terminal Output

There are a number of different options on how to present the serial output.

I wanted to keep things basic so I used the earlier USB serial code and I included some ANSI terminal codes.

Below is the Go source. I added some ANSI constants for clearing, resetting and color definitions.

The screen will refresh every time some new data is received from the Microbit.

package main

import "fmt"
import "log"
import "strings"
import "github.com/tarm/serial"
import "bufio"
import "strconv"

const SERIAL_PORT_NAME = "/dev/ttyACM0"
const SERIAL_PORT_BAUD = 115200

const clear = "\033[2J"
const reset = "\033[0m"
const uline = "\033[4m"
const nouline = "\033[24m"

// define some foreground colors
const red = "\033[38;5;1m"
const green = "\033[38;5;10m"
const magenta = "\033[38;5;13m"
const purple = "\033[38;5;5m"
const yellow = "\033[38;5;11m"

// Use ANSI color and fill character to show scaled bars with values 
func show_bar(inval string, label string, units string, toprange int, scale int, color string) { 
    bar1, _ := strconv.Atoi(inval) ; // convert USB serial text to int
    bar1 = bar1 * 50 / toprange    ; // scale string input to bar
    bar2 := scale - bar1
    print(color)
    fmt.Printf("%15s %s%s %s %s\n\n", label, strings.Repeat("█",bar1), strings.Repeat("░",bar2), inval, units)

}

func main() {
    conf := &serial.Config{Name: SERIAL_PORT_NAME, Baud: SERIAL_PORT_BAUD}
    ser, err := serial.OpenPort(conf)
    if err != nil {
        log.Fatalf("serial.Open: %v", err)
    }
    scanner := bufio.NewScanner(ser)
    for scanner.Scan() {
        println(clear, uline)
        println(" Micro:Bit Sensor Values ",nouline,"\n")
        values := strings.Split(scanner.Text(), " ")

	show_bar(values[0],"Temperature:","C", 40, 50, magenta)
        show_bar(values[1],"Pressure:","kPa", 1020, 50, green)
        show_bar(values[2],"Humidity:","%", 100, 50, yellow)
        println(reset) ; // Put terminal back to reset mode
    }
    if scanner.Err() != nil {
        log.Fatal(err)
    }
}

If you are looking at doing some more creative terminal programs there are some great libraries, for example: https://github.com/gizak/termui

Same Code on Different Modules

When I looked at running the same Go code on different modules I noticed:

  • Different hardware had different machine libraries, for example Microbit defines pins as P1, P2… , but Arduino defines pins as D1, D2 …. This will unfortunately means the same code can’t run directly.
  • I2C bus interfaces use SCL and SDA definitions which are uniform for all controllers, so my 2nd project would run on probably all the platforms without any code changes.
  • Libraries are hardware dependent, so a library that works on an Arduino may not work on other devices

Final Comments

Because TinyGo is still in development I found it a little challenging because of the lack of documentation. I especially missed having a Wifi library.

For Arduino I would definitely stick to the default C/C# interface, however for the Microbit C/C# isn’t a good option so I can see using TinyGo.

If you are looking at learning Go and you want to start with some small projects TinyGo might be a good option.

Raspberry Pi and Go Programming

Go or GoLang is a compiled programming language developed by Google.

Go is widely used for Web Development and it is one of the fastest growing programming languages today. Unlike Java which is runs in a JVM (Java Virtual Machine) , Go compiles directly to Windows, OS X or Linux  executable files. 

In this blog we will look at creating two Go programs that talk to Raspberry Pi GPIO. The first will be a simple keyboard input program and the second will be a standalone Go web app to control GPIO pins.

Installation

To install go enter:

sudo apt-get install golang

To test that the install is working you can check the Go version number:

$ go version
go version go1.7.4 linux/arm

A “Hello World” example (hello.go) is:


package main

import "fmt"

func main() {

  fmt.Println("Hello World");

}

The hello.go code is compiled and ran by:

$ go build hello.go  # compile the go code

$ ./hello   # run the go code

Hello World

Raspberry Pi GPIO

There are a number of different ways to have Go connect to the Pi General Purpose Inputs and Outputs (GPIO). For this example I am going to look at shelling out to the gpio command line utility, but there are also go rpi libaries that can be used.

For testing I like to use the gpio utility because it offers a good selection of commands and I can manually test and verify the command before I use them in my Go code. For help on gpio  use the -h option.

The Raspberry Pi hardware setup used an LED with a resistor on physical pin 7 (BCM pin 4).

Led_setu

Our first go program (keyin.go) will read keyboard input and then to shell out twice to gpio, first time to write a value and the second time to read the value back.

package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "os"
)

func main() {
    // Get keyboard input
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter value for GPIO pin 7 : ")
    pinval, _ := reader.ReadString('\n')

    // Write to GPIO pin 7 using keyboard input 
    testCmd := exec.Command("gpio","write", "7", pinval)
    testOut, err := testCmd.Output()
    if err != nil {
        println(err)
    }
    // Read back the GPIO pin 7 status
    testCmd = exec.Command("gpio","read", "7")
    testOut, err = testCmd.Output()
    if err != nil {
        println(err)
    } else {
      fmt.Print("GPIO Pin 4 value : ")
      fmt.Println(string(testOut))
    }
}

To compile and run the keyin.go program:

 $ go build keyin.go
 $ ./keyin
Enter value for GPIO pin 7 : 1
GPIO Pin 4 value : 1

Simple Go Web Application

For a starting example we’ll make a go web application (web_static.go) show a web page called web_static.html.

The web_static.html file will be :

<html>
  <head>
    <title>GO PI Static Page</title>
  </head>
  <body>
    <h1>GO PI Static Page</h1>
    <hr>
    This is a static test page
  </body>
</html>

The web_static.go program will need to import the “net/http” library. A http.HandleFunc call is used to look for the default address “/” and serve up our web_static.html file. The http.ListenAndServe function listens for web requests on port 8081.

package main

import (
    "log"
    "net/http"
)

func main() {
    // Create default web handler, and call a starting web page
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, "web_static.html")
        println("Default Web Page")
    })
    // start a listening on port 8081
    log.Fatal(http.ListenAndServe("8081", nil))

}

The Go code can be compiled and run by:

$ go build web_static.go
$ ./web_static
Default Web Page

From a web browse pointed at the Raspberry Pi on port 8081, our web page will show as:

web_static

Go Web App with Pi GPIO

The next step is to create a Web Page that can pass some parameters. For this application we’ll turn a GPIO output on and off.

A new web page (go_buttons.html) is created with two buttons. HTML anchor tags are used to pass  /on and /off parameters to our Web app.

A CACHE-CONTROL meta tag set to NO-CACHE is needed to ensure that the web page always refreshes.  I also included an EXPIRES meta tag (= 0) so that the browser always see the page as expired . If you don’t include meta tags the web page may only update once.

<html>
  <head>
    <title>GO PI GPIO</title>
    <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
    <META HTTP-EQUIV="EXPIRES" CONTENT="0">
  </head>
  <body>
    <h1>Go Raspberry Pi GPIO Test</h1>
    <a href="/on"><button>Set LED ON</button></a><br>
    <a href="/off"><button>Set LED OFF</button></a>
  </body>
</html>

Our new Go Web app (go_buttons.go) now includes two more http.HandleFunc handler functions, one for /on and for /off. These handler functions call a new function called gpio that is used to write a outputs and read back the output status. 

Our newly created gpio function shells out twice to the gpio command line utility, first time to write a value and the second time to read the value back.

package main

import (
	"log"
	"net/http"
	"os/exec"
)
func gpio( pinval string) {
    testCmd := exec.Command("gpio","write", "7", pinval)  
    testOut, err := testCmd.Output()      
    if err != nil {
        println(err)
    }
    testCmd = exec.Command("gpio","read", "7") 
    testOut, err = testCmd.Output()
    if err != nil {
        println(err)
    } else { 
      print("GPIO Pin 4 value : ")  
      println(string(testOut))
    }
}

func main() {

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "go_buttons.html")
		println("Default Web Page")
	})

	http.HandleFunc("/on", func(w http.ResponseWriter, r *http.Request) {
                 http.ServeFile(w, r, "go_buttons.html")
		 println("Web Page sent : ON")
		 gpio("1")

	})

	http.HandleFunc("/off", func(w http.ResponseWriter, r *http.Request) {
                 http.ServeFile(w, r, "go_buttons.html")
		 println("Web Page sent : OFF")
		 gpio("0")
	})

	log.Fatal(http.ListenAndServe(":8081", nil))

}

To compile and run of web app go_buttons:

$ go build go_buttons.go
$ ./go_buttons

Default Web Page
Web Page sent : ON
GPIO Pin 4 value : 1

Default Web Page
Web Page sent : OFF
GPIO Pin 4 value : 0

The web page should look something like:

go_buttons

Summary

For a polished application I would rather use a native Go library for GPIO calls, but for prototyping I found that the gpio command line utility to be easier for trouble-shooting.

After getting a basic web page working for anchor tags for /on and /off, the next step will be to use some Javascript with AJAX to show dynamic values.