Skip navigation

MCUXpresso SDK

3 posts

In my previous articles I have used the command line on Linux to build and debug NXP MCUXpresso SDK applications. In this article I'm running code on NXP i.MX RT1064 in RAM or FLASH.

i.MXRT1064 board with LPC845-BRK as debug probe

i.MXRT1064 board with LPC845-BRK as debug probe

 

Outline

In this tutorial I'm going to run code in RAM and FLASH (XiP, eXecute in Place) on the i.MX RT1064. For getting started with the MCUXpresso SDK on Linux I recommend to have a read at my previous articles:

I'm using the command line on purpose in this article. The MCUXpresso IDE is available on Linux too and is usually a better and easier starting point for development.

I'm using Linux in a Oracle VM (Ubuntu) with the NXP MCUXpresso SDK for the i.MX RT1064 EVK board (see First Steps with the NXP i.MX RT1064-EVK Board).

 

NXP i.MX RT1064 Board

The board has the different memory areas available:

  1. internal ITC SRAM, base address: 0x0000'0000, size 0x2'0000 (128 KByte)
  2. internal DTC SRAM, base address: 0x2000'0000, size 0x2'0000 (128 KByte)
  3. internal OC SRAM, base address: 0x2020'0000, size 0xC'0000 (768 KByte)
  4. internal SPI FLASH: base address: 0x7000’0000, size: 0x40’0000 (4 MByte)
  5. external SDRAM,base address 0x8000'0000, size 0x200'0000 (32 MByte)

There is an extra 64 MByte Hyperflash available on the board, but this requires adding/removing resistors on the backside of the board.

i.MX RT1064-EVK Board

i.MX RT1064-EVK Board


Running from FLASH (XiP)

The i.MX RT does not have FLASH memory integrated with the MCU as it is the case for most microcontrollers. Instead it uses serial (SPI) FLASH memory which usually is an external memory chip. In the case of the i.MX RT1064 there are 4 MByte FLASH wired to the device internally. Technically it is the same as having it externally, except that the needed board space is smaller. Because the CPU does not know about the FLASH, the FLASH need a special header programmed at the start of the memory which is read by the CPU. For this the following defines need to be turned on:

XIP_EXTERNAL_FLASH=1 XIP_BOOT_HEADER_ENABLE=1

The SPI FLASH memory is not used for data as it usually is used on microcontrollers. Instead the processor can execute code in it, which is called XiP or 'eXecute in Place'.

The next thing is the linker file: The following file places that header, code, constants and vector table into the external FLASH starting at address 0x7000'0000. The Data-Tightly-Coupled (DTC) RAM is used for the heap and stack.

GROUP (
"libcr_nohost_nf.a"
"libcr_c.a"
"libcr_eabihelpers.a"
"libgcc.a"
)

MEMORY
{
/* Define each memory region */
PROGRAM_FLASH (rx) : ORIGIN = 0x70000000, LENGTH = 0x400000 /* 4M bytes (alias Flash) */
SRAM_DTC (rwx) : ORIGIN = 0x20000000, LENGTH = 0x20000 /* 128K bytes (alias RAM) */
SRAM_ITC (rwx) : ORIGIN = 0x0, LENGTH = 0x20000 /* 128K bytes (alias RAM2) */
SRAM_OC (rwx) : ORIGIN = 0x20200000, LENGTH = 0xc0000 /* 768K bytes (alias RAM3) */
BOARD_SDRAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x2000000 /* 32M bytes (alias RAM4) */
}

ENTRY(ResetISR)

