Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1345869
  • 博文数量: 206
  • 博客积分: 10571
  • 博客等级: 上将
  • 技术积分: 2610
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-30 11:50
文章分类
文章存档

2014年(1)

2013年(4)

2012年(18)

2010年(14)

2009年(31)

2008年(3)

2007年(135)

分类: LINUX

2007-06-05 12:15:35

μC/OS-Ⅱ在ATmega128上的移植Step by Step

本文详细介绍了把μC/OS-Ⅱ移植到ATMEL公司的8位微控制器ATmega128上的全过程。所谓移植,就是使一个实时内核能在某个微处理器或微控制器上运行。在移植之前,希望读者能熟悉所用微处理器和C编译器的特点。

1 ATmega128的内核特点 

       之所以要先介绍ATmega128 MCU内核特点,是因为在μC/OS-Ⅱ的移植过程中,仍需要用户用C语言和汇编语言编写一些与微处理器相关的代码。这里主要介绍ATmega128与μC/OS-Ⅱ移植相关的内核特点。如果读者已经对ATmega128 比较了解了,那就不必阅读这一部分了。

1.1微控制器 (MCU) 

       ATmega128的MCU包括一个算术逻辑单元(ALU),一个状态寄存器(SREG),一个通用工作寄存器组和一个堆栈指针。状态寄存器(SREG)的最高位I是全局中断允许位。如果全局中断允许位为零,则所有中断都被禁止。当系统响应一个中断后,I位将由硬件自动清“0”;当执行中断返回(RETI)指令时,I位由硬件自动置“1”,从而允许系统再次响应下一个中断请求。 

       通用工作寄存器组是由32个8位的通用工作寄存器组成。其中R26~R31这6个寄存器还可以两两合并为3个16位的间接地址寄存器。这些寄存器可以用来对数据存储空间进行间接寻址。这3个间接地址寄存器的名称为:X寄存器、Y寄存器、Z寄存器。其中Z寄存器还能用作对程序存储空间进行间接寻址的寄存器。有些AVR C语言编译器还把Y寄存器作为软件堆栈的堆栈指针,比如ICC- AVR,CodevisionAVR。 

       堆栈指针(SP)是一个指示堆栈顶部地址的16位寄存器。在ICCAVR中,它被用作指向硬件堆栈的堆栈指针。AVR单片机上电复位后,SP指针的初始值为0x0000,由于AVR单片机的堆栈是向下生长的(从高地址向低地址生长),所以系统程序一开始必须对堆栈指针SP进行初始化,即将SP的值设为数据存储空间的最高地址。ICCAVR编译器在链接C程序文件的时候,会自动在程序头链入startup文件。startup文件里面的程序将会去做初始化SP指针的工作。链入startup文件是ICCAVR这个编译器的特点,在用其它编译器的时候,希望读者确认所使用的编译器是否带有自动初始化SP的功能,若没有,应在用户程序中初始化SP。

1.2 数据存储空间(仅内部) 

       AVR单片机的数据存储器是线形的,从低地址到高地址依次是CPU寄存器区(32个通用寄存器),I/O寄存器区,数据存储区。 

       ICCAVR编译器又将数据存储区划分为全局变量和字符串区,软件堆栈区和硬件堆栈区三个空间。如下图:


高地址
硬件堆栈区
软件堆栈区
全局变量和字符串区
I/O寄存器区
CPU寄存器区
低地址 

       ICCAVR编译器将堆栈分成了两个功能不同的堆栈来处理(这一点与8051系列的单片机编译器处理方式不同)。硬件堆栈用于储存子程序和中断服务子程序调用时的函数返回地址。这块数据区域由堆栈指针SP进行寻址,数据的进栈和出栈有专门的汇编指令(pop,push等)支持,所以叫做硬件堆栈区。软件堆栈用于传递参数,储存临时变量和局部变量。这块数据区域是用软件模拟堆栈储存数据的方式进行数据存储,对该区域寻址的指针由用户自己定义,所以叫做软件堆栈区。 

       AVR单片机的硬件堆栈的生长方向是向下的(从高地址向低地址生长),所以软件堆栈在定义的时候,也采取相同的生长方向。 

       这里没有用ATmega128而采用AVR单片机的提法是因为ATmega128属于AVR系列单片机中的一种,而所有的AVR单片机的数据存储器组织方式都是一致的。在创建μC/OS-Ⅱ的任务栈时,需要了解所用微处理器数据存储空间尤其是堆栈空间的组织形式及相关的操作。读者应参阅所用微处理器的资料和编译器的帮助文档,了解该部分知识。

