nullUC/OS-II在S3C44B0X上的移植UC/OS-II在S3C44B0X上的移植SA06157022 祁朋祥
王明富
杨永海主要内容主要内容概述
移植uc/os-II的条件
在s3c44b0x上移植uc/os-II的可行性
在s3c44b0x上移植uc/os-II前的准备
在s3c44b0x上移植uc/os-II的移植过程
移植后的测试概述概述 uC/OS-II是一个完整、占先式实时实时多任务操作系统,源码公开、内核小巧可裁,可以方便的进行移植、固化和裁减。
实时性强、支持多任务可方便移植到多种嵌入式平台,适用于安全性要求苛刻的系统
uC/OS-II是一个可剥夺式内核,不支持轮转法调度,每个任务都有独立优先级并且使用本地堆栈;在中断退出后并不返回被中断任务,而是直接调度就绪的高优先级任务执行,从而能够尽快让高优先级的任务得到响应,保证系统的实时性能;同时对临界资源管理借鉴了 Unix 中信号量等成熟技术。(也会避免优先级翻转)
uc/os-II 硬件/软件体系结构uc/os-II 硬件/软件体系结构概述(续)概述(续) S3C44B0X使用ARM7TDMI核,通过扩展一系列完整的通用外围器件,使系统费用降至最低,特别适合对成本和功耗敏感的应用场合。
主要功能描述如下:
①工作在66MHz;
②带8KHz缓存的2.5V静态ARM7TDMICPU核;
③扩展内存控制器(FP/FDO/SDRAM控制,片选逻辑);
④带有1个专用DMA通道的LCD控制器;
主要功能(续)主要功能(续) ⑤2个通用DMA通道,1个多主机I2C总线控制器;
⑥5个PWM定时器及1个内部定时器;
⑦71个通用I/O口,8个外部中断资源;
⑧能量控制模式:正常、低、休眠和停止;
⑨8个10位ADC;
⑩带PLL的片上时钟发生器。
移植概述移植概述 所谓移植就是把实时内核安装到某个微处理器上,使应用程序在它的支持下运行.
移植需要做好两部分的工作:
根据处理器要求对uc/os系统进行裁减,定义内核的大小和功能.
连接系统内核和该处理器,为内核编写与硬件相关的内核代码.移植uc/os-II的条件移植uc/os-II的条件 大部分 uC/OS-II 代码使用高度可移植性 ANSI C编写,少量的采用汇编语言编写
uC/OS-II要正常移植、运行,处理器以及环境必须满足一定的条件:
①C语言编译器可产生可重入代码;
②使用c语言就可以开、关中断; 移植uc/os-II的条件(续)移植uc/os-II的条件(续)③处理器支持中断,并且能产生定时中断(通常为10~100Hz);
④处理器能够支持一定数量的数据存储硬件堆栈(可能是几千字节);
⑤处理器有将堆栈指针以及其他CPU寄存器内容读出并存储到堆栈或内存中去的指令在s3c44b0x上移植uc/os-II的可行性在s3c44b0x上移植uc/os-II的可行性大部分 uC/OS-II 代码使用高度可移植性 ANSI C 编写,方便移植;
设计uC/OS-II时,设计者充分考虑了它的可移植性,采取了多种措施,以方便用户在各种微处理器上都能方便地移植它;
内核中与处理器相关代码被纳入单独模块,各模块高内聚,模块间低耦合,从而简化移植工作量;在s3c44b0x上移植uc/os-II的可行性在s3c44b0x上移植uc/os-II的可行性S3C44B0X 基于 ARM7TDMI 核,它具有使 uC/OS-II 能正常运行所必须满足的上述移植一切条件;
由于只有汇编语言才能对CPU内部寄存器直接操作,因此与硬件相关的代码仍然需要用汇编语言来编写.
在s3c44b0x上移植uc/os-II过程在s3c44b0x上移植uc/os-II过程 在移植时,要根据应用程序的功能在uc/os-II的系统配置文件中选择相应的功能函数,舍弃不用的功能, 根据需要进行配置,对文件进行修改.
在移植中最主要的是要对与硬件相关的三个代码文件OS_CPU.H,OS_CPU_A.ASM
OS_CPU_C.C进行修改,使之适应相应的CPU移植需要修改的部分移植需要修改的部分移植需要修改的部分(续)移植需要修改的部分(续)基本的配置和定义(OS_CPU.H)基本的配置和定义(OS_CPU.H)定义与编译器相关的数据类型
由于C语言中的short ,int ,long 等数据类型与处理器有关,隐含着不可移植性,故需要自己定义一套数据类型.
uc/os-II定义了浮点数据类型供用户使用.
任务堆栈是操作系统中很重要的数据结构,用户必须把任务堆栈的数据类型
uc/os II,所有的任务堆栈都必须使用OS_STK作为它的数据类型.定义允许和禁止中断宏定义允许和禁止中断宏uc/os-II需要先禁止中断,再访问代码的临界段,并且在访问完毕后再重新允许中断,以防止临界段代码被多任务或者中断服务例程ISR的破坏.(实例)
uc/os-II中定义了两个宏来禁止和允许中断:
OS_ENTER_CRITICAL( ) (禁止中断)
OS_EXIT_CRITICAL( ) (允许中断) 实例(备注)实例(备注)低优先级任务 高优先级任务
While(1){ while(1){
x=1; z=3;
y=2; t=4;
swap( &x, &y); swap( &z, &t);
{Temp=*x; Temp= =1 {Temp=*z;
*x=*y; *z=*t;
*y=Temp;} *t=Temp;}
… …
OSTimeDly(1); OSTimeDly(1);
} Temp= =3 }宏使用
(一/三种)宏使用方法(一/三种) 方法一:
在进入临界区的宏OS_ENTER_CRITICAL( )中,调用处理器指令来禁止中断,而在退出临界区的宏OS_EXIT_CRITICAL ( )中调用允许中断指令.
OS_ENTER_CRITICAL( )
临界区;
OS_EXIT_CRITICAL ( )
缺点:
会导致在脱离临界代码后中断强行被打开,不论进入临界区前中断是开还是关,从而破坏原有状态.
宏使用方法(二/三种)宏使用方法(二/三种) 方法二:
在进入临界区代码前,把状态寄存器推入堆栈保存,然后关中断;在脱离临界区时弹出保持的寄存器的状态.
缺点:
由于是在C中嵌入汇编语句来实现的,编译器可能不知道堆栈指针因入栈操作而改变了,因此有可能发生严重错误.宏使用方法(三/三种)宏使用方法(三/三种) 方法三:
在临界区代码宏调用的函数中设置一个局部变量,用来保存进入临界区的CPU状态寄存器,然后禁止中断,退出临界段代码前,再恢复CPU状态寄存器.
这种方法需要在访问临界区的函数中增设局部变量,但是这种方法最安全.
#define OS_ENTER_CRITICAL()
cpu_sr=get_processor_psw();
disable_interrupts();
#define OS_EXIT_CRITICAL();
set_processor_psw(cpu_sr);
使用宏开/关中断注意的问
使用宏开/关中断注意的问题如果在调用象OSTimeDly()之类的功能函数之前就关中断,应用程序就会崩溃.
原因:OSTimeDly()实际上是依靠时钟节拍实现中断的,而因为中断是关闭的,程序就不可能获得时钟节拍中断.
解决办法:应该在中断允许的情况下调用uC/OS-II的系统功能函数.定义OS_TASK_SW()宏定义OS_TASK_SW()宏 在低优先级任务切换到高优先级任务时被调用.
两种定义方式:
①如果处理器支持软中断指令,则使用软中断向量指向OSCtxSw()函数;
②直接调用OSCtxSw()函数.
处理器不支持软中断指令时需要用户把堆栈结构构造成与中断堆栈相同的结构.
移植汇编的四个与处理器相关的函数OS_CPU_A.ASM移植汇编的四个与处理器相关的函数OS_CPU_A.ASM ①OSStartHighRdy( )
该函数负责从最高优先级任务的TCB控制块中获得该任务的堆栈指针SP,并通过SP依次将CPU现场恢复.
只在多任务启动时被执行一次,用来启动最高优先级的任务执行.
移植原因:它涉及将处理器寄存器保存到堆栈的操作.移植汇编的四个与处理器相关的函数OS_CPU_A.ASM移植汇编的四个与处理器相关的函数OS_CPU_A.ASM ②OSCtxSw( )—任务级的任务切换
该函数先将当前任务的CPU现场保存到该任务的堆栈之中,然后获得最高优先级任务的堆栈指针,并从该堆栈中恢复此任务的CPU现场使之继续执行.到此该函数就完成了一次任务切换.
另外,具体负责任务切换的中断例程陷阱指令或异常处理例程的向量地址必须指向任务级切换函数OSCtxSw( ).移植汇编的四个与处理器相关的函数OS_CPU_A.ASM移植汇编的四个与处理器相关的函数OS_CPU_A.ASM ③OSIntCtxSw( )—中断级的任务切换
为使更高优先级的就绪任务能立即执行,在中断服务子程序的最后,OSIntExit( )函数会调用OSIntCxtSw( )做任务切换.这样就可以保证更高优先级的任务得到响应,保证系统的实时性能.
OSCtxSw( )与OSIntCtxSw( )区别:后者无须再保存寄存器,因为在调用此函数之前发生了中断,已经将CPU寄存器保存过了.移植汇编的四个与处理器相关的函数OS_CPU_A.ASM移植汇编的四个与处理器相关的函数OS_CPU_A.ASM ④OSTickISR ( ) -时钟节拍服务子程序
时钟的节拍式中断使得内核可将任务延时若干个整数时钟节拍,以及当任务等待事件发生时,提供等待超时的依据.
OSTickISR先将CPU的寄存器的值保存在被中断的任务堆栈中,之后调用OSTimeTick 检查所有处于延时等待状态的任务,判断是否有延时结束就绪的任务. 最后调用OSIntSw进行任务切换.
应该在OSStart()后调用,但是此函数并不返回,所以可以在开始调用的某个中调用。移植与C编写的6个与操作系统相关的函数OS_CPU_C.C移植与C编写的6个与操作系统相关的函数OS_CPU_C.COSTaskStkInit( ) 初始化堆栈结构
OSTaskCreateHook( ) 建立任务
OSTaskDelHook( ) 删除任务
OSTaskSwHook( ) 任务切换
OSTaskStatHook( ) 统计任务
OSTimeTickHook( ) 时钟节拍调用
在这六个函数中唯一必须要移植的是任务堆栈初始化函数OSTaskStkInit( ).
剩余的五个函数又称之为钩子函数,主要用来扩展uc/os-II的功能 ,必须被声明,但并不一定要包含代码.OSTaskStkInit( )
OSTaskStkInit( )
这个函数在任务创建时被调用,负责初始化任务的堆栈结构并返回新堆栈的指针stk. OSTaskCreate( )和OSTaskCreateExt( )会获得该地址,并且将它保存到任务控制块(OSTCB)中.处理器文档告诉用户堆栈指针指向下一个堆栈空闲位置,还是指向最后存入数据的堆栈单元位置.
初始化堆栈后的堆栈内容初始化堆栈后的堆栈内容 OSTaskStkInit( )的示意性代码
OS_STK *OSTaskStkInit(void (*task ) (void *pd),
Void *pada ,
OS_STK *ptos,
INT16U opt );
{
模拟带
(pada)的函数调用; (1)
模拟ISR向量; (2)
按照预先设计的寄存器值初始化堆栈结构; (3)
返回栈顶指针给调用该函数的函数; (4)
}OSTaskCreateHook( )OSTaskCreateHook( ) 当用OSTaskCreate ( )或者OSTaskCreateExt( )建立任务时就会调用OSTaskCreateHook( ). 该函数允许用户扩展uc/os-II的功能,即建立函数、扩展任务.
* 需要注意的是该函数在被调用时中断是开着的,因此要防止在其执行过程中被别的函数中断.OSTaskDelHook( )OSTaskDelHook( ) 当任务删除时,就会调用这个函数.当它被调用的时候会收到指向正在被删除任务的OS_TCB的指针.
OSTaskDelHook( )可以用来检验TCB扩展是否被建立了一个非空指针,并进行一些清除操作.该函数不返回任何值.
函数被调用的时候,中断是关掉的,所以该函数的代码太长会影响中断响应的时间OSTaskSwHook( )OSTaskSwHook( ) 当发生任务切换时会调用该函数.
不管任务切换是OSIntCtxSw ( )还是OSCtxSw ( )来执行,都会调用该函数.
OSTaskSwHook可以直接访问OSTCBCur和OSTCBHighRdy,因为他们都是全局变量OSTCBCur指向被切换出去任务OS_TCB的,OSTCBHighRdy指向新任务OS_TCB.
在调用时,中断一直是被禁止的.OSTaskStatHook( )OSTaskStatHook( ) OSTaskStatHook ( )每秒钟都会被统计函数(在测试程序中的大部分时间里该功能都被屏蔽掉).OSTaskStat ( )调用一次来扩展统计功能.
例如:在统计中可以保存并显示每个任务的执行时间,频率,每个任务所占用的CPU份额.OSTimeTickHook( )OSTimeTickHook( )
OSTimeTickHook( )在每个时钟节拍都会被OSTimeTick ( )调用.实际上OSTimeTick ( )是在节拍被uc/os-II处理,并在通知用户的移植实例或应用程序之前被调用的.*移植后的测试**移植后的测试* 当用户为自己的处理器做完uc/os-II的移植后,紧接着的工作就是验证移植的uc/os-II是否正常工作,而这是移植中最复杂的一步.
应该首先不加任何应用代码来测试移植好的uc/os-II,也就是说,应该首先测试内核自身的运行状况.
也就是所说的自己测试自己移植后的测试移植后的测试不加代码测试的原因:
用户不希望将事情复杂化.
如果有些部分没有正常工作,可以明白是移植本身的问题,而不是应用代码产生的问题.测试步骤测试步骤①确保C编译器/汇编编译器以及连接器正常工作;
②验证OSTaskStkInit (C)和OSStartHighRdy (A)函数;
③验证OSCtxSw(A)函数;
④验证OSIntCtxSw (A)和OSTickISR (A)函数;确保C编译器/汇编编译器以及连接器正常工作确保C编译器/汇编编译器以及连接器正常工作当根据CPU对程序作相应的修改后,要把这些文件同与处理器无关的文件一起进行编译和链接,生成目标文件。
下面是一段简单的TEST.C程序,由于没有添加任何的应用代码,所以可以很容易地解决与编译器、连接器和汇编器相关的错误。第一步:TEST.C第一步:TEST.C#include “includes.h”
void main(void)
{
OSInit( );
OSStart( );
}
验证OSTaskStkInit ( )和OSStartHighRdy ( )函数验证OSTaskStkInit ( )和OSStartHighRdy ( )函数使用源代码调试器测试
修改OS_CFG.H文件,设置OS_TASK_STAT_EN 为0,禁止统计任务,因为此时的唯一任务是空闲任务:OS_TaskIdle(). 可以单步执行到此处
OSStartHighRdy()会将OSTaskStkInit()压入堆栈的CPU寄存器,并按照相反的顺序弹出,检查。
一旦上面的没有错误,指向OS_TaskIdle()。检查修改OSTaskStkInit()函数。
null运行与不运行测试法
用发光二极管来指示处理器的运行状态:首先关闭LED,如果程序正常,则OS_TaskIdle()函数点亮LED,可以用示波器观察波形来确定二极管是否闪烁。
对相应的程序作适当的修改
#include “includes.h” void OSTaskIdleHook(void)
void main( void ) { if( LED is ON){
{OSInit(); 关LED;}
关LED; else{
OSStart(); 开LED;
} [TEST.C] }
}[OS_CUP_C.C]
验证OSCtxSw( )函数验证OSCtxSw( )函数#include “includes.h”
OS_STK TestTaskStk[100]; void TestTask(void *pdata)
void main( void ) { pdata=pdata;
{ OSInit(); while(1){
OSTaskCreate(TestTask, OSTimeDly(1);
(void *)0,&TestTaskStk[99],0); }
OSStart(); }
}null#include “includes.h”
OS_STK TestTaskStk[100];
void main( void )
{ OSInit();
关闭LED; (1)
OSTaskCreate(TestTask,(void *)0, &TestTaskStk[99],0); (2)
OSStart();
}
void TestTask(void *pdata) (3)
{
pdata=pdata;
while(1){ (4)
OSTimeDly(1);
}
}验证OSIntCtxSw ( )和OSTickISR ( )函数验证OSIntCtxSw ( )和OSTickISR ( )函数在此测试之前,应该保证时钟中断向量指向了时钟节拍中断服务子程序,然后,初始化时钟节拍并开中断。
这里的OSInitCtxSw()中的多数代码都可以从OSCtxSw()中获得。
程序从更改TEST.C中的main() 函数开始null#include “includes.h”
OS_STK TestTaskStk[100];
void main( void )
{ OSInit();
关闭LED; (1)
设置时钟节拍中断向量; (2)
OSTaskCreate(TestTask,(void *)0, &TestTaskStk[99],0); (3)
OSStart();
}
void TestTask (void *pdata) (4)
{ BOOL led_state;
pdata=pdata;
初始化时钟节拍中断(定时器); (5)null使能中断; (6)
Led_state=FALSE;
打开LED; (7)
while(1){
OSTimeDly(1); (8)
If(led_state= =FALSE){ (9)
led_state=TRUE;
开LED;
}else{
led_state=FALSE;
关LED;}
}
}
说明说明(1)无论是否使用源代码级的调试器,对这一步测试来说,使用LED(或者其他显示设备)显示都是非常有用的,在运行其他代码之前,先关闭LED
(2)必须设置时钟节拍中断向量。时钟节拍中断应该指向OSTickISR()
(3)建立更高优先级的任务。
(4)由于OSStartHighRdy() 能够正常工作,所以uC/OS-II应将TestTask()作为第1个任务开始执行
(5)当进入TestTask()任务时,应该初始化产生时钟中断的相关设备,以产生一定频率的时钟节拍中断。(如10Hz,则LED以5Hz的频率闪烁)
(6)开中断,允许进入TestTask()
(7)点亮LED,
明进入了TestTask()
null(8)调用OSTimeDly(),使OSCtxSw()将任务切换到空闲任务,空闲任务一直运行一直到接受到时钟节拍中断。此时调用OSTickISR(),进而调用OSTimeTick(),使计数器递减到0,使该任务进入就绪。
(9)如果OSIntSw()正常工作,而且已经将时钟节拍频率设置为10Hz,那么用户应该看到LED以5Hz的频率闪烁 谢谢大家! 谢谢大家!