SECTIONS
{
/* Image Vector Table and Boot Data for booting from external flash */
.boot_hdr : ALIGN(4)
{
FILL(0xff)
__boot_hdr_start__ = ABSOLUTE(.) ;
KEEP(*(.boot_hdr.conf))
. = 0x1000 ;
KEEP(*(.boot_hdr.ivt))
. = 0x1020 ;
KEEP(*(.boot_hdr.boot_data))
. = 0x1030 ;
KEEP(*(.boot_hdr.dcd_data))
__boot_hdr_end__ = ABSOLUTE(.) ;
. = 0x2000 ;
} >PROGRAM_FLASH

/* MAIN TEXT SECTION */
.text : ALIGN(4)
{
FILL(0xff)
__vectors_start__ = ABSOLUTE(.) ;
KEEP(*(.isr_vector))
/* Global Section Table */
. = ALIGN(4) ;
__section_table_start = .;
__data_section_table = .;
LONG(LOADADDR(.data));
LONG( ADDR(.data));
LONG( SIZEOF(.data));
LONG(LOADADDR(.data_RAM2));
LONG( ADDR(.data_RAM2));
LONG( SIZEOF(.data_RAM2));
LONG(LOADADDR(.data_RAM3));
LONG( ADDR(.data_RAM3));
LONG( SIZEOF(.data_RAM3));
LONG(LOADADDR(.data_RAM4));
LONG( ADDR(.data_RAM4));
LONG( SIZEOF(.data_RAM4));
__data_section_table_end = .;
__bss_section_table = .;
LONG( ADDR(.bss));
LONG( SIZEOF(.bss));
LONG( ADDR(.bss_RAM2));
LONG( SIZEOF(.bss_RAM2));
LONG( ADDR(.bss_RAM3));
LONG( SIZEOF(.bss_RAM3));
LONG( ADDR(.bss_RAM4));
LONG( SIZEOF(.bss_RAM4));
__bss_section_table_end = .;
__section_table_end = . ;
/* End of Global Section Table */

*(.after_vectors*)

} > PROGRAM_FLASH

.text : ALIGN(4)
{
*(.text*)
*(.rodata .rodata.* .constdata .constdata.*)
. = ALIGN(4);
} > PROGRAM_FLASH
/*
* for exception handling/unwind - some Newlib functions (in common
* with C++ and STDC++) use this.
*/
.ARM.extab : ALIGN(4)
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > PROGRAM_FLASH

__exidx_start = .;

.ARM.exidx : ALIGN(4)
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > PROGRAM_FLASH
__exidx_end = .;

_etext = .;

/* DATA section for SRAM_ITC */

.data_RAM2 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM2 = .) ;
*(.ramfunc.$RAM2)
*(.ramfunc.$SRAM_ITC)
*(.data.$RAM2*)
*(.data.$SRAM_ITC*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM2 = .) ;
} > SRAM_ITC AT>PROGRAM_FLASH
/* DATA section for SRAM_OC */

.data_RAM3 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM3 = .) ;
*(.ramfunc.$RAM3)
*(.ramfunc.$SRAM_OC)
*(.data.$RAM3*)
*(.data.$SRAM_OC*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM3 = .) ;
} > SRAM_OC AT>PROGRAM_FLASH
/* DATA section for BOARD_SDRAM */

.data_RAM4 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM4 = .) ;
*(.ramfunc.$RAM4)
*(.ramfunc.$BOARD_SDRAM)
*(.data.$RAM4*)
*(.data.$BOARD_SDRAM*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM4 = .) ;
} > BOARD_SDRAM AT>PROGRAM_FLASH
/* MAIN DATA SECTION */
.uninit_RESERVED : ALIGN(4)
{
KEEP(*(.bss.$RESERVED*))
. = ALIGN(4) ;
_end_uninit_RESERVED = .;
} > SRAM_DTC

/* Main DATA section (SRAM_DTC) */
.data : ALIGN(4)
{
FILL(0xff)
_data = . ;
*(vtable)
*(.ramfunc*)
*(NonCacheable.init)
*(.data*)
. = ALIGN(4) ;
_edata = . ;
} > SRAM_DTC AT>PROGRAM_FLASH

/* BSS section for SRAM_ITC */
.bss_RAM2 : ALIGN(4)
{
PROVIDE(__start_bss_RAM2 = .) ;
*(.bss.$RAM2*)
*(.bss.$SRAM_ITC*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM2 = .) ;
} > SRAM_ITC

/* BSS section for SRAM_OC */
.bss_RAM3 : ALIGN(4)
{
PROVIDE(__start_bss_RAM3 = .) ;
*(.bss.$RAM3*)
*(.bss.$SRAM_OC*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM3 = .) ;
} > SRAM_OC

/* BSS section for BOARD_SDRAM */
.bss_RAM4 : ALIGN(4)
{
PROVIDE(__start_bss_RAM4 = .) ;
*(.bss.$RAM4*)
*(.bss.$BOARD_SDRAM*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM4 = .) ;
} > BOARD_SDRAM

/* MAIN BSS SECTION */
.bss : ALIGN(4)
{
_bss = .;
*(NonCacheable)
*(.bss*)
*(COMMON)
. = ALIGN(4) ;
_ebss = .;
PROVIDE(end = .);
} > SRAM_DTC

/* NOINIT section for SRAM_ITC */
.noinit_RAM2 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM2*)
*(.noinit.$SRAM_ITC*)
. = ALIGN(4) ;
} > SRAM_ITC

/* NOINIT section for SRAM_OC */
.noinit_RAM3 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM3*)
*(.noinit.$SRAM_OC*)
. = ALIGN(4) ;
} > SRAM_OC

/* NOINIT section for BOARD_SDRAM */
.noinit_RAM4 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM4*)
*(.noinit.$BOARD_SDRAM*)
. = ALIGN(4) ;
} > BOARD_SDRAM

