21.7. Creating Assembly Projects in Keil® MDK-ARM

21.7.1. A Small Project

It is entirely possible to create a project entirely in assembly language. However, in many cases we might still want to reuse some of the C code such as system initialization function and peripheral driver because recreating these codes in assembly can just be too much work.
To do this in Keil MDK, we can use the following steps:
1. Create a project, but without adding the Cortex® Microcontroller Software Interface Standard (CMSIS) software components to the project.
2. Manually copy a start-up code (for example, from one of the previous example project) into a file and name is as an assembly language file (e.g., “startup_stm32l053.s”) and add it to the project.
3. Optionally, manually modify this start-up code so that it does not call SystemInit().
4. In project setting, select MicroLib so that the assembly start-up file does not reference to “__use_two_region_memory”. Alternatively, just remove the heap setup information from the project.
5. Manually add a simple assembly file that contains __main, as follows.
         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
         B        .   ; while(1)
         ENDFUNC
         END          ; End of file

21.7.2. Hello World

One of the most common projects in programming classes is the hello world. It is reasonably easy to do that in C/C++. However, to do this in assembly language programming requires quite a lot of work because existing device drivers and header files are in C/C++, and they need to be ported to assembly code.
To make the setup similar to what we have already got, the SystemInit() function and clock/PLL configuration functions are also ported to assembly code files, and are called at the beginning of main(). In many cases, such work can be very time-consuming and error prone, and that is the key disadvantage of programming in assembly language.
To demonstrate this, I have create a simple program to print a text string via Universal Asynchronous Receiver/Transmitter (UART). Although the main program code is fairly short, the effort to create the system and clock initialization functions is significant (see project example code from book companion web site).
main.s  an assembly language program to print a “Hello” message via UART
         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
;--------------------------------------------------------------
         IMPORT  SystemInit
         IMPORT  Config_32MHz_PLL_Clock
         IMPORT  UART_config
         IMPORT  UART_puts
main     FUNCTION
         BL       SystemInit
         BL       Config_32MHz_PLL_Clock
         BL       UART_config
         LDR      r0,=HELLO_TEXT
         BL       UART_puts
         B        .   ; while(1)
         ENDFUNC
;--------------------------------------------------------------
         LTORG    ; Literal data
HELLO_TEXT  DCB  "Hello
", 0 ; Null terminated string
         ALIGN    4
;--------------------------------------------------------------
         END                ; End of file
It is possible to pull in some of the C program codes for SystemInit() and peripheral control functions we have already prepared for C/C++ projects. However, since the C/C++ code will require CMSIS-CORE header files, so you will also need to add CMSIS-CORE header files, and might end up better off with creating the project in a C/C++ environment.

21.7.3. Additional Text Output Functions

In many case we need to display values, either it is UART or LCD, we still need some functions to convert the binary numbers into strings of characters so that the information is represent in a readable form. In the last example, we create a simple string printing function call UART_puts:
; Input R0 - starting address of text string. Null terminated
        EXPORT  UART_puts
UART_puts FUNCTION
         PUSH    {R4, LR}
         MOV     R4, R0
UART_puts_loop
         LDRB    R0, [R4]
         CMP     R0, #0  
         BEQ     UART_puts_end
         BL      UART_putc
         ADDS    R4, R4, #1
         B       UART_puts_loop
UART_puts_end
         POP     {R4, PC}
         ENDFUNC
To make the collection of functions more complete, functions for outputting values in hexadecimal and decimal formats are added.
A function call UART_put_Hex is developed to send hexadecimal numbers. This function calls the UART_putc function, which outputs single ASCII character each time it is called.
; Input  R0 - value to be converted and output via UART
         EXPORT   UART_put_Hex
UART_put_Hex FUNCTION
         ; Output register value in hexadecimal format
         ; Input R0 = value to be displayed
         PUSH   {R0, R4-R7, LR} ; Save registers to stack
         MOV    R4, R0      ; Save register value to R3 because R0 is used
                            ; for passing input parameter
         MOVS   R0,#'0'     ; Starting the display with "0x"
         BL     UART_putc
         MOVS   R0,#'x'
         BL     UART_putc
         MOVS   R5, #8      ; Set loop counter
         MOVS   R6, #28     ; Rotate offset
         MOVS   R7, #0xF    ; AND mask
