赞
踩
ENTRY
start
MOV r0, #4
MOV r1, #3
MOV r2, #2
MOV r3, #0
CMP r0, #num
BHS stop
ADR r4, JumpTable
CMP r0, #2
MOVEQ r3, #0
LDREQ pc, [r4,r3,LSL #2]
CMP r0, #3
MOVEQ r3, #1
LDREQ pc, [r4,r3,LSL #2]
CMP r0, #4
MOVEQ r3, #2
LDREQ pc, [r4,r3,LSL #2]
CMP r0, #1
MOVEQ r3, #3
LDREQ pc, [r4,r3,LSL #2]
DEFAULT
MOVEQ r0, #0
SWITCHEND
stop
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456
JumpTable
DCD CASE1
DCD CASE2
DCD CASE3
DCD CASE4
DCD DEFAULT
CASE1
ADD r0, r1, r2
B SWITCHEND
CASE2
SUB r0, r1, r2
B SWITCHEND
CASE3
ORR r0, r1, r2
B SWITCHEND
CASE4
AND r0, r1, r2
B SWITCHEND
END
程序其实很简单,可见我有多愚笨!还是简要介绍一下这段代码吧。首先用AREA伪代码加上CODE,表明下面引出的将是一个代码段(于此相对的还有数据段DATA),ENTRY 和END成对出现,说明他们之间的代码是程序的主体。start段给寄存器初始化。ADR r4, JumpTable一句是将相当于数组的JumpTable的地址付给r4这个寄存器。
stop一段是用来是程序退出的,第一个语句“MOV r0,#0x18”将r0赋值为0x18,这个立即数对应于宏angel_SWIreason_ReportException。表示r1中存放的执行状态。语句“LDR r1,=0x20026”将r1的值设置成ADP_Stopped_ApplicationExit,该宏表示程序正常退出。然后使用SWI,语句“SWI 0x123456”结束程序,将CPU的控制权交回调试器手中。
在JumpTable表中,DCD类型的数组包含四个字,所以,当实现CASE跳转的时候,需要将给出的索引乘上4,才是真正前进的地址数。
再看一个用汇编实现冒泡排序的例程:
AREA Sort,CODE,READONLY
ENTRY
start
MOV r4,#0
LDR r6,=src
ADD r6,r6,#len
outer
LDR r1,=src
inner
LDR r2,[r1]
LDR r3,[r1,#4]
CMP r2,r3
STRGT r3,[r1]
STRGT r2,[r1,#4]
ADD r1,r1,#4
CMP r1,r6
BLT inner
ADD r4,r4,#4
CMP r4,#len
SUBLE r6,r6,#4
BLE outer
stop
MOV r0,#0x18
LDR r1,=0x20026
SWI 0x123456
AREA Array,DATA,READWRITE
src DCD 2,4,10,8,14,1,20
len EQU 7*4
END
用汇编实现循环需要跳转指令,但是因为ARM系统只有一个CPSR寄存器,所以要实现双重循环还是有些难度。上面这个代码还是有相当大的借鉴意义。程序不难读懂,和C语言的冒泡排序基本思路是完全一样的。
Load CodeWarrior from the Start Menu.
Create a new project (File | New), select ARM Executable Image and give it the name "hello".
Create a new assembler source file (File | New Text File) and paste the following code in it.
; Hello world in ARM assembler
AREA text, CODE
; This section is called "text", and contains code
ENTRY
; Print "Hello world"
; Get the offset to the string in r4.
adr r4, hello ;; "address in register"
loop ; "loop" is a label and designates an address
; Call putchar to display each character
; to illustrate how a loop works
ldrb r0, [r4], #1 ; Get next byte and post-index r4
cmp r0, #0 ; Stop when we hit a null
beq outputstring ;; "branch if equal" = cond. goto
bl putchar
b loop ;; "branch" = goto
outputstring
; Alternatively, use putstring to write out the
; whole string in one go
adr r0, hello
bl putstring ;; "branch+link" = subroutine call
finish
; Standard exit code: SWI 0x123456, calling routine 0x18
; with argument 0x20026
mov r0, #0x18
mov r1, #0x20000 ; build the "difficult" number...
add r1, r1, #0x26 ; ...in two steps
SWI 0x123456 ;; "software interrupt" = sys call
hello
DCB "Hello World/n",0
END
从下面的一个ARM 汇编小程序要弄懂的以下三个问题:
1).在ARM状态转到THUNB状态和BX的应用
2).汇编的架构
3).SWI指令的使用
AREA ADDREG,CODE,READONLY
ENTRY
MAIN
ADR r0,ThunbProg + 1 ;(为什么要加1呢?因为BX指令跳转到指定的地址执行程序 时, 若 (BX{cond} Rm)Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位即把目标 代码解释为 Thunb代码)
BX r0
CODE16
ThunbProg
mov r2,#2
mov r3,#3
add r2,r2,r3
ADR r0,ARMProg
BX ro
CODE32
ARMProg
mov r4,#4
mov r5,#5
add r4,r4,r5
stop mov r0,#0x18
LDR r1,=0x20026
SWI 0x123456
END
SWI--软中断指令:
SWI指令用于产生软中断,从拥护模式变换到管理模式,CPSR保存到管理模式的SPSR中.
SWI{cond} immed_24 ;immed_24为软中断号(服务类型)
使用SWI指令时,通常使用以下两种方法进行传递参数,SWI 异常中断处理程序就可以提供相关的服务,这两种方法均是用户软件协定.SWI异常中断处理程序要通过读取引起软中断的SWI指令,以取得24位立即数.
(1) 指令中的24位立即数指定了用户请求的服务类型,参数通过通用寄存器传递.
mov r0,#34 ;设置子功能号位34
SWI 12 ;调用12号软中断
(2) 指令中的24位立即数被忽略,用户请求的服务类型有寄存器RO的值决定,参数通过其他的通用寄存器传递.
mov r0,#12 ;调用12号软中断
mov r1,#34 ;设置子功能号位34
SWI 0
在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thunb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如如下程序:
T_bit EQU 0X20
SWI_Handler
STMFD SP!,{R0-R3,R12,LR} ;现场保护
MRS R0,SPSR ;读取SPSR
STMFD SP!,{R0} :保存SPSR
TST R0,#T_bit
LDRNEH R0,[LR,#-2] ;若是Thunb指令,读取指令码(16位)
BICNE R0,#0XFF00 :取得Thunb指令的8位立即数
LDREQ R0,[LR,#-4] ;若是ARM指令,读取指令码(32位)
BICEQ R0,#0XFF000000 ;取得ARM指令的24位立即数
....
LDMFD SP!,{R0-R3,R12,PC}^ ;SWI异常中断返回
ARM汇编的SWI指令软中断
从下面的一个ARM 汇编小程序要弄懂的以下三个问题:
1).在ARM状态转到THUNB状态和BX的应用
2).汇编的架构
3).SWI指令的使用
AREA ADDREG,CODE,READONLY
ENTRY
MAIN
ADR r0,ThunbProg + 1 ;(为什么要加1呢?因为BX指令跳转到指定的地址执行程序 时, 若 (BX{cond} Rm)Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位即把目标 代码解释为 Thunb代码)
BX r0
CODE16
ThunbProg
mov r2,#2
mov r3,#3
add r2,r2,r3
ADR r0,ARMProg
BX ro
CODE32
ARMProg
mov r4,#4
mov r5,#5
add r4,r4,r5
stop mov r0,#0x18
LDR r1,=0x20026
SWI 0x123456
END
SWI--软中断指令:
SWI指令用于产生软中断,从拥护模式变换到管理模式,CPSR保存到管理模式的SPSR中.
SWI{cond} immed_24 ;immed_24为软中断号(服务类型)
使用SWI指令时,通常使用以下两种方法进行传递参数,SWI 异常中断处理程序就可以提供相关的服务,这两种方法均是用户软件协定.SWI异常中断处理程序要通过读取引起软中断的SWI指令,以取得24位立即数.
(1) 指令中的24位立即数指定了用户请求的服务类型,参数通过通用寄存器传递.
mov r0,#34 ;设置子功能号位34
SWI 12 ;调用12号软中断
(2) 指令中的24位立即数被忽略,用户请求的服务类型有寄存器RO的值决定,参数通过其他的通用寄存器传递.
mov r0,#12 ;调用12号软中断
mov r1,#34 ;设置子功能号位34
SWI 0
在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thunb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如如下程序:
T_bit EQU 0X20
SWI_Handler
STMFD SP!,{R0-R3,R12,LR} ;现场保护
MRS R0,SPSR ;读取SPSR
STMFD SP!,{R0} :保存SPSR
TST R0,#T_bit
LDRNEH R0,[LR,#-2] ;若是Thunb指令,读取指令码(16)
BICNE R0,#0XFF00 :取得Thunb指令的8位立即数
LDREQ R0,[LR,#-4] ;若是ARM指令,读取指令码(32位)
BICEQ R0,#0XFF000000 ;取得ARM指令的24位立即数
....
LDMFD SP!,{R0-R3,R12,PC}^ ;SWI异常中断返回
Thu Oct 12 2006
软件中断SWI的实现
在需要软件中断处调用
__SWI 0xNum ;Num为SWI中断处理模块的编号,见表SwiFunction
;软件中断
SoftwareInterrupt
CMP R0, #12 ;R0中的SWI编号是否大于最大值
/* 下面这句语句把 (LDRLO地址+ 8 + R0*4) 的地址装载到PC寄存器,举例如果上面的 Num="1",也就是R0 = 1, 假设LDRLO这条指令的地址是0x00008000,那么根据ARM体系的2级流水线 PC寄存器里指向是下两条指令 于是PC = 0x00008008 也就是伪指令DCD TASK_SW 声明的标号TASK_SW 的地址,注意DCD TASK_SW 这条指令本身不是ARM能执行的指令,也不会占有地址,这条指令靠汇编器汇编成可执行代码,它的意义就是声明 TASK_SW的地址, , [PC, R0, LSL #2] 这个寻址方式就是 PC + R0的值左移2位的值( 0x01<<2 => 0x04 ),这样PC的值就是0x0000800C, 即ENTER_CRITICAL的地址于是ARM执行该标号下的任务 */
LDRLO PC, [PC, R0, LSL #2]
MOVS PC, LR
SwiFunction
DCD TASK_SW ;0
DCD ENTER_CRITICAL ;1
DCD EXIT_CRITICAL ;2
DCD ISRBegin ;3
DCD ChangeToSYSMode ;4
DCD ChangeToUSRMode ;5
DCD __OSStartHighRdy ;6
DCD TaskIsARM ;7
DCD TaskIsTHUMB ;8
DCD OSISRNeedSwap ;9
DCD GetOSFunctionAddr ;10
DCD GetUsrFunctionAddr ;11
TASK_SW
MRS R3, SPSR ;保存任务的CPSR
MOV R2, LR ;保存任务的PC
MSR CPSR_c, #(NoInt | SYS32Mode) ;切换到系统模式
STMFD SP!, {R2} ;保存PC到堆栈
STMFD SP!, {R0-R12, LR} ;保存R0-R12,LR到堆栈
;因为R0~R3没有保存有用数据,所以可以这样做
B OSIntCtxSw_0 ;真正进行任务切换
ENTER_CRITICAL
;OsEnterSum++
LDR R1, =OsEnterSum
LDRB R2, [R1]
ADD R2, R2, #1
STRB R2, [R1]
;关中断
MRS R0, SPSR
ORR R0, R0, #NoInt
MSR SPSR_c, R0
MOVS PC, LR
批量数据加载/存储指令实验 2007-08-22 12:08:06
大 中 小
标签:arm指令 ldm/stm
这个程序用批量传输指令传输数据,一次可传8个字:
AREA Block, CODE, READONLY ; name this block of code
num EQU 20 ; Set number of words to be copied
ENTRY ; mark the first instruction to call
start
LDR r0, =src ; r0 = pointer to source block
LDR r1, =dst ; r1 = pointer to destination block
MOV r2, #num ; r2 = number of words to copy
MOV sp, #0x400 ; set up stack pointer (r13)
blockcopy
MOVS r3,r2, LSR #3 ; number of eight word multiples
BEQ copywords ; less than eight words to move ?
STMFD sp!, {r4-r11} ; save some working registers
octcopy
LDMIA r0!, {r4-r11} ; load 8 words from the source
STMIA r1!, {r4-r11} ; and put them at the destination
SUBS r3, r3, #1 ; decrement the counter
BNE octcopy ; ... copy more
LDMFD sp!, {r4-r11} ; dont need these now - restore originals
copywords
ANDS r2, r2, #7 ; number of odd words to copy
BEQ stop ; No words left to copy ?
wordcopy
LDR r3, [r0], #4 ; a word from the source
STR r3, [r1], #4 ; store a word to the destination
SUBS r2, r2, #1 ; decrement the counter
BNE wordcopy ; ... copy more
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SWI 0x123456 ; ARM semihosting SWI
下面的这个程序实现同样的功能,每次只能传一个字:
AREA BlockData, DATA, READWRITE
src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
END
AREA Word, CODE, READONLY ; name this block of code
num EQU 20 ; Set number of words to be copied
ENTRY ; mark the first instruction to call
start
LDR r0, =src ; r0 = pointer to source block
LDR r1, =dst ; r1 = pointer to destination block
MOV r2, #num ; r2 = number of words to copy
wordcopy
LDR r3, [r0], #4 ; a word from the source
STR r3, [r1], #4 ; store a word to the destination
SUBS r2, r2, #1 ; decrement the counter
BNE wordcopy ; ... copy more
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SWI 0x123456 ; ARM semihosting SWI
AREA BlockData, DATA, READWRITE
src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
END
当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足时,指令被执行,否则指令被忽略。
每一条ARM指令包含4位的条件码,位于指令的最高4位[31:28]。条件码共有16种,每种条件码可用两个字符表示,这两个字符可以添加在指令助记符的后面和指令同时使用。例如,跳转指令B可以加上后缀EQ变为BEQ表示“相等则跳转”,即当CPSR中的Z标志置位时发生跳转。
1、 B指令
B指令的格式为:
B{条件} 目标地址
B指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB的地址空间)。以下指令:
B Label ;程序无条件跳转到标号Label处执行
CMP R1,#0 ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label
3.3.6 批量数据加载/存储指令
ARM微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。常用的加载存储指令如下:
— LDM 批量数据加载指令
— STM 批量数据存储指令
LDM(或STM)指令
LDM(或STM)指令的格式为:
LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型}为以下几种情况:
IA 每次传送后地址加1;
IB 每次传送前地址加1;
DA 每次传送后地址减1;
DB 每次传送前地址减1;
FD 满递减堆栈;
ED 空递减堆栈;
FA 满递增堆栈;
EA 空递增堆栈;
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
指令示例:
STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。
ARM汇编的SWI指令软中断 [转贴 2007-05-25 11:21:49]
从下面的一个ARM 汇编小程序要弄懂的以下三个问题:
1).在ARM状态转到THUNB状态和BX的应用
2).汇编的架构
3).SWI指令的使用
AREA ADDREG,CODE,READONLY
ENTRY
MAIN
ADR r0,ThunbProg 1 ;(为什么要加1呢?因为BX指令跳转到指定的地址执行程序 时, 若 (BX{cond} Rm)Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位即把目标 代码解释为 Thunb代码)
BX r0
CODE16
ThunbProg
mov r2,#2
mov r3,#3
add r2,r2,r3
ADR r0,ARMProg
BX ro
CODE32
ARMProg
mov r4,#4
mov r5,#5
add r4,r4,r5
stop mov r0,#0x18
LDR r1,=0x20026
SWI 0x123456
END
SWI--软中断指令:
SWI指令用于产生软中断,从拥护模式变换到管理模式,CPSR保存到管理模式的SPSR中.
SWI{cond} immed_24 ;immed_24为软中断号(服务类型)
使用SWI指令时,通常使用以下两种方法进行传递参数,SWI 异常中断处理程序就可以提供相关的服务,这两种方法均是用户软件协定.SWI异常中断处理程序要通过读取引起软中断的SWI指令,以取得24位立即数.
(1) 指令中的24位立即数指定了用户请求的服务类型,参数通过通用寄存器传递.
mov r0,#34 ;设置子功能号位34
SWI 12 ;调用12号软中断
(2) 指令中的24位立即数被忽略,用户请求的服务类型有寄存器R0的值决定,参数通过其他的通用寄存器传递.
mov r0,#12 ;调用12号软中断
mov r1,#34 ;设置子功能号位34
SWI 0
在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thunb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如如下程序:
T_bit EQU 0X20
SWI_Handler
STMFD SP!,{R0-R3,R12,LR} ;现场保护
MRS R0,SPSR ;读取SPSR
STMFD SP!,{R0} :保存SPSR
TST R0,#T_bit
LDRNEH R0,[LR,#-2] ;若是Thunb指令,读取指令码(16位)
BICNE R0,#0XFF00 :取得Thunb指令的8位立即数
LDREQ R0,[LR,#-4] ;若是ARM指令,读取指令码(32位)
BICEQ R0,#0XFF000000 ;取得ARM指令的24位立即数
....
LDMFD SP!,{R0-R3,R12,PC}^ ;SWI异常中断返回
基于s3c2410软中断服务的uC/OS-II任务切换
1.关于软中断指令
软件中断指令(SWI)可以产生一个软件中断异常,这为应用程序调用系统例程提供了一种机制。
语法:
SWI {<cond>} SWI_number
SWI执行后的寄存器变化:
lr_svc = SWI指令后面的指令地址
spsr_svc = cpsr
pc = vectors + 0x08
cpsr模式 = SVC
cpsr I = 1(屏蔽IRQ中断)
处理器执行SWI指令时,设置程序计数器pc为向量表的0x08偏移处,同事强制切换处理器模式到SVC模式,以便操作系统例程可以在特权模式下被调用。
每个SWI指令有一个关联的SWI号(number),用于表示一个特定的功能调用或特性。
【例子】 一个ARM工具箱中用于调试SWI的例子,是一个SWI号为0x123456的SWI调用。通常SWI指令是在用户模式下执行的。
SWI执行前:
cpsr = nzcVqift_USER
pc = 0x00008000
lr = 0x003fffff ;lr = 4
r0 = 0x12
执行指令:
0x00008000 SWI 0x123456
SWI执行后:
cpsr = nzcVqIft_SVC
spsr = nzcVqift_USER
pc = 0x00000008
lr = 0x00008004
r0 = 0x12
SWI用于调用操作系统的例程,通常需要传递一些参数,这可以通过寄存器来完成。在上面的例子中,r0
用于传递参数0x12,返回值也通过寄存器来传递。
处理软件中断调用的代码段称为中断处理程序(SWI Handler)。中断处理程序通过执行指令的地址获取软件中断号,指令地址是从lr计算出来的。
SWI号由下式决定:
SWI_number = <SWI instruction> AND NOT<0xff000000>
其中SWI instruction就是实际处理器执行的32位SWI指令
SWI指令编码为:
31 - 28 27 - 24 23 - 0
cond 1 1 1 1 immed24
指令的二进制代码的bit23-bit0是24bit的立即数,即SWI指令的中断号,通过屏蔽高8bit即可获得中断号。lr寄存器保存的是中断返回指令的地址,所以 [lr - 4] 就是执行SWI的执行代码。通过load指令拷贝整个SWI指令到寄存器,使用BIC屏蔽指令的高8位,获取SWI中断号。
;read the SWI instruction
LDR r10, [lr, #-4]
BIC r10, r10, #0xff000000
2. 周立功移植uC/OS-II到s3c2410的软中断服务级的任务切换
uC/OS-II的任务调度函数
uC/OS-II的任务级的调度是由函数OS_Sched( )完成的。
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT8U y;
OS_ENTER_CRITICAL();
if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked */
y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
OS_EXIT_CRITICAL();
}
详细解释可以参考《嵌入式实时操作系统 uC/OS-II》,os_sched函数在确定所有就绪任务的最高优先级高于当前任务优先级时进行任务切换,通过OS_TASK_SW( )宏来调用。
OS_TASK_SW( )宏实际上定义的是SWI软中断指令。见OS_CPU.H文件的代码:
__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */
__swi(0x01) void _OSStartHighRdy(void); /* 运行优先级最高的任务 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断 */
__swi(0x40) void *GetOSFunctionAddr(int Index); /* 获取系统服务函数入口 */
__swi(0x41) void *GetUsrFunctionAddr(int Index);/* 获取自定义服务函数入口 */
__swi(0x42) void OSISRBegin(void); /* 中断开始处理 */
__swi(0x43) int OSISRNeedSwap(void); /* 判断中断是否需要切换 */
__swi(0x80) void ChangeToSYSMode(void); /* 任务切换到系统模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任务切换到用户模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任务代码是ARM代码 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任务代码是THUMB */
__swi(0x00) void OS_TASK_SW(void); 是与ADS相关的代码,通过反汇编可以看到,调用OS_TASK_SW实际上被替换成swi 0x00 软中断指令。执行此执行,pc会跳转到向量表的0x08偏移处。
中断向量表:(见Startup.s文件)
CODE32
AREA vectors,CODE,READONLY
; 异常向量表
Reset
LDR PC, ResetAddr
LDR PC, UndefinedAddr
LDR PC, SWI_Addr
LDR PC, PrefetchAddr
LDR PC, DataAbortAddr
DCD IRQ_Addr
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr
ResetAddr DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr DCD SoftwareInterrupt
PrefetchAddr DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse DCD 0
IRQ_Addr DCD IRQ_Handler
FIQ_Addr DCD FIQ_Handler
执行SWI 0x00指令后,pc会跳转到SoftwareInterrupt代码处开始执行:
见Os_cpu_a.s文件的SoftwareInterrupt函数:
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针
STMFD {R0-R3, R12, LR}
MOV R1, SP ; R1指向参数存储位置
MRS R3, SPSR
TST R3, #T_bit ; 中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI指令
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI指令
BICEQ R0, R0, #0xFF000000 ; 如上面所述,此处通过屏蔽SWI指令的高8位来获取SWI号,r0 = SWI号,R1指向参数存储位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw ;为0时跳转到OSIntCtxSwdi地址处
LDREQ PC, =__OSStartHighRdy ; 为1时,跳转到__OSStartHighRdy地址处。SWI 0x01为第一次任务切换
BL SWI_Exception ;进入中断号散转函数
LDMFD {R0-R3, R12, PC}^
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)
以上就是任务切换软中断级服务的实现。
利用arm 組語的PRE-INDEX 與POST-INDEX ADDRESSING,上課時CODING完成的範例--1+2+3+...+10之和 分類:IT技術分享2008/01/30 17:17於今日 97/01/30 上課時實作的程式範例,先貼上程式碼。
因是上課當場coding完成的,so沒有加上註解^^
範例一:使用 POST-INDEX ADDRESSING實作的code
=======================================
AREA ASM6,CODE,READONLY
ENTRY
START
LDR R0,=ARR1
MOV R1,#0
LOOP
LDR R2,[R0],#4
ADD R1,R1,R2
CMP R2,#0
BNE LOOP
STOP
LDR R0,=0X18
LDR R1,=0X20026
SWI 0X123456
AREA ARR,DATA,READWRITE
ARR1 DCD 1,2,3,4,5,6,7,8,9,10,0
END
範例二:使用 PRE-INDEX ADDRESSING實作的code
=======================================
AREA ASM8,CODE,READONLY
ENTRY
START
LDR R0,=ARR1
MOV R1,#0
MOV R3,#0
LOOP
LDR R2,[R0,R1,LSL #2]
ADD R1,R1,#1
ADD R3,R3,R2
CMP R2,#0
BNE LOOP
STOP
LDR R0,=0X18
LDR R1,=0X20026
SWI 0X123456
AREA ARR,DATA,READWRITE
ARR1 DCD 1,2,3,4,5,6,7,8,9,10,0
END
http://www.akaedu.org/bbs/redirect.php?tid=231&goto=lastpost
常用ARM指令
1、 内存访问指令
基本指令:
LDR:memory -> register (memory包括映射到内存空间的非通用寄存器)
STR:register -> memory
语法:
op{cond }{B}{T} Rd , [Rn ]
op{cond }{B} Rd , [Rn , FlexOffset ]{!}
op{cond }{B} Rd , label
op{cond }{B}{T} Rd , [Rn ], FlexOffset
op:基本指令,如LDR、STR
cond:条件执行后缀
B:字节操作后缀
T:用户指令后缀
Rd:源寄存器,对于LDR指令,Rd将保存从memory中读取的数值;对于STR指令,Rd保存着将写入memory的数值
Rn:指针寄存器
FlexOffset:偏移量
例子:
ldr r0, [r1] ;r1作为指针,该指针指向的数存入r0
str r0, [r1, #4] ;r1+4作为指针,r0的值存入该地址
str r0, [r1, #4]! ;同上,并且r1 = r1 + 4
ldr r1, =0x08100000 ;立即数0x08100000存到r1
ldr r1, [r2], #4 ;r2+4作为指针,指向的值存入r1,并且r2=r2+4
【label的使用】
addr1 ;定义一个名为“addr1”的label,addr1 = 当前地址
dcd 0 ;在当前地址出定义一个32bit的变量
~ ~ ~
ldr r1, label1 ;r1 = addr1,r1即可以作为var1的指针
ldr r0, [r1]
add r0, r0, #1
str r0, [r1] ;变量var1的值加1
【FlexOffset的使用】
FlexOffset可以是立即数,也可以是寄存器,还可以是简单的表达式
2、 多字节存取指令(常用于堆栈操作)
基本指令:
LDM:memory ――> 多个寄存器
STM:多个寄存器 ――> memory
语法:
op{cond }mode Rn{!}, reglist {^}
mode:指针更新模式,对应于不同类型的栈。最常用的是“FD”模式,相当于初始栈指针在高位,压栈后指针值减小。
Rn:指针寄存器
!:最后的指针值将写入Rn中
reglist:要操作的寄存器列表,如{r0-r8, r10}
^ :完成存取操作后从异常模式下返回
例子:
;异常处理程序:
sub lr, lr, #4 ; lr – 4是异常处理完后应该返回的地方
;保存r0~r12和lr寄存器的值到堆栈并更新堆栈指针。
stmfd sp!, {r0-r12, lr}
;异常处理
ldmfd sp!, {r0-r12, pc}^ ;从堆栈中恢复r0~r12,返回地址赋给pc指针,使程序返回到异常发生前所执行的地方,^标记用来使CPU退出异常模式,进入普通状态。
3、 算术运算指令
基本指令:
ADD:加
SUB:减
语法:
op{cond }{S} Rd, Rn, Operand2
S:是否设置状态寄存器(CPSR),如:N(有符号运算结果得负数)、Z(结果得0)、C(运算的进位或移位)、V(有符号数的溢出)等等。
Rd:保存结果的寄存器
Rn:运算的第一个操作数
Operand2:运算的第二个操作数,这个操作数的值有一些限定:如可以是8位立即数(例:0xa8)或一个8为立即数的移位(例:0xa800,而0xa801就不符合)。也可以是寄存器,或寄存器的移位(如“r2, lsl #4”)。
例子:
add r0, r1, r2 ; r0 = r1 + r2
adds r0, r1, #0x80 ; r0 = r1 + 0x80,并设置状态寄存器
subs r0, r1, #2000 ; r0 = r1 – 2000,并设置状态寄存器
4、 逻辑运算指令
基本指令:
AND:与
ORR:或
EOR:异或
BIC:位清0
语法:
op{cond }{S} Rd, Rn, Operand2
语法类似算术运算指令
例子:
ands r0, r1, #0xff00 ; r0 = r1 and 0xff00,并设置状态寄存器
orr r0, r1, r2 ; r0 = r1 and r2
bics r0, r1, #0xff00 ; r0 = r1 and ! (0xff00)
ands r0, r1, #0xffff00ff ; 错误
5、 MOV指令
语法:
MOV{cond}{S} Rd, Operand2
例子:
mov r0, #8 ; r0 = 8
mov r0, r1 ; r0 = r1
不同于LDR、STR指令,该指令可以寄存器间赋值
6、 比较指令
基本指令:
CMP:比较两个操作数,并设置状态寄存器
语法:
CMP{cond } Rn, Operand2
例子:
cmp r0, r1 ; 计算r0 – r1,并设置状态寄存器,由状态寄存器可以知r0是否大于、小于或等于r1
cmp r0, #0 ;
7、 跳转指令
基本指令:
B:跳转
BL:跳转并将下一指令的地址存入lr寄存器
语法:
op{cond} label
label:要跳向的地址
例子:
loop1
~ ~ ~
b loop1 ; 跳到地址loop1处
bl sub1 ; 将下一指令地址写入lr,并跳至sub1
~ ~ ~
sub1
~ ~ ~
mov pc, lr ; 从sub1中返回
【使用本地label(local label)】
本地label可以在一个程序段内多次使用,用数字作为label的名称,也可以在数字后面跟一些字母。引用本地label的语法是: %{F|B}{A|T}n{routname},其中F代表向前搜索本地label,B代表向后搜索,A/T不常使用。
例子
100 ; 定义本地label,名称为“100”
~ ~ ~
100 ; 第二次定义本地label,名称为“100”
~ ~ ~
b %f100 ; 向前跳到最近的“100”处
~ ~ ~
b %b100 ; 向后跳至最近的“100”处
100 ; 第三次定义本地label 100
8、 条件执行
条件:状态寄存器中某一或某几个比特的值代表条件,对应不同的条件后缀cond,如:
后缀 (cond) 状态寄存器中的标记 意义
EQ Z = 1 相等
NE Z = 0 不相等
GE N和V相同 >=
LT N和V不同 <
GT Z = 0, 且N和V相同 >
LE Z = 1, 或N和V不同 <=
例子:
cmp r0, r1 ;比较r0和r1
blgt sub1 ;如果r0>r1,跳转到sub1,否则不操作
;――――――――――――――――――――
;一段循环代码
ldr r2, =8 ;r2 = 8
loop
;这里可以进行一些循环内的操作
subs r2, r2, #1 ;r2 = r2 –1,并设置状态位
bne loop ;如果r2不等于0,则继续循环
;――――――――――――――――――――
mov r0, #1 ; r0 = 1
cmp r2, #8 ; 比较r2和8
movlt r0, #2 ; 如果r2<8,r0 = 2
ARM汇编程序结构
;――――――――――――――――――――
AREA EX2, CODE, READONLY
;AREA指令定义一个程序段,名称为EX2,属性为:CODE、READONLY
INCLUDE Common.inc ;包含汇编头文件
IMPORT sub1 ;引用外部符号
EXPORT prog1 ;向外输出符号
ENTRY ;ENTRY指令定义程序的开始
start ;此处定义了一个label start
MOV r0, #10
MOV r1, #3
ADD r0, r0, r1 ;r0 =r0 +r1
prog1 ;此处定义了一个label prog1
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456
END ;END指令表示程序段的结束
;――――――――――――――――――――
宏的使用
定义宏:
MACRO ;宏的起始
{label} macroname para1,para2……
;代码
MEND ;宏结束
引用宏:
marconame para1,para2……
例子
;定义一个宏,完成两个寄存器内容交换
MACRO
swap $w1, $w2, $w3
mov $w3, $w1
mov $w1, $w2
mov $w2, $w3
MEND
;使用这个宏
ldr r0, =1
ldr r1, =2
swap r0, r1, r2 ;此处调用了宏swap,运行完后r0、r1的值交换了
一般可以把宏写在宏文件(.mac文件)中,在程序里用INCLUDE指令包含宏文件
最超值的ARM7/ARM9开发板系列
AVR单片机开发板与仿真器
本章节主要介绍ARM 处理器的基本程序设计方法,包含ARM 指令实验,Thumb 指令实验和ARM 处理器工作模式实验。
4.1 ARM 指令实验
4.1.1 实验说明
实验目的: 透过实验掌握ARM 组译指令的使用方法。
实验设备: 硬件使用PC 主机,软件使用Embest IDE 2003 整合开发环境,Windows 98/2000/NT/XP。
实验内容: 使用简单ARM 组译指令,操作寄存器和内存区作互相的数据交换。
4.1.2 实验原理
ARM 处理器共有37个寄存器:31个通用寄存器,包括程序计数器(PC)。这些寄存器都是32 位的。6个状态寄存器。这些寄存器也是32 位的,但是只是使用了其中的12 位。
ARM 通用寄存器
通用寄存器(R0~R15)可分为3 类:
不分组寄存器R0~R7;
分组寄存器R8~R14;
程序计数器R15。
1)不分组寄存器R0~R7
R0~R7 是不分组寄存器。这意味着在所有处理器模式下,它们都存取一样的32 位寄存器。它们是真正的通用寄存器,没有架构所隐含的特殊用途。
2)分组寄存器R8~R14
R8~R14 是分组寄存器。它们存取的物理寄存器取决于当前的处理器模式。若要存取特定的物理寄存器而不依赖当前的处理器模式,则要使用规定的各字。
寄存器R8~R12 各有两组物理寄存器:一组为FIQ 模式,另一组为除了FIQ以外的所有模式。寄存器R8~R12 没有任何指定的特殊用途。只是使用R8~R14来简单地处理中断。寄存器R13,R14 各有6 个分组的物理寄存器。1 个用于用户模式和系统模式,其它5 个分别用于5 种异常模式。寄存器R13 通常用做堆迭指标,称为SP。每种异常模式都有自己的R13。寄存器R14 用作子程序链接寄存器,也称为LR。
3) 程序计数器R15
寄存器R15 用做程序计数器(PC)。程序状态寄存器在所有处理器模式下都可以存取当前的程序状态寄存器CPSR。CPSR 包含条件码标志位,中断禁止位,当前处理器模式以及其它状态和控制信息。每种异常模式都有一个程序状态保存寄存器SPSR。当例外出现时,SPSR 用于保留CPSR的状态。
CPSR 和SPSR 的格式如下:
31
30
29
28
27
26 8
7
6
5
4
3
2
1
0
N
Z
C
V
Q
DNM(RAZ)
I
F
T
M
M
M
M
M
条件码标志:N,Z,C,V 大多数指令可以测试这些条件码标志以决定程序指令如何执行
控制位:最低8 位I,F,T 和M 位用做控制位。当异常出现时改变控制位。当处理器在特权模式下也可以由软件改变。
中断禁止位:I 置1 则禁止IRQ 中断。F 置1 则禁止FIQ 中断。T 位:T=0 指示ARM 执行。T=1 指示Thumb 执行。在这些架构系统中,可自由地使用能在ARM 和Thumb 状态之间切换的指令。
模式位:M0, M1, M2, M3 和M4 (M[4:0]) 是模式位.这些位
决定处理器的工作模式.如表2-1 所示。
表4-1 ARM 工作模式M[4:0]
M[4:0]
模式
可存取的寄存器
0b10000
用户模式
PC, R14~R0,CPSR
0b10001
FIQ模式
PC, R14_fiq~R8_fiq,R7~R0,CPSR,SPSR_fiq
0b10010
IRQ模式
PC, R14_irq~R8_fiq,R12~R0,CPSR,SPSR_irq
0b10011
管理模式
PC, R14_svc~R8_svc,R12~R0,CPSR,SPSR_svc
0b10111
中止
PC, R14_abt~R8_abt,R12~R0,CPSR,SPSR_abt
0b11011
未定义
PC, R14_und~R8_und,R12~R0,CPSR,SPSR_und
0b11111
系统
PC, R14~R0,CPSR
其它位程序状态寄存器的其它位保留,用作以后的扩充。
4.1.3. 实验操作步骤
1. 执行ADS1.2开发环境,打开实验系统例程目录下ARMcode_test 子目录下的ARMcode.mcp 工程文件。
2. 透过操作菜单栏或使用快捷命令编译链接项目。
3. 选择Debug 菜单Remote Connect 进行连接软件仿真器,执行Download命令下载程序,并打开寄存器窗口。
4. 单步执行程序并观察和记录寄存器R0-R15 的值变化。
5. 结合实验内容和相关数据,观察程序执行,透过实验加深理解ARM指令的使用。
4.1.4 试验程序代码
;本程序将数据区从数据区SRC复制到目标数据区DST。复制时,以8个字节为单位进行。对于
;最后所剩不足的8个字节的数据,以字为单位进行复制,这时程序跳转到copywords处执行。
;在进行以8个字为单位的数据复制时,保存了所有的8个工作寄存器。
;设置本段程序的名称(Block)及属性
AREA Block,CODE,READONLY
;设置将要复制的字数(定义变量num,并赋值为20)
num EQU 20
;程序入口标志
ENTRY
start
;r0寄存器指向源数据区(SRC 标识的地址放入R0)
LDR r0,=src
;r1寄存器指向目标数据区(DST 标识的地址放入R1)
LDR r1,=dst
;r2指定将要复制的字数(装载num 的值到R2)
MOV r2, #num
;设置数据栈指针(R13),用于保存工作寄存器数值(设定SP堆栈开始地址为0x400)
MOV sp, #0x400
;进行以8个字节为单位的数据复制
blockcopy
;需要进行的以8个字为单位的复制次数( R2 右移3 位后的值放入R3)
MOVS r3,r2, LSR #3
;对于剩下的不足8个字的数据,跳转到copywords,以字为单位复制(判断是否为0,为0 跳移)
BEQ copywords
;保存工作寄存器(把R4 到R11 的值保存到SP 标识的堆栈中)
STMFD sp!, {r4-r11}
octcopy
;从数据区读取8个字节的数据,放到8个寄存器中,并更新目标数据区指针r0(把R0 中的地址标识的内容顺序装载到R4 到R11 中)
LDMIA r0!, {r4-r11}
;将这8个字数据写入到目标数据区,并更新目标数据区指针r1(把R4 到R11 的值顺序保存到以R1 起始地址的内存中)
STMIA r1!, {r4-r11}
;将块的复制次数减1 (R3 -1 计数)
SUBS r3, r3, #1
;循环,直到完成以8个字为单位的块复制
BNE octcopy
;需要注意的是,LDMIA 或者STMIA 指令执行后,R0,R1 的值产生变化,每一次寄存器操作,R0 或者R1 的值会自动增加一个字节的量,这里操作了8 个寄存器,R0 或者R1 的值也相应增加了8 个字节
;恢复工作寄存器值(把刚才保存的SP 堆栈中的值恢复到R4 到R11 中)
LDMFD sp!, {r4-r11}
copywords
;剩下不足8个字的数据的字数(逻辑与,把R2 前7 位扔掉)
ANDS r2, r2, #7
;数据复制完成(判断是否为0,为0 跳移)
BEQ stop
wordcopy
;从源数据区读取18个字的数据,放到r3寄存器中,并更新目标数据区指针r0(把R0 表示地址的内容的后4 位全部拷贝到R3)
LDR r3, [r0], #4
;将这r3中数据写入到目标数据区中,并更新目标数据区指针r1 (把R3 的内容,放入以R1 为起始地址的4 位内存中)
STR r3, [r1], #4
;将字数减1;(R2 -1 放回R2)
SUBS r2, r2, #1
;循环,直到完成以字为单位的数据复制(判断是否为0,不为0 跳移,同样的,这里R0,R1 操作后,R0,R1 会自动加上便宜量)
BNE wordcopy
stop
;从应用程序中退出
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456
;定义数据区bloackdata
AREA Bloackdata, DATA, READWRITE
;定义源数据区src及目标数据区dst
src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;结束汇编
END
4.1.5 实验练习题
1. 撰写程序循环对R4~R11 进行8 次累加,R4~R11 起始值为1~8,每次加操作后把R4~R11 的内容放入SP 堆栈中,SP 初始设定为0x800。最后把R4~R11 用LDMFD 指令清空值为0。
2. 对于每一种ARM 的寻址方法,简短的写出相对的应用程序片段。
4.2 ARM 处理器工作模式实验
4.2.1. 实验说明
实验目的:透过实验掌握ARM 处理器工作模式的切换。
实验设备:软件需要ADS1.2开发环境,Windows 98/2000/NT/XP。
实验内容:透过ARM 组译指令,在各种处理器模式下切换并观察各种模式下缓存器的区别;掌握ARM 不同模式的进入与退出。
4.2.2. 实验原理
ARM 处理器模式
ARM 架构支持下表2-2 所列的7 种处理器模式。
工作模式
描述
用户模式(User,usr)
正常程序执行的模式
快速中断模式(FIQ,fig)
用于高速数据传输和数据处理
外部中断模式(IRQ,irq)
用于通常的中断处理
特权模式(管理模式)(Supervisor,sve)
共操作系统使用的一种模式
数据访问中止模式(Abort,abt)
用于虚拟存储及存储保护
未定义指令中断模式(Undefind,und)
用于支持通过软件方针的协处理器
系统模式(System,sys)
用于运行特权的操作系统任务
大多数应用程序在用户模式下执行。当处理器工作在用户模式时,正在执行的程序不能存取某些被保护的系统资源,也不能改变模式,除非例外(exception)发生。这允许适当撰写操作系统来控制系统资源的使用。
除用户模式外的其它模式称未特权模式。它们可以自由的存取系统资源和改变模式。其中的5 种称为异常模式,即
n FIQ(Fash Interrupt request);
n IRQ(Interrupt ReQuest);
n 管理(Supervisor);
n 中止(Abort) ;
n 未定义(Underfined) 。
当特定的异常出现时,进入相应的模式。每种模式都有某些附加的寄存器,以避免异常出现时用户模式的状态不可靠。
剩下的模式是系统模式。仅ARM 架构V4 以及以上的版本有该模式。不能由于任何异常而进入该模式。它与用户模式有完全相同的寄存器。然而它是特权模式,不受用户模式的限制。它供需要存取系统资源的操作系统工作使用,单希望避免使用与例外模式有关的附加寄存器。避免使用附加寄存器保证了当任何异常出现时,都不会使工作的状态不可靠。
4.2.3. 实验操作步骤
1.开发环境,打开实验系统例程目录下ARMMode_test/ARMMode.MCP 项目,并编译链接项目。
3. 单步执行程序并观察和记录CPSP 和SPSR 缓存器值的变化;并观察在相应模式下执行程序后对应缓存器值的变化。
4. 结合实验内容和相关数据,观察程序执行,透过实验加深理解和掌握
4.2.4 试验程序代码
;设置本段程序的名称(Block)及属性
AREA Block,CODE,READONLY
;程序入口标志
ENTRY
start
B Reset_Handler
Undefined_Handler
B Undefined_Handler
B SWI_Handler
Prefetch_Handler
B Prefetch_Handler
Abort_Handler
B Abort_Handler
NOP ;空操作
IRQ_Handler
B IRQ_Handler
FIQ_Handler
B FIQ_Handler
SWI_Handler
mov pc, lr
;前面部分是处理程序,主要处理各种模式的入端口跳移
Reset_Handler
;into System mode
MRS R0,CPSR ;复制CPSR 到R0
BIC R0,R0,#0x1F ;清除R0 的后5 位
ORR R0,R0,#0x1F ;设定R0 的最后5 位为11111
MSR CPSR_c,R0 ;把R0 装载到CPSR,切换到系统模式
MOV R0, #1 ;对系统模式下的R0 赋值,下面的R1~R15 一样
MOV R1, #2
MOV R2, #3
MOV R3, #4
MOV R4, #5
MOV R5, #6
MOV R6, #7
MOV R7, #8
MOV R8, #9
MOV R9, #10
MOV R10, #11
MOV R11, #12
MOV R12, #13
MOV R13, #14
MOV R14, #15
;into FIQ mode
MRS R0,CPSR
BIC R0,R0,#0x1F
ORR R0,R0,#0x11 ;设定R0 的最后5 位为10001
MSR CPSR_c,R0 ;把R0 装载到CPSR,切换到Fiq 模式
MOV R8, #16 ;给Fiq 模式的特有缓存器R8 赋值, 下面的R9~R14 一样
MOV R9, #17
MOV R10, #18
MOV R11, #19
MOV R12, #20
MOV R13, #21
MOV R14, #22
;into SVC mode
MRS R0,CPSR
BIC R0,R0,#0x1F
ORR R0,R0,#0x13 ;设定R0 的最后5 位为10011
MSR CPSR_c,R0 ;把R0 装载到CPSR,切换到Svc 模式
MOV R13, #23 ;给SVC 模式的特有缓存器R13 赋值, 下面的R14 一样
MOV R14, #24
;into Abort mode
MRS R0,CPSR
BIC R0,R0,#0x1F
ORR R0,R0,#0x17 ;设定R0 的最后5 位为10111
MSR CPSR_c,R0 ;把R0 装载到CPSR,切换到Abort 模式
MOV R13, #25 ;给Abort 模式的特有缓存器R13 赋值, 下面的R14 一样
MOV R14, #26
;into IRQ mode
MRS R0,CPSR
BIC R0,R0,#0x1F
ORR R0,R0,#0x12 ;设定R0 的最后5 位为10010
MSR CPSR_c,R0 ;把R0 装载到CPSR,切换到IRQ 模式
MOV R13, #27 ;给IRQ 模式的特有缓存器R13 赋值, 下面的R14一样
MOV R14, #28
;into UNDEF mode
MRS R0,CPSR
BIC R0,R0,#0x1F
ORR R0,R0,#0x1b ;设定R0 的最后5 位为11011
MSR CPSR_c,R0 ;把R0 装载到CPSR,切换到UNDEF 模式
MOV R13, #29 ;给UNDEF 模式的特有缓存器R13 赋值, 下面的R14 一样
MOV R14, #30
B Reset_Handler ;跳移到最开始地方循环
END
4.2.5. 实验练习题
1. 参考例子,把其中系统模式程序更改为使用者模式程序,编译除错,观察执行结果,检查是否正确,如果有有错误分析其原因。(提示:不能从使用者模式直接切换到其它模式,可以先使用SWI 指令切换到管理模式)。
山东大学嵌入式系统原理与接口技术课程试卷(A) 2007——2008 学年 1 学期
题号 一 二 三 四 五 六 七 八 九 十 总分
得分
得分 阅卷人
单项选择题(每空2分,共10分)
1、对寄存器R1的内容乘以4的正确指令是( )。
①LSR R1,#2 ②LSL R1,#2
③MOV R1,R1, LSL #2 ④MOV R1,R1, LSR #2
2、下面指令执行后,改变R1寄存器内容的指令是( )。
①TST R1,#2 ②ORR R1,R1,R1 ③CMP R1,#2 ④EOR R1,R1,R1
3、 MOV R1,#0x1000
LDR R0,[R1],#4
执行上述指令序列后,R1寄存器的值是( )。
①0x1000 ②0x1004 ③0x0FFC ④0x4
4、当进行数据写操作时,可能Cache未命中,根据Cache执行的操作不同,将Cache分为两类( )
①数据Cache和指令Cache ②统一Cache和独立Cache ③写通Cache和写回Cache ④读操作分配Cache和写操作分配Cache
5、一个异步传输过程:设每个字符对应8个信息位、偶校验、2个停止位,如果波特率为2400,那么每秒钟能传输的最大字符数为( )个。
① 200,② 218,③ 240,④ 2400
得分 阅卷人
二、填空(共18分)
1、嵌入式处理器可分为以下4类:( )。
2、ARM处理器总共有( )个寄存器,这些寄存器按其在用户编程中的功能可划分为:( )和( ),这些寄存器根据ARM处理器不同工作模式,可将全部寄存器分成( )组,在使用中有( )特点。
3、ARM 4种存储周期的基本类型分别为:( )。
4、S3C44B0X UART单元发送器能够检测的四种异步串行通信数据错误为( )。
得分 阅卷人
简答题:(每空6分,共30分)
1、从硬件系统来看,嵌入式系统由哪几部分组成?画出简图。
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
基于ARM的嵌入式系统程序开发要点(一)
—— 嵌入式程序开发过程
ARM 系列微处理器作为全球16/32位RISC处理器市场的领先者,在许多领
域内得到了成功的应用.近年来,ARM在国内的应用也得到了飞速的发展,越
来越多的公司和工程师在基于ARM的平台上面开发自己的产品.
与传统的4/8位单片机相比,ARM的性能和处理能力当然是遥遥领先的,但
与之相应,ARM的系统设计复杂度和难度,较之传统的设计方法也大大提升了.
本文旨在通过讨论系统程序设计中的几个基本方面,来说明基于ARM的嵌入式
系统程序开发的一些特点,并提出和解决了一些常见的问题.
文章分成几个相对独立的章节刊载.第一部分讨论基于ARM的嵌入式程序
开发和移植过程中的一些基本概念.
1.嵌入式程序开发过程
不同于通用计算机和工作站上的软件开发工程,一个嵌入式程序的开发过程
具有很多特点和不确定性.其中最重要的一点是软件跟硬件的紧密耦合特性.
(不带操作系统支持) (带操作系统支持)
图-1:两类不同的嵌入式系统结构模型
这是两类简化的嵌入式系统层次结构图.由于嵌入式系统的灵活性和多样
性,上面图中各个层次之间缺乏统一的标准,几乎每一个独立的系统都不一样.
这样就给上层的软件设计人员带来了极大地困难.第一,在软件设计过程中过多
地考虑硬件,给开发和调试都带来了很多不便;第二,如果所有的软件工作都需
要在硬件平台就绪之后进行,自然就延长了整个的系统开发周期.这些都是应该
从方法上加以改进和避免的问题.
为了解决这个问题,工程和设计人员提出了许多对策.首先在应用与驱动(或
API)这一层接口,可以设计成相对统一的一些接口函数,这对于具体的某一个
开发平台或在某个公司内部,是完全做得到的.这样一来,就大大提高了应用层
应用(Application)
驱动/板级支持包
(Driver/BSP)
硬件(Hardware)
应用(Application)
硬件抽象层(HAL)
硬件(Hardware)
操作系统(OS)
标准接口函数(API)
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
软件设计的标准化程度,方便了应用程序在跨平台之间的复用和移植.
对于驱动/硬件抽象这一层,因为直接驱动硬件,其标准化变得非常困难甚至
不太可能.但是为了简化程序的调试和缩短开发周期,我们可以在特定的EDA
工具环境下面进行开发,通过后再进行移植到硬件平台的工作.这样既可以保证
程序逻辑设计的正确性,同时使得软件开发可平行甚至超前于硬件开发进程.
我们把脱离于硬件的嵌入式软件开发阶段称之为"PC软件"的开发,可以
用下面的图来示意一个嵌入式系统程序的开发过程.
"PC软件"开发 移植,测试 产品发布
图-2:嵌入式系统产品的开发过程
在"PC软件"开发阶段,可以用软件仿真,即指令集模拟的方法,来对用
户程序进行验证.在ARM公司的开发工具中,ADS 内嵌的ARMulator和
RealView 开发工具中的ISS,都提供了这项功能.在模拟环境下,用户可以设
置ARM处理器的型号,时钟频率等,同时还可以配置存储器访问接口的时序参
数.程序在模拟环境下运行,不但能够进行程序的运行流程和逻辑测试,还能够
统计系统运行的时钟周期数,存储器访问周期数,处理器运行时的流水线状态(有
效周期,等待周期,连续和非连续访问周期)等信息.这些宝贵的信息是在硬件
调试阶段都无法取得的,对于程序的性能评估非常有价值.
为了更加完整和真实地模拟一个目标系统,ARMulator和ISS还提供了一个
开放的API编程环境.用户可以用标准C来描述各种各样的硬件模块,连同工
具提供的内核模块一起,组成一个完整的"软"硬件环境.在这个环境下面开发
的软件,可以更大程度地接近最终的目标.
利用这种先进的EDA工具环境,极大地方便了程序开发人员进行嵌入式开
发的工作.当完成一个"PC软件"的开发之后,只要进行正确的移植,一个真
正的嵌入式软件就开发成功了.而移植过程是相对比较容易形成一套规范的流程
的,其中三个最重要的方面是:
考虑硬件对库函数的支持
移植
移植
开发/实验/
测试平台
最终产品
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
符合目标系统上的存储器资源分布
应用程序运行环境的初始化
2.开发工具环境里面的库函数
如果用户程序里调用了跟目标相关的一些库函数,则在应用前需要裁剪这些
函数以适合在目标上允许的要求.主要需要考虑以下三类函数:
访问静态数据的函数
访问目标存储器的函数
使用semihosting(半主机)机制实现的函数
这里所指的C库函数,除了ISO C标准里面定义的函数以外,还包括由编
译工具提供的另外一些扩展函数和编译辅助函数.
2.1 裁剪访问静态数据的函数
库函数里面的静态数据,基本上都是在头文件里面加以定义的.比如CTYPE
类库函数,其返回值都是通过预定义好的CTYPE属性表来获得的.比如,想要
改变isalpha() 函数的缺省判断,则需要修改对应CTYPE属性表里对字符属性的
定义.
2.2 裁减访问目标存储器的函数
有一类动态内存管理函数,如malloc() 等,其本身是独立于目标系统而运行
的;但是它所使用的存储器空间需要根据目标来确定.所以malloc() 函数本身
并不需要裁剪或移植,但那些设置动态内存区(地址和空间)的函数则是跟目标
系统的存储器分布直接相关的,需要进行移植.例如堆栈的初始化函数
__user_initial_stackheap(),是用来设置堆(heap)和栈(stack)地址的函数,显
然针对每一个具体的目标平台,该函数都需要根据具体的目标存储器资源进行正
确移植.
下面是对示例函数__user_initial_stackheap() 进行移植的一个例子:
__value_in_regs struct __initial_stackheap __user_initial_stackheap(
unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
struct __initial_stackheap config;
config.heap_base = (unsigned int) 0x11110000;
// config.stack_base = SP; // optional
return config;
}
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
请注意上面的函数体并不完全遵循标准C的关键字和语法规范,使用了ARM
公司编译器(ADS 或RealView Compilation tool) 里的C语言扩展特性.关于编译
器特定的C语言扩展,请参考相关的编译器说明,这里简单介绍函数
__user_initial_stackheap() 的功能,它主要是返回堆和栈的基地址.上面的程序中
只对堆(heap) 的基地址进行了设置(设成了0x11110000),也就是说用户把
0x11110000开始的存储器地址用作了动态内存分配区(heap区).具体地址的确
定是要由用户根据自己的目标系统和应用情况来确定的,至少要满足以下条件:
0x11110000开始的地址空间有效且可写(是RAM)
该存储器空间不与其它功能区冲突(比如代码区,数据区,stack区等)
因为__user_initial_stackheap() 函数的全部执行效果就是返回一些数值,所
以只要符合接口的调用标准,直接用汇编来实现看起来更加直观一些:
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR r0,0x11110000
MOV pc,lr
如果不对这个函数进行移植,编译过程中将使用缺省的设置,这个设置适用
于ARM公司的Integrator系列平台.
(注意:ARM的编译/连接工具链也提供了绕过库函数来设置运行时存储器模型
的方法,请参阅ARM公司其他的相关文档.)
2.3 裁剪使用semihosting(半主机)机制实现的函数
库函数里有一大部分函数是涉及到输入/输出流设备的,比如文件操作函数需
要访问磁盘I/O,打印函数需要访问字符输出设备等.在嵌入式调试环境下,所
有的标准C库函数都是有效且有其缺省行为的,很多目标系统硬件不能支持的
操作,都通过调试工具来完成了.比如printf() 函数,缺省的输出设备是调试器
里面的信息输出窗口.
但是一个真实的系统是需要脱离调试工具而独立运行的,所以在程序的移植
过程当中,需先对这些库函数的运行机制作一了解.
下图说明了在ADS下面这类C库函数的结构.
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
图-3:C库函数实现过程中的层次调用
如图中例子所示,函数printf() 最终是调用了底层的输入/输出函数
_sys_write() 来实现输出操作的,而_sys_write() 使用了调试工具的内部机制来把
信息输出到调试器.
显然这样的函数调用过程在一个真实的嵌入式系统里是无法实现的,因为独
立运行的嵌入式系统将不会有调试器的参与.如果在最终系统中仍然要保留
printf() 函数,而且在系统硬件中具备正确的输出设备(如LCD等),则在移植
过程中,需要把printf() 调用的输出设备进行重新定向.
考察printf() 函数的完整调用过程:
图-4:printf() 的调用过程
单纯考虑printf() 的输出重新定向,可以有三种途径实现:
改写printf() 本身
改写 fput()
改写 _sys_write()
需要注意的是,越底层的函数,被其他上层函数调用的可能性越大,改变了
一个底层函数的实现,则所有调用该函数的上层函数的行为都被改变了.
以fputc() 的重新实现为例,下面是改变fputc() 输出设备到系统串行通信端
口的实例:
int fputc(int ch, FILE *f)
ANSI C
Input/
output
Error
handling
Stack &
heap setup
Other
Semihosting Support
应用程序调用的
函数,如printf()
设备驱动程序级
使用semihosting
机制
如_sys_write()
由调试系统执行
printf() fput() _sys_wite()输出设备
其他函数 其他函数
C 库函数
调试辅助环境
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
{ /* e.g. write a character to an UART */
char tempch = ch;
sendchar(&tempch); // UART driver
return ch;
}
代码中的函数sendchar() 假定是系统的串口设备驱动函数.只要新建函数
fput() 的接口符合标准,经过编译连接后,该函数实现就覆盖了原来缺省的函数
体,所有对该函数的调用,其行为都被新实现的函数所重新定向了.
具体哪些库函数是跟目标相关的,这些函数之间的相互调用关系等,请参考
具体的编译器说明.
3.Semihosting (半主机) 机制
上面提到许多库函数在调试环境下的实现都调用了一种叫semihosting的机
制.Semihosting具体来讲是指一种让代码在ARM 目标上运行,但使用运行了
ARM 调试器的主机上I/O 设备的方法;也就是让ARM 目标将输入/ 输出请求
从应用程序代码传递到运行调试器的主机的一种机制.通常这些输入/输出设备
包括键盘,屏幕和磁盘I/O.
半主机由一组已定义的SWI 操作来实现.库函数调用相应的SWI(软件中
断),然后调试代理程序处理SWI 异常,并提供所需的与主机之间的通讯.
图-5:Semihosting的实现过程
多数情况下,半主机SWI 是由库函数内的代码调用的.但是应用程序也可
以直接调用半主机SWI.半主机SWI 的接口函数是通用的.当半主机操作在硬
件仿真器,指令集仿真器,RealMonitor或Angel下执行时,不需要进行移植处
理.
使用单个SWI 编号请求半主机操作.其它的SWI 编号可供应用程序或操
printf()
printf("Hello world! ");
SWI
调试器
Hello world!
C 库代码
应用程序代码
与运行在主机上的调试器通信
主机屏幕上显示的文本
目标
主机
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
作系统使用.用于半主机的SWI号是:
在ARM 状态下:0x123456
在Thumb 状态下:0xAB
SWI 编号向调试代理程序指示该SWI 请求是半主机请求.要辨别具体的操
作类型,用寄存器r0 作为参数传递.r0 传递的可用半主机操作编号分配如下:
0x00-0x31:这些编号由ARM 公司使用,分别对应32个具体的执行函
数.
0x32-0xFF:这些编号由ARM 公司保留,以备将来用作函数扩展.
0x100-0x1FF:这些编号保留给用户应用程序.但是,如果编写自己的
SWI 操作,建议直接使用SWI指令和SWI编号,而不要使用半主机
SWI 编号加这些操作类型编号的方法.
0x200-0xFFFFFFFF:这些编号未定义.当前未使用并且不推荐使用这
些编号.
半主机SWI使用的软件中断编号也可以由用户自定义,但若是改变了缺省
的软中断编号,需要:
更改系统中所有代码(包括库代码)的半主机SWI 调用
重新配置调试器对半主机请求的捕捉与相应
这样才能使用新的SWI 编号.
有关半主机SWI处理函数实现的更详细信息,请参考ARM编译器的相关
文档.
4.应用环境的初始化和根据目标系统资源进行的移植
在下一期中介绍应用环境和目标系统的初始化.
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
基于ARM的嵌入式系统程序开发要点(二)
—— 系统的初始化过程
基于ARM的芯片多数为复杂的片上系统集成(SoC),这种复杂的系统里多
数的硬件模块都是可配置的,需要由软件来设置其需要的工作状态.因此在用户
的应用程序启动之前,需要有专门的一段启动代码来完成对系统的初始化.由于
这类代码直接面对处理器内核和硬件控制器进行编程,一般都使用汇编语言.系
统启动程序所执行的操作跟具体的目标系统和开发系统相关,一般通用的内容包
括:
中断向量表
初始化存储器系统
初始化堆栈
初始化有特殊要求的端口,设备
初始化应用程序执行环境
改变处理器模式
呼叫主应用程序
1.中断向量表
ARM要求中断向量表必须放置在从0地址开始,连续8×4字节的空间内
(ARM720T和ARM9/10及以后的ARM处理器也支持从0xFFFF0000开始的高
地址向量表,在本文的其他地方对此不再另加说明).各个中断矢量在向量表中
的位置分配如下图:
图1:中断向量表
每当一个中断发生以后,ARM处理器便强制把PC指针置为向量表中对应中
Reset 复位中断 0x00
Undef 未定义指令中断 0x04
Software Interrupt 软件中断 0x08
Prefetch Abort 指令预取异常 0x0C
Data Abort 数据异常 0x10
(Reserved) 保留 0x14
IRQ 普通外部中断 0x18
FIQ 外部快速中断 0x1C
… …
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
断类型的地址值.因为每个中断只占据向量表中1个字的存储器空间,只能放置
1条ARM指令,所以通常在向量表中放的是跳转指令,使程序能从向量表里跳
转到存储器里的其他地方,再执行中断处理.
中断向量表的程序实现通常如下所示:
AREA Boot, CODE, READONLY
ENTRY
B Reset_Handler ; Reset_Handler is a label
B Undef_Handler
B SWI_Handler
B PreAbort_Handler
B DataAbort_Handler
B . ; for reserved interrupt, stop here
B IRQ_Handler
B FIQ_Handler
其中的关键字ENTRY是指定编译器保留这段代码,因为编译器可能会认为
这是一段冗余代码而加以优化.连接的时候要确保这段代码被链接在0地址处,
并且作为整个程序的入口点(关键字ENTRY并非总是用来设置程序入口点,所
以通常需要在连接选项里显式地指定程序入口点).
2.初始化存储器系统
初始化存储器系统的编程对象是系统的存储器控制器.存储器控制器并不是
ARM内核的一部分,不同的系统其设计不尽相同,所以应该针对具体的要求来
完成这部分的程序设计.一般来说,下面这两个方面是比较通用的.
2.1.存储器类型和时序配置
一个复杂的系统可能存在多种存储器类型的接口,需要根据实际的系统设计
对此加以正确配置.对同一种存储器类型来说,也因为访问速度的差异,需要不
同的时序设置.
通常Flash 和SRAM同属于静态存储器类型,可以合用同一个存储器端口;
而DRAM 因为动态刷新和地址线复用等特性,通常配有专用的存储器端口.
存储器端口的接口时序优化是非常重要的,影响到整个系统的性能.因为一
般系统运行的速度瓶颈都存在于存储器访问,所以存储器访问时序应尽可能地
快;但同时又要考虑由此带来的稳定性问题.只有根据具体选定的芯片,进行多
次的测试之后,才能确定最佳的时序配置.
2.2.存储器地址分布(memory map)
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
有些系统具有非常灵活的存储器地址分配特性,进行存储器初始化设计的时
候一定要根据应用程序的具体要求来完成地址分配.
一种典型的情况是启动ROM的地址重映射(remap).如前面第1节所述,
当一个系统上电后程序将自动从0地址处开始执行,因此在系统的初始状态,必
须保证在0地址处存在正确的代码,即要求0地址开始处的存储器是非易性的
ROM或Flash等.但是因为ROM或Flash的访问速度相对较慢,每次中断发生
后都要从读取ROM或Flash上面的向量表开始,影响了中断响应速度.因此有
的系统便提供一种灵活的地址重映射方法,可以把0地址重新指向到RAM中去.
在这种地址映射的变化过程当中,程序员需要仔细考虑的是程序的执行流程不能
被这种变化所打断.比如下面这种情况:
图2:启动ROM的地址重映射对程序执行流程的影响
系统上电后从Flash内的0地址开始执行,启动代码位于地址0x100开始的
空间,当执行到地址0x200时,完成了一次地址的重映射,把原来0开始的地址
空间由Flash转给了RAM.接下去执行的指令(这里为了简化起见,忽略流水
线指令预取的模型)将来自从0x204开始的RAM空间.如果预先没有对RAM
内容进行正确的设置,则里面的数据都是随机的,这样处理器在执行完0x200
地址处的指令之后,再往下取指执行就会出错.解决的方法就是要使RAM在使
用之前准备好正确的内容,包括开头的向量表部分.
有的系统不具备存储器地址重映射的功能,所有的空间地址就相对简单一
些,不需要考虑这方面的问题.
3.初始化堆栈
因为ARM处理器有7种执行状态,每一种状态的堆栈指针寄存器(SP)都
Flash
0x0100
(Reset_Handler)
B Reset_Handler
… …
.
.
.
(boot code)
.
.
(remap)
0x0000
0x0200
RAM
0x0200
remap 0x0204
Vector Table
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
是独立的(System和User模式使用相同的SP寄存器).因此对程序中需要用到
的每一种模式都要给SP寄存器定义一个堆栈地址.方法是改变状态寄存器CPSR
内的状态位,使处理器切换到不同的状态,然后给SP赋值.注意不要切换到User
模式进行User模式的堆栈设置,因为进入User模式后就不能再操作CPSR回到
别的模式了.可能会对接下去的程序执行造成影响.
一般堆栈的大小要根据需要而定,但是要尽可能给堆栈分配快速和高带宽的
存储器.堆栈性能的提高对系统整体性能的影响是非常明显的.
这是一段堆栈初始化的代码示例,其中只定义了三种模式的SP指针:
MRS R0, CPSR ; CPSR -> R0
BIC R0, R0, #MODEMASK ; 安全起见,屏蔽模式位以外的其它位
ORR R1, R0, #IRQMODE ; 把设置模式位设置成需要的模式
MSR CPSR_cxsf, R1 ; 转到IRQ模式
LDR SP, =UndefStack ; 设置 SP_irq
ORR R1,R0,#FIQMODE
MSR CPSR_cxsf, R1 ; FIQMode
LDR SP, =FIQStack
ORR R1, R0, #SVCMODE
MSR CPSR_cxsf, R1 ; SVCMode
LDR SP, =SVCStack
注意上面的程序中使用到的3个SP寄存器是不同的物理寄存器:SP_irq,
SP_fiq和SP_svc.引用的几个标号假设已经正确定义.
4.初始化有特殊要求的端口,设备
这要由具体的系统和用户需求而定.一般的外设初始化可以在系统初始化之
后进行.
比较典型的应用是驱动一些简单的输出设备,如LED等,来指示系统启动
的进程和状态.
5.初始化应用程序执行环境
一个简单的可执行程序的映像结构通常如下:
基于ARM的嵌入式程序开发要点
ARM-CHINA-040415A
图3:程序映像的结构
映像一开始总是存储在ROM/Flash里面的,其RO部分既可以在ROM/Flash
里面执行,也可以转移到速度更快的RAM中去;而RW和ZI这两部分必须是
需要转移到可写的RAM里去的.所谓应用程序执行环境的初始化,就是完成必
要的从ROM到RAM的数据传输和内容清零.
不同的工具链会提供一些不同的机制和方法帮助用户完成这一步操作,主要
是跟链接器(Linker)相关.下面是在ARM开发工具环境(ADS或RVCT)下,
一种常用存储器模型的直接实现:
LDR r0, =|Image
用并且不推荐使用这
些编号。
半主机SWI 使用的软件中断编号也可以由用户自定义,但若是改变了缺省
的软中断编号,需要:
?? 更改系统中所有代码(包括库代码)的半主机SWI 调用
?? 重新配置调试器对半主机请求的捕捉与相应
这样才能使用新的SWI 编号。
有关半主机SWI 处理函数实现的更详细信息,请参考ARM 编译器的相关
文档。
4.应用环境的初始化和根据目标系统资源进行的移植
在下一期中介绍应用环境和目标系统的初始化。
基于s3c2410软中断服务的uC/OS-II任务切换
1.关于软中断指令
软件中断指令(SWI)可以产生一个软件中断异常,这为应用程序调用系统例程提供了一种机制。
语法:
SWI {<cond>} SWI_number
SWI执行后的寄存器变化:
lr_svc = SWI指令后面的指令地址
spsr_svc = cpsr
pc = vectors + 0x08
cpsr模式 = SVC
cpsr I = 1(屏蔽IRQ中断)
处理器执行SWI指令时,设置程序计数器pc为向量表的0x08偏移处,同事强制切换处理器模式到SVC模式,以便操作系统例程可以在特权模式下被调用。
每个SWI指令有一个关联的SWI号(number),用于表示一个特定的功能调用或特性。
【例子】 一个ARM工具箱中用于调试SWI的例子,是一个SWI号为0x123456的SWI调用。通常SWI指令是在用户模式下执行的。
SWI执行前:
cpsr = nzcVqift_USER
pc = 0x00008000
lr = 0x003fffff ;lr = 4
r0 = 0x12
执行指令:
0x00008000 SWI 0x123456
SWI执行后:
cpsr = nzcVqIft_SVC
spsr = nzcVqift_USER
pc = 0x00000008
lr = 0x00008004
r0 = 0x12
SWI用于调用操作系统的例程,通常需要传递一些参数,这可以通过寄存器来完成。在上面的例子中,r0
用于传递参数0x12,返回值也通过寄存器来传递。
处理软件中断调用的代码段称为中断处理程序(SWI Handler)。中断处理程序通过执行指令的地址获取软件中断号,指令地址是从lr计算出来的。
SWI号由下式决定:
SWI_number = <SWI instruction> AND NOT<0xff000000>
其中SWI instruction就是实际处理器执行的32位SWI指令
SWI指令编码为:
31 - 28 27 - 24 23 - 0
cond 1 1 1 1 immed24
指令的二进制代码的bit23-bit0是24bit的立即数,即SWI指令的中断号,通过屏蔽高8bit即可获得中断号。lr寄存器保存的是中断返回指令的地址,所以 [lr - 4] 就是执行SWI的执行代码。通过load指令拷贝整个SWI指令到寄存器,使用BIC屏蔽指令的高8位,获取SWI中断号。
;read the SWI instruction
LDR r10, [lr, #-4]
BIC r10, r10, #0xff000000
2. 周立功移植uC/OS-II到s3c2410的软中断服务级的任务切换
uC/OS-II的任务调度函数
uC/OS-II的任务级的调度是由函数OS_Sched( )完成的。
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT8U y;
OS_ENTER_CRITICAL();
if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked */
y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
OS_EXIT_CRITICAL();
}
详细解释可以参考《嵌入式实时操作系统 uC/OS-II》,os_sched函数在确定所有就绪任务的最高优先级高于当前任务优先级时进行任务切换,通过OS_TASK_SW( )宏来调用。
OS_TASK_SW( )宏实际上定义的是SWI软中断指令。见OS_CPU.H文件的代码:
__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */
__swi(0x01) void _OSStartHighRdy(void); /* 运行优先级最高的任务 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断 */
__swi(0x40) void *GetOSFunctionAddr(int Index); /* 获取系统服务函数入口 */
__swi(0x41) void *GetUsrFunctionAddr(int Index);/* 获取自定义服务函数入口 */
__swi(0x42) void OSISRBegin(void); /* 中断开始处理 */
__swi(0x43) int OSISRNeedSwap(void); /* 判断中断是否需要切换 */
__swi(0x80) void ChangeToSYSMode(void); /* 任务切换到系统模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任务切换到用户模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任务代码是ARM代码 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任务代码是THUMB */
__swi(0x00) void OS_TASK_SW(void); 是与ADS相关的代码,通过反汇编可以看到,调用OS_TASK_SW实际上被替换成swi 0x00 软中断指令。执行此执行,pc会跳转到向量表的0x08偏移处。
中断向量表:(见Startup.s文件)
CODE32
AREA vectors,CODE,READONLY
; 异常向量表
Reset
LDR PC, ResetAddr
LDR PC, UndefinedAddr
LDR PC, SWI_Addr
LDR PC, PrefetchAddr
LDR PC, DataAbortAddr
DCD IRQ_Addr
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr
ResetAddr DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr DCD SoftwareInterrupt
PrefetchAddr DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse DCD 0
IRQ_Addr DCD IRQ_Handler
FIQ_Addr DCD FIQ_Handler
执行SWI 0x00指令后,pc会跳转到SoftwareInterrupt代码处开始执行:
见Os_cpu_a.s文件的SoftwareInterrupt函数:
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针
STMFD {R0-R3, R12, LR}
MOV R1, SP ; R1指向参数存储位置
MRS R3, SPSR
TST R3, #T_bit ; 中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI指令
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI指令
BICEQ R0, R0, #0xFF000000 ; 如上面所述,此处通过屏蔽SWI指令的高8位来获取SWI号,r0 = SWI号,R1指向参数存储位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw ;为0时跳转到OSIntCtxSwdi地址处
LDREQ PC, =__OSStartHighRdy ; 为1时,跳转到__OSStartHighRdy地址处。SWI 0x01为第一次任务切换
BL SWI_Exception ;进入中断号散转函数
LDMFD {R0-R3, R12, PC}^
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)
以上就是任务切换软中断级服务的实现。
浅析arm汇编中指令使用学习
本帖被 smd801124 设置为精华(2008-05-08)
macro restore_user_regs
ldr r1,[sp, #S_PSR]
ldr lr,[sp, #S_PC]! @ !用来控制基址变址寻址的最终新地址是否进行回写操作,
@ 执行ldr之后sp被回写成sp+#S_PC基址变址寻址的新地址
msr spsr,r1 @ 把cpsr的值保存到spsr中
ldmdb sp,{r0 - lr}^ @ lr=[sp-1*4],r13=[sp-2*4],r12=[sp-3*4],......,r0=[sp-15*4]
@ 因为没对pc赋值,所以^的表示将数据恢复到User模式的[r0-lr]寄存器组中[gliethttp]
mov r0,r0
add sp,sp,#S_FRAME_SIZE - S_PC
movs pc,lr
.endm
其他指令正在学习中[随时补充gliethttp]
-----------------------------
1.ldr ip,[sp],#4 将sp中内容存入ip,之后sp=sp+4;
ldr ip,[sp,#4] 将sp+4这个新地址下内容存入ip,之后sp值保持不变
ldr ip,[sp,#4]!将sp+4这个新地址下内容存入ip,之后sp=sp+4将新地址值赋给sp
str ip,[sp],#4 将ip存入sp地址处,之后sp=sp+4;
str ip,[sp,#4] 将ip存入sp+4这个新地址,之后sp值保持不变
str ip,[sp,#4]!将ip存入sp+4这个新地址,之后sp=sp+4将新地址值赋给sp
-----------------------------
2.movs r1,#3 ;movs将导致ALU被更改,因为r1赋值非0,即操作结果r0非0,所以ALU的Z标志清0
bne 1f ;因为Z=0,说明不等,所以向前跳到标号1:所在处继续执行其他语句
-----------------------------
3.LDM表示装载,STM表示存储.
LDMED LDMIB 预先增加装载
LDMFD LDMIA 过后增加装载
LDMEA LDMDB 预先减少装载
LDMFA LDMDA 过后减少装载
STMFA STMIB 预先增加存储
STMEA STMIA 过后增加存储
STMFD STMDB 预先减少存储
STMED STMDA 过后减少存储
注意ED不同于IB;只对于预先减少装是相同的.在存储的时候,ED是过后减少的.
FD、ED、FA、和 EA 指定是满栈还是空栈,是升序栈还是降序栈.
对于存储STM而言
先加后存 FA 姑且这么来记,先加(first add),存数据
后加先存 EA 姑且这么来记,存数据,后加end add
先减后存 FD 姑且这么来记,先减first dec,存数据
后减先存 ED 姑且这么来记,存数据,后减end dec
然后记忆LDM,LDM是STM的反相弹出动作,所以
因为是先加后存,所以后减先取 FA 就成了与STM对应的取数据,后减
因为是后加先存,所以先减后取 EA 就成了与STM对应的先减,取数据
因为是先减后存,所以后加先取 FD 就成了与STM对应的取数据,后加
因为是后减先存,所以先加后取 ED 就成了与STM对应的先加,取数据
我想通过上面的变态方式可以比较容易的记住这套指令[gliethttp]
一个满栈的栈指针指向上次写的最后一个数据单元,而空栈的栈指针指向第一个空闲单元.
一个降序栈是在内存中反向增长(就是说,从应用程序空间结束处开始反向增长)而升序栈在内存中正向增长.
其他形式简单的描述指令的行为,意思分别是
IA过后增加(Increment After)、
IB预先增加(Increment Before)、
DA过后减少(Decrement After)、
DB预先减少(Decrement Before).
RISC OS使用传统的满降序栈.在使用符合APCS规定的编译器的时候,它通常把你的栈指针设置在应用程序空间的
结束处并接着使用一个FD(满降序-Full Descending)栈.如果你与一个高级语言(BASIC或C)一起工作,你将别无选择.
栈指针(传统上是R13)指向一个满降序栈.你必须继续这个格式,或则建立并管理你自己的栈.
4.
teq r1,#0 //r1-0,将结果送入状态标志,如果r1和0相减的结果为0,那么ALU的Z置位,否则Z清0
bne reschedule//ne表示Z非0,即:不等,那么执行reschedule函数
-----------------------------
5.使用tst来检查是否设置了特定的位
tst r1,#0x80 //按位and操作,检测r1的0x1<<7,即第7位是否置1,按位与之后结果为0,那么ALU的Z置位
beq reset //如果Z置位,即:以上按位与操作结果是0,那么跳转到reset标号执行
-----------------------------
6.'^'的理解
'^'是一个后缀标志,不能在User模式和Sys系统模式下使用该标志.该标志有两个存在目的:
6.1.对于LDM操作,同时恢复的寄存器中含有pc(r15)寄存器,那么指令执行的同时cpu自动将spsr拷贝到cpsr中
如:在IRQ中断返回代码中[如下为ads环境下的代码gliethttp]
ldmfd {r4} //读取sp中保存的的spsr值到r4中
msr spsr_cxsf,r4 //对spsr的所有控制为进行写操作,将r4的值全部注入spsr
ldmfd {r0-r12,lr,pc}^//当指令执行完毕,pc跳转之前,将spsr的值自动拷贝到cpsr中[gliethttp]
6.2.数据的送入、送出发生在User用户模式下的寄存器,而非当前模式寄存器
如:ldmdb sp,{r0 - lr}^;表示sp栈中的数据回复到User分组寄存器r0-lr中,而不是恢复到当前模式寄存器r0-lr 当然对于User,System,IRQ,SVC,Abort,Undefined这6种模式来说[gliethttp]r0-r12是共用的,只是r13和r14
为分别独有,对于FIQ模式,仅仅r0-r7是和前6中模式的r0-r7共用,r8-r14都是FIQ模式下专有.
7.spsr_cxsf,cpsr_cxsf的理解
c - control field mask byte(PSR[7:0])
x - extension field mask byte(PSR[15:8])
s - status field mask byte(PSR[23:16)
f - flags field mask byte(PSR[31:24]).
老式声明方式:cpsr_flg,cpsr_all在ADS中已经不在支持
cpsr_flg对应cpsr_f
cpsr_all对应cpsr_cxsf
需要使用专用指令对cpsr和spsr操作:mrs,msr
mrs tmp,cpsr //读取CPSR的值
bic tmp,tmp,#0x80 //如果第7位为1,将其清0
msr cpsr_c,tmp //对控制位区psr[7:0]进行写操作
-----------------------------
8.cpsr的理解
CPSR = Current Program Status Register
SPSR = Saved Program Status Registers
CPSR寄存器(和保存它的SPSR寄存器)
本文来自: 电子论坛[url]http://www.eehome.cn[/url]电子工程师之家!
ARM的堆栈学习笔记
来源:http://www.hzlitai.com.cn/ 作者:蓝石头
字体大小:[大][中][小]
以下是我在学习ARM指令中记录的关于堆栈方面的知识:
1、寄存器 R13 在 ARM 指令中常用作堆栈指针
2、对于 R13 寄存器来说,它对应6个不同的物理寄存器,其中的一个是用户模式与系统模式共用,另外5个物理寄存器对应于其他5种不同的运行模式。采用以下的记号来区分不同的物理寄存器: R13_<mode> 其中,mode为以下几种模式之一:usr、fiq、irq、svc、abt、und。
3、寄存器R13在ARM指令中常用作堆栈指针,但这只是一种习惯用法,用户也可使用其他的寄存器作为堆栈指针。而在Thumb指令集中,某些指令强制性的要求使用R13作为堆栈指针。由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。
4、有四种类型的堆栈:
堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。
当堆栈指针指向最后压入堆栈的数据时,称为满堆栈(Full Stack),而当堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈(Empty Stack)。
同时,根据堆栈的生成方式,又可以分为递增堆栈(Ascending Stack)和递减堆栈(DecendingStack),当堆栈由低地址向高地址生成时,称为递增堆栈,当堆栈由高地址向低地址生成时,称为递减堆栈。这样就有四种类型的堆栈工作方式,ARM 微处理器支持这四种类型的堆栈工作方式,即: ◎ Full descending 满递减堆栈堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。 ARM-Thumb过程调用标准和ARM、Thumb C/C++ 编译器总是使用Full descending 类型堆栈。
◎ Full ascending 满递增堆栈堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。
◎ Empty descending 空递减堆栈堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向下一个将要放入数据的空位置。
◎ Empty ascending 空递增堆栈堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向下一个将要放入数据的空位置。
5、操作堆栈的汇编指令堆栈类型 入栈指令 出栈指令 Full descending STMFD (STMDB) LDMFD (LDMIA) Full ascending STMFA (STMIB) LDMFA (LDMDA) Empty descending STMED (STMDA) LDMED (LDMIB) Empty ascending STMEA (STMIA) LDMEA (LDMDB)
例子: STMFD r13!, {r0-r5} ; Push onto a Full Descending Stack LDMFD r13!, {r0-r5} ; Pop from a Full Descending Stack.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。