/* DEFAULT NOINIT SECTION */
.noinit (NOLOAD): ALIGN(4)
{
_noinit = .;
*(.noinit*)
. = ALIGN(4) ;
_end_noinit = .;
} > SRAM_DTC

/* Reserve and place Heap within memory map */
_HeapSize = 0x1000;
.heap : ALIGN(4)
{
_pvHeapStart = .;
. += _HeapSize;
. = ALIGN(4);
_pvHeapLimit = .;
} > SRAM_DTC

_StackSize = 0x1000;
/* Reserve space in memory for Stack */
.heap2stackfill :
{
. += _StackSize;
} > SRAM_DTC
/* Locate actual Stack in memory map */
.stack ORIGIN(SRAM_DTC) + LENGTH(SRAM_DTC) - _StackSize - 0: ALIGN(4)
{
_vStackBase = .;
. = ALIGN(4);
_vStackTop = . + _StackSize;
} > SRAM_DTC

/* Provide basic symbols giving location and size of main text
* block, including initial values of RW data sections. Note that
* these will need extending to give a complete picture with
* complex images (e.g multiple Flash banks).
*/
_image_start = LOADADDR(.text);
_image_end = LOADADDR(.data) + SIZEOF(.data);
_image_size = _image_end - _image_start;
}

Running from OC (On-Chip) RAM

It is possible to avoid external FLASH with linking things to the different RAM sections of the device. The following linker file runs everything from RAM:

GROUP (
"libcr_nohost_nf.a"
"libcr_c.a"
"libcr_eabihelpers.a"
"libgcc.a"
)

MEMORY
{
/* Define each memory region */
PROGRAM_FLASH (rx) : ORIGIN = 0x70000000, LENGTH = 0x400000 /* 4M bytes (alias Flash) */
SRAM_OC (rwx) : ORIGIN = 0x20200000, LENGTH = 0xc0000 /* 768K bytes (alias RAM) */
SRAM_ITC (rwx) : ORIGIN = 0x0, LENGTH = 0x20000 /* 128K bytes (alias RAM2) */
SRAM_DTC (rwx) : ORIGIN = 0x20000000, LENGTH = 0x20000 /* 128K bytes (alias RAM3) */
BOARD_SDRAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x2000000 /* 32M bytes (alias RAM4) */
}

/* Define a symbol for the top of each memory region */
__base_PROGRAM_FLASH = 0x70000000 ; /* PROGRAM_FLASH */
__base_Flash = 0x70000000 ; /* Flash */
__top_PROGRAM_FLASH = 0x70000000 + 0x400000 ; /* 4M bytes */
__top_Flash = 0x70000000 + 0x400000 ; /* 4M bytes */
__base_SRAM_OC = 0x20200000 ; /* SRAM_OC */
__base_RAM = 0x20200000 ; /* RAM */
__top_SRAM_OC = 0x20200000 + 0xc0000 ; /* 768K bytes */
__top_RAM = 0x20200000 + 0xc0000 ; /* 768K bytes */
__base_SRAM_ITC = 0x0 ; /* SRAM_ITC */
__base_RAM2 = 0x0 ; /* RAM2 */
__top_SRAM_ITC = 0x0 + 0x20000 ; /* 128K bytes */
__top_RAM2 = 0x0 + 0x20000 ; /* 128K bytes */
__base_SRAM_DTC = 0x20000000 ; /* SRAM_DTC */
__base_RAM3 = 0x20000000 ; /* RAM3 */
__top_SRAM_DTC = 0x20000000 + 0x20000 ; /* 128K bytes */
__top_RAM3 = 0x20000000 + 0x20000 ; /* 128K bytes */
__base_BOARD_SDRAM = 0x80000000 ; /* BOARD_SDRAM */
__base_RAM4 = 0x80000000 ; /* RAM4 */
__top_BOARD_SDRAM = 0x80000000 + 0x2000000 ; /* 32M bytes */
__top_RAM4 = 0x80000000 + 0x2000000 ; /* 32M bytes */

ENTRY(ResetISR)

