Skip to main content

Using the M4 MCU on the i.MX8M Mini


Over the last few years, SoCs targeted at embedded GNU/Linux applications became heterogenous architectures. Instead of adding more identical copies of CPU cores (Cortex-A class), a small companion micro controller core (Cortex-M class) was added. Compared to the complex architecture of Cortex-A systems, including their multi-layer caches, those cores are usually deterministic and thus predestined for low-latency "real time" jobs. NXP's first such SoC is the i.MX6 Solo X device featuring an Cortex-M4 next to an Cortex-A9 core. The i.MX8 family moved the Cortex-A cores into the 64 bit world and there are different combinations of Cortex-M companion micro controllers, but all of them do feature them as they are pretty "cheap" in terms of die space and transistor count.

From what I can see, it is still uncommon to use those micro controllers in actual projects though. Today I want to take a short look on how to run simple programs on the Cortex-M4 of the i.MX8M Mini SoC, especially we will use the official i.MX8M Mini EVK.


MCUXpresso for Cortex-M

While GNU/Linux development for the NXP SoCs is based on Yocto, the micro controllers in the i.MX RT, Kinetis and LPC families are supported through the MCUXpresso SDK. Although the SDK can be used with a "regular" ARM cross tool chain, it is tightly integrated into NXPs Eclipse based MCUXpresso IDE.

NXP decided to support the i.MX parts with a microcontroller also in the MCUXpresso SDK, but the MCUXpresso IDE currently (March 2022) cannot be used for those use cases. So we will use plain old command line together with the Debian provided ARM cross compiler to compile the examples.

Cross Tool Chain

Installing the cross compiler and debugger should be as easy as one "apt install" command:

dzu@krikkit:~$ sudo apt install gcc-arm-none-eabi gdb-multiarch
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  binutils-arm-none-eabi libnewlib-arm-none-eabi libnewlib-dev libstdc++-arm-none-eabi-dev libstdc++-arm-none-eabi-newlib
Suggested packages:
The following NEW packages will be installed:
  binutils-arm-none-eabi gcc-arm-none-eabi gdb-multiarch libnewlib-arm-none-eabi libnewlib-dev
  libstdc++-arm-none-eabi-dev libstdc++-arm-none-eabi-newlib
0 upgraded, 7 newly installed, 0 to remove and 19 not upgraded.
Need to get 389 MB of archives.
After this operation, 2582 MB of additional disk space will be used.
Do you want to continue? [Y/n] 
Get:1 bookworm/main amd64 binutils-arm-none-eabi amd64 2.37-7+15 [2730 kB]
Get:2 bookworm/main amd64 gcc-arm-none-eabi amd64 15:10.3-2021.07-4 [43.2 MB]
Get:3 bookworm/main amd64 gdb-multiarch amd64 10.1-2 [3720 kB]
Get:4 bookworm/main amd64 libnewlib-dev all 3.3.0-1.3 [262 kB]
Get:5 bookworm/main amd64 libnewlib-arm-none-eabi all 3.3.0-1.3 [43.6 MB]
Get:6 bookworm/main amd64 libstdc++-arm-none-eabi-dev all 15:10.3-2021.07-4+19 [1034 kB]     
Get:7 bookworm/main amd64 libstdc++-arm-none-eabi-newlib all 15:10.3-2021.07-4+19 [295 MB]   
Fetched 389 MB in 35s (11.2 MB/s)                                                                                         
Selecting previously unselected package binutils-arm-none-eabi.
(Reading database ... 541064 files and directories currently installed.)
Preparing to unpack .../0-binutils-arm-none-eabi_2.37-7+15_amd64.deb ...
Unpacking binutils-arm-none-eabi (2.37-7+15) ...
Selecting previously unselected package gcc-arm-none-eabi.
Preparing to unpack .../1-gcc-arm-none-eabi_15%3a10.3-2021.07-4_amd64.deb ...
Unpacking gcc-arm-none-eabi (15:10.3-2021.07-4) ...
Selecting previously unselected package gdb-multiarch.
Preparing to unpack .../2-gdb-multiarch_10.1-2_amd64.deb ...
Unpacking gdb-multiarch (10.1-2) ...
Selecting previously unselected package libnewlib-dev.
Preparing to unpack .../3-libnewlib-dev_3.3.0-1.3_all.deb ...
Unpacking libnewlib-dev (3.3.0-1.3) ...
Selecting previously unselected package libnewlib-arm-none-eabi.
Preparing to unpack .../4-libnewlib-arm-none-eabi_3.3.0-1.3_all.deb ...
Unpacking libnewlib-arm-none-eabi (3.3.0-1.3) ...
Selecting previously unselected package libstdc++-arm-none-eabi-dev.
Preparing to unpack .../5-libstdc++-arm-none-eabi-dev_15%3a10.3-2021.07-4+19_all.deb ...
Unpacking libstdc++-arm-none-eabi-dev (15:10.3-2021.07-4+19) ...
Selecting previously unselected package libstdc++-arm-none-eabi-newlib.
Preparing to unpack .../6-libstdc++-arm-none-eabi-newlib_15%3a10.3-2021.07-4+19_all.deb ...
Unpacking libstdc++-arm-none-eabi-newlib (15:10.3-2021.07-4+19) ...
Setting up binutils-arm-none-eabi (2.37-7+15) ...
Setting up gcc-arm-none-eabi (15:10.3-2021.07-4) ...
Setting up libnewlib-dev (3.3.0-1.3) ...
Setting up gdb-multiarch (10.1-2) ...
Setting up libnewlib-arm-none-eabi (3.3.0-1.3) ...
Setting up libstdc++-arm-none-eabi-dev (15:10.3-2021.07-4+19) ...
Setting up libstdc++-arm-none-eabi-newlib (15:10.3-2021.07-4+19) ...
Processing triggers for ccache (4.5.1-1) ...
Updating symlinks in /usr/lib/ccache ...
Processing triggers for man-db (2.10.1-1) ...
Processing triggers for libc-bin (2.33-7) ...

