FreeRTOS的简易使用

声明:本文所述的所有实例代码仅在stm32f103c6tb上跑过,其余单片机理论上都能使用,但未经实测


深入学习请点此处

操作系统启动步骤

1.定义任务函数

模块化,写在自己的.c文件中

/**
  * @brief    LED_Task任务主体
  * @param    parameter
  * @retval   void
  */
static void LED_Task(void* parameter)
{    
    while (1)
    {
        LED1_ON;
        vTaskDelay(500);   /* 延时500个tick */
        printf("LED_Task Running,LED1_ON\r\n");
        
        LED1_OFF;     
        vTaskDelay(500);   /* 延时500个tick */                 
        printf("LED_Task Running,LED1_OFF\r\n");
    }
}

2.空闲任务与定时器任务堆栈函数实现

当我们使用了静态创建任务的时候,configSUPPORT_STATIC_ALLOCATION 这个宏 定 义 必 须为 1 (在 FreeRTOS.h 文 件 中 ) , 并且 我 们需 要 实 现两 个 函数 : vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory(),这两个函数是用 户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而 不能是动态分配

写在main.c,函数在main函数前声明


/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
 
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;    
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;

/**
  * @brief    获取空闲任务的任务堆栈和任务控制块内存
  * @param    ppxIdleTaskTCBBuffer        :    任务控制块内存
  * @param    ppxIdleTaskStackBuffer    :    任务堆栈内存
  * @param    pulIdleTaskStackSize        :    任务堆栈大小
  * @retval   void
  */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
                                   StackType_t **ppxIdleTaskStackBuffer, 
                                   uint32_t *pulIdleTaskStackSize)
{
    *ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */
    *ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */
    *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}

/**
  * @brief    获取定时器任务的任务堆栈和任务控制块内存
  * @param    ppxTimerTaskTCBBuffer    :        任务控制块内存
  * @param    ppxTimerTaskStackBuffer    :    任务堆栈内存
  * @param    pulTimerTaskStackSize    :        任务堆栈大小
  * @retval   void
  */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
                                    StackType_t **ppxTimerTaskStackBuffer, 
                                    uint32_t *pulTimerTaskStackSize)
{
    *ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
    *ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
    *pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}

3.定义任务栈、控制块、句柄

写在main.c

/* AppTaskCreate任务堆栈 */
static StackType_t AppTaskCreate_Stack[128];
/* LED任务堆栈 */
static StackType_t LED_Task_Stack[128];

/* AppTaskCreate 任务控制块 */
static StaticTask_t AppTaskCreate_TCB;
/* AppTaskCreate 任务控制块 */
static StaticTask_t LED_Task_TCB;

/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle;

4.编写AppTaskCreate任务

这个任务是用于创建用户任务,为了方便管理,我们的所有的任务创建都统一放在这个函数中,在这个函数中创建成功的任务就可以直接参与任务调度了

写在main.c,在main函数前声明


static void AppTaskCreate(void)
{
  taskENTER_CRITICAL();           //进入临界区
  /* 创建LED_Task任务 */
    LED_Task_Handle = xTaskCreateStatic((TaskFunction_t    )LED_Task,        //任务函数
                                        (const char*     )"LED_Task",        //任务名称
                                        (uint32_t         )128,                    //任务堆栈大小
                                        (void*               )NULL,                //传递给任务函数的参数
                                        (UBaseType_t     )4,                 //任务优先级
                                        (StackType_t*   )LED_Task_Stack,    //任务堆栈
                                        (StaticTask_t*  )&LED_Task_TCB);    //任务控制块   
    if(NULL != LED_Task_Handle)/* 创建成功 */
        printf("LED_Task任务创建成功!\n");
    else
        printf("LED_Task任务创建失败!\n");
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  taskEXIT_CRITICAL();            //退出临界区
}

5.创建任务

静态创建任务

xTaskCreateStatic

在main函数内执行创建 AppTaskCreate 任务

/* 创建 AppTaskCreate 任务 */
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t    )AppTaskCreate,        //任务函数
                                         (const char*     )"AppTaskCreate",        //任务名称
                                         (uint32_t         )128,    //任务堆栈大小
                                         (void*               )NULL,                //传递给任务函数的参数
                                         (UBaseType_t     )3,     //任务优先级
                                         (StackType_t*   )AppTaskCreate_Stack,    //任务堆栈
                                         (StaticTask_t*  )&AppTaskCreate_TCB);    //任务控制块                        