SECTIONS
{
/* MAIN TEXT SECTION */
.text : ALIGN(4)
{
FILL(0xff)
__vectors_start__ = ABSOLUTE(.) ;
KEEP(*(.isr_vector))
/* Global Section Table */
. = ALIGN(4) ;
__section_table_start = .;
__data_section_table = .;
LONG(LOADADDR(.data));
LONG( ADDR(.data));
LONG( SIZEOF(.data));
LONG(LOADADDR(.data_RAM2));
LONG( ADDR(.data_RAM2));
LONG( SIZEOF(.data_RAM2));
LONG(LOADADDR(.data_RAM3));
LONG( ADDR(.data_RAM3));
LONG( SIZEOF(.data_RAM3));
LONG(LOADADDR(.data_RAM4));
LONG( ADDR(.data_RAM4));
LONG( SIZEOF(.data_RAM4));
__data_section_table_end = .;
__bss_section_table = .;
LONG( ADDR(.bss));
LONG( SIZEOF(.bss));
LONG( ADDR(.bss_RAM2));
LONG( SIZEOF(.bss_RAM2));
LONG( ADDR(.bss_RAM3));
LONG( SIZEOF(.bss_RAM3));
LONG( ADDR(.bss_RAM4));
LONG( SIZEOF(.bss_RAM4));
__bss_section_table_end = .;
__section_table_end = . ;
/* End of Global Section Table */

*(.after_vectors*)

} > SRAM_OC

.text : ALIGN(4)
{
*(.text*)
*(.rodata .rodata.* .constdata .constdata.*)
. = ALIGN(4);
} > SRAM_OC
/*
* for exception handling/unwind - some Newlib functions (in common
* with C++ and STDC++) use this.
*/
.ARM.extab : ALIGN(4)
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > SRAM_OC

__exidx_start = .;

.ARM.exidx : ALIGN(4)
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > SRAM_OC
__exidx_end = .;

_etext = .;

/* DATA section for SRAM_ITC */

.data_RAM2 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM2 = .) ;
*(.ramfunc.$RAM2)
*(.ramfunc.$SRAM_ITC)
*(.data.$RAM2*)
*(.data.$SRAM_ITC*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM2 = .) ;
} > SRAM_ITC AT>SRAM_OC
/* DATA section for SRAM_DTC */

.data_RAM3 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM3 = .) ;
*(.ramfunc.$RAM3)
*(.ramfunc.$SRAM_DTC)
*(.data.$RAM3*)
*(.data.$SRAM_DTC*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM3 = .) ;
} > SRAM_DTC AT>SRAM_OC
/* DATA section for BOARD_SDRAM */

.data_RAM4 : ALIGN(4)
{
FILL(0xff)
PROVIDE(__start_data_RAM4 = .) ;
*(.ramfunc.$RAM4)
*(.ramfunc.$BOARD_SDRAM)
*(.data.$RAM4*)
*(.data.$BOARD_SDRAM*)
. = ALIGN(4) ;
PROVIDE(__end_data_RAM4 = .) ;
} > BOARD_SDRAM AT>SRAM_OC
/* MAIN DATA SECTION */
.uninit_RESERVED : ALIGN(4)
{
KEEP(*(.bss.$RESERVED*))
. = ALIGN(4) ;
_end_uninit_RESERVED = .;
} > SRAM_OC

/* Main DATA section (SRAM_OC) */
.data : ALIGN(4)
{
FILL(0xff)
_data = . ;
*(vtable)
*(.ramfunc*)
*(.data*)
. = ALIGN(4) ;
_edata = . ;
} > SRAM_OC AT>SRAM_OC

/* BSS section for SRAM_ITC */
.bss_RAM2 : ALIGN(4)
{
PROVIDE(__start_bss_RAM2 = .) ;
*(.bss.$RAM2*)
*(.bss.$SRAM_ITC*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM2 = .) ;
} > SRAM_ITC

/* BSS section for SRAM_DTC */
.bss_RAM3 : ALIGN(4)
{
PROVIDE(__start_bss_RAM3 = .) ;
*(.bss.$RAM3*)
*(.bss.$SRAM_DTC*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM3 = .) ;
} > SRAM_DTC

/* BSS section for BOARD_SDRAM */
.bss_RAM4 : ALIGN(4)
{
PROVIDE(__start_bss_RAM4 = .) ;
*(.bss.$RAM4*)
*(.bss.$BOARD_SDRAM*)
. = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
PROVIDE(__end_bss_RAM4 = .) ;
} > BOARD_SDRAM

/* MAIN BSS SECTION */
.bss : ALIGN(4)
{
_bss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4) ;
_ebss = .;
PROVIDE(end = .);
} > SRAM_OC

/* NOINIT section for SRAM_ITC */
.noinit_RAM2 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM2*)
*(.noinit.$SRAM_ITC*)
. = ALIGN(4) ;
} > SRAM_ITC

/* NOINIT section for SRAM_DTC */
.noinit_RAM3 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM3*)
*(.noinit.$SRAM_DTC*)
. = ALIGN(4) ;
} > SRAM_DTC

/* NOINIT section for BOARD_SDRAM */
.noinit_RAM4 (NOLOAD) : ALIGN(4)
{
*(.noinit.$RAM4*)
*(.noinit.$BOARD_SDRAM*)
. = ALIGN(4) ;
} > BOARD_SDRAM

/* DEFAULT NOINIT SECTION */
.noinit (NOLOAD): ALIGN(4)
{
_noinit = .;
*(.noinit*)
. = ALIGN(4) ;
_end_noinit = .;
} > SRAM_OC

