Magic Lantern Firmware Wiki

ARM overview[]

  • 32-bit microcontroller
  • 16 registers: R0 ... R15
  • R13 contains the stack pointer (SP)
  • R14 contains the return address (for subroutine calls). It's called LR (link register).
  • R15 contains the program counter (PC, 24 bits) and flags (8 bits)
  • Flags: Negative result, Zero result, Carry, oVerflowed result, IRQ Interrupt disable, FIQ Fast Interrupt disable, S1 Processor mode 1, S0 Processor mode 0.
  • The ARM can run in 16 bit (Thumb) or 32 bit (standard ARM ASM). Dryos only uses 32bit. CHDK (which is a different beast) uses some Thumb to keep the code smaller [1].
  • Branch instructions (BEQ, BNE, B, BL, BX, etc) load the destination address into the program counter (R15)
  • The program counter (PC) is incremented by 4 bytes for each ARM instruction (2 bytes for each THUMB).
  • The PC doesn't contain the address of the current instruction during execution, this is typically stored at pc-8 (ARM), or pc-4 (THUMB).
​Simplest instructions
MOV R0, R1                ; Assignment: R0 = R1
ADD R0, R1, R2            ; R0 = R1 + R2
SUB and MUL: similar

Load and Store[]

LDR R0, <what to load>                                  ; load from memory and store into a register
STR R0, <where to store>                                ; store the contents of a register into memory
STRD R1, R2, [R_n <+ optional address offset>]                                ; store contents from R1,R2 (where R1 must be an even numbered register and R2 is R1 + 1[1]). (the D suffix is for 'double')
LDRD R1, R2, [R_n <+ optional address offset>]                                ; writes contents of double-sized data to R1, R2. same rules apply as in STRD. 
ADR  R1, aLeddriverEdled ; "LedDriver\\EDLedDriver.c"   ; load a pointer to a string?
ADR  R2, TH_intTimerCBR                                 ; or a pointer to a function?

Addressing modes[]

Notation      Meaning                            Example
#15           ; decimal constant 15              MOV R0, #15
#0xFF         ; hex constant                     MOV R0, #0xFF
R1            ; the contents of R1               MOV R0, R1
[R1]          ; memory address held in R1        LDR R0, [R1]          ; R0 = *(R1) // C notation
                (R1 contains a pointer)          STR R0, [R1]          ; *(R1) = R0
[base,offset] ; memory address is base+offset    LDR R0, [R1+#15]      ; is it possible to use MOV R0, [something] ?
=15 and =0xFF ; numerical constants              LDR R0, =15           ; equivalent to MOV R0, #15
              ; for use in LDR instruction       LDR R0, =0x55555555   ; The constant is too big and cannot be encoded as a single MOV
{R0-R12,R14}  ; register list, for load/store 
              ; on the stack

Conditional instructions[]

  • CMP R0, R1 ; Compare two numbers by performing the difference and setting the flags. The difference is not stored anywhere.
  • CMN <lhs>,<rhs> ; Compare Negative
  • TST <lhs>, <rhs> ; Test logical AND equivalence (what you'll want to use & run into more often than TEQ)
  • TEQ <lhs>,<rhs> ; tests for logical XOR equivalence (often used to test the sign of a value[2]).

Simple IFs can be written by adding some conditional suffixes to the instructions.

E.g. ADDEQ means: IF <EQ> THEN ADD ...

Conditional suffixes:

  • AL Always
  • NV Never
  • EQ Equal (Zero flag set)
  • NE Not Equal
  • VS Overflow Set
  • VC Overflow Clear
  • MI Minus (N flag set)
  • PL Plus
  • CS Carry Set
  • CC Carry Clear
  • HI Higher
  • LS Lower or Same
  • GE Greater or Equal
  • LT Less than
  • GT Greater than
  • LE Less than or equal

C calling convention[]

A subroutine call looks like this:

; store the subroutine arguments somewhere, usually in R0...R3
BL subroutine_name

When a BL is called, the return address (next instruction after the call) is stored in the link register LR.

When the subroutine is called, the first thing it does is decide which registers it will corrupt, and push those to the Stack (SP). This may include the LR if this sub calls another sub.

Before the subroutine returns, It pulls the regs from the Stack, then it MOV PC, LR (or LDR, or other means)

STMFD   SP!, {R2-R6,LR}           ; push to the stack the registers which will be corrupted
; subroutine code
LDMFD   SP!, {R2-R6,PC}           ; pull them back

Branch Instructions[]

Some functions are called with:

B subroutine_name  ; -Immediately jump to supplied subroutine (or label) and resume code execution from there.
BL subroutine_name ; -Branch with link to sub, R14 (LR) is loaded with the address of the next instruction, just before the
                   ; branch. You can then load the link register (R14) back into the program counter (R15)
                   ; to return to the instruction that called the branch, after executing the branch.
BX                 ; -Branch and optionally switch mode (ARM or Thumb). DryOS only uses ARM, so as far as we are
                   ; concerned, BX = B.
BEQ                ; -Branch if EQUAL TO. Usually preceeded by a CMP, the logical result of this CMP operation
                   ; decides if the branch will be executed or not.
BNE                ; -Branch if NOT EQUAL TO. Same as above.

A couple common examples:

LDR     R4, =0x762c     ; R4 = 0x762c
LDR     R0, [R4]        ; R0 = value at 0x762c in memory
CMP     R0, #0          ; Compare R0 to 0, don't do anything yet.
BNE     loc_FF0678E0    ; Branch if NOT EQUAL - branch to loc_FF0678E0 if R0 != 0.
                        ; PC = FF0678E0 now

Passing arguments to subroutines[]

A maximum of 4 arguments are passed to a subroutine in R0 ... R3. If more arguments are needed, they are put on the stack. [thanks AJ!]

  • [AJ, can you give an example of a function with 5 or more arguments?]

The return value is stored in R0.

What happens when the return value is more than 4 bytes? R0 stores a pointer to it?

See also: [2]

Complete Spec:


There are 4 flags of interest in the ARM processor:

  • N - "negative flag" - indicates a negative value
  • Z - "zero flag" - this is set when an instruction produces a result of 0.
  • C - "carry flag" - used when there is a carry generated by something like an addition operation.
  • V - "overflow flag" - this is set in, like the name suggests, the event of an overflow.

Here is a simple example of how flags are used. Consider this basic loop:

     MOV     R0, #0          ; R0 = 0
     B       loop            ; Branch to loop

     ADD     R0, R0, #1      ; Increment R0 by 1. R0 = R0 + 1
     CMP     R0, #9          ; Compare R0 to 9, set Z flag accordingly.
     BNE     loop            ; "Branch if NOT-EQUAL" - looks at Z flag.

This website ( is a great resource with a very nice explanation of a lot of ASM basics.

Proper tail calls[]

See [3] for a nice explanation.

So, if at the end of a function there is a subroutine call, and nothing after, the compiler can make an optimization. This allows infinite recursion without filling the stack :)

In this case, the subroutine may look like this:

ROM:FF063CA4 TurnAVLineMuteOn
ROM:FF063CA4       STMFD   SP!, {R4,LR}
ROM:FF063CE4       LDMFD   SP!, {R4,LR}
ROM:FF063CE8	   B       give_semaphore

Got it?

Useful resources[]

  • Advertisement