golang on the raspberry pi, part 2

raw_ht16k33 from William Beebe on Vimeo.

This is a bit more Go and Gobot-based sophisticated application. This application opens and writes using “raw” calls to Adafruit FeatherWing displays using the HT16K33 I2C display driver chip. It has the ability to check to make sure the device is really there, and you can pass more than one device address on the command line. To make the video, I executed “raw_ht16k33 0x70 0x71” and the application dutifully turned on every segment on the display at address 0x70, then 0x71.

Copyright (c) 2018 William H. Beebe, Jr.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at


Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and
limitations under the License.

package main

import (


const DEFAULT_ADDRESS int = 0x70

// Commands to send the HT16K33
const (
    HT16K33_SYSTEM_SETUP byte = 0x20
    HT16K33_OSCILLATOR_ON byte = 0x01
    HT16K33_DISPLAY_SETUP byte = 0x80
    HT16K33_DISPLAY_ON byte = 0x01
    HT16K33_BLINK_OFF byte = 0x00
    HT16K33_BLINK_2HZ byte = 0x02
    HT16K33_BLINK_1HZ byte = 0x04
    HT16K33_BLINK_HALFHZ byte = 0x06
    HT16K33_CMD_BRIGHTNESS byte = 0xE0

var address = DEFAULT_ADDRESS
var device i2c.Connection

func initialize() (device i2c.Connection, err error) {
    adapter := raspi.NewAdaptor()
    bus := adapter.GetDefaultBus()

    // Check to see if the device actually is on the I2C buss.
    // If it is then use it, else return an error.
    if device, err := adapter.GetConnection(address, bus) ; err == nil {
        if _, err := device.ReadByte() ; err == nil {
            fmt.Printf(" Using device 0x%x / %d on bus %d\n", address, address, bus)
        } else {
            return device, fmt.Errorf(" Could not find device 0x%x / %d", address, address)

    device, _ = adapter.GetConnection(address, bus)
    // Turn on chip's internal oscillator.
    device.WriteByte(HT16K33_SYSTEM_SETUP | HT16K33_OSCILLATOR_ON)
    // Turn on the display. YOU HAVE TO SEND THIS.
    device.WriteByte(HT16K33_DISPLAY_SETUP | HT16K33_DISPLAY_ON)
    // Set for maximum LED brightness.
    device.WriteByte(HT16K33_CMD_BRIGHTNESS | 0x0f)
    return device, nil

func lightAll() {
    // First four digits for Alphanumeric and 8x16 Matrix
    // FeatherWing Displays.
    // The 'digit' address is the address/offset into the
    // HT16K33's internal eight byte array. Each bit
    // represents a segment or LED, each address a section
    // within an entire device
    // Digit 0
    device.WriteWordData(0, 0xFFFF)

    // Digit 1
    device.WriteWordData(2, 0xFFFF)

    // Digit 2
    device.WriteWordData(4, 0xFFFF)

    // Digit 3
    device.WriteWordData(6, 0xFFFF)

    // Rest of the bytes for the
    // Adafruit 0.8" 8x16 LED Matrix FeatherWing Display
    device.WriteWordData(8, 0xFFFF)
    device.WriteWordData(10, 0xFFFF)
    device.WriteWordData(12, 0xFFFF)
    device.WriteWordData(14, 0xFFFF)

func darkenAll() {
    // Turn off every segment on every digit.
    var data []byte = make([]byte, 16)
    device.WriteBlockData(0, data)

func main() {
    // Hook the various system abort calls for us to use or ignore as we
    // see fit. In particular hook SIGINT, or CTRL+C for below.
    signal_chan := make(chan os.Signal, 1)

    // We can pass zero to many addresses on the command line.
    // For example, 'raw_ht16k33 0x70 0x71' will turn on both
    // displays, one after the other, if they are both attached.
    // If either one is not there or unreachable, the application
    // will abort.
    var addresses []int

    for _, arg := range os.Args[1:] {
        if newAddress, err := strconv.ParseInt(arg, 0, 32); err == nil {
            addresses = append(addresses, int(newAddress))
        } else {

    // If nothing passed on the command line then use the default
    // address/
    if len(addresses) == 0 {
        addresses = append(addresses, DEFAULT_ADDRESS)

    // We want to capture CTRL+C to first clear the display and then exit.
    // We don't want to leave the display lit on an abort.
    go func() {
        for {
            signal := <-signal_chan
            switch signal {
            case syscall.SIGINT:
                // CTRL+C

    // Iterate over all the addresses passed on the command line (or not),
    // aborting if any of the devices are unreachable.
    for _, addr := range addresses {
        address = int(addr)

        dev, err := initialize()
        if err != nil {

        device = dev
        time.Sleep(2 * time.Second)


This is also one of those little posts where I get to pull together a few other skills I’ve picked up along the way. The video was recorded with an Olympus E-M5 and mZuiko 17mm f/1.8 lens in manual focus mode. The video was quickly put together on my iPad Pro Mk 1, using iMovie, then pushed up to Vimeo where it’s linked above.