March 03, 2021
In the embedded world, more and more vendors offer Arm-based System-on-Chips (SoC) including both powerful Cortex-A CPU cores, designed to run a full-featured OS such as Linux, and one or more low-power Cortex-M cores, usually found in microcontrollers, designed to execute bare-metal or RTOS-based applications. With these designs, generally Linux runs on the main processor, with full networking, memory management and security capabilities with the coprocessor running a very specialized software, which completely depends on your system requirements:
In this article, we'll take a closer look at the ST Microelectronics STM32MP1 microprocessor series, which contains 2 650-800MHz Cortex-A7 cores and a single Cortex-M4 clocked at 209MHz. They both have access to all the SoC peripherals, except for the memory: while the main processor uses external DDR RAM, the coprocessor can only use a dedicated 448kb SRAM. To allow communication between the 2 processors, this SoC also includes a mailbox and hardware semaphore which can be used by all cores to ensure exclusive access to peripherals and exchange information.
|ST Microelectronics STM32MP1 discovery kit board|
While the Linux kernel can run on a wide range of devices, it requires a decent amount of memory (> 4MB), and therefore cannot be used on memory-constrained microcontrollers.
Enters Zephyr, a project initiated by Wind River, now developed as a Linux Foundation project.
Zephyr is also easily extensible, as third-party modules and libraries (such as zscilib) can be added to the project with little configuration required.
On a Debian-based system (Debian buster or later), installing Zephyr is relatively straightforward. At the time of this writing, you can download Zephyr's build dependencies and source code by following these steps:
sudo apt install --no-install-recommends git cmake ninja-build gperf \ ccache dfu-util device-tree-compiler wget \ python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \ make gcc gcc-multilib g++-multilib libsdl2-dev
sudo apt install gcc-arm-none-eabi
pip3 install --user -U west echo 'export PATH=~/.local/bin:"$PATH"' >> ~/.bashrc source ~/.bashrc
west init /directory/to/which/install/zephyr cd /directory/to/which/install/zephyr west update
Detailed instructions can be found in the Zephyr getting started guide.
IMPORTANT: make sure you use a recent version of Zephyr (>= v2.4.0) as older versions won't work with a mainline Linux kernel on STM32MP1.
Zephyr applications are much like any C program, with the
main() function being the entry point but since microcontroller applications aren't supposed to exit
main() usually consists of initialization code, followed by an infinite loop where all the work is done.
When looking at the
blinky sources, we see that it's all quite straightforward:
CMakeLists.txtfile is used for compilation and specifies which application-specific files should be compiled in
prj.confacts as an application-specific defconfig: each supported board has its own defconfig (much like what you'll find in the Linux kernel); config options required for the application to compile and run are added to
prj.conf, which is internally appended to the board's defconfig when building the project
sample.yamlis used to define test cases for Zephyr's test runner.
The source code of the application itself is in the
When looking at the code itself, it is pretty much self-explanatory although one interesting point is how device trees are used: unlike Linux, a binary will run on one platform only, so the device tree is parsed and interpreted at compile time which avoids unnecessary usage of the target's limited resources.
DT_*) are used to fetch specific device properties and functions such as
device_get_binding() are used to retrieve the compiled-in device information.
blinky's case, we look for the
led0 device-tree alias, which should point to the LED to be used, as you will see in the STM32MP157C-DK2 device-tree:
led0 is an alias for
red_led_1, which is an LED connected to pin 7 of GPIO port H, active high.
From the Zephyr directory, clone the sample application:
git clone https://gitlab.collabora.com/aferraris/zephyr-rpmsg-demo.git
This demo application showcases how sensor processing (here, a MPU6050 6-axis gyroscope + accelerometer) can be offloaded to the coprocessor while still making the data available to the Linux system. It runs 2 threads:
imu.c) is dedicated to the sensor: this task calibrates the MPU6050 then fetches and processes measurement from the sensor every 10 milliseconds
ipc.c) is used for communicating with the Linux system, using the OpenAMP library; this one is heavily based on the
Finally, the project also includes a
boards folder containing a device-tree overlay for the STM32MP157C-DK2, defining the following elements:
The communication is a simple ASCII-encoded request-response protocol where all communication is initiated on the Linux side:
fetchrequests the current sensor orientation from the coprocessor; the reply is in the form
X=<x_orientation>;Y=<y_orientation>;Z=<z_orientation>(values in degrees, the reference being the initial position)
quittells the coprocessor to end the IPC task; while not very useful for this application, it demonstrates the ability to stop a thread while keeping the other tasks running
In order to build the application, setup the build environment first, so that Zephyr knows its path and which toolchain to use:
. zephyr/zephyr-env.sh export ZEPHYR_TOOLCHAIN_VARIANT=cross-compile export CROSS_COMPILE=/usr/bin/arm-none-eabi-
Then build the application:
west build --board stm32mp157c_dk2 zephyr-rpmsg-demo
This will produce a file named
build/zephyr/zephyr-rpmsg-demo.elf, which is the executable file to be loaded on the coprocessor.
The MPU6050 is connected to port I2C5 of the STM32MP157C, which is available on the 40-pin GPIO connector CN2:
You could also use the Arduino headers on the other side of the board:
The Zephyr application could easily be loaded and started when booting the system using u-boot, following the instructions in u-boot's documentation (look for the
Coprocessor firmware paragraph). This can be useful when e.g. you want the coprocessor to monitor the Linux system.
However by doing this we would give away control of the coprocessor, while it can be interesting to fully manage it from the Linux system running on the A7 cores.
remoteproc is a Linux kernel framework aimed at providing the basic infrastructure for loading firmware to a remote processor, as well as powering it on and off.
rpmsg is a communication framework, based on
virtio, allowing Linux drivers to communicate with a remote processor by abstracting low-level implementation, allowing the clients to focus on the message payloads.
remoteproc relies on device-specific drivers and creates the
virtio devices necessary for
rpmsg to work. It also provides a sysfs interface for controlling the coprocessor and mostly works out of the box once properly configured.
rpmsg, however, requires writing a client kernel driver in order to implement the chosen communication protocol and expose high-level controls (such as a
/dev node or a sysfs interface) to either other drivers or userspace programs.
Using a mainline Linux kernel (we recommend using version 5.9 or above), the following options have to be set in order to enable
On the STM32MP1 processor series, you will also need the following device driver:
Note: these options are already enabled when using the mainline
The RPMsg client for this demo application is available here.
It can be built on the target device by executing the following commands:
$ git clone https://gitlab.collabora.com/aferraris/zephyr-rpmsg-client.git $ cd zephyr-rpmsg-client $ make -C /lib/modules/`uname -r`/build M=$PWD $ sudo make -C /lib/modules/`uname -r`/build M=$PWD modules_install
If cross-compiling from a workstation, simply replace
/lib/modules/`uname -r`/build with the kernel build directory and add the proper
CROSS_COMPILE= directives to the build command-line.
Once the kernel is properly configured and the system is running, you must first copy the coprocessor binary file (in our case, the
zephyr-rpmsg-demo.elf file) to the
/lib/firmware directory on the target board.
By default, on STM32MP1, Linux expects the firmware to be named
rproc-m4-fw. You can therefore either rename your firmware file to match the default name, or instruct the kernel to load the
zephyr-rpmsg-demo.elf file by executing the following command:
# echo zephyr-rpmsg-demo.elf > /sys/class/remoteproc/remoteproc0/firmware
The coprocessor can then be controlled by writing to
# echo start > /sys/class/remoteproc/remoteproc0/state
# echo stop > /sys/class/remoteproc/remoteproc0/state
You can check the current sensor orientation by reading the
Many modern SoCs such as the STM32MP1 now include coprocessor cores which can be used for a wide range of tasks and can offload some of the work from the main processor. Using Zephyr alongside Linux can be a simple and efficient way to take advantage of these additional cores and opens a new world of possibilities.
If you have questions or need assistance regarding asymmetric multi-core processing with Linux and Zephyr on the STM32MP1 or any other Arm based SoCs, please get in touch!
A step-by-step guide on how to enable 3D acceleration of Vulkan applications in QEMU through the new Venus experimental Vulkan driver for…
Maintaining a non-trivial set of GStreamer patches can be tricky. Thanks to the recent move to a single, unified git repo, you can now easily…
Earlier this year, I joined Collabora as an intern to work on improving testing in libcamera and automating it through KernelCI. Having…
With the LLVM toolchain seeing increasing development and adoption alongside the older, more established GNU toolchain, projects needing…
This summer, Christoph Haag and I had the pleasure of taking part in Google Summer of Code as mentors for xrdesktop, the Open Source project…
Earlier this year, from January to April 2021, I worked on adding support for stateless decoders for GStreamer as part of a multimedia internship…