Actions

EmSys

Starting Cortex-M3 Development Using the GNU Tool Chain - Part 2

From EdWiki

Introduction

In the previous part, we learnt how to write Cortex-M3 Assembly language program and execute it in Qemu. In this part, we will write a simple C-language program and execute it in Qemu.

Setting up the ARM Lab

Please setup an Embedded ARM Lab as described here.

Hello ARM

In this section, we will learn to compile a simple ARM Cortex-M3 program, and test it using Qemu.

  1. int main()
  2. {
  3.  
  4.     int i1,i2,i3;
  5.  
  6.     i1 = 0x12345678;
  7.     i2 = 0x87654321;
  8.  
  9.     i3 = i1 + i2;
  10.  
  11.     i3 = i2/i1;
  12.  
  13.     while(1)
  14.         ;
  15.  
  16.     return 0;
  17. }

Startup Code for use with GNU Tool Chain

  1. //*****************************************************************************
  2. //
  3. // startup.c - Startup code for use with GNU tools.
  4. //
  5. //*****************************************************************************
  6.  
  7. //*****************************************************************************
  8. //
  9. // Forward declaration of the default fault handlers.
  10. //
  11. //*****************************************************************************
  12. void ResetISR(void);
  13. static void NmiSR(void);
  14. static void FaultISR(void);
  15. static void IntDefaultHandler(void);
  16.  
  17. //*****************************************************************************
  18. //
  19. // The entry point for the application.
  20. //
  21. //*****************************************************************************
  22. extern int main(void);
  23.  
  24. //*****************************************************************************
  25. //
  26. // Reserve space for the system stack.
  27. //
  28. //*****************************************************************************
  29. static unsigned long pulStack[64];
  30.  
  31. //*****************************************************************************
  32. //
  33. // The vector table.  Note that the proper constructs must be placed on this to
  34. // ensure that it ends up at physical address 0x0000.0000.
  35. //
  36. //*****************************************************************************
  37. __attribute__ ((section(".isr_vector")))
  38. void (* const g_pfnVectors[])(void) =
  39. {
  40.     (void (*)(void))((unsigned long)pulStack + sizeof(pulStack)),
  41.                                             // The initial stack pointer
  42.     ResetISR,                               // The reset handler
  43.     NmiSR,                                  // The NMI handler
  44.     FaultISR,                               // The hard fault handler
  45.     IntDefaultHandler,                      // The MPU fault handler
  46.     IntDefaultHandler,                      // The bus fault handler
  47.     IntDefaultHandler,                      // The usage fault handler
  48.     0,                                      // Reserved
  49.     0,                                      // Reserved
  50.     0,                                      // Reserved
  51.     0,                                      // Reserved
  52.     IntDefaultHandler,                      // SVCall handler
  53.     IntDefaultHandler,                      // Debug monitor handler
  54.     0,                                      // Reserved
  55.     IntDefaultHandler,                      // The PendSV handler
  56.     IntDefaultHandler                       // The SysTick handler
  57. };
  58.  
  59. //*****************************************************************************
  60. //
  61. // The following are constructs created by the linker, indicating where the
  62. // the "data" and "bss" segments reside in memory.  The initializers for the
  63. // "data" segment resides immediately following the "text" segment.
  64. //
  65. //*****************************************************************************
  66. extern unsigned long _etext;
  67. extern unsigned long _data;
  68. extern unsigned long _edata;
  69. extern unsigned long _bss;
  70. extern unsigned long _ebss;
  71.  
  72. //*****************************************************************************
  73. //
  74. // This is the code that gets called when the processor first starts execution
  75. // following a reset event.  Only the absolutely necessary set is performed,
  76. // after which the application supplied entry() routine is called. 
  77. //
  78. //*****************************************************************************
  79. void ResetISR(void)
  80. {
  81.     unsigned long *pulSrc, *pulDest;
  82.  
  83.     //
  84.     // Copy the data segment initializers from flash to SRAM.
  85.     //
  86.     pulSrc = &_etext;
  87.     pulDest = &_data; 
  88.  
  89.     while(pulDest < &_edata )
  90.     {
  91.         *pulDest++ = *pulSrc++;
  92.     }
  93.  
  94.     //
  95.     // Zero fill the bss segment.
  96.     //
  97.     __asm("    ldr     r0, =_bss\n"
  98.           "    ldr     r1, =_ebss\n"
  99.           "    mov     r2, #0\n"
  100.           "    .thumb_func\n"
  101.           "zero_loop:\n"
  102.           "        cmp     r0, r1\n"
  103.           "        it      lt\n"
  104.           "        strlt   r2, [r0], #4\n"
  105.           "        blt     zero_loop");
  106.  
  107.     //
  108.     // Call the application's entry point.
  109.     //
  110.     main();
  111. }
  112.  
  113. //*****************************************************************************
  114. //
  115. // This is the code that gets called when the processor receives a NMI.  This
  116. // simply enters an infinite loop, preserving the system state for examination
  117. // by a debugger.
  118. //
  119. //*****************************************************************************
  120. static void NmiSR(void)
  121. {
  122.     //
  123.     // Enter an infinite loop.
  124.     //
  125.     while(1) {
  126.         ;
  127.     }
  128. }
  129.  
  130. //*****************************************************************************
  131. //
  132. // This is the code that gets called when the processor receives a fault
  133. // interrupt.  This simply enters an infinite loop, preserving the system state
  134. // for examination by a debugger.
  135. //
  136. //*****************************************************************************
  137. static void FaultISR(void)
  138. {
  139.     //
  140.     // Enter an infinite loop.
  141.     //
  142.     while(1) {
  143.         ;
  144.     }
  145. }
  146.  
  147. //*****************************************************************************
  148. //
  149. // This is the code that gets called when the processor receives an unexpected
  150. // interrupt.  This simply enters an infinite loop, preserving the system state
  151. // for examination by a debugger.
  152. //
  153. //*****************************************************************************
  154. static void IntDefaultHandler(void)
  155. {
  156.     //
  157.     // Go into an infinite loop.
  158.     //
  159.     while(1) {
  160.         ;
  161.     }
  162. }