In order for CMake to pick up this tool chain, all we need to do is to export the ARMGCC_DIR environment variable pointing to the root of the installation. As the cross compiler is installed via our native package management, we simply need to specify /usr here:

dzu@krikkit:~$ export ARMGCC_DIR=/usr

MCUXpresso SDK

To use the SDK for one of the microcontrollers, NXP chose to offer an "SDK Builder" web platform that will generate a delivery based on your actual inputs. To be honest, I never understood this and luckily it seems NXP seems to be moving away from this as there is now an GitHub repo for the SDK sources. As the SDK bundles up multiple components, NXP reuses the west tool from Zephyr to automate this task. So if you do not yet have west installed, now is the time to fix this with pip3:

dzu@krikkit:~$ pip3 install --user -U west

Having the required tools in place, following the documentation from the GitHub repository is easy. At the time of writing, the tag MCUX_2.10.0 is the latest tag in the repo, so I will use that. Note that my environment is setup for Zephyr development so I explicitly have to unset ZEPHYR_BASE before using west outside Zephyr. For completeness I will show the complete transcript to see what is going on, but these are really only two invocations of west:

dzu@krikkit:/opt/src/git$ unset ZEPHYR_BASE
dzu@krikkit:/opt/src/git$ west init -m --mr MCUX_2.10.0 mcuxsdk
=== Initializing in /opt/src/git/mcuxsdk
--- Cloning manifest repository from, rev. MCUX_2.10.0
Cloning into '/opt/src/git/mcuxsdk/.west/manifest-tmp'...
remote: Enumerating objects: 31462, done.
remote: Counting objects: 100% (31462/31462), done.
remote: Compressing objects: 100% (9370/9370), done.
remote: Total 31462 (delta 22687), reused 30488 (delta 21758), pack-reused 0
Receiving objects: 100% (31462/31462), 52.06 MiB | 11.86 MiB/s, done.
Resolving deltas: 100% (22687/22687), done.
Note: switching to '1e97870ccc900b4431c91f229e83c65f17d8376f'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

Updating files: 100% (8537/8537), done.
--- setting manifest.path to core
=== Initialized. Now run "west update" inside /opt/src/git/mcuxsdk.
dzu@krikkit:/opt/src/git$ cd mcuxsdk/
dzu@krikkit:/opt/src/git/mcuxsdk$ west update
=== updating mcux-sdk-examples (examples):
--- mcux-sdk-examples: initializing
Initialized empty Git repository in /opt/src/git/mcuxsdk/examples/.git/
--- mcux-sdk-examples: fetching, need revision MCUX_2.10.0
remote: Enumerating objects: 89227, done.
remote: Counting objects: 100% (89227/89227), done.
remote: Compressing objects: 100% (24324/24324), done.
remote: Total 89227 (delta 68793), reused 83892 (delta 63459), pack-reused 0
Receiving objects: 100% (89227/89227), 25.26 MiB | 11.88 MiB/s, done.
Resolving deltas: 100% (68793/68793), done.
 * tag                   MCUX_2.10.0 -> FETCH_HEAD
 * [new tag]             MCUX_2.10.0 -> MCUX_2.10.0