1.3 ATmega128的中断响应机制 

       ATmega128有34个不同的中断源,每个中断源和系统复位在程序存储空间都有一个独立的中断向量(中断入口地址)。每个中断源都有各自独立的中断允许控制位,当某个中断源的中断允许控制位为“1”且全局中断允许位I也为“1”时,系统才响应该中断。 

       当系统响应一个中断请求后,会自动将全局中断允许位I清零,此时,后续中断响应被屏蔽。当系统执行中断返回指令RETI时,会将全局中断允许位I置“1”,以允许响应下一个中断。若用户想实现中断嵌套,必须在中断服务子程序中将全局中断允许位I置“1”。(这一点与8051系列的单片机不同) 

       在中断向量表中,处于低地址的中断具有高的优先级。优先级高只是表明在多个中断同时发生的时候,系统先响应优先级高的中断,并不含有高优先级的中断能打断低优先级的中断处理工程的意思。这与8051系列单片机的中断优先级概念不同。 

       由于μC/OS-Ⅱ的任务切换实际上是模拟一次中断,因此需要知道CPU的中断响应机制。中断发生时,ATmega128按以下步骤顺序执行: 

       A. 全局中断允许位I清零。 
       B. 将指向下一条指令的PC值压入堆栈,同时堆栈指针SP减2。 
       C. 选择最高优先级的中断向量装入PC,程序从此地址继续执行中断处理。 
       D. 当执行中断处理时,中断源的中断允许控制位清零。 

       中断结束后,执行RETI指令,此时 
       A. 全局中断允许位I置“1”。 
       B. PC从堆栈推出,程序从被中断的地方继续执行。 
       特别要注意的是:AVR单片机在响应中断及从中断返回时,并不会对状态寄存器SREG和通用寄存器自动进行保存和恢复操作,因此,对状态寄存器SREG和通用寄存器的中断保护工作必须由用户来完成。

1.4 ATmega128的定时器中断 

       ATmega128有三个定时器:T0,T1,T2;它们三者都有计数溢出中断功能,而且T1和T2还有匹配比较中断,即定时器计数到设定的值时,产生中断并自动清零。若系统采用这种中断方式,其好处是在中断服务程序ISR中不需要重新装载定时器的值。但本文出于通用性的考虑,仍采用定时器计数溢出中断方式。

2 μC/OS-Ⅱ的移植

2.1移植条件 

       要实现μC/OS-Ⅱ的移植,所用的处理器和编译器必须满足一定的条件: 

       (1) 所用的C编译器能产生可重入代码。 
       可重入代码是指可以被一个以上的任务调用,而不必担心其数据会被破坏的代码。可重入代码任何时候都可以被中断,一段时间以后又可以重新运行,而相应的数据不会丢失,不可重入代码则不行。本文所使用ImageCraft公司的ICCAVR V6.29编译器能产生可重入代码。 
       (2) 用C语言就可以打开和关闭中断。 
       本文所使用的ICCAVR V6.29编译器支持在C语言中内嵌汇编语句且提供专门开关中断的宏:CLI()和SEI()。这样,使得在C语言中开关中断非常方便。 
       (3) 处理器支持中断,并且能产生定时中断(通常在10至100Hz之间)本文使用的ATmega128,有3个定时器,能产生μC/OS-Ⅱ所需的定时中断。 
       (4) 处理器支持能够容纳一定数量数据的硬件堆栈。本文使用的ATmega128有4K RAM,硬件堆栈可以开辟在这4K RAM中。 
       (5) 处理器有将堆栈指针和其它CPU寄存器从内存中读出和存储到堆栈或内存中的指令。一般的单片机都满足这个要求(如PUSH、POP指令),且ATmega128还具有直接访问I/O寄存器的指令(IN、OUT等),它比8051系列的单片机更容易实现上述要求。

2.2移植的实现 

       μC/OS-Ⅱ的移植工作包括以下几个内容: 
       用typedef声明与编译器相关的10个数据类型(OS_CPU.H) 
       用#define设置一个常量的值(OS_CPU.H) 
       #define声明三个宏(OS_CPU.H) 
       用C语言编写六个简单的函数(OS_CPU_C.C) 
       编写四个汇编语言函数(OS_CPU_A.S) 
       根据这几项内容,本文逐步来完成。

