02. µCOS-Ⅲ的基本使用

一、任务创建和删除

  任务的创建和删除本质上就是调用 µC/OS-Ⅲ 的 API 函数。

1.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,             // 任务选项
                    OS_ERR        *p_err            // 指向接收错误代码变量的指针
);

  此函数用于创建任务,任务的任务控制块以及任务栈空间所需的内存,需要由用户手动分配并提供。当任务被创建好后,就会立马处于 就绪态,此时只要任务调度器处于正常工作状态,那么创建好的任务就会由任务调度器调度。要注意的是,不能在中断服务函数中创建任务。

  任务控制块是一个 OS_TCB 类型的结构体,它的主要成员如下:

struct os_tcb {
    CPU_STK *StkPtr;                // 任务栈栈顶,必须为TCB的第一个成员
    CPU_STK *StkLimitPtr;           // 指向任务栈警戒线指针
    OS_TCB *NextPtr;                // 指向任务列表中下一个任务控制块指针
    OS_TCB *PrevPtr;                // 指向任务列表中前一个任务控制块指针
    OS_TCB *TickNextPtr;            // 指向任务列表中下一个任务控制块指针
    OS_TCB *TickPrevPtr;            // 指向任务列表中前一个任务控制块指针
    OS_PRIO Prio;                   // 任务优先级,数值越小,优先级越大
    OS_TICK TickRemain;             // 任务延迟的剩余时钟节拍数
    OS_TICK TimeQuanta;             // 任务时间片
    OS_TICK TimeQuantaCtr;          // 任务剩余时间片
    ...
};

任务栈栈顶,在任务切换时与任务的上下文保存、任务恢复息息相关。

每个任务都有属于自己的任务控制块。

  任务选项共有 5 个值,它们的描述如下:

#define  OS_OPT_TASK_NONE       (OS_OPT)(0x0000u)   // 没有选项
#define  OS_OPT_TASK_STK_CHK    (OS_OPT)(0x0001u)   // 是否允许对任务进行堆栈检查
#define  OS_OPT_TASK_STK_CLR    (OS_OPT)(0x0002u)   // 是否需要清除任务堆栈
#define  OS_OPT_TASK_SAVE_FP    (OS_OPT)(0x0004u)   // 是否保存浮点寄存器
#define  OS_OPT_TASK_NO_TLS     (OS_OPT)(0x0008u)   // 不需要对正在创建的任务提供LTS(线程本都存储)支持

  函数 OSTaskCreate() 的错误代码描述,如下表所示:

OS_ERR_NONE                     // 任务创建成功
OS_ERR_ILLEGAL_CREATE_RUN_TIME  // 定义了OS_SAFETY_CRITICAL_IEC61508,且在OSStart()之后非法地创建内核对象
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              // 指向任务控制块的指针为空

任务栈必须定义为 CPU_STK 类型。

在任务函数中必须调用 µC/OS-Ⅲ 提供的用来让任务挂起的函数,例如延时函数、任务挂起函数或等待内核对象(等待消息队列、事件标志、信号量、互斥信号量等)的函数,这样才能够让其他任务获得 CPU 的使用权。

应用程序中创建的任务不能够使用数值为 0 、 1 、 OS_CFG_PRIO_MAX-2 和 OS_CFG_PRIO_MAX-1 的任务优先级,因为这些任务优先级是保留给 µC/OS-Ⅲ 使用的。

1.2、删除任务

void  OSTaskDel (OS_TCB *p_tcb,     // 指向任务控制块指针
                 OS_ERR *p_err      // 接收错误代码变量指针
);

  此函数用于删除任务。当不再需要某一任务时,可以使用此函数来删除任务。删除任务并不会删除任务的代码或释放任务栈,删除任务指示意味着,任务的代码和任务栈都不再由 µCOS-Ⅲ 内核管理,因此被删除任务的任务代码和任务栈依然可以在创建其他任务时使用。

  函数 OSTaskDel() 的错误代码描述,如下表所示:

