Overview
Functions are used during software development for a number of reasons. Functions can be used to encapsulate a software component, promote code reuse, increase readability, and aide in verification. We will examine the assembly instructions supported by the Cortex-M architecture that provide the ability to implement functions.
Branch(B) vs Branch With Link(BL)
The Cortex-M architecture supports two similar commands: B and BL. The branch command (B) allows the user to jump, or branch, to an instruction that does not immediately follow the current instruction. So what is missing from the branch instruction that is required to implement a function call? The answer to that is somewhat simple: functions can be called from numerous locations in an application, so the function cannot have a hard coded return address to a single memory location. A function needs to have the ability to return to an arbitrary address in the application.
In order for a function to return to an arbitrary address, the MCU needs to have a mechanism that saves the address of the instruction immediately after the function call. The BL command saves the address of the instruction immediately following the BL command into the Link Register (R14). By storing the address of the next instruction into the link register, a function can “return” by moving the contents of the LR back into the PC using the BX command.
In some situations, the function being executed may be at an address that is more than 16MBs from the address of the BL instruction. In this situation, you can use BLX, the register indirect version of Branch With Link.
Assembly Function Syntax
The code below demonstrates how to declare a function using assembly code. This example assumes that the function exists in a file that is separate from the main routine.
; Filename: ece353_main.s ; Author: ece353 staff ; Description: export __main ; (1) import testBit0 ; (2) Import address for 'testBit0' import testBit10 ; (3) Import address for 'testBit10' ;********************************************** ; Indicate the following describes part ; of the application ;********************************************** AREA |.text|, CODE, READONLY ;(4) ;********************************************** ; Code (FLASH) Segment ; main assembly program ;********************************************** __main PROC ;(5) ; Initialize R0 MOV R0, #0x01 ; Call the function BL testBit0 ; Load the address of testBit10 LDR R2, =(testBit10) ; If the function is more than 16MBs from the PC, ; use the register indirect version of BL BLX R2 ;Loop forever B __main ENDP ; (6) ALIGN END ; (7)
- Makes the address of __main available to code found in other files
- Imports the address of the function named testBit0
- Imports the address of the function named testBit10
- Indicates the resulting machine instructions are part of FLASH, or the code segment
- Gives a name of the function. This is a label that represents the address of the function. The PROC directive tells the assembler that this is a function call (more info).
- Tells the assembler where the end of the functions is located
- Tells the assembler that there are no additional instructions in the file.
Here is the Assembly source for the two functions that were called.
export testBit0 export testBit10 ;********************************************** ; Indicate the following describes part ; of the application ;********************************************** AREA |.text|, CODE, READONLY ;********************************************** ; function testBit0 ; ; Description: ; Tests to see if bit 0 is a 1 or a 0. ; ; Paramters: ; R0 - Data being Examined ; ; Returns: ; Returns a 1 (True) in R1 if bit 0 is a 1. ; Returns a 0 (False)in R1 if bit 0 is a 0. ; ; NOTE: Not an EABI compliant routine! ;********************************************** testBit0 PROC TST R0, #0x01 MOVEQ R1, #0 MOVNE R1, #1 BX LR ; Return from the function ENDP ;********************************************** ; function testBit10 ; ; Description: ; Tests to see if bit 10 is a 1 or a 0. ; ; Paramters: ; R0 - Data being Examined ; ; Returns: ; Returns a 1 (True) in R1 if bit 10 is a 1. ; Returns a 0 (False)in R1 if bit 10 is a 0. ; NOTE: Not an EABI compliant routine! ;********************************************** testBit10 PROC TST R0, #0x400 MOVEQ R1, #0 MOVNE R1, #1 BX LR ; Return from the function ENDP END
Analysis
- Notice that a single file can contain multiple functions. Each function needs to be bracketed with a PROC and ENDP directive
- Function names are simply labels that represent the address where the function is located in the memory map
- If you are going to access a function that resides in a different file, you will need to use import/export directives
- If a function is within 16MBs of the current PC you can use use a BL command. If the function is more than 16MBs away from the PC, you will need to use BLX.
- Parameters and return values can be passed in/out of a function using general purpose registers.
Looking Ahead
Examining both of the functions above, we see that register R1 is modified to indicate if a particular bit in R0 is equal to 1. But what happens to the application if the main routine (__main) placed some type of meaningful data into R1 prior to calling either of these functions? The simple answer is that the data in R1 is lost and our application would not function correctly.
When an application calls a function, there is a reasonable assumption that calling the function will not modify registers containing data that is critical to the operation of the application. In a scenario where all the the general purpose registers contain meaningful data, how does a function execute commands that require modifying a general purpose register? The answer is that we must save any registers with meaningful data to SRAM prior to the function overwriting the register. The section of SRAM that is used to save and restore registers due to function calls is called the Stack. We will examine the Stack in more detail in the next chapter.