sw-announce-freertos

One of the many advantages of developing with NXP’s i.MX Family of application processors is the ability to utilize both the Cortex-A as well as the Cortex-M cores. This blog post will first present the architecture of the i.MX heterogeneous processors and then explain how to build and run the FreeRTOS SDK v2.16.0 on its MCU.

For the impatient

You can download any of our currently available OS images for our Nitrogen platforms:

Note that you can find pre-built binaries of the examples here for each CPU variant:

There are 3 examples for each variant:

  • hello_world.bin & hello_world.elf
    • simple application printing “hello world” on the M-core serial port
  • rpmsg_lite_pingpong_rtos_linux_remote.bin & rpmsg_lite_pingpong_rtos_linux_remote.elf
    • ping pong example between both CPU clusters using RPMSG
  • rpmsg_lite_str_echo_rtos.bin & rpmsg_lite_str_echo_rtos_imxcmX.elf
    • TTY example showing serial communication between CPU clusters using RPMSG

You can refer to the Run the demo apps section to learn how to start those examples.

What’s new?

As usual, it’s best to look into the source code to see the differences. However we note the following changes:

  • Update to NXP latest BSP v2.16.0
  • FreeRTOS kernel updated to its version 11.1.0

Also, it is worth noticing that we are updating device tree names as well as U-Boot variables:

  • Device trees for FreeRTOS usage used to be called imxXX-nitrogenYY-m4.dtb which will become imxXX-nitrogenYY-rpmsg.dtb
    • Reason is that our platforms are now using Cortex-M4, M7 or M33 depending on the processor
    • The bootscript will take care of the name change automatically but can be useful to know for manual testing
    • This change happened already in kernel 6.1 for latest SMARC modules, but will be applied for all in kernel 6.6
  • Our U-Boot variables used to be called m4xxx and will be replaced by mcorexxx
    • m4enabled becomes mcore_enabled
    • m4boot becomes mcoreboot
    • m4image becomes mcore_image
    • m4loadaddr becomes mcore_loadaddr

Architecture

As an introduction, here is the definition of terms that will be used throughout the post:

  • MCU: Microcontroller Unit such as the ARM Cortex-M series, here referring to the Cortex-M4, M7 or M33
  • MPU: Microprocessor Unit such as the ARM Cortex-A series, here referring to the Cortex-A53, A35 or A55
  • RTOS: Real-Time Operating System such as FreeRTOS or MQX The i.MX 8/9 processors are Heterogeneous Multicore Processors as they offer an MCU and a MPU in the same chip.

How it works?

It actually depends on which CPU you are using:

  • For the i.MX 8MQ / 8MM / 8MN / 8MP:
    • The BootROM will always boot the Cortex-A core first.
    • Once started, the Cortex-A can then load the Cortex-M code and start it
  • For the i.MX 8ULP / 93:
    • Both Cortex-M & Cortex-A have their own power domains and can function independently!
    • The BootROM can load both Cortex-A & Cortex-M on its own

Note: the i.MX 8ULP needs a M33 image inside its flash.bin as U-Boot waits for the M33 to send a handshake to boot. Therefore all the M33 firmware files for the 8ULP should be integrated inside the bootloader image.

Where is the code running from?

It can run from 2 different places, depending on the application linker script used:

  • TCM (Tightly Coupled Memory): 128kB available (default location)
  • DDR: up to 1MB available (can be increased in the device tree)

Note: External memories, such as the DDR, offer more space but are also much slower to access. In this article, every application runs from the TCM as it is the option offering the best performance.

When is the MCU useful?

The MCU is perfect for all the real-time tasks whereas the MPU can provide a great UI experience with non real-time OS such as GNU/Linux. It is understood that the Linux kernel is not real-time, not deterministic whereas FreeRTOS on Cortex-M is. Also, since its firmware is pretty small and fast to load, the MCU can be fully operating within a few hundred milliseconds whereas it usually takes Linux OS much longer to be operational. Examples of applications where the MCU has proven to be useful:

  • Motor control: DC motors only perform well in a real-time environment since feedback response time is crucial
  • Automotive: the MCU can handle CAN messages and be operational at a very early stage

Resource Domain Controller (RDC)