OS_ERR_NONE                     // 任务删除成功
OS_ERR_ILLEGAL_DEL_RUN_TIME     // 定义了OS_SAFETY_CRITICAL_IEC61508,且OS_ERR_ILLEGAL_DEL_RUN_TIME在OSStart()之后非法地删除内核对象
OS_ERR_OS_NOT_RUNING            // μC/OS-Ⅲ内核还未运行
OS_ERR_STATE_INVALID            // 任务处于无效状态
OS_ERR_TASK_DEL_IDLE            // 非法删除空闲任务
OS_ERR_TASK_DEL_INVALID         // 非法删除μC/OS-Ⅲ的中断服务任务
OS_ERR_TASK_DEL_ISR             // 在中断中非法地删除任务

不能删除空闲任务和 µC/OS-Ⅲ 的中断服务任务。

如果任务控制块的参数为 NULL,则代表删除任务自身(当前正在运行的任务)。

1.3、任务创建的流程

  1. 定义函数入口参数
  2. 调用创建任务 API 函数
  3. 实现任务函数功能

1.3.1、main()函数

  main() 函数内容如下:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    UART_Init(&g_usart1_handle, USART1, 115200);

    LED_Init();
    Key_Init();
  
    UC_OS3_Demo();
  
    return 0;
}

1.3.2、LED功能函数

  LED 初始化函数:

/**
 * @brief led初始化函数
 * 
 */
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOF_CLK_ENABLE();                                               // 使能GPIOF的时钟

    GPIO_InitStruct.Pin = GPIO_PIN_9;                                           // GPIO引脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;                                 // 推挽输出模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;                                         // 不使用上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;                               // 高速模式
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);                                     // GPIO初始化

    GPIO_InitStruct.Pin = GPIO_PIN_10;                                          // GPIO引脚
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);                                     // GPIO初始化

    LED_Status(GPIOF, GPIO_PIN_9, LED_OFF);
    LED_Status(GPIOF, GPIO_PIN_10, LED_OFF);
}

  LED 状态设置函数:

typedef enum LED_State
{
    LED_ON,
    LED_OFF,
} LED_State;
/**
 * @brief 设置LED状态函数
 * 
 * @param LED_Port LED的GPIO端口
 * @param LED_Pin LED的GPIO引脚
 * @param status LED的状态枚举值
 */
void LED_Status(GPIO_TypeDef *LED_Port, uint16_t LED_Pin, LED_State status)
{
    if(status == LED_ON)
    {
        HAL_GPIO_WritePin(LED_Port, LED_Pin, GPIO_PIN_RESET);
    }
    else
    {
        HAL_GPIO_WritePin(LED_Port, LED_Pin, GPIO_PIN_SET);
    }
}

1.3.3、按键功能函数

  按键初始化函数:

#define WKUP_GPIO_PORT          GPIOA
#define WKUP_GPIO_PIN           GPIO_PIN_0
#define WKUP_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOA_CLK_ENABLE(); \
                                } while(0);

#define KEY1_GPIO_PORT          GPIOE
#define KEY1_GPIO_PIN           GPIO_PIN_4
#define KEY1_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOE_CLK_ENABLE(); \
                                } while(0);

#define KEY2_GPIO_PORT          GPIOE
#define KEY2_GPIO_PIN           GPIO_PIN_3
#define KEY2_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOE_CLK_ENABLE(); \
                                } while(0);

#define KEY3_GPIO_PORT          GPIOE
#define KEY3_GPIO_PIN           GPIO_PIN_2
#define KEY3_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOE_CLK_ENABLE(); \
                                } while(0);
