


Click to edit Master text styles,,Second level,,Third level,,Fourth level,,Fifth level,,*,Click to edit Master title style,,,,,,,,XUPT,嵌入式系统,,原理与应用,Click to edit Master text styles,,Second level,,Third level,,Fourth level,,Fifth level,,Click to edit Master title style,,,,,Click to edit Master text styles,,Second level,,Third level,,Fourth level,,Fifth level,,Click to edit Master title style,,,*,第八章,基于,µC/OS-II,的嵌入式应用程序开发,,目 录,8.1,嵌入式应用程序开发的特点,,8.2 µC/OS-II,应用程序结构分析,,8.3 µC/OS-II,程序设计技术,,8.4 µC/OS-II,在,ARM,微处理器上的移植,,8.5,应用程序设计及实例,2,8.1,嵌入式应用程序开发的特点,8.1.1,开发调试环境的建立,,,在嵌入式系统开发中,典型的调试环境一般通常由三部分构成:一是通用计算机(用来运行调试软件,称为调试主机);二是协议转换器(连接调试主机发出的高级命令与微处理器,JTAG,接口的低级命令之间的接口);三是调试目标。
硬件调试环境如下图:,,3,8.1,嵌入式应用程序开发的特点,8.1.2,基于嵌入式处理器的直接编程技术,,如果针对硬件电路直接设计应用程序,那么应用程序的代码必须包括以下几个部分:,,①启动部分:硬件加电后首先运行(硬件检测和资源分配),,②处理器管理部分:实现处理器状态转换和寄存器使用等,,③外围设备访问部分:外围设备(如串口)的初始化等,,④程序功能部分:实现程序所要完成的具体功能,,这样设计程序对设计人员的能力要求是很高的4,8.1,嵌入式应用程序开发的特点,8.1.3,基于嵌入式操作系统的编程技术,,随着嵌入式微处理器和程序设计技术的发展,人们把一个大的程序从功能上可划分为三个部分:,,① 板级支持包(,BSP,):其目的是屏蔽下层硬件,主要由,Boot Loader,和,Drivers,两部分构成② 嵌入式实时操作系统(,RTOS,)③ 应用程序:基于相应操作系统、在相应开发环境下设计的并最终运行在相应目标机上的程序这里,可由掌握不同技术的人去设计不同的部分,并在三部分之间设计相应的接口以供三部分之间相互调用5,8.2 µC/OS-II,应用程序结构分析,下面以一个简单实例来说明基于,µC/OS-II,操作系统设计的嵌入式应用程序的结构。
程序清单,8.1,两个,LED,交替闪烁,,#include “config.h” (1),,#define LED1 (1<<18) //,定义,LED1,,,P1.18,控制,LED1 (2),,#define LED2 (1<<19) //,定义,LED2,,,P1.19,控制,LED2 (3),,#define TASK_STACK_SIZE 128 //,定义用户任务的堆栈长度,(4),,OS_STK task1Stk[TASK_STACK_SIZE]; //,定义任务,LED1,的堆栈,(5),,OS_STK task2Stk[TASK_STACK_SIZE]; //,定义任务,LED2,的堆栈,(6),,,void task1(void *pdata); //LED1,任务函数声明,(7),,void task2(void *pdata); //LED2,任务函数声明,(8),6,8.2 µC/OS-II,应用程序结构分析,7,int main(void) //,主函数,(9),,{,,OSInit(); //,初始化,µC/OS-II (10),,OSTaskCreate(task1,(void *)0, //,创建,LED1,任务,, //,优先级为,4 (11),,OSTaskCreate(task2,(void *)0, //,创建,LED2,任务,, //,优先级为,5 (12),,OSStart(); //,启动,µC/OS-II,,任务开始执行,(13),,return(0); (14),,},,// main,函数中所调用的,OSInit,、,OSTaskCreate,和,OSStart,,,是,,//µC/OS-II,操作系统提供的,API,函数。
// main,函数并没有直接调用,task1,和,task2,,只是在,main,中调用,,//OSTaskCreate,创建两个任务时分别把,task1,和,task2,作为参数8.2 µC/OS-II,应用程序结构分析,8,void task1(void *pdata) //,任务,LED1,函数定义,(15),,{,,pdata=pdata; //,防止出现编译警告,(16),,TargetInit(); //,目标板初始化,包括初始化中断系统,(17),,PINSEL2 &=,~,0x08; //,选择管脚,P1.18,~,25,为,GPIO (18),,IO1DIR |= 0x03<<18; //,设置,LED(P1.18,、,P1.19),为输出,(19),,IO1SET = 0x03<<18; //LED1,和,LED2,熄灭,(20),,while(1) //,超级循环,(21),,{,,IO1CLR=LED1; //,点亮,LED1 (22),,OSTimeDly(OS_TICKS_PER_SEC/4); //,延时,1/4,秒,(23),,IO1SET=LED1; //,熄灭,LED1 (24),,OSTimeDly(OS_TICKS_PER_SEC/4); //,延时,1/4,秒,(25),,},,},8.2 µC/OS-II,应用程序结构分析,9,void task2(void *pdata) //,任务,LED2,函数定义,(26),,{,,pdata=pdata; (27),,while(1) //,超级循环,(28),,{,,IO1CLR=LED2; //,点亮,LED2 (29),,OSTimeDly(OS_TICKS_PER_SEC/3); //,延时,1/3,秒,(30),,IO1SET=LED2; //,熄灭,LED2 (31),,OSTimeDly(OS_TICKS_PER_SEC/3); //,延时,1/3,秒,(32),,},,},,//task1,和,task2,中的“,pdata=pdata;”,是防止在编译时出现警告。
//task1,和,task2,中调用的,OSTimeDly,,是,µC/OS-II,提供的,API,函数// PINSEL2,、,IO1DIR,等都是宏定义,代表寄存器地址,,给该寄,,//,存器赋值可以实现不同的功能,它们都在(*,.h,)文件中定义8.2 µC/OS-II,应用程序结构分析,10,上述程序的功能是让,2,个,LED,灯以不同的速度闪烁,其执行流程如下图:,,8.2 µC/OS-II,应用程序结构分析,11,关于该程序的执行过程作以下说明:,,①该程序首先从,main,函数开始执行,初始化,µC/OS-II,,创建任务,task1,和,task2,,最后启动,µC/OS-II,中任务调度程序的执行②任务调度程序、任务,task1,和,task2,、空闲任务(,µC/OS-II,中)等的交替执行当任务调度程序执行后,任务调度程序会选择当前处于就绪态的最高优先级的任务来执行;当正在执行的任务调用延时函数延时时,就会挂起该任务,程序又会执行调度程序来调度其它任务的执行③main,函数最后一条语句是,return(0),,但程序一直在任务调度程序、任务,task1,和,task2,等之间交替执行,永远也不会返回到,main,函数,所以永远也不会执行,return(0),。
由以上分析可知,在使用,µC/OS-II,操作系统的嵌入式应用程序中,程序的基本结构除,main,函数外,还包括一个个任务函数,当然也包括非任务函数设计程序的任务主要是划分和设计一个个任务函数8.3 µC/OS-II,程序设计技术,8.3.1,任务的划分与设计,,⒈ 任务的特性,,,任务的基本特性有:动态性、独立性和并发性,,① 任务的动态性是指:在程序的运行过程中,各个任务的状态是动态变化的这些状态有就绪态、运行态和等待态等② 任务的独立性是指:程序中的所有任务在逻辑上都是平等的它们的执行是由调度程序调度来实现的,这样,在每个任务看来,,CPU,为自己独占任务之间要传输信息时必须通过第三方来完成,如消息邮箱等③ 任务的并发性是指:所有任务共有一个,CPU,,但在某一时刻,一个,CPU,只能运行一个任务高优先级的任务可以剥夺另一个正在运行的低优先级任务的运行权而进入运行状态高优先级任务在运行一段时间后必须将自己挂起(如调用延时函数等) 以让出,CPU,而让处于就绪态的低优先级的任务得到执行这样所有任务的运行时间就会相互重叠,表面上看起来好象同时运行一样12,8.3 µC/OS-II,程序设计技术,⒉ 任务的划分方法,,在进行任务划分时,可以有不同的方案,但其要达到的目标都是一致的。
首先要满足系统对“实时性”的要求,其次要使任务数目合理和简化软件系统,最后要降低系统对资源的需求任务的划分有下列一些基本原则:,,① 设备依赖性任务的划分:以,CPU,为中心,将与各种输入,/,输出设备相关的功能 分别划分为独立的任务(如键盘任务、显示任务等)② 关键任务的划分:“关键性”是指某种功能在应用系统中的重要性若该功能不能正常实现,将会造成重大影响,如火灾报警中传感器信号的检测③ 紧迫任务的划分:“紧迫性”是指某种功能必须在规定的时间内得到运行,并在规定的时刻前执行完毕此外,还有:数据处理任务的划分、功能聚合任务的划分、触发条件相同任务的划分、运行周期相同任务的划分、顺序操作任务的划分等原则这些原则只是一般性原则,设计任务时必须具体问题具体分析13,8.3 µC/OS-II,程序设计技术,⒊ 任务函数的代码结构,,在任务函数中,必须至少调用一次操作系统的服务函数,否则低优先级的任务将永远无法得到运行按照执行方式可以将任务函数的结构分为三类① 单次执行的任务:此类任务在创建后只执行一次,执行结束后即自行删除其任务函数的代码结构如下:,,void MyTask(void *pdata) //,单次执行的任务函数,,{,,,进行准备工作的代码;,,任务实体代码;,,调用任务删除函数;,//,调用,OSTaskDel(OS_PRIO_SELF),,},,“进行准备工作的代码”完成各项准备工作,如定义和初始化变量等;“任务实体代码”完成该任务的具体功能,通常包含对系统函数的调用,除若干临界段代码(中断被关闭)外,其它代码均可以被中断,用以保证高优先级的就绪任务能够及时得到运行;“调用任务删除函数”将自己删除。
14,8.3 µC/OS-II,程序设计技术,② 周期性执行的任务,,此类任务在创建后按一个固定的周期来执行其任务函数的结构如下:,,void MyTask(void *pdata) //,周期性执行的任务函数,,{,,,进行准备工作的代码;,,,while (1) //,无限循环,,{,,任务实体代码;,,调用系统延时函数;,//,调用,OSTimeDly(),或,OSTimeDlyHMSM(),,},,},,“调用系统延时函数”使自己挂起,把,CPU,的控制权交给操作系统,由操作系统(中的调度程序)来调度其它已经就绪的最高优先级的任务运行当延时时间到后,该周期性任务重新进入就绪状态15,8.3 µC/OS-II,程序设计技术,③ 事件触发执行的任务,,此类任务在创建后,很快可以获得运行权,但实体代码的执行需要等待某种事件的发生,在相关事件发生之前,该任务则被,µC/OS-II,挂起其结构如下:,,void MyTask(void *pdata) //,事件触发执行的任务函数,,{,,,进行准备工作的代码;,,,while (1) //,无限循环,,{,,调用获取事件的函数;,//,如等待信号量等,,任务实体代码;,,,},,},,“调用获取事件的函数”调用,µC/OS-II,提供的获取某种事件(如信号量)的函数,来等待另外一个任务(或,ISR,)发出的信息,此后该任务处于挂起状态;当另外一个任务(或,ISR,)调用了,µC/OS-II,提供的通信函数发出相关信息时,,µC/OS-II,就使该任务进入就绪状态,并且通过任务调度,使该任务的实体代码得到执行。
相关事件发生一次,任务实体代码就执行一次16,8.3 µC/OS-II,程序设计技术,8.3.2,任务间的行为同步方法,,在实时操作系统,µC/OS-II,的支持下,系统的整体功能是通过各个任务(包括,ISR,)的协同运行来实现的一个任务的运行,往往需要和其它的任务配合才能达到预期的效果,任务之间的这种配合和协调关系就称为任务间的行为同步µC/OS-II,所提供的控制任务间行为同步的通信手段有:计数信号量、事件标志组、消息邮箱和消息队列通常使用的行为同步方法有:,,① 两个任务之间的单向同步:即一个任务为控制方,它发出控制信息;而另一个任务为被控制方,它获得控制方发出的控制信息后即进入就绪状态这可使用信号量来实现17,8.3 µC/OS-II,程序设计技术,② 两个任务之间的双向同步:即两个任务同为控制方和被控制方在这种情况下,首先一个任务必须为控制方,它发出控制信息后就变为被控制方,此时其需等待另一个任务发出的控制信息后才能继续运行;而另一个任务首先为被控制方,当它获得控制方发出的控制信息后才能运行,并变为控制方这可使用消息邮箱来实现③ 一个任务同步多个任务:即一个任务为控制方,它发出控制信息,来控制多个任务的执行。
此时可采用具有消息分发功能的通信机制(当然也可采用多个通信工具)来实现④ 两个以上任务同步一个任务:即多个任务为控制方,它们发出控制信息来控制一个任务的执行此时可采用“事件标志组”来实现18,8.3 µC/OS-II,程序设计技术,8.3.3,共享资源的同步方法,,被两个以上并发程序单元(任务或,ISR,)访问的资源称为共享资源(如全局变量、外设等)任务对共享资源进行访问的代码段落称为关键段落各个任务访问同一共享资源的关键段落必须互斥,才能保障共享资源信息的可靠性和完整性这种使得不同任务访问共享资源时能够确保共享资源信息可靠和完整的措施称为共享资源同步实现共享资源同步的方法有:关中断、关调度、使用互斥信号量等中断有可能会引起任务切换,使某个低优先级的任务挂起,使某个高优先级的任务得到执行如果这两个任务对同一个共享资源进行访问,这就有可能引起错误的结果此时可以关闭中断当共享资源的使用者全部是任务(即不包含,ISR,)时,就可以采用“关调度”的方法来访问共享资源关调度可使,µC/OS-II,的任务调度器停止工作,不能进行任务切换,从而保证关键段落代码的执行不会受到其它任务的干扰当需要访问的共享资源比较复杂,且访问过程比较费时时,关中断和关调度措施都会严重影响到系统的实时性。
当该共享资源的使用者全部是任务(即不包含,ISR,)时,就可以采用互斥信号量的方法来访问这个共享资源8.3 µC/OS-II,程序设计技术,8.3.4,任务间的数据通信方法,,ISR,与任务函数在形式上与普通,C,函数没有什么区别ISR,的运行是由异步事件引起的,任务函数的运行是由,µC/OS-II,中的调度器调度的,它们之间不能直接调用,其数据通信是通过以下几种方法实现的① 全局变量:,提供数,据的任务或,ISR,向全局变量中写数据,使用数据的任务或,ISR,从全局变量中读数据,对全局变量的访问必须遵循“共享资源同步”的规则任务或,ISR,向全局变量中写入数据后,并不能通知相关的任务,也就是不能实现“行为同步”② 内存数据块:当需要传输的数据量很大时,采用内存数据块来存放这些数据是最方便的内存数据块是共享资源,也不能用来实现“行为同步”③ 消息邮箱:当每次发送的数据都要求接收方及时接收时,在数据通信的同时必然发生“行为同步”,此时可使用消息邮箱,并,要求接收消息的任务总是在等待消息④ 消息队列:消息队列是具有“行为同步”功能和缓冲功能的数据通信手段,它与消息邮箱的不同之处是可以存放多条消息。
20,8.4 µC/OS-II,在,ARM,微处理器上的移植,,所谓移植,就是使一个实时内核能够在某个微处理器或微控制器上运行µC/OS-Ⅱ,在设计时就已经充分考虑了可移植性8.4.1 µC/OS-II,对处理器的要求,,要使,µC/OS-Ⅱ,能正常运行,处理器平台必须满足以下要求:,,① 处理器的,C,编译器能够产生可重入代码② 用,C,语言就可以打开和关闭中断③ 处理器支持中断,并且能产生定时中断,(,通常在,10,至,100Hz,之间,),④ 处理器支持能够容纳一定量数据,(,一般是几千字节,),的硬件堆栈⑤ 处理器有将堆栈指针和其它,CPU,寄存器读出和存储到堆栈(或内存) 的指令LPC2000,系列微控制器(,ARM7,)可以满足第③、④和⑤点的要求,所使用的,ADS1.2,的,C,编译器可以满足第①和②点要求21,8.4 µC/OS-II,在,ARM,微处理器上的移植,,8.4.2 µC/OS-II,移植所涉及的文件,,移植,µC/OS-Ⅱ,涉及到三个文件:,,⒈,OS_CPU.H,文件,,OS_CPU.H,文件是操作系统移植头文件其中包括了用,#define,定义的与处理器相关的常量、宏和数据类型等。
其文件结构如下:,,…… //,数据类型,(,与编译器相关,),,,typedef,unsigned char INT8U; /*,无符号,8,位整数 *,/,,,typedef,unsigned,int,OS_STK; /*,堆栈入口宽度为,16,位 *,/,,…… //,与处理器相关的代码,,#define OS_ENTER_CRITICAL() ??? /*,禁止中断 *,/,,#define OS_STK_GROWTH 1 //,堆栈增长方向:,1=,向下,, 0=,向上,,……,22,8.4 µC/OS-II,在,ARM,微处理器上的移植,,⒉,OS_CPU_A.ASM,文件,,OS_CPU_A.ASM,是与处理器有关的汇编语言代码文件,,,主要进行任务切换它在,ADS,中的后缀名为“,.S”,,即文件名为,OS_CPU_A.S,其中要求用户编写的四个汇编语言函数为:,,①,OSStartHighRdy(),:,µC/OS-Ⅱ,启动时运行优先级最高的任务,,②,OSCtxSw(),:任务级的任务切换函数,,③,OSIntCtxSw(),:中断级的任务切换函数,,④,OSTickISR(),:时钟中断处理函数,,如果用户的编译器支持在,C,语言代码中插入汇编语言代码的话,用户就可以将所有与处理器相关的代码放到,OS_CPU_C.C,文件中,而不必再拥有一些分散的汇编文件。
23,8.4 µC/OS-II,在,ARM,微处理器上的移植,,⒊,OS_CPU_C.C,文件,,OS_CPU_C.C,是移植中要修改的,C,语言程序文件其中要求用户编写六个,C,语言函数:,,①,OSTaskStkInit(),:初始化任务的堆栈结构,,②,OSTaskCreateHook(),:创建任务钩子函数,允许用户扩展,µC/OS-Ⅱ,功能,,③,OSTaskDelHook(),:删除任务钩子函数,,④,OSTaskSwHook(),:任务切换钩子函数,,⑤,OSTaskStatHook(),:统计任务钩子函数,用来扩展统计任务的功能,,⑥,OSTimeTickHook(),:时钟节拍钩子函数,,,用户必须编写的唯一函数是,OSTaskStkInit(),,其它五个钩子函数是用来扩展相应的功能,必须声明但不一定要包含代码24,8.4 µC/OS-II,在,ARM,微处理器上的移植,,8.4.3 µC/OS-II,的移植过程及内容,,移植,µC/OS-Ⅱ,到一个具体处理器的过程如下:,,第一步:设置,OS_CPU.H,文件中的数据类型等,,① 定义与编译器无关的数据类型,,,µC/OS-Ⅱ,中没有使用与编译器相关的,C,中的,short,等数据类型,而是使用了如,INT16U,代表,16,位的无符号整数类型等。
所以要对,INT16U,等进行重新定义typedef,unsigned short INT16U; /*,无符号,16,位整数 *,/,,,② 用,#define,设置一个常量的值,,根据微处理器和,C,编译器支持的堆栈增长方向来定义,µC/OS-Ⅱ,中的堆栈增长方向常量,,#define OS_STK_GROWTH 1 /*,堆栈是从上往下增长的 *,/,,③ 用,#define,声明三个宏,,OS_ENTER_CRITICAL(),和,OS_EXIT_CRITICAL(),的功能分别是关中断和开中断而宏,OS_TASK_SW(),的功能是在任务级进行任务调度25,8.4 µC/OS-II,在,ARM,微处理器上的移植,,第二步:编写四个汇编语言函数,(OS_CPU_A.ASM),,① OSStartHighRdy(),,在调用,OSStart(),之前,用户必须至少已经建立了自己的一个任务当调用,OSStart(),时,它会调用,OSStartHighRdy(),运行优先级最高的任务②,OSCtxSw(),,该函数是任务级的任务切换函数,在任务因为被阻塞而主动请求或,CPU,调度时执行。
主要工作是先将当前任务的,CPU,现场保存到该任务堆栈中,然后获得就绪的最高优先级任务的堆栈指针,从该堆栈中恢复此,CPU,现场,使之继续执行,从而完成一次任务切换③,OSIntCtxSw(),,该函数是中断级的任务切换函数,在时钟中断,ISR,中发现有高优先级任务在等待时,不必返回被中断的任务,而是直接调度就绪的高优先级任务执行④,OSTickISR(),,该函数是时钟中断处理函数,主要负责处理时钟中断,调用系统实现的,OSTimeTick(),函数如果有等待时钟信号的高优先级的任务,则需要在中断级别上调用其执行26,8.4 µC/OS-II,在,ARM,微处理器上的移植,,第三步:用,C,语言编写六个简单的函数,(OS_CPU_C.C),,在这些函数中,,OSTaskStkInit(),是最重要的,其功能是初始化任务的栈结构OSTaskStkInit(),的代码如下:,,OS_STK *OSTaskStkInit(void (*task)(void *pd),void *pdata,OS_STK *ptos, INT16U opt),,{,,OS_STK *stk;,,opt = opt; // 'opt',没使用,作用是避免编译器警告,,stk = ptos; //,获取堆栈指针,,*stk = (OS_STK) task; /* pc */ //,建立任务环境,,ADS1.2,使用满递减堆栈,,*--stk = (OS_STK) task; /* lr */,,*--stk = 0; /* r12 */,,*--stk = 0; /* r11 */,,*--stk = 0; /* r10 */,,*--stk = 0; /* r9 */,,*--stk = 0; /* r8 */,,*--stk = 0; /* r7 */,,*--stk = 0; /* r6 */,,*--stk = 0; /* r5 */,27,8.4 µC/OS-II,在,ARM,微处理器上的移植,,*,--stk = 0; /* r4 */,,*--stk = 0; /* r3 */,,*--stk = 0; /* r2 */,,*--stk = 0; /* r1 */,,*--stk = (unsigned int) pdata; /* r0,,第一个参数使用,R0,传递 *,/,,*--stk = (USER_USING_MODE|0x00); /* CPSR,,允许,IRQ,FIQ,中断 *,/,,*--stk = 0; /*,关中断计数器,OsEnterSum; */,,return (stk);,,},,,除,OSTaskStkInit(),之外,其余函数必须声明,但不一定要包含代码。
第四步:测试,,一旦代码移植结束,下一步工作就是测试测试首先可以在没有应用程序的情况下测试,也就是让内核自己测试自己这样做有两个好处:第一,避免使问题复杂化;第二,如果出现问题,可以知道问题出在内核代码上其次可以运行一些简单的任务和时钟节拍中断服务例程最后,一旦多任务调度成功地运行了,再添加应用程序的任务就是非常简单的工作了28,8.4 µC/OS-II,在,ARM,微处理器上的移植,,8.4.4 µC/OS-II,的裁剪,,每个具体的嵌入式应用系统对实时操作系统的要求并不完全相同可以根据实际情况,把不需要的系统服务删除掉,即对其进行裁减对,μC/OS-II,的裁剪是在配置文件,OS_CFG.H,中进行的,其配置项是由一系列,#define constant,语句构成通过设置不同的配置项,可以达到保留或裁剪不同功能、降低系统对存储空间要求的目的下面给出几个常用的配置项:,,①,OS_MAX_EVENTS,:定义系统中最大的事件控制块数量②,OS_MAX_TASKS,:定义用户程序中最大的任务数其值不能大于,62,③,OS_LOWEST_PRIO,:设定系统中的任务最低优先级(,0,最高,,63,最低)。
④,OS_MAX_QS,:定义系统中最大的消息队列数⑤,OS_CPU_HOOKS_EN,:此常量设定是否在文件,OS_CPU_C.C,中声明对外接口函数29,8.5,应用程序设计及实例,要让,µC/OS-Ⅱ,能够在,ARM,处理器上运行,就要对其进行移植,编写移植代码;而要在,µC/OS-Ⅱ,操作系统基础上设计应用程序,并且让其能够在,ARM,处理器上运行,还需要做那些工作呢?,,当,ARM,芯片加电复位后,系统就会进入管理模式、,ARM,状态,其,PC,(,R15,)寄存器的初始值为,0x00000000,,此时系统从,0x00000000,处开始执行程序程序首先应该对硬件及其运行环境进行初始化,然后才能转入相应的功能处理程序去运行因此所设计程序分为两部分:硬件及其运行环境初始化部分和应用程序部分30,8.5,应用程序设计及实例,8.5.1,硬件及其运行环境初始化,,ARM,公司只设计内核,并不生产芯片,它把内核授权给其他厂商,其他厂商购买了授权后加入自己的外设,生产出各具特色的芯片依据这种情况,并不容易设计出统一的初始化代码在一般,32,位,ARM,应用系统中,软件大多数采用,C,语言进行编写,但为了能够进行系统初始化,通常会用一个汇编文件作为启动代码。
根据具体设计方法不同,一个应用系统中会包含多个文件下面分别介绍可能的各个文件⒈ 启动代码(,STARTUP.S,),,启动代码文件是用汇编语言编写的,内容包括:中断异常向量表的定义、各异常处理函数的定义、,ARM,控制器各工作模式堆栈的定义、芯片加密处理以及相关常量的定义、标识符的引入与导出等31,8.5,应用程序设计及实例,下面给出,LPC2131,的部分初始化代码:,,;,各模式堆栈大小的常量定义,,SVC_STACK_LEGTH EQU 0,,FIQ_STACK_LEGTH EQU 0,,IRQ_STACK_LEGTH EQU 256,,ABT_STACK_LEGTH EQU 0,,UND_STACK_LEGTH EQU 0,,;,各模式常量定义,,NoFIQ EQU 0x40 ;F,位,,Bit6,,NoInt EQU 0x80 ;I,位,,Bit7,,USR32Mode EQU 0x10 ;,用户模式:,10000,,SVC32Mode EQU 0x13 ;,管理模式:,10011,,SYS32Mode EQU 0x1f ;,系统模式:,11111,,FIQ32Mode EQU 0x11 ;FIQ,中断模式:,10001,,IRQ32Mode EQU 0x12 ;IRQ,中断模式:,10010,32,8.5,应用程序设计及实例,;,引入的外部标识符(在本文件中使用而在其他文件中定义),,IMPORT __use_no_semihosting_swi,,,IMPORT FIQ_Exception ;,快速中断异常处理程序,,,IMPORT TargetResetInit ;,目标板基本初始化,,,IMPORT __main ;C,语言主程序入口,,;,输出到外部的标识符(在本文件定义而在其他文件中可以使用),,EXPORT bottom_of_heap,,EXPORT StackUsr,,EXPORT Reset,,EXPORT __user_initial_stackheap,,,CODE32,,AREA vectors,CODE,READONLY,,ENTRY,33,8.5,应用程序设计及实例,;,中断异常向量表定义,,Reset,,LDR PC, ResetAddr ;0x00,:复位,,,LDR PC, UndefinedAddr ;0x04,:未定义地址,,,LDR PC, SWI_Addr ;0x08,:软件中断,,,LDR PC, PrefetchAddr ;0x0C,:预取指中止,,,LDR PC, DataAbortAddr ;0x10,:数据中止,,,DCD 0xb9205f80 ;0x14,:保留,,,LDR PC, [PC, #-0xff0] ;0x18,:,IRQ,中断,,,LDR PC, FIQ_Addr ;0x1C,:,FIQ,中断,,;,各异常处理程序地址,,ResetAddr DCD ResetInit ;,复位初始化处理程序地址,,UndefinedAddr DCD Undefined ;,未定义指令处理程序地址,,SWI_Addr DCD SoftwareInterrupt ;,软件中断处理程序地址,,PrefetchAddr DCD PrefetchAbort ;,预取指中止处理程序地址,,DataAbortAddr DCD DataAbort ;,数据中止处理程序地址,34,8.5,应用程序设计及实例,Nouse DCD 0 ;,未使用,,IRQ_Addr DCD 0 ;IRQ,中断,在“,LDR PC,[PC, #-0xff0]”,中处理,,FIQ_Addr DCD FIQ_Handler ;FIQ,中断处理程序地址,,;,各异常处理程序、开,/,关中断处理程序,(SWI),定义,,Undefined ;,未定义指令,,,B Undefined ;,死循环,,PrefetchAbort ;,取指令中止,,,B PrefetchAbort ;,死循环,,DataAbort ;,取数据中止,,,B DataAbort,,FIQ_Handler ;,快速中断,,,STMFD SP!, {R0-R3, LR} ;,寄存器,R0-R3,,,LR,入栈,,,BL FIQ_Exception ;,调用,FIQ,异常处理程序(,Target.c,),,,LDMFD SP!, {R0-R3, LR} ;,寄存器,R0-R3,,,LR,出栈,,,SUBS PC, LR, #4 ;,计算返回地址,35,8.5,应用程序设计及实例,SoftwareInterrupt ;,软中断,中断号为,0,~,3,,CMP R0, #4 ;,判断传过来的参数是否大于,4,,LDRLO PC, [PC,R0,LSL #2] ;,如果小于,4,,参数正确,进行查表,,,MOVS PC, LR ;,否则,参数出错,返回,,SwiFunction,,DCD IRQDisable ;0,号调用,禁止,IRQ,中断,,,DCD IRQEnable ;1,号调用,使能,IRQ,中断,,,DCD FIQDisable ;2,号调用,禁止,FIQ,中断,,,DCD FIQEnable ;3,号调用,使能,FIQ,中断,,IRQDisable ;,禁止,IRQ,中断,,,MRS R0, SPSR ;,读取,SPSR,的值,,,ORR R0, R0, #NoInt ;,设置关,IRQ,中断(置位,I,位),,,MSR SPSR_c, R0 ;,回写,SPSR,,MOVS PC, LR ;,返回,36,8.5,应用程序设计及实例,IRQEnable ;,使能,IRQ,中断,,,MRS R0, SPSR ;,读取,SPSR,的值,,,BIC R0, R0, #NoInt ;,设置开,IRQ,中断(清零,I,位),,,MSR SPSR_c, R0 ;,回写,SPSR,,MOVS PC, LR ;,返回,,FIQDisable ;,禁止,FIQ,中断,,,MRS R0, SPSR ;,读取,SPSR,的值,,,ORR R0, R0, #NoFIQ ;,(置位,F,位),,,MSR SPSR_c, R0 ;,回写,SPSR,(设置关,FIQ,中断),,,MOVS PC, LR ;,返回,,FIQEnable ;,使能,FIQ,中断,,,MRS R0, SPSR ;,读取,SPSR,的值,,,BIC R0, R0, #NoFIQ ;,(清零,F,位),,,MSR SPSR_c, R0 ;,回写,SPSR,(设置开,FIQ,中断),,,MOVS PC, LR ;,返回,37,8.5,应用程序设计及实例,;,各模式堆栈初始化,,InitStack ;,此时禁止,IRQ,(,I=1,)和,FIQ,(,F=1,),且为,ARM,状态(,T=0,),,,MOV R0, LR,,MSR CPSR_c, #0xd3 ;,设置管理模式堆栈,,,LDR SP, StackSvc,,MSR CPSR_c, #0xd2 ;,设置中断模式堆栈,,,LDR SP, StackIrq,,MSR CPSR_c, #0xd1 ;,设置快速中断模式堆栈,,,LDR SP, StackFiq,,MSR CPSR_c, #0xd7 ;,设置中止模式堆栈,,,LDR SP, StackAbt,,MSR CPSR_c, #0xdb ;,设置未定义模式堆栈,,,LDR SP, StackUnd,,MSR CPSR_c, #0xdf ;,设置系统模式堆栈,38,8.5,应用程序设计及实例,;,切换到系统模式之后,除非进行模式切换,否则将在系统模式下运行。
LDR SP, =StackUsr,,MOV PC, R0 ;,返回,,;,复位异常处理程序,,ResetInit,,BL InitStack ;,初始化堆栈,,,BL TargetResetInit ;,目标板基本初始化,(target.c),,B __main ;,跳转到,C,语言入口,,;,库函数初始化堆和栈,不能删除,,__user_initial_stackheap,,LDR R0,=bottom_of_heap,,MOV PC,LR,,StackSvc DCD SvcStackSpace+(SVC_STACK_LEGTH - 1)* 4 ;,管理模式堆栈,,StackIrq DCD IrqStackSpace+(IRQ_STACK_LEGTH - 1)* 4 ;IRQ,模式堆栈,,StackFiq DCD FiqStackSpace+(FIQ_STACK_LEGTH - 1)* 4 ;FIQ,模式堆栈,,StackAbt DCD AbtStackSpace+(ABT_STACK_LEGTH - 1)* 4 ;,中止模式堆栈,,StackUnd DCD UndtStackSpace+(UND_STACK_LEGTH - 1)* 4 ;,未定义模式堆栈,39,8.5,应用程序设计及实例,;,芯片加密处理,,,IF :DEF: EN_CRP,,IF . >= 0x1fc,,INFO 1,"\nThe data at 0x000001fc must be 0x87654321.\nPlease delete some source before this line.",,ENDIF,,CrpData,,WHILE . < 0x1fc,,NOP ;,循环用,NOP,填充,直到,0x1fc,,WEND,,CrpData1,,DCD 0x87654321 ;,当此数为,0x87654321,时,用户程序被保护,,,ENDIF,40,8.5,应用程序设计及实例,;,各模式堆栈空间分配,,,AREA MyStacks, DATA, NOINIT, ALIGN=2 ;,通过分散加载文件定位,,SvcStackSpace SPACE SVC_STACK_LEGTH * 4 ;,管理模式堆栈空间,,IrqStackSpace SPACE IRQ_STACK_LEGTH * 4 ;,中断模式堆栈空间,,FiqStackSpace SPACE FIQ_STACK_LEGTH * 4 ;,快速中断模式堆栈空间,,AbtStackSpace SPACE ABT_STACK_LEGTH * 4 ;,中止模式堆栈空间,,UndtStackSpace SPACE UND_STACK_LEGTH * 4 ;,未定义模式堆栈空间,,,AREA Heap, DATA, NOINIT ;Heap,通过分散加载文件定位,,bottom_of_heap SPACE 1,,AREA Stacks, DATA, NOINIT ;Stacks,通过分散加载文件定位,,StackUsr,,END,41,8.5,应用程序设计及实例,从前面知道,,ARM,芯片复位后,系统进入管理模式、,ARM,状态,此时,PC,寄存器的值为,0x00000000,,系统就从,0x00000000,处开始执行程序。
而,0x00000000,处放置着异常向量表,程序将根据异常向量表进行跳转其执行过程如下:,,① 芯片根据异常处理程序地址表,得到复位处理程序(,ResetInit,)的地址,并跳转到复位处理程序(,ResetInit,)处去执行② 调用,InitStack(),函数,初始化,ARM,处理器各工作模式的堆栈③ 调用目标板初始化函数(在,TARGET.C,中定义),初始化目标板④ 跳转到用户,C,程序入口,main(),处,执行用户程序在芯片启动过程中,在堆栈初始化(,InitStack(),函数)的末尾,处理器切换到系统模式,因而用户程序是在系统模式下运行的42,8.5,应用程序设计及实例,⒉ 目标板初始化(,TARGET.C,),,目标板初始化代码文件是用,C,语言编写的,其内容主要包括:启动代码调用的初始化函数、用户调用的初始化函数、一些异常处理函数以及一些系统库函数的实现等⒊ 有关的头文件(*,.H,),,要设计一个系统,为了进行必要类型定义、常量与变量声明以及函数声明等,会有各种各样的头文件如下:,,① 用户配置文件(,config.h,):包含一些类型定义和系统时钟定义②,target.h,文件:包含一些特殊定义和开,/,关,IRQ,中断、,FIQ,中断的函数声明。
根据所用的目标板和编译器的要求来修改该文件③,LPC2294.h,文件:包含,LPC2000,系列芯片的特殊寄存器定义文件,其中包含特殊寄存器的定义及固件程序函数的声明43,8.5,应用程序设计及实例,44,⒋ 分散加载文件(*,.scf,),,有时候希望把不同的代码放在不同的存储空间上,也就是生成的映像文件需要包含多个域,每个域在加载和运行时可以有不同的地址要生成这样的映像文件,必须通过某种方式告诉连接器相关的地址映射关系在,ADS,中,可以通过分散加载机制实现8.5.2,应用程序设计,,(参考书中相关实例),,,,,(完),。