参考教程:【正点原子】手把手教你学UCOS-III实时操作系统_哔哩哔哩_bilibili
一、任务创建和删除的API函数
1、概述
(1)任务的创建和删除本质就是调用µC-/OS-III的API函数。
API函数 | 描述 |
OSTaskCreate() | 创建任务 |
OSTaskDel() | 删除任务 |
(2)在调用任何关µC-/OS-III函数之前必须先初始化µC-/OS-III,仅初始化一次即可,通过调用函数OSInit实现。
(3)任务在创建之后是不会直接运行的,需开启任务调度器,任务才能得以运行,通过调用函数OSStart实现。
2、任务控制块
(1)任务控制块结构体成员介绍:
struct os_tcb
{
CPU_STK *StkPtr; /* 任务栈栈顶,必须为TCB的第一个成员 */
CPU_STK *StkLimitPtr; /* 指向任务栈警戒线指针 */
OS_TCB *NextPtr; /* 指向任务列表中下一个任务控制块指针 */
OS_TCB *PrevPtr; /* 指向任务列表中上一个任务控制块指针 */
OS_TCB *TickNextPtr; /* 指向 Tick任务列表中下一个任务控制块指针 */
OS_TCB *TickPrevPtr; /* 指向 Tick任务列表中上一个任务控制块指针 */
OS_PRIO Prio; /* 任务优先级,数值越小,优先级越大 */
OS_TICK TickRemain; /* 任务延时的剩余时钟节拍数 */
OS_TICK TimeQuanta; /* 任务时间片 */
OS_TICK TimeQuantaCtr; /* 任务剩余时间片 */
//…
//省略很多条件编译的成员
};
(2)每个任务都有属于自己的任务控制块,类似每个人都有属于自己的身份证。
(3)任务栈栈顶在任务切换时与任务的上下文保存、任务恢复息息相关。
3、任务创建函数OSTaskCreate
(1)函数定义:
void OSTaskCreate
(
OS_TCB* p_tcb, /* 指向任务控制块的指针 */
CPU_CHAR* p_name, /* 指向任务的名字的指针 */
OS_TASK_PTR p_task, /* 指向任务函数的指针 */
void* p_arg, /* 传递给任务函数的参数 */
OS_PRIO prio, /* 任务的任务优先级,数字越小,优先级越高 */
CPU_STK* p_stk_base, /* 指向任务栈的起始地址的指针 */
CPU_STK_SIZE stk_limit, /* 任务栈的使用警戒线 */
CPU_STK_SIZE stk_size, /* 任务栈大小 */
OS_MSG_QTY q_size, /* 任务内嵌消息队列的大小(暂不介绍) */
OS_TICK time_quanta, /* 任务的时间片(暂不介绍) */
void* p_ext, /* 指向用户扩展内存的指针 */
OS_OPT opt, /* 任务选项,共有5个 */
OS_ERR* p_err /* 指向接收错误代码变量的指针 */
);
(2)当任务使用的栈空间超过警戒线后,µC-/OS-III可执行一些操作,具体执行什么操作由用户自行定义(也可不做任何处理,毕竟还没有栈溢出)。
(3)使用OSTaskCreate创建任务,任务的任务控制块以及任务栈空间所需的内存需要由用户手动分配并提供;当任务被创建好后,就会立马处于就绪态。
(4)任务选项(可多选,用“|”合并多个选项):
Opt(任务选项) | 描述 |
OS_OPT_TASK_NONE | 没有选项 |
OS_OPT_TASK_STK_CHK | 是否允许对任务进行堆栈检查 |
OS_OPT_TASK_STK_CLR | 是否需要清除任务堆栈 |
OS_OPT_TASK_SAVE_FP | 是否保存浮点寄存器 |
OS_OPT_TASK_NO_TLS | 不需要对正在创建的任务提供 TLS(线程本地存储)支持 |
(5)错误代码:
p_err(错误代码) | 描述 |
OS_ERR_NONE | 任务创建成功 |
OS_ERR_ILLEGAL_CREATE_RUN_TIME | 定义了 OS_SAFETY_CRITICAL_IEC61508,且 |
OS_ERR_PRIO_INVALID | 非法的任务优先级数值 |
OS_ERR_STAT_STK_SIZE_INVALID | 任务栈在初始化期间溢出 |
OS_ERR_STK_INVALID | 指向任务栈起始地址的指针为空 |
OS_ERR_STK_SIZE_INVALID | 任务栈小于配置项 OS_CFG_STK_SIZE_MIN |
OS_ERR_STK_LIMIT_INVALID | 任务栈警戒线大小,大于或等于任务栈大小 |
OS_ERR_TASK_CREATE_ISR | 在中断中非法地创建任务 |
OS_ERR_TASK_INVALID | 指向任务函数的指针为空 |
OS_ERR_TCB_INVALID | 指向任务控制块的指针为空 |
4、任务删除函数OSTaskDel
(1)函数定义:
void OSTaskDel
(
OS_TCB* p_tcb, /* 指向任务控制块的指针 */
OS_ERR* p_err /* 接收错误代码变量的指针 */
)
(2)错误代码:
p_err(错误代码) | 描述 |
OS_ERR_NONE | 任务删除成功 |
OS_ERR_ILLEGAL_DEL_RUN_TIME | 定义了 OS_SAFETY_CRITICAL_IEC61508,且在 OSStart()之后非法地删除内核对象 |
OS_ERR_OS_NOT_RUNING | µC/OS-III 内核还未运行 |
OS_ERR_STATE_INVALID | 任务处于无效状态 |
OS_ERR_TASK_DEL_IDLE | 非法删除空闲任务 |
OS_ERR_TASK_DEL_INVALID | 非法删除 µC/OS-III 的中断服务任务 |
OS_ERR_TASK_DEL_ISR | 在中断中非法地删除任务 |
(3)如果p_tcb代入的参数为NULL(或者0),则代表删除任务自身,也即当前正在运行的任务,或者说调用该函数的任务(需要说明的是,不可删除空闲任务)。
(4)当不再需要某一任务时,可以使用OSTaskDel函数来删除任务,不过删除任务并不会删除任务的代码或释放任务栈,仅代表该任务的代码和任务栈都不再由µC/OS-III内核管理。
二、任务创建和删除实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计4个任务——start_task、task1、task2、task3:
[1]start_task:用于初始化CPU库、配置Systick中断及优先级,并创建其它三个任务,然后删除自身。
[2]task1:实现LED1状态反转。
[3]task2:实现LED2状态反转。
[4]task3:按下按键1,删除task1、task2;按下按键2,创建task1、task2。
②预期实验现象:
[1]程序下载到板子上后,两个LED灯闪烁。
[2]按下按键1后,LED灯停止闪烁,接着按下按键2,LED灯恢复闪烁。
2、实验步骤
(1)将上一章中移植了µCOS-III的工程文件夹复制一份,在拷贝版中进行实验。
(2)在User分组下添加源文件UCOS_experiment.c和头文件UCOS_experiment.h,并在main.c文件中包含UCOS_experiment.h。
(3)在UCOS_experiment.c文件中添加如下内容。
①包含头文件、宏定义、任务函数声明及任务句柄:
#include "stm32f10x.h" // Device header
#include "os.h"
#include "cpu.h"
#include "LED.h"
#include "Key.h"
#include <stdio.h>
/* START_TASK 任务配置:任务优先级 任务栈大小 任务控制块 任务栈 任务函数 */
#define START_TASK_PRIO 5
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
OS_TCB start_task_tcb;
void start_task(void);
/* TASK1 任务配置:任务优先级 任务栈大小 任务控制块 任务栈 任务函数 */
#define TASK1_PRIO 4
#define TASK1_STACK_SIZE 256
CPU_STK task1_stack[TASK1_STACK_SIZE];
OS_TCB task1_tcb;
void task1(void);
/* TASK2 任务配置:任务优先级 任务栈大小 任务控制块 任务栈 任务函数 */
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 256
CPU_STK task2_stack[TASK2_STACK_SIZE];
OS_TCB task2_tcb;
void task2(void);
/* TASK3 任务配置:包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数 */
#define TASK3_PRIO 2
#define TASK3_STACK_SIZE 256
CPU_STK task3_stack[TASK3_STACK_SIZE];
OS_TCB task3_tcb;
void task3(void);
②供主函数调用的实验函数:
void UCOS_Test(void)
{
OS_ERR err;
OSInit(&err); //调用μC/OS-III的函数前需初始化μC/OS-III
//创建Start Task
OSTaskCreate (&start_task_tcb,
"start_task",
(OS_TASK_PTR)start_task,
NULL,
START_TASK_PRIO,
start_task_stack,
START_TASK_STACK_SIZE / 10,
START_TASK_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
OSStart(&err); //开始任务调度(开启任务调度器)
}
③start_task任务函数:
void start_task(void)
{
OS_ERR err; //接收错误代码使用,对错误代码进行分情况处理可增强程序鲁棒性
CPU_Init(); //初始化CPU库
CPU_SR_ALLOC(); //关中断之前需调用此函数
//滴答定时器重装载值 = 系统主频 / 滴答定时器中断频率(SysTick是递减计数)
CPU_INT32U cnts = SystemCoreClock / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); //配置Systick中断及优先级
CPU_CRITICAL_ENTER(); //进入临界区(关中断)
//创建task1
OSTaskCreate (&task1_tcb,
"task1",
(OS_TASK_PTR)task1,
0,
TASK1_PRIO,
task1_stack,
TASK1_STACK_SIZE / 10,
TASK1_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
//创建task2
OSTaskCreate (&task2_tcb,
"task2",
(OS_TASK_PTR)task2,
0,
TASK2_PRIO,
task2_stack,
TASK2_STACK_SIZE / 10,
TASK2_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
//创建task3
OSTaskCreate (&task3_tcb,
"task3",
(OS_TASK_PTR)task3,
0,
TASK3_PRIO,
task3_stack,
TASK3_STACK_SIZE / 10,
TASK3_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
CPU_CRITICAL_EXIT(); //退出临界区(开中断)
OSTaskDel(NULL, &err); //删除任务自身
}
④task1任务函数:
void task1(void)
{
OS_ERR err;
while(1) //task1永远不停歇(除非被其它任务删除)
{
LED1_Turn(); //LED1状态翻转
OSTimeDly(500, OS_OPT_TIME_DLY, &err); //自我阻塞500ms
}
}
⑤task2任务函数:
void task2(void)
{
OS_ERR err;
while(1) //task2永远不停歇(除非被其它任务删除)
{
LED2_Turn(); //LED2状态翻转
OSTimeDly(500, OS_OPT_TIME_DLY, &err); //自我阻塞500ms
}
}
⑥task3任务函数:
void task3(void)
{
OS_ERR err;
uint8_t key = 0;
while(1)
{
key = Key_GetNum(); //读取按键键值
if(key == 1)
{
OSTaskDel(&task1_tcb, &err); //删除task1
OSTaskDel(&task2_tcb, &err); //删除task2
}
if(key == 2)
{
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER(); //进入临界区
//创建task1
OSTaskCreate (&task1_tcb,
"task1",
(OS_TASK_PTR)task1,
0,
TASK1_PRIO,
task1_stack,
TASK1_STACK_SIZE / 10,
TASK1_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
//创建task2
OSTaskCreate (&task2_tcb,
"task2",
(OS_TASK_PTR)task2,
0,
TASK2_PRIO,
task2_stack,
TASK2_STACK_SIZE / 10,
TASK2_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
CPU_CRITICAL_EXIT(); //退出临界区
}
OSTimeDly(10, OS_OPT_TIME_DLY, &err); //自我阻塞10ms
}
}
(4)核对Key.c文件和LED.c文件,其中出现的延时函数都需要更换为OSTimeDly函数。
①Key.c文件:
#include "stm32f10x.h" // Device header
#include "os.h"
#include "cpu.h"
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
OS_ERR err;
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
OSTimeDly(20, OS_OPT_TIME_DLY, &err); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
OSTimeDly(20, OS_OPT_TIME_DLY, &err); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
OSTimeDly(20, OS_OPT_TIME_DLY, &err); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
OSTimeDly(20, OS_OPT_TIME_DLY, &err); //延时消抖
KeyNum = 2; //置键码为2
}
return KeyNum; //返回键码值,如没有按键按下,则键码为默认值0
}
②LED.c文件:
#include "stm32f10x.h" // Device header
/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平
}
/**
* 函 数:LED1开启
* 参 数:无
* 返 回 值:无
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平
}
/**
* 函 数:LED1关闭
* 参 数:无
* 返 回 值:无
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平
}
/**
* 函 数:LED1状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}
/**
* 函 数:LED2开启
* 参 数:无
* 返 回 值:无
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平
}
/**
* 函 数:LED2关闭
* 参 数:无
* 返 回 值:无
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平
}
/**
* 函 数:LED2状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为低电平
}
}
(5)在UCOS_experiment.h文件中添加如下代码。
#ifndef __UCOS_EXPERIMENT_H
#define __UCOS_EXPERIMENT_H
void UCOS_Test(void);
#endif
(6)在main.c文件中添加如下代码。
#include "stm32f10x.h" // Device header
#include "LED.h"
#include "Key.h"
#include "UCOS_experiment.h"
int main(void)
{
/*模块初始化*/
LED_Init(); //LED初始化
Key_Init(); //按键初始化
UCOS_Test();
while (1)
{
}
}
(7)程序完善好后点击“编译”,然后将程序下载到开发板上。
3、程序执行流程
(1)main函数全流程:
①初始化LED模块、按键模块。
②调用UCOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。
②三个任务中task3的优先级最高,故优先执行task3,假设先不按下任何按键,task3未接收到任何键码,不做出其它行为,直接自我阻塞10ms,此时task2的优先级最高(除开已阻塞的task3),故执行task2,LED2的状态翻转后,task2自我阻塞500ms,此时未阻塞的就绪任务仅剩task1,故执行task1,LED1的状态翻转后,task1自我阻塞500ms。待task3的自我阻塞时间结束,task3重新抢占CPU,task3自我阻塞后,待task2的自我阻塞时间结束,task2重新抢占CPU,待task1的自我阻塞时间结束,task1重新抢占CPU,以此往复,实现双LED灯闪烁。
③在某次执行task3时,按下按键1,task1和task2将会被删除,LED1和LED2会停止在任务删除前的状态,不再闪烁。(按下按键1的过程中因为按键消抖使用了OSTimeDly函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)
④在只有任务task3的情况下,按下按键2,task1和task2将会重新恢复,LED1和LED2将继续闪烁。(按下按键2的过程中因为按键消抖使用了OSTimeDly函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)