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.
Ingredients
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:
libnewlib-doc
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 http://deb.debian.org/debian bookworm/main amd64 binutils-arm-none-eabi amd64 2.37-7+15 [2730 kB]
Get:2 http://deb.debian.org/debian bookworm/main amd64 gcc-arm-none-eabi amd64 15:10.3-2021.07-4 [43.2 MB]
Get:3 http://deb.debian.org/debian bookworm/main amd64 gdb-multiarch amd64 10.1-2 [3720 kB]
Get:4 http://deb.debian.org/debian bookworm/main amd64 libnewlib-dev all 3.3.0-1.3 [262 kB]
Get:5 http://deb.debian.org/debian bookworm/main amd64 libnewlib-arm-none-eabi all 3.3.0-1.3 [43.6 MB]
Get:6 http://deb.debian.org/debian bookworm/main amd64 libstdc++-arm-none-eabi-dev all 15:10.3-2021.07-4+19 [1034 kB]
Get:7 http://deb.debian.org/debian 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) ...
dzu@krikkit:~$
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 https://github.com/NXPmicro/mcux-sdk --mr MCUX_2.10.0 mcuxsdk
=== Initializing in /opt/src/git/mcuxsdk
--- Cloning manifest repository from https://github.com/NXPmicro/mcux-sdk, 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.
From https://github.com/NXPmicro/mcux-sdk-examples
* 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 README.md file to describe known issue for TAG MCUX_2.10.0
HEAD is now at 09bfa4f69 Update README.md 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.
From https://github.com/NXPmicro/FreeRTOS-Kernel
* 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.
From https://github.com/NXPmicro/mcux-sdk-middleware-sdmmc
* 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.
From https://github.com/NXPmicro/mcux-sdk-middleware-multicore
* 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.
From https://github.com/NXPmicro/rpmsg-lite
* [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.
From https://github.com/EmbeddedRPC/erpc
* 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
dzu@krikkit:/opt/src/git/mcuxsdk$
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 */
BOARD_RdcInit();
BOARD_InitBootPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
BOARD_InitMemory();
PRINTF("hello world.\r\n");
while (1)
{
ch = GETCHAR();
PUTCHAR(ch);
}
}
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_ddr_ram.ld build_ddr_debug.sh* build_flash_debug.sh* clean.sh*
MIMX8MM6xxxxx_cm4_flash.ld build_ddr_release.bat build_flash_release.bat config.cmake*
MIMX8MM6xxxxx_cm4_ram.ld build_ddr_release.sh* build_flash_release.sh* flags.cmake*
build_all.bat build_debug.bat build_release.bat
build_all.sh* build_debug.sh* build_release.sh*
dzu@krikkit:/opt/src/git/mcuxsdk/examples/evkmimx8mm/demo_apps/hello_world/armgcc ((MCUX_2.10.0))$ ./build_debug.sh
-- TOOLCHAIN_DIR: /usr
-- BUILD_TYPE: debug
-- TOOLCHAIN_DIR: /usr
-- 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 two 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 from U-Boot
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: DRAM rate 3000MTS 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] MMC: FSL_SDHC: 1, FSL_SDHC: 2 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 BuildInfo: - 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 u-boot=>
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 192.168.44.122 (104 ms) u-boot=> setenv serverip 192.168.44.35 u-boot=> tftp 0x7e0000 hello_world.bin Using ethernet@30be0000 device TFTP from server 192.168.44.35; our IP address is 192.168.44.122 Filename 'hello_world.bin'. TFTP error: trying to overwrite reserved memory... u-boot=>
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 192.168.44.35; our IP address is 192.168.44.122 Filename 'hello_world.bin'. Load address: 0x80000000 Loading: # 2.7 MiB/s done Bytes transferred = 14092 (370c hex) u-boot=> cp.b 0x80000000 0x7e0000 ${filesize} u-boot=>
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... u-boot=>
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) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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
(gdb)
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.
OpenOCD
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 = 192.168.44.122 fdt_blob = 0x00000000baceb650 new_fdt = 0x00000000baceb650 fdt_size = 0x000000000000c760 Video = lcdif@32e00000 inactive lmb_dump_all: 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 u-boot=>
So this U-Boot will not allow commands (except cp
) to write to areas
outside of memory, i.e. 0x4000.0000
- 0xbe00.0000
.
Comments
Comments powered by Disqus