FreeRTOS的学习

学习有感

  1. 对于搬运大量数据,利用指针效率高于赋值,即直接访问数据量
  2. 对于FreeRTOS可以解决在裸机开发过程中的不同任务的执行频率和延时函数的CPU的死等的问题,提高了CPU对计算资源的利用率。
  3. 对于内存小的单片机,FreeRTOS占用资源过多,不利于使用,对于任务过于简单,由于切换任务占比执行任务太大,不利于使用,对于每个任务执行时间较长,使用RTOS的时间片机制能够增强每个任务的实时性。

1.创建函数和删除

  1. 动态创建为FreeRTOS分配的堆栈(方便),而静态创建为人为分配空间。动态应用多
  2. 任务中必须有while(1)否则只会执行一次
  3. 任务中的延时要用         vTaskDelay(500); 延时期间执行其它任务
    1. 任务中的延时使用的是软件延时,即还是让CPU等待来达到延时的效果。使用RTOS的优势就是能充分发挥CPU的性能,永远不让它闲着。任务如果需要延时,也就不能再让CPU空等来实现延时的效果。RTOS中的延时叫作阻塞延时,即任务需要延时时,会放弃CPU的使用权,CPU可以去做其他的事情,当任务延时时间到,重新获取CPU使用权,任务继续运行,这样就充分地利用了CPU的资源,而不是空等
      当任务需要延时而进入阻塞状态时,那CPU在做什么?如果没有其他任务可以运行,RTOS都会为CPU创建一个空闲任务,这个时候CPU就运行空闲任务。在FreeRTOS中,空闲任务是系统在启动调度器时创建的优先级最低的任务,空闲任务主体主要是做一些系统内存的清理工作。

  4. 任务创建后马上就执行,一个MCU能够支持多少任务,取决于RAM空间的大小。
  5. FreeRTOS支持抢占式和时间片两种方式同时运行。
  6. 进入临界区,不使用任务调度,退出临界区,开始任务调度
  7. 关于堆栈大小可以使用这个函数:
    xGetFreeStackSpace任务栈历史剩余最小值
动态创建任务
#define Led2_PRIO        1		       /* 任务优先级 */		
#define Led2_STACK_SIZE   128          /* 任务堆栈大小*/		
TaskHandle_t    Led2_task_handler;      /* 任务句柄 */		
void Led2Task(void *argument);          /* 任务函数声明 */	

    xTaskCreate((TaskFunction_t         )   Led2Task,				  /* 任务函数 */
                (char *                 )   "Led2",		              /* 任务名字 */
                (configSTACK_DEPTH_TYPE )   Led2_STACK_SIZE,	       /* 堆栈大小 */
                (void *                 )   NULL,	   				   /* 入口参数 */
                (UBaseType_t            )   Led2_PRIO,	               /* 任务优先级 */
                (TaskHandle_t *         )   &Led2_task_handler );      /* 任务句柄 */
任务函数
void Led2Task(void *argument)
{
  /* USER CODE BEGIN LedTask */
  /* Infinite loop */
  for(;;)
  {
    LED2_ON;
	vTaskDelay(500);
	LED2_OFF;
	vTaskDelay(500);
  }
  /* USER CODE END LedTask */
}
创建和删除任务

       vTaskDelete(task1_handler);         //填写任务句柄

// 删除任务
    if(task1_handler!= NULL)
    {
        printf("删除start_task任务\r\n");
        vTaskDelete(task1_handler);         //填写任务句柄
        task1_handler = ZERO;
    }

//创建任务
	if(Led2_task_handler == NULL)
    {
	   xTaskCreate(Led2Task,"Led2",Led2_STACK_SIZE,             
       ZERO,Led2_PRIO,&Led2_task_handler );     
    }

vTaskDelete(null);		            Delete your own tasks		
保护任务:
// 保护任务
    taskENTER_CRITICAL();               /* 进入临界区 */
    /* 被保护的代码 */
    taskEXIT_CRITICAL();                /* 退出临界区 */ 
静态创建:

        静态创建时,任务句柄作为返回值。

#define Led3_PRIO        osPriorityLow             /* 任务优先级 */		
#define Led3_STACK_SIZE   128					   /* 任务堆栈大小*/		
TaskHandle_t    Led3_task_handler;				    /* 任务句柄 */	
StackType_t     Led3_task_stack[Led3_STACK_SIZE];	/* 任务堆栈*/	
StaticTask_t    Led3_task_tcb;						/* 任务控制块*/	
void Led3Task(void *argument);					   /* 任务函数声明 */		


Led3_task_handler = xTaskCreateStatic( (TaskFunction_t )   Led3Task,		  /* 任务函数 */
                                       (char *         )   "Led3",    		  /* 任务名字 */
                                       (uint32_t       )   Led3_STACK_SIZE,	  /* 堆栈大小 */
                                       (void *         )   NULL,			  /* 入口参数 */
                                       (UBaseType_t    )   Led3_PRIO,	    /* 任务优先级* /
                                       (StackType_t *  )   Led3_task_stack,	  /* 任务堆栈 */
                                       (StaticTask_t * )   &Led3_task_tcb );  /* 任务名字 */

