Building a Firmware Image and Flashing a Device from a GitHub Workflow

Before running a system integration test on an embedded device, the device needs to be flashed with the firmware. Here’s how to do this from a GitHub Workflow.

First we need to checkout the firmware and build it. Checking out the project is easy with the {% c-line %}checkout{% c-line-end %} action from GitHub. Building it can be a little trickier, but if you have taken the time to Dockerize your build environment, building the project is also easy. Here’s an example Job for checking out and building a project using a Docker build environment.

{% c-block language="yaml" %}
build_firmware:
   name: Build firmware
   runs-on: ubuntu-20.04
   container:
     image: docker://lagerdata/devenv-cortexm:latest

   steps:
     - name: Checkout project
       uses: actions/checkout@v4
       with:
         fetch-depth: '0'
         submodules: 'recursive'

     - name: Build
       run: mkdir -p _build;cd _build;cmake .. -G Ninja;cmake --build .
{% c-block-end %}

The {% c-line %}CMake{% c-line-end %} build command {% c-line %}mkdir -p _build;cd _build;cmake .. -G Ninja;cmake --build .{% c-line-end %} is run inside the {% c-line %}lagerdata/devenv-cortexm:latest{% c-line-end %} Docker image so no setup has to be done on the GitHub runner itself.

Once the project is built we can flash our device using either the Lager CLI command {% c-line %}lager debug flash{% c-line-end %} or with a method from Lager’s Python library, {% c-line %}dut.flash("path/to/image.hex"){% c-line-end %}.

CLI Command

{% c-block language="yaml" %}
build_firmware:
   name: Build firmware
   runs-on: ubuntu-20.04
   container:
     image: docker://lagerdata/devenv-cortexm:latest

   steps:
     - name: Checkout project
       uses: actions/checkout@v4
       with:
         fetch-depth: '0'
         submodules: 'recursive'

     - name: Build
       run: mkdir -p _build;cd _build;cmake .. -G Ninja;cmake --build .

     - name: Power Device
       run: lager supply V_IN enable --yes

     - name: Flash Device
       run: lager debug flash --hexfile _build/image.hex
{% c-block-end %}

In the above Workflow, we first power up the device using {% c-line %}lager supply{% c-line-end %} which allows us to control power to the device

And then we flash the image that was built in the previous step, {% c-line %}image.hex{% c-line-end %}.

Python

Or we could combine both those steps into a small python script:

{% c-block language="python" %}
from lager import lager
from lager.pcb.net import Net, NetType

power_net = Net.get('V_IN', type=NetType.PowerSupply) # Used to control power
power_net.enable() # Power the device
dut = lager.DUT() #Create a device under test object
dut.flash("_build/image.hex") #flash the device
{% c-block-end %}

Assuming this script is saved in the project as {% c-line %}tests/flash.py{% c-line-end %} you could then run

{% c-block language="yaml" %}
build_firmware:
   name: Build firmware
   runs-on: ubuntu-20.04
   container:
     image: docker://lagerdata/devenv-cortexm:latest

   steps:
     - name: Checkout project
       uses: actions/checkout@v4
       with:
         fetch-depth: '0'
         submodules: 'recursive'

     - name: Build
       run: mkdir -p _build;cd _build;cmake .. -G Ninja;cmake --build .
       
     - name: Power and Flash Device
       run: lager python tests/flash.py
{% c-block-end %}

And that’s it!

Reach out to learn more about Lager's hardware test automation platform.

Try One Month Free

hello@lagerdata.com