Updating files: 100% (122278/122278), done.
HEAD is now at 09bfa4f69 Update file to describe known issue for TAG MCUX_2.10.0
HEAD is now at 09bfa4f69 Update file to describe known issue for TAG MCUX_2.10.0
=== updating FreeRTOS-Kernel (rtos/freertos/freertos_kernel):
--- FreeRTOS-Kernel: initializing
Initialized empty Git repository in /opt/src/git/mcuxsdk/rtos/freertos/freertos_kernel/.git/
--- FreeRTOS-Kernel: fetching, need revision MCUX_2.10.0
remote: Enumerating objects: 151740, done.
remote: Counting objects: 100% (35/35), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 151740 (delta 12), reused 14 (delta 8), pack-reused 151705
Receiving objects: 100% (151740/151740), 105.16 MiB | 12.02 MiB/s, done.
Resolving deltas: 100% (109665/109665), done.
 * tag                   MCUX_2.10.0             -> FETCH_HEAD
 * [new tag]             BackupPoints            -> BackupPoints
 * [new tag]             MCUX_2.10.0             -> MCUX_2.10.0
 * [new tag]             MCUX_2.10.0_aarch64_0.1 -> MCUX_2.10.0_aarch64_0.1
 * [new tag]             MCUX_2.9.0              -> MCUX_2.9.0
 * [new tag]             PartialReleases         -> PartialReleases
 * [new tag]             V10.0.0                 -> V10.0.0
 * [new tag]             V10.0.1                 -> V10.0.1
 * [new tag]             V10.1.0                 -> V10.1.0
 * [new tag]             V10.1.1                 -> V10.1.1
 * [new tag]             V10.2.0                 -> V10.2.0
 * [new tag]             V10.2.1                 -> V10.2.1
 * [new tag]             V10.3.0                 -> V10.3.0
 * [new tag]             V10.3.0-kernel-only     -> V10.3.0-kernel-only
 * [new tag]             V10.3.1-kernel-only     -> V10.3.1-kernel-only
 * [new tag]             V10.4.0-kernel-only     -> V10.4.0-kernel-only
 * [new tag]             V10.4.1-kernel-only     -> V10.4.1-kernel-only
 * [new tag]             V10.4.2                 -> V10.4.2
 * [new tag]             V10.4.3                 -> V10.4.3
 * [new tag]             V4.0.1                  -> V4.0.1
 * [new tag]             V4.0.2                  -> V4.0.2
 * [new tag]             V4.0.3                  -> V4.0.3
 * [new tag]             V4.0.5                  -> V4.0.5
 * [new tag]             V4.1.0                  -> V4.1.0
 * [new tag]             V4.1.1                  -> V4.1.1
 * [new tag]             V4.1.2                  -> V4.1.2
 * [new tag]             V4.1.3                  -> V4.1.3
 * [new tag]             V4.2.0                  -> V4.2.0
 * [new tag]             V4.2.1                  -> V4.2.1
 * [new tag]             V4.3.0                  -> V4.3.0
 * [new tag]             V4.3.1                  -> V4.3.1
 * [new tag]             V4.4.0                  -> V4.4.0
 * [new tag]             V4.5.0                  -> V4.5.0
 * [new tag]             V4.6.1                  -> V4.6.1
 * [new tag]             V4.7.0                  -> V4.7.0
 * [new tag]             V4.7.1                  -> V4.7.1
 * [new tag]             V4.7.2                  -> V4.7.2
 * [new tag]             V4.8.0                  -> V4.8.0
 * [new tag]             V5.0.0                  -> V5.0.0
 * [new tag]             V5.0.2                  -> V5.0.2
 * [new tag]             V5.0.3                  -> V5.0.3
 * [new tag]             V5.1.2                  -> V5.1.2
 * [new tag]             V5.2.0                  -> V5.2.0
 * [new tag]             V5.3.0                  -> V5.3.0
 * [new tag]             V5.3.1                  -> V5.3.1
 * [new tag]             V5.4.1                  -> V5.4.1
 * [new tag]             V5.4.2                  -> V5.4.2
 * [new tag]             V6.0.0                  -> V6.0.0
 * [new tag]             V6.0.1                  -> V6.0.1
 * [new tag]             V6.0.3                  -> V6.0.3
 * [new tag]             V6.0.4                  -> V6.0.4
 * [new tag]             V6.0.5                  -> V6.0.5
 * [new tag]             V6.1.0                  -> V6.1.0
 * [new tag]             V6.1.1                  -> V6.1.1
 * [new tag]             V7.0.0                  -> V7.0.0
 * [new tag]             V7.0.1                  -> V7.0.1
 * [new tag]             V7.0.2                  -> V7.0.2
 * [new tag]             V7.1.0                  -> V7.1.0
 * [new tag]             V7.1.1                  -> V7.1.1
 * [new tag]             V7.2.0                  -> V7.2.0
 * [new tag]             V7.3.0                  -> V7.3.0
 * [new tag]             V7.4.0                  -> V7.4.0
 * [new tag]             V7.4.1                  -> V7.4.1
 * [new tag]             V7.4.2                  -> V7.4.2
 * [new tag]             V7.5.0                  -> V7.5.0
 * [new tag]             V7.5.1                  -> V7.5.1
 * [new tag]             V7.5.2                  -> V7.5.2
 * [new tag]             V7.5.3                  -> V7.5.3
 * [new tag]             V7.6.0                  -> V7.6.0
 * [new tag]             V8.0.0                  -> V8.0.0
 * [new tag]             V8.0.0-rc1              -> V8.0.0-rc1
 * [new tag]             V8.0.0rc1               -> V8.0.0rc1
 * [new tag]             V8.0.1                  -> V8.0.1
 * [new tag]             V8.1.0                  -> V8.1.0
 * [new tag]             V8.1.1                  -> V8.1.1
 * [new tag]             V8.1.2                  -> V8.1.2
 * [new tag]             V8.2.0                  -> V8.2.0
 * [new tag]             V8.2.0-rc1              -> V8.2.0-rc1
 * [new tag]             V8.2.0rc1               -> V8.2.0rc1
 * [new tag]             V8.2.1                  -> V8.2.1
 * [new tag]             V8.2.2                  -> V8.2.2
 * [new tag]             V8.2.3                  -> V8.2.3
 * [new tag]             V9.0.0                  -> V9.0.0
 * [new tag]             V9.0.0-rc1              -> V9.0.0-rc1
 * [new tag]             V9.0.0-rc2              -> V9.0.0-rc2
 * [new tag]             V9.0.0rc1               -> V9.0.0rc1
 * [new tag]             V9.0.0rc2               -> V9.0.0rc2