Linker script for applications using startup.c

  1. /******************************************************************************
  2.  *
  3.  * standalone.ld - Linker script for applications using startup.c 
  4.  *
  5.  *****************************************************************************/
  6. ENTRY(ResetISR) 
  7. MEMORY
  8. {
  9.     FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256K
  10.     SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
  11. }
  12.  
  13. SECTIONS
  14. {
  15.     .text :
  16.     {
  17.         KEEP(*(.isr_vector))
  18.         *(.text*)
  19.         *(.rodata*)
  20.         _etext = .;
  21.     } > FLASH
  22.  
  23.     .data : AT (ADDR(.text) + SIZEOF(.text))
  24.     {
  25.         _data = .;
  26.         *(vtable)
  27.         *(.data*)
  28.         _edata = .;
  29.     } > SRAM
  30.  
  31.     .bss :
  32.     {
  33.         _bss = .;
  34.         *(.bss*)
  35.         *(COMMON)
  36.         _ebss = .;
  37.     } > SRAM
  38. }

Building Binaries

arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -g -c test.c -o test.o
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -g -c startup.c -o startup.o
arm-none-eabi-ld  -g -T standalone.ld startup.o test.o -o test.elf
arm-none-eabi-objcopy -O binary test.elf test.bin

Executing in Qemu

In order to use GDB, launch Qemu with the -s option. It will wait for a GDB connection.

qemu-system-arm -M lm3s6965evb --kernel test.bin --serial null -nographic -S -s

In another terminal, launch GDB:

arm-none-eabi-gdb test.elf 

In GDB, connect to Qemu:

(gdb) target remote localhost:1234

Then you can use gdb normally. For example, type C to launch the kernel.

(gdb) c

Here are some useful tips in order to use gdb on system code:

use info reg to display all the CPU registers
use x/10i $ip to display the code at the PC position