Since both cores can access the same peripherals, a mechanism has been created to avoid concurrent access, ensuring a program’s behavior on one core does not depend on what is executed/accessed on the other core. This mechanism is the Resource Domain Controller (RDC) which grants peripheral and memory access permissions to each core. The FreeRTOS apps reserve the used peripherals for the Cortex-M, any access from Cortex-A core on those may cause the A-core to hang. The default RDC settings are:

  • The ARM Cortex-M core is assigned to RDC domain 1, and ARM Cortex-A core and other bus masters use the default assignment (RDC domain 0).
  • Every example/demo has its specific RDC setting in its board.c (see BOARD_RdcInit() function).

In order for a peripheral not to show up as available in Linux, it is mandatory to disable it in the device tree, which is why it is required to use a specific dtb file when using the MCU. The device tree also declares different reserved memory sections for FreeRTOS communication/shared memory.

Remote Processor Messaging (RPMsg)

The Remote Processor Messaging (RPMsg) is a virtio-based messaging bus that allows Inter Processor Communications (IPC) between independent software contexts running on homogeneous or heterogeneous cores present in an Asymmetric Multi Processing (AMP) system.

The RPMsg API is compliant with the RPMsg bus infrastructure present in upstream Linux which offers the following advantages:

  • No data processing in the interrupt context
  • Blocking receive API
  • Zero-copy send and receive API
  • Receive with timeout provided by RTOS

Note: RPMsg uses the DDR by default to exchange messages between cores.

Remoteproc API

This release allows to start, stop and reload the firmware from user-space thanks to the remoteproc API!

Thanks to this, you can start any application whose elf file is located under /lib/firmware:

# echo hello_world.elf > /sys/class/remoteproc/remoteproc0/firmware
# echo start > /sys/class/remoteproc/remoteproc0/state

Where can I find more documentation?

The BSP comes with some documentation which we recommend reading in order to know more on the subject:

Build instructions

Development environment setup

In order to build the FreeRTOS BSP, you first need to download and install a toolchain for ARM Cortex-M processors.

~$ cd && mkdir toolchains && cd toolchains
~/toolchains$ wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2017q4/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2
~/toolchains$ tar xjf gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2
~/toolchains$ rm gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2

Note: The commands above load the toolchain for Linux machines. You can get toolchains for other build machines here:

FreeRTOS relies on cmake to build, so you also need to make sure the following packages are installed on your machine:

~$ sudo apt-get install make cmake

Download the BSP

The FreeRTOS SDK v2.16.0 is available from our GitHub freertos-boundary repository.

~$ git clone https://github.com/boundarydevices/freertos-boundary.git freertos
~$ cd freertos
~/freertos$ git checkout <branch_name>

You need to use the proper branch name for your platform:

Finally, you need to export the ARMGCC_DIR variable so FreeRTOS knows your toolchain location.

~/freertos$ export ARMGCC_DIR=~/toolchains/gcc-arm-none-eabi-7-2017-q4-major
~/freertos$ export PATH=$PATH:~/toolchains/gcc-arm-none-eabi-7-2017-q4-major/bin

Build the FreeRTOS apps

All the applications are located under the boards/ folder:

~/freertos$ tree boards/evk*/ -L 1
boards/evk*/
├── cmsis_driver_examples
├── demo_apps
├── driver_examples
├── freertos_examples
├── multicore_examples
└── project_template

As an example, we will build the helloworld application:

~/freertos$ cd boards/evk*/demo_apps/hello_world/armgcc/
~/freertos/boards/evk*/demo_apps/hello_world/armgcc$ ./build_release.sh
~/freertos/boards/evk*/demo_apps/hello_world/armgcc$ ls release/
hello_world.bin  hello_world.elf

You can then copy that hello_world.bin firmware to the root of the eMMC or any other storage you use. As for the hello_world.elf file, it needs to be copied to the /lib/firmware/ folder of your OS rootfs.

Run the demo apps

Basic setup

Before going any further, make sure to hook up the second serial port to your machine as it will display data coming from the MCU.

If you want to automatically load your binary at bootup, you need to set the following variables in U-Boot:

