Embedded Freaks..

August 9, 2009

Cortex-M3 Blinky in Assembly

Filed under: Cortex-M — Tags: — kunilkuda @ 12:02 am

As tradition for new comers, I created my own ‘hello world’ using Cortex-M3’s assembly using Codesourcery’s GCC assembler. The code below has been tested on LPC1766 – Keil’s MCB1700 board. It will turn on/off the LEDs on the board.

Here’s the code:

/* Simple startup file for Cortex-M3 */

 /* Thumb-2 instructions are only supported in unified syntax mode */
 .syntax unified

/* Vector table definition */
 .section ".cs3.interrupt_vector"
 .long  __cs3_stack                 /* Top of Stack                 */
 .long  Reset_Handler               /* Reset Handler                */
 .long  NMI_Handler                 /* NMI Handler                  */
 .long  HardFault_Handler           /* Hard Fault Handler           */
 .long  MemManage_Handler           /* MPU Fault Handler            */
 .long  BusFault_Handler            /* Bus Fault Handler            */
 .long  UsageFault_Handler          /* Usage Fault Handler          */
 .long  0                           /* Reserved                     */
 .long  0                           /* Reserved                     */
 .long  0                           /* Reserved                     */
 .long  0                           /* Reserved                     */
 .long  SVC_Handler                 /* SVCall Handler               */
 .long  DebugMon_Handler            /* Debug Monitor Handler        */
 .long  0                           /* Reserved                     */
 .long  PendSV_Handler              /* PendSV Handler               */
 .long  SysTick_Handler             /* SysTick Handler              */

/* Vendor specific interrupts
 *  ---- Not implemented
 */

 /* Program section */
 .section ".text"

 /* Declare as thumb function. Otherwise it will not be linked
 * correctly
 */
 .thumb_func
 /* Export the symbol so linker can see this */
 .global Reset_Handler
Reset_Handler:
 /* Jump to main(), a thumb function */
 LDR     R0, =main
 BX      R0
 /* If main() ever exit, this should hold MCU from running wild */
 B       .

/* This is how the lazy guy doing it: by aliasing all the
 * interrupts into single address
 */
.thumb_func
NMI_Handler:
.thumb_func
HardFault_Handler:
.thumb_func
MemManage_Handler:
.thumb_func
BusFault_Handler:
.thumb_func
UsageFault_Handler:
.thumb_func
SVC_Handler:
.thumb_func
DebugMon_Handler:
.thumb_func
PendSV_Handler:
.thumb_func
SysTick_Handler:
 B    . /* while(1); */

The startup file contains the initial vector address. Save it into ‘startup_lpc17xx.S’ file, then compile it using:

# arm-none-eabi-as -mcpu=cortex-m3 -ostartup_lpc17xx.o startup_lpc17xx.S

Then, here comes the main file to blink the LEDs on MCB1700:

/*
 * Blinky program for MCB1768 - ARM Cortex-M3
 *
 * The LEDs are at these pins:
 * - P1.28, P1.29, P1.31
 * - P2.2, P2.3, P2.4, P2.5, P2.6
 *
 */
 /* Thumb-2 instructions are only supported in unified syntax mode */
 .syntax unified
 /* Put these codes into .text section */
 .section ".text"
 /* Declare main() as global..Otherwise the linker won't find it */
 .global main
 /* Declare as thumb function..Otherwise it may not linked correctly */
 .thumb_func
main:
 /* Set the pins direction as output */
 LDR     R0, =set_gpio_dir
 BLX     R0
loop:
 LDR     R0, =clear_leds
 BLX     R0
 LDR     R0, =delay
 BLX     R0
 LDR     R0, =set_leds
 BLX     R0
 LDR     R0, =delay
 BLX     R0
 B       loop

 .thumb_func
