以一款简单、易学的嵌入式开发平台ARM Mini2440(CPU是三星ARM 9系列的ARM S3C2440)为例,通过具体代码实现,介绍如何从裸板入手设计简单的轮询系统、前后台系统,以及如何一步一步在ARM Mini2440上编写RTOS内核,到如何让RTOS内核支持多核嵌入式处理器。
aCoral是2009年创建的开源的、支持多核的RTOS。
目前aCoral包括五大模块
aCoral支持多任务模式,其最小配置时,生成的代码为7KB左右,而配置文件系统,轻型TCP/IP,GUI后生成的代码仅有300KB左右。
轮询系统也称为简单循环控制系统,是一种最简单的嵌入式实时软件体系结构模型。
在单个微处理器情况下,系统功能由多个函数(子程序)完成,每个函数负责该系统的一部分功能。这些函数被循环调用执行,它们按照一个指向顺序构成一个单向的有序环(轮循环),依次占用CPU。
每个函数访问完成之后,才将CPU交给下一个函数使用。对于某个函数而言,当它提出执行请求时,必须等到它被CPU接管后才能执行。
initialize();
while(TRUE)
{
if(condition 1)
{
F1();
}
if(condition 2)
{
F2();
}
if(condition 3)
{
F3();
}
}
在系统工作以前,首先进行系统初始化,然后系统进入无限循环状态;主程序依次对轮循环中的函数进行判断,若该函数要求占用CPU,则让其执行,否则跳过该函数去执行提出请求的下一个函数,这种判定某个函数是否满足执行条件的过程,称为轮询。
根据程序结构特点,基本轮询系统具有以下工作特点:系统完成一个轮询的时间取决于轮循环中需要执行的函数的个数。循环的次序是静态固定的,在运行时不能进行动态调整。
这种特点决定了轮询系统在诸如多路采样系统、实时监控系统等嵌入式应用中可以得到广泛使用,但是所有函数必须顺序执行,不区分函数的重要程度,系统也无法根据应用的实际需要灵活地调整对函数的使用粒度。
F_1()
{
if(condition 1)
{
F1();
}
}
F_2()
{
if(condition 2)
{
F2();
}
}
#define N_ACTION 3
int action_ptr;
main()
{
other_initialization();
action_ptr=0;
while(TRUE)
{
if(++action_ptr==N_ACTION) action_ptr=0;
}
}
当某些函数的执行时间相对较长时,可以将其分解成若干子函数,这些子函数也构成一个轮询,称为子轮询。
例如,一个函数需要打印消息到一个慢速输出设备中,可以将其分解成两部分:第一部分是打印消息处理(F2_1),第二部分是慢速设备忙时的等待处理(F2_2)。
许多工业现场网络中,由于需要控制的设备较多,相互距离又较远,且现场有较强的工业干扰,因此采用体积小、抗干扰能力强的单片机作为上位机,与现场控制器一起组成分布式数据采集与控制系统,是一种较好的选择。
如图所示,在一个多机通信系统中,只有一台单机(8051)作为主机,各台从机不能相互通信,必须通过主机转发来交换信息。单片机通过RS-485总线通信,主机通过点名方式向各从机发送命令,实现对系统的控制。同时,对从机不断地轮询,监视从机的状态,接收从机的请求或信息。
开发人员的程序是在PC上编写的,PC是Intel的处理器,Windows的操作系统。
单片机可能是TI的8051、430等。
Intel的处理器和单片机采用的是不同指令集。
交叉开发Cross Developing是嵌入式软件系统开发的特殊方法。
开发系统建立在软硬件资源均比较丰富的PC或工作站上,一般称为宿主机或Host,嵌入式软件的编辑、编译、链接等过程都是在宿主机上完成。
嵌入式软件的最终运行平台却是和宿主机有很大差别的嵌入式设备,一般称为目标机或Target,这里的目标机就是ARM Mini2440。
宿主机与目标机通过串口、并口、网口或其它通信端口相连,嵌入式软件的调试和测试是由宿主机和目标机之间协作完成。
宿主机与目标机的差别主要在于:
裸板轮询系统的交叉开发环境如图所示。
其中,ADS(ARM Development Suite)是针对ARM处理器的集成开发环境,包含了代码编辑器、编译器、连接器、调试器。
图中的宿主机与目标机的连接通过J-LINK、串口连接。
J-LINK主要是为了烧写和调试被调试程序(将要开发的轮询系统),而串口主要是为了回显调试信息。
J-LINK要正常工作,需要在PC上安装J-flashARM。
这样,就可以在PC上编写代码,然后用ADS将其编译、链接成具有ARM指令集的可执行程序,即被调试程序;再通过J-LINK将被调试程序烧写在Mini2440的NORFlash上(NORFlash的起始地址是0x00000000,开发板上电后PC将指向这里,从该地址存放的指令开始运行);最后,重新启动Mini2440,被调试程序便可在目标机上运行,并通过串口回显运行信息。
将PC、Mini2440和J-LINK连接好后,就可以在ADS下创建工程了,具体步骤如下:
交叉开发环境搭建好后,便可动手编码了,编码的首要工作是:用ARM汇编语言启动处理器S3C2440A。
无论一个计算机系统由多少硬件设备组合而成,该系统能够运行的基础至少需要一个CPU与运行指令与数据的载体,该载体被称为主存。
当系统上电后,CPU挂在主存的存储器上开始命令的执行,一般的CPU通常是从0x0处开始取指执行,Mini2440的S3C2440A芯片也是如此。
当开发板上电后,CPU的PC寄存器的值通过硬件机制被初始化为0x0。
一切指令与程序都只能在主存上运行。而主存价格比较昂贵,并且开发人员的程序通常比较大,因此开发人员的程序通常存储在外存中,而外存无法作为应用程序运行的载体。
当系统上电后,要将应用程序从其它存储设备复制到主存。
启动代码的作用可以随着需求的增加而进行扩充,上述描述只是确保系统能够基本运行启动代码的功能。其实,启动代码还可以包括实际开发板上各级硬件和接口的驱动程序,BootLoader(如FriendlyARM BIOS2.0或者Supervivi)就是这样一类启动代码。
启动代码是与硬件设备密切相关的。
这一节将以Mini2440的S3C2440A处理器为例来讲述启动代码的流程。
如图所示,这里的启动代码只是一个能使CPU正常工作的一个最小系统。
Mini2440开发板有两种启动模式,一种是从NOR Flash启动,另一种是从NAND Flash启动,本节从NAND Flash启动为例介绍。
当Mini2440从NAND Flash启动时,因为NAND Flash无法作为程序运行的载体,所以S3C2440A芯片通过硬件机制将NAND Flash的开头4KB的内容复制到了S3C2440A芯片内部的4KB大小的SRAM中,并且S3C2440A芯片会自动将这4KB大小的SRAM映射为自身内存的BANK0,将这4KB大小的内容映射到从0x00000000开始的地址上,然后处理器从0x00000000地址开始执行。
当程序在S3C2440A芯片上运行发生异常时,程序指针PC会自动跳转到主存最开始的地址(0x00000000),这里就是异常向量表的起始地址,然后会通过专门的硬件机制定位到相应的异常向量。
ARM处理器内核一共定义了七种异常:复位异常、未定义指令异常、软中断异常、预取指终止异常、数据终止异常、IRQ中断异常、IRQ快速中断异常。
七种运行模式中,有两个模式对应于中断:中断模式,快中断模式。
快中断的优先级比一般中断高。
当发生中断和快中断时,程序计数器(PC)将会跳到指定的地址开始执行,为执行相应的中断服务提供了可能。
下面代码是建立异常向量表的过程,其中第一个指令通常都是存放在主存的零地址。异常向量表存放的全是汇编跳转指令,这些指令从主存的零地址(0x0)开始连续存储在内存中(每条指令长度为4B)。
B Reset ;
b Undef ;
b SWI ;
b PreAbort ;
b DataAbort ;
b. ;
b IRQ ;中断模式的入口地址,当产生中断后,PC会自动通过硬件机制跳转到该地址,然后执行该地址开始的代码
b FIQ ;快速中断模式的入口地址,当产生中断后,PC会自动通过硬件机制跳转到该地址,然后执行该地址开始的代码
代码中的标识符Reset…等都代表了一个地址,该地址就是跳转指令需要跳转的地址,指向各异常服务程序的起始地址。这些标识符的定义和使用在不同的汇编器下是不同的。
当发生对应的异常时,PC将通过硬件机制跳转到相应异常向量对应的地址开始执行,因为是由硬件机制实现的,所有跳转的地址都是在CPU芯片生产时就确定且无法更改,并且这些跳转指令都是单条指令连续在一起的,所以无法在原地实现中断服务程序,这也是异常向量表中的代码全是跳转指令的原因。
当产生异常时,通过硬件跳转到一个确定的地址,再通过跳转指令跳转到一个异常处理程序的起始地址。
不同嵌入式处理器集成的硬件设备不一样。
在Mini2440开发板上,如果设置从NAND Flash启动,则最开始的启动代码不能超过4KB,所以在这个阶段的硬件初始化最好只对CPU和主存进行初始化,使CPU能运行,将更为完整的硬件初始化代码从外存复制到主存中执行。
在硬件初始化阶段,是不希望有中断的打扰。
WTCON EQU 0x53000000
LDR R0,=WTCON
MOV R1,0X00000000
STR R1,[R0]
INTMSK EQU 0x4A000008
INTSUBMSK EQU 0x4A00001C
ldr r0,=INTMSK
ldr r1,=0xffffffff
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff
str r1,[r0]
在启动代码屏蔽中断后,若启动完了所加载的应用程序,需重新开启中断。
设定设置时钟与PLL时,需要用到的寄存器主要有锁定时间计数寄存器LOCKTIME、MPLL配置寄存器MPLLCON、UPLL配置寄存器UPLLCON、时钟分频控制寄存器CLKDIVN。
初始化BANK
S3C2440A芯片配置了8个BANK,每一个BANK大小最大为128MB。
BANK6的起始地址为0x30000000,Mini2440开发板的内存挂载在BANK6,所以Mini2440的内存起始地址为0x30000000,这也是CPU启动后需要将应用程序加载到的地方。
在应用程序被加载到内存并运行前,必须对BANK进行初始化,确定8个BANK的内存分布,完成内存刷新频率等设置。
ARM有七种不同的运行模式,而各模式都共同享有公用的通用寄存器,所以在模式切换后,有必要将前一种模式的通用寄存器上的数据保存以便模式切换后能正常运行。
这时,不同模式下的堆栈就发挥了保护现场的作用。
ARM在不同模式下都有专用的堆栈指针,所以每个模式的堆栈初始化只需要将堆栈指针赋值为预先确定好的一个固定的、与各模式相对应的地址。
在Mini2440开发板复位和上电时,ARM920T处于管理模式(Supervisor Mode),又因为在进行各模式的堆栈初始化时,需要分别进入各个工作模式分别初始化,所以将管理模式的系统堆栈初始化放在最后。