RealView MDK中宏的使用方法
武汉理工大学 熊 刚
宏可以减少源代码长度,结构清晰,可以给宏起个与功能相关的名字,增加可读性。另外在RealView MDK中可以用宏来处理中断。即把一个普通函数作为中断服务子程序。例如AT91RM9200这块芯片采用的ARM9内核。ARM9核有多种模式,进入中断时需要进行模式切换。因此中断服务子程序时,除了保护现场之外还需要在进入中断之间保存当前模式,在中断子程序执行完时恢复到中断之前的模式。但是我们知道普通函数并不会保存进入之前的模式也不会恢复到进入之前的模式。为将一个普通函数改造为一个中断函数,我们需要在进入函数之前和退出函数之后加上一小段汇编代码来进行模式的保存和恢复。这就是ARM9核惯用的中断处理方法之一。下面介绍在MDK中如何使用宏。
宏的定义格式:
MACRO
{$label} macroname {$parameter}{$parameter }…
;宏定义体
MEND
其中 $label 宏指令被展开时,label可被替换成相应的符号,通常为一个标号在一个符号前使用$表示被汇编时使用相应的值替代$后的符号。
Macroname 所定义的宏的名称。
$parameter 宏指令的参数。当宏指令被展开时将被替换成相应的值,类似于函数中的形参。
下面举个例子,这个例子中先定义一个宏,然后调用该宏,并显示调用该宏之后展开的汇编代码。让读者深刻理解宏的使用方法。
MACRO
$Lab DivMod $Div,$Top,$Bot,$Temp
ASSERT $Top <> $Bot ; Produce an error message if the
ASSERT $Top <> $Temp ; registers supplied are
ASSERT $Bot <> $Temp ; not all different
IF "$Div" <> ""
ASSERT $Div <> $Top ; These three only matter if $Div
ASSERT $Div <> $Bot ; is not null ("")
ASSERT $Div <> $Temp ;
ENDIF
$Lab
MOV $Temp, $Bot ; Put divisor in $Temp
CMP $Temp, $Top, LSR #1 ; double it until
90 MOVLS $Temp, $Temp, LSL #1 ; 2 * $Temp > $Top
CMP $Temp, $Top, LSR #1
BLS %b90 ; The b means search backwards
IF "$Div" <> "" ; Omit next instruction if $Div is null
MOV $Div, #0 ; Initialize quotient
ENDIF
91 CMP $Top, $Temp ; Can we subtract $Temp?
SUBCS $Top, $Top,$Temp ; If we can, do so
IF "$Div" <> "" ; Omit next instruction if $Div is null
ADC $Div, $Div, $Div ; Double $Div
ENDIF
MOV $Temp, $Temp, LSR #1 ; Halve $Temp,
CMP $Temp, $Bot ; and loop until
BHS %b91 ; less than divisor
MEND
调用该宏语句:
ratio DivMod r0,r5,r4,r2
该宏展开后的汇编代码如下:
ASSERT r5 <> r4 ; Produce an error if the
ASSERT r5 <> r2 ; registers supplied are
ASSERT r4 <> r2 ; not all different
ASSERT r0 <> r5 ; These three only matter if $Div
ASSERT r0 <> r4 ; is not null ("")
ASSERT r0 <> r2 ;
ratio
MOV r2, r4 ; Put divisor in $Temp
CMP r2, r5, LSR #1 ; double it until
90 MOVLS r2, r2, LSL #1 ; 2 * r2 > r5
CMP r2, r5, LSR #1
BLS %b90 ; The b means search backwards
MOV r0, #0 ; Initialize quotient
91 CMP r5, r2 ; Can we subtract r2?
SUBCS r5, r5, r2 ; If we can, do so
ADC r0, r0, r0 ; Double r0
MOV r2, r2, LSR #1 ; Halve r2,
CMP r2, r4 ; and loop until
BHS %b91 ; less than divisor
看完以上例子,读者应该对MDK下宏的使用很清晰了。
下面以AT91RM9200为例,说明如何将一个普通函数用作中断服务子程序。
普通函数定义如下:
void AT91F_ST_HANDLER(void)
{
volatile int StStatus;
// Read the system timer status register
StStatus = *(AT91C_ST_SR);
StTick++;
}
处理中断的宏为:
MACRO
IRQHandle $in_handle,$out_handle
EXTERN $in_handle
GLOBAL $out_handle
;#- Adjust and save LR_irq in IRQ stack
sub r14, r14, #4
stmfd sp!, {r14}
;#- Write in the IVR to support Protect Mode
;#- No effect in Normal Mode
;#- De-assert the NIRQ and clear the source in Protect Mode
ldr r14, =AT91C_BASE_AIC
str r14, [r14, #AT91C_AIC_IVR-AT91C_BASE_AIC]
;#- Save SPSR and r0 in IRQ stack
mrs r14, SPSR
stmfd sp!, {r0, r14}
;#- Enable Interrupt and Switch in SYS Mode
mrs r0, CPSR
bic r0, r0, #I_BIT
orr r0, r0, #ARM_MODE_SYS
msr CPSR_c, r0
;#- Save scratch/used registers and LR in User Stack
stmfd sp!, { r1-r3, r12, r14}
ldr r1, =$in_handle
mov lr, pc
bx r1
;#- Restore scratch/used registers and LR from User Stack
ldmia sp!, { r1-r3, r12, r14}
;#- Disable Interrupt and switch back in IRQ mode
mrs r0, CPSR
bic r0, r0, #ARM_MODE_SYS
orr r0, r0, #I_BIT|ARM_MODE_IRQ
msr CPSR_c, r0
;#- Mark the End of Interrupt on the AIC
ldr r0, =AT91C_BASE_AIC
str r0, [r0, #AT91C_AIC_EOICR-AT91C_BASE_AIC]
;#- Restore SPSR_irq and r0 from IRQ stack
ldmia sp!, {r0, r14}
msr SPSR_cxsf, r14
;#- Restore adjusted LR_irq from IRQ stack directly in the PC
ldmia sp!, {pc}^
MEND
GLOBAL AT91F_ST_ASM_HANDLER
AT91F_ST_ASM_HANDLER
IRQHandle AT91F_ST_HANDLER,AT91F_ST_ASM_HANDLER
以上红色部分即为利用定义的宏来处理中断。其中AT91F_ST_ASM_HANDLER是一个全局标号,可在其它文件中引用。AT91F_ST_ASM_HANDLER是前面定义的C函数。中断发生时道先找到标号AT91F_ST_ASM_HANDLER并执行其展开的汇编代码,保存PC及模式之后跳到C函数void AT91F_ST_HANDLER(void)。在返回该函数时有恢复PC和模式。这样就完成了一次中断的调用过程。
注:读者可能会注意到宏IRQHandle前面并没有标号,这也是允许的,该标号是可选的。
附:
MDK中还允许直接使用中断函数,而无须使用汇编代码来处理中断。只须在中断服务子程序加上一些关键字来标识它是一个中断服务子程序,这样在编译器编译的时候会自动在服务子程序的入口和出口加上一些与中断相关的现场保护与恢复汇编指令。
中断标识符与您所选的编译器有关。强大的RealView MDK支持三种主流编译器。它们是RealView编译器、Keil CARM编译器以及GNU编译器。在使用不同的编译器时,中断标识符有所不同。现通过举例来说明不同编译器下中断标识符的用法:
RealView编译器:
__irq void IRQ_Handler (void) {
/* the interrupt code */
}
Keil CARM编译器:
void IRQ_Handler (void) __irq {
/* the interrupt code */}
GNU编译器:
void IRQ_Handler (void) __attribute__ ((interrupt)); // Generate Interrupt
void IRQ_Handler (void) {
/* the interrupt code */