Minimal semihosted ARM Cortex-M "Hello World"
The following is an extremely minimal, semihosted "Hello World" D program for ARM Cortex-M processors.
Contents
[hide]Goals
- Verify the ARM Cortex-M toolchain (compiler-->linker-->debugger)
- Demonstrate that a full port of the D runtime and D standard library are not mandatory
- Demonstrate that linking to C code is not necessary
- Demonstrate that all required assembly code can be done within D
- Provide a starting point, with a low barrier to entry, for developers to begin testing the toolchain, porting the D runtime and libraries to the ARM Cortex-M platform, and programming their ARM Cortex-M software in D
Tools
- (host computer) Arch Linux 64-bit
- (compiler) LDC with ARM backend, or GDC cross-compiler for arm-none-eabi 4.8.2
- (linker & debugger) GNU Tools for GNU Tools for ARM Embedded Processors 4.7-2013-q3
- (GDB server) OpenOCD 0.7.0
- (JTAG emulator) JTAG-lock-pick Tiny 2
The Code
Program Source Code (start.d)
module start;
version(LDC)
{
import ldc.llvmasm;
}
extern(C) __gshared void * _Dmodule_ref;
//Must be stored as second 32-bit word in .text section
alias void function() ISR;
extern(C) immutable ISR ResetHandler = &OnReset;
void SendCommand(int command, void* message)
{
version(LDC)
{
__asm
(
"mov r0, $0;
mov r1, $1;
bkpt #0xAB",
"r,r,~{r0},~{r1}",
command, message
);
}
else version(GNU)
{
asm
{
"mov r0, %[cmd];
mov r1, %[msg];
bkpt #0xAB"
:
: [cmd] "r" command, [msg] "r" message
: "r0", "r1";
};
}
}
void OnReset()
{
while(true)
{
// Create semihosting message message
uint[3] message =
[
2, //stderr
cast(uint)"hello\r\n".ptr, //ptr to string
7 //size of string
];
//Send semihosting command
SendCommand(0x05, &message);
}
}
Minimal Runtime Implementation (object.d) (GDC only)
GDC requires the following minimal object.d file in the same folder as start.d. It is imported automatically.
module object;
alias immutable(char)[] string;
Compiling
NOTE: You will have to change the -mcpu option for your processor accordingly (e.g. cortex-m0, cortex-m3, etc...)
LDC
ldc2 -march=thumb -mcpu=cortex-m4 -c start.d
For LDC, object.d must NOT exist, or it will try to import it automatically.
GDC
arm-none-eabi-gdc -mthumb -mcpu=cortex-m4 -fno-emit-moduleinfo -c -fdata-sections start.d
For GDC, object.d MUST exist, and it is imported automatically.
Linking
Linker Script (link.ld) (LDC)
NOTE: This linker script was written specifically for an STM32F4 MCU. The MEMORY section and _stackStart will have to be tailored to your hardware.
MEMORY
{
CCRAM (rxw) : ORIGIN = 0x10000000, LENGTH = 64k
SRAM (rxw) : ORIGIN = 0x20000000, LENGTH = 128k
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024k
}
_stackStart = ORIGIN(CCRAM) + LENGTH(CCRAM);
SECTIONS
{
.text :
{
LONG(_stackStart); /* Initial stack pointer */
KEEP(start.o(.data.rel.ro)) /* Interrupt vector table */
/* the code */
*(.text)
*(.text*)
/* for "hello\r\n" string constant */
. = ALIGN(4);
*(.rodata)
*(.rodata*)
}>FLASH
/* Need .data, .bss, .ctors and probably more as program becomes
More complex */
}
Linker Script (link.ld) (GDC)
NOTE: This linker script was written specifically for an STM32F4 MCU. The MEMORY section and _stackStart will have to be tailored to your hardware.
/*********************************************************************************************
Memory Spaces Definitions
*********************************************************************************************/
MEMORY
{
CCRAM (rxw) : ORIGIN = 0x10000000, LENGTH = 64k
SRAM (rxw) : ORIGIN = 0x20000000, LENGTH = 128k
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024k
}
_stackStart = ORIGIN(CCRAM) + LENGTH(CCRAM);
/*********************************************************************************************
Section Definitions
*********************************************************************************************/
SECTIONS
{
.text :
{
LONG(_stackStart); /* Initial stack pointer */
KEEP(start.o(.rodata.ResetHandler)) /* Internet vector table */
/* the code */
*(.text)
*(.text*)
/* for "hello\r\n" string constant */
. = ALIGN(4);
*(.rodata)
*(.rodata*)
}>FLASH
/* Need .data, .bss, .ctors and probably more as program becomes
More complex */
}
Link with:
arm-none-eabi-ld -T link.ld --gc-sections start.o -o start.elf
Execution
Start OpenOCD:
openocd -f interface/jtag-lock-pick_tiny_2.cfg -f target/stm32f4x.cfg
Start GDB:
arm-none-eabi-gdb start.elf
In GDB, attach to OpenOCD, reset the hardware, load the executable, and begin execution
target remote localhost:3333
monitor arm semihosting enable
monitor reset halt
load
monitor reset init
continue
Output (in OpenOCD window):
hello
hello
...
TODO
- Modify software to include data and bss sections and initialize them in the reset handler (Part 2?)
- Begin porting the D Runtime to the ARM Cortex-M platform