HEAD is now at 0c1056bf5 Apply MCUXpresso SDK 2.10.0 updates.
HEAD is now at 0c1056bf5 Apply MCUXpresso SDK 2.10.0 updates.
=== updating mcux-sdk-middleware-sdmmc (middleware/sdmmc):
--- mcux-sdk-middleware-sdmmc: initializing
Initialized empty Git repository in /opt/src/git/mcuxsdk/middleware/sdmmc/.git/
--- mcux-sdk-middleware-sdmmc: fetching, need revision MCUX_2.10.0
remote: Enumerating objects: 147, done.
remote: Counting objects: 100% (147/147), done.
remote: Compressing objects: 100% (60/60), done.
remote: Total 147 (delta 80), reused 142 (delta 75), pack-reused 0
Receiving objects: 100% (147/147), 144.28 KiB | 358.00 KiB/s, done.
Resolving deltas: 100% (80/80), done.
 * tag               MCUX_2.10.0 -> FETCH_HEAD
 * [new tag]         MCUX_2.10.0 -> MCUX_2.10.0
 * [new tag]         MCUX_2.9.0  -> MCUX_2.9.0
HEAD is now at 10968f0 Sync with MCUX SDK 2.10.0 RFP release.
HEAD is now at 10968f0 Sync with MCUX SDK 2.10.0 RFP release.
=== updating mcux-sdk-middleware-multicore (middleware/multicore):
--- mcux-sdk-middleware-multicore: initializing
Initialized empty Git repository in /opt/src/git/mcuxsdk/middleware/multicore/.git/
--- mcux-sdk-middleware-multicore: fetching, need revision MCUX_2.10.0
remote: Enumerating objects: 212, done.
remote: Counting objects: 100% (212/212), done.
remote: Compressing objects: 100% (96/96), done.
remote: Total 212 (delta 103), reused 212 (delta 103), pack-reused 0
Receiving objects: 100% (212/212), 6.26 MiB | 5.14 MiB/s, done.
Resolving deltas: 100% (103/103), done.
 * tag               MCUX_2.10.0 -> FETCH_HEAD
 * [new tag]         MCUX_2.10.0 -> MCUX_2.10.0
HEAD is now at 965e9bd MulticoreSDK 2.10.0 release
HEAD is now at 965e9bd MulticoreSDK 2.10.0 release
=== updating rpmsg-lite (middleware/multicore/rpmsg_lite):
--- rpmsg-lite: initializing
Initialized empty Git repository in /opt/src/git/mcuxsdk/middleware/multicore/rpmsg_lite/.git/
--- rpmsg-lite: fetching, need revision 3933134378690a9c04d533e26516b3388fe4acc3
remote: Enumerating objects: 1001, done.
remote: Counting objects: 100% (268/268), done.
remote: Compressing objects: 100% (113/113), done.
remote: Total 1001 (delta 136), reused 225 (delta 107), pack-reused 733
Receiving objects: 100% (1001/1001), 705.09 KiB | 9.93 MiB/s, done.
Resolving deltas: 100% (544/544), done.
 * [new branch]      develop    -> refs/west/develop
 * [new branch]      gh-pages   -> refs/west/gh-pages
 * [new branch]      master     -> refs/west/master
 * [new tag]         v1.1.0     -> v1.1.0
 * [new tag]         v1.2.0     -> v1.2.0
 * [new tag]         v2.0.0     -> v2.0.0
 * [new tag]         v2.2.0     -> v2.2.0
 * [new tag]         v3.0.0     -> v3.0.0
 * [new tag]         v3.1.0     -> v3.1.0
 * [new tag]         v3.1.1     -> v3.1.1
 * [new tag]         v3.1.2     -> v3.1.2
 * [new tag]         v3.2.0     -> v3.2.0
