FreeRTOS系列三:动态任务创建和API函数介绍和实战编程

基于STM32F103RCT6,请结合正点原子视频观看,源码百度网盘地址在文章末尾

参考材料:"D:\探索者F4 资料盘(A盘)\【正点原子】STM32F103最小系统板资料\6,软件资料\6,软件资料\13,FreeRTOS学习资料\13,FreeRTOS学习资料\FreeRTOS实时内核使用指南-中文.pdf"

我在这里不累述文档里已经有的概念,只是记录核心思想和困难。

任务的函数原型

void ATaskFunction( void *pvParameters ) 
{ 
/* 可以像普通函数一样定义变量。用这个函数创建的每个任务实例都有一个属于自己的iVarialbleExample变
量。但如果iVariableExample被定义为static,这一点则不成立 – 这种情况下只存在一个变量,所有的任务实
例将会共享这个变量。 */ 
int iVariableExample = 0; 
/* 任务通常实现在一个死循环中。 */ 
for( ;; ) 
{ 
/* 完成任务功能的代码将放在这里。 */ 
} 
/* 如果任务的具体实现会跳出上面的死循环,则此任务必须在函数运行完之前删除。传入NULL参数表示删除
的是当前任务 */ 
vTaskDelete( NULL ); 
}
  • FreeRTOS 任务不允许以任何方式从实现函数中返回——它们绝不能有一 条”return”语句,也不能执行到函数末尾,其必须返回 void。
  • 每个任务都是在自己权限范围内的一个小程序。其具有程序入口,通常会运行在一 个死循环中,也不会退出。
  • 如果一个任务不再需要,可以显式地将其删除。

创建任务使用 FreeRTOS 的 API 函数 xTaskCreate()

宏 configSUPPORT_DYNAMIC_ALLOCATION 必须为 1

ps:

pcName包括’\0’结束符。如果传入的字符串长度超 过了这个最大值,字符串将会自动被截断

pxCreatedTask 用于传出任务的句柄。这个句柄将在 API 调用中对 该创建出来的任务进行引用,比如改变任务优先级,或者删除任务。 如果应用程序中不会用到这个任务的句柄,则 pxCreatedTask 可以 被设为 NULL。

创建静态任务使用 FreeRTOS 的 API 函数xTaskCreateStatic()

 如 果 要 使 用 此 函 数 的 话 需 要 将 宏 configSUPPORT_STATIC_ALLOCATION 定义为 1

任务删除函数vTaskDelete()

 这里理解一下第二点,如果在TASK1中执行删除TASK2的函数,那么TASK2的堆栈在TASK1中执行时释放(TASK1和TASK2均为动态创建)

函数 xTaskCreateRestricted() 此函数也是用来创建任务的,只不过此函数要求所使用的 MCU 有 MPU(内存保护单元), 用此函数创建的任务会受到 MPU 的保护。其他的功能和函数 xTaxkCreate()一样。

在正点原子下载的课程源码中找到xTaskCreate()函数

 然后在task.h的257行可以找到这个函数的实例

 也可以到官网看,官网的特别详细This page describes the RTOS xTaskCreate() FreeRTOS API function which is part of the RTOS task control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers.

pvTaskCode   指向任务入口函数的指针(即 实现任务的函数名称,请参阅如下示例)。

任务通常 以 无限循环的形式实现;实现任务的函数决不能试图返回 或退出。 但是,任务可以 自我删除

pcName   任务的描述性名称。主要是为了方便 调试,但也可用于 获取任务句柄

任务名称的最大长度由 中的 FreeRTOSConfig.hconfigMAX_TASK_NAME_LEN 定义。

usStackDepth   分配用于 任务堆栈的 字数(不是字节)。例如,如果堆栈的宽度为 16 位,usStackDepth 为 100,则将分配 200 字节用作该任务的堆栈。 再举一例,如果堆栈的宽度为 32 位,usStackDepth 为 400,则将分配 1600 字节用作该任务的堆栈。

堆栈深度与堆栈宽度的乘积不得超过 size_t 类型变量所能包含的最大值。

请参阅常见问题:堆栈应有多大?

pvParameters   作为参数传递给创建的任务的一个值。

如果 pvParameters 设置为变量的地址, 则在执行创建的任务时该变量必须仍然存在——因此 传递堆栈变量的地址是无效的。

uxPriority   创建任务执行的优先级 。

包含 MPU 支持的系统可在特权(系统)模式下选择性地创建任务, 方法是在 uxPriority 中设置 portPRIVILEGE_BIT 位。 例如,要创建一个优先级为 2 的特权任务,可将 uxPriority 设置为 ( 2 | portPRIVILEGE_BIT )。

断言优先级低于 configMAX_priority。 如果未定义 configASSERT,则优先级会被静默限制为 ( configMAX_priority - 1)。

pxCreatedTask   用于将句柄传递至由 xTaskCreate() 函数创建的任务 。 pxCreatedTask 是可选的,可设置为 NULL。

