Data Processing and Memory Operations
This experiment develops fluency with the ARM Cortex-M4 instruction set for data manipulation and memory access. You will practice arithmetic, logical, and shift/rotate instructions, apply various addressing modes for load/store operations, and define data structures with assembler directives. By the end of this lab, you will be able to write assembly routines that perform register-level computation and structured memory access, providing the foundation for flow control and procedure calls in later experiments.
Objectives
- Perform arithmetic, logical, and shift/rotate operations using data-processing instructions (including
Operand2with the barrel shifter). - Move data between registers and memory using load/store instructions with immediate, register-offset, and pre-/post-indexed addressing modes.
- Declare and initialize data objects (arrays, strings, buffers) with assembler directives, and use pointers to access and modify them.
- Trace how instructions affect registers and xPSR flags using the Keil uVision5 debugger (breakpoints, single-step, register/memory views).
Background
As mentioned in Experiment 1, assembly instructions are split into three main categories: data processing, load/store, and branch instructions. This experiment focuses on data processing instructions, load/store instructions and their addressing modes. branch instructions and flow control will be covered in the next experiment.
Data Processing Instructions
Data processing instructions perform arithmetic and logical operations on data stored in registers. They can also manipulate the condition flags in the xPSR based on the results of the operations. Common data processing instructions take the following form:
where:
LABEL: optional label for branching.OPCODE: the operation to be performed (e.g.,ADD,SUB,AND,ORR).<cond>: optional condition code that predicates execution.S: optional suffix indicating whether to update the condition flags.Rd: destination register where the result is stored.Rn: first operand register.Operand2: second operand — a register, a barrel shifter operation (see Section 0.1.4), or an immediate. Cortex-M4 executes Thumb-2 only (there is no classic 32-bit ARM state), and its "modified immediate" encoding forOperand2is more flexible than a plain 8-bit value; this is also why a plainMOV Rd, #imm16(as in Experiment 1) can load a 16-bit constant directly — the assembler silently selects aMOVWencoding when the value doesn’t fit the modified-immediate form.
Arithmetic Instructions
Arithmetic instructions perform basic mathematical operations. Some common arithmetic instructions include addition, subtraction, multiplication, and their variants. The following table summarizes some of the most commonly used arithmetic instructions in the ARM Cortex-M4 architecture.
| Instr. | Syntax | Operation | Description |
|---|---|---|---|
| ADD | ADD{S} Rd, Rn, Operand2 | Operand2 may be a register, an immediate, or a shifted register. | |
| ADC | ADC{S} Rd, Rn, Operand2 | Adds carry-in | |
| SUB | SUB{S} Rd, Rn, Operand2 | Standard subtraction. | |
| SBC | SBC{S} Rd, Rn, Operand2 | Subtract with carry. If carry flag is clear, result is reduced by one. Used for multiword arithmetic. | |
| RSB | RSB{S} Rd, Rn, Operand2 | Reverse subtract. | |
| MUL | MUL{S} Rd, Rn, Rm | 32×32 → low 32 bits. | |
| MLA | MLA Rd, Rn, Rm, Ra | Multiply-accumulate. | |
| MLS | MLS Rd, Rn, Rm, Ra | Multiply-subtract. | |
| UMULL | UMULL RdLo, RdHi, Rn, Rm | Unsigned 32×32 → 64-bit product. | |
| SMULL | SMULL RdLo, RdHi, Rn, Rm | Signed 32×32 → 64-bit product. | |
| UDIV | UDIV Rd, Rn, Rm | Unsigned division. | |
| SDIV | SDIV Rd, Rn, Rm | Signed division. |
Common ARM Cortex-M4 Arithmetic Instructions
Note: xPSR. Operand2 may be an immediate or a shifted register depending on the encoding.
Logical and Move Instructions
Logical instructions perform bitwise operations on data, while move instructions transfer data between registers or load immediate values. The following table summarizes some of the most commonly used logical and move instructions in the ARM Cortex-M4 architecture.
| Instr. | Syntax | Operation | Description |
|---|---|---|---|
| AND | AND Rd, Rn, Operand2 | Bitwise AND. | |
| ORR | ORR Rd, Rn, Operand2 | $Rd \leftarrow Rn , | , Operand2$ |
| EOR | EOR Rd, Rn, Operand2 | Bitwise XOR. | |
| BIC | BIC Rd, Rn, Operand2 | Bit clear. | |
| MVN | MVN Rd, Operand2 | Bitwise NOT of operand. | |
| MOV | MOV Rd, Operand2 | Register or immediate move. | |
| MOVW | MOVW Rd, #imm16 | Write low halfword (high cleared). | |
| MOVT | MOVT Rd, #imm16 | Write high halfword (low preserved). |
Logical and Move Instructions
Note: xPSR. Operand2 may be an immediate or a shifted register depending on the encoding.
In this experiment, you will work with bitwise logical instructions to manipulate individual bits within registers. Such operations are fundamental in microcontroller programming, where control and status registers often contain multiple configuration fields packed into a single 32-bit word. Understanding how to set, clear, toggle, or test specific bits without altering the rest of the register is essential for safely modifying hardware configurations and controlling peripherals.
Set and Clear Bits
To set, clear, or toggle specific bits in a register, you can use the following logical instructions:
ORR Rd, Rn, #mask: Sets bits inRdwhere the corresponding bits inmaskare 1.BIC Rd, Rn, #mask: Clears bits inRdwhere the corresponding bits inmaskare 1.EOR Rd, Rn, #mask: Toggles bits inRdwhere the corresponding bits inmaskare 1.
BIC is essentially an AND operation with the negated mask, i.e., BIC Rd, Rn, #mask is equivalent to AND Rd, Rn, #~mask.\
Check Bits
To check whether certain bits are set or cleared, you can use the AND instruction followed by a comparison:
AND Rd, Rn, #mask: Isolates bits inRnwhere the corresponding bits inmaskare 1. The result is stored inRd.- You can then use
CMP Rd, #0to determine if the result is zero (all masked bits cleared) or non-zero (at least one bit set). - Alternatively, you can use
TST Rn, #mask, which performs the AND operation and updates the condition flags without storing the result.
The TST instruction and its interaction with the condition flags will be explored in more detail in the next experiment, where you will learn how conditional execution and branching depend on flag states.
Shift and Rotate Instructions
| Instr. | Syntax | Operation | Description |
|---|---|---|---|
| LSL | LSL Rd, Rm, #shRs | Logical left shift by immediate or by register. | |
| LSR | LSR Rd, Rm, #shRs | Logical right shift (zero fill). | |
| ASR | ASR Rd, Rm, #shRs | Arithmetic right shift (sign fill). | |
| ROR | ROR Rd, Rm, #shRs | Rotate right by immediate or by register. | |
| RRX | RRX Rd, Rm | Rotate right 1 bit through carry (uses |
Shift and Rotate Instructions
Note: Shift amount can be an immediate #sh (0–31) or a register Rs (low 8 bits used). For immediates: LSL #0 = no shift; LSR #0 is treated as shift by 32; ASR #0 is treated as shift by 32; ROR #0 means RRX.
Not all shift/rotate instructions are explicitly present in the ARMv7-M ISA. For example, there is no ROL (rotate left) or ASL (arithmetic shift left) instruction, as these operations can be achieved using existing shift instructions: ROL can be implemented using ROR with a complementary shift amount, and ASL is equivalent to LSL.
Barrel Shifter
The barrel shifter is a hardware feature that allows for efficient shifting and rotating of register values as part of data processing instructions. It can perform operations such as logical shifts (left or right), arithmetic shifts, and rotations on the second operand (Operand2) before it is used in the instruction without wasting extra instructions or cycles.
Examples of barrel shifter usage:
ADD R0, R2, R1, LSL #2 ; R0 = R2 + (R1 << 2) using barrel shifter
SUB R3, R4, R5, LSR #1 ; R3 = R4 - (R5 >> 1) using barrel shifter
ORR R6, R7, R8, ROR #3 ; R6 = R7 | (R8 rotated right by 3)Load and Store Instructions
Since the ARM Cortex-M4 follows the RISC design philosophy, it uses a load/store architecture. This means that arithmetic and logical instructions operate only on registers. Any data in memory must first be loaded into a register before processing, and results must be stored back to memory if they need to be preserved.
| Instr. | Syntax Example | Description |
|---|---|---|
| LDR / STR | LDR/STR Rt, [Rn, #off] | Load/store a 32-bit word. |
| LDRB / STRB | LDRB/STRB Rt, [Rn, #off] | Load/store an 8-bit byte. |
| LDRH / STRH | LDRH/STRH Rt, [Rn, #off] | Load/store a 16-bit halfword. |
| LDRSB / LDRSH | LDRSB/LDRSH Rt, [Rn, #off] | Load signed byte/halfword and sign-extend to 32 bits. |
| LDRD / STRD | LDRD/STRD Rt, Rt2, [Rn, #off] | Load/store a 64-bit doubleword (two registers). |
Load and Store Instructions (Summary)
Reset_Handler
LDR R0, =XVAL ; R0 = &XVAL (address of XVAL)
LDR R1, [R0] ; R1 = 0x12345678 (load word from memory)
ADR R5, XVAL ; R5 = PC-relative address of XVAL (if in range)
LDRB R2, [R0] ; R2 = 0x78 (lowest byte of XVAL)
LDRH R3, [R0] ; R3 = 0x5678 (lowest halfword of XVAL)
MOV R4, #0xFF
LDR R0, YPTR ; R0 = contents of YPTR = &YVAL
STRB R4, [R0] ; store 0xFF into YVAL (low byte only)
STOP B STOP
AREA CONSTANTS, DATA, READONLY
XVAL DCD 0x12345678 ; word in memory
YPTR DCD YVAL ; contains the address of YVAL
AREA MYDATA, DATA, READWRITE
YVAL SPACE 4
ENDDeclaring Data in Memory
Data in assembly is defined using assembler directives that reserve and optionally initialize memory. Common directives include DCD, DCW, and DCB, which define words, halfwords, and bytes, respectively. These are typically placed within a DATA area to create arrays, lookup tables, buffers, or strings.
DCD— Define Constant Word (32 bits per element)DCW— Define Constant Halfword (16 bits per element)DCB— Define Constant Byte (8 bits per element)SPACE— Reserve uninitialized memory (in bytes)FILL— Fill memory with a specified value for a given length (FILLbytes, value, value size)
Initial values given by DCD, DCB, and FILL only take effect in READONLY areas. As explained in Experiment 1, READWRITE memory is not initialized in these projects, so reserve it with SPACE and write it before reading it.
AREA CONSTANTS, DATA, READONLY
NUMBERS DCD 10, 20, 30, 40 ; array of 32-bit integers
BYTES DCB 1, 2, 3, 4 ; array of bytes
TEXT DCB "HELLO",0 ; null-terminated ASCII string
PATTERN FILL 64, 0xFF, 1 ; 64 bytes, each 0xFF
AREA MYDATA, DATA, READWRITE
BUFFER SPACE 64 ; reserve 64 bytes (uninitialized)Each label (e.g., NUMBERS, TEXT) marks the starting address of a data object in memory. You can load these addresses into registers using LDR R0, =NUMBERS or ADR R0, TEXT, then access individual elements through the appropriate addressing modes.
Note: Strings are stored as consecutive ASCII characters in memory. A terminating zero (0x00) is typically appended to indicate the end of the string, similar to C-style strings.
Understanding Pointer Declarations
The directive YPTR DCD YVAL reserves a 32-bit word at the label YPTR and initializes it with the address of YVAL. In other words, YPTR acts as a pointer variable that holds the address of another variable (YVAL). Executing LDR Rn, YPTR loads the 32-bit contents stored at YPTR—that is, the address of YVAL—into Rn, making Rn a pointer to YVAL.
| Address | Label | Contents |
|---|---|---|
| 0x2000 | XVAL | 0x12345678 |
| 0x2004 | YPTR | 0x2008 (address of YVAL) |
| 0x2008 | YVAL | (uninitialized) |
Loading Addresses and Values: LDR, LDR =, and ADR
In ARM assembly, it is important to distinguish between loading a value from memory and loading the address of a label. Although these instructions look similar, their behavior and purpose differ depending on how the assembler interprets them.
LDR Rn, labelLoads the 32-bit value stored at the memory address identified bylabelinto registerRn. The CPU performs a direct memory read:Example:
LDR R0, XVALloads the contents ofXVAL(e.g.,0x12345678) intoR0.LDR Rn, =labelLoads the address oflabelintoRn, rather than the data stored at that address. The assembler generates this by either constructing the address usingMOVW/MOVTinstructions or placing it in a nearby literal pool for PC-relative loading.[^1]Example:
LDR R0, =XVALplaces the address ofXVALinR0.ADR Rn, labelLoads the address oflabelintoRnby computing it relative to the current program counter. This method requires no literal pool or memory access but only works for nearby addresses (about ±4 KB in Thumb mode):
Addressing Modes
Addressing modes define how the effective address or operand value is obtained by an instruction. The ARM Cortex-M4 supports several common addressing modes, summarized below:
| Mode | Syntax Example | Description |
|---|---|---|
| Immediate | MOV R0, #10 | Operand is a constant value encoded in the instruction. |
| Register Direct | MOV R0, R1 | Operand is taken directly from a register. |
| Register Indirect | LDR R0, [R1] | Register holds the address of the operand in memory. |
| Register Offset | LDR R0, [R1, R2] | Effective address = base register + offset register. |
| Scaled Register Offset | LDR R0, [R1, R2, LSL #2] | Effective address = base register + (offset register shifted); common for indexing arrays of words (LSL #2 = |
| Immediate Offset | LDR R0, [R1, #4] | Effective address = base register + constant offset. |
| Pre-indexed | LDR R0, [R1, #4]! | Base updated first, then memory access. |
| Post-indexed | LDR R0, [R1], #4 | Memory access first, then base register updated. |
General Addressing Modes in ARM Cortex-M4
; Immediate Offset
LDR R0, [R1, #4] ; R0 = word at memory[R1 + 4]
; Register Offset
LDR R0, [R1, R2] ; R0 = word at memory[R1 + R2]
; Pre-indexed
LDR R0, [R1, #4]! ; R1 = R1 + 4, then load R0 = [R1]
; Post-indexed
LDR R0, [R1], #4 ; load R0 = [R1], then R1 = R1 + 4[^1]: For more details, see ARM Developer Documentation, “Literal pools and LDR =const”. https://developer.arm.com/documentation/dui0473/m/dom1359731147760
Examples
Setting up the project
This experiment is written in assembly and runs in Keil's simulator. If you haven't made a project yet, follow the Assembly Project guide first.
Example 1: Data Processing Instructions
This example demonstrates various arithmetic and bitwise operations on registers.
AREA RESET, CODE, READONLY
EXPORT __Vectors
__Vectors
DCD 0x20001000
DCD Reset_Handler
ALIGN
AREA MYCODE, CODE, READONLY
ENTRY
EXPORT Reset_Handler
Reset_Handler
; Load values from memory into registers
LDR R1, NUM1 ; R1 = 50
LDR R2, NUM2 ; R2 = 12
; Arithmetic operations
ADD R3, R1, R2 ; R3 = 50 + 12 = 62
SUB R3, R3, #4 ; R3 = 62 - 4 = 58
MUL R4, R3, R2 ; R4 = 58 * 12 = 696
; Logical operations
AND R5, R4, #0xFF ; R5 = 696 & 0xFF = 0xB8 (184)
ORR R5, R5, #0x01 ; R5 = 0xB8 | 0x01 = 0xB9 (185)
BIC R5, R5, #0x08 ; R5 = 0xB9 & ~0x08 = 0xB1 (177)
EOR R5, R5, #0x02 ; R5 = 0xB1 ^ 0x02 = 0xB3 (179)
; Shift and Barrel Shifter
LSL R8, R4, #4 ; logical left shift by 4 bits
LSR R9, R4, #8 ; logical right shift by 8 bits
ADD R5, R5, R5, LSL #1 ; R5 = R5 + (R5 << 1) = 3 * R5
; Store result in memory using a pointer
LDR R6, RP ; R6 = address of RESULT
STR R5, [R6] ; RESULT = R5
; Read back for verification
LDR R7, [R6] ; R7 = RESULT
STOP B STOP
AREA CONSTANTS, DATA, READONLY
NUM1 DCD 50 ; First integer
NUM2 DCD 12 ; Second integer
RP DCD RESULT ; Pointer to RESULT variable
AREA MYDATA, DATA, READWRITE
RESULT SPACE 4 ; Will hold the final computed value
ENDExample 2: Load/Store with Different Addressing Modes
This example demonstrates load and store instructions using various addressing modes.
AREA RESET, CODE, READONLY
EXPORT __Vectors
__Vectors
DCD 0x20001000 ; Initial SP (example)
DCD Reset_Handler ; Reset vector
AREA MYCODE, CODE, READONLY
ENTRY
EXPORT Reset_Handler
Reset_Handler
LDR R0, =ARRAY ; R0 = &ARRAY (base address)
; 1) Immediate Offset: EA = R0 + #8 (third element)
; ---------------------------------------------------------
LDR R1, [R0, #8] ; R1 = ARRAY[2] = ? (expect 30)
LDR R7, =OUT
STR R1, [R7, #0] ; OUT[0] = R1
; 2) Pre-indexed: R2 = [R2 + #4], then R2 = R2 + #4
; Use a scratch pointer so R0 remains the base.
; ---------------------------------------------------------
MOV R2, R0 ; R2 = &ARRAY
LDR R3, [R2, #4]! ; R2 -> &ARRAY[1], R3 = ARRAY[1] = ? (expect 20)
STR R3, [R7, #4] ; OUT[1] = R3
; After this, R2 now points at ARRAY[1].
; 3) Post-indexed: R4 = [R4], then R4 = R4 + #12
; Load first element, then advance pointer to the 4th.
; ---------------------------------------------------------
MOV R4, R0 ; R4 = &ARRAY
LDR R5, [R4], #12 ; R5 = ARRAY[0] = ? (expect 10), R4 -> &ARRAY[3]
STR R5, [R7, #8] ; OUT[2] = R5
; 4) Register Offset: EA = R0 + R6
; Offset register holds byte offset (multiple of 4 for words).
; ---------------------------------------------------------
MOV R6, #12 ; byte offset to ARRAY[3]
LDR R8, [R0, R6] ; R8 = ARRAY[3] = ? (expect 40)
STR R8, [R7, #12] ; OUT[3] = R8
; read second element via register offset
MOV R6, #4 ; byte offset to ARRAY[1]
LDR R9, [R0, R6] ; R9 = ARRAY[1] = ? (expect 20)
STOP B STOP
AREA CONSTS, DATA, READONLY
ARRAY DCD 10, 20, 30, 40 ; four words at consecutive addresses
AREA MYDATA, DATA, READWRITE
OUT SPACE 16 ; capture buffer for observed loads.
ENDLab work
Lab work 1: Bitwise Register Manipulation
Start with R0 = 0x12345678. Perform the following operations and observe the results in the debugger (verify in hex and binary):
- Clear bits 4–7 (second hex nibble).
- Set bits 8–11 (force that nibble to
F). - Toggle bits 28–31 (highest nibble).
Hint: Use BIC, ORR, and EOR with appropriate masks.
Lab work 2: Temperature Conversion
Convert between Fahrenheit and Celsius using values stored in memory:
Start from the following data definitions:
AREA CONSTANTS, DATA, READONLY
TEMP_F DCD 70 ; input temperature in Fahrenheit
TEMP_C DCD 22 ; input temperature in Celsius
AREA MYDATA, DATA, READWRITE
OUT_C SPACE 4 ; TEMP_F converted to Celsius
OUT_F SPACE 4 ; TEMP_C converted to Fahrenheit- Load both inputs from memory into registers.
- Compute both conversions using
SUB,MUL,SDIV, andADD. - Store the results in
OUT_CandOUT_F, and verify them in the Memory window. - Change
TEMP_Fto-40and run again (the result should also be −40). Explain why the program must useSDIVrather thanUDIV.
Hint: Integer division truncates, so multiply before you divide. For