Mastering ARM Assembly: Instructions & Programming Examples

ARM Assembly Programming Examples

Sum of First 10 Integers

This ARM assembly program calculates the sum of the first 10 natural numbers.

AREA INTSUM, CODE, READONLY
ENTRY
    MOV R0, #10     ; Initialize counter R0 with 10
    MOV R1, #0      ; Initialize sum R1 with 0
    MOV R2, #1      ; Initialize current number R2 with 1
LOOP
    ADD R1, R1, R2  ; Add current number (R2) to sum (R1)
    ADD R2, #1      ; Increment current number (R2)
    SUBS R0, #1     ; Decrement counter (R0) and update flags
    BNE LOOP        ; If R0 is not zero, branch back to LOOP
HERE
    B HERE          ; Infinite loop to stop execution
END

Factorial Calculation in ARM Assembly

This program computes the factorial of a number (e.g., 5!) using ARM assembly instructions.

AREA FACT, CODE, READONLY
ENTRY
    MOV R0, #5      ; Number for which to calculate factorial (e.g., 5)
    MOV R1, #1      ; Initialize result R1 with 1
    CMP R0, #0      ; Compare R0 with 0
    BEQ STOP        ; If R0 is 0, branch to STOP (factorial of 0 is 1)
    MOV R1, R0      ; If R0 is not 0, initialize R1 with R0 (e.g., 5)
FACT
    SUBS R0, #1     ; Decrement R0 (e.g., 4, 3, 2, 1)
    CMP R0, #0      ; Compare R0 with 0
    BEQ STOP        ; If R0 is 0, branch to STOP
    MUL R2, R1, R0  ; Multiply R1 (current product) by R0 (current number)
    MOV R1, R2      ; Move the product to R1
    B FACT          ; Branch back to FACT
STOP
    NOP             ; No operation, program stops here
HERE
    B HERE          ; Infinite loop to stop execution
END

Summing 10 Integers from an Array

This ARM assembly code demonstrates how to sum 10 integer numbers stored in a data array.

AREA RESET, CODE, READONLY
ENTRY
    MOV R1, #10     ; Initialize counter R1 with 10 (number of elements)
    LDR R2, =ARRAY  ; Load the base address of ARRAY into R2
    MOV R4, #0      ; Initialize sum R4 with 0
NEXT
    LDR R3, [R2], #4 ; Load word from memory at R2 into R3, then increment R2 by 4
    ADD R4, R4, R3  ; Add the loaded value (R3) to the sum (R4)
    SUBS R1, R1, #1 ; Decrement counter R1 and update flags
    BNE NEXT        ; If R1 is not zero, branch back to NEXT
STOP
    B STOP          ; Infinite loop to stop execution
ARRAY
    DCD 12, 15, 3, 1, 7, 8, 9, 4, 6, 10 ; Define constants (the array elements)
END

ARM Assembly Program: Multiply Two 16-bit Numbers

An ARM assembly language program (ALP) to multiply two 16-bit binary numbers.

AREA MULTIPLY, CODE, READONLY
ENTRY
    MOV R1, #0X6400 ; Load first 16-bit number (25600 decimal) into R1
    MOV R2, #0X3200 ; Load second 16-bit number (12800 decimal) into R2
    MUL R3, R1, R2  ; Multiply R1 by R2, store result in R3
HERE
    B HERE          ; Infinite loop to stop execution
END

ARM Instruction Set: Data Processing & Control Flow

ARM Data Processing: Arithmetic & Logical Instructions

This section discusses the arithmetic and logical instructions within the ARM data processing instruction set, crucial for mathematical operations and bit manipulation.

1. Arithmetic Instructions

These instructions are used to perform mathematical operations like addition, subtraction, and their variants with carry.

  • ADD: Add two numbers.
    Example: ADD R0, R1, R2R0 = R1 + R2
  • ADC: Add with Carry. Adds two numbers plus the carry bit from a previous operation.
  • SUB: Subtract.
    Example: SUB R0, R1, R2R0 = R1 - R2
  • SBC: Subtract with Carry. Subtracts considering the carry (borrow) bit.
  • RSB: Reverse Subtract.
    Example: RSB R0, R1, R2R0 = R2 - R1
  • RSC: Reverse Subtract with Carry.
    Example: RSC R0, R1, R2R0 = R2 - R1 - Carry

2. Logical Instructions

