In the previous assembly language function examples, the data processing can be handled with just a few registers, so it does not use any stack memory at all. By default, the stack memory allocation is done for us in the default start-up code. We could reduce the stack size allocated by modifying the Stack_Size definition from 0x200 to other stack size required:
Stack_Size EQU 0x00000200
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
For most applications, there would be fair amount of data variables. For simple applications, we can also allocate memory space in the RAM. For example, we can add a section in our application code to define three data variables “MyData1” (a word size data variable), “MyData2” (a half-word size data variable), and “MyData3” (a byte size data variable).
PRESERVE8 ; Indicate the code here preserve
; 8 byte stack alignment
THUMB ; Indicate THUMB code is used
AREA |.text|, CODE, READONLY ; Start of CODE area
EXPORT __main ; Make function visible from outside
__main FUNCTION
B main
ENDFUNC
main FUNCTION
LDR R0,=MyData1
LDR R1,=0x00001234
STR R1,[R0] ; MyData1 = 0x00001234
LDR R0,=MyData2
LDR R1,=0x55CC
STRH R1,[R0] ; MyData2 = 0x55CC
LDR R0,=MyData3
LDR R1,=0xAA
STRB R1,[R0] ; MyData3 = 0xAA
B . ; while(1)
ENDFUNC
ALIGN 4
; --------------------------------------------------
; Allocate data variable space
AREA | Header Data|, DATA ; Start of Data definitions
ALIGN 4
MyData1 DCD 0 ; Word size data
MyData2 DCW 0 ; half Word size data
MyData3 DCB 0 ; byte size data
ALIGN 4
; --------------------------------------------------
END ; End of file
Once the program is compiled, we can examine the data memory layout by right clicking on the target name (e.g., “Target 1”) in the project window and select “Open Map file”. From the map report file, we can see the address location and size of the variables we allocated:
Image Symbol Table
Local Symbols
Symbol Name Value Ov Type Size Object(Section)
main.s 0x00000000 Number 0 main.o ABSOLUTE
startup_stm32l053.s 0x00000000 Number 0 startup_stm32l053.o ABSOLUTE
RESET 0x08000000 Section 192 startup_stm32l053.o(RESET)
.text 0x080000c0 Section 24 startup_stm32l053.o(.text)
.text 0x080000d8 Section 48 main.o(.text)
main 0x080000db Thumb Code 20 main.o(.text)
Header Data 0x20000000 Section 8 main.o( Header Data)
MyData1 0x20000000 Data 4 main.o( Header Data)
MyData2 0x20000004 Data 2 main.o( Header Data)
MyData3 0x20000006 Data 1 main.o( Header Data)
STACK 0x20000008 Section 1024 startup_stm32l053.o(STACK)
Since the RAM in microcontroller device used starts at address 0x20000000 onward, the variables are located starting from this address.
In gcc, the same data space allocation can be done by using .lcomm:
/∗ Data in LC, Local Common section ∗/
.lcomm MyData4 4 /∗ A 4 byte data called MyData4 ∗/
.lcomm MyData5 2 /∗ A 2 byte data called MyData5 ∗/
.lcomm MyData6 1 /∗ A 1 byte data called MyData6 ∗/
The .lcomm pseudo-op is used to create an uninitialized block of storage inside the “bss” region. The program code can then access this space using the defined labels MyData4, MyData5, and MyData6.
Another way to allocate memory space is to use the stack memory. In order to allocate memory space for local variables inside a function, we can modify the value of SP at the beginning of a function:
MyFunction
PUSH {R4, R5}
SUB SP, SP , #8 ; Allocate two words for space for local variables
MOV R4, SP ; Make a copy of SP to R0
LDR R5,=0x00001234
STR R5,[R4,#0] ; MyData1 = 0x00001234
LDR R5,=0x55CC
STRH R5,[R4,#4] ; MyData2 = 0x55CC
MOVS R5,#0xAA
STRB R5,[R4,#6] ; MyData3 = 0xAA
…
ADD SP, SP, #8 ; Restore SP back to starting value to free space
POP {R4, R5}
BX LR
The main advantage of using the stack for local variables is that local variables in functions that are not active do not take up any space in RAM. In contrast, many 8-bit microcontroller architectures allocate all data variables in static memory locations, results in larger SRAM requirements.
21.9.2. Complex Branch Handling
When a conditional branch operation is based on a combination of input variables, it can take a complex decision sequence to decide if a branch should be taken. In some cases, it is possible to simplify the decision steps using assembly code.
If the branch condition is based on the variable of 5 bits or less, we can encode the branch condition as a 32-bit constant and extract the decision bit using shift or rotate instruction. For example,
if ((x
==
0)||(x
==
3)||((x>12)&&(x<19))||(x=23)) goto label; // x is a 5-bit data
The decision can be written as:
LDR R0,=x ; Get address of x
LDR R0,[R0] ; Read x from memory
LDR R1,=0x0087E009 ; Encoded branch condition bit 23, 18-13, 3, 0 are set to1
ADDS R0, R0, #1 ; Shift as least one bit
LSRS R1, R1, R0 ; Extract branch condition to carry flag
BCS label ; Branch if condition met
Alternatively, the branch condition can be encoded into an array of data bytes if the branch condition is more than 5-bit wide.
LDR R0,=x ; Get address of x
LDR R0,[R0] ; Read x from memory
LSRS R1,R1,R0 ; Get byte offset in look up table
LDR R2,=BranchConditionTable
LDRB R2,[R2,R1] ; Get encoded condition
MOVS R1, #7
ANDS R1, R1, R0 ; Get lowest 3 bit of x
ADDS R0, R0, #1 ; Shift as least one bit
LSRS R2, R2, R0 ; Extract branch condition to carry flag
BCS label ; Branch if condition met
…
BranchConditionTable
DCB 0x09, 0xE0, 0x87, 0x00, … ; Byte array of encoded branch condition