=> setenv m4image myappname.bin
=> setenv m4enabled 1
=> setenv mcore_image myappname.bin
=> setenv mcore_enabled 1
=> saveenv

Note: by default, U-Boot loads the firmware from the same partition as the bootscript to TCM.

If you want to load your application from Linux:

# echo myappname.elf > /sys/class/remoteproc/remoteproc0/firmware
# echo start > /sys/class/remoteproc/remoteproc0/state

Hello World app

The Hello World project is a simple demonstration program that uses the BSP software. It prints the hello world message to the ARM Cortex-M terminal using the BSP UART drivers. The purpose of this demo is to show how to use the UART and to provide a simple project for debugging and further development.

In U-Boot, type the following using the old format:

=> setenv m4image hello_world.bin
=> load mmc 0 $m4loadaddr $m4image
=> bootaux $m4loadaddr

For latest platforms / U-Boot version:

=> setenv mcore_image hello_world.bin
=> load mmc 0 $mcore_loadaddr $mcore_image
=> bootaux $mcore_loadaddr

On the second serial port, you should see the following output:

hello world.

You can then type anything in that terminal, it will be echoed back to the serial port as you can see in the source code.

In order to load that same application from Linux, simply type the following:

# echo hello_world.elf > /sys/class/remoteproc/remoteproc0/firmware
# echo start > /sys/class/remoteproc/remoteproc0/state

RPMsg TTY demo

This demo application demonstrates the RPMsg remote peer stack. It works with Linux RPMsg master peer to transfer string content back and forth. The Linux driver creates a tty node to which you can write to. The MCU displays the data and echoes back the same message as an acknowledgement. The tty reader on ARM Cortex-A core can get the message, and start another transaction. The demo demonstrates RPMsg’s ability to send arbitrary content back and forth.

In U-Boot, type the following in order to boot the OS automatically while loading the M core:

=> setenv m4image rpmsg_lite_str_echo_rtos.bin ; setenv mcore_image rpmsg_lite_str_echo_rtos.bin
=> setenv m4enabled 1 ; setenv mcore_enabled 1
=> boot

If you wish to load the example from Linux:

# echo rpmsg_lite_str_echo_rtos_imxcm7.elf > /sys/class/remoteproc/remoteproc0/firmware
# echo start > /sys/class/remoteproc/remoteproc0/state

Note: the name of the elf file depends on the type of core it is running on (m4, m7 or m33)

On the second serial port, you should see the following output:

RPMSG String Echo FreeRTOS RTOS API Demo...

You can then load the RPMsg module so the communication between the two cores can start.

# modprobe imx_rpmsg_tty
# echo "this is a test" > /dev/ttyRPMSG30

The last command above writes into the tty node, which means that the Cortex-M received data:

Nameservice sent, ready for incoming messages...
Get Message From Master Side : "hello world!" [len : 12]
Get Message From Master Side : "this is a test" [len : 14]
Get New Line From Master Side

RPMsg Ping Pong demo

Same as previous demo, this one demonstrates the RPMsg communication. After the communication channels creation, Linux OS transfers the first integer to FreeRTOS OS. The receiving peer adds 1 to the integer and transfers it back, a hundred times and then stops.

The procedure to boot from U-Boot is the same as the TTY demo but the image is called rpmsg_lite_pingpong_rtos_linux_remote.bin.

Now, if you want to start this app after the TTY one without rebooting, you first need to close the TTY one:

# rmmod imx_rpmsg_tty
# echo stop > /sys/class/remoteproc/remoteproc0/state

Then you can load the ping pong example:

# echo rpmsg_lite_pingpong_rtos_linux_remote.elf > /sys/class/remoteproc/remoteproc0/firmware
# echo start > /sys/class/remoteproc/remoteproc0/state
# modprobe imx_rpmsg_pingpong

On the second serial port, you should see the following output:

RPMSG Ping-Pong FreeRTOS RTOS API Demo...
RPMSG Share Base Addr is 0x55000000
Link is up!
Nameservice announce sent.
Waiting for ping...
Sending pong...
...
Waiting for ping...
Sending pong...
Ping pong done, deinitializing...
Looping forever...

That’s it, you should now be able to build, modify, run and debug any app!