/* Reserve and place Heap within memory map */
_HeapSize = 0x1000;
.heap : ALIGN(4)
{
_pvHeapStart = .;
. += _HeapSize;
. = ALIGN(4);
_pvHeapLimit = .;
} > SRAM_OC

_StackSize = 0x1000;
/* Reserve space in memory for Stack */
.heap2stackfill :
{
. += _StackSize;
} > SRAM_OC
/* Locate actual Stack in memory map */
.stack ORIGIN(SRAM_OC) + LENGTH(SRAM_OC) - _StackSize - 0: ALIGN(4)
{
_vStackBase = .;
. = ALIGN(4);
_vStackTop = . + _StackSize;
} > SRAM_OC

/* Provide basic symbols giving location and size of main text
* block, including initial values of RW data sections. Note that
* these will need extending to give a complete picture with
* complex images (e.g multiple Flash banks).
*/
_image_start = LOADADDR(.text);
_image_end = LOADADDR(.data) + SIZEOF(.data);
_image_size = _image_end - _image_start;
}

 

Mixing RAM and FLASH

Running code from RAM has a performance benefit. So it makes sense to run 'slower' parts in normal XiP FLASH, but run code in RAM where higher performance is needed.

To place a function into RAM, I add an attribute with the desired section name:

static void __attribute__((section (".ramfunc"))) blinkRAM(void) {
if (g_pinSet) {
GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);
g_pinSet = false;
} else {
GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);
g_pinSet = true;
}
}

With this, the function gets copied and placed in RAM. One thing to note is that depending on the call distance a veneer function might be used to reach the RAM address where the function is placed. More details on this topic in Execute-Only Code with GNU and gcc which covers that topic from a different angle.

Summary

It is possible to place code and data either in FLASH or in RAM. All what is needed is the correct linker file for it. The placement is controlled by the linker file, and with using __attribute__ parts of the application can be in FLASH or RAM. If you are not familiar with the GNU linker file syntax, I recommend you start with the MCUXpresso IDE because it provides projects with working linker files.

 

Happy XiPing :-)


Links

 

- - -

Originally published on May 19, 2019 by Erich Styger

In “Tutorial: MCUXpresso SDK with Linux, Part 1: Installation and Build with Maked” I used cmake and make to build the SDK application. In this part I’m going to use the command line gdb to debug the application on the board.

Cross-Debugging with GDB

Cross-Debugging with GDB

 

List of this multi-part tutorial:

Outline

In this tutorial I’m going through the steps to install and use the GNU debugger (gdb) from the console to debug the application on the board. I’m running the Ubuntu Linux in a VM on Windows. I’m using the NXP FRDM-K64F board, but the steps are applicable to any other board.

MQTT running on NXP FRDM-K64F

NXP FRDM-K64F

While this tutorial is using gdb and the command line, the Eclipse based MCUXpresso IDE can be used instead too: this way might be easier for most developers as with using Eclipse things are handled automatically.

For an earlier overview about command line debugging with GDB, see “Command Line Programming and Debugging with GDB“.

GDB Debugging Chain

GDB Debugging Chain

Debug Connections

The board I’m using has a on-board debug circuit (OpenSDA) which can used either as a CMSIS-DAP (with a LPC-Link2), SEGGER J-Link or P&E Multilink. The board has a standard 10pin SWD debug header, so an external debug probe can used too:

Debugging FreeRTOS on NXP FRDM-K64F with P&E Multilink Universal

Debugging FreeRTOS on NXP FRDM-K64F with P&E Multilink Universal

LPC-Link2 debugging FRDM-K64F

LPC-Link2 debugging FRDM-K64F

J-Link Hooked Up to recover the K64F

J-Link Hooked Up to recover the K64F

GDB Client

GDB is using a client-server connection: the GDB client is rather generic, while the server part is making the connection to the target and/or implements the connection to the board.

In the case of using Eclipse, the GDB client is integrated with Eclipse:

GDB with GDB Server

GDB with GDB Server

But the gdb client in fact is a command line tool like the compiler, and it can be started on the command line too:

$ ~/opt/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gdb

See “Tutorial: MCUXpresso SDK with Linux, Part 1: Installation and Build with Make” about the GNU and gdb tool installation.

This launches the GDB client which then waits for commands:

Launched GDB

Launched GDB

I prefer to run the gdb client in one console window. So for the next parts (server for P&E and SEGGER) I use a different console.

SEGGER J-Link GDB Server

To work with the board using a SEGGER probe, I have to install the J-Link software first. The download is available from https://www.segger.com/downloads/jlink/#J- LinkSoftwareAndDocumentationPack

