Lesson 04: ARM Cortex-M Assembly
ARM Cortex Architecture
There are two types of processor architecture: Harvard architecture and von Neumann architecture. Harvard architecture has separate data and instruction buses, allowing transfers to be performed simultaneously on both buses. A von Neumann architecture has only one bus which is used for both data transfer and instruction fetches, and therefore data transfers and instruction fetches must be scheduled - they can not be performed at the same time.
ARM Cortex-M microcontroller is a Harvard architecture. Instructions are fetched from Flash ROM using the ICode bus. Data are exchanged with memory and I/O via the system bus interface. On the Cortex-M4, there is a second I/O bus for high-speed devices like USB.
Operating Modes
The ARM processor has seven modes of operation:
- User Mode (USR): is the usual ARM program execution state, and is used for executing most application programs. It has access to the base register set, and no privileges.
- System Mode (SYS): is a privileged user mode for the operating system.
- Fast Interrupt Mode (FIQ): supports a data transfer or channel process. The difference between FIQ and IRQ mode is the FIQ can interrupt regular IRQ. In comparison with other processors, like 6502 or 80x86 CPU, an FIQ is similar to an NMI; the difference being that the ARM has no NMI (FIQs can be disabled). However, it is the same concept of a two-level interrupt system where a more important interrupt can interrupt an interrupt.
- Interrupt Mode (IRQ): is used for regular, general-purpose interrupt handling.
- Supervisor Mode (SVC): is a protected mode for the operating system. OS calls (SWI) set the processor to SVC mode, and then the process jumps to \(08 (or \)FFFF0008).
- Abort Mode (ABT): An abort is signaled by the memory system as a result of a failure to load either an instruction (Prefetch Abort) or data (Data Abort).
- A Prefecth Abort occurs if the processor attempts to execute a failed instruction load (note - no abort happens if the processor fails to load an instruction, but said instruction is not executed due to a branch or suchlike).
- A Data Abort occurs if the processor attempts to fetch data but the memory system says it is unable to, The abort occurs before the failed instruction alters the processor state.
- Undefined Mode (UND): When an undefined instruction is encountered, the ARM will wait for a coprocessor to acknowledge that it can deal with the instruction (if in co-processor instruction space). If no coprocessor responds, or the instruction is one that is not defined, then the undefined instruction vector is taken.
Registers
ARM processor provides general-purpose and special-purpose registers. Some additional registers are available in privileged execution modes.
In ARM state, 16 general registers and one or two status registers are accessible at any time. In privileged modes, mode-specific banked registers become available. Figure 1 shows which registers are available in each mode.
Figure 1: Registers Organization in ARM State
The ARM state register set contains 16 directly-accessible registers, R0 ~ R15. Another register, the Current Program Status Register (CPSR), contains condition code flags, status bits, and current mode bits.
- 13 general-purpose registers R0 ~ R12, can be used to hold either data or address values
- R0 ~ R7: Low registers
Accessible by 16-bit Thumb instruction set and 32-bit ARM/Thumb-2 instruction set - R8 ~ R15: High registers
Only 32-bit ARM/Thumb-2 instruction set can access the Higher registers
- R0 ~ R7: Low registers
- R13 / SP
Register R13 is used as the Stack Pointer (SP) - R14 / LR
Register R14 is used as the subroutine Link Register (LR) which holds the caller's return address when a Branch with Link (BL or BLX) instruction is executed - R15 / PC
Register R15 holds the Program Counter (PC):
- In ARM state this is word-aligned (4 Bytes)
- In THUMB state this is halfword-aligned (2-Bytes)
- CPSR
It holds the program status flags: results of arithmetic and logical operations.
The registers R0 ~ R15 are 32 bits wide.
R0 | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | R10 | R11 | R12 | R13 | R14 | R15 |
The registers may also be referred to by the following aliases:
A1 | A2 | A3 | A4 | V1 | V2 | V3 | V4 WR |
V5 | V6 SB |
V7 SL |
V8 FP |
IP | SP | LR | PC |
ARM Cortex-M microcontrollers only support Thumb-1 and Thumb-2 instruction sets, but the legacy 32-bit ARM instruction set isn't supported.
Memory Space
Reset
Assembler Directives
Assembler Directives are symbolic commands in the source program that controls the operation of the assembler program. Directives are placed in the operation field, and they are not part of the instruction set.
ALIGN
The ALIGN
directive is used to ensure the next object is aligned properly by padding whit zeros or NOP
instructions.
Syntax
ALIGN { expr {, offset {, pad {, padsize }}}}
- expr
a numeric expression evaluates to any power of 2 from 20 to 231 - offset
can be any numeric expression - pad
can be any numeric expression - padsize
can be 1, 2, or 4
Example
ALIGN ; skips 0 to 3 bytes to make next work aligned ALIGN 2 ; skips 0 to 1 bytes to make next halfword aligned ALIGN 4 ; skips 0 to 3 bytes to make next word aligned ; This example places the two bytes in the first and fourth bytes of the same word. DCB 1 ALIGN 4,3 DCB 1 ; The second DCB is offset by 3 bytes from the first DCB. ; In this example, n cannot be 0 because it clashes with the 3rd DCB. ; The assembler sets n to 1. DCB 1 DCB 1 DCB 1 ALIGN 4,2 ; The next instruction is word aligned and offset by 2. DCB 2
AREA
The AREA
directive instructs the assembler to assemble a new code or data section.
Syntax
AREA sectionName {, attr} {, attr} …
- sectionName
- the name is giving to the section. Section are independent, named, indivisible chunks of code or data that are manipulated by the linker. You can choose any name for the sections. However, name starting with a non-alphabetic character must be enclosed in bars. For example,
|1_LocalVariables|
. - Some certain name are conventional. For example,
|.text|
is used for code section produced by the C compiler, or for code sections otherwise associated with C library. Using|.text|
makes this assembly code callable from C.
- the name is giving to the section. Section are independent, named, indivisible chunks of code or data that are manipulated by the linker. You can choose any name for the sections. However, name starting with a non-alphabetic character must be enclosed in bars. For example,
- attr
are one or more comma-delimited section attributes. Valid attributes are:
- ALIGN=expression
The section is aligned on a 2expression -byte boundary. the expression can have any integer value from 0 to 31. For example, if the expression is 4, the section is aligned on a 16-bytes boundary. - CODE
Contains machine instructions. READONLY is the default. - DATA
Contains data, not instructions. READWRITE is the default. - STACK
It is a place for the stack, also in RAM. - NOINIT
Normal RAM areas are initialized to zero, but NOINIT defines a RAM area that is not initialized. - READONLY
Indicates that this section must not be written to. This is the default for Code areas. - READWRITE
Indicates that this section can be read from and written to. This is the default for Data areas.
- ALIGN=expression
Do not use ALIGN=0
or ALIGN=1
for ARM code sections.
Do not use ALIGN=0
for Thumb code sections.
Example
; AREA RESET, CODE, READONLY ; reset vectors in flash ROM AREA DATA ; places objects in data memory (RAM) AREA |.text|, CODE, READONLY, ALIGN=2 ; code in flash ROM AREA STACK, NOINIT, READWRITE, ALIGN=3 ; stack area
DCB
The DCB
directive allocates one or more bytes of memory and defines the initial runtime content of the memory.
Syntax
{label} DCB expr, {, expr } …
- expr
is either:
- A numeric expression that evaluates to an integer in the range -128 to 255
- A quoted string. The characters of the string are loaded into consecutive bytes of store.
Example
C_string DCB "C_string",0
DCW and DCWU
The DCW
directive allocates one or more halfwords (16-bit) of memory, aligned on tw0-byte boundaries, and defines the initial runtime contents of the memory.
The DCWU
is the same, except that the memory alignment is arbitrary.
Syntax
{label} DCW {U} expr, {, expr } …
- expr
is a numeric expression that evaluates to an integer in the range -32768 to 65535.
Example
data DCW -128, 0x20FF DCWU 0x0128+4
DCD abd DCDU
The DCD
directive allocates one or more words (32-bit) of memory, aligned on four-byte boundaries, and defines the initial runtime contents of the memory.
The DCDU
is the same, except that the memory alignment is arbitrary.
Syntax
{label} DCD {U} expr, {, expr } …
- expr
is either:
- A numeric expression
- A PC-relative expression
Example
data1 DCD 2,9,100 ; Defines 3 words containing ; decimal values 2, 9, and 100 data2 DCD data1 + 4 ; Defines 1 word containing 4 + ; the address of the label data1 AREA MyData, DATA, READWRITE DCB 255 ; Now misaligned ... data3 DCDU 1,5,20 ; Defines 3 words containing ; 1, 5 and 20, not word aligned
EQU
The EQU
directive gives a symbolic name to a numeric constant, a register-relative value or a program-relative value.
Syntax
name EQU expr {, type}
- name
is the symbolic name to assign to the value - expr
is a register-relative address, a PC-relative address, an absolute address, or a 32-bit integer constant - type
is optional.
type
can be any one of:ARM
THUMB
CODE32
CODE16
DATA
Example
GPIO_PORTD_DATA_R EQU 0x400073FC GPIO_PORTD_DIR_R EQU 0x40007400 GPIO_PORTD_DEN_R EQU 0x4000751C
EXPORT or GLOBAL
The EXPORT
directive declares a symbol that can be used by the linker to resolve symbol references in separate object and library files. GLOBAL
is a synonym for EXPORT
.
Syntax
EXPORT symbol
- symbol
is only imported into other sources if no other source exports an alternative symbol.
Example
AREA Example, CODE, READONLY EXPORT DoAdd ; Export the function name DoAdd for use elsewhere DoAdd ADD R0, R0, R1 ; R0 = R0 + R1
IMPORT and EXTERN
The IMPORT
and EXTERN
directives provide the assembler with a name that is not defined in the current assembly.
The IMPORT
directive imports the symbol unconditionally.
The EXTERN
directive imports the symbol only if it is referred to in the current assembly
Syntax
IMPORT symbol
GLOBAL symbol
- symbol
is only imported into other sources if no other source exports an alternative symbol.
Example
; AREA Example, CODE, READONLY EXTERN __CPP_INITIALIZE ; If C++ library linked, gets the address of __CPP_INITIALIZE function LDR R0, =__CPP_INITIALIZE ; If not linked, address is zeroed. CMP R0, #0 ; Test if zero. BEQ noCplusplus ; Branch on the result.
GET or INCLUDE
Syntax
GET filename
INCLUDE filename
- filename
The name of the file will be included in the assembly. The assembler accepts pathnames in either UNIX or MS-DOS format.
Example
; GET file1.s ; includes file1 if it exists in the current place GET c:\project\file2.s ; includes file2 GET c:\Program files\file3.s ; space is permitted
SPACE or FILL
Syntax
{label} SPACE expr
{label} FILL expr {, value {, vlauesize}}
- label
is an optional label - expr
Evaluates to the number of bytes to fill or zero - value
- evaluates to the value to fill the reserved bytes with.
- value is optional and if omitted, it is 0.
- the value must be 0 in a NOINIT area
- valuesize
- is the size, in bytes, of value
- It can be any of 1, 2, or 4
- valuesize is optional and if omitted, it is 1
Example
AREA MyData, DATA, READWRITE data1 SPACE 255 ; defines 255 bytes of zeroed store data2 FILL 50,0xAB,1 ; defines 50 bytes containing 0xAB
THUMB
The THUMB
directive instructs the assembler to use Thumb instructions.
Syntax
THUMB
END
The END
directive informs the assembler that it has reached the end of a source file.
Syntax
END
Every assembly language source file must end with END
on a line by itself.
- If the source file has been included in a parent file by a
GET
directive, the assembler returns to the parent file and continues assembly at the first line following theGET
directive. - If
END
is reached in the top-level source file during the first pass without any errors, and the second pass begins. - If
END
is reached in the top-level source file during the second pass, the assembler finishes the assembly and writes the appropriate output.
Example
Assembler Operators
Primitive operations cab be performed on data before it is used in an instruction. Operators can be used on a single value (unary operators) or two values (binary operators). Unary operators are not that common; however, binary operators prove to be quite handy for shuffling bits across a register or creating masks. Some of the most useful binary operators are:
Keil IDE | TI Code Composer Studio | |
A modulo B | A :MOD: B | A % b |
Rotate A left by B bits | A :ROL: B | |
Rotate A right by B bits | A :ROR: B | |
Shift A left by B bits | A :SHL: B or A << B | A << B |
Shift A right by B bits | A :SHR: B or A >> B | A >> B |
Add A to B | A + B | A + B |
Subtract B from A | A - B | A - B |
Bitwise AND of A and B | A :AND: B | A & B |
Bitwise Exclusive OR of A and B | A :EOR: B | A ^ B |
Bitwise OR of A and B | A :OR: B | A | B |
For Example, to enable some particular bits as output pins in a GPIO direction register (GPIO_PORTn_DIR_R), you might have the direction register copied to a general-purpose register first. Then the bits of output pins would be modified using an OR operation, and the direction register would be stored back. The instructions might look like this:
BIT0 EQU 2_00000001 BIT1 EQU 2_00000010 BIT2 EQU 2_00000100 BIT3 EQU 2_00001000 BIT4 EQU 2_00010000 BIT5 EQU 2_00100000 BIT6 EQU 2_01000000 BIT7 EQU 2_10000000 LDR R0, =GPIO_PORTF_DIR_R LDR R1, [R0] OR R1, #(BIT1 :OR: BIT2 :OR: BIT3) STR R1, [R0]
Assuming you like to set pins 1, 2, and 3 as output I/O, you have now set bits 2,3, and 4. The advantage of writing it this way is that you are more likely to understand that you wanted one in particular bit locations, rather than simply using a logical operation with a value such as 0x0E.
You can even use these operators in the creating of constants, for example:
DCD (0x3F10 :SHL: 4) :OR: 2