2.任务挂起和恢复

  1. 任务优先级越大,优先程度越高,中断优先级数值越小,优先级越高。中断中优先级恢复任务中 , 中断优先级在5—15级

        vTaskSuspend(TaskHandle_t xTaskToSuspend)    //任务挂起  ——任务句柄

         vTaskResume(TaskHandle_t xTaskToResume)    //任务恢复  ——任务句柄

        xTaskResumeFromISR(TaskHandle_t xTaskToResume)   //中断中任务恢复  ——任务句柄

挂起任务与恢复任务:
vTaskSuspend(LedHandle);  

vTaskResume(LedHandle);  

3.中断:

    STM32 总共有16个优先级(0—15)     

  1. 中断优先级在5—15级的中断中才能调用FreeRTOS中的函数,小于5的中断无法使用。且要使用尾部为FromISR的函数。
  2. 对 5—15 等级中断的管理
  3. vTaskDelay(500); 会打开中断
	portENABLE_INTERRUPTS();        //开启中断
    portDISABLE_INTERRUPTS();       //关闭中断

5.临界段代码保护:

             同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时,应该是这样:A用完,B再用;B用完,A再用。

什么是临界段:
        临界段,用一句话概括就是一段在执行时不能被中断的代码段。在FreeRTOS中,临界段最常出现的地方就是对全局变量的操作。全局变量就像是一个靶子,谁都可以对其开枪,但是有一人开枪,其他人就不能开枪,否则就不知道是谁命中了靶子。
那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在FreeRTOS中,系统调度最终也是产生PendSV中断,在PendSV Handler中实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS对临界段的保护最终还是回到对中断的开和关的控制。

作用:  防止中断和任务调度的打断。

应用方面:

  1. 通信时候,IIC SPI
  2. 系统自身需求
  3. 用户需求

特点:

  1. 成对使用
  2. 主要通过关闭和开启中断实现,和开启和关闭任务调度器
  3. 被保护的代码一定要短
  4. 关闭中断,即所有中断都不能执行;而关闭任务调度器,可以任务无法抢占,而可以中断可以打断。
//开启和关闭任务调度器    #include "FreeRTOS.h"
vTaskSuspendAll() ;
{
        … …	/* 内容 */
}
xTaskResumeAll()	;


// 代码中使用,关闭所有中断
taskENTER_CRITICAL() ;
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL()	;

// 中断中使用,关闭所有中断
uint32_t  save_status;
save_status  = taskENTER_CRITICAL_FROM_ISR();
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );

6.列表和列表项

  1. 列表和C语言中的链表类似
  2. 列表项为列表的子集
  3. FreeRTOS内部是一个双向环形列表
  4. 链表可以随意删除和加入成员,而且成员间地址并不是连续的,成员数字可以随意更改。
  5. 每个列表项就是一个任务
  6. 任务列表分为:阻塞列表,就绪列表,运行列表

6.任务状态查询

 1.获取与设置任务优先级:

        参数: 任务句柄,获取自己填写NULL 

    UBaseType_t priority_num = 0;
    vTaskPrioritySet( task2_handler,4 );          //设置任务的优先级 
    priority_num = uxTaskPriorityGet( NULL );	  //获取任务的优先级
    printf("task2任务优先级为%ld\r\n",priority_num);

2.获取任务数量:

        参数:无

UBaseType_t task_num = 0; 
task_num = uxTaskGetNumberOfTasks();
printf("任务数量:%ld\r\n",task_num);
3.由任务名获取任务句柄:

        参数:无

   TaskHandle_t task_handle = 0;
   task_handle = xTaskGetHandle( "task1" );
    printf("任务句柄:%#x\r\n",(int)task_handle);
4.查询历史剩余最小堆栈

         如果剩余很小,则表明要调大堆栈。剩余字节=剩余数*4  ,

与创建的时候填写的是同一个单位,即将初级填写的堆栈大小减去剩余的大小,再稍微加点就是比较合适的堆栈。

	UBaseType_t task_stack_min = 0;         /* 局部 */	    
	printf("Task:\t Residual\r\n");

    vTaskDelay(500);
	task_stack_min = uxTaskGetStackHighWaterMark( LvglHandle );
	printf("Lvgl:\t%ld\r\n",task_stack_min);// 


    vTaskDelay(500);
	task_stack_min = uxTaskGetStackHighWaterMark( LedHandle );
	printf("Led:\t%ld\r\n",task_stack_min);

    vTaskDelay(500);
	task_stack_min = uxTaskGetStackHighWaterMark( Led2_task_handler );
	printf("Led2:\t%ld\r\n",task_stack_min);

	printf("\r\n");
5.统计任务时间占比:

1、将宏 configGENERATE_RUN_TIME_STATS 1

2、将宏 configUSE_STATS_FORMATTING_FUNCTIONS  1