For my environment I have downloaded the JLink_Linux_V644g_x86_64.deb file. For a RedHat system the .rpm would be the choice to use. To install it:

$ sudo apt install ./JLink_Linux_V644g_x86_64.deb

The files get installed into \opt\bin. I can verify that it works properly with calling the GDB server:

$ /usr/bin/JLinkGDBServer

Which produces something like this:

SEGGER J-Link GDB Server V6.44g Command Line Version JLinkARM.dll V6.44g (DLL compiled Apr 18 2019 17:15:02) -----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: Unspecified Target interface: JTAG Target interface speed: 4000kHz Target endian: little Connecting to J-Link... Connecting to J-Link failed. Connected correctly? GDBServer will be closed... Shutting down... Could not connect to J-Link.

For how the GDB client and server connection works, see “GDB Client and Server: Unlocking GDB“.

The above output is produced with no J-Link debug probe attached. If using a USB based probe with the VM, make sure the USB port is available to the VM:

Using USB in VirtualBox

With this, the probe is detected:

SEGGER J-Link GDB Server V6.44g Command Line Version JLinkARM.dll V6.44g (DLL compiled Apr 18 2019 17:15:02) -----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: Unspecified Target interface: JTAG Target interface speed: 4000kHz Target endian: little Connecting to J-Link... J-Link is connected. Failed to set device (Unspecified). Unknown device selected?ERROR : Failed to set device. Firmware: J-Link OpenSDA 2 compiled Jun 28 2018 09:44:47 Hardware: V1.00 S/N: 621000000 Checking target voltage... Target voltage: 3.30 V Listening on TCP/IP port 2331 Connecting to target...ERROR: Debugger tries to select target interface JTAG. This interface is not supported by the connected emulator. Selection will be ignored by the DLL. ERROR: No CPU core or target device has been selected. Please make sure at least the core J-Link shall connect to, is selected. ERROR: Could not connect to target. Target connection failed. GDBServer will be closed...Restoring target state and closing J-Link connection... Shutting down... Could not connect to target.

The end of the message indicates that we are trying to connect to a JTAG interface which is not supported. Indeed, the OpenSDA circuit on the FRDM-K64F board only supports SWD. To use SWD, the -if swd option has to be added:

$ /usr/bin/JLinkGDBServer -if swd

In addition to that, the device has to be selected using the -device option:

$ /usr/bin/JLinkGDBServer -if swd -device MK64FN1M0xxx12

The list of devices are available from https://www.segger.com/downloads/supported-devices.php

With this, the SEGGER gdb server waits for the client connection:

SEGGER J-Link GDB Server V6.44g Command Line Version JLinkARM.dll V6.44g (DLL compiled Apr 18 2019 17:15:02) Command line: -if swd -device MK64FN1M0xxx12 -----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: MK64FN1M0xxx12 Target interface: SWD Target interface speed: 4000kHz Target endian: little Connecting to J-Link... J-Link is connected. Firmware: J-Link OpenSDA 2 compiled Jun 28 2018 09:44:47 Hardware: V1.00 S/N: 621000000 Checking target voltage... Target voltage: 3.30 V Listening on TCP/IP port 2331 Connecting to target...Connected to target Waiting for GDB connection...

The SEGGER server is listening on port 2331, so I connect to that port from the gdb client:

(gdb) target remote localhost:2331
GDB Client and Server

GDB Client and Server

From the client, I can reset the target:

(gdb) monitor reset
monitor reset

monitor reset

To load the binary, I use

(gdb) load debug/led_blinky.elf
load binary

load binary

followed by loading the symbols (debug information):

(gdb) file debug/led_blinky.elf
loaded symbols

loaded symbols

To set a breakpoint on main:

(gdb) b main

and to continue execution:

(gdb) c

This will hit the breakpoint on main:

Hit breakpoint on main

Hit breakpoint on main

GDB has many commands, for example check the following cheat sheets:

To quit the gdb debug session:

(gdb) q

PEMicro (P&E)

Everyting said above applies to the P&E (PEMICRO) debug connection too. The P&E debug probes (e.g. Multilink) use their own GDB. That GDB Serve and drivers are integrated in Eclipse (e.g. NXP MCUXpresso IDE)

I recommend to download and install the MCUXpresso IDE for Linux as it comes with the latest P&E plugins and drivers.  That way the USB drivers and GDB server don’t need to be installed manually as described below.

Download the P&E Linux drivers from http://www.pemicro.com/opensda/ and unzip the file:

$ gunzip pemicro-other-20181128.zip.tar.gz

untar the archive:

$ tar -xvf pemicro-other-20181128.zip.tar
$ cd pemicro-other-20181128/drivers/libusb_64_32/

untar the drivers:

