引言
S3C44B0X片上资源十分丰富,在高新科技领域得到了广泛的应用;但迄今为止,其设计程序在Flash中运行较慢的不足及其解决方法在国内外相关技术资料中却较少提及,且相应RO_base、RW_base的设置方法在相关参考书目中也未见报道。众所周知,μC/OSII是实时多任务内核,设计的主要任务就是操作系统移植中几个相关函数的编写,现在网络上虽有多种版本可以选用,但这些版本有的存在错误,有的是针对其他类型处理器,不能直接照搬;特别是OSTickISR编写时,必须结合现有的Bootloader程序以及ARM中断处理过程,才能实现操作系统的正确移植、很多人初次移植时遭遇挫折,就是没能正确加以处理。在S3C44B0X中有很多中断源,传统的由汇编语言实现的OSTickISR函数缺乏通用性,不利于其他中断程序的编写。本文针对上述问题给出相应的解决方法。
1 Flash复制跳转程序的设计
相对于Flash来说,SDRAM的运行速度要快很多。现在有些处理器利用地址重映射功能,在程序运行的最初几条指令中,利用系统的存储器映射机构进行地址重映射,重新将SDRAM映射到地址0x0处,使得程序在SDRAM中执行。这样会使系统运行速度更快,同时使系统响应异常中断的速度更快。由于S3C44B0X没有地址重映射功能,因此无法像前面所说的那样将SDRAM映射到地址0x0处执行。下面介绍一种方法,将位于NOR Flash中的程序复制到SDRAM中以获得更快的运行速度并正确执行,从而实现地址的伪“重映射”。下面以优龙科技S3C44B0X开发板中提供的Bootloader为例进行分析。
1.1 复制程序的设计
由于程序要复制到SDRAM中去执行,因此最好在SDRAM初始化以后进行复制。一个程序编译完成后,生成的二进制文件中只有RO(code和data)和RW两个部分,ZI段在程序运行时初始化。通过比较程序入口点(ResetEntry)和在ADS开发环境中设置的RO_base的大小来判断是否要对程序进行复制。其中ResetEntry是一个基于PC的标号。程序被烧写至Flash,刚开始从Flash运行时,ResetEntry值肯定为0x0。如果将RO_base设置为0x0,那么就无须复制,表明程序要在Flash中运行;如果将RO_base设置为SDRAM所处的地址空间,那么首先要将Flash中所有程序(RO和RW两个部分)复制到指定的SDRAM空间并在SDRAM中执行程序。为了能够正确复制,要计算出待复制程序空间的大小,这个大小由RO_Base、RW_base给出。将程序复制到RO_base指向的空间。方法步骤如下:
① 计算出RO程序段的大小。
RO_size = TopOfROM-BaseOfROM
BaseOfROMDCD|Image$$RO$$Base|;TopOfROMDCD|Image$$RO$$Limit|
② 计算出RW数据段的大小。
RW_size = BaseOfZero- BaseOfBSS
BaseOfBSSDCD|Image$$RW$$Base|;BaseOfZeroDCD |Image$$ZI$$Base|
③ 计算出Flash中整个程序段的大小。
CODE_size = RO_size + RW_size
④ 比较BaseOfROM++与CODE_size+BaseOfROM的大小。
如果前者小,就继续将代码从ResetEntry起始地址处复制至BaseOfROM起始地址空间,循环复制程序直至递增地址与CODE_size +BaseOfROM相等。其中CODE_size + BaseOfROM= TopOfROM+ RW_size。下面的程序设计直接用这个结果编写。
代码实现如下:
BL InitSystem;进入InitSystem进行SDRAM、
;堆栈等初始化
LDR r0,ResetEntry
LDR r1,BaseOfROM
CMP r0,r1;比较ResetEntry与BaseOfROM,
;看是否要进行复制
BEQ InitRamData;如果相同,不复制;否则复制程序,
;直接运行下面代码
LDR r3, TopOfROM;运行到此,说明要进行复制
LDRr4, BaseOfBSS
LDRr5, BaseOfZero
SUBr2, r5, r4
ADDr2, r3, r2 ;取得待比较地址TopOfROM+
;RW_size,并存入r20
LDMIA r0!,{r4r11} ;从ResetEntry开始取值,并将r0;递增
STMIA r1!,{r4r11} ;存入BaseOfROM开始的地址,并
;将r1递增
CMP r1, r2 ;比较r1与r2,判断是否继续复制
BCC %B0 ;若r1<r2,继续复制程序;否则退;出循环,进行下一步
这样程序就复制到了SDRAM中,起始地址由RO_base确定。
1.2 跳转程序的设计
程序要正确运行,必须用一段指令找到位于SDRAM中的InitRamData标号,跳转到此处才能实现程序正确运行。跳转的目的地址是BaseOfROM+InitRamData标号。
代码实现如下:
LDRr0,TopOfROM ;为下面InitRamData函数所准备
;(此处没有用到)
LDR r1,BaseOfROM;跳转目的地址的实现
LDRr2,=InitRamData
ADD r1,r1,r2
MOVpc,r1 ;程序跳转到SDRAM中InitRamData;函数处运行
InitRamData ;初始化RAM空间段程序:RW和ZI
通过上面两个步骤,就实现了Flash程序到SDRAM的复制。此处要指出的是,在复制后,有些读者可能考虑到了绝对地址和相对地址的关系问题。比如,在51系列单片机中可以用一条伪指令指定程序存放的绝对地址;但在此Bootloader中,不用考虑绝对地址,指令前的标号都是基于PC的,编译时直接将其处理成一个基于PC的偏移。还有一些用EQU和MAP、FIELD等伪指令定义的标号,编译器会在Flash存储器中为这些标号分配一个地址空间(文字池),存放表示这些标号的数值,这些标号存放的位置也是基于当前PC的偏移。程序之间的相对位置在编译时已经确定,因此将程序复制到SDRAM中不会影响程序的运行。
2 μC/OSII中断函数的实现
目前网络上有很多移植版本,有些有较严重的错误,有些是针对其他处理器的,并不能拿来就用,这些函数需要根据相应的处理器和Bootloader进行修改或重新设计。确切的说,μC/OSII移植主要有4个函数要编写:OS_CPU.H、OS_CPU_C.C、OS_CPU_A.S和OS_CFG.H。要指出的是,OS_CFG.H一定要定义正确,如果没有定义相关宏,编译器就会根据UCOS_II.H中的定义给出相应的错误信息。Jean J.Labrosse建议在OS_CPU_C.C的OSTaskIdleHook()函数中加上一些延时指令。因为调用这个函数前执行OSIdleCtr++指令时间段内中断被屏蔽,这样利用OSTaskIdleHook()函数可以为中断的进入提供时间。所有这些函数中,中断部分比较难处理,时钟中断函数OSTickISR是中断的一个特例,其他的函数比较简单。下面设计一种中断函数的处理方式,并给出OSTickISR函数的C语言实现(所有中断定义为IRQ非向量模式,单层中断机制)。
2.1 中断函数的处理过程
图1 中断处理流程
为了能够响应中断请求,必须同时满足两个前提条件:当前CPSR中I位为0,允许中断;中断屏蔽寄存器INTMSK的Global位和相应的中断位为0。当处理器发生中断时,处理器由原先的SVC模式进入IRQ模式,当前LR寄存器保存着当前任务的返回地址(需要减4修正),并且在0x18处有一条“b IRQ_SVC_HANDLER”指令将程序跳转到IRQ_SVC_HANDLER标号处执行。中断函数流程如图1所示。详细步骤如下:
IRQ_SVC_HANDLER(标号,汇编程序)
① 进入IRQ模式,保存现场至堆栈。
② 如果RO_base指向0x0,则不用复制;若指向SDRAM空间,此段指令将使PC跳转到SDRAM中RUN_IN_SDRAM标号处执行,以提高中断响应速度。(Flash中代码已由前面复制程序复制到了SDRAM中)
RUN_IN_SDRAM(标号,汇编程序)
③ 复制现场至SVC模式下SP指向的地址空间。由于任务可能会被高优先级的任务取代,因此将当前SP存入OSTCBCur﹥OSTCBStkPtr中,保存完整状态为下次任务切换做准备。之后,再次进入IRQ模式,利用“BL OSIntEnter”记录中断嵌套次数,利用“B SYS_ISR”进入系统中断处理程序。
void SYS_ISR(void)
④ 进入系统中断处理函数。在这个函数中,首先对中断进行选择,然后进入相应的USER_ISR()函数,执行用户定义的中断函数,最后返回处理系统中断的其他部分。程序如下:
{
IsrIRQ_choose();/*调用USER_ISR()函数*/
OSIntEnter();
OSIntExit;
RESTORE_reg()
}
SYS_ISR函数调用IsrIRQ_choose(),其利用IRQ中断服务挂起寄存器I_ISPR来判断哪个中断正在被响应,从而选择中断类型并运行相应的USER_ISR()。IsrIRQ_choose是个标号,用汇编语言编写。USER_ISR()主要由两个部分组成: 一个是用户中断程序代码,主要是应用程序编程人员编写的代码;另一个是清中断源的中断申请标志,在S3C44B0X中就是rI_ISPC = BIT_TYPE指令,用来清除INTPND中的中断请求标记,为下一次中断做好准备(可见下面OSTickISR()函数的编写)。之后调用OSIntExit()判断是否有更高优先级任务就绪。如果有,就执行OSIntCtxSw退出ISR并实现任务中断级切换;如果没有,则调用RESTORE_reg()实现任务的返回。上述几个部分代码的设计如下:
第②步PC跳转到SDRAM中RUN_IN_SDRAM标号处执行的指令代码如下(设计思路与前面将程序跳转到SDRAM中InitRamData标号处运行思路一样):
LDR r1,BaseOfROM
ADR r2,SET_FOR_RAM
ADD r1,r1,r2
MOV pc,r1
RESTORE_reg()函数用汇编语言编写。本设计的模式切换过程是:IRQ→SVC→IRQ→SVC。前面已经分析,此函数切换到SVC模式下运行。由于两次进入IRQ模式,如果此模式下SP被先前的指令所更改,则指向SDRAM某个地址;如果不对其进行恢复,那么当再次进入中断时,SP所指向的空间可能是一个有用的空间。用STMFD指令保存某个寄存器值时,此时SP所指向的空间的内容将会被更改,因此在进入SVC模式之前,用IRQStack对其进行了恢复以确保程序正确运行。采用其他方式设计时,这个问题也应该考虑到。
RESTORE_reg
LDR sp,=IRQStack
MSR CPSR_cxsf, #SVC32Mode| IRQ_BIT | FIQ_BIT
LDR r0,=OSTCBCur
LDR r1,[r0]
LDR sp,[r1]
LDMFD sp!,{r0}
MSR CPSR_cxsf, r0
LDMFD sp!,{r0-r12,lr,pc}
在进入中断级切换函数OSIntCtxSw时,有与RESTORE_reg()函数一样的问题。由于要退出IRQ,所以必须对IRQ模式下的SP进行修改。之后进入SVC管理模式进行任务切换,其与OSCtxSw后半部分基本一样,可以在相应的地方加个标号,直接调用。
时钟中断函数是USER_ISR()函数的一种。通过上面的分析,就能够很简单地实现时钟中断函数OSTickISR的C语言编写,读者可以根据这个处理过程将某些函数进行封装或进行优化。下面给出一种OSTickISR的C语言设计。
2.2 时钟中断函数OSTickISR的设计
对实时嵌入式操作系统来说,系统时钟一般要求在50~200 Hz。定时器TIMERn可用于中断源,通过设置定时器和中断控制器中几个相关寄存器就能为系统提供稳定的200 Hz的时钟中断。这样当发生时钟中断时,系统就能够调用OSTickISR()函数实现中断的处理。C语言实现如下:
void OSTickISR(void) {
OSTimeTick();
rI_ISPC =BIT_TIMERn;
}
3 嵌入式设计中一些要点总结
在S3C44B0X及类似微处理器的嵌入式系统设计中,由于涉及硬件底层,要考虑的因素比较多。有些细节问题在一些参考书中不会提及,往往使初学者非常困惑。
此处将笔者认为比较重要的几个问题总结如下:
① 一个程序编译完成后,如果要将其烧写到Flash中运行,一般将RO_base设置为0x0,RW_base设置为SDRAM空间地址。如果Bootloader有将程序复制到SDRAM空间运行的代码,那么RO_base可设置为0x0或SDRAM空间地址;如果只是调试生成的程序,则应将RO_base设置为SDRAM空间地址。
② Flash在烧写时不能读取自身指令和数据,因此,必须可以在被烧写的Flash以外的存储器中取指令使CPU运行。比如,从其他的Flash或SDRAM取指令,同时对此Flash烧写。
③ 在用C语言和汇编语言混合编程时,子程序通过R0~R3进行参数传递,因此如果一个C函数调用了一个汇编语言函数,则可以放心地用这几个寄存器,其会自动保存这些寄存器,但其他的寄存器在使用前必须保存。汇编函数可以通过R0返回一个32位的整数,比如μC/OSII中开关中断函数INTS_OFF就用R0作为返回参数。
④ 通过Fluted.exe程序可以完成Flash烧写。针对不同的CPU需要不同的Bsd文件,不同的Flash需要对Fcd文件进行修改,相应的Bsd文件和Fcd文件可从网上搜索。Fluted.exe的典型用法是:FluteD a f xxx.bin v s 0。其中: a为自动擦除,f为写入文件,v为写入后校验,s为起始地址。
⑤ 针对不同位的Flash或SDRAM,S3C44B0X地址接线方法不同。这是由于对于Flash或SDRAM地址每变化1位,它们就输出1个字的大小;对于不同的Flash或SDRAM,这个字的大小可能是8、16和32位。而S3C44B0X是字节地址,每次改变是1个字节8位,因此二者连接和编程时要考虑到错位和移位的问题。