1.任务的定义
1)定义任务堆栈
1 #define TASK1_STK_SIZE 128 2 #define TASK2_STK_SIZE 128 3 4 static CPU_STK Task1Stk[TASK1_STK_SIZE]; 5 static CPU_STK Task2Stk[TASK2_STK_SIZE];
2)定义任务函数
3)定义任务控制块
任务控制块是一种数据类型,包含着任务的所有信息(任务堆栈,名字,优先级,链表指针等),任务的执行是通过系统的调度,系统对任务的操作则是通过任务控制块来实现
4)定义任务函数
2.系统初始化
系统初始化一般是在硬件初始化完成之后进行
3.启动系统
1 void OSStart (OS_ERR *p_err) 2 { 3 if ( OSRunning == OS_STATE_OS_STOPPED ) { 4 /* 手动配置任务 1 先运行 */ 5 OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; 6 7 /* 启动任务切换*/ 8 OSStartHighRdy(); 9 10 /* 不会运行到这里,运行到这里表示发生了致命的错误 */ 11 *p_err = OS_ERR_FATAL_RETURN; 12 } else { 13 *p_err = OS_STATE_OS_RUNNING; 14 } 15 } /*********************** OSStartHighRdy() ***************************/ OSStartHighRdy LDR R0, = NVIC_SYSPRI14 ; 设置 PendSV 异常优先级为最低(在 uC/OS-III 中,上下文切换是在 PendSV 异常服务程序中执行的,配置 PendSV的优先级为最低,防止在中断服务程序中执行上下文切换) LDR R1, = NVIC_PENDSV_PRI STRB R1, [R0] MOVS R0, #0 ;设置 psp 的值为 0,开始第一次上下文切换 MSR PSP, R0 LDR R0, =NVIC_INT_CTRL ; 触发 PendSV 异常 LDR R1, =NVIC_PENDSVSET STR R1, [R0] 18 19 CPSIE I ; 使能总中断,NMI 和 HardFault 除外 20 21 OSStartHang 22 B OSStartHang ; 程序应永远不会运行到这里 /************************************************************************/
4.任务切换
当调用 OSStartHighRdy()函数时会触发 PendSV 异常,然后在PendSV 异常服务函数里面进行任务切换。
PendSV 异常服务函数名称必须与启动文件里面向量表中 PendSV 的向量名一致,如果不一致则内核是响应
不了用户编写的 PendSV 异常服务函数的,只响应启动文件里面默认的 PendSV 异常服务函数。启动文件里
面为每个异常都编写好默认的异常服务函数,函数体都是一个死循环,当发现代码跳转到这些启动文件里面默
认的异常服务函数的时候,就要检查下异常函数名称是否写错了,是否跟向量表里面的一致。
/************************ PendSV_Handler() ****************************/ PendSV_Handler PendSV_Handler ; 关中断,NMI 和 HardFault 除外,防止上下文切换被中断 CPSID I (1) ; 将 psp 的值加载到 R0 MRS R0, PSP (2) ; 判断 R0,如果值为 0 则跳转到 OS_CPU_PendSVHandler_nosave ; 进行第一次任务切换的时候,R0 肯定为 0 CBZ R0, OS_CPU_PendSVHandler_nosave (3) ;-----------------------一、保存上文----------------------------- ; 任务的切换,即把下一个要运行的任务的堆栈内容加载到 CPU 寄存器中 ;-------------------------------------------------------------- ; 在进入 PendSV 异常的时候,当前 CPU 的 xPSR,PC(任务入口地址), ; R14,R12,R3,R2,R1,R0 会自动存储到当前任务堆栈, ; 同时递减 PSP 的值,随便通过 代码:MRS R0, PSP 把 PSP 的值传给 R0 ; 手动存储 CPU 寄存器 R4-R11 的值到当前任务的堆栈 STMDB R0!, {R4-R11} ; 加载 OSTCBCurPtr 指针的地址到 R1,这里 LDR 属于伪指令 LDR R1, = OSTCBCurPtr (16) ; 加载 OSTCBCurPtr 指针到 R1,这里 LDR 属于 ARM 指令 LDR R1, [R1] (17) ; 存储 R0 的值到 OSTCBCurPtr->OSTCBStkPtr,这个时候 R0 存的是任务空闲栈的栈顶 STR R0, [R1] (18) ;-----------------------二、切换下文----------------------------- ; 实现 OSTCBCurPtr = OSTCBHighRdyPtr ; 把下一个要运行的任务的堆栈内容加载到 CPU 寄存器中 ;-------------------------------------------------------------- OS_CPU_PendSVHandler_nosave (4) ; 加载 OSTCBCurPtr 指针的地址到 R0,这里 LDR 属于伪指令 LDR R0, = OSTCBCurPtr (5) ; 加载 OSTCBHighRdyPtr 指针的地址到 R1,这里 LDR 属于伪指令 LDR R1, = OSTCBHighRdyPtr (6) ; 加载 OSTCBHighRdyPtr 指针到 R2,这里 LDR 属于 ARM 指令 LDR R2, [R1] (7) ; 存储 OSTCBHighRdyPtr 到 OSTCBCurPtr STR R2, [R0] (8) ; 加载 OSTCBHighRdyPtr 到 R0 LDR R0, [R2] (9) ; 加载需要手动保存的信息到 CPU 寄存器 R4-R11 LDMIA R0!, {R4-R11} (10) ; 更新 PSP 的值,这个时候 PSP 指向下一个要执行的任务的堆栈的栈底 ;(这个栈底已经加上刚刚手动加载到 CPU 寄存器 R4-R11 的偏移) MSR PSP, R0 (11) ; 确保异常返回使用的堆栈指针是 PSP,即 LR 寄存器的位 2 要为 1 ORR LR, LR, #0x04 (12) ; 开中断 CPSIE I (13) ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到 xPSR, ; PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参) ; 同时 PSP 的值也将更新,即指向任务堆栈的栈顶。 ; 在 STM32 中,堆栈是由高地址向低地址生长的。 BX LR /*************************************************************************/
5.程序示例
/* ******************************************************************* * 包含的头文件 ******************************************************************* */ #include "os.h" #include "stm32f4xx_hal.h" /* ******************************************************************* * 宏定义 ******************************************************************* */ /* ******************************************************************* * 全局变量 ******************************************************************* */ /* ******************************************************************* * TCB & STACK & 任务声明 ******************************************************************* */ #define TASK1_STK_SIZE 20 #define TASK2_STK_SIZE 20 static CPU_STK Task1Stk[TASK1_STK_SIZE]; static CPU_STK Task2Stk[TASK2_STK_SIZE]; static OS_TCB Task1TCB; static OS_TCB Task2TCB; void Task1( void *p_arg ); void Task2( void *p_arg ); /* ******************************************************************* * 函数声明 ******************************************************************* */ /* ******************************************************************* * main 函数 ******************************************************************* int main(void) { OS_ERR err; /* 初始化相关的全局变量 */ OSInit(&err); /* 创建任务 */ OSTaskCreate ((OS_TCB*) &Task1TCB, (OS_TASK_PTR ) Task1, (void *) 0, (CPU_STK*) &Task1Stk[0], (CPU_STK_SIZE) TASK1_STK_SIZE, (OS_ERR *) &err); OSTaskCreate ((OS_TCB*) &Task2TCB, (OS_TASK_PTR ) Task2, (void *) 0, (CPU_STK*) &Task2Stk[0], (CPU_STK_SIZE) TASK2_STK_SIZE, (OS_ERR *) &err); /* 将任务加入到就绪列表 */ OSRdyList[0].HeadPtr = &Task1TCB; OSRdyList[1].HeadPtr = &Task2TCB; /* 启动 OS,将不再返回 */ OSStart(&err); } /* ******************************************************************* * 函数实现 ******************************************************************* */ /* 任务 1 */ void Task1( void *p_arg ) { while(1) { /******* 功能实现 *********/ ..... /***************************/ /* 手动切换任务 */ OSSched(); } } /* 任务 2 */ void Task2( void *p_arg ) { while(1) { /******* 功能实现 *********/ ..... /***************************/ /* 手动切换任务 */ OSSched(); } } /************* OSSched() *****************/ /* 任务切换,实际就是触发 PendSV 异常,然后在 PendSV 异常中进行上下文切换 */ void OSSched (void) { if ( OSTCBCurPtr == OSRdyList[0].HeadPtr ) { OSTCBHighRdyPtr = OSRdyList[1].HeadPtr; } else { OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; } OS_TASK_SW(); //触发 PendSV异常 } /*******************************************/
来源:https://www.cnblogs.com/taury/p/12186695.html