In a Blinkt!, Golang on a Raspberry Pi

The Blinkt! by Pimoroni is a relatively inexpensive 8 LED display which gently introduces GPIO programming concepts. Pimoroni provide their own library and examples based on Python. There are various library ports available on github and this article will focus on the Golang library created by Alex Ellis.

As Alex's Blinkt! go examples repo welcomes pull requests I thought it an ideal opportunity to get hands on with Golang and port some of the Pimoroni Python examples. Ultimately this led to the base library also being enhanced, bringing it close to the API offered by the Pimoroni Python Library.


The simple answer is that I wanted to get hands on with Golang and from my perspective porting existing code from one language to another offers a means by which to focus more on the target language syntax and semantics than the program's algorithm; this can often remain similar to the source program.

It has to be said that Alex was also extremely helpful in reviewing code and providing guidance. He even blogged around one of the pull requests to demonstrate squashing and merging.


Initially my approach was to use the library as was and start porting the simpler examples. This to me was logical because using the library to build the examples meant my understanding of the library naturally increased. By the time the features offered by the library were no longer sufficient to port the next example, my understanding of Golang and the library had increased enough for me to start making changes to the library. I took a bit of a leap here because the library repo wasn't explicit on whether PRs were welcome or not.

What changed?

New function

The very first change was to introduce a function to set all the pixels to the same values:

func (bl Blinkt) SetAll(r int, g int, b int) {  
     for i, _ := range bl.pixels {
         bl.SetPixel(i, r, g, b)

This had a couple of effects: not only would the public function be directly accessible from the examples, but existing library functions could be refactored to call this rather than employing their own loops to do the same.


The next change was one that Alex suggested. There was repetition in the examples around clearing the pixels when the program exited & introducing time delays during execution. Given the repetition the obvious next step was to move these into the library and make them public. I learnt a lot here about aliasing imports as the package was using dot notation on the single package it imported and we had reused the name of a function (Delay) used in a lower level package meaning leading to some odd behaviour. For example:

import . ""  

Meant that calls to exposed functions could be made without fully referencing the package:

func pulse(pulses int) {  
     DigitalWrite(GpioToPin(DAT), 0)

However, removing the dot meant that references to functions provided by that package had to be qualified:

import ""

func pulse(pulses int) {  
     rpi.DigitalWrite(rpi.GpioToPin(DAT), 0)

The final merge was by far the largest change. The handling of brightness in library had been static after being set during instantiation. This was by far the biggest limitation to completing some of the elaborate examples.


There had always been an ambition to align more closely with the mode of operation of the Pimoroni library, and that meant changing the supplied brightness value from an int to a float between 0.0 and 1.0. It also meant that we needed to mirror the availability of a default brightness value, which presented a challenge as Golang doesn't accommodate optional parameters. The closest mechanism to achieve a similar effect is through the use of Variadic parameters and testing the argument for length:

func NewBlinkt(brightness ...float64) Blinkt {

    brightnessInt := defaultBrightnessInt

    if len(brightness) > 0 {
        brightnessInt = convertBrightnessToInt(brightness[0])

The brightness parameter here is a slice of floats, so we interrogate the slice to determine a non-zero length and take the first value we find. It's worth pointing out at this stage that the supplied value is tested for validity within the convertBrightnessToInt function.


This again extended my learning as my initial approach to implementation was to refactor the existing functions to allow for the additional brightness value to be passed. However, discussions in the open with Alex led me towards method chaining. This was a great idea as it would limit the amount of refactoring existing users would have to undertake whilst still making dynamic brightness available to them. To achieve this the existing methods needed to be adapted to return a Blinkt object which would act as an input into the subsequently chained method.

The existing methods such as SetPixel were changed from:

func (bl *Blinkt) SetPixel(p int, r int, g int, b int) {  


func (bl *Blinkt) SetPixel(p int, r int, g int, b int) *Blinkt {  

This means that at the time of setting a pixel colour we can also set the brightness, thus:

blinkt.SetAll(128, 0, 0).SetBrightness(0.5)  

Furthermore, SetBrightness also returns a Blinkt object, so the two methods are interchangable:

blinkt.SetBrightness(0.5).SetAll(128, 0, 0).SetBrightness(0.5)  

Here I am celebrating initial success:


At this point a decision was made to also implement some constant values to remove magic numbers and aid readability of the code:

for i, _ := range pixels {  
                  pixels[i][0] = 0
                  pixels[i][1] = 0
                  pixels[i][2] = 0
                  pixels[i][3] = brightness                

With the addition of constants became:

for p, _ := range pixels {  
                  pixels[p][redIndex] = 0
                  pixels[p][greenIndex] = 0
                  pixels[p][blueIndex] = 0
                  pixels[p][brightnessIndex] = brightness

Closing thoughts

This was my first real venture into the open source community, and I like to consider it a genuine success. Alex was a really big help with ideas, and thankfully was both understanding & patient with the git/repo aspects.

With the changes described it will be worth examining a worked example, so look out for the next part where I'll build a Blinkt! program to demonstrate the use of the library. I'll also employ Docker to build and run the program in a container, thus removing the need for a local Golang build environment.

Show Comments