目录
1. 任务定义与切换原理
1.1 任务是什么
1.1.1 任务的外观
1.1.2 任务的内在
1.2 任务切换原理
1.2.1 任务切换的本质
1.2.2 要保存哪些任务运行状态
1.2.3 任务运行状态保存方案
1.3 设计实现
1.3.1 类型定义
1.3.2 任务定义与初始化
2. 任务切换的实现
2.1 设计目标
2.2 任务切换原理
2.2.1 如何启动初始任务
2.2.2 如何实现任务切换
2.3 设计实现
2.3.1 设置任务初始栈
2.3.2 启动初始任务
2.3.3 申请任务调度
2.3.4 任务切换
3. 双任务时间片运行原理
3.1 设计目标
3.2 时间片切换原理
3.2.1 时间片实现
3.2.2 SytTick定时器简介
3.3 设计实现
3.3.1 SysTick设置
3.3.2 SysTick_Handler
3.3.3 任务函数
4. 双任务延时原理与空闲任务
4.1 设计目标
4.2 任务延时原理
4.2.1 任务延时
4.2.2 软件计时器
4.2.3 延时精度问题
4.2.4 空闲任务
4.3 设计实现
4.3.1 增设软件计时器
4.3.2 tTaskDelay函数
4.3.3 空闲任务的实现
4.3.4 SysTick_Handler
4.3.5 任务调度函数
1. 任务定义与切换原理
1.1 任务是什么
1.1.1 任务的外观
任务的外观:一个永不返回的函数
![](https://img-blog.csdnimg.cn/20210407161907441.png)
说明:使用void *类型形参,确保可以传入任意类型的参数
1.1.2 任务的内在
任务的内在:一个函数的执行
![](https://img-blog.csdnimg.cn/2021040716191612.png)
① 代码段和数据区由编译器在编译代码时自动分配与控制
② 堆的分配和使用由程序员控制
③ C代码中一般不会显式使用栈,将由编译器完成;在汇编代码中,程序员可以设置栈的位置并使用
④ C代码也不会显示使用寄存器,也是由编译器完成
个人:代码区 + 数据区 + 栈 + 堆可以理解为任务的实体 + 运行环境
1.2 任务切换原理
1.2.1 任务切换的本质
任务切换的本质:保存前一任务(prev)的当前运行状态,恢复后一任务(next)之前的运行状态,并切换到该任务运行
![](https://img-blog.csdnimg.cn/20210407161935616.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
1.2.2 要保存哪些任务运行状态
![](https://img-blog.csdnimg.cn/20210407161941753.png)
① 代码区 & 数据区:由编译器自动分配,各个任务相互独立,并不冲突
② 堆:由程序员定义并使用,对堆操作的句柄保存在栈中
③ 栈:内核硬件只支持两个栈空间(MSP & PSP),而不同任务的栈不能共用,所以需要给每个任务设置自己的栈空间
④ 寄存器:编译器会在某些时刻将寄存器保存到栈中(e.g. 函数调用,异常处理),如果其他寄存器也会被破坏,则需要程序员自己保存
⑤ 其他需要保存的状态数据
1.2.3 任务运行状态保存方案
解决方案:为每个任务分配独立的栈,用于保存该任务的所有状态数据
![](https://img-blog.csdnimg.cn/20210407161948650.png)
在进行任务切换时,
① 将前一(prev)任务的当前状态保存在该任务的栈中
② 找到下一(next)任务的栈,然后从该栈中恢复任务状态
1.3 设计实现
1.3.1 类型定义
① 栈类型
Cortex-M中对栈操作的单位为4B,所以使用uint32_t类型
typedef uint32_t tTaskStack;
② 任务类型
typedef struct _tTask {
// 任务栈指针
tTaskStack *stack;
} tTask;
说明:每个任务的栈地址,均保存在任务结构体中,即TCB中
1.3.2 任务定义与初始化
tTask tTask1;
tTask tTask2;
// 任务栈
tTaskStack task1Env[1024];
tTaskStack task2Env[1024];
// 任务函数
void task1(void *param)
{
for (;;) {
}
}
void task2(void *param)
{
for (;;) {
}
}
// 任务初始化函数
// 当前仅初始化任务栈指针
void tTaskInit(tTask *task, void (*entry)(void *), void *param,
tTaskStack *stack)
{
task->stack = stack;
}
// 调用任务初始化函数
// 由于默认使用满减栈,所以调用时传递任务栈高地址,即栈顶
tTaskInit(&tTask1, task1, (void *)0x11111111, task1Env + 1024);
tTaskInit(&tTask2, task2, (void *)0x22222222, task2Env + 1024);
2. 任务切换的实现
2.1 设计目标
![](https://img-blog.csdnimg.cn/20210407162128350.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明:在本节实现中,任务切换由任务主动调用tTaskSched函数实现
2.2 任务切换原理
2.2.1 如何启动初始任务
① 上电启动后,系统进入特权级线程模式(线程模式 + MSP),而任务一般运行在用户级线程模式(线程模式 + PSP)
② 设置任务初始栈
![](https://img-blog.csdnimg.cn/20210407162136573.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
③ 触发任务切换,使用任务初始栈内容填充任务运行状态
![](https://img-blog.csdnimg.cn/20210407162142700.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明1:由于启动初始任务的步骤是先设置任务初始栈,然后触发任务调度,用初始栈的内容设置当前运行环境。所以任务调度函数中需要识别出这种场景
说明2:在设置任务初始栈时,会将任务函数入口地址(task entry)填入PC寄存器对应的位置,这样在执行任务切换后,CPU即可运行任务函数
2.2.2 如何实现任务切换
① 将当前任务运行状态保存到当前任务栈中
![](https://img-blog.csdnimg.cn/20210407162155811.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20210407162201880.png)
说明:此处的任务运行状态保存分为2部分,
a. 硬件自动保存部分(进入pendSV异常时硬件自动保存),硬件保存的数据也是保存在系统当前使用的栈中,也就是当前任务的栈中
b. 程序员自行保存部分
② 找到下一任务,并用下一任务的任务栈恢复该任务运行环境
![](https://img-blog.csdnimg.cn/20210407162210484.png)
![](https://img-blog.csdnimg.cn/20210407162215682.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明:与任务状态的保存类似,任务状态的恢复也分为,
a. 硬件自动恢复部分
b. 程序员自行恢复部分
在任务切换函数中,只需要完成程序员自行恢复部分即可
2.3 设计实现
2.3.1 设置任务初始栈
void tTaskInit(tTask *task, void (*entry)(void *), void *param,
tTaskStack *stack)
{
// 硬件自动保存状态
// 在设置初始栈状态时,必须与硬件操作顺序一致
*(--stack) = (unsigned int)(1 << 24); // xPSR,进入thumb状态
*(--stack) = (unsigned int)entry; // PC,任务函数入口
*(--stack) = (unsigned int)0x14; // R14(LR),任务不会通过return结束
*(--stack) = (unsigned int)0x12; // R12
*(--stack) = (unsigned int)0x3; // R3
*(--stack) = (unsigned int)0x2; // R2
*(--stack) = (unsigned int)0x1; // R1
*(--stack) = (unsigned int)param; // R0 = param,根据ATPCS规则传参
// 程序员自行保存状态
*(--stack) = (unsigned int)0x11; // R11
*(--stack) = (unsigned int)0x10; // R10
*(--stack) = (unsigned int)0x9; // R9
*(--stack) = (unsigned int)0x8; // R8
*(--stack) = (unsigned int)0x7; // R7
*(--stack) = (unsigned int)0x6; // R6
*(--stack) = (unsigned int)0x5; // R5
*(--stack) = (unsigned int)0x4; // R4
// 任务栈指针
// 切换任务时的R13(SP)从该字段获取
task->stack = stack;
}
说明1:任务初始化栈,是任务首次运行时的环境。由于是任务首次运行,软硬件均为设置过该任务的栈,所以此时同时初始化了由硬件保存的部分和由程序员保存的部分
说明2:任务初始化栈设置注意事项
![](https://img-blog.csdnimg.cn/20210407183336800.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20210407183343987.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
① 硬件自动保存状态部分必须与硬件操作顺序一致
② 程序员自行保存状态部分要遵循高编号寄存器对应高地址的规则(STRM & LDRM指令要求的规则)
说明:其实硬件自动保存状态部分也遵循了该规则
2.3.2 启动初始任务
void tTaskRunFirst(void)
{
// 将PSP寄存器设置为0,标识系统中第1个任务运行
__set_PSP(0);
// 设置pendSV为最低优先级
MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI;
// 设置挂起pendSV位
MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;
}
说明:触发pendSV异常后,系统进入pendSV ISR运行,由于此处将PSP寄存器置为0,在进行任务切换时据此可识别出此时为启动初始任务,无需保存前一任务的运行状态(因为此时还木有这个"前一任务")
2.3.3 申请任务调度
void tTaskSched(void)
{
if (currentTask == taskTable[0]) {
nextTask = taskTable[1];
} else {
nextTask = taskTable[0];
}
// 设置挂起pendSV位
MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;
}
说明:此处调度实现非常简单,就是在2个任务之间相互切换
2.3.4 任务切换
__asm void PendSV_Handler(void)
{
// 在汇编中引入C变量
IMPORT currentTask;
IMPORT nextTask;
// 根据PSP判断是否是启动初始任务
// 如果是启动初始任务,则无需保存前一任务状态
// 关于MRS R0, PSP说明2点:
// 1. 使用MRS获取PSP是因为在Handler模式下只能使用MSP
// 2. 可以覆盖R0是因为进入ISR时硬件自动保存了R0 ~ R3
MRS R0, PSP
CBZ R0, PendSVHandler_nosave
// 保存前一任务状态(即当前任务)
// 此处仅保存程序员自主保存部分,硬件自动保存部分此时已经入栈(PSP)
STMDB R0!, {R4-R11}
// 更新前一任务栈指针
LDR R1, =currentTask
LDR R1, [R1]
STR R0, [R1]
PendSVHandler_nosave
// 将nextTask设置为currentTask
LDR R0, =currentTask
LDR R1, =nextTask
LDR R2, [R1]
STR R2, [R0]
// 恢复后一任务状态
// 此处仅恢复程序员自主恢复部分,硬件自动恢复部分将由BX LR指令启动
LDR R0, [R2]
LDMIA R0!, {R4-R11}
// 设置PSP为后一任务栈指针
MSR PSP, R0
// 异常返回用户级线程模式
ORR LR, LR, #0x04
BX LR
}
说明1:任务切换流程图如下,
![](https://img-blog.csdnimg.cn/20210407183551469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明2:修改LR的目的
在进入异常处理流程后,LR会被修改为EXC_RETURN,在硬件自动处理的过程中,异常结束后会返回触发异常前的状态(e.g. 从特权级线程模式进入异常后,将返回特权级线程模式;从用户级线程模式进入异常后,将返回用户级线程模式)
在启动初始任务时,系统处于特权级线程模式,但是结束ISR进入任务时,我们希望系统处于用户级线程模式,所以在此处修改了LR,将bit 2置为1,即进入用户级
说明3:切换任务栈是通过用任务栈的地址设置PSP寄存器实现的,即
MSR PSP, R0
说明4:启动初始任务后,首次进入PendSV_Handler时,硬件保存的寄存器值是保存在MSP指向的栈中,因为Cortex-M3启动后默认进入thread + MSP模式
3. 双任务时间片运行原理
3.1 设计目标
![](https://img-blog.csdnimg.cn/20210407183603245.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明:在上节实现中,任务切换由任务主动调用tTaskSched函数实现;在本节实现中,将在SysTick ISR中调用tTaskSched函数进行任务切换,进而实现基于时间片的任务调度
3.2 时间片切换原理
3.2.1 时间片实现
![](https://img-blog.csdnimg.cn/20210407183609895.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
核心:利用SysTick的周期性中断,在该中断的ISR中选择下一任务,并触发pendSV异常进行任务切换
3.2.2 SytTick定时器简介
SysTick定时器是Cortex-M3内核内置的24位递减定时器,当递减到0时,将从RELEAD寄存器中自动重载定时初值到CURRENT寄存器,如此反复
![](https://img-blog.csdnimg.cn/20210407183618243.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明1:SysTick控制与状态寄存器
![](https://img-blog.csdnimg.cn/20210407183624359.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
① bit [1:0]置位后,SysTick定时器将开始工作,并在计数值为0时触发SysTick异常,因此需要注意使能SysTick的时机,基于时间片的调度即以此处触发的SysTick异常为依据
② 示例中使用内核时钟作为SysTick计数所用时钟,同时需要修改工程中的system_ARMCM3.c,设置__XTAL & __SYSTEM_CLOCK的值
![](https://img-blog.csdnimg.cn/2021040718363586.png)
此处设置晶振频率为12MHz,且系统时钟与晶振频率相同
说明2:SysTick重装值寄存器
① RELOAD初始值可以是1到0x00FFFFFF之间的任何值
② 作为一个连拍式(multi-shot)定时器,SysTick每N + 1个时钟脉冲就触发一次SysTick异常(e.g. 如果希望每100个时钟脉冲触发一次异常,则需要将RELOAD值设置为99)
③ 如果每次SysTick异常后都写入一个新值,那么可以作为单拍式(single-shot)定时器,此时必须写入时机的倒计数值(e.g. 如果希望在100个时钟脉冲后触发一次异常,则需要将RELOAD值设置为100)
3.3 设计实现
3.3.1 SysTick设置
void tSetSysTickPeriod(uint32_t ms)
{
SysTick->LOAD = ms * SystemCoreClock / 1000 - 1; // RELOAD值
NVIC_SetPriority(SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
说明1:将SysTick优先级设置为最低
此处__NVIC_PRIO_BITS宏为使用多少个bit位标识中断优先级,因此 (1 << __NVIC_PRIO_BITS) - 1就是设置最低优先级(有效位均为1)
说明2:tSetSysTickPeriod函数调用时机
一旦调用tSetSysTickPeriod函数SysTick计时器即开始工作,因此我们在启动的初始任务中调用该函数,以避免初始任务尚未启动即进入SysTick_Handler,从而导致调度错误
个人:另一种处理方式则是先关闭SysTick的异常中断使能位,这样可以在初始化阶段就设置系统时钟,直到准备就绪时再使能该中断
3.3.2 SysTick_Handler
void SysTick_Handler(void)
{
tTaskSched();
}
说明:使用SysTick_Handler这个函数名也是因为在Keil初始化环境中将SysTick的异常向量设置为SysTick_Handler
![](https://img-blog.csdnimg.cn/20210407183713658.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
3.3.3 任务函数
int task1Flag = 0;
void task1Entry(void *param)
{
// 启动SysTick计时器,周期为10ms
tSetSysTickPeriod(10);
for (;;) {
task1Flag = 1;
delay(100);
task1Flag = 0;
delay(100);
}
}
int task2Flag = 0;
void task2Entry(void *param)
{
for (;;) {
task2Flag = 1;
delay(100);
task2Flag = 0;
delay(100);
}
}
说明:由于在SysTick_Handler中进行任务切换,所以在任务函数中无需再调用tTaskSched函数
4. 双任务延时原理与空闲任务
4.1 设计目标
![](https://img-blog.csdnimg.cn/20210407183739629.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明:delay函数的延时通过在循环中递减计数值实现,属于忙等操作,在延时过程中持续占用CPU,因此降低了CPU使用率
本节设计的任务延时接口的要点就是在延时过程中释放CPU
![](https://img-blog.csdnimg.cn/20210407183747677.png)
4.2 任务延时原理
4.2.1 任务延时
目标:在任务延时过程中,暂停当前任务运行,释放CPU控制权给其他任务
步骤:
① 开始延时,启动计时器,暂停当前任务
![](https://img-blog.csdnimg.cn/20210407183755651.png)
② 延时结束,关闭计时器,恢复任务运行
![](https://img-blog.csdnimg.cn/20210407183802141.png)
说明:
① 启动任务计时器时,通过调用任务切换函数释放CPU
② 结束任务计时器时,也会调用任务切换函数,恢复之前暂停的任务(当然,此处还涉及多任务优先级的调度问题)
4.2.2 软件计时器
![](https://img-blog.csdnimg.cn/20210407183809741.png)
说明1:由于任务数量不限,而硬件计时器资源有限,所以任务计时器一般使用软件计时器实现
说明2:软件计时器是基于硬件计时器工作的,从本质上说,软件计时器是一个计数值,每当硬件SysTick触发时,在SysTick ISR中维护软件计时器计数值
说明3:由于软件计时器基于硬件计时器工作,所以软件定时时间必须是硬件定时器周期的倍数
相当于硬件计时器提供了最小的计时分辨率,如果需要更高精度的延时则只能使用硬件定时器
4.2.3 延时精度问题
![](https://img-blog.csdnimg.cn/20210407183817490.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5jaGVuZ3d1ZGk=,size_16,color_FFFFFF,t_70)
说明:由于使用软件计时器,加之延时处理需要时间,所以延时精度有限,使用时需要注意场景。在上图中,
① 开始延时的位置(在一个时间片的中间开始延时)
② 中断抢占(更高优先级中断抢占SysTick中断)
③ 任务优先级调度(多个任务延时完成,调度优先级最高的任务运行)
均会影响软件计时精度(准确说是任务实际睡眠的时间)
4.2.4 空闲任务
4.2.4.1 引入背景
如果所有任务都进入延时(或者当前没有任务可运行),那么RTOS应该运行什么 ?(毕竟CPU不能闲着呀~)
4.2.4.2 引入空闲任务的好处
① 简化RTOS代码结构
引入空闲任务后,CPU总有任务可以运行
② 可在空闲任务中添加自定义功能
e.g. 在空闲任务中添加低功耗运行指令或者进行CPU统计任务
4.3 设计实现
4.3.1 增设软件计时器
typedef struct _tTask {
// 任务栈指针
tTaskStack *stack;
// 任务延时计数器(SysTick个数)
uint32_t delayTick;
} tTask;
说明:在任务初始化函数tTaskInit函数中需要对应地将delayTick字段初始化为0
4.3.2 tTaskDelay函数
void tTaskDelay(uint32_t delay)
{
// 设置当前任务延时计数器值
currentTask->delayTick = delay;
// 触发任务切换
tTaskSched();
}
说明1:tTaskDelay函数的参数为延时时间对应的SysTick个数
说明2:任务调用tTaskDelay函数设置延时后,需要释放CPU控制权,该动作通过调用tTaskSched函数实现
4.3.3 空闲任务的实现
tTask tTaskIdle;
tTaskStack idleTaskEnv[1024];
tTask *idleTask;
void idleTaskEntry(void *param)
{
for (;;) {
// 空闲任务
}
}
// 在main函数中创建空闲任务
tTaskInit(&tTaskIdle, idleTaskEntry, (void *)0, idleTaskEnv + 1024);
idleTask = &tTaskIdle;
4.3.4 SysTick_Handler
void SysTick_Handler(void)
{
int i = 0;
for (i = 0; i < 2; ++i) {
if (taskTable[i]->delayTick > 0)
--taskTable[i]->delayTick;
}
tTaskSched();
}
说明:在SysTick ISR中,首先遍历所有任务的延时计数值,如果非零则递减,说明完成一个SysTick的延时;然后调用tTaskSched函数触发任务切换,这是原先基于时间片调度就需要完成的操作
4.3.5 任务调度函数
void tTaskSched(void)
{
// 当前为空闲任务
if (currentTask == idleTask) {
// 如果有延时结束的任务则切换到该任务
// 否则继续执行空闲任务
if (taskTable[0]->delayTick == 0) {
nextTask = taskTable[0];
} else if (taskTable[1]->delayTick ==0) {
nextTask = taskTable[1];
} else {
return; // 不切换
}
}
// 当前不是空闲任务
else {
// 如果其他任务延时结束则切换到该任务
// 否则判断当前任务是否需要进入延时状态,如果需要则切换到空闲任务
// 否则不进行切换
if (currentTask == taskTable[0]) {
if (taskTable[1]->delayTick == 0) {
nextTask = taskTable[1];
} else if (currentTask->delayTick != 0) {
nextTask = idleTask;
} else {
return; // 不切换
}
} else {
if (taskTable[0]->delayTick == 0) {
nextTask = taskTable[0];
} else if (currentTask->delayTick != 0) {
nextTask = idleTask;
} else {
return; // 不切换
}
}
}
// 设置挂起pendSV位
MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)