KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记4——Directives
一、若干重要的DIRECTIVE介绍
在前面的工程中,我感觉下面的这些Directives是非常有用的。
名称 | 功能 |
---|---|
GET/INCLUDE | 包含其他文件 |
IF…ELSE…ENDIF | 类似与条件编译 |
WHILE…ENDW | 重复生成代码、汇编器变量计算 |
DEF | 类似于C中的#ifdef |
GBLA,GBLL,GBLS | 定义汇编器变量 |
SETA,SETL,SETS | 汇编器变量赋值 |
EQU | 符号赋值 |
RLIST | 寄存器列表定义 |
RN | 寄存器别名 |
MACRO…MEND | 宏定义 |
ROL、ROR、MOD等运算符 | 实现汇编器运算 |
EXPORT、IMPORT、EXTERN | 输出/引入符号 |
这里所有的Directives都在《ARM Developer Suite Assembler Guide》可以查到。
这些Directives是在汇编器的第一遍pass的时候被执行的。有点类似C语言的宏。
二、一些概念
三、Directives详细介绍
1,GET/INCLUDE 文件包含
这个就跟C语言下面的include是几乎一样的。可以将其他的可汇编文件内容拷贝到当前的文件里。
GET filename
这里注意GET的文件要可汇编的。所以如果弄个.h文件是不能用的。一般来说,后面的文件都是.s的。比如。
get registers.s
get irqn_vector.s
get bus_clocks.s
2, IF…ELSE…ENDIF 条件汇编
具体的语法是
IF logical-expression … {ELSE …} ENDIF
注意IF、ELSE和ENDIF都不能顶格写。会被误判成符号。
可以实现条件编辑。比如我这里有一段这样写。就可以实现在设置不同的USART时钟选择的时候,给USART_BSS_VAL这个符号赋不同的值。
if USART_CLOCK_BUSEN = RCC_APB2ENR
USART_BSS_VAL equ (FREQ_APB2 / BAUDRATE / 16 :rol: 4) + ( FREQ_APB2 / BAUDRATE :mod: 16 )
else
USART_BSS_VAL equ (FREQ_APB1 / BAUDRATE / 16 :rol: 4) + (FREQ_APB1 / BAUDRATE :mod: 16 )
endif
3, WHILE…ENDW 循环汇编
可以重复开始一段指令序列或directives。
WHILE logical-expression
code
WEND
where: logical-expression is an expression that can evaluate to either {TRUE} or {FALSE}
注意WHILE和ENDW都不能顶个,会被误判成符号。
我用这个指令可以实现一些数值的计算。
gbla nCal_PPRE2_Div
gbla nCal_PPRE2_Reg
nCal_PPRE2_Div seta FDIV_APB2 :ror: 1
nCal_PPRE2_Reg seta 0
while nCal_PPRE2_Div != 0x01
nCal_PPRE2_Reg seta nCal_PPRE2_Reg + 1
nCal_PPRE2_Div seta nCal_PPRE2_Div :ror: 1
wend
nCal_PPRE2_Reg seta nCal_PPRE2_Reg + 0x04
4, DEF 符号/变量定义检查
可以检查某个符号/变量是否在系统中被定义。
DEF :DEF:A {TRUE} if A is defined, otherwise {FALSE}.
用的时候要注意那两个冒号不能漏了。
这个运算符可以配合条件汇编、符号/变量定义实现C头文件中的那种Guard。例如下面这样的编码。先判断是否定义了_REGISTER_S_这个符号/变量。如果没有定义,就用gbll定义一个这样的汇编器变量。
if :def: _REGISTER_S_
else
gbll _REGISTER_S_
; General Purpose Registers
callee_regs rlist {r4-r12,lr}
;SCB
SCB_BaseAddr equ 0xE000ED00
SCB_CPUID equ 0xE000ED00
SCB_VTOR equ 0xE000ED08
endif
end
5,GBLA,GBLL,GBLS变量定义
用于定义变量。必须不能顶格写。
Directive | 功能 |
---|---|
GBLA | 定义一个文内数值变量,初始值为0 |
GBLL | 定义一个文内逻辑变量, 初始值为FALSE |
GBLS | 定义一个文内字符串, 初始值为null |
语法为
gblx variable
where:
gblx is one of GBLA, GBLL, or GBLS.
variable is the name of the variable. variable must be unique amongst symbols within a source file.
用法在前面有出现
6,SETA,SETL,SETS变量赋值
用于给GBLx定义的变量进行赋值。必须顶格写。
Directive | 功能 |
---|---|
SETA | 给一个文内数值变量赋值 |
SETL | 给一个文内逻辑变量赋值 |
SETS | 给一个文内字符串赋值 |
用法在前面已经出现过。
7,EQU符号常量赋值
给常量赋值。语法比较简单。必须顶格写。 一般来说就是:
name EQU expr{, type}
一般后面花括号里面的我不怎么用。详细的查手册吧。
这里说明一点。由于这些Directives都是运行在第一pass的,而指令都是在第二pass的时候被扫描的。所以对于指令里用的立即数,不论是汇编常量还是汇编变量,用法都是一样的。
8,RLIST寄存器列表定义
给一个通用寄存器的集合命名。必须顶格写。 语法是
name RLIST {list-of-registers}
目前我只是用来给函数需要保护的寄存器做个集合,这样在进入函数和离开函数的时候能有个统一一点的写法。
; General Purpose Registers
callee_regs rlist {r4-r12,lr}
在函数里就比较方便。例如下面的这个函数。
area USB_UART_CODE_SECTION, code
align 4
init proc
push callee_regs
...
pop callee_regs
bx lr
endp
9,RN寄存器别名
给寄存器起别名。必须顶格写。 语法是
name RN expr
; Define the registers commonly used in this file.
rRCC rn r12
rGPIO rn r11
rUART rn r10
rNVIC rn r9
rpBuf rn r8
rBuf_Size rn r7
如果寄存器够用的话,给寄存器起别名可以大大增加汇编代码的可读性。如果不够用的话,就得好好规划一下。但是也不是说就没有机会。
具体应用如下面这段代码。
ldr r0, [rGPIO, #GPIO_MODER]
orr r0, #TX_PIN_MODER_VAL :or: RX_PIN_MODER_VAL
str r0, [rGPIO, #GPIO_MODER]
ldr r0, [rGPIO, #GPIO_PORT_AFIO]
orr r0, #RX_PIN_AFIO_VAL:OR:TX_PIN_AFIO_VAL
str r0, [rGPIO, #GPIO_PORT_AFIO]
在每个汇编文件中,一个别名只能给一个寄存器,但是反过来不一定。就是说一个寄存器可以有多个别名。
10,MACRO…MEND
可以定义一个类似于C的内联函数或宏函数那样的存在。我叫它是汇编宏。语法是这里打不出来,直接去7-27看就好。
应用如下面这段代码。我定义了一个汇编宏叫wait_for_sr_tc_macro_via_r1 。之后只要在这个文件中需要轮询查sr_tc,就可以用这个宏。
macro
$label wait_for_sr_tc_macro_via_r1
$label.test_sr_tc_m
ldr r1, [rUART, #USART_SR]
tst r1, #USART_SR_TC
beq $label.test_sr_tc_m
mend
align 4
send_ch proc
push callee_regs
load_rUART
ldr r2, [rUART, #USART_CR1]
orr r2, #USART_CR1_TE
str r2, [rUART, #USART_CR1]
before_send wait_for_sr_tc_macro_via_r1 ; It is important to wait for the tc to be set.
; By checking this bit via DEBUG window is not possible
; to find if this bit is set or cleared.
str r0, [rUART, #USART_DR]
after_sent wait_for_sr_tc_macro_via_r1 ; This is the same as the previous waiting.
bic r2, #USART_CR1_TE
str r2, [rUART, #USART_CR1]
pop callee_regs
bx lr
endp
如果宏内需要用到跳转,可以用$label指代调用这个宏的那句的标号。
11,ROL、ROR、MOD等运算符
常用的汇编器可以直接使用的运算指令。具体语法参考手册。这里就是强调2点
- IMPORT和EXTERN进来的符号只能用+和-进行处理,其它的运算符不支持。
- 汇编器的这些运算符的运算顺序和C语言是不同的。所以尽可能使用小括号约束。
- 不要忘了运算符单词两边的冒号。
A1466W: Operator precedence means that expression would evaluate differently in C
以上的这个警告就是提示,如果运算太长并过于复杂,建议用小括号进行限制一下。
12,EXPORT、IMPORT、EXTERN
将本文中的符号引出或引入。这个用的很常见。只是要注意,在外部weak定义的符号,如果你想覆盖它,必须把你在文件中定义的符号用export引出去才能真的覆盖。否则这个符号只是在本文中有效。
四、一些体会
灵活使用Directives,可以实现软件的可配置性,并且可以将很多原来由MCU完成的动作提前在上位机上做好。
本文中做的Keil工程可以从这个链接下载:STDIO-App的Keil工程。