2.2.1 INCLUDES.H文件 

       INCLUDES.H 是主头文件,在所有后缀名为.C的文件的开始都包含INCLUDES.H文件。使用INCLUDES.H的好处是所有的.C文件都只包含一个头文件,简洁,可读性强。缺点是.C文件可能会包含一些它并不需要的头文件,增加编译时间。我们是以增加编译时间为代价来换取程序的可移植性的。用户可以改写INCLUDES.H文件,增加自己的头文件,但必须加在文件末尾。

程序清单L2.2.1 INCLUDES.H.
#include // ATmega128的寄存器头文件
#include // ICCAVR的宏
#include
#include
#include
#include //一些C语言的标准库

/*
***************************************************************************
* μC/OS-Ⅱ 头文件
***************************************************************************
*/
#include "G:\Porting\ICCAVR\porting12_8\ATmega128\os_cpu.h"
#include "G:\Porting\ICCAVR\Porting12_8\EX1_mega128\os_cfg.h"
#include "G:\Porting\ICCAVR\Porting12_8\SOURCE\ucos_ii.h"
要注意,μC/OS-Ⅱ 的3个头文件的先后顺序是:os_cpu.h,os_cfg.h最后是ucos_ii.h。

2.2.2 OS_CPU.H文件 

       OS_CPU.H包括了用#define定义的与处理器相关的常量、宏和类型定义。其中需要注意以下三点: 

       一,是堆栈的生长方向。正如前面所述,ATmega128的堆栈生长方向是向下生长,即从高地址到低地址,因此,OS_STK_GROWTH要被定义为1。 

       二,是进入临界代码段(critical code section)的方法。μC/OS-II提供了三种进入临界代码段的方法,第一种方法是直接对中断允许位置1或清零,即进入临界代码段时,把中断允许位清零,退出临界代码段时,把中断允许位置1;第二种方法是进入临界代码段时,先将中断状态保存到堆栈中,然后关闭中断。与之对应的是,退出临界代码段时,从堆栈中恢复前面保存的中断状态。第三种方法是,由于某些编译提供了扩展功能,用户可以得到当前处理器状态字的值,并将其保存在C函数的局部变量之中。这个变量可用于恢复状态寄存器SREG的值。由于ICCAVR不提供此项扩展功能,所以本文暂不考虑用第三种方法进入临界代码段。第一种方法存在着一个小小的问题:如果在关闭中断后调用μC/OS-II的功能函数,当函数返回后,中断可能会被打开。我们希望如果在调用μC/OS-II的功能函数前,中断是关着的,那么在函数返回后,中断仍然是关着的。方法1显然不满足要求。本文使用μC/OS-II的第二种方法——先将中断状态保存到堆栈中,然后关闭中断。 

       三,是任务切换函数OS_TASK_SW( )是个宏,具体的实现是在OSCtxSw( )(OS_CPU_A.S)中程序清单L 2.2.2 OS_CPU.H.

#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT extern
#endif
/*
**************************************************************************
* 数据类型
* (与编译器相关的内容)
*************************************************************************
*/
typedef unsigned char BOOLEAN;
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; // 单精度浮点数

typedef unsigned char OS_STK; // 堆栈入口宽度为8位
typedef unsigned char OS_CPU_SR; // 定义状态寄存器为8位
/*
*************************************************************************
*
*方法 #1: 用简单指令开关中断。
* 注意,用方法1关闭中断,从调用函数返回后中断会重新打开!

* 方法 #2: 关中断前保存中断被关闭的状态.
*
*************************************************************************
*/
#define OS_CRITICAL_METHOD 2

#if OS_CRITICAL_METHOD = = 1
#define OS_ENTER_CRITICAL() _CLI() // 关闭中断
#define OS_EXIT_CRITICAL() _SEI() // 打开中断
#endif

