单片机与嵌入式
51单片机的寻址方式
51单片机的汇编指令系统
51汇编语言的伪指令
51汇编语言程序结构与程序示例
Keil C51程序设计
Keil C51的库函数
PIC单片机的基本结构
PIC单片机的汇编语言指令
PIC单片机的C语言编程
ATmega16单片机基本结构
ATmega16单片机的汇编语言
ATmega128单片机的结构
STM32单片机基础
使用STM32CubeMX开发STM32单片机
uC/OS-II嵌入式操作系统
uC/OS-II在STM32F10xx上的移植
FreeRTOS系统介绍
Linux系统介绍
Linux系统编程
嵌入式Linux编程
一、uC/OS-II的移植:
嵌入式系统宿主对象的差异很大,硬件各具特色,很难统一。所以,uC/OS-II只是提供了一个微内核,并没有提供硬件抽象层,用户必须自己根据使用的硬件来编写其硬件抽象层,或者找一个与硬件相配的硬件抽象层来使用。
要使uC/OS-II正常运行,处理器必须满足以下条件:
·处理器的C编译器能产生可重入型代码;
·用C语言就可以使能和禁止中断;
·处理器支持中断,并且能产生定时中断;
·处理器能支持容纳一定量数据存储的硬件堆栈;
·处理器包含能操作堆栈的指令,可把CPU寄存器的内容读出并保存到堆栈或内存中。
可重入的代码指的是一段代码(如一个函数)可以被多个任务同时调用,而不必担心会破坏数据。可重入代码或者使用局部变量,即变量保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。uC/OS-II进行任务调度时,会把当前任务的CPU寄存器存放到此任务的堆栈中,然后再从另一个新任务的堆栈中恢复原来的工作寄存器,继续运行。所以,寄存器的入栈和出栈是uC/OS-II多任务调度的基础。在STM32F10xx处理器中,多寄存器加载/存储汇编指令STMFD可将所以寄存器压栈,指令LDMFD可将所有寄存器出栈。
uC/OS-II的移植就是需要根据处理器的特点编写OS_CPU.H、OS_CPU_A.ASM、OS_CPU_C. C这3个文件。
移植中需要修改的关键函数和宏定义如表:
名称 |
类型 |
所在文件 |
语言 |
功能 |
OS_CRITICAL_METHOD |
宏定义 |
OS_CPU.H |
C语言 |
处理临界区的方式选择 |
OS_STK_GROWTH |
宏定义 |
OS_CPU.H |
C语言 |
定义堆栈的生长方向 |
OS_TASK_SW() |
宏定义 |
OS_CPU.H |
C语言 |
定义任务级上下文切换 |
OSTaskStkInit() |
函数 |
OS_CPU_C.C |
C语言 |
任务堆栈初始化 |
OSStartHighRdy() |
函数 |
OS_CPU_A.ASM |
汇编 |
就绪状态最高优先级任务运行 |
OSCtxSw() |
函数 |
OS_CPU_A.ASM |
汇编 |
任务级任务切换 |
OSIntCtxSw() |
函数 |
OS_CPU_A.ASM |
汇编 |
中断级任务切换 |
OS_ENTER_CRITICAL() |
函数 |
OS_CPU_A.ASM |
汇编 |
进入临界区的处理函数 |
OS_EXIT_CRITICAL() |
函数 |
OS_CPU_A.ASM |
汇编 |
退出临界区的处理函数 |
1. OS_CPU.H文件:
OS_CPU.H主要包含与编译器相关的数据类型定义、堆栈类型定义,以及几个宏定义和函数说明。OS_CPU.H的大体结构如下:
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned int INT16U;
typedef signed int INT16S;
typedef unsigned long INT32U;
typedef signed long INT32S;
typedef float FP32;
typedef double FP64;
typedef unsigned int OS_STK; //堆栈为32位宽度
typedef unsigned int OS_CPU_SR; //定义CPU状态寄存器为32位
#define OS_STK_GROWTH 1 //定义堆栈生长方向,由高到低
#define OS_TASK_SW() OSCtxSw() //任务级上下文切换
//开关中断原型声明
#if OS_CRITICAL_METHOD= =3
OS_CPU_SR OS_CPU_SR_Save (void);
void OS_CPU_SR_Restore (OS_CPU_SR cpu_sr);
#endif
//其他原型声明
void OSCtxSw(void);
void OSIntCtxSw(void);
void OSStartHighRdy(void);
void OSPendSv(void);
#endif
1)定义相关的数据类型:
因为不同的微控制器字长不一定相同,为了确保移植性,uC/OS-II代码不使用C语言中与编译器相关的short、int和long等数据类型,而是重新定义了一系列数据类型。为了方便,虽然uC/OS-II不使用浮点类型,但还是定义了浮点数据类型。此外,必须将任务堆栈的数据类型告诉uC/OS-II,STM32F10xx处理器上的堆栈成员是32位的,将OS_STK声明为无符号整型,所有的任务堆栈都必须用OS_STK声明数据类型。
2)临界段代码保护:
uC/OS-II在访问代码的临界段时首先要关闭中断,在访问完毕后重新允许中断,这使得uC/OS-II能够保护临界段代码免受多任务或中断服务子程序的破坏。通常,每个处理器都会提供一定的汇编指令开开/关中断,因此用户使用的C语言编译器必须有一定的机制支持直接从C语言中执行这些操作。为了隐藏编译器厂商提供的不同实现方法以增加可移植性,uC/OS-II定义了两个宏来开/关中断,即OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。
#define OS_ENTER_CRITICAL() {cpu_sr=OS_CPU_SR_Save ();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore ();}
在Cortex-M3处理器中,开/关中断是通过改变只有1位的中断屏蔽寄存器组来实现的,在STM32F10xx微控制器上改变这些位是通过嵌入汇编实现的,并在OS_CPU_A.ASM文件中具体编写这两个函数:
OS_CPU_SR_Save
MSR R0, PRIMASK ; set prio int mask to mask all (except faults)
CPSID I
BX LR
OS_CPU_SR_Restore
MSR PRIMASK, R0
BX LR
3)设定堆栈生长方向:
绝大多数微处理器和微控制器的堆栈是从上往下生长的,但也有些相反,uC/OS-II被设计成这两种情况都可以处理,只要在结构常量OS_STK_GROWTH中指定堆栈的生长方式即可。OS_STK_GROWTH为0,表示堆栈从下往上生长,置OS_STK_GROWTH为1,表示堆栈从上往下生长。这里采用从上往下生长的方式。
#define OS_STK_GROWTH 1 //定义堆栈生长方向,由高到低
4)任务调度:
在uC/OS-II中,处于就绪状态任务的堆栈结构看起来就像刚刚发生过中断一样,所有的寄存器都保存在堆栈中。也就是说,uC/OS-II要运行处于就绪状态的任务就必须从任务堆栈中恢复处理器所有的寄存器,并且执行中断返回指令。为了实现任务调度,可以通过执行OS_TASK_SW()模仿中断的产生。
#define OS_TASK_SW() OSCtxSw() //任务级上下文切换
OS_TASK_SW()是uC/OS-II从低优先级任务切换到高优先级任务时被调用的。任务切换只是简单地将处理器的寄存器保存到被挂起的任务的堆栈中,并从堆栈中恢复要运行的更高优先级的任务。可以采用以下两种方式定义OS_TASK_SW():如果处理器支持软中断,则中断服务子程序或指令陷阱使OS_TASK_SW()的中断向量地址指向汇编语言函数OSCtxSw();否则直接在OS_TASK_SW()中调用OSCtxSw()函数。
2. OS_CPU_C.C文件:
OS_CPU_C.C中包含与移植有关的C函数,包括堆栈的初始化函数和一些Hook函数的实现。这里需要用C语言编写10个与操作系统相关的函数。
1)OSTaskStkInit():
OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInit()来初始化任务的堆栈结构。堆栈看起来就像刚发生过中断并将所有寄存器保存到堆栈中的情形一样。用户建立任务的时候,传递任务的地址,任务的堆栈栈顶和任务的优先级传递给OSTaskCreate()和OSTaskCreate Ext()函数。一旦用户初始化了堆栈,OSTaskStkInit()就需要返回堆栈指针所指的地址,函数OSTaskCreate()和OSTaskCreate Ext()会获得该地址并将它保存到任务控制块OS_TCB中。
修改后的OSTaskStkInit()函数程序如下:
OS_STK * OSTaskStkInit (
void (*task) (void *p_arg),
void *p_arg,
OS_STK *ptos,
INT16U opt)
{
OS_STK *stk;
(void)opt; //避免编译器警告
stk=ptos; //获取堆栈指针
/*模拟成异常,自动把寄存器压栈,使用满递减堆栈*/
*(stk)=(INT32U) 0x01000000L; //xPSR
*(--stk)=(INT32U) task; //任务入口地址
*(--stk)=(INT32U) 0xFFFFFFFEL; //R14(LR)
*(--stk)=(INT32U) 0x12121212L; //R12
*(--stk)=(INT32U) 0x03030303L; //R3
*(--stk)=(INT32U) 0x02020202L; //R2
*(--stk)=(INT32U) 0x01010101L; //R1
*(--stk)=(INT32U) p_arg; //R0,输入参数
//剩下的寄存器保存到堆栈
*(--stk)=(INT32U) 0x11111111L; //R11
*(--stk)=(INT32U) 0x10101010L; //R10
*(--stk)=(INT32U) 0x09090909L; //R9
*(--stk)=(INT32U) 0x08080808L; //R8
*(--stk)=(INT32U) 0x07070707L; //R7
*(--stk)=(INT32U) 0x06060606L; //R6
*(--stk)=(INT32U) 0x05050505L; //R5
*(--stk)=(INT32U) 0x04040404L; //R4
return(stk);
}
2)OSTaskCreateHook():
当用OSTaskCreate()和OSTaskCreateExt()函数建立任务时,会调用OSTaskCreateHook(),该函数允许用户或使用移植实例的用户扩展uC/OS-II功能。当uC/OS-II设置完成了自己的内部结构后,会在调用任务调度程序之前调用OSTaskCreateHook()。该函数被调用时中断是禁止的,因此用户应尽量减少该函数中的代码以缩短中断的响应时间。当OSTaskCreateHook()被调用时,它会收到指向已建立任务的OS_TCB的指针,这样它就可以访问所有的结构成员。
#if OS_CPU_HOOKS_EN>0
void OSTaskCreateHook (OS_TCB *ptcb);
{
#if OS_APP_HOOKS_EN>0
APP_TaskCreateHook (ptcb);
#else
(void)ptcb;
#endif
}
#endif
3)OSTaskDelHook():
当任务被删除时会调用OSTaskDelHook(),该函数在把任务从uC/OS-II的内部任务链表中解开之前被调用。当OSTaskDelHook()被调用时,它会收到指向正被删除任务的OS_TCB的指针,这样就可以访问所有的结构成员。OSTaskDelHook()可以检验TCB扩展是否被建立(一个非空指针)并进行一些清除操作。
#if OS_CPU_HOOKS_EN>0
void OSTaskDelHook (OS_TCB *ptcb);
{
#if OS_APP_HOOKS_EN>0
APP_TaskDelHook (ptcb);
#else
(void)ptcb;
#endif
}
#endif
4)OSTaskSwHook():
当发生任务切换时会调用OSTaskSwHook()。OSTaskSwHook()可以直接访问OSTCBCur和OSTCBHighRdy,因为它们是全局变量。OSTCBCur指向被切换出去的任务OS_TCB,而OSTCBHighRdy指向新任务OS_TCB。注意,在调用OSTaskSwHook()期间中断一直是被禁止的,因此用户应尽量减少该函数中的代码以缩短中断的响应时间。
#if (OS_CPU_HOOKS_EN>0)&&(OS_TASK_SW_HOOKS_EN>0)
void OSTaskSwHook (void);
{
#if OS_APP_HOOKS_EN>0
APP_TaskSwHook ();
#endif
}
#endif
5)OSTaskStatHook():
OSTaskStatHook()每秒都会被OSTaskStat()调用一次,用户可以用来扩展统计功能。例如,用户可以保持并显示每个任务的执行时间、每个任务所用的CPU份额,以及每个任务执行的频率等。
#if OS_CPU_HOOKS_EN>0
void OSTaskStatHook (void);
{
#if OS_APP_HOOKS_EN>0
APP_TaskStatHook ();
#endif
}
#endif
6)OSTimeTickHook():
OSTimeTickHook()在每个时钟节拍都会被OSTimeTick()调用。实际上,OSTimeTickHook()是在节拍被uC/OS-II真正处理并通知用户的移植实例或应用程序之前被调用的。
#if (OS_CPU_HOOKS_EN>0)&&(OS_TIME_TICK_HOOKS_EN>0)
void OSTimeTickHook (void);
{
#if OS_APP_HOOKS_EN>0
APP_TimeTickHook ();
#endif
#if OS_TMR_EN>0
OSTmrCtr++;
if (OSTmrCtr>=(OS_TICKS_PER_SEC/OS_TMR_CFG_TICKS_PER_SEC)){
OSTmrCtr=0;
OSTmrSignal();
}
#endif
}
#endif
7)OSTaskIdleHook():
OSTaskIdleHook()被空闲任务OS_TaskIdle()调用。当没有其他高优先级的任务运行时,会运行空闲任务,这样可以使CPU进入低功耗模式。
#if (OS_CPU_HOOKS_EN>0)&&(OS_VERSION>=251)
void OSTaskIdleHook(void);
{
#if OS_APP_HOOKS_EN>0
APP_TaskIdleHook ();
#endif
}
#endif
8)OSTCBInitHook():
无论是否建立了任务,在分配并初始化了任务控制块TCB且任务栈结构已经初始化完毕后,就可以调用OSTCBInitHook()。该函数是用户可以按自己的方式扩展建立任务控制块的函数。
#if (OS_CPU_HOOKS_EN>0)&&(OS_VERSION>203)
void OSTCBInitHook (OS_TCB *ptcb);
{
#if OS_APP_HOOKS_EN>0
APP_TCBInitHook(ptcb);
#else
(void)ptcb;
#endif
}
#endif
9)OSInitHookBegin():
进入OSInit()函数后,OSInitHookBegin()会立即被调用,这个函数可使用户将自己特定的代码放在OSInit()中。
#if (OS_CPU_HOOKS_EN>0)&&(OS_VERSION>203)
void OSInitHookBegin (void);
{
#if OS_TMR_EN>0
OSTmrCtr=0;
#endif
}
#endif
10)OSInitHookEnd():
OSInit()在结束时会调用OSInitHookEnd()函数。
#if (OS_CPU_HOOKS_EN>0)&&(OS_VERSION>203)
void OSInitHookEnd (void);
{
}
#endif
后9个函数名带Hook字样的函数称为钩子函数,移植时可以全部为空函数,不加代码。用户可以在系统函数中编写自己的代码以扩展某些功能。只有当OS_CFG.H中的OS_CPU _HOOKS_EN被置为1时才会产生这些函数的代码。
3. OS_CPU_A.S文件:
uC/OS-II在移植过程中要求用户编写OS_CPU_A.S文件中4个简单的汇编语言函数,包括OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()、OS_CPU_PendSVHandler()。如果C语言编译器支持插入行汇编代码,就可以将所有与处理器相关的代码放到OS_CPU_C.C文件中,而不必建立单独的汇编语言文件。
1)OSStartHighRdy()函数:
uC/OS-II的多任务启动函数OSStart()通过调用函数OSStartHighRdy(),使得处于就绪状态的、优先级最高的任务开始运行。函数OSStartHighRdy()负责从最高优先级任务的TCB控制块中获得该任务的堆栈指针SP,并通过SP依次将CPU现场恢复,这时系统将控制权交给用户创建的任务进程,直到该任务被阻塞或被其他更高优先级的任务抢占CPU。该函数仅在多任务启动时被执行一次,用来启动最高优先级的任务。函数OSStartHighRdy()首先必须设置优先级PendSV,将PSP置0,并且设置OSRunning为TRUE,然后切换到优先级最高的任务。
OSStartHighRdy
LDR R0, =NVIC_SYSPRI2 ;设置PendSV的中断优先级
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 ;清零PSP
MSR PSP, R0
LDR R0, =OS_Running ;OS_Running=TRUE
MOVS R1, #1
STRB R1, [R0]
LDR R0, =NVIC_INT_CTRL ;触发软件中断
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
CPSIE I ;使能所有中断优先级
OSStartHang
B OSStartHang
2)OSCtxSw()函数:
OSCtxSw()是任务级切换函数。任务级的切换时通过软中断命令或依靠处理器执行陷阱指令来完成的。中断服务程序或异常处理例程的向量地址必须指向OSCtxSw()。
OSCtxSw()函数由OS_TASK _SW()宏调用,而OS_TASK _SW()由函数OSSched()调用,函数OSSched()负责任务之间的调度。OSCtxSw()被调用后,先将当前任务的CPU现场保存到该任务的堆栈中;然后获得最高优先级任务的堆栈指针,并从该堆栈中恢复此任务的CPU现场,使之继续运行。这样,函数OSCtxSw()就完成了一次任务级的任务切换。其示意性代码如下:
OSCtxSw
LDR R0, =NVIC_INT_CTRL ;触发软件中断
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
这些代码必须用汇编语言编写,因为用户不能直接在C语言中访问CPU寄存器。
3)OSIntCtxSw()函数:
OSIntCtxSw()是中断级的任务切换函数。OSIntExit()通过调用函数OSIntCtxSw()在ISR中执行任务切换功能。因为中断可能会使更高优先级的任务进入就绪状态,所以为了让更高优先级的任务能够立即运行,在中断服务子程序退出前,函数OSIntExit()会调用OSIntCtxSw()做任务切换,从而保证系统的实时性。
函数OSIntCtxSw()和OSCtxSw()都是用来实现任务切换功能的,区别是OSIntCtxSw()不需要保存CPU寄存器,因为在调用OSIntCtxSw()之前已经发生了中断,在中断服务子程序中已经将CPU寄存器保存到了被中断任务的堆栈中。函数OSIntCtxSw()的示意性代码如下:
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ;触发软件中断
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
这些代码必须用汇编语言编写,因为用户不能直接在C语言中访问CPU寄存器。
4)OS_CPU_PendSVHandler()函数:
这个函数主要用于处理中断。
OS_CPU_PendSVHandler
CPSID I
OSPendSV
MRS R0, PSP ;PSP是任务的堆栈指针
CBZ R0, OS_CPU_PendSVHandler_nosave ;time第一次跳过保存
SUBS R0, R0, #0x20 ;保存寄存器R4~R11到堆栈
STM R0, {R4-R11} ;堆栈,把堆栈指针R0减去32
LDR R1, =OSTCBCur ;OS_TCBCur->OSTCBStkPtr=SP;
LDR R1, [R1]
STR R0, [R1] ;R0是指向被切换的任务堆栈
OS_CPU_PendSVHandler_nosave
PUSH {R14} ;值保存LR返回值
LDR.W R0, =OSTaskSwHook ;OS_TaskSwHook()
BLX R0
POP {R14}
LDR R0, =OSPrioCur ;OSPrioCur=OSPrioHighRdy
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, OSTCBCur ;OSTCBCur=OSTCBHighRdy
LDR R1, OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ;SP=OSTCBHighRdy->OSTCBStkPtr;
LDM R0, {R4-R11} ;stack从新任务的堆栈恢复R4~R11
ADDS R0, R0, #0x20
MSR PSP, R0 ;从新任务的堆栈恢复PSP
ORR LR, LR, #0x04 ;PSP确保返回后使用PSP
BX LR ;返回时会恢复剩下的上下文
3. uC/OS-II应用实例:
1)DrawTFT函数:在液晶屏上绘制花纹。
static void DrawTFT(void *p_arg){
int i,j,k;
//作为任务,函数永远不会返回
while(1)
{
//绘制特殊形状的花纹
for (k=0; k<65535; k++)
for (i=0; i<320; i++)
for (j=0; j<240; j++)
ili9320_SetPoint (i, j, i*j*k);
//把自己挂起来,等待系统调用
OSTaskSuspend(OS_PRIO_SELF);
}
}
2)AppTaskStart()函数:完成系统初始化后调用DrawTFT。
static void AppTaskStart(void *p_arg){
(void)p_arg;
System_Initialize(); //初始化系统时钟等
ili9320_Initializtion(); //初始化液晶屏
#if (OS_TASK_STAT_EN>0)
OSStatInit (); //开启CPU占用率统计任务
#endif
//建立第2个任务,专门用来重复绘制液晶屏幕
OSTaskCreateExt ( DrawTFT,(void *)0,
(OS_STK *)&TFT_STK[TFT_TASK_STK_SIZE-1],
TFT_TASK_PRIO, TFT_TASK_PRIO,
(OS_STK *)&TFT_STK[0],
TFT_TASK_STK_SIZE,
(void *)0,
OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR );
//永远挂起自身,因为系统初始化已完成
OSTaskSuspend(OS_PRIO_SELF);
while(1);
OSTaskSuspend(OS_PRIO_SELF);
}
3)主函数:
int main(void){
OSInit(); //初始化uC/OS-II
//建立第1个任务,初始化系统,建立液晶屏绘制任务
OSTaskCreateExt ( AppTaskStart,(void *)0,
(OS_STK *)&AppTaskStartStk[APP_TASK_START_STK_SIZE-1],
APP_TASK_START_PRIO, APP_TASK_START_PRIO,
(OS_STK *)&AppTaskStartStk[0],
APP_TASK_START_STK_SIZE,
(void *)0,
OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR );
//启动多任务
OSStart ();
}
主程序中先调用OSInit()初始化系统,然后调用OSTaskCreateExt()建立任务,最后调用OSStart()启动任务,这里的三个函数是uC/OS-II构造任务的主力。
4)OSInit():
OSInit()作为uC/OS-II的第一行代码,负责全局的变量、结构体、存储器、端口等的初始化工作。具体代码为:
void OSInit(void)
{
OSInitHookBegin();
OS_InitMisc(); //初始化OSIntNesting等全局变量
OS_InitRdyList(); //初始化就绪任务表及其相关变量
OS_InitTCBList(); //初始化空任务控制块链表
OS_InitEventList(); //初始化事件控制块链表
#if (OS_FLAG_EN>0)&&(OS_MAX_FLAGS> 0)
OS_FlagInit(); //初始化信号量集
#endif
#if (OS_MEM_EN>0)&&(OS_MAX_MEM_PART>0)
OS_MemInit(); //存储管理初始化
#endif
#if (OS_Q_EN>0)&&(OS_MAX_Q>0)
OS_QInit(); //初始化消息队列
#endif
OS_InitTaskIdle(); //空闲任务初始化
#if OS_TASK_STAT_EN>0
OS_InitTaskStat(); //统计任务初始化
#endif
#if OS_TMR_EN>0
OSTmr_Init(); //定时器初始化
#endif
OSInitHookEnd(); //呼叫口特殊初始化
#if OS_DEGUG_EN>0
OSDebugInit();
#endif
}
5)OSTaskCreateExt ():
STaskCreateExt()函数的原型如下:
INT8U OSTaskCreateExt (
void (*task)(void *p_arg), //指向任务的指针
void *p_arg, //传递给任务的参数
OS_STK *ptos, //指向任务堆栈栈顶的指针
INT8U prio, //任务的优先级
INT16U id, //任务的标识
OS_STK *pbos, //任务堆栈栈底的指针
INT32U stk_size, //任务堆栈的容量
void *pext, //指向附加数据域的指针
INT16U opt ); //用于设定操作选项
其中:task是建立任务的函数地址,该函数必须声明为如下形式:
static void TaskToCreate (void *p_arg)
p_arg是传递给任务的参数;ptos是任务堆栈栈顶地址;prio是任务优先级;id,是任务ID,在uC/OS-II中与优先级相同;pbos任务堆栈底地址;stk_size是任务堆栈字节大小;pext是传递给任务的附加数据;opt任务可选参数。将函数展开:
{
OS_STK *psp();
INT8U err;
#if OS_CRITICAL_METHOD= =3
OS_CPU_SR cpu_sr;
#endif
#if OS_ARG_CHK_EN> 0
if (prio>OS_LOWEST_PRIO){ //检查是否为有效优先级
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL(); //关中断,进入临界区
if (OSIntNesting>0){ //uC/OS-II不允许在中断例程中建立任务
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio]= =(OS_TCB *)0){
OSTCBPrioTbl[prio]=OS_TCB_RESERVED; //相同ID的任务不存在则允许创建
OS_EXIT_CRITICAL();
#if OS_TASK_STAT_STK_CHK_EN>0
OS_TaskStkClr(pbos, stk_size, opt); //清除堆栈,若开启该选项
#endif
psp=OS_TaskStkInit(task, p_arg, ptos, opt);
err=OS_TCBInit(prio, psp, pbos, id, stk_size, pext, opt);
if (err= =OS_ERR_NONE){
if (OSRunning= =OS_TRUE){
//任务调度
OS_Sched();
}
else
{
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio]=(OS_TCB *)0;
OS_EXIT_CRITICAL();
}
return (err);
}
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_PRIO_EXIST);
}
6)OSStart():
系统建立任务后,任务处于挂起状态,要让其立即运行,还需调用OSStart()。实际上新建立的任务能否运行还要看其优先级是否足够高。
void OSStart(void)
{
If(OSRunning= =FALSE)
{
OS_SchedNew(); //寻找最高优先级的就绪任务
OSPrioCur=OSPrioHighRdy;
OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy]; //指向最高优先级的就绪任务
OSTCBCur=OSTCBHighRdy;
OSStartHighRdy(); //运行最高优先级的就绪任务
}
}
OSStart()首先查找优先级最高的任务编号,查找过程是检查TCB控制表;找到该任务编号后,将其放入就绪任务列表中,调用函数OSTCBHighRdy()进行任务切换。
二、uC/OS-II程序的编译:
1.ARM Keil uVision环境下的编译:
1)新建模板工程test.mcp,将主文件main.c,移植代码os_cpu_c.c和os_cpu_a.s,启动代码init.s、vectors.s、target.c,操作系统代码os_core.c、os_flag.c、os_mbox.c、os_mem.c、os_mutex.c、os_q.c、os_sem.c、os_task.c、os_time.c添加到工程中。
其中,启动代码init.s、vectors.s、target.c放入Startup目录中;主文件main.c放入src目录中;移植代码os_cpu_c.c和os_cpu_a.s放入arm目录中;操作系统代码os_core.c、os_flag.c、os_mbox.c、os_mem.c、os_mutex.c、os_q.c、os_sem.c、os_task.c、os_time.c放入ucos-ii目录中。如果使用spi,将使用驱动文件spi.c,放入src目录中。
2)在ARM Linker设置界面,Output选项卡,Linktype保持Simple,RO Base保持0x0,修改RW Base为0x40000040。(菜单Edit/Release Settings)
3)在ARM Linker设置界面,Option选项卡,保持勾选Read-only、read-write和Zero-initial,勾选Include debugging informat、Search standard libr和Output local symb,Image entry point栏输入0x0。
4)菜单Project/Make,对工程进行编译,生成test.hex文件。
2.IAR环境下的编译:
1)新建工作空间ucosii.eww,新建项目test.ewp,添加主文件main.c,移植代码os_cpu_c.c、os_cpu_a.asm和os_dbg.c,BSP代码bsp.c和bsp_a.s,操作系统代码os_core.c、os_flag.c、os_mbox.c、os_mem.c、os_mutex.c、os_q.c、os_sem.c、os_task.c、os_time.c添加到工程中。
其中,主文件main.c放入APP目录中;移植代码os_cpu_c.c、os_cpu_a.asm和os_dbg.c放入arm目录中;BSP代码bsp.c和bsp_a.s放入BSP目录中;操作系统代码os_core.c、os_flag.c、os_mbox.c、os_mem.c、os_mutex.c、os_q.c、os_sem.c、os_task.c、os_time.c放入uCOS-II目录中。如果使用spi,将使用驱动文件spi.c,放入APP目录中。
2)工作空间选择test-Debug,鼠标右键菜单选Option,进行工程配置。
3)菜单Project/Make,对工程进行编译,生成test.hex文件。
4)将J-Link仿真器一端与pc机的USB口相连,另一端与开发板的JTAG口相连。菜单Project /Debug,将可执行代码装入开发板;菜单Debug/Go全速运行。