Want to Flash and Debug an MCU Over the Air? Just Use Your Raspberry Pi
How do embedded developers set up everything they need to do programming? A typical wired setup includes a device that stays on the developer’s table, and a ribbon cable that connects it to a debug probe, which in turn is connected to the developer’s computer by a USB cable. The computer is running probe-specific software and an IDE.
In many cases, this setup works fine. But embedded devices work with real-world objects. Sometimes an MCU-based control board manages high-voltage or dangerous equipment. Sometimes it’s mounted outdoors. The equipment could be moving on wheels, or floating on the water. It could even be mounted in a remote location. Using a cable setup in these cases is generally inconvenient and could even be dangerous.
When I started developing an outdoor device, I immediately encountered this problem. What could be done? I took stock of what I had available to me and found a Raspberry Pi board. I have spent several hours setting it up as a wireless debug probe, and now it works like a charm.
- Simplest wireless setup principles
- Which board is better suited?
- Raspberry PI OS and connectivity
- OpenOCD installation
- Final Raspberry settings
- Setting up the example project
- Hardware wiring
- Let’s start it up
- Bonus Track – JTAG and RISC-V
Simplest wireless setup principles
Every ARM-based MCU has a two-wire debug interface (SWD) and a reset line. These three lines are supposed to be connected to a debug probe. In my wireless setup, I use my Raspberry Pi GPIO pin header to connect directly to the device. On the software side, my Raspberry Pi is running the open-source on-chip flashing and debug utility OpenOCD as a remote GDB server, and I connect a debugger, bundled with the CLion IDE, to it over Wi-Fi. Open network communications might be unsafe, so I wrap a TCP channel between GDB and OpenOCD into an SSH tunnel. Here’s a schematic representation that captures the differences between these two setups.
Which board is better suited?
In my setup, I used Raspberry Pi Zero W, and I think it’s a very good option. It’s small. It already has Wi-Fi on board. It consumes relatively little energy. It does not need a fan for cooling. It’s sold everywhere. And it’s fast enough to do the job. But, this board has a couple of disadvantages relative to RPi 2+. It has a slower CPU, and setting it up from scratch might take longer. Further, RPi Zero W only supports 2.4GHz Wi-Fi networks, not 5GHz.
You can use whichever Raspberry board you prefer. The only major differences are the network setup and the OpenOCD configuration parameters that need to be adjusted: bcm2835gpio_peripheral_base and bcm2835gpio_speed_coeffs.
Raspberry PI OS and connectivity
First of all, we need to get our Raspberry Pi up and running, and connected to the Wi-Fi network. This requires an empty SD card, and 4 GB should be sufficient.
The very first step is to copy an OS image to the SD card. The best choice is Raspberry Pi OS (32-bit) Lite. You can find the official installation instructions for Windows, Linux, and MacOS here.
Once the image has been written to the SD card, the next step is to ensure that the board will connect to a Wi-Fi network. Eject and insert your SD card back into the reader, and then follow these instructions for setting up Wi-Fi headlessly. (Note that RPi Zero W only supports 2.4GHz Wi-Fi.)
Now write a file named ssh to the SD card’s root folder. The content of the file is not important, and even an empty file will do. This will activate the ssh server for remote access when Raspberry is booted. More details.
Now we can insert the card into Raspberry Pi and turn it on. If everything is done correctly, in a couple of minutes the board will appear as a new Wi-Fi device in the network. Its IP address can be found on the Wi-Fi router DHCP status page. I’d recommend setting up a permanent IP address for the board there. In my case, it is 192.168.0.110.
To take control of the board, we need an ssh client. Use the ssh utility on Linux and MacOS or putty on Windows:
The default password is raspberry. We highly recommended changing the password as soon as you log into the board. Use the passwd command to do so.
The next step is to update Raspberry OS. This is time-consuming, but I’d recommend doing it anyway. It’s best to keep all the Linux software up to date. The commands:
sudo apt update; sudo apt -y full-upgrade; sudo apt -y autoremove --purge
will do the trick. Reboot the board (
sudo reboot), and log back in using your new password.
Since there isn’t a pre-built binary package of OpenOCD for Raspberry, we need to build it ourselves. This process could take some time, so please be patient.
First of all, Git, build tools, and libraries must be installed with the following command:
sudo apt install -y git gcc binutils make libtool pkg-config autoconf automake texinfo libgpiod-dev libusb-1.0
Now we need to download the OpenOCD sources. For our purposes, we only need the latest revision, and a shallow clone of the Git repository should be sufficient. The following command will make that clone in the openocd-git folder:
git clone --depth 1 git://git.code.sf.net/p/openocd/code openocd-git
Let’s configure, build, and then install the actual utility. The commands in the following sequence should be called one by one:
cd openocd-git ./bootstrap ./configure --enable-linuxgpiod --enable-bcm2835gpio --enable-sysfsgpio make sudo make install
Once these commands have finished running, OpenOCD is ready.
Final Raspberry settings
OpenOCD is a utility for in-system programming and on-chip debugging. It supports a number of various microcontroller cores and different flashing interfaces and different protocols. OpenOCD uses three types of configuration files:
- Probe interface configurations.
- Target device core configurations.
- Board configurations, which usually include a reference to the probe interface, a reference to the core configuration, and optional tweaks.
We are not going to touch the cores, but we do need a probe interface configuration. The following command creates such a config, named rpi1-gpio.cfg, at the pi user home.
cat <<EOF > ~/rpi1-gpio.cfg # # Config for using Raspberry Pi's expansion header # # This is best used with a fast enough buffer but also # is suitable for direct connection if the target voltage # matches RPi's 3.3V and the cable is short enough. # # Do not forget the GND connection, pin 6 of the expansion header. # adapter driver bcm2835gpio bcm2835gpio_peripheral_base 0x20000000 # Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET # These depend on system clock, calibrated for stock 700MHz # bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET bcm2835gpio_speed_coeffs 113714 28 # Each of the SWD lines need a gpio number set: swclk swdio # Header pin numbers: 22 18 bcm2835gpio_swd_nums 25 24 # If you define trst or srst, use appropriate reset_config # Header pin number: 26 # bcm2835gpio_trst_num 7 # reset_config trst_only # Header pin number: 12 bcm2835gpio_srst_num 18 reset_config srst_only srst_push_pull # If you have both connected, # reset_config trst_and_srst srst_push_pull transport select swd EOF
From the configuration file above we can see the GPIO header pinout:
Pin 6 -> GND Pin 12 -> !RESET Pin 18 -> SWDIO Pin 22 -> SWCLK
And there’s one final, optional step. It can be easy to forget the pinout of the wireless probe, but we can make an easy reminder. The following command adds a login banner with the pinout.
cat <<EOF1 >>~/.profile cat <<EOF RPi GPIO Header Pinout +--------+ | 1 2 | +5V | 3 4 | | 5 6 | GND | 7 8 | | 9 10 | | 11 12 | RESET | 13 14 | | 15 16 | TMS | 17 18 | SWDIO/TDI | 19 20 | | 21 22 | SWCLK/TCK | 23 24 | TDO | 25 26 | | 27 28 | | 29 30 | | 31 32 | | 33 34 | | 35 36 | | 37 38 | | 39 40 | +--------+ EOF EOF1
The next time we forget the pinout, we can just log back in the Raspberry and it will be displayed on the banner.
Now we’ve finished setting up the Raspberry software. We’ll next move on to look at the hardware and the firmware example project. But before we do, there’s one last point we need to consider.
Setting up the example project
For the example project, I’m going to use the MCU soldered onto a STM32F3-Discovery board, but with RPi instead of the on-board ST-LINK interface. The example project can be cloned from a GitHub repository. The project is written in C and C++17.
First, I detach both jumpers from the “ST-LINK” connector. The on-board flashing probe is now disconnected from the MCU.
The target MCU for this demo is powered by the Raspberry Pi itself, using pin 2 of the GPIO header.
Note! If you make your own device, make sure your target device supports 5 V power supply voltage, and does not consume more than 200 mA. Otherwise, use some other power supply scheme.
So, the wiring should look like this:
Rpi Pin 6 -> GND -> DISCO GND Rpi Pin 12 -> RESET -> DISCO nRST Rpi Pin 18 -> SWDIO -> DISCO PA13 Rpi Pin 22 -> SWCLK -> DISCO PA14 Rpi Pin 2 -> PWR -> DISCO 5V
As you can see, no extra components are involved – only the target device, a power supply, RPi, and some wires.
Let’s start it up
The demo project is supposed to run the locally installed OpenOCD and use a regular wired setup.
Let’s create an alternate run configuration of the Embedded GDB Server type with the following parameters:
- Target – select one from the list; by default there should only be one.
- Executable – select one from the list; by default there should only be one.
- Debugger – either bundled or from the ARM toolchain, whichever you prefer.
- Target remote args – tcp::3333. The GDB server will try to connect to localhost, port 3333.
- GDB server – path to your plink(Windows + putty) or ssh(Linux, MacOS) utility
- GDB server args – -t -pw somepassword -L 3333:127.0.0.1:3333 pi@your_rpi_ip_address openocd -f rpi1-gpio.cfg -f target/stm32f3x.cfg
Note that you have to use your own RPi IP address and password. Additionally, you may use a target configuration file that is not target/stm32f3x.cfg.
The GDB server args are the trickiest of these parameters. Let’s go over the various elements of them one by one.
- -t reserves a pseudo-terminal for the utility started at Rpi (openocd).
- -pw somepassword provides an ssh password. Use your own.
- pi@your_rpi_ip_address provides the user name and IP address of the RPi board. Use your address.
- -L 3333:127.0.0.1:3333 organizes an encrypted ssh tunnel. Local port 3333 at RPi board is transparently forwarded to port 3333 of the developer’s computer.
- openocd -f rpi1-gpio.cfg -f target/stm32f3x.cfg starts openocd at RPi using our interface configuration and stm32f3 series kernel.
Now we have one last thing to set:
Advanced GDB Server Options -> Startup Delay – 1000 ms
And we’re done! Just click the Debug button – the MCU firmware will be compiled and flashed, and the debugger session will start.
Bonus Track – JTAG and RISC-V
Have you noticed that on the pinout hint some additional pins are marked with typical JTAG signals? This was intentional, as the JTAG interface is also supported. Let’s put that JTAG interface to use.
This script generates a JTAG config file:
cat <<EOF >~/rpi1-gpio-jtag.cfg # # Config for using Raspberry Pi's expansion header # # This is best used with a fast enough buffer but also # is suitable for direct connection if the target voltage # matches RPi's 3.3V and the cable is short enough. # # Do not forget the GND connection, pin 6 of the expansion header. # adapter driver bcm2835gpio bcm2835gpio_peripheral_base 0x20000000 # Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET # These depend on system clock, calibrated for stock 700MHz # bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET bcm2835gpio_speed_coeffs 113714 28 # Each of the JTAG lines need a gpio number set: tck tms tdi tdo # Header pin numbers: 22 16 18 24 bcm2835gpio_jtag_nums 25 23 24 8 # If you define trst or srst, use appropriate reset_config # Header pin number: 26 # bcm2835gpio_trst_num 7 # reset_config trst_only # Header pin number: 12 bcm2835gpio_srst_num 18 reset_config srst_only srst_push_pull # If you have both connected, # reset_config trst_and_srst srst_push_pull adapter speed 600 EOF
For this example, I’ve connected a Longan Nano board powered by a GD32VF103 chip, which is a RISC-V kernel and thus has no SWD interface.
Rpi Pin 1 -> 3.3V -> Longan 3V3 <strong>Be careful! Pin 2 5V might damage your board</strong> Rpi Pin 6 -> GND -> Longan GND Rpi Pin 16 -> TMS -> Longan TMS Rpi Pin 18 -> TDI -> Longan TDI Rpi Pin 22 -> TCK -> Longan TCK Rpi Pin 24 -> TDO -> Longan TDO
I used a Risc-V toolchain, downloaded from the xPack repository. The bundled debugger does not support Risc-V, so we’ll have to use the toolchains debugger.
Don’t forget to switch the toolchain on the CMake settings page
At the time of this blog post’s composition (March 2021), the master branch OpenOCD does not support RISC-V chips particularly well, so OpenOCD should be cloned from a specific Risc-V fork and built as shown above.
The example project is located at GitHub, and the run configuration has already been created there. This run configuration for RISC-V MCU is very similar to the configuration for STM32F3 mentioned above, but it uses slightly different GDB server arguments that refer to a different transport and a different MCU:
-t -pw raspberry -L 3333:127.0.0.1:3333 firstname.lastname@example.org openocd -f ~/rpi1-gpio-jtag.cfg -f target/gd32vf103.cfg
Now all you need to do is set your own password and RPi network address.
Raspberry Pi is a very popular and easy-to-use single-board Linux computer. It is usually used for DIY projects. OpenOCD is an open-source program for flashing, debugging, and testing microcontrollers, including ARM and RISC-V kernels as well as many others. When used together with CLion, these resources make embedded development easier, more comfortable, and even safer in certain situations. The wireless setup described in this blog post is suitable for both hobby projects and professional ones.
Your CLion team
The Drive to Develop