These instructions are used to manipulate individual bits in data, which is very common in embedded systems for checking, setting, or clearing specific bits.

  • AND: Bitwise AND.
    Example: AND R0, R1, R2R0 = R1 & R2
  • ORR: Bitwise OR.
    Example: ORR R0, R1, R2R0 = R1 | R2
  • EOR: Bitwise Exclusive OR (XOR).
    Example: EOR R0, R1, R2R0 = R1 ^ R2
  • BIC: Bit Clear (AND with NOT). Clears specific bits.
    Example: BIC R0, R1, R2R0 = R1 & (~R2)
  • MVN: Move NOT. Performs bitwise negation.
    Example: MVN R0, R1R0 = ~R1

Essential ARM Instructions: MOV, CMP, MUL

A discussion of fundamental ARM instructions: MOV for data transfer, CMP for comparisons, and MUL for multiplication.

1. MOV Instruction (Move Data)

The MOV instruction is used to copy a value into a register.

  • Syntax: MOV destination_register, source
  • Example: MOV R0, #10 (Copies the immediate value 10 into register R0)

2. Comparison Instructions

Comparison instructions are used to compare values. They do not store a result in a register but update the status flags (e.g., N, Z, C, V) in the CPSR (Current Program Status Register). These flags are then used for conditional operations like if statements or while loops.

  • Example: CMP R1, R2 (Compares the value in R1 with R2, setting flags based on R1 - R2)

3. Multiply Instruction

The MUL instruction is used to multiply two values stored in registers.

  • Example: MUL R0, R1, R2 (Multiplies the value in R1 by R2, storing the 32-bit result in R0)

ARM Multiple Register Transfer: LDM & STM

These instructions efficiently move multiple data values between registers and memory with a single instruction. They are particularly useful for saving or loading a group of registers at once, such as during function calls or context switching.

LDM – Load Multiple Registers

LDM (Load Multiple) loads several registers from memory.

  • Syntax: LDM Rn, {Rlist}
  • Example: LDM R0, {R1, R2, R3} (Loads values from memory starting at the address in R0 into registers R1, R2, and R3)

STM – Store Multiple Registers

STM (Store Multiple) stores several registers into memory.

  • Syntax: STM Rn, {Rlist}
  • Example: STM R5, {R1, R2, R3} (Stores the values from registers R1, R2, and R3 into memory starting at the address in R5)

ARM Branch Instructions: Control Program Flow

Branch instructions are fundamental for controlling the flow of a program in ARM assembly, enabling jumps from one part of the code to another. They are essential for implementing loops, conditional statements, and function calls.

Basic Branch (B)

The basic B instruction performs an unconditional jump to a specified label.

  • Syntax: B label
  • Example: B loop_start (Jumps to the instruction at loop_start)

Branch with Condition (B<condition>)

ARM allows conditional branches based on the status flags set by previous operations (e.g., CMP, SUBS). If the condition is met, the branch occurs; otherwise, execution continues to the next instruction.

  • Syntax: B<condition> label
  • Common Conditions: EQ (Equal), NE (Not Equal), GT (Greater Than), LT (Less Than), GE (Greater Than or Equal), LE (Less Than or Equal)
  • Example:
    CMP R0, #5
    BEQ found   ; If R0 equals 5, branch to 'found'
    

Branch with Link (BL) for Function Calls

The BL (Branch with Link) instruction is used for calling subroutines or functions. It saves the return address (the address of the instruction immediately following BL) in the Link Register (LR or R14) before branching. This allows the function to return control to the caller using MOV PC, LR or BX LR.

  • Syntax: BL function_name
  • Example: BL add_numbers (Calls the add_numbers subroutine)

Branch and Exchange (BX) for Mode Switching

The BX (Branch and Exchange) instruction is used to branch to the address stored in a register. It is also commonly used for switching between ARM and Thumb instruction sets, as well as returning from subroutines when the return address is in LR.

  • Syntax: BX register
  • Example: BX LR (Branches to the address in the Link Register, often used for function returns)

ARM Multiple Register Transfer: LDM & STM (Continued)

This section reiterates the functionality of multiple register transfer instructions, which are vital for efficient data movement between the ARM processor’s registers and memory.

LDM – Load Multiple Registers

LDM (Load Multiple) loads several registers from memory in a single operation.

  • Syntax: LDM Rn, {Rlist}
  • Example: LDM R0, {R1, R2, R3} (Loads values from memory starting at the address in R0 into registers R1, R2, and R3)

STM – Store Multiple Registers

STM (Store Multiple) stores the contents of several registers into memory.

  • Syntax: STM Rn, {Rlist}
  • Example: STM R5, {R1, R2, R3} (Stores the values from registers R1, R2, and R3 into memory starting at the address in R5)