#if OS_CRITICAL_METHOD = = 2
#define OS_ENTER_CRITICAL() asm("st -y,r16\n in r16,0x3F\n cli\n push r16\n ld r16,y+"); // 关闭中断
#define OS_EXIT_CRITICAL() asm("st -y,r16\n pop r16\n out 0x3F,r16\n ld r16,y+"); // 打开中断
#endif
#define OS_STK_GROWTH 1 // 堆栈向下生长
#define OS_TASK_SW() OSCtxSw()
2.2.3 OS_CPU_C.C文件
μC/OS-II的移植需要用户编写OS_CPU_C.C中的十个函数:
OSTaskStkInit();
OSInitHookBegin ();
OSInitHookEnd ();
OSTaskCreateHook();
OSTaskDelHook();
OSTaskSwHook();
OSTaskStatHook();
OSTimeTickHook();
OSTCBInitHook ();
OSTaskIdleHook (); 

       实际需要修改的只有OSTaskStkInit()函数,其它九个函数都是由用户定义的。如果用户需要使用这九个函数,可将文件OS_CFG.H中的#define constant OS_CPU_HOOKS_EN设为1,设为0表示不使用这些函数。本文自定义的任务堆栈结构下图所示。函数OSTaskStkInit()是由OSTaskCreate()或OSTaskCreateExt()调用,用来初始化任务堆栈的。经初始化后的任务堆栈应该跟发生过一次中断后任务的堆栈结构一样。由前叙述可知,ATmega128在发生中断后,自动保存了程序计数器PC。为了保存全部现场,还需要保存状态寄存器SREG,R0~R31这32个通用寄存器及SP的值。 

       需要注意的是:μC/OS-Ⅱ规定,在建立任务时,只能传递一个参数给任务,而且这个参数是一个指针;ICCAVR编译器规定,传递给函数的第一个参数是放在R16、R17中的,所以在R16、R17的位置中放置的是向任务传递的参数。R28、R29的值不需要入栈,是因为R28、R29所组成的Y指针被用作软件堆栈的指针返回给调用函数。 

       根据上述自定义任务堆栈的结构,编写OSTaskStkInit()。其程序清单如2.2.3所示。

