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: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf
Flags[]
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 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 (http://www.mitchellwebdesign.com/arm/lecture3/lecture3-1.html) 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?