在AppTaskCreate任务中创建其余任务

/* 创建LED_Task任务 */
LED_Task_Handle = xTaskCreateStatic((TaskFunction_t    )LED_Task,            //任务函数
                                    (const char*     )"LED_Task",        //任务名称
                                    (uint32_t         )128,                //任务堆栈大小
                                    (void*               )NULL,                //传递给任务函数的参数
                                    (UBaseType_t     )4,                 //任务优先级
                                    (StackType_t*   )LED_Task_Stack,    //任务堆栈
                                    (StaticTask_t*  )&LED_Task_TCB);    //任务控制块   
if(LED_Task_Handle != NULL)/* 创建成功 */
    printf("LED_Task任务创建成功!\n");
else
    printf("LED_Task任务创建失败!\n");

动态创建任务

xTaskCreate

程序跑起来内存会溢出,未查明原因

在AppTaskCreate任务中创建其余任务

/* 创建Receive_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任务入口函数 */
                      (const char*    )"Receive_Task",/* 任务名字 */
                      (uint16_t       )512,   /* 任务栈大小 */
                      (void*          )NULL,    /* 任务入口函数参数 */
                      (UBaseType_t    )2,        /* 任务的优先级 */
                      (TaskHandle_t*  )&Receive_Task_Handle);/* 任务控制块指针 */

6.启动任务

//这里建议先判断AppTaskCreate任务是否创建成功

跟在创建任务语句后

if(AppTaskCreate_Handle != NULL)/* 创建成功 */
   vTaskStartScheduler();   /* 开启调度器 */

任务管理

任务状态切换

(1):创建任务→就绪态

(2):就绪态→运行态

(3):运行态→就绪态

(4):运行态→阻塞态:(挂起、延时、 读信号量等待)

(5):阻塞态→就绪态:(任务恢复、延时时间超时、读 信号量超时或读到信号量等),

(6) (7) (8):就绪态、阻塞态、运行态→挂起态:调用 vTaskSuspend() 或 vTaskSuspendAll()

(9):挂起态→就绪态:调用 vTaskResume() 或 vTaskResumeFromISR()

删除任务

调用 vTaskDelete() 形参为要删除任 务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。

任务轮转

任务轮转即将运行态任务转就绪态,并运行最高优先级就绪态任务

以下是几个触发轮转的条件(满足一个即可)

  • 任务由运行态转阻塞态(挂起、延时、 读信号量等待)
  • 运行态→挂起态

延时函数

vTaskDelay 和 vTaskDelayUntil 函数的参数是以系统节拍为单位的延时时间,而不是以毫秒为单位。要将毫秒转换为系统节拍,可以使用portTICK_PERIOD_MS常量,例如vTaskDelay(500 / portTICK_PERIOD_MS)表示延时500毫秒。

相对延时vTaskDelay()

指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束。

绝对延时vTaskDelayUntil

指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。

消息队列使用步骤

要使用消息队列功能需要引入头文件
#include “queue.h”

1.创建消息队列

静态创建队列

xQueueCreateStatic()

使 用 xQueueCreateStatic()创建队列时,使用的是静态内存分配,所以要想使用该函数必须在 FreeRTOSConfig.h 中把 configSUPPORT_STATIC_ALLOCATION 定义为 1 来使能。


/* 创建一个可以最多可以存储 10 个 64 位变量的队列 */
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint64_t )

/* 该变量用于存储队列的数据结构 */
static StaticQueue_t xStaticQueue;

/* 该数组作为队列的存储区域,大小至少有 uxQueueLength * uxItemSize 个字节 */
uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ]; 

void vATask( void *pvParameters ){
    QueueHandle_t xQueue;
    /* 创建一个队列 */ 
    xQueue = xQueueCreateStatic(QUEUE_LENGTH, /* 队列深度 */ 
                                ITEM_SIZE, /* 队列数据单元的单位 */ 
                                ucQueueStorageArea,/* 队列的存储区域 */ 
                                &xStaticQueue ); /* 队列的数据结构 */ 
    /* 剩下的其他代码 */
    while(1){
        
    }
}