程序清单L 2.2.3 OS_CPU_C.C
#define OS_CPU_GLOBALS
#include "G:\Porting\ICCAVR\porting12_8\EX1_mega128\includes.h" //包含头文件
/*
*************************************************************************
* 九个接口函数(暂未使用)
*************************************************************************
*/
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
void OSInitHookBegin (void)
{
}
#endif
... ...
/*
*************************************************************************
* OSTaskStkInit()
*************************************************************************
*/
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos,
INT16U opt)
{
INT8U *psoft_stk;
INT8U *phard_stk; //为操作AVR单片机软、硬件堆栈而建立的临时指针
INT16U tmp;

opt = opt; //'opt'未使用,此处可防止编译器的警告
psoft_stk = (INT8U *)ptos; // 载入堆栈指针
phard_stk = (INT8U *)ptos -OS_TASK_SOFT_STK_SIZE // 任务栈栈空间的大小 L1

+ OS_TASK_HARD_STK_SIZE; // 系统返回的堆栈(硬件堆栈) L2
tmp = *(INT16U const *)task;
*phard_stk-- = (INT8U)tmp;
*phard_stk-- = (INT8U)(tmp >> 8); //把任务入口地址放入硬件堆栈
//******************通用寄存器入栈**************************/
*psoft_stk-- = (INT8U)0x00; // R0 = 0x00 L3
*psoft_stk-- = (INT8U)0x01; // R1 = 0x01
*psoft_stk-- = (INT8U)0x02; // R2 = 0x02
*psoft_stk-- = (INT8U)0x03; // R3 = 0x03
*psoft_stk-- = (INT8U)0x04; // R4 = 0x04
*psoft_stk-- = (INT8U)0x05; // R5 = 0x05
*psoft_stk-- = (INT8U)0x06; // R6 = 0x06
*psoft_stk-- = (INT8U)0x07; // R7 = 0x07
*psoft_stk-- = (INT8U)0x08; // R8 = 0x08
*psoft_stk-- = (INT8U)0x09; // R9 = 0x09
*psoft_stk-- = (INT8U)0x10; // R10 = 0x10
*psoft_stk-- = (INT8U)0x11; // R11 = 0x11
*psoft_stk-- = (INT8U)0x12; // R12 = 0x12
*psoft_stk-- = (INT8U)0x13; // R13 = 0x13
*phard_stk-- = (INT8U)tmp;
*phard_stk-- = (INT8U)(tmp >> 8); //把任务入口地址放入硬件堆栈
//***********R16、R17的位置中放置向任务传递的参数*****************/
tmp = (INT16U)p_arg;
*psoft_stk-- = (INT8U)tmp;
*psoft_stk-- = (INT8U)(tmp >> 8);
*psoft_stk-- = (INT8U)0x18; // R18 = 0x18
*psoft_stk-- = (INT8U)0x19; // R19 = 0x19
*psoft_stk-- = (INT8U)0x20; // R20 = 0x20
*psoft_stk-- = (INT8U)0x21; // R21 = 0x21
*psoft_stk-- = (INT8U)0x22; // R22 = 0x22
*psoft_stk-- = (INT8U)0x23; // R23 = 0x23
*psoft_stk-- = (INT8U)0x24; // R24 = 0x24
*psoft_stk-- = (INT8U)0x25; // R25 = 0x25
*psoft_stk-- = (INT8U)0x26; // R26 = 0x26
*psoft_stk-- = (INT8U)0x27; // R27 = 0x27
/***R28、R29用作软件堆栈的指针储存在任务控制块OS_TCB的OSTCBStkPtr中***/
*psoft_stk-- = (INT8U)0x30; // R30 = 0x30
*psoft_stk-- = (INT8U)0x31; // R31 = 0x31L2
*psoft_stk-- = (INT8U)0x80; // SREG = 0x80,开全局中断
tmp = (INT16U)phard_stk;
*psoft_stk-- = (INT8U)(tmp >> 8); // SPH
*psoft_stk = (INT8U) tmp; // SPL
return ((void *)psoft_stk);} 

       L1 OS_TASK_SOFT_STK_SIZE是指用户任务栈栈空间的大小,这个值在Os_cfg.h中设定。决定任务栈栈空间的大小是一件很困难的事情,因为不仅要计算任务本身的需求(局部变量、函数调用等),还需要计算最多中断嵌套层数(保存寄存器,中断服务子程序的局部变量等)。在本文的移植过程中,所建任务不是特别复杂,所以将任务栈栈空间的大小设定为80。 

       L2 OS_TASK_HARD_STK_SIZE 是指默认的系统硬件堆栈空间的大小,这个值也是在Os_cfg.h中设定。本文设定为20,因为在程序嵌套程度不是很深的情况下,20个字节的硬件堆栈空间是足够了。当OS_TASK_HARD_STK_SIZE设定为20时,应该在ICCAVR编译器中,把Complier Option ->Target下的Return Stack Size也设置为20。这样设置后,编译器才会将硬件堆栈空间按照我们所希望的大小分配。需要注意的是:任务栈栈空间要根据具体的微处理器来定义,比如8051系列的微处理器没有软件堆栈和硬件堆栈的概念,就不需要在任务栈中划分硬件堆栈空间了。 

       L3 在初始化用以保存通用寄存器的区域时,本文不放零而放与寄存器标号一致的十六进制数,是为了在测试代码的时候方便从内存中找出任务栈。

2.2.4 OS_CPU_A.S文件 

       μC/OS-II 的移植需要用户编写OS_CPU_A.S中的四个函数:OSStartHighRdy();OSTaskSwitch();OSIntCtxSw();OSTickISR();这些函数涉及到出栈和入栈操作,因此需要用汇编语言编写。注意,在ICCAVR中,汇编语言文件是以.S而不是.asm作为文件后缀名的。

(1) OSStartHighRdy() 

       该函数由OSStart()函数调用,功能是在多任务调度开始时运行优先级最高的就绪任务。它主要完成三件事:一是通知操作系统,多任务调度已经开始;二是使SP指向优先级最高的就绪任务的任务栈栈顶;三是恢复最高优先级任务的运行环境——各CPU寄存器的内容等。函数OSStartHighRdy()的代码如程序清单224-1所示。