$ tar -xvf linux_drivers_64bit_58_b181128.tar.gz

Install the drivers

$ cd libusb_64_32 $ sudo ./setup.sh

Now with the USB drivers installed, we need the P&E GDB Server which is inside the P&E Eclipse plugin. The Eclipse plugin is available as direct download from

https://www.pemicro.com/products/product_viewDetails.cfm?product_id=15320151&productTab=1

Again: I recommend to download and install the MCUXpresso IDE for Linux as it comes with the latest P&E plugins.

unzip the jar file (replace the file name with the one you have downloaded):

$ unzip com.pemicro.debug.gdbjtag.pne_3.0.9.201707131553.jar -d pnegdbserver

make the gdb server executable:

$ cd pnegdbserver $ chmod +x pegdbserver_console

Make sure the P&E Device and driver is available for the Linux VM:

PEMicro USB

PEMicro USB

The list of supported devices can be displayed with

$ ./pegdbserver_console -devicelist

Then launch the server. Specify -startserver and the -device used. For FRDM-K64F I use

$ ./pegdbserver_console -startserver -device=NXP_K6x_K64FN1M0M12

With this I have the server running:

P&E GDB Server for Arm(R) devices, Version 6.45.00.00 Copyright 2014, P&E Microcomputer Systems Inc, All rights reserved Loading library /home/erich/MCUXpresso/pemicro/pnegdbserver/lin/gdi/unit_ngs_arm_internal.so ... Done. Command line arguments: -startserver -device=NXP_K6x_K64FN1M0M12 Device selected is NXP_K6x_K64FN1M0M12 HW Auto-Selected : Interface=USBMULTILINK Port=PEM6B012B ; USB1 : Multilink Universal FX Rev C (PEM6B012B) Connecting to target. P&E Interface detected - Flash Version 10.15 Device is NXP_K6x_K64FN1M0M12. Mode is In-Circuit Debug. (C)opyright 2012, P&E Microcomputer Systems, Inc. (www.pemicro.com) API version is 101 Creating kernel driver for freertos Server 1 running on 127.0.0.1:7224 Server 2 running on 127.0.0.1:7226 Server 3 running on 127.0.0.1:7228 Server 4 running on 127.0.0.1:7230 Server 5 running on 127.0.0.1:7232 Server 6 running on 127.0.0.1:7234 All Servers Running

Start the gdb client in a different console:

$ ~/opt/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gdb

The P&E GDB server is listening on port 7224, so I use the following in the gdb client:

(gdb) target remote localhost:7224

From now on, I can use gdb to talk to the target. To reset the target:

(gdb) monitor reset

to load the binary and the symbols, setting a breakpoint on main and run:

(gdb) load debug/led_blinky.elf (gdb) file debug/led_blinky.elf (gdb) b main (gdb) c
Debugging with P&E gdbserver

Debugging with P&E gdbserver

To terminate the debug session:

(gdb) q

Automation

Instead using the gdb client in interactive mode, I can script it for example to program the application. For example create following script file:

set pagination off target remote localhost:2331 monitor reset load debug/led_blinky.elf quit gdb script file

Then launch the script file with

$ ~/opt/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gdb -x gdb_flash.script

Summary

It is possible to use gdb in command line mode on Linux with the NXP MCUXpresso SDK, so this is applicable for a very small setup. It works well for the SEGGER and PEMICRO debug connections, as their driver/installer cares about the USB connection, so using that is fairly easy. Using the gdb command line is very powerful, but is for sure not as intuitive as using an Eclipse IDE as the MCUXpresso IDE, but certainly doable. Using gdb in command line mode on the other side is a good option to do automated testing at least.

 

Happy debugging  :-)

 

Links

- - -

Originally published on April 20, 2019 by Erich Styger

I admit: my work laptop machine is running a Windows 10 OS by default. But this does not prevent me running Linux in a Virtual Machine (VM). Each host platform has its benefits, and I don’t feel biased to one or the other, but I have started using Ubuntu more and more, simply because I have worked more on Embedded Linux projects. While I have used mostly Windows with Eclipse for NXP LPC, Kinetis and i.MX platforms in the past, I started using Ubuntu too from last year with the NXP MCUXpresso SDK. I did not find much documentation about this on the web, so I thought it might be a good idea to write a tutorial about it. So here we go…

 

Outline

This tutorial shows how to install and develop on Linux with the NXP MCUXpresso SDK. It goes through the steps all the needed pieces and to build one of the SDK example projects.

If you want to develop the easy way with MCUXpesso SDK using a GUI: The MCUXpresso IDE (Eclipse based) runs on Ubuntu (and Windows and Mac) too. This article uses make files and no GUI for a ‘headless’ or ‘GUI-less’ environment.

This is what I’m using in this tutorial:

