Advanced Github Actions for Arduino

Wouldn’t it be great if you could automate all your arduino compilations, even when the preprocessor definitions have to change? You CAN. Let me show you how.

And….Action!

Github Actions make it possible to test my code every time I push an update. The examples provided work great for testing one sketch that basically never changes. But what if you have Makelangelo firmware, code that can be run on many different controllers and many different kinematic linkages? Here now I present my method to you. If you find it helpful, like share subscribe Patreon Instragram facebook twitter smoke signals etc etc etc. Capiche? Neato.

Use case: Makelangelo firmware

Makelangelo-firmware knows which motherboard you have and which kinematic linkage because they are defined at the top of configure.h. Immediately after that the code looks for local_config.h and includes it only if the file exists.

//------------------------------------------------------------------------------
// YOUR CHANGES GO HERE
//------------------------------------------------------------------------------

#if __has_include("local_config.h")
// Your local changes go here.
// Do not send your local_config.h in a pull request.  Thank you!
#include "local_config.h"
#endif

This is great for me because I can make sure local_config.h is never in the Github repository. All your changes go there and nobody loses their settings when an update is pushed out. It also means Github Actions can generate local_config.h as needed. More on that in a minute.

The Action is a script that sets up and then runs arduino-cli, the Arduino Command Line Interface. If you want to build arduino code on a raspberry pi over the internet or on a box that has no monitor then this is the tool I recommend.

First things first, we’ll need to name our Action and say when it happens. I called it “Arduino CI” where CI is short for Continuous Integration.

name: Arduino CI

on: push

Next I need to set up a matrix – a table that explains which boards I want, which kinematic models I want, and which combinations are legal. the list below generates 19 different tests. if I allowed all possible combinations on all boards I’d have 72 tests run!

For every board I also need to know the Fully Qualified Board Name (FQBN), a magic word that the Arduino compiler demands. You’ll also see I’ve used exclude to ban bad motherboard-kinematic combinations. A board that can only run 4 motors (like RAMPS) should not attempt to compile for a 6 motor machine like SIXI or a Stewart platform.

jobs:
  build:
    strategy:
      matrix:
        board: [BOARD_RUMBA, BOARD_RAMPS, BOARD_SIXI_MEGA ]
        # we cannot yet compile BOARD_ESP32, BOARD_WEMOS, 
        # BOARD_TEENSYLU, BOARD_CNCV3, or BOARD_SANGUINOLULU yet.
        robot: [POLARGRAPH, TRADITIONALXY, COREXY, ZARPLOTTER, SKYCAM, DELTA, STEWART, ARM3, SIXI, TRADITIONAL6, SCARA]
        include:
          - board: BOARD_RUMBA
            arduino-platform: arduino:avr
            fqbn: arduino:avr:mega
          - board: BOARD_RAMPS
            arduino-platform: arduino:avr
            fqbn: arduino:avr:mega
          - board: BOARD_SIXI_MEGA
            arduino-platform: arduino:avr
            fqbn: arduino:avr:mega
          #- board: BOARD_ESP32
          #  arduino-platform: esp32:esp32
          #  fqbn: esp32:esp32:lolin32
          #- board: BOARD_WEMOS
          #  arduino-platform: esp8266:esp8266
          #  fqbn: esp8266:esp8266:lolin32
        exclude:
          - board: BOARD_RUMBA
            robot: SIXI
            # BOARD_RAMPS can only handle 5 motors
          - board: BOARD_RAMPS
            robot: SIXI
          - board: BOARD_RAMPS
            robot: STEWART
          - board: BOARD_RAMPS
            robot: TRADITIONAL6
            # BOARD_SIXI_MEGA only meant for sixi
            # I wish I could say robot: [ a, b, c, d, e, f, g ]
          - board: BOARD_SIXI_MEGA
            robot: POLARGRAPH
          - board: BOARD_SIXI_MEGA
            robot: TRADITIONALXY
          - board: BOARD_SIXI_MEGA
            robot: COREXY
          - board: BOARD_SIXI_MEGA
            robot: ZARPLOTTER
          - board: BOARD_SIXI_MEGA
            robot: SKYCAM
          - board: BOARD_SIXI_MEGA
            robot: DELTA
          - board: BOARD_SIXI_MEGA
            robot: STEWART
          - board: BOARD_SIXI_MEGA
            robot: ARM3
          - board: BOARD_SIXI_MEGA
            robot: TRADITIONAL6
          - board: BOARD_SIXI_MEGA
            robot: SCARA

Ok! Now we have our allowed combinations, what do we do with them?

Firstly we’ll tell Github that everything is going to be run in a Docker container running the latest Ubuntu. Then we get into the steps.

    runs-on: ubuntu-latest
    steps:
      # First of all, we clone the repo using the checkout action.
      - name: Checkout
        uses: actions/checkout@master

As I understand it every action has to have @[version] to better control the results. For this reason I was a little hesitant to use the ubuntu-latest docker instead of a specific ubuntu version. I don’t know if that will come back to haunt me, place your bets.

      # We use the arduino/setup-arduino-cli action to install and
      # configure the Arduino CLI on the system.
      - name: Setup Arduino CLI
        uses: arduino/[email protected]

      # We then install arduino
      - name: Install platform
        run: arduino-cli core update-index

      # Install the 'core', the piece of custom code that handles our motherboard of choice.
      - name: Add core(s)
        run: arduino-cli core install ${{ matrix.arduino-platform }} 
        
      # Add some libraries we need
      - name: Add SDFat
        run: arduino-cli lib install SdFat
        
      - name: Add TMC2130Stepper
        run: arduino-cli lib install TMC2130Stepper
      
      - name: Add LiquidCrystal
        run: arduino-cli lib install LiquidCrystal

      # Make the local_config.h file!
      - name: Add local_config.h
        uses: DamianReeves/[email protected]
        with:
          path: ./local_config.h
          contents: "#pragma once\n#define MOTHERBOARD ${{matrix.board}}\n#define MACHINE_STYLE ${{matrix.robot}}"
          write-mode: overwrite 
          
      # Finally, we compile the sketch, using the FQBN that was set
      # in the build matrix.
      - name: Compile Sketch
        run: arduino-cli compile --fqbn ${{ matrix.fqbn }} ./Makelangelo-firmware.ino

If all goes well the output looks a little like this.

Makelangelo-firmware Arduino Continuous Integration Success in Github Actions

Final thoughts

You can find the latest version on the Github repository for Makelangelo-firmware. It took several days of chiselling at the problem to get it working, and then I needed a walk outside before I could face all the bugs that the CI system found.

So in the end as long as it works I should have more robust code, fewer lingering doubts, and happier users. Worth it!

Special thanks to Github user per1234 for helping me through the wilderness.