程序清单224-1 OSStartHighRdy( )
_OSStartHighRdy:: CALL _OSTaskSwHook ;调用用户定义的任务切换接口函数
LDS R16,_OSRunning ;表明多任务调度开始 L1
INC R16
STS _OSRunning,R16 ;OSRunning变为“TRUE”
LDS R30,_OSTCBHighRdy ;让Z指针指向优先级最高的
LDS R31,_OSTCBHighRdy+1 ;任务的任务控制块TCB
LD R28,Z+ ;将任务栈的指针装入
LD R29,Z+ ;Y寄存器中
POP_SP ;装入当前任务的SP
POP_SREG ;装入当前任务的SREG
POP_ALL ;装入当前任务的寄存器
RET ;开始执行当前任务 L2 

       L1 ICCAVR规定:在汇编程序中,向编译器声明一个全局变量是在该变量的标识符前加上一条下划线作为前缀。_OSRunning表示OSRunning是一个全局变量;向编译器声明一个外部函数则是在该函数的标识符前加上一条下划线并在其后加上两个冒号。_OSStartHighRdy::表示OSStartHighRdy是个外部函数,其它C言语文件可以以OSStartHighRdy()的形式对它进行调用。 

       L2 POP_SP,POP_SREG,POP_ALL是三个宏。其作用是恢复任务现场。当执行RET指令后,当前任务的地址将被装入PC,从而开始执行当前任务。任务现场的恢复顺序应该同自定义的任务栈的存储顺序一致,否则会出现出入栈错误。现给出POP_ALL详细代码,请读者比照OSTaskStkInit()函数初始化后任务栈进行理解。其余宏代码的编写,与POP_ALL类同。

程序清单 POP_ALL
_.macro POP_ALL ;重新装载所有的通用寄存器
LD R31,Y+
LD R30,Y+
LD R27,Y+ L1
LD R26,Y+
LD R25,Y+
LD R24,Y+
LD R23,Y+
LD R22,Y+
LD R21,Y+
LD R20,Y+
LD R19,Y+
LD R18,Y+
LD R17,Y+
LD R16,Y+
LD R15,Y+
LD R14,Y+
LD R13,Y+
LD R12,Y+
LD R11,Y+
LD R10,Y+
LD R9,Y+
LD R8,Y+
LD R7,Y+
LD R6,Y+
LD R5,Y+
LD R4,Y+
LD R3,Y+
LD R2,Y+
LD R1,Y+
LD R0,Y+
.endmacro
L1 OSTaskStkInit()中没有给R28和R29留存储空间,所以POP_ALL里面也不能将R28和R29弹出。
(2) OSCtxSw()
OSCtxSw()是一个任务级的任务切换函数,它主要完成以下几件事:保存当前任务现场;保存当前任务的任务栈指针到当前任务的任务控制块;切换最高优先级任务为当前任务;使SP指向最高优先级任务的任务栈的栈顶;恢复新任务的运行环境。由于ATmega128没有软中断指令,只能通过汇编子程序来模拟中断。OSCtxSw()程序如程序清单224-4所示。程序清单224-2 OSCtxSw( )
_OSCtxSw:: PUSH_ALL ;保存当前任务现场
PUSH_SREG
PUSH_SP

LDS R30,_OSTCBCur ;保存当前任务的堆栈指针
LDS R31,_OSTCBCur+1 ;到当前任务的任务控制块TCB
ST Z+,R28
ST Z+,R29

CALL _OSTaskSwHook ;调用用户接口程序 L1

LDS R16,_OSPrioHighRdy ; 把最高优先级的任务切换为当前任务
STS _OSPrioCur,R16 ;OSPrioCur = OSPrioHighRdy
LDS R30,_OSTCBHighRdy ;Z = OSTCBHighRdy->OSTCBStkPtr
LDS R31,_OSTCBHighRdy+1
STS _OSTCBCur,R30 ;将新任务的任务栈指针保存到 L2
STS _OSTCBCur+1,R31 ;任务控制块中

LD R28,Z+ ;装载新任务的任务栈指针
LD R29,Z+

POP_SP ;恢复任务的运行环境
POP_SREG
POP_ALL
RET ; 转到新任务中去执行
L1 如果这个由用户定义的接口程序不用,可以把它注释掉,这样可以节约节约几个时钟周期。
L2 虽然新任务有可能还是原来正在运行的任务,如果原来任务的优先级仍然是最高的话。这里的新任务指经过任务切换后,即将运行的任务。

(3) OSIntCtxSw() 

       由于中断可能会使更高优先级的任务进入就绪态。为了让更高优先级的任务能立即运行,所以需要在中断中进行任务切换。在中断服务子程序的最后,OSIntExit()函数会调用OSIntCtxSw()做任务切换。OSIntCtxSw()是一个中断级的任务切换函数。由于在此之前,中断服务程序已经保存了被中断任务的现场,因此不需要再保存现场了。中断级任务切换函数OSIntCtxSw()的其它操作跟OSCtxSw()一样,它的程序如程序清单224-3所示。
程序清单224-3 OSIntCtxSw( )
_OSIntCtxSw::
CALL _OSTaskSwHook ;调用用户接口程序