这里解释下 ucQueueStorageArea 和 xStaticQueue的区别:

ucQueueStorageArea:就是一个数组,存放队列的实际内容

xStaticQueue:是一个结构体变量,内涵了队列的属性、ucQueueStorageArea的首地址,后续操作队列实际上也就是操作xStaticQueue

动态创建队列

程序跑起来内存会溢出,未查明原因

xQueueCreate()

使用 xQueueCreate()创建队列时,使用的是动态内存分配,所以要想使用该函数必须在 FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 来使能,这 是个用于使能动态内存分配的宏,通常情况下,在 FreeRTOS 中,凡是创建任务,队列, 信号量和互斥量等内核对象都需要使用动态内存分配,所以这个宏默认在 FreeRTOS.h 头文 件中已经使能(即定义为 1)。

在AppTaskCreate中执行

BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL();           //进入临界区

/* 创建Test_Queue */
Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                          (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
if(NULL != Test_Queue)
    printf("创建Test_Queue消息队列成功!\r\n");
taskEXIT_CRITICAL();     //退出临界区

2.读队列

使用 xQueueReceive() 或 xQueueReceiveFromISR() 函数读队列,读到一个数据后,队列中该数据会被移除。两个函数分别为:在任务中使用、在ISR中使用。

这里举出xQueueReceive函数的应用实例


static void Receive_Task(void* parameter)
{    
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdTRUE */
  uint32_t r_queue;    /* 定义一个接收消息的变量 */
  while (1)
  {
    xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
                             &r_queue,      /* 发送的消息内容 */
                             portMAX_DELAY); /* 等待时间 一直等 */
    if(pdTRUE == xReturn)
      printf("本次接收到的数据是%d\n\n",r_queue);
    else
      printf("数据接收出错,错误代码0x%lx\n",xReturn);
  }
}

3.写列队

可以把数据写到队列头部,也可以写到尾部

xQueueSend                                往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait

xQueueSendToBack                    往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait

xQueueSendToBackFromISR      往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞

xQueueSendToFront                    往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait

xQueueSendToFrontFromISR     往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞

阻塞时间表示着等待队列空闲的最大超时时间。如果队列满并且xTicksToWait 被设置成0,函数立刻返回

xQueueSend()函数使用实例

static void Send_Task(void* parameter)
{     
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  uint32_t send_data1 = 1;
  uint32_t send_data2 = 2;
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {/* K1 被按下 */
      printf("发送消息send_data1!\n");
      xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
                            &send_data1,/* 发送的消息内容 */
                            0 );        /* 等待时间 0 */
      if(pdPASS == xReturn)
        printf("消息send_data1发送成功!\r\n");
    } 
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {/* K2 被按下 */
      printf("发送消息send_data2!\n");
      xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
                            &send_data2,/* 发送的消息内容 */
                            0 );        /* 等待时间 0 */
      if(pdPASS == xReturn)
        printf("消息send_data2发送成功!\r\n");
    }
    vTaskDelay(20);/* 延时20个tick */
  }
}

4.消息队列删除函数

需要注意的是调用删除消息队列函 数前,系统应存在 xQueueCreate()或 xQueueCreateStatic()函数创建的消息队列。此外 vQueueDelete()也可用于删除信号量。如果删除消息队列时,有任务正在等待消息,则不应 该进行删除操作。

消息队列删除函数 vQueueDelete()使用实例

#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE 4
 
int main( void )
{
  QueueHandle_t xQueue;
 /* 创建消息队列 */
 xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_ITEM_SIZE );
 
 if ( xQueue == NULL ) {
    /* 消息队列创建失败 */
   } else {
    /* 删除已创建的消息队列 */ 
    vQueueDelete( xQueue ); 
   }
}

5.复位

xQueueReset()

队列刚被创建时,里面没有数据;使用过程中可以调用xQueueReset()把队列恢复为初始状态

6.查询

可以查询队列中有多少个数据、有多少空余空间。

/*返回队列中可用数据的个数*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
 
/*返回队列中可用空间的个数*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

一些奇怪的发现

如果有大佬知道我这个问题出在哪,劳请赐教

  • 挂起一个任务后,再重新解挂,这个任务函数会从头执行
  • stm32f103c6芯片动态创建任务或队列都会导致ZI-data区达到20000多,程序直接炸
  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值