返回:如果任务创建成功,则返回 pdPASS。否则返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

 这里解释一下第三个参数usStackDepth 如果堆栈的宽度为16位,即每个堆栈元素占2个字节;如果堆栈的宽度为32位,即每个堆栈元素占4个字节
对于给定的堆栈宽度和usStackDepth,可以使用以下公式来计算任务堆栈的大小:

堆栈大小(字节)= 堆栈宽度(字节) x usStackDepth

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint32_t odr;

  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  /* get current Output Data Register value */
  odr = GPIOx->ODR;

  /* Set selected pins that were at low level, and reset ones that were high */
  GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
}

通过操作 ODR 寄存器的位,我们可以改变相应引脚的电平状态,这是由微控制器芯片内部的驱动电路和逻辑实现的。

BSRR 只写寄存器:[color=Red]既能控制管脚为高电平,也能控制管脚为低电平。
对寄存器高 16bit 写1 对应管脚为低电平,对寄存器低16bit写1对应管脚为高电平。写 0 ,无动作

(11条消息) GPIO 配置之ODR, BSRR, BRR 详解_gpiob->odr_byhunpo的博客-CSDN博客

(odr & GPIO_Pin):这是一个按位与操作,保留ODR寄存器中对应的GPIO引脚的值的,然后左移GPIO_Pin位将BSRR中与GPIO_Pin相应引脚位置为对应的值,当GPIO->ODR为高电平时,对BSRR寄存器高十六位的对应引脚位写1会复位,也就是把该引脚复位为低电平。而(~odr & GPIO_Pin)则把ODR对应引脚的值保留在BSRR低十六位,如果原来ODR里对应引脚位为1是取反为0,则BSRR理赋值后不会对该引脚置位为高电平,然后相与就可以实现对引脚电平的取反了。

贴一下main.c

/**
 ****************************************************************************************************
 * @file        freertos.c
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.4
 * @date        2022-01-04
 * @brief       FreeRTOS 移植实验
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 F407电机开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 ****************************************************************************************************
 */

#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"

/******************************************************************************************************/
/*FreeRTOS配置*/

/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO         1
#define START_TASK_STACK_SIZE   128
TaskHandle_t    start_task_handler;
void start_task( void * pvParameters );

/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );

/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO         3
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );

/* TASK3 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK3_PRIO         4
#define TASK3_STACK_SIZE   128
TaskHandle_t    task3_handler;
void task3( void * pvParameters );
/******************************************************************************************************/


/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{    
    xTaskCreate((TaskFunction_t         )   start_task,
                (char *                 )   "start_task",
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   START_TASK_PRIO,
                (TaskHandle_t *         )   &start_task_handler );
    vTaskStartScheduler();
}


void start_task( void * pvParameters )
{
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
                
    xTaskCreate((TaskFunction_t         )   task3,
                (char *                 )   "task3",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &task3_handler );
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
}

/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
    while(1)
    {
        printf("task1正在运行!!!\r\n");
        LED0_TOGGLE();
        vTaskDelay(500);
    }
}

/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
    while(1)
    {
        printf("task2正在运行!!!\r\n");
        LED1_TOGGLE();
        vTaskDelay(500);
    }
}

/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{
    uint8_t key = 0;
    while(1)
    {
        printf("task3正在运行!!!\r\n");
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
            if(task1_handler != NULL)
            {
                printf("删除task1任务\r\n");
                vTaskDelete(task1_handler);
                task1_handler = NULL;
            }

        }
        vTaskDelay(10);
    }
}


在这段代码中,通过调用taskENTER_CRITICAL()taskEXIT_CRITICAL()函数,实现了对临界区的保护。

临界区是指在这段代码中需要保证原子性执行的部分。当进入临界区时,中断会被禁止,防止其他任务或中断干扰当前任务的执行。只有当前任务完成临界区的执行后,中断才会重新启用。

进入临界区的作用主要有两个方面:

  1. 保护共享资源:在多任务环境中,可能会存在多个任务同时访问共享资源的情况。当进入临界区时,会禁止其他任务对该共享资源的访问,防止出现数据竞争和不一致的情况。
  2. 确保操作的原子性:某些操作需要连续执行而不能被中断,否则可能会导致意外结果。通过进入临界区,可以确保这些操作的原子性,保证它们不会被其他任务或中断打断。

在这段代码中,进入临界区的目的是保护任务创建过程的原子性。在创建任务的过程中,需要对任务句柄进行赋值,并且要确保任务创建的完整性,避免其他任务的干扰。因此,在进入临界区后,先创建了LED0任务和LED1任务,并删除了开始任务,最后退出临界区。因为TASK1的任务优先级比START_TASK高,所以要禁止中断。否则会出先创建完TASK1后TASK1的任务优先级比开始任务高,执行TASK1任务去了等到阻塞才会回来继续创建函数。

链接:https://pan.baidu.com/s/1RAlmLu6V9p3NCkuGYD_4gA?pwd=rtos 
提取码:rtos

猜你喜欢

转载自blog.csdn.net/qq_51519091/article/details/131368988
今日推荐