




已阅读5页,还剩21页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C/OS-在51单片机上的移植姓名:学号:目 录1 绪论21.1 嵌入式实时操作系统21.2 C/OS-嵌入式操作系统21.3 C/OS-原理22 C/OS-内核结构32.1 临界区32.2 任务及任务控制块32.3 任务状态42.4 任务调度 52.5 中断处理52.6 时钟节拍52.7 C/OS-初始化与启动63 C/OS-在51单片机上的移植63.1 OS_CPU.H文件的移植63.2 OS_CPU_A.ASM文件的移植83.3 OS_CPU_C.C文件的移植204 移植结果测试234.1 设计原理234.2 应用程序设计234.3 结果分析251 绪论1.1嵌入式实时操作系统大多数的操作系统只注重平均性能,如对于整个系统来说,所有任务的平均响应时间是关键,而不关心单个任务的响应时间。而嵌入式实时操作系统最主要的特征是性能上的实时性,从这个角度上看,可以把嵌入式实时操作系统定义为“当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统作出快速响应,并控制所有实时任务协调一致运行的嵌入式操作系统 ”。目前大多数嵌入式操作系统提供以下管理功能: 1.任务管理所有嵌入式操作系统都是多任务的,目前所说的多任务大都是指多线程方式或多进程方式,两者的运行机制不完全一样。以多进程为例,调度程序的好坏直接影响到系统的性能。和一般的操作系统一样,嵌入式操作系统的作用也是决定在特定的某一时刻系统应该运行哪一个进程,对嵌入式系统中的运行软件进行描述和管理,并完成处理机资源的分配与调度。2.存储管理在嵌入式系统中,一般不采用虚拟内存管理,而采用动态内存管理方式,即当程序的某一部分需要使用内存时,利用操作系统提供的分配函数来处理,一旦使用完,可通过释放函数来释放所占用的内存,这样内存就可以重复使用,这样提高了内存的利用率,方便了用户的使用,并提供了足够的存储空间。3.周边资源管理 在操作系统中必须提供周边资源的驱动程序,以方便资源管理和应用程序使用。 4.中断管理 嵌入式操作系统和一般操作系统一样,一般都是用中断方式来处理外部事件和I/O请求。中断管理负责中断的初始化安装、现场的保存和恢复、中断栈的嵌套管理等。 1.2 C/OS-嵌入式操作系统C/OS-是一个可裁剪、源码开放、结构小巧、抢先式的实时多任务内核,主要面向中小型嵌入式系统,具有执行效率高、占用空间小、可移植性强、实时性能优良和可扩张性强等特点。 C/OS-结构小巧,即使包含全部功能如信号量、消息邮箱、消息队列以及相关函数等,编译后的C/OS-内核也仅有610 KB,所以它比较适用于小型控制系统,C/OS-也具有良好的扩展性能。1.3 C/OS-II原理 C/OS-II 包括任务调度、时间管理、内存管理、资源管理四大部分。它的移植只与4个文件相关:汇编文件(OS_CPU_A.ASM)、处理器相关C文件(OS_CPU.H、OS_CPU_C.C)和配置文件(OS_CFG.H)。有64个优先级,系统占用8个,用户可创建56个任务,不支持时间片轮转。它的基本思路就是 “近似地每时每刻总是让优先级最高的就绪任务处于运行状态”。为了保证这一点,它在调用系统API函数、中断结束、定时中断结束时总是执行调度算法。任务的切换是通过模拟一次中断实现的。C/OS-II工作核心原理是:近似地让最高优先级的就绪任务处于运行状态。2 C/OS-内核结构2.1 临界区一个任务在某些时候可能会访问共享内存、共享文件或其他共享资源,这些对共享内存进行访问的程序片断称作临界区。为了防止不同的任务同时处于临界区,必须使用一定互斥的方法来避免这种情况的发生,因此C/OS-在处理临界区代码时需要关中断,处理完毕后再开中断。C/OS-定义两个宏来开关中断,C/OS-中的这两个宏调用分别是:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。2.2 任务及任务控制块在C/OS-中,一个任务看起来像其它C的函数一样,有函数返回类型,有形式参数变量,但是任务是不会返回的,所以返回参数要定义成void类型,下面这个程序就是一个任务函数: void Task (void *pdata) for(;) /* 用户代码 */ /* 调用C/OS-的各种服务 */ /* 用户代码 */ 当任务完成以后,任务函数可以调用OSTaskDel()来实现自我删除。任务想要再次进入内核可以调用OSTaskCreat()或者OSTaskCreatExt()。任务函数的形式参数变量是由用户代码在第一次执行时带入的,将变量定义成void指针是为了允许用户应用程序传递任何类型的数据给任务。用户也可以建立许多相同的任务,且所有都使用同一个任务函数,但可以向这个任务传入不同的数据,就可以达到不同的任务使用同一个任务函数的目的,大大节省了代码的存储空间. C/OS-可以管理多达64个任务,但作者保留了优先级为0、1、2、3、OS_LOWEST_PRIO-3、OS_LOWEST_PRI0-2,OS_LOWEST_PRI0-1以及OS_LOWEST_PRI-0这8个任务以被将来使用。必须给每个任务赋以不同的优先级,优先级号越低,任务的优先级越高。C/OS-总是运行进入就绪态的优先级最高的任务。任务控制块(OS_TCB)是一个数据结构,是用来描述任务的一些属性。当一个任务建立时,任务控制块将被初始化。当任务的CPU使用权被剥夺时,C/OS-用任务控制块来保存改任务的状态,当任务重新得到CPU使用权时,任务控制块能确保任务从中断的那一点继续执行下去。2.3 任务状态 每个任务都处在以下5种状态里:休眠态、就绪态、运行态、挂起态和被中断态。在任何时刻,任务的状态一定是这5种状态之一。 休眠态指任务在程序空间之中,还没有交给C/OS-管理,可以通过调用OSTaskCreate()或OSTaskCreateExt()函数来把任务交给C/OS-。当任务一旦建立,这个任务就进入就绪态准备运行。一个任务可以通过调用OSTaskDel()函数再返回到休眠态,或通过调用该函数让另一个任务进入休眠态。 调用OSStart()可以启动多任务。OSStart()函数运行进入就绪态的优先级最高的任务。只有当所有优先级高于它的任务转为等待状态,或者是被删除了,就绪的任务才能进入运行态。 正在运行的任务可以通过调用OSTimeDly()或OSTimeDlyHMSM()这两个函数将自身延迟一段时间,此时这个任务就进入了等待状态,等待这段时间过去,下一个优先级最高的、并进入了就绪态的任务立刻被赋予了CPU的控制权。等待的时间过去以后,系统服务函数OSTimeTick()使延迟了的任务进入就绪态。 正在运行的任务期待某一事件的发生也要等待,手段是调用以下3个函数之一:OSSemPend()、OSMboxPend()或OSQPend()。调用后任务进入了等待状态。当任务因等待事件被挂起,下一个优先级最高的任务立即得到了CPU的控制权。当事件发生了,被挂起的任务进入就绪态。正在运行的任务是可以被中断的,除非该任务或者C/OS-关中断。被中断了的任务就进入了中断服务子程序。响应中断时,正在执行的任务被挂起,中断服务子程序控制了CPU的使用权。中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就绪态。在这种情况下,从中断服务子程序返回之前,C/OS-要判定,被中断的任务是否还是就绪态任务中优先级最高的任务。如果中断服务子程序使一个优先级更高的任务进入了就绪态,则新进入就绪态的这个优先级更高的任务将得以运行,否则原来被中断了的任务还会继续运行。 当所有的任务都处在等待事件发生或等待延迟时间结束的状态时,C/OS-执行空闲任务。 2.4 任务调度C/OS-总是运行进入就绪态任务中优先级最高的那个任务。每个任务的就绪态标志都放入就绪表中,就绪表中有两个变量OSRdyGrp和OSRdyTbl。通过这两个变量和优先级判定表OSUnMapTbl256来确定哪个任务运行。所谓任务切换,其实要做的就是CPU寄存器内容切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,即CPU寄存器中的全部内容。这些内容保存在任务的当前状况保存区,也就是任务自己的栈区之中。入栈工作完成以后,就是把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU的寄存器,并开始下一个任务的运行。任务切换由以下两步完成: (1)将被挂起任务的微处理器寄存器推入堆栈; (2)将较高优先级的任务的寄存器值从栈中恢复到寄存器中。 2.5 中断处理 C/OS-II中的中断服务程序最好用汇编语言来写,以减少时间上的开销。中断服务程序的示意代码如下。 用户中断服务子程序: 保存全部CPU寄存器; (1) 调用OSIntEnter或OSIntNesting直接加1; (2) 执行用户代码做中断服务; (3) 调用OSIntExit(); (4) 恢复所有CPU寄存器; (5) 执行中断返回指令; (6)用户代码应该将全部CPU寄存器推入当前任务栈(1)。 C/OS-需要了解用户在做中断服务,故用户应该调用OSIntEnter(),或者将全程变量OSIntNesting(2)直接加1。直接给OSIntNesting加1比调用OSIntEnter()快得多,可能时,直接加1更好。上述两步完成以后,用户可以开始服务于叫中断的设备了(3)。调用脱离中断函数OSIntExit()(4)标志着中断服务子程序的结束,OSIntExit()将中断嵌套层数计数器减1。当嵌套计数器减到零时,所有中断,包括嵌套的中断就都完成了,此时C/OS-要判定有没有优先级较高的任务被中断服务子程序唤醒了。如果有优先级高的任务进入了就绪态,C/OS-就返回到那个高优先级的任务,OSIntExit()返回到调用点(5)。保存的寄存器的值是在这时恢复的,然后是执行中断返回指令(6)。 2.6 时钟节拍 C/OS-需要用户提供周期性的信号源,用于实现时间延时和确认超时。节拍率应在每秒10次到100次之间,也就是每l0ms到l00ms响应一次。51单片机有两个16位的定时器,可以作为时钟节拍的定时源。用户必须在多任务系统启动之后再启动时钟节拍源开始计时,也就是在调用OSStart()之后。 2.7 C/OS-初始化与启动C/OS-的初始化是通过调用系统初始化函数OSInit()实现的。OSInit()初始化内核所需要的所有变量和数据结构。OSInit()还建立了空闲任务,使这个任务始终处于就绪态。空闲任务OSTaskIdle()的优先级总是被设置成所有任务中最低的。OSInit()初始化变量主要是一些系统的全局变量,还初始化了4个空的数据结构空间。这4个结构都是单向链表,允许C/OS-从缓冲区中迅速得到或释放一个其中的元素。在空缓冲区中空任务控制块的数目取决于最多任务OS_MAX_TASKS,最多任务数在OS_CFG.H文件中定义的。然后OSInit()将会建立两个任务,并将这两个任务的任务控制块OS_TCB用双向链表链接在一起。OSTCBList指向这个链表的起始处。当建立一个任务时,这个任务总是被放在这个链表的起始处。换句话说,OSTCBList总是指向最后建立的那个任务。因为两个任务都处于就绪态,在就绪任务表OSRdyTbl中的相应位也要设为1。 C/OS-多任务的启动是用户通过调用OSStart()实现的,在启动之前,用户至少要建立一个应用任务。OSStart()是从任务就绪表中构造出优先级最高的任务,然后调用高优先级就绪任务启动函数OSStartHighRdy()。 3 C/OS-在51单片机上的移植所谓移植,就是使一个实时内核能在某个微处理器或微控制器上运行。 要使C/OS-正常运行,处理器必须满足以下要求: 1. 处理器的C编译器能产生可重入代码。 2. 用C语言就可以打开和关闭中断。 3. 处理器支持中断,并且能产生定时中断(通常在10至100Hz之间)。 4. 处理器支持能够容纳一定量数据(可能是几千字节)的硬件堆栈。 5. 处理器有将堆栈指针和其它CPU寄存器读出和存储到堆栈或内存中的指令。 移植工作主要包括以下几个内容: 用#define设置一个常量的值(OS_CPU.H) 声明10个数据类型(OS_CPU.H) 用#define声明三个宏(OS_CPU.H) 用C语言编写六个简单的函数(OS_CPU_C.C) 编写四个汇编语言函数(OS_CPU_A.ASM) 下面分别从这几个方面谈一下。3.1 OS_CPU.H文件的移植OS_CPU.H包括了用#defines定义的与处理器相关的常量、宏和类型定义。OS_CPU.H的大体结构如下所示:#ifdef OS_CPU_GLOBALS #define OS_CPU_EXT #else #define OS_CPU_EXT extern #endif typedef unsigned char BOOLEAN; (1) typedef unsigned char INT8U; /* 无符号8位整数 */ typedef signed char INT8S; /* 有符号8位整数 */ typedef unsigned int INT16U; /* 无符号16位整数 */ typedef signed int INT16S; /* 有符号16位整数 */ typedef unsigned long INT32U; /* 无符号32位整数 */ typedef signed long INT32S; /* 有符号32位整数 */ typedef float FP32; /* 单精度浮点数 */ (2) typedef double FP64; /* 双精度浮点数 */ typedef unsigned char OS_STK; /* 堆栈入口宽度为8位 */ (3) #define OS_ENTER_CRITICAL() EA = 0 /*禁止中断 */ (4) #define OS_EXIT_CRITICAL() EA = 1 /*允许中断 */ #define OS_STK_GROWTH 0 /*定义堆栈的增长方向 */ (5) #define OS_TASK_SW() OSCtxSw() (6) 3.1.1 与编译器相关的数据类型因为不同的微处理器有不同的字长,所以C/OS-的移植包括了一系列的类型定义以确保其可移植性。C/OS-代码从不使用C的short,int和long等数据类型,因为它们是与编译器相关的,不可移植,因此将其定义成整型数据结构(1)。C/OS-还定义了浮点数据类型(2)。 用户必须将任务堆栈的数据类型告知C/OS-,这个过程是通过为OS_STK声明正确的C数据类型来完成的。51单片机的堆栈成员是8位的,并且用户的编译文件指定字符型为8位数,所以应将OS_STK声明为无符号字符型数据类型(3)。所有的任务堆栈都必须用OS_STK来声明数据类型。3.1.2 OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL() 与所有的实时内核一样,C/OS-需要先禁止中断再访问代码的临界段,并且在访问完毕后重新允许中断,这就使得C/OS-能够保护临界段代码免受多任务或中断服务例程的破坏。C/OS-定义了两个宏来禁止和允许中断:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()(4)。OS_ENTER_CRITICAL()用来关中断,OS_EXIT_CRITICAL()用来开中断。 执行这两个宏的最简单的方法是在OS_ENTER_CRITICAL()中调用处理器指令来禁止中断,以及在OS_EXIT_CRITICAL()中调用允许中断指令。本移植过程就是通过这种方法来禁止允许中断的,在51单片机中是通过中断允许寄存器来控制中断的,当EA=0时为关中断,当EA=1时为开中断。3.1.3 OS_STK_GROWTH 只要在结构常量OS_STK_GROWTH中指定堆栈的生长方式就可以了。 置OS_STK_GROWTH为0表示堆栈从低地址向高地址增长。 置OS_STK_GROWTH为1表示堆栈从高地址向低地址增长。 由于51单片机的堆栈是从低地址向高地址增长的,因此将OS_STK_GROWTH设置成0(5)。 3.1.4 OS_TASK_SW() OS_TASK_SW()(6)是一个宏,它是在C/OS-从低优先级任务切换到最高优先级任务时被调用的。OS_TASK_SW()总是在任务级代码中被调用的。另一个函数OSIntExit()被用来在ISR使得更高优先级任务处于就绪状态时,执行任务切换功能。任务切换只是简单的将处理器寄存器保存到将被挂起的任务的堆栈中,并且将更高优先级的任务从堆栈中恢复出来。 C/OS-要运行处于就绪状态的任务必须要做的事就是将所有处理器寄存器从任务堆栈中恢复出来,并且执行中断返回。为了切换任务可以通过执行OS_TASK_SW()来产生中断。大部分的处理器会提供软中断或是陷阱指令来完成这个功能。ISR或是陷阱处理函数的向量地址必须指向汇编语言函数OSCtxSw()。 然而51单片机并不提供软中断机制,因此需要尽自己的所能将堆栈结构设置成与中断堆栈结构一样。OS_TASK_SW()只会简单的调用OSCtxSw()而不是将某个向量指向OSCtxSw()。而?C_XBP指针能做到这点。?C_XBP是外部可重入栈的堆栈指针。51由于内部RAM容量的限制,可重入栈没有使用硬件堆栈(堆栈指针SP),而是在外部RAM中模拟的可重入栈,其堆栈指针即为?C_XBP,不过它是用软件模拟的。3.2 OS_CPU_A.ASM文件的移植 OS_CPU_A.ASM的移植需要编写四个简单的汇编语言函数: OSStartHighRdy() OSCtxSw() OSIntCtxSw() OSTickISR() 3.2.1 准备工作 要对所要移植的函数定义重定位段,声明引用的变量,分配堆栈空间,定义必要的宏等,如下面的程序所示:;定义重定位段 ?PR?OSStartHighRdy?OS_CPU_A SEGMENT CODE (1) ?PR?OSCtxSw?OS_CPU_A SEGMENT CODE ?PR?OSIntCtxSw?OS_CPU_A SEGMENT CODE ?PR?OSTickISR?OS_CPU_A SEGMENT CODE ;声明引用全局变量和外部子程序 EXTRN IDATA (?C_XBP) ;仿真堆栈指针用于重入局部变量保存 (2) EXTRN IDATA (OSTCBCur) EXTRN IDATA (OSTCBHighRdy) EXTRN IDATA (OSRunning) EXTRN IDATA (OSPrioCur) EXTRN IDATA (OSPrioHighRdy) EXTRN CODE (_?OSTaskSwHook) EXTRN CODE (_?OSIntEnter) EXTRN CODE (_?OSIntExit) EXTRN CODE (_?OSTimeTick) ;对外声明4个不可重入函数 PUBLIC OSStartHighRdy (3) PUBLIC OSCtxSw PUBLIC OSIntCtxSw PUBLIC OSTickISR ;分配堆栈空间。?STACK SEGMENT IDATA (4) RSEG ?STACK OSStack: DS 40H OSStkStart IDATA OSStack-1 ;定义压栈宏 PUSHALL MACRO (5) PUSH PSW PUSH ACC PUSH B PUSH DPL PUSH DPH MOV A,R0 ;R0-R7入栈 PUSH ACC MOV A,R1 PUSH ACC MOV A,R2 PUSH ACC MOV A,R3 PUSH ACC MOV A,R4 PUSH ACC MOV A,R5 PUSH ACC MOV A,R6 PUSH ACC MOV A,R7 PUSH ACC ;PUSH SP ;不必保存SP,任务切换时由相应程序调整 ENDM ;定义压栈宏 POPALL MACRO ;POP SP ;不必保存SP,任务切换时由相应程序调整 POP ACC ;R0-R7出栈 MOV R7,A POP ACC MOV R6,A POP ACC MOV R5,A POP ACC MOV R4,A POP ACC MOV R3,A POP ACC MOV R2,A POP ACC MOV R1,A POP ACC MOV R0,A POP DPH POP DPL POP B POP ACC POP PSW ENDM 3.2.2 OSStartHighRdy()函数的移植使就绪状态的任务开始运行的函数叫做OSStart()。在用户调用OSStart()之前,用户必须通过调用OSTaskCreate()或OSTaskCteateExt()建立至少一个以上自己的任务。OSStartHighRdy()假设OSTCBHighRdy指向的是优先级最高任务的任务控制块。在C/OS-中处于就绪状态的任务的堆栈结构看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。要想运行最高优先级任务,用户所要做的是将所有处理器寄存器按顺序从任务堆栈中恢复出来,并且执行中断返回。为了简单一点,堆栈指针总是储存在任务控制块(即它的OS_TCB)的开头。换句话说,也就是要想恢复的任务堆栈指针总是储存在OS_TCB的0偏址内存单元中。 OSStartHighRdy()程序流程图如图3-1所示。 图3-1 OSStartHighRdy()程序流程图移植的程序如下所示:RSEG ?PR?OSStartHighRdy?OS_CPU_A OSStartHighRdy: USING 0 ;使用寄存器第0组.上电后51自动关中断,此处不必用CLR EA指令,因为到此处还未开中断,本程序退出后,开中断。 LCALL _?OSTaskSwHook (1) OSCtxSw_in: ;OSTCBCur = DPTR 获得当前TCB指针 MOV R0,#LOW (OSTCBCur) ;获得OSTCBCur指针低地址,指针占3字节。+0类型+1高8位数据+2低8位数据 INC R0 (2) MOV DPH,R0 ;全局变量OSTCBCur在IDATA中 INC R0 MOV DPL,R0 ;OSTCBCur-OSTCBStkPtr = DPTR 获得用户堆栈指针 INC DPTR (3) MOVX A,DPTR ;OSTCBStkPtr是void指针 MOV R0,A INC DPTR MOVX A,DPTR MOV R1,A MOV DPH,R0 MOV DPL,R1 ;*UserStkPtr = R5 用户堆栈起始地址内容(即用户堆栈长度放在此处) MOVX A,DPTR (4) MOV R5,A ;R5=用户堆栈长度 ;恢复现场堆栈内容 MOV R0,#OSStkStart (5) restore_stack: INC DPTRINC R0 MOVX A,DPTR MOV R0,A DJNZ R5,restore_stack (6) ;恢复堆栈指针SP MOV SP,R0 (7) ;恢复仿真堆栈指针?C_XBP INC DPTR (8) MOVX A,DPTR MOV ?C_XBP,A ;?C_XBP 仿真堆栈指针高8位 INC DPTR MOVX A,DPTRMOV ?C_XBP+1,A ;?C_XBP 仿真堆栈指针低8位 ;OSRunning=TRUE MOV R0,#LOW (OSRunning) (9) MOV R0,#01 POPALL (10) SETB EA ;开中断 RETI OSStartHighRdy()函数是在C/OS-启动时被OSStart()调用的。OSStartHighRdy()必须调用OSTaskSwHook()(1),OSTaskSwHook()可以通过检查OSRunning来知道是OSStartHighRdy()在调用它(OSRunning为FALSE)还是正常的任务切换在调用它(OSRunning为TRUE)。刚启动的时候,CPU没有执行任何任务,而任务控制块就绪表中有多个任务,由于刚开始调用OSTaskSwHook(),通过检查OSRunning为FALSE,CPU就知道不需要将当前寄存器的内容入栈,而直接将处在任务就绪表中任务优先级最高的任务的任务控制块变成当前任务控制块,然后将其堆栈内容复制到系统堆栈中去,以此来执行任务就绪表中优先级最高的任务。在复制之前,要先找到用户堆栈。这个过程要分两步走: (1)要找到当前任务控制块的地址(假设任务就绪表中任务优先级最高的任务的任务控制块已经赋给了当前任务控制块)。 (2)找到用户堆栈的地址。 因为任务控制块TCB是一个结构体变量,该结构体变量的第一个成员是一个指针变量OSTCBStkPtr,该指针指向该任务的用户堆栈。OSTCBCur是一个指针变量,它指向当前任务控制块。所以,要获取当前任务控制块的地址,只要获取OSTCBCur的内容即可。由于该指针变量占3个字节,第1个字节存放的是该指针变量所指向数据的类型,第2个字节为TCB的高8为地址,第3个字节为TCB的低8位地址。所以,在取地址的时候,要跳过1个字节(2)。在获取用户堆栈的地址的时,其原理与上述过程一样(3)。在获取用户堆栈地址后,就可以进行复制工作了。在复制之前,要先取出用户堆栈的第1个字节。因为用户堆栈的第1个字节并不是要复制的内容,而是要复制的字节的个数,该字节内容取出后赋给R5,用来确定复制时循环的次数(4)。然后将R0指向系统堆栈起始地址减1处(OSStkStart)(5),此时DPTR是指向用户堆栈存储长度处的。这样,就可以进行复制工作了。复制的方向是从用户堆栈到系统硬件堆栈。每次复制前,都先将R0和DPTR值加1,用来指向下一个存储单元,直到R5减为0为止(6)。复制完后,将硬件堆栈指针SP指向R0所指处,用此办法来恢复SP(7)。所以在定义入栈出栈时,并不需要保存SP。然后获取仿真堆栈指针?C_XBP的值(8)。该指针占两个字节,其值保存在要复制到系统堆栈的用户堆栈的最后一个字节内容的下两个字节处。接下来,OSStartHighRdy()必须在最高优先级任务恢复之前和调用OSTaskSwHook()之后设置OSRunning为TRUE,用此方法来告诉以后的任务切换都是系统运行过程中的任务切换,而不是系统刚刚启动起来(9)。然后通过调用出栈宏、开中断以及中断返回(10),就可以运行就绪态任务表中最高优先级的任务了。 3.2.3 OSCtxSw()函数的移植任务级的切换问题是通过发软中断命令或依靠处理器执行陷阱指令来完成的。但51单片机并不提供软中断机制,这就需要将堆栈结构设置成与中断堆栈结构一样。 如果当前任务调用C/OS-提供的系统服务,并使得更高优先级任务处于就绪状态,C/OS-就会借助上面提到的向量地址找到OSCtxSw()。在系统服务调用的最后,C/OS-会调用OSSched(),并由此来推断当前任务不再是要运行的最重要的任务了。OSSched()先将最高优先级任务的地址装载到OSTCBHighRdy中,再通过调用OS_TASK_SW()来执行软中断或陷阱指令。变量OSTCBCur早就包含了指向当前任务的任务控制块(OS_TCB)的指针。软中断 (或陷阱) 指令会强制一些处理器寄存器(比如返回地址和处理器状态字)到当前任务的堆栈中,并使处理器执行OSCtxSw()。OSCtxSw()函数的流程图如图3-2所示。 图 3-2 OSCtxSw()函数的流程图移植的程序如下所示: RSEG ?PR?OSCtxSw?OS_CPU_A OSCtxSw: PUSHALL (1) OSIntCtxSw_in: ;获得堆栈长度和起址 MOV A,SP (2) CLR C SUBB A,#OSStkStart MOV R5,A ;获得堆栈长度 ;OSTCBCur = DPTR 获得当前TCB指针 MOV R0,#LOW (OSTCBCur) (3) ;获得OSTCBCur指针低地址,指针占3字节。+0类型+1高8位数据+2低8位数据 INC R0 MOV DPH,R0 ;全局变量OSTCBCur在IDATA中 INC R0 MOV DPL,R0 ;OSTCBCur-OSTCBStkPtr = DPTR 获得用户堆栈指针 INC DPTR (4) MOVX A,DPTR ;OSTCBStkPtr是void指针 MOV R0,A INC DPTR MOVX A,DPTR MOV R1,A MOV DPH,R0 MOV DPL,R1 ;保存堆栈长度 MOV A,R5 (5) MOVX DPTR,A MOV R0,#OSStkStart ;获得堆栈起址 (6) save_stack: INC DPTR INC R0 MOV A,R0 MOVX DPTR,A DJNZ R5,save_stack ;保存仿真堆栈指针?C_XBP INC DPTR (7) MOV A,?C_XBP ;?C_XBP 仿真堆栈指针高8位 MOVX DPTR,A INC DPTR MOV A,?C_XBP+1 ;?C_XBP 仿真堆栈指针低8位 MOVX DPTR,A ;调用用户程序 LCALL _?OSTaskSwHook (8) ;OSTCBCur = OSTCBHighRdy MOV R0,#OSTCBCur (9) MOV R1,#OSTCBHighRdy MOV A,R1 MOV R0,A INC R0 INC R1 MOV A,R1 MOV R0,A INC R0 INC R1 MOV A,R1 MOV R0,A ;OSPrioCur = OSPrioHighRdy 使用这两个变量主要目的是为了使指针比较变为字节比较,以便节省时间。 MOV R0,#OSPrioCur (10) MOV R1,#OSPrioHighRdy MOV A,R1 MOV R0,A LJMP OSCtxSw_in (11) 在系统运行过程中,如果发现有更高优先级任务进入就绪态,这时系统就会自动进行任务切换,使系统运行更高优先级的就绪态任务。系统是通过调用OSSched()函数来判断是否有更高优先级任务进入就绪态的。在调用OSSched()函数时一开始中断就被关了,然后判断当前任务优先级是否最高,如果不是,就将指针变量OSTCBHighRdy指向优先级最高的那个任务控制块OS_TCB。然后通过调用OS_TASK_SW()函数来实现任务切换的。OSCtxSw()函数就是在OS_TASK_SW()函数执行中被调用的。所以在移植OSCtxSw()函数时并没有关中断,那是因为在执行该函数时,中断肯定是关着的。任务切换工作具体可分两部分完成: (1)释放运行着的低优先级任务的CPU使用权 (2)使处于就绪态的最高优先级任务获得CPU使用权 调用OSCtxSw()函数时,一开始就将CPU当前寄存器的内容入栈(1)。 入栈结束时,硬件堆栈指针SP是指向最后一个入栈单元的。这样,用SP值减去系统堆栈起始地址减1处OSStkStart的值,就可以获得堆栈长度了,并将其保存在R5中,以确定接下来在复制过程中循环的次数(2)。然后获取被切换的任务的用户堆栈地址。首先,将R0指向TCBCur指针,以此获得当前任务控制块TCB的地址,使DPTR指向当前任务控制块结构体变量OS_TCB(3)。然后通过当前OS_TCB结构体变量的第一个成员指针变量OSTCBStkPtr获取当前任务的用户栈地址,并让DPTR重新指向该用户栈(4)。TCBCur指针和OSTCBStkPtr指针都是3个字节的,取地址是需要对其进行调整。DPTR指向用户堆栈后,需要将原先保存的堆栈长度R5值复制到用户栈中,以便以后重新运行该任务时,确定复制到CPU堆栈中寄存器的个数(5)。然后将R0指向系统堆栈起始地址减1处OSStkStart,同时DPTR是指向用户堆栈的。这样就可以进行复制工作了。复制的方向是从系统硬件堆栈到用户堆栈。每次复制前,都先将R0和DPTR值加1,让它们指向下一个单元,循环的次数由堆栈长度R5值决定。当R5减为0时,复制工作结束(6)。然后保存仿真堆栈指针?C_XBP。在复制结束时,DPTR是指向用户堆栈中最后一个复制过来的存储单元的,因此在保存?C_XBP前,要先将DPTR值加1(7)。接着调用OSTaskSwHook()函数,通过判断OSRunning为TRUE来确定系统是在运行过程中进行的任务切换(8)。到此,任务切换的第一部分工作完成了。要使处于就绪态的最高优先级任务运行起来,首先要找到该任务的执行地址。在任务正常运行时,其起始地址都是保存在当前任务控制块中的。而此时高优先级任务的地址是保存在指针变量OSTCBHighRdy中的。所以,要将OSTCBHighRdy的内容复制到当前任务控制块的第一个结构体成员指针变量OSTCBStrPtr中(9)。这两个指针都占3个字节,第1个字节为指针所指数据的类型,第2个字节为高8位地址,第3个字节为低8位地址。地址装入完成后,还要修改当前任务的优先级。因为在修改之前,当前任务的优先级还是被切换了的那个任务的优先级。所以,要将要切换的任务的优先级赋给当前任务(10)。然后就可以将要切换的任务的用户堆栈内容复制到系统硬件堆栈中去,然后CPU堆栈内容出栈,这样该任务就获得了CPU的使用权,高优先级任务得以运行了。该部分程序与OSStartHighRdy()函数中标号OSCtxSw_in以后的程序一样。所以,在此由一个长跳转语句LJMP直接跳到那部分去执行,以减少程序代码长度(11)。3.2.4 OSIntCtxSw()函数的移植OSIntExit()通过调用OSIntCtxSw()来从ISR中执行切换功能。因为OSIntCtxSw()是在ISR中被调用的,所以可以断定所有的处理器寄存器都被正确地保存到了被中断的任务的堆栈之中。实际上堆栈结构中还有其它的一些东西。OSIntCtxSw()必须要清理堆栈,这样被中断的任务的堆栈结构内容才能满足我们的需要。 OSIntCtxSw()函数移植代码如下所示:RSEG ?PR?OSIntCtxSw?OS_CPU_A OSIntCtxSw: ;调整SP指针去掉在调用OSIntExit(),OSIntCtxSw()过程中压入堆栈的多余内容 ;SP=SP-4 MOV A,SP (1) CLR C SUBB A,#4 MOV SP,A LJMP OSIntCtxSw_in (2) 在执行OSIntCtxSw()时,要对硬件堆栈指针SP进行调整。在本移植过程中是将SP值减4(1)。因为在执行ISR中调用OSIntExit()时的返回地址和在执行OSIntExit()中调用OSIntCtxSw()时的返回地址都压入了堆栈。而在进行任务切换的时候并不是跑到ISR中被中断的地方执行,而是去执行新的任务。压入堆栈的这两个地址都占两个字节,所以在此,要将SP减4,使SP指针指向被中断的任务所要保存的寄存器。注意,本移植中认为在执行ISR中压入多余地址时并没有将处理器状态字压入堆栈。SP调整正确后,就可以将被中断的任务所有要保存的寄存器内容复制到该任务的任务控制块TCB中了,以保证该任务再次被正常调用。然后,修改当前任务的用户栈地址及当前任务的优先级,再将要切换的任务用户堆栈内容复制到系统堆栈中,通过执行CPU寄存器内容出栈、中断返回,跳到要切换的任务处开始执行新的任务。以上这部分程序的代码跟OSCtxSw()程序中标号为OSIntCtxSw_in后面的程序一样,所以,为了节约代码空间,可以用长跳转语句LJMP跳到OSIntCtxSw_in标号处去执行(2)。 3.2.5 OSTickISR()函数的移植C/OS-要求用户提供一个时钟资源来实现时间的延时和期满功能。时钟节拍应该每秒钟发生10100次。在本移植过程中采用定时器T0来产生时钟中断。 用户必须在开始多任务调度后(即调用OSStart()后)允许时钟节拍中断。换句话说,就是用户应该在OSStart()运行后,C/OS-启动运行的第一个任务中初始化节拍中断,而不能在调用OSInit()和OSStart()之间允许时钟节拍中断。否则,C/OS-的运行状态不确定,应用程序有可能会崩溃。OSTickISR()程序流程图如图3-3所示。 图 3-3 OSTickISR()程序流程图OSTickISR()函数移植程序如下所示:CSEG AT 000BH ;OSTickISR (1) LJMP OSTickISR ;使用定时器0 RSEG ?PR?OSTickISR?OS_CPU_A OSTickISR: USING 0 (2) PUSHALL (3) CLR TR0 (4) MOV TH0,#0B1H ;定义Tick=50次/秒(即0.02秒/次) (5) MOV TL0,#0E0H SETB TR0 (6) LCALL _?OSIntEnter (7)LCALL _?OSTimeTick LCALL _?OSIntExit POPALL (8) RETI C/OS-中的时钟节拍服务是通过在中断服务子程序中调用OSTimeTick()实现的。在本移植过程中使用了定时器T0来实现时间的延时和期满功能。在MCS-51系列单片机中,T0的中断向量入口地址为000BH(1)。移植过程中使用0区寄存器来保存内部变量(2)。一旦开始执行OSTickISR()函数,系统就将CPU寄存器内容入栈(3)。然后初始化T0。在初始化T0前,要先将TR0清零,使T0停止计数(4)。然后对TH0、TL0赋初值。假设所选单片机的晶振频率为12MHZ,则一个机器周期为1S。T0每20ms中断一次,即让TR0计数20000次才中断一次。由于T0工作在方式1,所以用65536减去20000,所得值再转换成十六进制数,高8位赋给TH0,低8位赋给TL0。计算结果为TH0=0B1H,TL0=0E0H(5)。然后将TR0置1,启动计数器开始计数(6)。这样T0就开始工作了。然后调用OSIntEnter()、OSTimeTick()、OSIntExit()这3个函数(7)。然后将CPU寄存器内容出栈,返回到被中断的地方继续执行(8)。 3.3 OS_CPU_C.C文件的移植 C/OS-的移植实例需要编写六个C函数: OSTaskStkInit() OSTaskCreateHook() OSTaskDelHook() OSTaskSwHook() OSTaskStatHook() OSTimeTickHook() 唯一必要的函数是OSTaskStkInit(),其它五个函数必须声明但没必要包含代码。OS_CPU_C.C文件的移植代码如下所示: #define OS_CPU_GLOBALS #include includes.h void *OSTaskStkInit (void (*task)(void *pd), void *ppdata, void *ptos, INT16U opt) reentrant (1) OS_STK *stk; ppdata = ppdata; opt = opt; /opt没被用到,保留此语句防止告警产生 stk = (OS_STK *)ptos; /用户堆栈最低有效地址 (2) *stk+ = 15; /用户堆栈长度 (3) *stk+ = (INT16U)task & 0xFF; /任务地址低8位 (4) *stk+ = (INT16U)task 8; /任务地址高8位 (5) *stk+ = 0x00; /PSW (6) *stk+ = 0x0A; /ACC *stk+ = 0x0B; /B *stk+ = 0x00; /DPL *stk+ = 0x00; /DPH *stk+ = 0x00; /R0
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 【正版授权】 ISO 4070:2025 EN Polyvinylidene fluoride (PVDF) - Effect of time and temperature on expected strength
- 股权转让与品牌授权经营合同
- 金融资产交易股权转让合同样本
- 定制家具配合及沟通协议
- 苗木采购协议全文
- 购买合同解除协议书范本
- 2025版合同代理协议书范本
- 钣金合同协议书模板
- 2025关于进口贸易合同范本
- 环卫公益岗位合同协议书
- 【真题】2023年常州市中考道德与法治试卷(含答案解析)
- 酒吧计划创业计划书
- 光伏项目安全培训课件
- 拉森钢板桩监理实施细则样本
- 个人房屋抵押借款合同范本-借款合同
- 《原码一位乘法》课件
- 中华人民共和国监察法学习解读课件
- 中小学教务主任培训
- 眼镜行业目标市场分析
- 空间向量与立体几何教材分析
- 1-STM32F4xx中文参考手册
评论
0/150
提交评论