Blinky on FRDM-K64F Board

Blinky on FRDM-K64F Board

Linux Host

I recommend using Ubuntu Linux. I’m using it in a VM (Virtual Machine) on Windows 10 with the Oracle VirtualBox. There are plenty of tutorials available on the internet about installing Ubuntu on VirtualBox if you are not familiar with it.

NXP MCUXpresso SDK

Select and configure the SDK on https://mcuxpresso.nxp.com. The important part is to select Linux as Host OS and GCC ARM Embedded as toolchain:

MCUXpresso SDK

MCUXpresso SDK

Then click on ‘Download SDK’ and download the archive to the user home directory and extract it there in a subdirectory:

$ cd ~ $ mkdir MCUXpresso $ cd MCUXpresso $ mkdir SDK_2.5.0_FRDM-K64F $ cd SDK_2.5.0_FRDM-K64F

Place the SDK .tar.gz file into that created folder. Unzip the downloaded SDK file:

$ gunzip SDK_2.5.0_FRDM-K64F.tar.gz

Then untar the archive:

tar -xvf SDK_2.5.0_FRDM-K64F.tar

Extraced SDK Files

Installing CMake

By default the SDK is using CMake and not directly normal make files. As CMake is not installed by default in my Ubuntu distribution, I have to add it:

$ sudo apt install cmake

CMake is a cross-platform make file *generator*. As with the make file syntax, it takes some time to learn using. To learn more about CMake: there are plenty of resources available online, a good starting point is https://cmake.org/.

I’m using cmake 3.10.2. The SDK documentation recommends using an older version 3.0.x. Using the 3.10 version will show warnings like ‘The CMAKE_FORCE_C_COMPILER macro is deprecated.  Instead just set CMAKE_C_COMPILER and allow CMake to identify the compiler.’ because the SDK cmake files are not compatible with newer cmake versions. The warnings are annoying, but do not affect the functionality so they can be ignored.

Installing GNU ARM Toolchain for Embedded

To develop with the SDK, I have to install a cross toolchain, available from https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads

I don’t want to have the toolchain installed for everyone on my Linux, so I install it locally into a ‘opt’ folder inside my user home directory:

The reason for this is to separate different GNU ARM Embedded Toolchains for different projects. If I would add it to the global path/system, separation would be very difficult.

$ mkdir ~/opt $ cd ~/opt

The current release is gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz which I download with

$ wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2

unzip the archive:

$ tar xjf gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2

Finally change the permissions to read-only:

$ chmod -R -w "${HOME}"/opt/gcc-arm-none-eabi-7-2017-q4-major

Verify that the compiler is working:

$ ~/opt/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gcc --version

which returns the following:

arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 8-2018-q4-major) 8.2.1 20181213 (release) [gcc-8-branch revision 267074] Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

CMake Environment

CMake needs a variable set to find the compiler. This can be done with

$ export ARMGCC_DIR=~/opt/gcc-arm-none-eabi-8-2018-q4-major

To make it permanent for the user, it can be added to the ~/.bashrc file:

$ nano ~/.bashrc

And then add it to the file which gets executed every time I log in as this user.

Setting Environment

Setting Environment

Use CTRL+X to exit nano and to save the file.

Building Example SDK Projects

In the next step I’m going to build one of the example projects: the LED Blinky one:

$ cd ~/MCUXpresso/SDK_2.5.0_FRDM-K64F/boards/frdmk64f/demo_apps/led_blinky/armgcc
LED Blinky Example Projects

LED Blinky Example Projects

This folder contains several *.bat files (left-over from the Windows world?). Relevant are the Shell script *.sh script files:

  • clean.sh: used to clean the build files
  • build_release.sh: generate make file for a release build
  • build_debug.sh: generate make file for a debug build
  • build_all.sh: build a make file for release and debug build

The shell script to generate the make file are using cmake (see above) with all the necessary information for cmake in the CMakeLists.txt file.

To build a make file for both debug version:

$ ./build_debug.sh
Running build_debug.sh

Running build_debug.sh

Beside of generating the make file, it generates the binary too in the ‘debug’ output folder:

Generated Files

Generated Files

With the ‘file’ command I can verify that indeed the correct binary has been produced:

$ file debug/led_blinky.elf
ELF 32-bit Executable

ELF 32-bit Executable

Because CMake has generated all the needed make files, I can now build the project with

$ make

To clean it I can use

$ make clean

that’s it 

Summary

With this tutorial I showed the installation of the necessary tools (SDK, cmake, GNU ARM for Embedded toolchain) and how to build example projects.

In a next part I’ll touch on the debugging part with gdb. Until then, I hope you find this one useful.

Happy Linuxing :-)

 

Links

- - -

Originally published on April 14, 2019 by Erich Styger