HEAD is now at 3933134 Adding support for i.MX8 MP multicore platform
HEAD is now at 3933134 Adding support for i.MX8 MP multicore platform
=== updating erpc (middleware/multicore/erpc):
--- erpc: initializing
Initialized empty Git repository in /opt/src/git/mcuxsdk/middleware/multicore/erpc/.git/
--- erpc: fetching, need revision 1.8.1
remote: Enumerating objects: 5055, done.
remote: Counting objects: 100% (884/884), done.
remote: Compressing objects: 100% (320/320), done.
remote: Total 5055 (delta 608), reused 772 (delta 561), pack-reused 4171
Receiving objects: 100% (5055/5055), 4.90 MiB | 7.48 MiB/s, done.
Resolving deltas: 100% (3886/3886), done.
 * tag               1.8.1      -> FETCH_HEAD
 * [new tag]         1.4.0      -> 1.4.0
 * [new tag]         1.4.1      -> 1.4.1
 * [new tag]         1.5.0      -> 1.5.0
 * [new tag]         1.6.0      -> 1.6.0
 * [new tag]         1.7.0      -> 1.7.0
 * [new tag]         1.7.1      -> 1.7.1
 * [new tag]         1.7.2      -> 1.7.2
 * [new tag]         1.7.3      -> 1.7.3
 * [new tag]         1.7.4      -> 1.7.4
 * [new tag]         1.8.0      -> 1.8.0
 * [new tag]         1.8.1      -> 1.8.1
 * [new tag]         1.9.0      -> 1.9.0
HEAD is now at 9849c5d eRPC updates 07/2021
HEAD is now at 9849c5d eRPC updates 07/2021

Hello world demo

The i.MX8MM EVK exposes two UART interfaces through its USB connection. The first one is routed to the Cortex-A core(s) and the second one is used by the Cortex-M4. So in order to prove basic functionality we will use a sample demo outputting only the string "Hello world" on the UART dedicated to the Cortex-M core. We can find the source for this in examples/evkmimx8mm/demo_apps/hello_world:

dzu@krikkit:/opt/src/git/mcuxsdk$ cd examples/evkmimx8mm/demo_apps/hello_world/
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world ((MCUX_2.10.0))$ ls
armgcc/            fsl_iomuxc.h   hello_world_v3_8.xml  pin_mux.h
empty_rsc_table.c  hello_world.c  pin_mux.c             readme.txt
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world ((MCUX_2.10.0))$ 

The hello_world.c program is easy to understand:

 * Copyright (c) 2013 - 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 * SPDX-License-Identifier: BSD-3-Clause

#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"

 * Definitions

 * Prototypes

 * Code
 * @brief Main function
int main(void)
    char ch;

    /* Init board hardware. */
    /* Board specific RDC settings */


    PRINTF("hello world.\r\n");

    while (1)
	ch = GETCHAR();

Next to the sources we find an armgcc sub directory containing the infrastructure to compile the example with the regular ARM GCC cross tool chain. When available, other tool chains are supported in correspondingly named sub directories.

To compile it with our cross toolchain, we use one of the supplied shell scripts. Note that there are three different linker scripts (ending with .ld) corresponding to three different target link addresses. The "_cm4_ram.ld" script links the application at 0x1ffe.000 representing the address of the tightly coupled memory area "TCML" in the Cortex-M4 address space. Chapter 2 "Memory Map" in the i.MX8M Mini Technical Reference Manual lists this together with the fact that the same TCML area is mapped at 0x7e.0000 in the Cortex-A53 address space. So we have to keep this in mind when loading the binary from U-Boot (running on the first A53):

dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world ((MCUX_2.10.0))$ cd armgcc/
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$ ls
CMakeLists.txt                build_ddr_debug.bat    build_flash_debug.bat    clean.bat
MIMX8MM6xxxxx_cm4_flash.ld    build_ddr_release.bat  build_flash_release.bat  config.cmake*
MIMX8MM6xxxxx_cm4_ram.ld**  flags.cmake*
build_all.bat                 build_debug.bat        build_release.bat*       **
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$ ./ 
-- BUILD_TYPE: debug
-- BUILD_TYPE: debug
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/arm-none-eabi-gcc
-- The C compiler identification is GNU 10.3.1
-- The CXX compiler identification is GNU 10.3.1
utility_debug_console_lite component is included.
component_iuart_adapter component is included.
driver_common component is included.
driver_reset component is included.
device_CMSIS component is included.
CMSIS_Include_core_cm component is included.
driver_iuart component is included.
utility_assert_lite component is included.
driver_clock component is included.
driver_rdc component is included.
component_lists component is included.
device_startup component is included.
device_system component is included.
utilities_misc_utilities component is included.
-- Configuring done
-- Generating done
-- Build files have been written to: /opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc
Scanning dependencies of target hello_world.elf
[ 11%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/boards/evkmimx8mm/clock_config.c.obj
[ 11%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/hello_world.c.obj
[ 16%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/boards/evkmimx8mm/board.c.obj
[ 27%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/pin_mux.c.obj
[ 27%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/empty_rsc_table.c.obj
[ 33%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/drivers/common/fsl_common_arm.c.obj
[ 38%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/utilities/debug_console_lite/fsl_debug_console.c.obj
[ 44%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/drivers/common/fsl_common.c.obj
[ 50%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/drivers/iuart/fsl_uart.c.obj
[ 55%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/components/uart/fsl_adapter_iuart.c.obj
[ 61%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/utilities/assert/fsl_assert.c.obj
[ 66%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/devices/MIMX8MM6/drivers/fsl_clock.c.obj
[ 77%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/drivers/rdc/fsl_rdc.c.obj
[ 72%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/components/lists/fsl_component_generic_list.c.obj
[ 83%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/devices/MIMX8MM6/system_MIMX8MM6_cm4.c.obj
[ 88%] Building ASM object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/devices/MIMX8MM6/gcc/startup_MIMX8MM6_cm4.S.obj
[ 94%] Building C object CMakeFiles/hello_world.elf.dir/opt/src/git/mcuxsdk/core/utilities/misc_utilities/fsl_sbrk.c.obj
[100%] Linking C executable debug/hello_world.elf
Memory region         Used Size  Region Size  %age Used
    m_interrupts:         576 B        576 B    100.00%
	  m_text:       14892 B     130496 B     11.41%
	  m_data:        2384 B       128 KB      1.82%
	 m_data2:          0 GB        16 MB      0.00%
[100%] Built target hello_world.elf
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$

The compilation results in an ELF file (.elf) that can be understood by the debugger and ELF loaders and a "linear" binary copy (.bin) which can be loaded into RAM without any ELF loader:

dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$ ls debug/
hello_world.bin*  hello_world.elf*
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$

We basically have to ways of loading the binary to the target. For the final product, either U-Boot or Linux will do the loading, but in development we can also use a debugger to do this.

Without a debugger

To use the binary on our target, I copy the resulting binary file to my tftp directory:

dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$ cp debug/hello_world.bin /srv/tftp/
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$

Booting i.MX8MM EVK

In order to run the binary, we will use U-Boot to download the binary into RAM and use its bootaux command to instruct the Cortex-M core to start execution. The general approach is documented in the Getting Started Guide for i.MX8M Mini EVK in section "4. MCUXpresso SDK". So let's try it. Connect to the serial port of the EVK connected to the Cortex-A core, i.e. the second port which is /dev/ttyUSB1 on my system and stop U-Boot:

U-Boot SPL 2021.04-lf_v2021.04+g263b27e076 (Nov 22 2021 - 01:39:23 +0000)
Can't find PMIC:PCA9450
DDRINFO: start DRAM init
DDRINFO:ddrphy calibration done
DDRINFO: ddrmix config done
SEC0:  RNG instantiated
Normal Boot
Trying to boot from MMC1
NOTICE:  BL31: v2.4(release):lf-5.10.72-2.2.0-0-g5782363f9
NOTICE:  BL31: Built : 12:17:17, Nov 18 2021

U-Boot 2021.04-lf_v2021.04+g263b27e076 (Nov 22 2021 - 01:39:23 +0000)

CPU:   i.MX8MMQ rev1.0 at 1200MHz
CPU:   Commercial temperature grade (0C to 95C) at 32C
Reset cause: POR
Model: NXP i.MX8MM EVK board
DRAM:  2 GiB
TCPC:  Vendor ID [0x1fc9], Product ID [0x5110], Addr [I2C1 0x52]
Power supply on USB2
TCPC:  Vendor ID [0x1fc9], Product ID [0x5110], Addr [I2C1 0x50]
Loading Environment from MMC... *** Warning - bad CRC, using default environment

[*]-Video Link 0adv7535_mipi2hdmi adv7535@3d: Can't find cec device id=0x3c
fail to probe panel device adv7535@3d
mxs_video lcdif@32e00000: failed to get any video link display timings
probe video device failed, ret -22

	[0] lcdif@32e00000, video
	[1] mipi_dsi@32e10000, video_bridge
	[2] adv7535@3d, panel
adv7535_mipi2hdmi adv7535@3d: Can't find cec device id=0x3c
fail to probe panel device adv7535@3d
mxs_video lcdif@32e00000: failed to get any video link display timings
probe video device failed, ret -22
In:    serial
Out:   serial
Err:   serial
SEC0:  RNG instantiated

  - ATF 5782363

switch to partitions #0, OK
mmc1 is current device
flash target is MMC:1
Net:   eth0: ethernet@30be0000
Fastboot: Normal
Normal Boot
Hit any key to stop autoboot:  0 

Loading the binary

Contrary to the documentation I want to load the binary over tftp, so let's try. First I'll request an DHCP lease and then I will try to transfer the binary to its intended position in RAM.

u-boot=> setenv autoload no
u-boot=> dhcp
BOOTP broadcast 1
DHCP client bound to address (104 ms)
u-boot=> setenv serverip
u-boot=> tftp 0x7e0000 hello_world.bin
Using ethernet@30be0000 device
TFTP from server; our IP address is
Filename 'hello_world.bin'.

TFTP error: trying to overwrite reserved memory...

Ok, so it seems the documentation is no longer in sync with the current state of the software. U-Boot declines to transfer data from the tftp server directly into the address given by the NXP documentation. If you want to know why this is the case, check out Appendix A below.

To circumvent this problem, we will first transfer the binary to memory and then copy it to the target address:

u-boot=> tftp 0x80000000 hello_world.bin 
Using ethernet@30be0000 device
TFTP from server; our IP address is
Filename 'hello_world.bin'.
Load address: 0x80000000
Loading: #
	 2.7 MiB/s
Bytes transferred = 14092 (370c hex)
u-boot=> cp.b 0x80000000 0x7e0000 ${filesize}

Starting the binary

Before releasing the M4 core out of reset, be sure to open a connection to its serial port (/dev/ttyUSB0 in my case) and then proceed as documented in the getting started guide:

u-boot=> bootaux 0x7e0000
## Starting auxiliary core stack = 0x20020000, pc = 0x1FFE030D...

And sure enough the text shows up in the terminal connected to the UART port.

Using a debugger

Using JLink Debugger

The i.MX8M Mini EVK includes connector J902 to access the JTAG chain of the system. We will use a JLink probe to directly connect to it. There are GNU/Linux binaries available on the JLink download page. On my Debian bookworm, the 64 bit x86 .deb works just fine. More information on the support for our device can be found on the JLink Support for i.MX8M Mini page. In order to understand the differences between these systems, we take a look at the i.MX8M Mini data sheet which explains the differences. Effectively they boils down to this:

| Part Number     | Family                | Part Differentiator  |
| MIMX8MM6DVTLZAA | i.MX 8M Mini Quad     | 4x A53, M4, GPU, VPU |
| MIMX8MM5DVTLZAA | i.MX 8M Mini QuadLite | 4x A53, M4, GPU      |
| MIMX8MM4DVTLZAA | i.MX 8M Mini Dual     | 2x A53, M4, GPU, VPU |
| MIMX8MM3DVTLZAA | i.MX 8M Mini DualLite | 2x A53, M4, GPU      |
| MIMX8MM2DVTLZAA | i.MX 8M Mini Solo     | 1x A53, M4, GPU, VPU |
| MIMX8MM1DVTLZAA | i.MX 8M Mini SoloLite | 1x A53, M4, GPU      |

Starting JLinkGDBServer

In order to find the command line for the command line GDB server, I used the JLinkGDBServerExe GUI tool once and selected MIMX8MM6_M4 manually from the list of supported devices. The GUI shows us what command line switches are required to launch GDB server without a GUI. Copying this, we can launch GDB server from the command line:

dzu@krikkit:~$ JLinkGDBServer -select USB -device MIMX8MM6_M4 -endian little -if JTAG -speed 4000 -noir -noLocalhostOnly -nologtofile
SEGGER J-Link GDB Server V7.62a Command Line Version

JLinkARM.dll V7.62a (DLL compiled Feb 23 2022 17:02:35)

Command line: -select USB -device MIMX8MM6_M4 -endian little -if JTAG -speed 4000 -noir -noLocalhostOnly -nologtofile
-----GDB Server start settings-----
GDBInit file:                  none
GDB Server Listening port:     2331
SWO raw output listening port: 2332
Terminal I/O port:             2333
Accept remote connection:      yes
Generate logfile:              off
Verify download:               off
Init regs on start:            off
Silent mode:                   off
Single run mode:               off
Target connection timeout:     0 ms
------J-Link related settings------
J-Link Host interface:         USB
J-Link script:                 none
J-Link settings file:          none
------Target related settings------
Target device:                 MIMX8MM6_M4
Target interface:              JTAG
Target interface speed:        4000kHz
Target endian:                 little

Connecting to J-Link...
J-Link is connected.
Firmware: J-Link EDU Mini V1 compiled Dec  7 2021 08:38:51
Hardware: V1.00
S/N: 801030219
Feature(s): FlashBP, GDB
Checking target voltage...
Target voltage: 1.78 V
Listening on TCP/IP port 2331
Connecting to target...

J-Link found 1 JTAG device, Total IRLen = 4
JTAG ID: 0x5BA00477 (Cortex-M4)
Connected to target
Waiting for GDB connection...

Connecting and loading the binary

Once the JLink connection is established, we can switch to a different terminal and start the cross debugger there. Once the connection to GDB server is established, we download the binary with the help of the ELF loader:

dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$ gdb-multiarch debug/hello_world.elf
GNU gdb (Debian 10.1-2+b1)
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from debug/hello_world.elf...
(gdb) target remote localhost:2331
Remote debugging using localhost:2331
0x1ffe0008 in __isr_vector ()
(gdb) load
Loading section .interrupts, size 0x240 lma 0x1ffe0000
Loading section .resource_table, size 0x10 lma 0x1ffe0240
Loading section .text, size 0x39dc lma 0x1ffe0280
Loading section .ARM, size 0x8 lma 0x1ffe3c5c
Loading section .init_array, size 0x4 lma 0x1ffe3c64
Loading section .fini_array, size 0x4 lma 0x1ffe3c68
Loading section .data, size 0x68 lma 0x1ffe3c6c
Start address 0x1ffe0378, load size 15524
Transfer rate: 226 KB/sec, 2217 bytes/write.
(gdb) n
187	    ldr     r0, =VTOR

Once the debugging process is working, we can enable gdb tui mode and enjoy a nicer view on the debugged process:


If you are just interested in the results, then instruct gdb to "c"continue and you should be able to see the result on the first USB port of this device in another shell session:

dzu@krikkit:~$ kermit -l /dev/ttyUSB0 -b 115200 -c
?SET SPEED has no effect without prior SET LINE
Connecting to /dev/ttyUSB0, speed 115200
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
hello world.


In theory this interaction with the target should also be possible using the OpenOCD Free Software, but as yet this does not work for me. I can load the binary, but debugging is not possible at all. I will follow up with a separate blog post once I got this working.

Appendix A - U-Boot Error

So let's find out, why we get the TFTP error in U-Boot. Looking into the code base, we find that the error message originates in net/tftp.c - however there are 3 sites with such an error message. Following the code flow, we find that line 847 is relevant for our use case: net/tftp.c#847. It is caused by tftp_init_load_addr returning zero. This obviously shows that U-Boot uses the CONFIG_LMB configuration. This "Logical memory Block" functionality in lib/lmb.c is used to keep track on reserved memory on a board, which also includes the real volatile DDR memory. We can use the bdinfo command to find out more details:

u-boot=> bdinfo
boot_params = 0x0000000000000000
DRAM bank   = 0x0000000000000000
-> start    = 0x0000000040000000
-> size     = 0x000000007e000000
flashstart  = 0x0000000000000000
flashsize   = 0x0000000000000000
flashoffset = 0x0000000000000000
baudrate    = 115200 bps
relocaddr   = 0x00000000bccf8000
reloc off   = 0x000000007caf8000
Build       = 64-bit
current eth = ethernet@30be0000
ethaddr     = 00:04:9f:05:d0:47
IP addr     =
fdt_blob    = 0x00000000baceb650
new_fdt     = 0x00000000baceb650
fdt_size    = 0x000000000000c760
Video       = lcdif@32e00000 inactive
    memory.cnt		   = 0x1
    memory.size		   = 0x0
    memory.reg[0x0].base   = 0x40000000
		   .size   = 0x7e000000

    reserved.cnt	   = 0x1
    reserved.size	   = 0x0
    reserved.reg[0x0].base = 0xbacea230
		     .size = 0x3315dd0
arch_number = 0x0000000000000000
TLB addr    = 0x00000000bdff0000
irq_sp      = 0x00000000baceb640
sp start    = 0x00000000baceb640
Early malloc usage: 8978 / 9000

So this U-Boot will not allow commands (except cp) to write to areas outside of memory, i.e. 0x4000.0000 - 0xbe00.0000.


Comments powered by Disqus