当将此宏 configGENERATE_RUN_TIME_STAT  1之后,还需要实现2个宏定义:

portCONFIGURE_TIMER_FOR_RUNTIME_STATE() :用于初始化用于配置任务运行时间统计的时基定时器;

注意:这个时基定时器的计时精度需高于系统时钟节拍精度的10100

 3portGET_RUN_TIME_COUNTER_VALUE():用于获取该功能时基硬件定时器计数的计数值 。

7.延时函数

相对延时:vTaskDelay()

从延时开始计数,到了计时时间,返回执行。

绝对延时:xTaskDelayUntil()

适用于需要按照一定频率运行的任务,把任务和延时组成一个周期,按照一定的频率执行。

任务以周期性固定执行。

8.进程间通信

基本概念
1. 消息队列:

        1. 队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递

        2. 全局变量在多任务操作时容易数据受损。由于队列读写队列是会保护

        3. 先进先出(FIFO),任务先得到的是最先进入消息队列的消息,

        4. 用于任务间通信的数据结构,在创建队列时,就要指定队列长度以及队列项目的大小!
         5 .队列传递实际值,不好的是耗时,而ucos,是传递值的地址,值就会不安全。当传递较大的数据时FreeRTOS也可以传递指针。

        6. 当队列已满,可以设置阻塞时间,种类有:未读到直接返回 向下执行,等待特定时间,时间到就向下执行 ,直到等到消息才往下执行,否则一直是阻塞状态。

用法:
1.创建队列

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

动态创建:

        参数:队列长度,队列数据类型长度

        返回: 队列句柄 ( 用于读取和写入)

创建数据类型

#define queueQUEUE_TYPE_BASE                  			( ( uint8_t ) 0U )	/* 队列 */
#define queueQUEUE_TYPE_SET                  			( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX                 			( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE     	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX       		( ( uint8_t ) 4U )	/* 递归互斥信号量 */

写入位置:

覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用

#define queueSEND_TO_BACK       ( ( BaseType_t ) 0 )		/* 写入队列尾部 */
#define queueSEND_TO_FRONT     	( ( BaseType_t ) 1 )		/* 写入队列头部 */
#define queueOVERWRITE          ( ( BaseType_t ) 2 )		/* 覆写队列*/

示例创建:

先添加头文件

#include "queue.h"  

创建:

QueueHandle_t key_queue;        /* 数据句柄  放在全局   */

 /* 队列的创建  返回句柄  */
 key_queue = xQueueCreate( 2, sizeof(uint8_t) );  /* 几个这样的数据,每个数据的大小 */ 
 if(key_queue != NULL)
 {
    printf("key_queue队列创建成功!!\r\n");
 }else printf("key_queue队列创建失败!!\r\n");

2.写入队列

函数

描述

xQueueSend()

往队列的尾部写入消息

xQueueSendToBack()

xQueueSend()

xQueueSendToFront()

往队列的头部写入消息

xQueueOverwrite()

覆写队列消息(只用于队列长度为 1 的情况)

xQueueSendFromISR()

在中断中往队列的尾部写入消息

xQueueSendToBackFromISR()

xQueueSendFromISR()

xQueueSendToFrontFromISR()

在中断中往队列的头部写入消息

xQueueOverwriteFromISR()

在中断中覆写队列消息(只用于队列长度为 1 的情况)

写入:

uint8_t key = 0;          //返回值  局部  类型要和写入队列一致
BaseType_t   err = 0;      //返回值  局部

// key =传入的值;

 // 句柄  传入的值   阻塞方式(死等)
err = xQueueSend( key_queue, &key, portMAX_DELAY );
	if(err != pdTRUE)
	{
		printf("队列写入失败\r\n");
	}
		printf("队列写入成功\r\n");

3.读取队列

函数

描述

xQueueReceive()

从队列头部读取消息,并删除消息

xQueuePeek()

从队列头部读取消息

xQueueReceiveFromISR()

在中断中从队列头部读取消息,并删除消息

xQueuePeekFromISR()

在中断中从队列头部读取消息

    uint8_t key = 0;  
    BaseType_t err = 0;

//  读取队列     句柄    取值变量    阻塞方式(死等)
        err = xQueueReceive( key_queue,&key,portMAX_DELAY);
        if(err != pdTRUE)
        {
            printf("key_queue队列读取失败\r\n");
        }else 
        {
            printf("key_queue读取队列成功,数据:%d\r\n",key);
        }

2. 事件组:

3. 信号量:

通知任务完成的信号,解决同步问题的机制,对共享资源的有序访问。

最大值为1,即只有0和1两种,就是二值信号量。(单个资源,例如串口)

最大值不为1,他就是计数型信号量(多个资源,例如多个车位的使用)。

二值信号量:

互斥访问和任务同步,但更适合与同步,用于对一个外设资源的获取和释放。

互斥信号量:

用于互斥访问资源

计数型信号量:
互斥信号量:

4. 任务通知:
5. 互斥量:

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值