基于uCOS-II的嵌入式应用程序开发.ppt
第八章基于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(118)/定义LED1,P1.18控制LED1(2)#define LED2(119)/定义LED2,P1.19控制LED2(3)#define TASK_STACK_SIZE 128/定义用户任务的堆栈长度(4)OS_STK task1StkTASK_STACK_SIZE;/定义任务LED1的堆栈(5)OS_STK task2StkTASK_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任务(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/延时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|0 x00);/*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)寄存器的初始值为0 x00000000,此时系统从0 x00000000处开始执行程序。程序首先应该对硬件及其运行环境进行初始化,然后才能转入相应的功能处理程序去运行。因此所设计程序分为两部分:硬件及其运行环境初始化部分和应用程序部分。,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 0 x40;F位,Bit6 NoInt EQU 0 x80;I位,Bit7 USR32Mode EQU 0 x10;用户模式:10000 SVC32Mode EQU 0 x13;管理模式:10011 SYS32Mode EQU 0 x1f;系统模式:11111 FIQ32Mode EQU 0 x11;FIQ中断模式:10001 IRQ32Mode EQU 0 x12;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;0 x00:复位 LDR PC,UndefinedAddr;0 x04:未定义地址 LDR PC,SWI_Addr;0 x08:软件中断 LDR PC,PrefetchAddr;0 x0C:预取指中止 LDR PC,DataAbortAddr;0 x10:数据中止 DCD 0 xb9205f80;0 x14:保留 LDR PC,PC,#-0 xff0;0 x18:IRQ中断 LDR PC,FIQ_Addr;0 x1C: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,#-0 xff0”中处理 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;软中断,中断号为03 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,#0 xd3;设置管理模式堆栈 LDR SP,StackSvc MSR CPSR_c,#0 xd2;设置中断模式堆栈 LDR SP,StackIrq MSR CPSR_c,#0 xd1;设置快速中断模式堆栈 LDR SP,StackFiq MSR CPSR_c,#0 xd7;设置中止模式堆栈 LDR SP,StackAbt MSR CPSR_c,#0 xdb;设置未定义模式堆栈 LDR SP,StackUnd MSR CPSR_c,#0 xdf;设置系统模式堆栈,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.=0 x1fc INFO 1,nThe data at 0 x000001fc must be 0 x87654321.nPlease delete some source before this line.ENDIF CrpData WHILE.0 x1fc NOP;循环用NOP填充,直到0 x1fc WEND CrpData1 DCD 0 x87654321;当此数为0 x87654321时,用户程序被保护 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 寄存器的值为0 x00000000,系统就从0 x00000000处开始执行程序。而0 x00000000处放置着异常向量表,程序将根据异常向量表进行跳转。其执行过程如下:芯片根据异常处理程序地址表,得到复位处理程序(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 应用程序设计(参考书中相关实例)(完),