This blog post will present the architecture of the i.MX6SoloX and i.MX7D processors and explain how to build and run the FreeRTOS BSP v1.0.1 on the MCU.
Both processors are coupling a Cortex-A with a Cortex-M4 core inside one chip to offer the best of MPU and MCU worlds (see i.MX7D diagram). Content below will apply for our Nit6_SoloX and Nitrogen7 platforms.
You can download a demo image from here:
As usual, you’ll need to register on our site and agree to the EULA because it contains NXP content. The image is a 1GB SD card image that can be restored using zcat and dd under Linux.
~$ zcat 20160804-buildroot*.img.gz | sudo dd of=/dev/sdX bs=1M
For Windows users, please use Alex Page’s USB Image Tool.
This image contains the following components:
Please make sure to update U-Boot from its prompt before getting started:
=> run clearenv => run upgradeu
After the upgrade, the board should reset, you can start your first Hello World application on the Cortex-M4:
=> run m4update => run m4boot
As an introduction, here is the definition of terms that will be used throughout the post:
The i.MX6SX and i.MX7 processors offer an MCU and a MPU in the same chip, this is called a Heterogeneous Multicore Processing Architecture.
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, it is assumed that U-Boot is the bootloader used by your system. The reason is that U-Boot provides a bootaux
command which allows to start the Cortex-M4.
Once started, both CPU are on their own, executing different instructions at different speeds.
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:
Note that the TCM is the preferred option when possible since it offers the best performances since it is an internal memory dedicated to the Cortex-M4.
External memories, such as the DDR or QSPI, offer more space but are also much slower to access.
In this article, it is assumed that every application runs from the TCM.
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.
We insist here on the fact that the Linux kernel is not real-time, not deterministic whereas FreeRTOS on Cortex-M4 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:
Since both cores can access the same peripherals, a mechanism has been created to avoid concurrent access, allowing to ensure a program's behavior on one core does not depend on what is executed/accessed on the other core.
This mechanism is the RDC, it can be used to grant 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-M4, and any access from ARM Cortex-A core on those peripherals may cause the program to hang.
The default RDC settings are:
The user of this package can remove or change the RDC settings in the example/demo or in his application. It is recommended to limit 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 a specific device tree is used when using the MCU:
The memory declaration is also modified in the device tree above in order to reserve some areas for FreeRTOS and/or shared memory.
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:
Note that the DDR is used by default in RPMsg to exchange messages between cores.
Here are some links with more details on the implementation:
The BSP actually comes with some documentation which we recommend reading in order to know more on the subject:
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://launchpad.net/gcc-arm-embedded/4.9/4.9-2015-q3-update/+download/gcc-arm-none-eabi-4_9-2015q3... ~/toolchains$ tar xjf gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2 ~/toolchains$ rm gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2
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
The FreeRTOS BSP v1.0.1 is available from our GitHub freertos-boundary repository.
~$ git clone https://github.com/boundarydevices/freertos-boundary.git freertos ~$ cd freertos
Depending on the processor/board you plan on using, the branch is different.
For Nit6_SoloX (i.MX6SX), use the imx6sx_1.0.1 branch.
~/freertos$ git checkout origin/imx6sx_1.0.1 -b imx6sx_1.0.1
For Nitrogen7 (i.MX7D), use the imx7d_1.0.1 branch.
~/freertos$ git checkout origin/imx7d_1.0.1 -b imx7d_1.0.1
Finally, you need to export the ARMGCC_DIR
variable so FreeRTOS knows your toolchain location.
~/freertos$ export ARMGCC_DIR=$HOME/toolchains/gcc-arm-none-eabi-4_9-2015q3/
First, here is a quick overview of what the FreeRTOS BSP looks like:
All the applications are located under the examples folder:
examples/imx6sx_nit6sx_m4/
for Nit6_SoloXexamples/imx7d_nitrogen7_m4/
for Nitrogen7As an example, we will build the helloworld application for Nitrogen7:
~/freertos$ cd examples/imx7d_nitrogen7_m4/demo_apps/hello_world/armgcc/ ~/freertos/examples/imx7d_nitrogen7_m4/demo_apps/hello_world/armgcc$ ./build_all.sh ~/freertos/examples/imx7d_nitrogen7_m4/demo_apps/hello_world/armgcc$ ls release/ hello_world.bin hello_world.elf hello_world.hex hello_world.map
The build_all.sh
script builds both debug and release binaries. If you don't have a JTAG to debug with, the debug target can be discarded.
You can then copy that hello_world.bin firmware to the root of an SD card to flash it.
First you need to flash the image provided at the beginning of this post to an SD Card.
The SD Card contains the U-Boot version that enables the use of the Cortex-M4 make sure to update it as explained in the impatient section.
By default, the firmware is loaded from NOR to TCM. You can execute m4update
to upgrade the firmware in NOR. It will look for file named m4_fw.bin as the root of any external storage (SD, USB, SATA) and flash it at the offset 0x1E0000
of the NOR:
=> run m4update
If you wish to flash a file named differently, you can modify the m4image
variable as follows:
=> setenv m4image
While debugging on the MCU, you might wish not to write every firmware into NOR so we've added a command that loads the M4 firmware directly from external storage.
=> setenv m4boot 'run m4boot_ext'
Before going any further, make sure to hook up the second serial port to your machine as the one marked as "console" will be used for U-Boot and the other one will display data coming from the MCU.
In order to start the MCU automatically at boot up, we need to set a variable that will tell the 6x_bootscript
to load the firmware. To do so, make sure to save this variable.
=> setenv m4enabled 1 => saveenv
This blog post only considers the TCM as the firmware location for execution. If you wish to use another memory, such as the OCRAM or QSPI or DDR, you can specify it with U-Boot variables.
=> setenv m4loadaddr => setenv m4size
Note that the linker script must be different for a program to be executed from another location. Also, the size reserved in NOR right now is 128kB.
The Hello World project is a simple demonstration program that uses the BSP software. It prints the "Hello World" message to the ARM Cortex-M4 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 => run m4update => run m4boot
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.
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 what is received, 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:
=> setenv m4image rpmsg_str_echo_freertos_example.bin => run m4update => boot
On the second serial port, you should see the following output:
RPMSG String Echo FreeRTOS RTOS API Demo... RPMSG Init as Remote
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 imx_rpmsg_tty rpmsg0: new channel: 0x400 -> 0x0! Install rpmsg tty driver! # echo test > /dev/ttyRPMSG
The last command above writes into the tty node, which means that the Cortex-M4 should have received data as it can be seen on the second serial port.
Name service handshake is done, M4 has setup a rpmsg channel [0 ---> 1024] Get Message From Master Side : "test" [len : 4] Get New Line From Master Side
Same as previous demo, this one demonstrates the RPMsg communication. After the communication channels are created, Linux OS transfers the first integer to FreeRTOS OS. The receiving peer adds 1 to the integer and transfers it back. The loop continues infinitely.
In U-Boot, type the following:
=> setenv m4image rpmsg_pingpong_freertos_example.bin => run m4update => boot
On the second serial port, you should see the following output:
RPMSG PingPong FreeRTOS RTOS API Demo... RPMSG Init as Remote
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 imx_rpmsg_pingpong rpmsg0: new channel: 0x400 -> 0x0! # get 1 (src: 0x0) get 3 (src: 0x0) get 5 (src: 0x0) ...
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.
Name service handshake is done, M4 has setup a rpmsg channel [0 ---> 1024] Get Data From Master Side : 0 Get Data From Master Side : 2 Get Data From Master Side : 4 ...
That's it, you should now be able to build, modify, run and debug
t