One of the many advantages of developing with NXP's i.MX 8M Family of application processors is the ability to utilize both the Cortex-A53 as well as the Cortex-M core. This blog post will first present the architecture of the i.MX 8M heterogeneous processors as a starting point for the discussion, and then explain how to build and run the FreeRTOS SDK v2.13 on its MCU. The i.MX 8MQ and i.MX 8M Mini processors are coupling a Cortex-A53 cluster (4 cores) alongside a Cortex-M4 whereas the i.MX 8M Nano and i.MX 8M Plus are using a Cortex-M7.
For the impatient
You can download any of our currently available OS images for our i.MX 8M platforms:
Note that you can find pre-built binaries of the examples here for each CPU variant:
- i.MX 8M Mini (Nitrogen8MM & SOM)
- i.MX 8M Nano (Nitrogen8MN & SOM)
- i.MX 8M Plus (Nitrogen8MP SOM)
- i.MX 8M Quad (Nitrogen8M & SOM)
There are 3 examples for each variant:
hello_world.bin
- simple application printing "hello world" on the M-core serial port
rpmsg_lite_pingpong_rtos_linux_remote.bin
- ping pong examples between both CPU clusters using RPMSG
rpmsg_lite_str_echo_rtos.bin
- TTY example showing serial communication between CPU clusters using RPMSG
You can then start your first Hello World application on the Cortex-M manually (after copying one of the binary above to your storage):
=> load mmc 0 $m4loadaddr hello_world.bin
=> bootaux $m4loadaddr
Note that all the U-Boot variables are named 'm4x' for legacy reasons, but those still apply for platforms with an M7 core. On the second serial port (UART4 for 8M Mini/Nano/Plus, UART2 for 8M Quad), you should see the following output:
Hello World!
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.13
- FreeRTOS kernel updated to its version 10.5.0
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 or M7
- MPU: Microprocessor Unit such as the ARM Cortex-A series, here referring to the Cortex-A53
- RTOS: Real-Time Operating System such as FreeRTOS or MQX
The i.MX 8M processors are Heterogeneous Multicore Processors as they offer an MCU and a MPU in the same chip.
How it works?
The first thing to know is that one of the cores is the "master", meaning that it is in charge to boot the other core which otherwise will stay in reset. The BootROM will always boot the Cortex-A core first. In this article, the assumption is that U-Boot is your system bootloader. The reason is that U-Boot provides a bootaux
command which allows the Cortex-M to start. Once started, both CPU clusters are on their own, executing different instructions at different speeds.
Where is the code running from?
It actually depends on the application linker script used. When GCC is linking your application into an ELF executable file, it needs to know the code location in memory. There are several options in both processors, code can be located in one of the following:
- TCM (Tightly Coupled Memory): 128kB available
- DDR: up to 1MB available (can be increased, set in the device tree)
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 RDC which grants peripheral and memory access permissions to each core. The examples and demo applications in the FreeRTOS BSP use RDC to allocate peripheral access permission. When running the ARM Cortex-A application with the FreeRTOS BSP example/demo, it is important to respect the reserved peripheral. The FreeRTOS BSP application has reserved peripherals that are used only by ARM Cortex-M, and any access from ARM Cortex-A core on those peripherals may cause the program 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).
The user of this package can remove or change the RDC settings in the example/demo or in his application. We recommend limiting the access of a peripheral to the only core using it when possible. Also, in order for a peripheral not to show up as available in Linux, it is mandatory to disable it in the device, which is why it is required to use a specific device tree when using the MCU. The device tree also declares different memory sections and reserves them 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 3.4.x kernel onward. This API 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 that RPMsg uses the DDR by default to exchange messages between cores.
Where can I find more documentation?
The BSP comes with some documentation which we recommend reading in order to know more on the subject:
- MCUXpresso SDK API Reference Manual
- Getting Started with MCUXpresso SDK
- Getting Started with MCUXpresso SDK and FreeRTOS
- Getting Started with Multicore SDK
Remoteproc
Like the previous release, this one allows to start, stop and reload the firmware from user-space thanks to the remoteproc API! Here is how to use that new API (using our i.MX 8M Mini platform for this example):
-
- Place your Cortex-M firmware (in elf format) under
/lib/firmware/
- Place your Cortex-M firmware (in elf format) under
# wget http://linode.boundarydevices.com/mcore/sdk_2.13/imx8mm/hello_world.elf \
-o /lib/firmware/
-
- Tell the remoteproc device what is the name of your firmware
# echo hello_world.elf > /sys/class/remoteproc/remoteproc0/firmware
-
- Start the Cortex-M processor
# echo start > /sys/class/remoteproc/remoteproc0/state
At this stage, if you use the RPMSG sub-system, you can probe the module. If you want to load another application (or another version), you only need to stop the current execution as shown below. Then you can re-iterate the above step to (re)load your app.
# echo stop > /sys/class/remoteproc/remoteproc0/state
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.13 is available from our GitHub freertos-boundary repository.
~$ git clone https://github.com/boundarydevices/freertos-boundary.git freertos
~$ cd freertos
~/freertos$ git checkout
You need to use the proper branch_name for your platform:
imx8mm_2.13
for Nitrogen8M Mini and SOMimx8mn_2.13
for Nitrogen8M Nano and SOMimx8mp_2.13
for Nitrogen8M Plus SOMimx8mq_2.13
for Nitrogen8M and SOM
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
├── multicore_examples
├── project_template
└── rtos_examples
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.
Run the demo apps
Basic setup
By default, U-Boot loads the firmware from eMMC to TCM. 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. This blog post only considers the firmware file is named m4_fw.bin
, if you wish to use another name, you need to set the m4image
variable:
=> setenv m4image rpmsg_lite_str_echo_rtos.bin
=> saveenv
If you want to load the MCU fimware manually from eMMC, here is the procedure:
=> load mmc 0 $m4loadaddr $m4image
=> bootaux $m4loadaddr
If you want to load the MCU fimware from TFTP:
=> dhcp $m4loadaddr 192.168.1.60:$m4image
=> bootaux $m4loadaddr
In order to start the MCU automatically at boot up, we need to set a variable that will tell the boot.scr
to load the firmware. To do so, make sure to save this variable.
=> setenv m4enabled 1
=> saveenv
Note that this section keeps on using the previous way of loading apps (from U-Boot) but you can now use the remoteproc approach if you want.
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:
=> setenv m4image hello_world.bin
=> load mmc 0 $m4loadaddr $m4image
=> bootaux $m4loadaddr
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.
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 m4enabled 1
=> boot
On the second serial port, you should see the following output:
RPMSG String Echo FreeRTOS RTOS API Demo...
Once Linux has booted up, you need to 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. In U-Boot, type the following:
=> setenv m4image rpmsg_lite_pingpong_rtos_linux_remote.bin
=> setenv m4enabled 1
=> boot
On the second serial port, you should see the following output:
RPMSG Ping-Pong FreeRTOS RTOS API Demo...
Once Linux has booted up, you need to load the RPMsg module so the communication between the two cores can start.
# modprobe imx_rpmsg_pingpong
[ 30.501148] get 1 (src: 0x1e)
[ 30.506527] get 3 (src: 0x1e)
...
[ 30.730958] get 101 (src: 0x1e)
[ 30.734104] imx_rpmsg_pingpong virtio0.rpmsg-openamp-demo-channel.-1.30: goodbye!
While you can send the received data from the MCU on the main serial port, you can also see the data received from the MPU on the secondary serial port.
RPMSG Share Base Addr is 0xb8000000
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!