以一款简单、易学的嵌入式开发平台ARM Mini2440(CPU是三星ARM 9系列的ARM S3C2440)为例,通过具体代码实现,介绍如何从裸板入手设计简单的轮询系统、前后台系统,以及如何一步一步在ARM Mini2440上编写RTOS内核,到如何让RTOS内核支持多核嵌入式处理器。
aCoral是2009年创建的开源的、支持多核的RTOS。
aCoral
目前aCoral包括五大模块
- 内核:由电子科技大学实时计算实验室编写。
- 文件系统
- 轻型TCP/IP
- GUI:来自开源嵌入式Linux图形系统LGUI。
- 简单应用。
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,则让其执行,否则跳过该函数去执行提出请求的下一个函数,这种判定某个函数是否满足执行条件的过程,称为轮询。
调度
根据程序结构特点,基本轮询系统具有以下工作特点:系统完成一个轮询的时间取决于轮循环中需要执行的函数的个数。循环的次序是静态固定的,在运行时不能进行动态调整。
这种特点决定了轮询系统在诸如多路采样系统、实时监控系统等嵌入式应用中可以得到广泛使用,但是所有函数必须顺序执行,不区分函数的重要程度,系统也无法根据应用的实际需要灵活地调整对函数的使用粒度。
- 优先权调度
克服以上缺陷的最简单办法就是允许优先级高的函数被多次重复调度,即在轮询环中增加重要函数的访问CPU的次数。这样,每一次轮询中,相对重要的函数获得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总线通信,主机通过点名方式向各从机发送命令,实现对系统的控制。同时,对从机不断地轮询,监视从机的状态,接收从机的请求或信息。
搭建开发环境
- ARM9 Mini2440开发板
- 仿真器J-LINK
开发人员的程序是在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下创建工程了,具体步骤如下:
- 新建一个ADS工程,并为其命名为MINE,然后新建file文件my_2440_init.s(.s表示该文件由汇编语言编写,因为此时开发板尚未初始化,只支持汇编语言),在创建好后,就将该文件添加到刚建立的MINE工程中,并在debug、release和debugrel三个选项上打√,工程路径和命名都不能有中文。
- 设置“my_2440_init.s”文件编译链接后生成的可执行文件的格式。将ADS菜单“Edit” - > DebugRel Settings -> Linker -> ARM formELF中的“output format”输出形式设定为“Plain binary”文件类型,后缀为“.bin”。
- 在my_2440_init.s文件中输入将要编写的轮询系统汇编代码。如果代码编写完成,并且编译成功,可以在MINE工程文件的MINE_data -> DebugRel文件夹中发现一个生成了的MINE.bin的可执行文件。
- 生成bin文件后,就可通过J-LINK将该文件“MINE.bin”烧写到Mini2440的开发板中。此时,开发板设置为“NORFlash”启动,接上J-LINK后,插在底板的JTAG插座上,J-LINK另一头接PC的USB接口。
- 开发板上电。
- 打开之前在PC上安装的J-flashARM工具,开始->所有程序->SEGGER->JLINK ARM V4.08 ->JLINK ARM,该界面能将开发板上从0x00000000开始的内存数据显示出来。
- 烧写完成后断电,再取下J-LINK.
- 再重新上电,如果MINE.bin程序正确无误,Mini2440上将会有所显示,例如LED灯被点亮,当然Mini2440的显示结果与开发人员的程序有关。
启动Mini2440
交叉开发环境搭建好后,便可动手编码了,编码的首要工作是:用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快速中断异常。
- 复位异常。当开发板复位时,S3C2440A的ARM920T核将当前正在运行程序的CPSR与PC存入管理模式下的SPSR与LR中。然后,强制将CPSR的M[4:0]位写为10011(管理模式),并将CPSR的I位与F位置1(屏蔽IRQ与FIQ),将CPSR的T位清0(进入ARM指令模式)。之后强制将PC的值设为0x0,让CPU从0x0开始取指执行命令,这时CPU运行在ARM状态。
- 未定义指令异常,当ARM920T遇到一个无法处理的指令时,未定义指令异常发生。当前运行程序的下一条指令的地址将会被保存到相应模式下的LR,CPSR被保存到相应的SPSR中,然后CPSR的M[4:0]被设置为相应的模式值,并且PC被强制赋值为0x4(ARM 9是32位的指令集,每条指令占用4B的空间)。
- 软中断异常。当软中断指令SWI被执行时,软中断异常发生。当软中断异常发生时,PC值取值为0x8。软中断是用户模式切换到特权模式的唯一途径,软中断会将程序带到管理模式下,这样程序就可以对更多的寄存器,特别是CPSR有了修改的权利。软中断通常用来实现特权模式下的系统调用功能。
- 预取指终止异常。当在一条指令的预取指片段执行失败(通常为内存读取错误时),预取指终止异常发生,PC取值为0xC。预取指终止异常只有当该指令进入了流水线时(也就是该指令被预取指时)发生,但是如果引发该异常的指令没有被执行,程序不会终止去处理异常。
- 数据终止异常。当在读出数据时发生内存错误时,数据终止异常发生。PC取值为0x10。
- IRQ中断异常。当CPU接受到外部设备发出的中断请求时,IRQ中断异常发生。PC取值为0x18。IRQ中断异常会在FIQ快速中断异常中被屏蔽。
- FIQ快速中断异常。FIQ快速中断异常是为数据传输与处理提供的快速中断通道。当FIQ发生时,PC取值为0x1C,FIQ快速中断异常将进入FIQ快速中断模式,在此模式下,ARM提供了更多的专用寄存器,为中断处理节省了寄存器保护入栈的时间。
七种运行模式中,有两个模式对应于中断:中断模式,快中断模式。
快中断的优先级比一般中断高。
当发生中断和快中断时,程序计数器(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能运行,将更为完整的硬件初始化代码从外存复制到主存中执行。
在硬件初始化阶段,是不希望有中断的打扰。
硬件初始化流程
- 关闭看门狗。看门狗WDT(Watch Dog Timer)是S3C2440A提供的一个计数器。看门狗是一种防止程序跑飞的监测机制,它需要在一个设定的时间内向其发送一个喂狗的脉冲信号,如果超出了设定值,看门狗就会发出一个复位信号,让程序复位。如果不关闭看门狗,启动程序在一定的时间内就需要去“喂狗”,增加了代码量,还浪费了系统的宝贵资源。因为在启动代码中不会涉及大量运算和长时间循环。
WTCON EQU 0x53000000
LDR R0,=WTCON
MOV R1,0X00000000
STR R1,[R0]
- 屏蔽中断
在启动代码的开始阶段,屏蔽中断是显而易见的,因为CPU等硬件初始化是一切服务的基础,在没有完成初始化前,中断是没有意义的。其实,在复位异常发生后,ARM920T已经通过硬件机制将CPSR的I为与F为置为1,屏蔽了中断。为了代码更具有通用性、严整性与逻辑性,通过设置中断控制器相关寄存器来屏蔽中断。
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
S3C2440A芯片为用户提供了多个CPU频率与多个相应的AHB(Advanced High-performance Bus)总线频率和APB(Advanced Peripheral Bus)总线频率。
在启动代码中,选择所需的频率来配置CPU是必须的,不同的芯片时钟设置方法也不一样。
在S3C2440A芯片中,所有时钟的时钟源均来自于一个12MHz的外部晶体振荡器,CPU的高频率是通过锁相环PLL(Phase Locked Loop)电路模块将12MHz频率提升后得到的,AHB与APB的时钟频率是通过设置与CPU时钟的比值得到的。
有两个PLL:MPLL和UPLL。
MPLL是用于CPU及其他外围期间的,UPLL是用于USB的,MPLL产生三种频率:FCLK、HCLK、PCLK,其用途分别为:
(1)FCLK为CPU提供时钟信号,S3C2440最大支持400MHz的主频。
(2)HCLK为AHB总线提供时钟信号,主要用于高速外设,如内存控制器、中断控制器、LCD控制器、DMA等。
(3)PCLK为APB总线提供时钟信号,主要用于低速外设,如看门狗、UART控制器等。
UPLL专门用于驱动USB host/Device,并且驱动的频率必须为48MHz。
必须先设定UPLL,才能设定MPLL,而且中间需要若干空指令(NOP)的间隔。
设定设置时钟与PLL时,需要用到的寄存器主要有锁定时间计数寄存器LOCKTIME、MPLL配置寄存器MPLLCON、UPLL配置寄存器UPLLCON、时钟分频控制寄存器CLKDIVN。
- LOCKTIME寄存器。LOCKTIME寄存器使用与设置的原因是与PLL的硬件特征有关的。当PLL启动后需要一定的时间才能稳定工作。所以在向CPU与外部USB总线提供时钟频率之前,需要锁定一段时间等待PLL能正常工作。根据S3C2440A的数据手册,MPLL与UPLL所需的等待时间应该大于300us,将其设置为默认值0xFFFF即可。
- MPLLCON与UPLLCON寄存器。这两个寄存器用来设置CPU频率与USB频率相对外部晶体振荡器频率的倍数参数的,即设置相应频率大小的。两个PLL在MPLLCON与UPLLCON寄存器值没有重新写入之前是不会工作的。
- CLKDIV寄存器。设置CPU、AHB和APB频率的比值,当AHB与CPU的频率比不为1:1时,CPU的总线模式应该从快速总线模式切换到异步总线模式。如果不切换,CPU将以AHB的频率运行。
初始化BANK
S3C2440A芯片配置了8个BANK,每一个BANK大小最大为128MB。
BANK6的起始地址为0x30000000,Mini2440开发板的内存挂载在BANK6,所以Mini2440的内存起始地址为0x30000000,这也是CPU启动后需要将应用程序加载到的地方。
在应用程序被加载到内存并运行前,必须对BANK进行初始化,确定8个BANK的内存分布,完成内存刷新频率等设置。
初始化堆栈
ARM有七种不同的运行模式,而各模式都共同享有公用的通用寄存器,所以在模式切换后,有必要将前一种模式的通用寄存器上的数据保存以便模式切换后能正常运行。
这时,不同模式下的堆栈就发挥了保护现场的作用。
ARM在不同模式下都有专用的堆栈指针,所以每个模式的堆栈初始化只需要将堆栈指针赋值为预先确定好的一个固定的、与各模式相对应的地址。
在Mini2440开发板复位和上电时,ARM920T处于管理模式(Supervisor Mode),又因为在进行各模式的堆栈初始化时,需要分别进入各个工作模式分别初始化,所以将管理模式的系统堆栈初始化放在最后。