分类:
2008-07-27 15:55:40
所谓裸机在这里主要是指系统软件平台没有用到操作系统。在基于ARM处理器平台的软件设计中,如果整个系统只需要完成一个相对简单而且独立的任务,那么可以不使用操作系统,只需要考虑在平台上如何正确地执行这个单任务程序。不过,在这种方式下同样需要一个Boot Loader,这个时候的Boot Loader一般是自己写的一个简单的启动代码加载程序。大家所熟悉的各种Boot Loader下的设备驱动,其实就是很好的裸机驱动程序。比如说U-Boot下的网卡驱动、串口驱动、LCD驱动等。
在裸机方式下,ARM的软件集成开发环境就显得极为重要,因为在这种方式下可以把所有代码都放在这个环境里面编写、编译和调试。在这种方式下测试驱动程序,首先要完成CPU的初始化,然后把需要测试的程序装载到系统的RAM区/或者SDRAM中。当然,如果需要处理一些复杂的中断处理的话,最好也把CPU的复位向量表放到RAM区中。把所有程序都调试好之后,再把最后的程序烧写到Flash里面去执行。
所谓复位向量表,其实就是一些跳转指令表,这些跳转指令就是针对ARM处理器的多种异常处理的,有关处理器的工作模式及异常处理的介绍,读者可以回顾本书1.4的介绍。复位向量表通常是放在CPU复位执行的第一块内存地址空间中,一般来说都是0x00000000这个地址。
在32位ARM系统中,都是在中断向量表中放置一条分支指令或PC寄存器加载指令,来实现程序跳转到中断服务例程的功能。例如:
IRQEntry B HandleIRQ ;跳转范围较小一般在64KB之内
B HandleFIQ ;
或
IRQEntry LDR PC,HandleIRQ ;跳转的范围是任意32位地址空间
LDR PC,HandleFIQ
LDR伪指令等效于生成一条存储读取指令和一条32位常数定义指令。32位常数存储在LDR指令附近的存储单元中,相对偏移小于4KB。该32位数据就是要跳转到中断服务程序的入口地址。之所以使用LDR伪指令,是因为ARM的RISC指令为单字指令,不能装载32位的立即数(常数),无法直接把一个32位常数数据或地址数据装载到寄存器中。下面一段程序与上述伪指令功能等效,但中断向量表描述得更为清晰。其中VectorTable为相对LDR指令的偏移量。
IRQEntry LDR PC,VectorTable+0 ;与LDR PC,=HandleIRQ 等效
LDR PC,VectorTable+4 ;与LDR PC,=HandleFIQ 等效
……
VectorTable DCD HandleIRQ
DCD HandleFIQ
……
HandleIRQ
……
HandleFIQ
一个典型的复位向量表的例子(Nucleus系统中比较常用)如例程5-2所示。
例程5‑2 典型复位向量表
;**********************************
;* VECTOR TABLE *
;**********************************
.sect "vectors"
.def INT_Vectors
INT_Vectors
LDR pc, INT_Initialize_Addr ; 复位异常中断地址
LDR pc, INT_Undef_Inst_Addr ; 未定义指令异常中断地址
LDR pc, INT_Software_Addr ; 软件异常中断地址
LDR pc, INT_Data_Abort_Addr ; 取数据错误异常中断地址
LDR pc, INT_Reserved_Addr ; 保留
LDR pc, INT_IRQ_Addr ; IRQ异常中断地址
LDR pc, INT_FIQ_Addr ; 快速IRQ异常中断地址
INT_Initialize_Addr
.word _INT_Initialize
INT_Undef_Inst_Addr
.word _INT_Undef_Inst
INT_Software_Addr
.word _INT_Software
INT_Prefetch_Abort_Addr
.word _INT_Prefetch_Abort
INT_Data_Abort_Addr
.word _INT_Data_Abort
INT_Reserved_Addr
.word _INT_Reserved
INT_IRQ_Addr
.word _INT_IRQ
INT_FIQ_Addr
.word _INT_FIQ
从上面的复位向量程序可以看出,CPU判断如果是复位异常则会跳转到_INT_Initialize执行,如果是未定义指令异常则跳转到_INT_Undef_Inst执行程序,其他几个异常也是同样的处理方式。
一般ARM嵌入式系统的程序都是固化在从0x00000000开始的低端ROM空间中,中断向量表VectorTable也固化在ROM中,所以上述两种方法都无法在程序运行时动态、随机地修改中断向量表。不论对于初学ARM处理器的程序员还是有经验的程序员,设置中断向量都相当烦琐,必须修改ARM的C程序启动代码。
还有一点需要指出的是,如果CPU有REMAP功能的话,一定要在初始化程序的结尾把整个程序包含复位向量表一起复制到RAM中,然后执行REMAP,如果没有REMAP功能,则需要把复位向量表单独转移到RAM中去。
ARM处理器的中断处理程序实际可以称为对于处理器的异常处理程序。在对这些异常进行处理时就需要一定的中断服务程序。一般在进入中断服务程序之前,都要完成对程序现场和一定数据、堆栈的保护,然后跳转到中断服务程序执行中断服务,完成中断服务之后,在退出中断服务程序之前恢复保存的数据、堆栈信息等。在ARM处理器设计中通常把复位初始化程序也算到异常中断处理程序中。对应处理器的8个异常,分别有如下8个处理程序。
n _INTCPU_Initialize:用来处理CPU复位初始化异常中断;
n _INT_Undef_Inst:用来处理未定义指令异常中断;
n _INT_Software:用来处理软件异常中断;
n _INT_Prefetch_Abort:用来处理取指溢出异常中断;
n _INT_Data_Abort:用来处理数据溢出异常中断;
n _INT_Reserved:保留异常中断;
n _INT_FIQ:用来处理FIQ异常中断;
n _INT_IRQ:用来处理IRQ异常中断。
在8个异常中断处理函数里面,常用的主要是处理器复位异常中断、软件异常中断、常规异常中断(IRQ)、快速异常中断(FIQ)4个。
下面结合实际的异常处理源码来具体分析这8个异常中断程序。这些代码针对的ARM处理器是OMAP5910。
1)CPU复位初始化异常中断处理INTCPU_Initialize,该异常中断处理的主要功能有:
(1)初始化处理器系统控制寄存器。
(2)初始化中断向量表。
(3)初始化系统堆栈指针。
(4)跳转到高级初始化函数,进行高级初始化。
该异常处理函数的具体代码通常如例程5-3所示。
例程5‑3 INTCPU_Initialize
.def _c_int00
_c_int00
.def _INTCPU_Initialize
_INTCPU_Initialize:
; //确保处理器进入Super模式
MRS r0,CPSR ; 获取当前CPSR值
BIC r0,r0,#MODE_MASK ; 清除CPSR中对应的CPU模式位
ORR r0,r0,#SUP_MODE ; 设置supervisor模式位
ORR r0,r0,#LOCKOUT ; 确保 IRQ/FIQ 中断被禁止
MSR CPSR,r0 ; 设置新的CPSR参数值
;//清除未初始化的BSS区域
LDR r0,BSS_Start ; 获取BSS段的起始地址
MOV r2,#0 ; 清除r2寄存器值
LDR r1,BSS_End ; 获取BSS段的结束地址
INT_BSS_Clear_Loop:
STR r2,[r0],#4 ; 进入循环清空BSS段数据
INT_BSS_Clear_Check:
CMP r0,r1 ; 与BSS结束地址比较
BNE INT_BSS_Clear_Loop ; 如果相等,表示BSS段清空了
; 判断C运行段是否需要自动初始化.
LDR r0, c_cinit
CMN r0, #1
BLNE _auto_init
; 打开I-Cache (针对 TI 925T 处理器)
MRC p15,#0,r1,C1,C0,#0 ; 获取CP15 协处理器中寄存器的值
ORR r1,r1,#0x1000 ; 使能指令cache I-cache
NOP
MCR p15,#0,r1,C1,C0,#0 ; 写CP15协处理器的寄存器.
; 建立向量表装载标识位,主要是为让系统中的其他调用了解默认的向量表是否已经装载了。
; 如果INT_Loaded_Flag 为1, 表示所有的默认的向量表已经装载
; 否则, 如果INT_Loaded_Flag 为 0, 可以通过登记一个LISR来使得默认的向量被装载
; 在 ARM60系统应用中,这个变量总是为1
; 所有的向量必须通过这个函数建立
; INT_Loaded_Flag = 0;
MOV r0,#1 ; 设定所有的向量被装载(通过设置Loaded_Flag=1)
LDR r1,Loaded_Flag ;
STR r0,[r1,#0] ; 初始化Loaded_Flag
; 初始化系统堆栈指针。 通常在BSS段清空之后完成这个初始化,
; 原因是TCD_System_Stack也是一个BSS段里的变量。
; 这里定义在BSS段之后的ram空间为可以使用的堆栈空间。
;
LDR r10,System_Stk_Limit
LDR r3,System_Limit ; 获取系统堆栈Limit地址
STR r10,[r3, #0] ; 保存该地址
LDR sp,System_Stack_SP ; 建立系统堆栈指针
LDR r3,System_Stack ; 获取系统堆栈地址
STR sp,[r3, #0] ; 保存堆栈指针
MRS r0,CPSR ; 获取当前的CPSR
BIC r0,r0,#MODE_MASK ; 清除模式位
ORR r0,r0,#IRQ_MODE ; 设置IRQ_mode位
MSR CPSR,r0 ;
LDR sp,IRQ_Stack_SP ; 建立IRQ堆栈指针
MRS r0,CPSR ; 获取当前的CPSR
BIC r0,r0,#MODE_MASK ; 清除模式位
ORR r0,r0,#FIQ_MODE ; 设置FIQ_mode位
MSR CPSR,r0 ;
LDR sp,FIQ_Stack_SP ; 建立FIQ堆栈指针
MRS r0,CPSR ; 获取当前的CPSR
BIC r0,r0,#MODE_MASK ; 清除模式位
ORR r0,r0,#SUP_MODE ; 设置supervisor模式位
MSR CPSR,r0 ; 建立了所有异常的堆栈之后
; 返回supervisor模式
; 定义全局数据结构
; 初始化该数据结构。
;
; TMD_HISR_Stack_Ptr = (VOID *) r2;
; TMD_HISR_Stack_Size = TIMER_SIZE;
; TMD_HISR_Priority = TIMER_PRIORITY;
LDR r2,HISR_Stack_Mem ; 获取HISR_Stack_Mem变量地址
LDR r3,HISR_Stack_Ptr ; 获取HISR_Stack_Ptr变量地址
STR r2,[r3, #0] ; 建立timer HISR堆栈指针
MOV r1,#HISR_STACK_SIZE ; 获取HISR_STACK_SIZE参数值
LDR r3,HISR_Stack_Size ; 获取HISR_Stack_Size变量地址
STR r1,[r3, #0] ; 设置timer HISR 堆栈大小
MOV r1,#HISR_PRIORITY ; 获取HISR 优先级参数
; HISR_PRIORITY的值(0-2)
LDR r3,HISR_Priority ; 获取HISR_Priority变量地址
STR r1,[r3, #0] ; 设置HISR 优先级
/*BL调用中断向量表建立函数*/
/*BL调用初始化Timer函数*/
BL _INT_Install_Vector_Table ; 调用装载向量表子函数
BL _INT_Timer_Initialize ; 调用初始化Timer函数
;
;
; 调用INC_Initialize分配第一个可用的ram地址,通常都是上层来进行调用的
; INC_Initialize(first_available_memory);
LDR r0,First_Avail_Mem ; 获取First_Avail_Mem地址
/*跳转到_INC_Initialze高级初始化部分*/
B _INC_Initialize ; 跳转到高级初始化部分,注意使用B指令
复位异常处理是CPU上电复位之后第一个执行代码。复位异常中断处理不论是在裸机设计方式中,还是基于操作系统的软件设计方式中,都是最重要的异常处理程序之一。上述程序完成CPU的初始化,堆栈空间的初始化等工作。那么下面再看看其他几个异常处理函数的定义部分。
2)未定义指令异常服务程序
未定义指令异常在通常的系统设计中都不做深入处理,在完成对现场的保护后,在服务程序中打印出相关的提示信息。其服务程序通常如下:
.def _INT_Undef_Inst
_INT_Undef_Inst
STMDB sp!,{r0-r3,lr}
LDMIA sp!,{r0-r3,lr} ;保存寄存器组
BX LR
B _INT_Undef_Inst
3)软件中断服务程序
这是一个由用户定义的中断指令,可用于用户模式下的程序调用特权操作。软件中断服务程序在基于操作系统的嵌入式系统设计中十分重要,可以通过该机制实现系统功能调用。软件中断服务程序的常规流程一般是先得到软件中断号,然后根据中断号执行不同的代码。常规的处理方式如下。
; The CPSR Register is not accessible User mode, so a switch to the
; privileged mode is done by the assembly instruction SWI.
.state32
; 使能异常,建立堆栈
; Works only if called from 16-bit (THUMB) mode
;/-------------------------------------------------------------------
.def _INT_Software
_INT_Software:
LDRH r4, [LR, #-2] ;得到软件中断指令
AND r4, r4, #0xFF ;把中断号放到寄存器R4中
/*对R4 进行判别,从而进入不同的处理,一系列的CMP和BEQ指令*/
CMP r4, #0xF6
BEQ SetFIQIRQBit
…
… /*类似的CMP和BEQ指令*/
CMP r4, #0xF3
BEQ ClearFIQBit
B ExitSwi ;
SetSupervisor: //切换CPU模式到Supervisor模式
MRS r7, SPSR ; 保存SPSR副本
BIC r7,r7,#MODE_MASK ; 清除模式位
ORR r7,r7,#SUP_MODE ; 设置Supervisor模式位
MSR SPSR, r7 ; 写回SPSR参数
B ExitSwi ;
SetUser: //切换CPU模式到User模式
MRS r7, SPSR ; 保存SPSR副本
BIC r7, r7, #MODE_MASK ; 清除模式位
ORR r7, r7, #USR_MODE ; 设置User模式位
MSR SPSR, r7 ; 写回SPSR参数
B ExitSwi ;
EnableIRQ://使能IRQ
MOV r5, #IRQ_MODE
MOV r6, #IRQ_MASK
B DoIt
EnableFIQ://使能FIQ
MOV r5, #FIQ_MODE
MOV r6, #FIQ_MASK
B DoIt
Set_SVC_Stack://建立Supervisor模式下的堆栈
;STACK MANIPULATION
MOV SP, r0 ; 堆栈地址
MOV r1,r1,lsl #2 ; 转换字地址到字节地址
ADD SP, r1, SP ; 在初始化SP上累加
B ExitSwi
Set_Abort_or_Udef_Stack:
MRS r2, CPSR
BIC r2, r2, #MODE_MASK
ORR r2, r2, r5
MSR CPSR, r2 ; 设置Udef模式位
MOV SP, r0 ; 堆栈地址
MOV r1,r1,lsl #2 ; 转换字地址到字节地址
ADD SP, r1, SP ; 在初始化SP上累加
BIC r2, r2, #MODE_MASK
ORR r2, r2, #SUP_MODE
MSR CPSR, r2 ; 保存Supervisor模式位
B ExitSwi
SetIRQBit:
MOV r5, #IRQ_MASK
MOV r6, #SET_BIT
B SetClearFIQIRQ
ClearIRQBit:
MOV r5, #IRQ_MASK
MOV r6, #CLEAR_BIT
B SetClearFIQIRQ
SetFIQBit:
MOV r5, #FIQ_MASK
MOV r6, #SET_BIT
B SetClearFIQIRQ
ClearFIQBit:
MOV r5, #FIQ_MASK
MOV r6, #CLEAR_BIT
B SetClearFIQIRQ
SetFIQIRQBit:
MOV r5, #FIQ_IRQ_MASK
B SetFIQIRQ
RestoreFIQIRQBit:
MOV r5, #FIQ_IRQ_MASK
B RestoreFIQIRQ
SetFIQIRQ:
MRS r7, SPSR ; 读取保存的SPSR
;
AND r0, r7, #0xC0 ; 屏蔽FIQ、IRQ位,写入r0寄存器.
ORR r7, r7, r5 ; 设置FIQ、IRQ位,保存SPSR
B STORE_SPSR
RestoreFIQIRQ:
MRS r7, SPSR ; 读取保存的SPSR值
BIC r7, r7, r5
ORR r7, r7, r0 ; 设置FIQ、IRQ位,保存SPSR
B STORE_SPSR
SetClearFIQIRQ:
MRS r7, SPSR ; 读取保存的SPSR值
BIC r7, r7, r5 ; 清除FIQ、IRQ位
CMP r6, #CLEAR_BIT ; 判断是否等于 #CLEAR_BIT
BEQ STORE_SPSR ; 相等的话,调到STORE_SPSR
ORR r7, r7, r5 ; 不相等,则设置FIQ、IRQ位,保存SPSR
STORE_SPSR:
MSR SPSR, r7 ;保存SPSR
B ExitSwi
DoIt:
;
MRS r4, CPSR ; 读取保存的SPSR值
BIC r4,r4,#MODE_MASK ; 清除所有的模式位
ORR r4,r4,r5 ;
MSR CPSR, r4 ;
;STACK MANIPULATION
MOV SP, r0 ; 堆栈地址
MOV r1,r1,lsl #2 ;
ADD SP, r1, SP ;
BIC r4,r4,#MODE_MASK ;
ORR r4,r4,#SUP_MODE
MSR CPSR, r4 ;
MRS r4,SPSR ;
BIC r4,r4,r6 ;
MSR SPSR,r4 ;
ExitSwi:
MOVS PC, R14 ; 从SWI返回
取指异常、数据异常、保留异常中断处理函数都是比较简单的,通常都不做详细地处理,具体代码如4)、5)、6)所示。
4)取指异常中断服务程序如下所示
;********************************************************************
.def _INT_Prefetch_Abort
_INT_Prefetch_Abort
MOV r0, lr
B _INT_Prefetch_Abort
5)数据异常中断服务程序如下所示
;********************************************************************
.def _INT_Data_Abort
_INT_Data_Abort:
STMDB sp!,{r0-r3,lr}
LDMIA sp!,{r0-r3,lr}
BX LR
B _INT_Data_Abort
6)保留异常中断服务程序如下所示
;********************************************************************
.def _INT_Reserved
_INT_Reserved:
MOV r0, lr
B _INT_Reserved
7)IRQ中断服务程序
当ARM处理器的外部中断请求有效,而且CPSR寄存器的I控制位被清除时,ARM处理器产生外部中断请求IRQ异常中断。一般来说系统中的各个外设通常都是通过该异常中断请求ARM处理器服务的。IRQ中断服务程序如下所示。
;********************************************************************
.def _INT_IRQ
_INT_IRQ:
; ARM Core Check
STMDB sp!, {r1}
MRS r1, SPSR
TST r1, #IRQ_BIT
LDMIA sp!, {r1}
SUBNES pc,lr,#4
STMDB sp!,{r0-r4} ; 保存R0~R4到当前的IRQ堆栈空间中
SUB lr,lr,#4 ; 调整IRQ返回地址
;********************************
;* Begin Hardware Specific Code *
;********************************
LDR r3, INT_CNTRL_BASE_1 ;
LDR r4, [r3,#INT_CNTRL_MIR] ;
;******************************
;* End Hardware Specific Code *
;******************************
STMDB sp!,{r4} ;
MVN r4,#0 ;
;********************************
;* Begin Hardware Specific Code *
;********************************
LDR r2, [r3,#INT_CNTRL_ITR] ;
;******************************
;* End Hardware Specific Code *
;******************************
LDR r3, IRQ_Priority ;
;以下流程在向量表里面进行优先级识别
IRQ_VECTOR_LOOP
LDR r0, [r3,#0] ; 从优先级表里面检查装载的第一个向量表
MOV r1, #1 ;
MOV r1, r1, LSL r0 ;
TST r1, r2 ;
BNE IRQ_VECTOR_FOUND ;
BIC r4,r4,r1 ; 清除屏蔽位保证高级优先级
ADD r3, r3, #4 ;
ADR r0, Priority_End ;
CMP r0, r3 ;
BNE IRQ_VECTOR_LOOP ;
;
ADD sp,sp,#4 ;
LDMIA sp!,{r0-r4} ; 保存r0-r4
STMDB sp!,{lr} ; 保存返回值
LDMIA sp!,{pc}^ ; 保存SPSR等参数,返回
IRQ_VECTOR_FOUND
;********************************
;* Begin Hardware Specific Code *
;********************************
LDR r3, INT_CNTRL_BASE_1
MVN r2, r1
STR r2, [r3,#INT_CNTRL_ITR]
LDR r2, [r3,#INT_CNTRL_MIR]
ORR r4, r2, r4
STR r4, [r3,#INT_CNTRL_MIR] ; 屏蔽所有低优先级中断
MOV r1, #1 ; 清除pending中断
STR r1, [r3,#INT_CNTRL_CONTROL_REG] ;
;******************************
;* End Hardware Specific Code *
;******************************
LDR r3, IRQ_Vectors ; 获取IRQ向量地址
MOV r2, r0, LSL #2
ADD r3, r3, r2 ; 调整向量表地址到当前偏移量
LDR r2, [r3,#0] ; 从向量表转载跳转地址
MOV PC, r2 ; 修改PC,进行跳转
; END: INT_IRQ
8)FIQ中断服务程序
当ARM处理器的快速中断请求有效,并且CPSR寄存器的F控制位被清除时,ARM处理器产生FIQ异常中断。FIQ中断服务程序如下所示。
;*************************************************************
.def _INT_FIQ
_INT_FIQ
.if $$isdefed("NU_FIQ_DEMO")
STMDB sp!, {r1}
MRS r1, SPSR
TST r1, #FIQ_BIT ;
LDMIA sp!, {r1}
SUBNES pc, lr, #4
SUB lr,lr,#4 ; 调整返回地址
STMDB sp!,{r0-r7,lr} ; 保存寄存器等到当前的FIQ堆栈空间
LDR r3, INT_CNTRL_BASE ; 装载INT_CNTRL_BASE
MOV r1, #2 ; 清除pending中断
STR r1,[r3,#INT_CNTRL_CTRL_REG] ;
BL _FIQ_LISR ; 跳转到FIQ中断服务程序
LDMIA sp!,{r0-r7,pc}^ ;
.else
B _INT_FIQ
.endif
当然如果系统中很多中断没有用,那么对应的中断处理函数可以写得较为简单一些,甚至一条空语句就可以表示。主要的中断处理函数是IRQ和FIQ两个,在两个服务程序里面来判断中断状态寄存器,得到中断的产生源,然后分发出去。
在介绍了ARM处理器的复位向量表,以及相关的8个异常中断处理机制之后,接下来探讨一下在裸机方式下的常用程序调试方式。
最常用的也是最方便的一种程序调试方式就是把程序加载到RAM区中去调试,在ADS集成开发环境下实现通常都需要一个入口函数。一个简单的例子程序代码如下。
IMPORT C_Entry
AREA Init,CODE,READONLY
ENTRY
LDR R0,=0x3FF0000 ;初始化系统配置寄存器,它的地址为0X3FF0000
LDR R1,=0xE7FFFF80 ; Start_addr = 0x3FF00000
STR R1,[R0] ; 4KB Cache,4KB SRAM,WB disable
LDR SP,=0x3FE1000 ;初始化用户堆栈,
BL C_Entry ;跳转到函数C_Entry()处执行的C/C++代码
B.
END ;标识汇编代码的结束
在ADS下可以通过“Debug Settings”窗口设置RO Base(程序段的起始地址)和RW Base(数据段的起始地址),在“Options”选项卡中要注意指定程序实体entry(调试入口)的地址和“Equivalent Command Line”里面的组合语句的产生。在如图5-1所示的Debug Settings界面中,Image Entry point是0x40010000。注意一下图中红色圈里面的地址,这些地址都是在RAM区里面的。如果有多个程序时,可以在“Layout”选项卡里面指定第一个程序模块.o和第一个段名(Section)。通常填中断向量所在的模块。
图5‑1 Debug Settings界面
通过上面的介绍,现在就可以开始裸机驱动程序的设计了,不过要注意,主程序的入口函数一定是C_Entry()函数。裸机串口驱动的代码可以参考本书7.3介绍的代码例子。