@@ -1656,7 +1656,7 @@ for more details.
1656
1656
A complete project source code you can find in
1657
1657
[step-7-webserver](step-7-webserver) directory.
1658
1658
1659
- ## Automatic software and hardware tests
1659
+ ## Automated firmware builds (software CI)
1660
1660
1661
1661
It is a good practice for a software project to have a continuous
1662
1662
integration (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
1680
1680
keep quiet. See an [example successful
1681
1681
run](https://github.com/cpq/bare-metal-programming-guide/actions/runs/3840030588).
1682
1682
1683
+
1684
+ # # Automated firmware tests (hardware CI)
1685
+
1683
1686
Would it be great to also test built firmware binaries on a real hardware, to
1684
1687
test 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
+
1687
1826
1688
1827
## About the author
1689
1828
0 commit comments