/**
 * @brief 按键初始化函数
 * 
 */
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    WKUP_GPIO_CLK_ENABLE();
    KEY1_GPIO_CLK_ENABLE();
    KEY2_GPIO_CLK_ENABLE();
    KEY3_GPIO_CLK_ENABLE();

    GPIO_InitStruct.Pin = WKUP_GPIO_PIN;                                        // GPIO引脚
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;                                     // 输入模式
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;                                       // 使用下拉
    HAL_GPIO_Init(WKUP_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化

    GPIO_InitStruct.Pin = KEY1_GPIO_PIN;                                        // GPIO引脚
    GPIO_InitStruct.Pull = GPIO_PULLUP;                                         // 使用上拉
    HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化

    GPIO_InitStruct.Pin = KEY2_GPIO_PIN;                                        // GPIO引脚
    HAL_GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化

    GPIO_InitStruct.Pin = KEY3_GPIO_PIN;                                        // GPIO引脚
    HAL_GPIO_Init(KEY3_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化
}

  按键扫描函数:

/* 读取按键对应的GPIO引脚的电平状态 */
#define WK_UP                   HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN)
#define KEY1                    HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN)
#define KEY2                    HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN)
#define KEY3                    HAL_GPIO_ReadPin(KEY3_GPIO_PORT, KEY3_GPIO_PIN)


/* 对应按键按下时代表的数值 */
#define WKUP_PRESS              1
#define KEY1_PRESS              2
#define KEY2_PRESS              3
#define KEY3_PRESS              4
/**
 * @brief 按键扫描函数
 * 
 * @param mode 0:不支持连续按;1:支持连续按
 * @return uint8_t WKUP_PRES,1,WKUP按下
 *                 KEY1_PRES,2,KEY0按下
 *                 KEY2_PRES,3,KEY1按下
 *                 KEY3_PRES,4,KEY2按下
 * 
 * @note 该函数有响应优先级,同时按下多个按键:WK_UP > KEY3 > KEY2 > KEY1
 */
uint8_t Key_Scan(uint8_t mode)
{
    static uint8_t flag = 1;                                                    // 按键按松开标志
    uint8_t keyValue = 0;

    flag = (mode ? 1 : flag);                                                   // 支持连按

    if (flag && ( WK_UP == 1 || KEY1 == 0 || KEY2 == 0 || KEY3 == 0))           // 按键松开标志为1, 且有任意一个按键按下了
    {
        HAL_Delay(10);                                                          // 按键消抖
        flag = 0;   
        // 再次读取GPIO引脚的电平
        keyValue = ((KEY1 == 0) ? KEY1_PRESS : keyValue);
        keyValue = ((KEY2 == 0) ? KEY2_PRESS : keyValue);
        keyValue = ((KEY3 == 0) ? KEY3_PRESS : keyValue);
        keyValue = ((WK_UP == 1) ? WKUP_PRESS : keyValue);  
    }
    else if (WK_UP == 0 && KEY1 == 1 && KEY2 == 1 && KEY3 == 1)                 // 没有任何按键按下, 标记按键松开
    {
        flag = 1;
    }

    return keyValue;                                                            // 按键没有按下返回0 
}

1.3.4、串口功能函数

  串口初始化函数:

UART_HandleTypeDef g_usart1_handle;                                             // USART1句柄

uint8_t g_uart_rx_buffer[1];                                                    // HAL库使用的串口接收数据缓冲区
uint8_t g_usart1_rx_buffer[UART_RECEIVE_LENGTH];                                // 接收数据缓冲区
uint16_t g_usart1_rx_status = 0;                                                // 接收状态标记

/**
 * @brief 串口初始化函数
 * 
 * @param huart 串口句柄
 * @param UARTx 串口寄存器基地址
 * @param band 波特率
 */
void UART_Init(UART_HandleTypeDef *huart, USART_TypeDef *UARTx, uint32_t band)
{
    huart->Instance = UARTx;                                                    // 寄存器基地址
    huart->Init.BaudRate = band;                                                // 波特率
    huart->Init.WordLength = UART_WORDLENGTH_8B;                                // 数据位
    huart->Init.StopBits = UART_STOPBITS_1;                                     // 停止位
    huart->Init.Parity = UART_PARITY_NONE;                                      // 奇偶校验位
    huart->Init.Mode = UART_MODE_TX_RX;                                         // 收发模式
    huart->Init.HwFlowCtl = UART_HWCONTROL_NONE;                                // 硬件流控制
    huart->Init.OverSampling = UART_OVERSAMPLING_16;                            // 过采样
    HAL_UART_Init(huart);

    HAL_UART_Receive_IT(huart, (uint8_t *)g_uart_rx_buffer, 1);                 // 开启接收中断
}

  串口底层初始化函数:

/**
 * @brief 串口底层初始化函数
 * 
 * @param huart 串口句柄
 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (huart->Instance == USART1)                                              // 初始化的串口是否是USART1
    {
        __HAL_RCC_USART1_CLK_ENABLE();                                          // 使能USART1时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();                                           // 使能对应GPIO的时钟

        // PA9 -> USART TXD
        GPIO_InitStruct.Pin = GPIO_PIN_9;                                       // USART1 TXD的引脚
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                                 // 推挽式复用
        GPIO_InitStruct.Pull = GPIO_NOPULL;                                     // 不使用上下拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;                           // 输出速度
        GPIO_InitStruct.Alternate = GPIO_AF7_USART1;                            // 复用功能
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        // PA10 -> USART RXD
        GPIO_InitStruct.Pin = GPIO_PIN_10;                                      // USART1 RXD的引脚
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        HAL_NVIC_EnableIRQ(USART1_IRQn);                                        // 使能USART1中断
        HAL_NVIC_SetPriority(USART1_IRQn, 7, 0);                                // 设置中断优先级
    }
}

  串口 1 中断服务函数:

/**
 * @brief USART1中断服务函数
 * 
 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&g_usart1_handle);                                      // 调用HAL库公共处理函数
    HAL_UART_Receive_IT(&g_usart1_handle, (uint8_t *)g_uart_rx_buffer, 1);      // 再次开启接收中断
}

  串口接收中断回调函数:

/**
 * @brief USART接收中断回调函数
 * 
 * @param huart 串口句柄
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        if ((g_usart1_rx_status & 0x8000) ==  0)                                // 接收未完成
        {
            if (g_usart1_rx_status & 0x4000)                                    // 接收到了0x0D,即回车键
            {
                if (g_uart_rx_buffer[0] != 0x0A)                                // 接收到的不是0x0A,即不是换行符
                {
                    g_usart1_rx_status = 0;                                     // 接收错误,重新开始
                }
                else                                                            // 接收到的是0x0A,即换行符
                {
                    g_usart1_rx_status |= 0x8000;                               // 接收完成  
                }
            }
            else                                                                // 还没接收到0x0D,即还没接收到回车键
            {
                if (g_uart_rx_buffer[0] == 0x0D)                                // 接收到的是0x0D,即回车键
                {
                    g_usart1_rx_status |= 0x4000;
                }
                else                                                            // 如果没有接收到回车
                {
                    // 接收到的数据存入接收缓冲区
                    g_usart1_rx_buffer[g_usart1_rx_status & 0x3FFF] = g_uart_rx_buffer[0];
                    g_usart1_rx_status++;
                    if (g_usart1_rx_status > (UART_RECEIVE_LENGTH - 1))         // 接收到数据大于接收缓冲区大小
                    {
                        g_usart1_rx_status = 0;                                 // 接收数据错误,重新开始接收
                    }
                }
            }
        }
    }
}

  重映像 printf() 函数:

/**
 * @brief 重写_write使用printf()函数
 * 
 * @param fd 一个非负整数,代表要写入数据的文件或设备的标识
 * @param ptr 一个指向字符数据的指针,即要写入的数据的起始位置
 * @param length 一个整数,表示要写入的数据的字节数
 * @return int 数据的字节数
 */
int _write(int fd, char *ptr, int length)
{
    HAL_UART_Transmit(&g_usart1_handle, (uint8_t *)ptr, length, 0xFFFF);        // g_usart1_handle是对应串口
    return length;
}

1.3.5、µC/OS-Ⅲ任务函数

  µC/OS-Ⅲ例程入口函数:

/**
 * @brief µC/OS-Ⅲ例程入口函数
 * 
 */
void UC_OS3_Demo(void)
{
    OS_ERR error = {0};

    OSInit(&error);                                                             // 初始化µC/OS-Ⅲ

    // 创建开始任务
    OSTaskCreate((OS_TCB *   )  &start_task_tcb,                                // 任务控制块
                (CPU_CHAR *  )  "start_task",                                   // 任务名
                (OS_TASK_PTR )  Start_Task,                                     // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  START_TASK_PRIORITY,                            // 任务优先级
                (CPU_STK *   )  start_task_stack,                               // 任务堆栈
                (CPU_STK_SIZE)  START_TASK_STACK_SIZE / 10,                     // 任务栈的使用警戒线
                (CPU_STK_SIZE)  START_TASK_STACK_SIZE,                          // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    OSStart(&error);                                                            // 开始任务调度
}

  START_TASK 任务配置:

/**
 * START_TASK 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define START_TASK_PRIORITY     5
#define START_TASK_STACK_SIZE   256

OS_TCB  start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];

void Start_Task(void *p_arg);

/**
 * @brief 开始任务的任务函数
 * 
 * @param p_arg 任务参数
 */
void Start_Task(void *p_arg)
{
    OS_ERR error = {0};
    CPU_INT32U cnts = 0;

    CPU_Init();                                                                 // 初始化CPU库

    CPU_SR_ALLOC();                                                             // 临界区保护

    cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
    OS_CPU_SysTickInit(cnts);                                                   // 根据配置的节拍频率配置SysTick中断及优先级

    CPU_CRITICAL_ENTER();                                                       // 进入临界区

    // 创建任务1
    OSTaskCreate((OS_TCB *   )  &task1_tcb,                                     // 任务控制块
                (CPU_CHAR *  )  "task1",                                        // 任务名
                (OS_TASK_PTR )  Task1,                                          // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  TASK1_PRIORITY,                                 // 任务优先级
                (CPU_STK *   )  task1_stack,                                    // 任务堆栈
                (CPU_STK_SIZE)  TASK1_STACK_SIZE / 10,                          // 任务栈的使用警戒线
                (CPU_STK_SIZE)  TASK1_STACK_SIZE,                               // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    // 创建任务2
    OSTaskCreate((OS_TCB *   )  &task2_tcb,                                     // 任务控制块
                (CPU_CHAR *  )  "task2",                                        // 任务名
                (OS_TASK_PTR )  Task2,                                          // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  TASK2_PRIORITY,                                 // 任务优先级
                (CPU_STK *   )  task2_stack,                                    // 任务堆栈
                (CPU_STK_SIZE)  TASK2_STACK_SIZE / 10,                          // 任务栈的使用警戒线
                (CPU_STK_SIZE)  TASK2_STACK_SIZE,                               // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    // 创建任务3
    OSTaskCreate((OS_TCB *   )  &task3_tcb,                                     // 任务控制块
                (CPU_CHAR *  )  "task3",                                        // 任务名
                (OS_TASK_PTR )  Task3,                                          // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  TASK3_PRIORITY,                                 // 任务优先级
                (CPU_STK *   )  task3_stack,                                    // 任务堆栈
                (CPU_STK_SIZE)  TASK3_STACK_SIZE / 10,                          // 任务栈的使用警戒线
                (CPU_STK_SIZE)  TASK3_STACK_SIZE,                               // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    CPU_CRITICAL_EXIT();                                                        // 退出临界区

    OSTaskDel(NULL, &error);                                                    // 删除任务
}

  TASK1 任务配置:

/**
 * TASK1 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define TASK1_PRIORITY          4
#define TASK1_STACK_SIZE        256

OS_TCB  task1_tcb;
CPU_STK task1_stack[TASK1_STACK_SIZE];

void Task1(void *p_arg);

/**
 * @brief 任务1的任务函数,实现LED1每500ms闪烁一次
 * 
 * @param p_arg 任务参数
 */
void Task1(void *p_arg)
{
    OS_ERR error = {0};

    while (1)
    {
        printf("task1 正在运行\r\n");
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
        OSTimeDly(500, OS_OPT_TIME_DLY, &error);
    }
}

  TASK2 任务配置:

/**
 * TASK2 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define TASK2_PRIORITY          3
#define TASK2_STACK_SIZE        256

OS_TCB  task2_tcb;
CPU_STK task2_stack[TASK2_STACK_SIZE];

void Task2(void *p_arg);

/**
 * @brief 任务2的任务函数,实现LED2每500ms闪烁一次
 * 
 * @param p_arg 任务参数
 */
void Task2(void *p_arg)
{
    OS_ERR error = {0};

    while (1)
    {
        printf("task2 正在运行\r\n");
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
        OSTimeDly(500, OS_OPT_TIME_DLY, &error);
    }
}

  TASK3 任务配置:

/**
 * TASK3 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define TASK3_PRIORITY          2
#define TASK3_STACK_SIZE        256

OS_TCB  task3_tcb;
CPU_STK task3_stack[TASK3_STACK_SIZE];

void Task3(void *p_arg);

/**
 * @brief 任务3的任务函数,K1按键按下删除任务1
 * 
 * @param p_arg 任务参数
 */
void Task3(void *p_arg)
{
    OS_ERR error = {0};

    while (1)
    {
        printf("task3 正在运行\r\n");

        switch (Key_Scan(0))
        {
        case KEY1_PRESS:
            printf("删除 task1 任务\r\n");
            OSTaskDel(&task1_tcb, &error);
            break;
        }

        OSTimeDly(10, OS_OPT_TIME_DLY, &error);
    }
}

创建成功的任务会立即进入就绪态,由任务调度器调度运行。

在调用任务关于 µC/OS-Ⅲ 函数之前必须先初始化 µC/OS-Ⅲ,仅需要调用一次 OSInit() 函数初始化一次即可。

任务在创建之后是不会直接运行的,需要调用一次 OSStart() 函数开启任务调度器,任务才能得以运行。

临界区保护,保护那些不想打断的程序段,关闭 µC/OS-Ⅲ 所管理的中断,中断无法打断,SysTick 和 PendSV 中断无法进行。

不可以删除空闲任务。

二、任务的挂起和恢复

2.1、任务挂起

void   OSTaskSuspend (OS_TCB  *p_tcb,           // 指向任务控制块的指针
                      OS_ERR  *p_err);          // 指向接收错误代码变量的指针

  此函数用于无条件地挂起任务,被挂起的任务不会参与任务调度,如果被挂起的任务是当前正在运行的任务,那么就会发起任务调度,将 CPU 的使用权交给另一个任务。无论任务优先级如何,被挂起的任务都将不会再被执行,直到任务恢复。被函数 OSTaskSuspend() 挂起的任务只能通过函数 OSTaskResume() 恢复。任务挂起的本质是 将任务从就绪列表中移除,不在参与运行。

  当传入的任务控制块的指针为 0 时,则代表挂起任务自身(当前正在运行的任务)。如果任务挂起等待其他内核对象,那么即使任务被该函数恢复后,也将继续挂起等待内核对象。

挂起任务类似暂停,可恢复;删除任务,无法恢复,只能重新创建;

挂起函数 OSTaskSuspend() 不允许在中断中调用。

2.2、任务恢复

void  OSTaskResume (OS_TCB  *p_tcb,             // 指向任务控制块的指针
                    OS_ERR  *p_err);            // 指向接收错误代码变量的指针

  此函数用于恢复被函数 OSTaskSuspend()挂起的任务,并且只有该函数才能恢复被函数 OSTaskSuspend() 挂起的任务,由此可见,被函数 OSTaskSuspend() 挂起的任务只能由另一个任务通过函数 OSTaskResume() 进行恢复。我们可以调用多次 OSTaskSuspend() 挂起同一个任务,解卦时需要调用同样次数的 OSTaskResume() 才可恢复任务。恢复的本质就是 把任务重新插入到就绪列表,继续运行。

函数 OSTaskSuspend()与函数 OSTaskResume()必须成对出现。

恢复函数 OSTaskResume() 不允许在中断中调用。

2.3、实验例程

  修改 TASK3 任务函数:

/**
 * @brief 任务3的任务函数,K1按键按下删除任务1,K2按键挂起任务1,K3按键恢复任务1
 * 
 * @param p_arg 任务参数
 */
void Task3(void *p_arg)
{
    OS_ERR error = {0};

    while (1)
    {
        switch (Key_Scan(0))
        {
        case KEY1_PRESS:
            printf("删除 task1 任务\r\n");
            OSTaskDel(&task1_tcb, &error);
            break;

        case KEY2_PRESS:
            printf("挂起 task1 任务\r\n");
            OSTaskSuspend(&task1_tcb, &error);
            break;

        case KEY3_PRESS:
            printf("恢复 task1 任务\r\n");
            OSTaskResume(&task1_tcb, &error);
            break;
        }

        OSTimeDly(10, OS_OPT_TIME_DLY, &error);
    }
}
posted @ 2024-02-08 18:34  星光映梦  阅读(24)  评论(0编辑  收藏  举报