set_gpio_dir:
 /* P1.28, P1.29, and P1.31 direction mode is located at
 * FP1DIR (0x2009_C020).
 *
 * P1.28 -> Bit[28] -> Output dir : 1
 * P1.29 -> Bit[29] -> Output dir : 1
 * P1.31 -> Bit[31] -> Output dir : 1
 */
 MOVW    R3, #0xC020
 MOVT    R3, #0x2009
 /* b1011_0000_0000_0000_0000_0000_0000_0000 */
 MOVW    R2, #0x0000
 MOVT    R2, #0xB000
 STR     R2, [R3, #0]

 /* P2.2, P2.3, P2.4, P2.5, and P2.6 function mode is located at
 * FP2DIR (0x2009_C040).
 *
 * P2.2  -> Bit[2] -> Output dir : 1
 * P2.3  -> Bit[3] -> Output dir : 1
 * P2.4  -> Bit[4] -> Output dir : 1
 * P2.5  -> Bit[5] -> Output dir : 1
 * P2.6  -> Bit[6] -> Output dir : 1
 */
 MOVW    R3, #0xC040
 MOVT    R3, #0x2009
 /* b0000_0000_0000_0000_0000_0000_0111_1100 */
 MOVW    R2, #0x007C
 MOVT    R2, #0x0000
 STR     R2, [R3, #0]

 BX      LR

 .thumb_func
set_leds:
 /* P1.28, P1.29, and P1.31 set is located at
 * FP1SET (0x2009_C038).
 *
 * P1.28 -> Bit[28]
 * P1.29 -> Bit[29]
 * P1.31 -> Bit[31]
 */
 MOVW    R3, #0xC038
 MOVT    R3, #0x2009
 /* b1011_0000_0000_0000_0000_0000_0000_0000 */
 MOVW    R2, #0x0000
 MOVT    R2, #0xB000
 STR     R2, [R3, #0]

 /* P2.2, P2.3, P2.4, P2.5, and P2.6 set is located at
 * FP2SET (0x2009_C058).
 *
 * P2.2  -> Bit[2]
 * P2.3  -> Bit[3]
 * P2.4  -> Bit[4]
 * P2.5  -> Bit[5]
 * P2.6  -> Bit[6]
 */
 MOVW    R3, #0xC058
 MOVT    R3, #0x2009
 /* b0000_0000_0000_0000_0000_0000_0111_1100 */
 MOVW    R2, #0x007C
 MOVT    R2, #0x0000
 STR     R2, [R3, #0]

 BX      LR

 .thumb_func
clear_leds:
 /* P1.28, P1.29, and P1.31 clear is located at
 * FP1CLR (0x2009_C03C).
 *
 * P1.28 -> Bit[28]
 * P1.29 -> Bit[29]
 * P1.31 -> Bit[31]
 */
 MOVW    R3, #0xC03C
 MOVT    R3, #0x2009
 /* b1011_0000_0000_0000_0000_0000_0000_0000 */
 MOVW    R2, #0x0000
 MOVT    R2, #0xB000
 STR     R2, [R3, #0]

 /* P2.2, P2.3, P2.4, P2.5, and P2.6 clear is located at
 * FP2CLR (0x2009_C05C).
 *
 * P2.2  -> Bit[2]
 * P2.3  -> Bit[3]
 * P2.4  -> Bit[4]
 * P2.5  -> Bit[5]
 * P2.6  -> Bit[6]
 */
 MOVW    R3, #0xC05C
 MOVT    R3, #0x2009
 /* b0000_0000_0000_0000_0000_0000_0111_1100 */
 MOVW    R2, #0x007C
 MOVT    R2, #0x0000
 STR     R2, [R3, #0]

 BX      LR

 .thumb_func
delay:
 MOVW    R3, #0xFFFF
 MOVT    R3, #0x0000
__delay_loop:
 CBZ     R3, __delay_exit
 SUB     R3, R3, #1
 B       __delay_loop
__delay_exit:
 BX      LR

You may find it not very optimized, but who cares =) It’s only a ‘hello world’ after all. Anyway, save the file into ‘blinky.S’, and compile it with:

# arm-none-eabi-as -mcpu=cortex-m3 -oblinky.o blinky.S

Now, to combine both object files (startup_lpc17xx.o and blinky.o), we will call the linker. The linker needs to know the device’s memory layout, which is put inside the linker file. Here’s the linker file:

/*
 * Define the supported output formats - elf32-littlearm is the
 *  default
 */
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")

/* Define the target architecture */
OUTPUT_ARCH(arm)

/* Define the system entry point */
ENTRY(Reset_Handler)

/* Define the memory layout for the board */
SECTIONS
{
 /* LPC1766 flash is at 0x0000_0000 */
 .flash 0x00000000 :
 {
 /* Tell the linker to collect any .cs3.interrupt_vector section to this
 address */
 KEEP(*(.cs3.interrupt_vector));

 /* Collect the executable parts here */
 *(.text .text.*);
 /* Collect the const variables here */
 *(.rodata .rodata.*);

 __sidata = .;
 }

 /*
 * This section refers to variables with init value. The init values are
 * stored on ROM, but addressed on RAM (otherwise the variables cannot be
 * manipulated).
 *
 * This section will be loaded at 0x10000000, the start of LPC1766 RAM
 */
 .data_at_ram 0x10000000: AT (LOADADDR(.flash) + SIZEOF(.flash))
 {
 __sdata = .;
 *(.data .data.*);
 __edata = .;
 }

 .ram :
 {
 __sbss = .;
 /* Collect the global / static variables, without init value here */
 *(.bss .bss.*);
 __ebss = .;
 }

 /* Set __cs3_stack to point the end of memory
 * This one depends on your MCU. I'm using LPC1766 with 256kB flash and
 * 32kB RAM
 */

 __cs3_stack = 0x10000000 + (32 * 1024);
}

Save it into ‘linked.ld’. Now, you can combine all the object files into real ARM executable using this command:

# arm-none-eabi-gcc -Wl,--gc-sections -Wl,-Map,LPC1766_Blinky_Assembly.map -Wl,-T/home/kunil/workspace/LPC1766_Blinky_Assembly/linker.ld -oLPC1766_Blinky_Assembly.elf startup_lpc17xx.o blinky.o

This will produce the ARM executable file (LPC1766_Blinky_Assembly.elf) and also the map of the symbols (global / static variables location, function locations, etc) in ‘LPC1766_Blinky_Assembly.map’. Unfortunately, the executable itself is pretty useless, unless you have OS that can load the executable part from it. To load the executable part from the executable file and put it into Intel HEX file, use:

# arm-none-eabi-objcopy -j .flash -j .data_at_ram -O ihex LPC1766_Blinky_Assembly.elf LPC1766_Blinky_Assembly.hex

The thing that you’ll need to copy to your flash is: executable file (on .flash section) and init value for global/static variables (on .data_at_ram section).

The hex file contains the executable part that can be run by your MCU directly. Just download it using your favourite programmer (such as FlashMagic http://www.flashmagictool.com).

2 Comments »

  1. […] Cortex-M3 Blinky in Assembly (via Embedded Freaks..) 2011/04/13 bygreencn Leave a comment Go to comments As tradition for new comers, I created my own 'hello world' using Cortex-M3's assembly using Codesourcery's GCC assembler. The code below has been tested on LPC1766 – Keil's MCB1700 board. It will turn on/off the LEDs on the board. Here's the code: [/sourcecode] /* Simple startup file for Cortex-M3 */ /* Thumb-2 instructions are only supported in unified syntax mode */ .syntax unified /* Vector table definition */ .section ".cs … Read More […]

    Pingback by Cortex-M3 Blinky in Assembly (via Embedded Freaks..) « Green World — April 13, 2011 @ 11:40 am

  2. Nice starting point for Cortex M3. !

    Comment by Udayan U — July 28, 2011 @ 7:49 pm


RSS feed for comments on this post. TrackBack URI

Leave a comment