LDS R16,_OSPrioHighRdy ;把最高优先级的任务切换为当前任务
STS _OSPrioCur,R16 ;OSPrioCur = OSPrioHighRdy
LDS R30,_OSTCBHighRdy ;Z = OSTCBHighRdy->OSTCBStkPtr
LDS R31,_OSTCBHighRdy+1
STS _OSTCBCur,R30 ;将新任务的堆栈指针保存到
STS _OSTCBCur+1,R31 ;任务控制块中

LD R28,Z+ ;装载新任务任务栈的指针
LD R29,Z+

POP_SP ;恢复任务运行的环境
POP_SREG
POP_ALL
RET ;转到新任务中去执行

(4) OSTickISR() 

       μC/OS-II要求用户提供一个周期行的时钟源,来实现时间的延时和超时功能。为了达到这一要求,可以使用硬件定时器,也可以从交流电中获得50/60Hz的时钟频率。本文是采用ATmega128的硬件定时器Timer1来获得周期为100ms的时钟节拍。它的程序如程序清单224-4所示。
程序清单224-4 OSTickISR( )
_OSTickISR:: PUSH_ALL ;保存当前任务现场
IN R16,SREG ;保存SREG寄存器
SBR R16,0x80 ;使该保存值含有中断允许信息 L1
ST -Y,R16
PUSH_SP ;保存任务硬件堆栈指针到任务栈中

LDS R16,_OSIntNesting ;告诉uC/OS-II发生了中断
INC R16 ;中断嵌套计数器加1
STS _OSIntNesting,R16

CPI R16,1 ;如果是第一次中断
BRNE OSTickISR_1 ;

LDS R30,_OSTCBCur ;则需要在当前任务的任务控制块TCB
LDS R31,_OSTCBCur+1 ;中保存堆栈指针Y
ST Z+,R28 ;
ST Z+,R29 ;

OSTickISR_1:
LDI R16,0xF9 ;重新装载定时器
OUT TCNT1H,R16
LDI R16,0xE6
OUT TCNT1L,R16
SEI ;允许中断嵌套 L2

CALL _OSTimeTick ;调用OSTimeTick() L3
CALL _OSIntExit ;调用OSIntExit() L4

POP_SP 恢复新任务运行的环境
POP_SREG
POP_ALL
RET ; 转到新任务中去执行 L5 

       L1 如前所述,当中断发生后,硬件会自动将SREG寄存器的全局中断允许位I清零,执行中断返回指令RETI以后,硬件会自动将全局中断允许位置“1”。那么,我们为什么还要在保存SREG寄存器前使它的保存值重新带上允许中断的信息呢,我们为什么不直接用中断返回指令RETI而要用函数返回指令RET呢?原因是这样的,假设任务A正在执行,这时系统时钟节拍到了,μC/OS-II开始进行时钟节拍服务,它先保存A任务的现场,然后执行中断服务程序,在退出中断以前,μC/OS-II调用OSIntExit()进行任务切换,如果这时有一个优先级比A高的任务B就绪,这样,恢复新任务的运行环境就是恢复B任务的环境,A任务的运行环境将被保存在A任务的任务栈中。试想,如果在保存A任务的SREG寄存器的值的时候,没有使它带上允许中断的信息,那在下次恢复A任务的运行环境时,中断将会被永远关掉。 

       L2 如前所述,欲在AVR单片机中实现中断嵌套,需在中断服务子程序中打开全局中断。 
       L3 告诉μC/OS-II发生了一次时钟中断。 
       L4 告诉μC/OS-II即将退出中断,并作一次任务切换。 
       L5 中断服务子程序返回用RET替代了RETI,是因为允许中断由恢复新任务现场完成,而不是由RETI完成。具体原因如L1所述。 

       至此,所有移植工作就算完成了,我们再回顾一下在移植的过程中需要用户做的工作: 
       1. 了解处理器和C编译器的技术细节; 
       2. 编写一个头文件:Includes.h; 
       3. 编写三个与处理器相关的文件:OS_CPU.H,OS_CPU_C.C,OS_CPU_A.S 
       当上述工作完成后,代码移植也就完成了,接下去的工作便是测试移植的代码,具体的测试工作,请参考邵贝贝译的《嵌入式实时操作系统μC/OS-II(第2版)》。

原文地址:http://www.eefocus.com/html/06-12/061002037093.shtml

 

阅读(1287) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~