@@ -1656,7 +1656,7 @@ for more details.
16561656A complete project source code you can find in
16571657[step-7-webserver](step-7-webserver) directory.
16581658
1659- ## Automatic software and hardware tests
1659+ ## Automated firmware builds (software CI)
16601660
16611661It is a good practice for a software project to have a continuous
16621662integration (CI) test. On every change pushed to the
@@ -1680,10 +1680,149 @@ the repo which breaks a build, Github will notify us. On success, Github will
16801680keep quiet. See an [example successful
16811681run](https://github.com/cpq/bare-metal-programming-guide/actions/runs/3840030588).
16821682
1683+
1684+ # # Automated firmware tests (hardware CI)
1685+
16831686Would it be great to also test built firmware binaries on a real hardware, to
16841687test not only the build process, but that the built firmware is correct and
1685- functional? Easy. See my https://github.com/cpq/continuous-hardware-test
1686- for detailed instructions.
1688+ functional?
1689+
1690+ It is not trivial to build such system ad hoc. For example,
1691+ one can setup a dedicated test workstation, attach a tested device
1692+ (e.g. Nucleo-F429ZI board) to it, and write a piece of software for remote
1693+ firmware upload and test using a built-in debugger. Possible, but fragile,
1694+ consumes a lot of efforts and needs a lot of attention.
1695+
1696+ The alternative is to use one of the commercial hardware test systems (or EBFs,
1697+ Embedded Board Farms), though such commercial solutions are quite expensive.
1698+
1699+ But there is an easy way.
1700+
1701+ # ## Solution: ESP32 + vcon.io
1702+
1703+ And there is a simple and inexpensive way to do it using the https://vcon.io
1704+ service, which implements remote firmware update and UART monitor:
1705+
1706+ 1. Take any ESP32 or ESP32C3 device (e.g. any inexpensive development board)
1707+ 2. Flash a pre-built firmware on it, turning ESP32 into a remotely-controlled programmer
1708+ 3. Wire ESP32 to your target device: SWD pins for flashing, UART pins for capturing output
1709+ 4. Configure ESP32 to register on https://dash.vcon.io management dashboard
1710+
1711+ When done, your target device will have an authenticated, secure RESTful
1712+ API for reflashing and capturing device output. It can be called from anywhere,
1713+ for example from the software CI:
1714+
1715+ ! [](images/ota.svg)
1716+
1717+ The [vcon.io](https://vcon.io) service is run by Cesanta - the company I work
1718+ for. It is a paid service with a freebie quota: if you have just a few devices
1719+ to manage, it is completely free.
1720+
1721+ # ## Configuring and wiring ESP32
1722+
1723+ Take any ESP32 or ESP32C3 device - a devboard, a module, or your custom device.
1724+ Our recommendation is ESP32C3 XIAO devboard
1725+ ([buy on Digikey](https://www.digikey.ie/en/products/detail/seeed-technology-co-ltd/113991054/16652880))
1726+ because of its low price (about 5 EUR) and small form factor.
1727+
1728+ We' re going to assume that the target device is a Raspberry Pi
1729+ [W5500-EVB-Pico](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico)
1730+ board with a built-in Ethernet interface. If your device is different,
1731+ adjust the "Wiring" step according to your device' s pinout.
1732+
1733+ - Follow [Flashing ESP32](https://vcon.io/docs/# flashing-esp32) to flash your ESP32
1734+ - Follow [Network Setup](https://vcon.io/docs/# network-setup) to register ESP32 on https://dash.vcon.io
1735+ - Follow [Wiring](https://vcon.io/docs/# quick-start-guide) to wire ESP32 to your device
1736+
1737+ This is how a configured device breadboard setup may look like:
1738+ ! [](images/breadboard.webp)
1739+
1740+ This is how a configured device dashboard looks like:
1741+ ! [](images/screenshot.webp)
1742+
1743+ Now, you can reflash your device with a single command:
1744+
1745+ ` ` ` sh
1746+ curl -su :$API_KEY ' https://dash.vcon.io/api/v3/devices/$ID/ota' --data-binary @firmware.bin
1747+ ` ` `
1748+
1749+ Where ` API_KEY` is the dash.vcon.io authentication key, ` ID` is the registered
1750+ device number, and ` firmware.bin` is the name of the newly built firmware. You
1751+ can get the ` API_KEY` by clicking on the " api key" link on a dashboard. The
1752+ device ID is listed in the table.
1753+
1754+ We can also capture device output with a single command:
1755+
1756+ ` ` ` sh
1757+ curl -su :$API_KEY ' https://dash.vcon.io/api/v3/devices/$ID/tx?t=5'
1758+ ` ` `
1759+
1760+ There, ` t=5` means wait 5 seconds while capturing UART output.
1761+
1762+ Now, we can use those two commands in any software CI platform to test a new
1763+ firmware on a real device, and test device' s UART output against some expected
1764+ keywords.
1765+
1766+ ### Integrating with Github Actions
1767+
1768+ Okay, our software CI builds a firmware image for us. It would be nice to
1769+ test that firmware image on a real hardware. And now we can!
1770+ We should add few extra commands that use `curl` utility to send a built
1771+ firmware to the test board, and then capture its debug output.
1772+
1773+ A `curl` command requires a secret API key, which we do not want to expose to
1774+ the public. The right way to go is to:
1775+ 1. Go to the project settings / Secrets / Actions
1776+ 2. Click on "New repository secret" button
1777+ 3. Give it a name, `VCON_API_KEY`, paste the value into a "Secret" box, click "Add secret"
1778+
1779+ One of the example projects builds firmware for the RP2040-W5500 board, so
1780+ let' s flash it using a ` curl` command and a saved API key. The best way is
1781+ to add a Makefile target for testing, and let Github Actions (our software CI)
1782+ call it:
1783+ [.github/workflows/test.yml](https://github.com/cpq/bare-metal-programming-guide/blob/8d419f5e7718a8dcacad2ddc2f899eb75f64271e/.github/workflows/test.yml#L18)
1784+
1785+ Note that we pass a ` VCON_API_KEY` environment variable to ` make` . Also note
1786+ that we' re invoking `test` Makefile target, which should build and test our
1787+ firmware. Here is the `test` Makefile target:
1788+ [step-7-webserver/pico-w5500/Makefile](https://github.com/cpq/bare-metal-programming-guide/blob/4fd72e67c380e3166a25c27b47afb41d431f84b9/step-7-webserver/pico-w5500/Makefile#L32-L37)
1789+
1790+ Explanation:
1791+ - line 34: The `test` target depends on `build`, so we always build firmware
1792+ before testing
1793+ - line 35: We flash firmware remotely. The `--fail` flag to `curl` utility
1794+ makes it fail if the response from the server is not successful (not HTTP 200
1795+ OK)
1796+ - line 36: Capture UART log for 5 seconds and save it to `/tmp/output.txt`
1797+ - line 37: Search for the string `Ethernet: up` in the output, and fail if it
1798+ is not found
1799+
1800+ This is the example output of the `make test` command described above:
1801+
1802+ ```sh
1803+ $ make test
1804+ curl --fail ...
1805+ {"success":true,"written":59904}
1806+ curl --fail ...
1807+ 3f3 2 main.c:65:main Ethernet: down
1808+ 7d7 1 mongoose.c:6760:onstatechange Link up
1809+ 7e5 3 mongoose.c:6843:tx_dhcp_discover DHCP discover sent
1810+ 7e8 2 main.c:65:main Ethernet: up
1811+ 81d 3 mongoose.c:6726:arp_cache_add ARP cache: added 192.168.0.1 @ 90:5c:44:55:19:8b
1812+ 822 2 mongoose.c:6752:onstatechange READY, IP: 192.168.0.24
1813+ 827 2 mongoose.c:6753:onstatechange GW: 192.168.0.1
1814+ 82d 2 mongoose.c:6755:onstatechange Lease: 86336 sec
1815+ bc3 2 main.c:65:main Ethernet: up
1816+ fab 2 main.c:65:main Ethernet: up
1817+ ```
1818+
1819+ Done! Now, our automatic tests ensure that the firmware can be built, that is
1820+ it bootable, that it initialises the network stack correctly. This mechanism
1821+ can be easily extended: just add more complex actions in your firmware binary,
1822+ print the result to the UART, and check for the expected output in the test.
1823+
1824+ Happy testing!
1825+
16871826
16881827## About the author
16891828
0 commit comments