ARM Single Register Transfer: LDR & STR

In ARM architecture, single register load and store instructions are used to move individual data values between memory and registers. These are fundamental for accessing data stored in RAM.

  • Load: Copies data from a specific memory address into a register.
  • Store: Copies data from a register to a specific memory address.

These are called Single Register Transfer instructions because only one register is involved in the data transfer at a time.

LDR – Load Register Instruction

LDR (Load Register) is used to load a value (typically a word, 32-bit) from memory into a register.

  • Syntax: LDR Rd, [Rn] (Loads the word at the address in Rn into Rd)
  • Example: LDR R0, [R1] (Loads the value from the memory address pointed to by R1 into R0)

STR – Store Register Instruction

STR (Store Register) is used to store a value from a register into memory.

  • Syntax: STR Rd, [Rn] (Stores the word from Rd to the memory address in Rn)
  • Example: STR R2, [R3] (Stores the value from R2 into the memory address pointed to by R3)

ARM Stack Operations: Pushing & Popping Data

A stack is a crucial memory area used for temporary data storage, including return addresses for subroutines, local variables, and saving/restoring register values. It operates on a LIFO (Last In, First Out) principle. The Stack Pointer (SP, typically R13) register always points to the current top of the stack.

Pushing Data to Stack (Store)

To store multiple registers onto the stack, typically using a Full Descending (FD) stack model where the stack grows downwards (towards lower memory addresses) and the stack pointer points to the last item pushed.

  • Syntax: STMFD sp!, {registers} (! indicates write-back to SP)
  • Example: STMFD SP!, {R4, R5, LR} (Pushes R4, R5, and LR onto the stack, decrementing SP after each push)

Popping Data from Stack (Load)

To load multiple registers from the stack, reversing the push operation. For a Full Descending stack, this means incrementing the stack pointer after each load.

  • Syntax: LDMFD sp!, {registers} (! indicates write-back to SP)
  • Example: LDMFD SP!, {R4, R5, PC} (Pops R4, R5, and PC from the stack, incrementing SP after each pop. Loading PC effectively returns from a subroutine.)

ARM Software Interrupt (SWI) Instruction

A software interrupt (SWI) is a special instruction that signals the processor to temporarily halt its current execution and transfer control to a predefined handler routine, typically part of the operating system (OS) or a firmware service. It’s used to request system-level functions like input/output operations or memory management, allowing user applications to access privileged services in a controlled manner.

Working Principle

  1. The SWI instruction is executed in a program.
  2. The processor:
    • Saves the current program status (CPSR) in SPSR (Saved Program Status Register).
    • Stores the return address (address of the instruction after SWI) in the Link Register (LR).
    • Switches to Supervisor mode (a privileged processor mode).
    • Jumps to a predefined memory address where the SWI handler (a part of the OS) is located.
  3. The SWI handler performs the requested task based on the SWI number.
  4. Once done, it returns control to the original program using the saved return address.
  • ARM Instruction: SWI <number>
  • Example: SWI 0x01 (Calls the system service associated with interrupt number 0x01)

Loading Constants in ARM Assembly

In ARM programming, it’s often necessary to load a fixed value (a constant) into a register. This process is known as loading a constant, and ARM provides several methods depending on the size and nature of the constant.

1. Using the MOV Instruction

The MOV instruction can directly load certain constant values into a register. However, due to the ARM instruction format, MOV can only handle immediate values that can be represented by an 8-bit number rotated by an even number of bits. This limits the range of constants that can be loaded directly.

  • Syntax: MOV Rd, #constant
  • Example: MOV R0, #100 (Loads the immediate value 100 into R0)

2. Using the MVN Instruction (Move NOT)

The MVN instruction performs a bitwise negation of its operand before moving it to the destination register. This can be useful for loading constants that are the bitwise complement of a small immediate value, effectively allowing a wider range of constants to be loaded in a single instruction.

  • Syntax: MVN Rd, #constant
  • Example: MVN R0, #0 (Loads ~0, which is 0xFFFFFFFF, into R0)

3. Using LDR Pseudo-instruction

If a constant is too large or complex to be loaded directly with MOV or MVN, the assembler provides the LDR pseudo-instruction. The assembler will automatically convert this into a valid instruction sequence, typically by placing the constant in a literal pool (a section of memory near the code) and then using a true LDR instruction to load it from memory.

  • Syntax: LDR Rd, =constant
  • Example: LDR R0, =0xFF000000 (Loads the large constant 0xFF000000 into R0)