UART_put_Hex_loop
         RORS   R4, R6      ; Rotate data value left by 4 bits(right 28)
         MOV    R0, R4      ; Copy to R0
         ANDS   R0, R7      ; Extract the lowest 4 bit
         CMP    R0, #0xA    ; Convert to ASCII
         BLT    UART_put_Hex_Char0to9
         ADDS   R0, #7      ; If larger or equal 10, then convert to A-F 
                            ; (R0=R0+7+48)
UART_put_Hex_Char0to9
         ADDS   R0, #48     ; otherwise convert to 0-9
         BL     UART_putc    ; Output 1 hex character
         SUBS   R5, #1      ; decrement loop counter
         BNE    UART_put_Hex_loop  ; if all 8 hexadecimal characters been displayed
         POP    {R0, R4-R7, PC}  ; then return, otherwise process next 4-bit
         ENDFUNC
A function called UartPutDec for outputting decimal numbers is also created. Similar to the last function, it also uses the UART_putc function. An array of constant values (refer as masks in the program code) are used in the function to speed up the conversion of the value to a decimal string.
; Input  R0 - value to be converted and output via UART
         EXPORT   UART_put_Dec
UART_put_Dec FUNCTION
         ; Output register value in decimal format
         ; Input R0 = value to be displayed
         ; For 32-bit value, the maximum number of digits is 10
         PUSH   {R4-R6, LR}  ; Save register values
         MOV    R4, R0       ; Copy input value to R4 because R0 is
                             ; used for character output
         ADR    R6, UART_put_Dec_Const ; Starting address of mask array
UART_put_Dec_CompareLoop1       ; compare until input value is same or
                             ; larger than the current mask (…/100/10/1)
         LDR    R5, [R6]     ; Get Mask value
         CMP    R4, R5       ; Compare input value to mask value
         BHS    UART_put_Dec_Stage2 ; Value is same or larger than current mask
         ADDS   R6, #4       ; Next smaller mask address
         CMP    R4, #10      ; Check for zero to 9
         BLO    UART_put_Dec_SmallNumber0to9
         B      UART_put_Dec_CompareLoop1
UART_put_Dec_Stage2
         MOVS   R0, #0       ; Initial value for current digit
UART_put_Dec_Loop2
         CMP    R4, R5       ; Compare to mask value
         BLO    UART_put_Dec_Loop2_exit
         SUBS   R4, R5       ; Subtract mask value
         ADDS   R0, #1       ; increment current digit
         B      UART_put_Dec_Loop2
UART_put_Dec_Loop2_exit
         ADDS   R0, #48      ; convert to ascii 0-9
         BL     UART_putc    ; Output 1 character
         ADDS   R6, #4       ; Next smaller mask address
         LDR    R5,[R6]      ; Get Mask value
         CMP    R5, #1       ; Last Mask
         BEQ    UART_put_Dec_SmallNumber0to9
         B      UART_put_Dec_Stage2
UART_put_Dec_SmallNumber0to9    ; Remaining value in R4 is from 0 to 9
         ADDS   R4, #48      ; convert to ascii 0-9
         MOV    R0, R4       ; Copy to R0 for display
         BL     UART_putc    ; Output 1 character
         POP    {R4-R6, PC}  ; Restore registers and return
         ALIGN      4
UART_put_Dec_Const           ; array of mask values for conversion
         DCD    1000000000
         DCD    100000000
         DCD    10000000
         DCD    1000000
         DCD    100000
         DCD    10000
         DCD    1000
         DCD    100
         DCD    10
         DCD    1
         ALIGN
         ENDFUNC
Using these functions, it is fairly easy to transfer information between your targeted systems to a different system via UART interface, e.g., a personal computer running a terminal program or customize the code to output information to a display to help software development, or as a user interface.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.77.98