STM32 development on PlatformIO in VSCode on 64 bi ARM - riban-bw/blog GitHub Wiki
Developing embedded code using PlatformIO plugin in Visual Studio Code on a Raspberry Pi (or other 64-bit ARM machine) via ssh using the Arduino Framework
There are currently (2023-04-05) some configuration issues with PlatformIO that stop it working with STM32 on 64-bit ARM platforms. This guide describes the steps I took to get this working, including running VSCode with ssh connection and some firmware upload solutions. The development environment used for this guide was an Acer CB314 ARM based Chomebook (they also make x86 models with same name) and Raspberry Pi 400 running Raspbian OS 64-bit. Some steps relating to the Chromebook Linux container may be skipped if you have direct access to your system's operating system. Connection to remote host (Raspberry Pi) may be ommited if running VSCode locally, e.g. you have direct console access to the machine you want to develop on.
The Chromebook provides a Linux Container that runs a Debian based OS. This is used to run VSCode. The Linux Container must be initalised first.
- Download the ARM 64 version of VSCcode
- Double click the .deb file in the file browser and follow instructions to install
If you prefer to install via command line:
- Open a terminal to the Linux container
- Install VSCode, e.g. sudo dpkg -i /mnt/chromeos/MyFiles/Downloads/code_1.77.0-1680084405_arm64.deb
After installing VSCode on the machine, launch it using any launch method. On the Chromebook the application becomes available in the search menu. It could also be launched from the command line, e.g. code
In my local development environment I am happy to implement access from my laptop to my development machine without password. The security implications are for you to decide. I find this makes development process smoother. Without this step, VSCode will ask for password on each connection (start of session) to the remote host.
- Within Linux container terminal:
ssh-keygen -t rsa -b 4096 -C "[email protected]"
- Accept defaults and press enter if asked for password / passcode
ssh-copy-id remote_username@server_ip_address
- You will need to enter the password for the remote host
VSCode allows development to occur on a remote host over ssh. The VSCode UI is shown on the local machine, e.g. my Chromebook whilst a VSCode server runs on the remote machine, e.g. my Raspberry Pi 400. Any configurations, plugins, etc. must be installed on the remote machine separate to the local machine, i.e. you may have plugins enabled for local development and different plugins enabled for remote development.
- Within VSCode:
- Access the search menu, e.g. press F1 (On my Chromebook the function keys default to ChromeOS operations. F1..F10 are accessed by using the search modifier key. I have changed the default behaviour for these keys to act as function keys.)
- Type, "ssh" to search for "Remote-SSH: Connect to Host..."
- Select "+ Add New SSH Host..."
- Enter the hostname/IP address, user and password then accept default save location
- The next time you try to "Remote-SSH: Connect to Host..." this remote machine will appear in the list
- If ssh access has not been configured without requiring a password (see previous step) then you will be prompted for a password. (This is subtle - part of the drop-down, similar to the F1 search dialog.)
After connecting to the remote host, VSCode server will be installed on the remote host. Each time you connect to the remote host it will check if VSCode server is the same version as you are running and install a new server if it has changed, e.g. after an update. After the server has started yo are now effectively working on the remote machine with access to its file system and using its resources (CPU, RAM, etc.)
PlatformIO is a framework for developing firmware for embedded devices such as microcontrollers. This guide relates to developing code for the STM32 series of processors, specifically the STM32F103C8 that is used for the "Blue Pill" development board.
If running PlaformIO on the local machine, python3-venv is required due to linux being a virtual environment within ChromeOS. This may not be required when connecting to a remote (proper linux installation) machine.
sudo apt install python3-venv
- Within VSCode:
- Click on the extensions icon and search for PlatformIO
- Click the install button and wait for installation to complete
PlatformIO has a "Home" page that provides access to its features. This is an embedded web page which by default is served to localhost IP address 127.0.0.1. Because we are running VSCode remotely this points to the wrong host.
- Within VSCode:
- Access settings, e.g. ctrl+comma
- Search for "platformio-ide:pio home server http host"
- This should show "127.0.0.1"
- Enter the hostname or ip address of the remote machine, e.g. rpi400
- Click on PlatformIO icon to launch / show PlatformIO extension
- Click on PIO Home->Open
- PlatformIO's home view should display
If using maple framework there is an issue with the PlatformIO configuration that does not allow selection of a toolcahin version which works on aarch64. The issue with the toolkit is resolved in later versions but PlatformIO config does not use these working versions.
- On the machine being used for development, e.g. remote ssh host (my Raspberry Pi 400):
- Edit ~/.platformio/platforms/ststm32/platform.json
- Find the "packages" stanza
- Change upper version limit, e.g.
"version": ">=1.60301.0,<1.130000.0",
Note: These changes relate to the current PlatformIO configurations and the currently working toolchain available from PlatformIO. It is likely that updates to the plugin may change these values requiring manual intervention to reapply these changes. It is plausible (let's hope probable) that the original issue is resolved upstream and that these fixes are unrequired - let's hope!
The website https://registry.platformio.org/tools/platformio/toolchain-gccarmnoneeabi/installation shows information about this toolchain. On the right is a link to the versions and indication of the latest version. Clicking on a version and then the compatibility tab will show if it works with your platform.
The toolchain should now be installed and working so let's test it.
- Within VSCode:
- Click on PlatformIO icon to show extension
- Click on PIO Home->Open to show PIO home
- Click on "New Project"
- Name: Test
- Board: STM32F103C8 (20k RAM, 65k Flash) (Generic)
- Framework: Arduino Framework
- Location: Use default location
- Click "Finish"
- The wizard runs and (hopefully) completes without error, presenting a dialog, "Do you trust the authors of the files in this folder?"
- Click "OK" (if you trust yourself)
- PlatformIO has a bit of a think then shows the platform.ini file, adding your project to the current workspace
- A default (do nothing) main.cpp file is created with empty setup() and loop() functions
- Click the PlatformIO:Build button (tick on the bottom status bar) or F1 search for "PlatformIO:Build" or Ctrl + Alt + B
- Project is built (successfully?) and firmware image .pio/build/genericSTM32F103C8/firmware.bin is created
Note: Do not use spaces in name in PlatformIO wizard. This creates a directory with spaces in its name which the build system does not handle well!
Having created a firmware we need to transfer it to the STM32 module. There are several ways to accomplish this, most of which are supported by PlatformIO.
The default upload method is to use ST-LINK/V2, described by STMicroelectronics as, "...an in-circuit debugger and programmer for the STM8 and STM32 microcontrollers. The single-wire interface module (SWIM) and JTAG/serial wire debugging (SWD) interfaces are used to communicate with any STM8 or STM32 microcontroller located on an application board. In addition to providing the same functionalities as the ST-LINK/V2, the ST-LINK/V2-ISOL features digital isolation between the PC and the target application board. It also withstands voltages of up to 1000 Vrms."
This device is available from our friends in China for a couple of dollars so a worthwhile investment. It provides a relatively simple method of interfacing with the STM32 including firmware transfer and debugging. In theory it should just work but the device does depend on some supporting applications and configuration.
Configuration
- Within (remote) development machine terminal:
- Install ST-Link software: sudo apt install stlink-tools stlink-gui
- Install udev rules:
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules | sudo tee /etc/udev/rules.d/99-platformio-udev.rules
- Plug in ST-LINK/V2 to (remote) development machine USB port
- Connect ST-LINK/V2 to the STM32 board
- Run stlink-gui which requires you use X forwarding to see the GUI, e.g. ssh -Y
- Click the "Connect" button
- stlink-gui should show the STM32 board info
The udev rules should configure the machine for various devices that PlatformIO supports.
Flashing
- Connect STM32 to STLink
- Click the right arrow button in the bottom toolbar or F1 search "PlatformIO:Upload" or Ctrl + Alt + U
- The firmware should transfer to the STM32 then it should enter run mode
Many Blue Pill boards use a clone chip CS32F103C8T6 even though they show STM32F103C8 on the chip. (Cheeky!) STLINK expects to see a the correct id and fails to flash with this error (or similar):
Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477
This can be resolved by adding configuration to platform.ini:
upload_flags = -c set CPUTAPID 0x2ba01477
PlatformIO uses openocd to interface with ST-LINK. This provides firmware upload, debug, etc. To manually upload a firmware with openocd:
- Enable flash mode, e.g. press and hold reset button
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "program firmware.bin reset exit 0x08000000"
- Disable flash mode, e.g. release reset button
The STM32 has a builtin first-stage bootloader that supports uploading firmware over one of its serial ports. This requires the BOOT0 jumper to be set to 1 then the board to be reset which puts it into firmware upload (via serial) mode. An application called stm32flash can upload an image to the STM32 when it is in this mode. stm32flash can also control GPI pins which can allow us to prepare the module for upload and reset after upload.
Configuration
- Choose two spare GPI pins for boot mode and reset, e.g. GPI17 & GPI18
- Connect Raspberry Pi pin 6 (GND) to STM32 5V
- Connect Raspberry Pi pin 4 (5V) to STM32 5V (or pin 1 (3.3V) to STM 3.3V)
- Connect Raspberry Pi pin 8 (Tx) to STM32 PA10 (Rx)
- Connect Raspberry Pi pin 10 (Rx) to STM32 PA9 (Tx)
- Connect Raspberry Pi pin 11 (GPI17) to STM32 RESET
- Connect Raspberry Pi pin 12 (GPI18) to STM32 BOOT 0 (centre pin - remove jumper)
- Ensure jumper for BOOT 1 is still in position 0
- Within (remote) development machine terminal:
- Install stm32flash: sudo apt install stm32flash
- Allow access to GPI pins: echo 17 > /sys/class/gpio/export ; echo 18 > /sys/class/gpio/export
- Within VSCode, open the project platform.ini file
- Add lines:
upload_protocol = serial
upload_flags =
-b921600
-i'18,-17,17:-18,-17,17'
The -b parameter sets the upload speed. I found 921600 works reliably but you may need to decrease this if upload proves unreliable. Valid values are: 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 128000, 230400, 256000, 460800, 500000, 576000, 921600, 1000000, 1500000, 2000000.
The -i parameter controls the GPIs. We raise the boot pin (18) then lower and raise the reset pin(-17,17). Commas represent a short delay. Colon divides the pre/post flash actions. After flash we lower the boot pin(-18) then lower and raise the reset pin(-17,17).
Flashing
- Connect STM32 to computer via serial port
- Click the right arrow button in the bottom toolbar or F1 search "PlatformIO:Upload" or Ctrl + Alt + U
- The firmware should transfer to the STM32 then it should enter run mode
If a USB serial port is plugged into the Raspberry Pi then PlatformIO may not be able to automatically detect which serial port to use for upload. In this case, change the upload port from "auto":
- Click on the plug icon in the bottom tool bar
- Select the correct serial port from the drop down at top of screen
stm32flash (described above) also supports flashing to an I2C peripheral but STM32F103 does not support I2C upload in its primary bootloader. It is possible to write a second stage bootloader that supports the I2C upload protocol as described by AN3078 but I haven't yet found anyone who has written such a bootloader - maybe I need to do it myself... TODO
Maple Leaf produced a range of STM32 based development boards and wrote a second-stage bootloader that presents the STM32 as a USB device and allows control including flashing images. STM32duino-bootloader is a firmware that implements the maple bootloader protocol. Using this bootloader it is possible to flash new images to the STM32 using its USB port. Some boards may not work fully, e.g. if they do not have the onboard reset circuitry, you may need to insert USB during the upload process, when the message, "Looking for upload port..." appears.
The STM32 needs the booloader installed. Some have this but most generic Bluepill boards do not.
Configuration
- Download the bootloader from here
- Use ST-LINK to write to 0x08000000 - it _may _be necessary to move BOOT1 jumper (if the existing code prohibits this action). This can be done with stlink-gui or the command line tool:
sudo st-flash write <file_to_flash>.bin 0x08000000
- Use these configuration in project's platformio.ini:
[env:stms32]
framework = arduino
platform = ststm32
board = genericSTM32F103C8
board_build.core = maple
upload_protocol = dfu
Flashing
- Connect STM32 board to computer via USB
- Click the right arrow button in the bottom toolbar or F1 search "PlatformIO:Upload" or Ctrl + Alt + U
- The firmware should transfer to the STM32 then it should enter run mode
This should be sufficient to upload from PlatformIO in VSCode to the STM32 board. Note that this uses the generic STM32 pin names but the Roger Clark (Maple) framework which supports Composite USB in the target code.
Note: The USB devices are not presented correctly to Chomebook Linux subsystem. I made this work by connecting the STM32 board to the USB port of a Raspberry Pi and using VSCode+PlatformIO over ssh from the development machine to the Raspberry Pi.
TODO
The STM32F103C8 has a USB port which can be used by the firmware at runtime but by default is disabled. Build flags may be used to enable the USB interface.
To present the device as a USB serial port add the following to the platform.ini file:
build_flags =
-D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
-D USBCON
To present the device as a USB HID (keyboard & mouse) add the following to the platform.ini file:
build_flags =
-D PIO_FRAMEWORK_ARDUINO_ENABLE_HID
-D USBCON
Then instantiate a keyboard class object in the script, e.g.
#include <Keyboard.h> ... Keyboard.begin();
USB MIDI is not supported by the standard STM32 libraries but Roger Clark's framework does provide USB MIDI. To use Roger Clark's framework add this to the platform.ini file:
board_build.core = maple
This allows use of the USB Composite driver that allows multiple USB interfaces to be implemented simultaneously sharing the same USB port. Add this to the source code:
#include <USBComposite.h>
USBMIDI midi;
void setup() {
USBComposite.setProductId(0x0031);
midi.begin();
while (!USBComposite);
}
It is possible to debug code running on the board but the default debug configuration does not like clone chips with the alternative CPUTAPID. To enable debugging, add these lines to the [env] section of platform.ini:
[env]
debug_tool = stlink
debug_server =
${sysenv.HOME}/.platformio/packages/tool-openocd/bin/openocd
-f interface/stlink.cfg
-c "transport select hla_swd"
-c "set CPUTAPID 0x2ba01477"
-f target/stm32f1x.cfg
-c "reset_config none"
Sometimes running the debugger fails until a normal (release) build is run after which it seems to work.
The PlatformIO project wizard asks for the project name, board type and platform then creates a folder structure (usually) with boilerplate code. It is then up to the developer to add source and header files for their project. They may also add libraries to support it. PlatformIO's handling of this is managed by platformio.ini file in the root of this folder structure. A default configuration for a generic STM32F103C8 board using Arduino framework may look like this:
[env:genericSTM32F103C8]
platform = ststm32
board = genericSTM32F103C8
framework = arduino
[env:genericSTM32F103C8]
defines a section that acts as a build target. You may select any of these from PlatformIO's build menus, e.g. in vscode, it is shown in the bottom status bar and may be selected to change the target. The content of the section define the behaviour for the target. The content of a section called [env] will be inherited by other sections. You may also select [env] as a target which will build all the child targets sequentially.
platform
identifies the target's platform. We are using ststm32. You may have different build targets with different platforms, e.g. build the same code for an Arduino Nano. Running the wizard and providing the same project name will add a configuration to the existing project. I am not particularity concerned about that as I target my code at a single platform.
board
defines the hardware target. This may configure various elements like naming conventions, available features, etc. In this example a generic STM32F103C8 board is targetted which is similar to a Bluepill but the Bluepill has external oscillator crystals enabled (amongst other differences). An STM32F4 processor based board may be used with the same ststm32 platform. I generally work with STM32F1xx boards.
framework
identifies the firmware framework. In this example it is arduino which implements (most) of the Arduino infrastructure allowing fast, simple programming. An alternative is CMSIS which is a low-level, bare metal framework where you need to configure registers directly. There are others but these two seem the best options for low and higher level development on the STM32F1 series.
There are variants of the frameworks, e.g. adding board_build.core = maple
to the configuration will tell PlatformIO to use the maple variation of Arduino framework. This uses a different set of libraries with some extra features but some missing and also adds a second stage bootloader that supports the maple DFU (USB) upload mechanism.