The ARM Cortex-M3 is a 32-bit RISC processor core licensed by ARM Holdings. It is intended for microcontroller use, and has been implemented by many chip manufacturers in their microcontroller products. The Cortex-M3 processor has a 3-stage pipeline and uses the ARMv7-M architecture, which includes Thumb-2 technology for improved code density. This allows it to achieve very high performance and efficiency while maintaining relatively low power consumption.
Programming the Cortex-M3 in assembly language provides full control over the processor and allows developers to optimize performance for their specific application. However, assembly programming requires deep knowledge of the processor architecture and instruction set. This article provides some examples of basic Cortex-M3 assembly code to demonstrate core concepts and capabilities.
1. Basic Program Structure
All Cortex-M3 assembly programs have the same basic structure. This includes the vector table, stack pointer initialization, main program entry point label, and infinite loop at end. Here is a simple “hello world” example: .thumb .thumb_func .global _start _start: /* Vector table */ .word 0x20001000 .word Reset_Handler .word Default_Handler /* … */ /* Initialize stack pointer */ ldr sp, =0x20020000 /* Main program */ bl main b . main: /* Code here */ b . Default_Handler: b .
The vector table contains the initial stack pointer value and reset handler address. The reset handler configures the system and then calls main(). At minimum, a default handler stub is needed. The main program executes and then the infinite loop at end keeps the processor running.
2. Data Processing Instructions
Data processing instructions operate on registers and include arithmetic, logical, and move operations. Here are some examples: /* Addition */ add r0, r1, r2 /* Subtraction */ sub r3, r4, #5 /* Logical AND */ and r5, r6, r7 /* Logical OR */ orr r8, r9, #0xFF /* Move */ mov r10, #0x100
These instructions allow math operations, boolean logic, and moving immediate values into registers. Multiple addressing modes are supported for flexibility.
3. Load/Store Instructions
Load/store instructions move data between registers and memory. This includes byte, halfword, and word loads/stores. For example: /* Load byte */ ldrb r0, [r1] /* Store word */ str r2, [r3, #8] /* Load halfword */ ldrh r4, [r5, #0x10]
Loads use the source memory address in brackets. Stores use the destination address. Offsets can also be specified like above. These provide access to variables and data structures in RAM.
4. Branch Instructions
Branch instructions alter program flow by jumping to a new instruction address. Some common examples: /* Branch */ b loop /* Branch with link */ bl function /* Compare and branch */ cmp r0, #10 bgt else /* Test bit and branch */ tst r1, #0x1 beq endif
The b instruction performs a simple branch. bl branches and links, saving the return address to the LR register. Conditional branches like bgt branch if greater than based on status flags. Bit testing branches check register values.
5. Subroutine Calls
The bl instruction is used for calling subroutines and functions. Here is an example: /* Function call */ bl function /* Within function */ push {lr} /* Code */ pop {lr} bx lr function: /* Code here */ bx lr
bl branches to the function and links back to the next instruction. The function prologue pushes LR to save the return address. The epilogue pops it back and returns. This implements standard function calling procedure.
6. Interrupt Handling
The Cortex-M3 supports advanced interrupt handling with a Nested Vectored Interrupt Controller (NVIC). This allows setting priority and enabling interrupts: /* Set priority of IRQ interrupt */ ldr r0, =0xE000ED20 mov r1, #0x40 str r1, [r0] /* Enable IRQ interrupt */ ldr r0, =0xE000E100 ldr r1, [r0] orr r1, #0x2 str r1, [r0]
The NVIC registers are accessed to configure the specific IRQ interrupt priority and enable it. When an interrupt occurs, the processor will then jump to the corresponding vector handler.
7. Inline Assembly
For C programs, inline assembly allows mixing C code with ARM instructions. For example: int x; /* Inline assembly */ asm volatile( “mov r0, %[x]\n\t” “lsl r0, #2\n\t” : : [x] “r” (x) );
This shifts the variable x left 2 bits using inline ARM instructions. The input/output operands are defined to interface with the C code. This allows utilizing assembly in C programs.
8. Debugging
Debugging assembly code requires inspecting register and memory contents. The Cortex-M3 DWT (Data Watchpoint and Trace) module enables real-time debug support: /* Set watchpoint on variable */ ldr r0, =0xE0001000 mov r1, #0x100 str r1, [r0, #0x10] /* Single step */ ldr r0, =0xE000EDF0 mov r1, #0x1 str r1, [r0]
A watchpoint can be set to break on variable access. Single stepping progresses one instruction at a time for inspection. More advanced breakpoint and tracing capabilities are also available.
9. Startup Code
The Cortex-M3 startup code handles early system bring-up: .thumb_func .global Reset_Handler Reset_Handler: /* Copy data section values */ ldr r1, =_etext ldr r2, =_data_start ldr r3, =_data_end cmp r2, r3 bcs data_loop data_loop: ldr r0, [r1], #4 str r0, [r2], #4 cmp r2, r3 bcc data_loop /* Clear bss section */ ldr r2, =__bss_start__ ldr r3, =__bss_end__ mov r0, #0 bss_loop: str r0, [r2] , #4 cmp r2, r3 bcc bss_loop /* Branch to main */ bl main
This copies initialized data, clears BSS, and calls main(). Additional configuration like stack setup is also performed before main().
10. Context Switching
Context switching is used to swap between threads or tasks. This example saves context to the PSP stack pointer: /* PSP = Process Stack Pointer */ push {r4-r11} /* Save registers */ mov r0, sp /* Read PSP to r0 */ str r0, [r8] /* Store old PSP value */ ldr r0, =new_PSP /* Get new PSP */ msr psp, r0 /* Load it */ isb /* Instruction barrier */ pop {r4-r11} /* Restore registers */
The general purpose registers r4-11 are saved/restored. The old PSP is stored and new one loaded atomically. This allows Switching stacks between threads safely.
These examples demonstrate some of the key ARM Cortex-M3 assembly programming concepts. Assembly coding for this processor provides great flexibility but requires an intimate knowledge of the architecture. Many principles like subroutines, interrupts, and context switching are directly applicable to other ARM cores as well.