FreeRTOS的入门函数


FreeRTOS

简介

FreeRTOS它是免费开源的,可以适用于很多的RAM内核,是一种实时操作系统。

实现流程

对于单核单片机而言,能够利用时间片的轮询调度进行任务间的流转,以实现多任务同时进行。

每个任务可以理解成一个线程,但是任务间的通信又像是进程和线程间通信的综合(不会的建议去看一看进程和线程间的通信)

时间片的流转其实就是任务状态的转换,具体查看如下图所示:

由图可见,FreeRTOS中的任务要是执行的话,只能从就绪态转换成运行态,此外,任务的状态还有挂起态和阻塞态,对于这两个状态比较复杂,使用到了钩子函数空闲任务(后期讲解)。

不同优先级的任务的调度问题

图片解析:以两个任务为例,如果任务的优先级相同,任务A先执行,任务A将会执行一小段时间,然后就会切换任务进行任务B的执行,来回进行切换。

如果再不理解,可以查看FreeRTOS的任务切换

API函数的应用

此章不讲解关于中断的RTOS

一、创建任务

讲解的不是特别深入

注意点:如果不将宏置1的话,无法进行任务的创建。

(1)动态创建
task. h

BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,//函数指针
                            const char * const pcName,//名称
                            const configSTACK_DEPTH_TYPE uxStackDepth,//任务的堆栈大小
                            void *pvParameters,//任务的传参
                            UBaseType_t uxPriority,//任务的优先级
                            TaskHandle_t *pxCreatedTask//任务的句柄
                        );

验证实例:

TaskHandle_t   CreatedTask1=NULL;

void Task1(void *pvParameters )
{
/*代码区*/
    for(;;)
    {
        /*任务的循环区*/
    }
}

int main(void)
{
    prvSetupHardware();
    xTaskCreate(Task1,"Check1",128,NULL,3,&CreatedTask1);
    vTaskStartScheduler();
    return 0;
}

注意任务之间,进行公共资源的访问的话。例如:如果使用串口通信的话,会出现资源的抢占问题,导致串口的数据不完整输出。

(2)静态创建(不建议新手使用)

与动态创建唯一的不同之处是:

puxStackBuffer:用户指定的这块内存地址,作为任务堆栈的地址;

pxTaskBuffer:用户指定的这块地址作为,任务的控制块;

task. h
 TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                 const char * const pcName,
                                 const uint32_t ulStackDepth,
                                 void * const pvParameters,
                                 UBaseType_t uxPriority,
                                 StackType_t * const puxStackBuffer,
                                 StaticTask_t * const pxTaskBuffer );

二、删除任务

使用删除函数的时候需要将宏INCLUDE_vTaskDelete 必须定义为 1

task. h
void vTaskDelete( TaskHandle_t xTask );

参数:任务的句柄

在实际应用当中基本不会用到。

如果用到了,可以采用以下流程:

  1. 将宏INCLUDE_vTaskDelete 置一
  2. 入口参数输入需要删除的任务句柄(NULL代表自身)
  3. 判断要删除的任务:{删除任务自身,需要添加到等待删除列表,内存释放在空闲任务删除其他任务,释放内存,任务数量……}
  4. 更新下一个任务的阻塞时间:更新下一个任务的阻塞超出的时间,以防被删除的任务为下一个阻塞超时的对象

!!!注意:任务不可以重复删除。主要是跟内存的算法相关,(简单理解:删除之后就已经把那部分内存释放掉了,所以一直释放的话会出现一些错误)

(1)动态删除任务:
if(CirticalResourceSemaphoreHandle != NULL)
		{
			printf("任务LED3ORLED4删除了\r\n");
			vTaskDelete(CirticalResourceSemaphoreHandle);//Delete LED3ORLED4
            CirticalResourceSemaphoreHandle = NULL;
		}
(2)静态删除任务(比较复杂)
/* 空闲任务配置 */
StaticTask_t idle_task_tcb;
StackType_t  idle_task_stack[configMINIMAL_STACK_SIZE];

/* 软件定时器任务配置 */
StaticTask_t timer_task_tcb;
StackType_t  timer_task_stack[configTIMER_TASK_STACK_DEPTH];

/* 空闲任务内存分配 */
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                    StackType_t ** ppxIdleTaskStackBuffer,
                                    uint32_t * pulIdleTaskStackSize )
{
    * ppxIdleTaskTCBBuffer = &idle_task_tcb;
    * ppxIdleTaskStackBuffer = idle_task_stack;
    * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
void freertos_demo(void)
{    
    start_task_handler = xTaskCreateStatic( (TaskFunction_t )   start_task,
                                            (char *         )   "start_task", 
                                            (uint32_t       )   START_TASK_STACK_SIZE,
                                            (void *         )   NULL,
                                            (UBaseType_t    )   START_TASK_PRIO,
                                            (StackType_t *  )   start_task_stack,
                                            (StaticTask_t * )   &start_task_tcb );
    vTaskStartScheduler();
}


void start_task( void * pvParameters )
{
    taskENTER_CRITICAL();               /* 进入临界区 */
    
    task1_handler = xTaskCreateStatic(  (TaskFunction_t )   task1,
                                        (char *         )   "task1", 
                                        (uint32_t       )   TASK1_STACK_SIZE,
                                        (void *         )   NULL,
                                        (UBaseType_t    )   TASK1_PRIO,
                                        (StackType_t *  )   task1_stack,
                                        (StaticTask_t * )   &task1_tcb );
                        
    task2_handler = xTaskCreateStatic(  (TaskFunction_t )   task2,
                                        (char *         )   "task2", 
                                        (uint32_t       )   TASK2_STACK_SIZE,
                                        (void *         )   NULL,
                                        (UBaseType_t    )   TASK2_PRIO,
                                        (StackType_t *  )   task2_stack,
                                        (StaticTask_t * )   &task2_tcb );
                        
    task3_handler = xTaskCreateStatic(  (TaskFunction_t )   task3,
                                        (char *         )   "task3", 
                                        (uint32_t       )   TASK3_STACK_SIZE,
                                        (void *         )   NULL,
                                        (UBaseType_t    )   TASK3_PRIO,
                                        (StackType_t *  )   task3_stack,
                                        (StaticTask_t * )   &task3_tcb );
    
    vTaskDelete(start_task_handler);
    taskEXIT_CRITICAL();                /* 退出临界区 */
}

/*删除代码块主要部分*/
if(task1_handler != NULL)
{
    printf("删除task1任务\r\n");
    vTaskDelete(task1_handler);
    task1_handler = NULL;
}

 三、信号量

简单来说这个相当于一个开关,进行开启和关闭

configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中被设置为 1。

(1)二进制信号量

简单介绍:先当于一个开关,计数器只有0和1,要么为开,要么为关,1,的时候表示可以执行,0的时候为阻塞状态,不可执行。适用于互斥间访问,确保同一时间只有一个任务被执行。

【1】使用STM32CodeMX进行创建

使用的osSemaphoreCreate()函数,其实调用的是xSemaphoreCreateBinary()函数。

【2】标准库的创建

SemaphoreHandle_t xSemaphore1;//只需要创建一个句柄来使用信号量
/*Test1*/
if(xSemaphoreTake(xSemaphore1,portMAX_DELAY) == pdTRUE)//pdTRUE:1
	  {
		  printf("Tast1\r\n");
		  osSemaphoreRelease(xSemaphore1);
		  vTaskDelay(1);
	  }
	  else
	  {
		  printf("Task1 xSemaphoreTake is err\r\n"); 
	  }

/*Test2*/
if (xSemaphoreTake(xSemaphore1, portMAX_DELAY) == pdTRUE)
	  {
		  printf("Task2\r\n");
          xSemaphoreGive(xSemaphore1);
          vTaskDelay(1);
      }
      else
      {
          printf("Task2 xSemaphoreTake is err\r\n"); 
      }

陌生函数讲解:

获取信号量的函数:

xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

释放信号量:

osSemaphoreRelease

发送信号量的函数:

xSemaphoreGive

(2)计数信号量

定义:

①取值只有0与1两种状态的信号量称之为二值信号量;取值大于1的信号量称之为计数信号量

计数信号量是一种长度大于1,消息大小为0的特殊消息队列。

③计数信号量的取值也可以为1,但通常大于1,如果取值为1,相当于只有0与1两种状态,则使用二值信号量即可。

意义:实现实现任务的同步或者资源共享功能。

计数信号量允许多个任务对其进行操作,但限制了任务的数量。

count也为可用的资源数量

API函数:

创建计数信号量 xSemaphoreCreateCounting()

释放计数信号量 xSemaphoreGive() 与 xSemaphoreGiveFromISR()

获取计数信号量 xSemaphoreTake()

删除计数信号量 vSemaphoreDelete()

实现流程:

/*Test02*/
if (xSemaphoreTake(myCountingSem01Handle, portMAX_DELAY) == pdTRUE)
	  {
		  printf("Test2 OK\r\n");
          vTaskDelay(1);
      }
      else
      {
          printf("Test2 ERR\r\n"); 
      }
      
/*Test01*/
if(xSemaphoreGive(myCountingSem01Handle) == pdTRUE)
		  {
			  printf("Test1 OK\r\n");
			  vTaskDelay(1);
		  }
		  else
		  {
			  printf("Test1 ERR\r\n"); 
		  }

四、互斥信号量

这段比较复杂,不理解的请跳过。

定义:

取值只有0与1两种状态的信号量称之为二进制信号量。 而互斥信号量是一种特殊的二值信号量,具有防止优先级翻转的特性。

应用:

在嵌入式操作系统中,互斥信号量用于临界资源的独占式访问,只能用于任务与任务间,因为其特有的优先级继承机制只能在任务中起作用,在中断的上下文环境毫无意义。

将上述的信号量换成互斥信号量即可使用

应用场景:

比如有两个任务需要通过同一串口发送数据,其硬件资源只有一个,那么两个任务不能同时发送,否则会导致数据错误。此时就可以用互斥信号量对串口资源进行保护,当任务1正在使用串口发送数据时,互斥信号量变为无效,任务2无法使用串口,任务2必须等待互斥信号量有效(任务1释放信号量),才能获得串口使用权,进而发送数据。

递归互斥信号量:递归互斥信号量是一种特殊的互斥信号量,支持拥有该信号量使用权的任务重复多次获取,而不会死锁。 任务成功获取几次递归互斥信号量,就要返还几次,在此之前,递归互斥信号量都处于无效状态。

优先级继承

作用:消除了优先级反转的问题

任务优先级修改的函数

//优先级的修改
//参数:1.句柄---NULL代表自身
//参数:2
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )

//获取任务的优先级
//参数:任务的句柄,NULL代表本任务
uxTaskPriorityGet(TaskHandle_t xTask)

实例:

void StartDefaultTask(void const * argument)//低优先级
{
  for(;;)
  {
	  printf("this is Task01 %ld\r\n",uxTaskPriorityGet(NULL));
	  printf("将任务恢复到1\r\n");
	  vTaskPrioritySet(NULL,1);
    osDelay(1);
  }
}

void StartTask02(void const * argument)//高优先级
{
  for(;;)
  {
	  printf("this is Task02 Task01的优先级%ld\r\n",uxTaskPriorityGet(myTask01Handle));
	  printf("将任务提升到到4\r\n");
	  vTaskPrioritySet(myTask01Handle,4);  
    osDelay(1);
  }
}

互斥信号量的实验验证:

此实验选自嵌入式实时操作系统——基于STM32Cube、FreeRTOS和Tracealyzer的应用开-》发课本

创建三个不同优先级的任务,Task01/Task02/Task03,其中Task03的优先级最高,其次是Task02,最小的优先级的是Task01

采用串口输出的方式来进行互斥信号量的检测,串口抢占,属于公共的资源的抢占,也可以使用全局变量来使用(不建议),会导致程序运行不正常,

此实验就会出现高优先级的会等待低优先级的运行完毕

实验代码:

void Task01(void const * argument)
{
  for(;;)
  {
	  if(xSemaphoreTake(CirtcalResourceMutexHandle,portMAX_DELAY) == pdTRUE)
		  printf("TASK01 is ring……\r\n");
	  xSemaphoreGive(CirtcalResourceMutexHandle);
	  osDelay(500);
    osDelay(1);
  }
}

void Task02(void const * argument)
{
  for(;;)
  {
    printf("TASK02 is ring……\r\n");
    osDelay(500);
    osDelay(1);
  }
}

void Task03(void const * argument)
{
  for(;;)
  {
	  if(xSemaphoreTake(CirtcalResourceMutexHandle,portMAX_DELAY) == pdTRUE)
		  printf("TASK03 is ring……\r\n");
	  osDelay(3000);
	  xSemaphoreGive(CirtcalResourceMutexHandle);
	  osDelay(500);
    osDelay(1);
  }
}

结果:TASK03 is ring……                     3s后
        TASK02 is ring……                    持续执行直到Task03解除阻塞
        TASK01 is ring……                    500ms后

五、任务挂起和恢复

//挂起函数
//参数:xTaskToSuspend是任务的句柄
// NULL代表自己本任务
vTaskSuspend(TaskHandle_t xTaskToSuspend);


//恢复函数
//参数:xTaskResume是任务的句柄
//NULL代表自己本任务
vTaskResume(TaskHandle_t xTaskToSuspend);

六、直达任务通知

最为使用,且比较难理解的

作用:实现任务之间的通信和同步
直达任务就是直接发送至任务的事件,而不是通过中间对象(信号量、列等)发送到人物的事件
优势:具有高灵活性,使用时无需创建,直接使用。通过直接通知解除RTOS任务的阻塞状态,占
用的RAM内存更小

操作流程

函数

描述

xTaskNotify()

发送通知,带有通知值并且不保留接收任务原通知值,用在任务中。

xTaskNotifyFromISR()

发送通知,函数 xTaskNotify()的中断版本。

xTaskNotifyWait()

接收通知

xTaskNotifyGive()

发送通知,函数 xTaskNotifyGive()的中断版本。

vTaskNotifyGiveFromISR()

发送通知,带有通知值并且保留接收任务的原通知值,用在任务中。

ulTaskNotifyTake()

接收通知

以下使用这两种方式更加全面:

xTaskNotify()函数功能更强大,可以使用不同参数实现各类功能,兼xTaskNotifyGive(),也更好用。
ulTaskNotifyTake()更灵活、强大,使用上也就更复杂,兼容xTaskNotifyWait(),也更好用。

//RTOS任务通知的API函数
//uxIndexToWaitOn:索引的值,一般在索引0处等待通知,这个函数不存在
//ulBitsToClearOnEntry:将清除任务通知值中的所有位,有效将值清除为0
//ulBitsToClearOnExit:设置的任何位都将在调用 RTOS 任务通知值中被清除,前提是接收到通知
//pulNotificationValue:RTOS 任务的通知值,不需要时设置为NULL
//xTicksToWait:在阻塞状态下等待接收通知的最长时间不会消耗任何 CPU 时间,时间以RTOS tick周期为单位,宏 pdMS_TO_TICKS() 可以 将以毫秒为单位的时间 转换为以 tick 为单位的时间。

BaseType_t  xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                             uint32_t ulBitsToClearOnExit,
                             uint32_t *pulNotificationValue,
                             TickType_t xTicksToWait );
                             
BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn, 
                                    uint32_t ulBitsToClearOnEntry, 
                                    uint32_t ulBitsToClearOnExit, 
                                    uint32_t *pulNotificationValue, 
                                    TickType_t xTicksToWait );
//函数-->直达任务通知是直接发送至任务的事件, 可以取消阻塞接收任务,并 可以选择通过多种方式更新接收任务的某个通知值。
//uxIndexToWaitOn:调用任务的数组中通知值的索引,调用任务将在该通知值上等待通知 变成非零。xTaskNotifyTake() 没有此参数,总是在索引 0 处等待通知。
//xClearCountOnExit:如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdFALSE,则 RTOS 任务的通知值 在 ulTaskNotifyTake() 退出之前递减。 这 等同于 通过成功调用 xSemaphoreTake() 而递减计数信号量的值。如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdTRUE,则 RTOS 任务的通知值 在 ulTaskNotifyTake() 退出之前重设为 0。 这 等同于在成功调用 xSemaphoreTake() 后 将二进制信号量的值保留为 0 (或为空,或为“不可用” )。
//xTicksToWait:在阻塞状态下等待接收通知的最长时间, 如果通ulTaskNotifyTake() 被调用时 尚未挂起。处于阻塞状态的 RTOS 任务不会消耗 任何 CPU 时间。而时间以 RTOS tick 周期为单位。 宏 pdMS_TO_TICKS() 可以 将以毫秒为单位的时间 转换为以 tick 为单位的时间。

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                            TickType_t xTicksToWait );
 
 uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn, 
                                   BaseType_t xClearCountOnExit, 
                                   TickType_t xTicksToWait );
                                   
唯一的区别 是 ulTaskNotifyTakeIndexed() 可以在数组内的任何任务通知上运行, 而 ulTaskNotifyTake() 始终在数组索引 0 处的任务通知上运行。

注意:数组中每个通知都独立运行——在数组中的一个通知上一次只能阻塞一个任务,并且该任务不会被发送到任何其他数组索引处的通知取消阻塞。

方法一(二进制信号量)

不接收二进制信号量
实例:

void StartDefaultTask(void const * argument)
{
	int cnot_ok = 0,notify_val = 0,ulValue = 0;
	BaseType_t xResult;
  for(;;)
  {
	  xResult = xTaskNotifyWait(0x00,0x00,(uint32_t*)&ulValue,portMAX_DELAY);
		if(xResult == pdPASS)
			printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,ulValue);
    osDelay(1);
  }
}

void StartTask02(void const * argument)
{
	 int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
   const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);//2s    
  for(;;)
  {
        printf("xTaskNotifyGiv %d time: ok\r\n", cnot_ok++);    
        vTaskDelay(xTicksToWait);
    osDelay(1);
  }
}


//结果:
xTaskNotifyGiv 0 time: ok
xTaskNotifyGiv 1 time: ok
xTaskNotifyGiv 2 time: ok
xTaskNotifyGiv 3 time: ok
xTaskNotifyGiv 4 time: ok

结果的分析:
        StartDefaultTask这个任务没有接收到StartTask02的二进制信号

接收二进制信号量

void StartDefaultTask(void const * argument)
{
	int cnot_ok = 0,notify_val = 0,ulValue = 0;
	BaseType_t xResult;
  for(;;)
  {
	  xResult = xTaskNotifyWait(0x00,0x00,(uint32_t*)&ulValue,portMAX_DELAY);
		if(xResult == pdPASS)
			printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,ulValue);
    osDelay(1);
  }
}

void StartTask02(void const * argument)
{
	 int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
   const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);//2s    
  for(;;)
  {
     if(xTaskNotify( myTask01Handle, (uint32_t)0x00, eNoAction ) == pdPASS)//eNoAction 枚举

        printf("xTaskNotifyGiv %d time: ok\r\n", cnot_ok++);    
        vTaskDelay(xTicksToWait);
    osDelay(1);
  }
}

//实验结果:
xTaskNotifyGiv 0 time: ok
xTaskNotifyWait OK:0, data:0
xTaskNotifyGiv 1 time: ok
xTaskNotifyWait OK:1, data:0
xTaskNotifyGiv 2 time: ok
xTaskNotifyWait OK:2, data:0
结果分析:

                StartDefaultTask这个任务可以接收到StartTask02的二进制信号

方法二(位信号量)

简单解释:通知的值 | ulValue,按位或

实例展示:

void StartDefaultTask(void const * argument)
{
	int cnot_ok = 0,notify_val = 0,ulValue = 0;
	BaseType_t xResult;
  for(;;)
  {
	  xResult = xTaskNotifyWait(0x00,0x00,(uint32_t*)&ulValue,portMAX_DELAY);
		if(xResult == pdPASS)
			printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
    osDelay(1);
  }
}

void StartTask02(void const * argument)
{
	 int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
	 char c;
   const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);    
  for(;;)
  {
		for(i = 0;i < 3;i++)
		{
			c = cnot_tx;
			cnot_tx++;
			if(xTaskNotify( myTask01Handle, (uint32_t)c, eSetBits ) == pdPASS)
				printf("xTaskNotifyGiv %d time:ok,val:%d\r\n", cnot_ok++,c);  
			else
				printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
		}
    vTaskDelay(xTicksToWait);
		
    osDelay(1);
  }
}

结果:
xTaskNotifyGiv 0 time:ok,val:0
xTaskNotifyGiv 1 time:ok,val:1
xTaskNotifyGiv 2 time:ok,val:2
xTaskNotifyWait OK:0, data:3
xTaskNotifyGiv 3 time:ok,val:3
xTaskNotifyGiv 4 time:ok,val:4
xTaskNotifyGiv 5 time:ok,val:5
xTaskNotifyWait OK:1, data:7

结果分析:xTaskNotifyWait(形参1, 形参2, &形参3, portMAX_DELAY);形参1,形参2设置为0,不管有没有用减少影响形参3(ulValue)是获取的值,形参4:是否使用阻塞态
本次实例使用的为8位

接收ulValue值的变化

方式三(计数信号量)

说明:通知值 = 原来的值 + 1
实例展示:

void StartDefaultTask(void const * argument)
{
	int cnot_ok = 0;
	uint32_t ulValue = 0;
	BaseType_t xResult;
  for(;;)
  {
	  xResult = xTaskNotifyWait(0x00,0x00,&ulValue,portMAX_DELAY);
		if(xResult == pdPASS)
			printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
    osDelay(1);
  }
}

void StartTask02(void const * argument)
{
	 int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
	 char c;
   const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);    
  for(;;)
  {
		for(i = 0;i < 3;i++)
		{
			c = cnot_tx;
			cnot_tx++;
			if(xTaskNotify( myTask01Handle, (uint32_t)c, eIncrement ) == pdPASS)
				printf("xTaskNotifyGiv %d time:ok,val:%d\r\n", cnot_ok++,c);  
			else
				printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
		}
    vTaskDelay(xTicksToWait);
		
    osDelay(1);
  }
}

//结果
xTaskNotifyGiv 0 time:ok,val:0
xTaskNotifyGiv 1 time:ok,val:1
xTaskNotifyGiv 2 time:ok,val:2
xTaskNotifyWait OK:0, data:3
xTaskNotifyGiv 3 time:ok,val:3
xTaskNotifyGiv 4 time:ok,val:4
xTaskNotifyGiv 5 time:ok,val:5
xTaskNotifyWait OK:1, data:6
xTaskNotifyGiv 6 time:ok,val:6
xTaskNotifyGiv 7 time:ok,val:7
xTaskNotifyGiv 8 time:ok,val:8
xTaskNotifyWait OK:2, data:9

实例结果分析:
xTaskNotifyWait(参数1,参数2,参数3,portMAX_DELAY参数1和参数2必须设置为0,否则的话会影响到后边的计数问题。参数3是实际的计数值,参数4表示是否为阻塞态

方式四(不覆盖信号量)

解释说明:如果数据表示“有数据未读”,则此次嗲用xTaskNotify不做任何事情,并且返回pdFAIL,如果通知状态不是"pending"则表示没有新数据。

实例如下:
 

void StartDefaultTask(void const * argument)
{
	int cnot_ok = 0;
	uint32_t ulValue = 0;
	BaseType_t xResult;
  for(;;)
  {
	  xResult = xTaskNotifyWait(0x00,0x00,&ulValue,portMAX_DELAY);
		if(xResult == pdPASS)
			printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
    osDelay(1);
  }
}

void StartTask02(void const * argument)
{
	 int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
	 char c;
   const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);    
  for(;;)
  {
		for(i = 0;i < 3;i++)
		{
			c = 'a'+cnot_tx;
			cnot_tx++;
			if(xTaskNotify( myTask01Handle, (uint32_t)c, eSetValueWithoutOverwrite ) == pdPASS)
				printf("xTaskNotifyGiv %d time:ok,val:%c\r\n", cnot_ok++,c);  
			else
				printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
		}
    vTaskDelay(xTicksToWait);
		
    osDelay(1);
  }
}

结果
xTaskNotifyGiv 0 time:ok,val:a
xTaskNotifyGiv 0 time:err
xTaskNotifyGiv 1 time:err
xTaskNotifyWait OK:0, data:97   ----> a的ASII码值
xTaskNotifyGiv 1 time:ok,val:d
xTaskNotifyGiv 2 time:err
xTaskNotifyGiv 3 time:err
xTaskNotifyWait OK:1, data:100
xTaskNotifyGiv 2 time:ok,val:g
xTaskNotifyGiv 4 time:err
xTaskNotifyGiv 5 time:err
xTaskNotifyWait OK:2, data:103

结果分析:
任务二中循环3次发送任务通知,只有第一次有效,第二第三次没有获取当中的值,发送任务失败,而任务1只接受了一次数值。
xTaskNotifyWait(),中的参数1,参数2设置为0,减少影响。参数3是获取的值,参数4是否使用阻塞态

方式五(覆盖信号量)

说明解释:相当于覆盖,无论如何,不管通知状态是否“表示数据未读”,通知值为&ulValue

实例展示:

void StartDefaultTask(void const * argument)
{
	int cnot_ok = 0;
	uint32_t ulValue = 0;
	BaseType_t xResult;
  for(;;)
  {
	  xResult = xTaskNotifyWait(0x00,0x00,&ulValue,portMAX_DELAY);
		if(xResult == pdPASS)
			printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
    osDelay(1);
  }
}

void StartTask02(void const * argument)
{
	 int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
	 char c;
   const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);    
  for(;;)
  {
		for(i = 0;i < 3;i++)
		{
			c = 'a'+cnot_tx;
			cnot_tx++;
			if(xTaskNotify( myTask01Handle, (uint32_t)c, eSetValueWithOverwrite ) == pdPASS)
				printf("xTaskNotifyGiv %d time:ok,val:%c\r\n", cnot_ok++,c);  
			else
				printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
		}
    vTaskDelay(xTicksToWait);
    osDelay(1);
  }
}

结果:
xTaskNotifyGiv 0 time:ok,val:a
xTaskNotifyGiv 1 time:ok,val:b
xTaskNotifyGiv 2 time:ok,val:c
xTaskNotifyWait OK:0, data:99=c
xTaskNotifyGiv 3 time:ok,val:d
xTaskNotifyGiv 4 time:ok,val:e
xTaskNotifyGiv 5 time:ok,val:f
xTaskNotifyWait OK:1, data:102=f

七、任务控制

延时函数(vTaskDelay & vTaskDelayUntil & xTaskDelayUntil & xTaskAbortDelay)

函数解析:

函数:vTaskDelay
相对延时---->延时的ms
 INCLUDE_vTaskDelay 定义为 1才可以使用
void vTaskDelay( const TickType_t xTicksToDelay );

函数:vTaskDelayUntil
绝对延时---->定时器
INCLUDE_vTaskDelayUntil 定义为1才可以使用
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );

函数:xTaskDelayUntil
参数1:保存最后一次时间解除的时间
参数2:周期时间段,固定的间隔时间
返回:
一个可用于检查任务是否实际延迟的值: 如果任务延迟,则返回 pdTRUE,否则返回 pdFALSE。 如果下一个预计唤醒时间已过,则任务将不会延迟。
pdMS_TO_TICKS()用来计算毫秒级别的节拍数
INCLUDE_xTaskDelayUntil 必须定义为 1
BaseType_t xTaskDelayUntil( TickType_t *pxPreviousWakeTime,
                            const TickType_t xTimeIncrement );
                            
函数:xTaskAbortDelay(TaskHandle_t xTask)
参数:任务的句柄
返回值:如果引用的任务不在阻塞状态则返回pdFAIL,否则为pdPASS
BaseType_t xTaskAbortDelay( TaskHandle_t xTask );

函数分析:

  1. vTaskDelay()延时函数是相对延时函数,会使任务进入阻塞状态,暂时释放处理器资源,并在指定的时间后重新进行调度执行,而这个时间是相对于当前的时间,举例:如果vTaskDelay(1000);任务将在当前时间的基础上延时1000个节拍。
  2. vTaskDelayUntil()延时函数是绝对延时延时函数,会使任务进入阻塞状态,并在绝对的时间点后重新被调度执行,此时间是相对于系统的时间,例子:如果调度此函vTaskDelayUntil(&LastWakeTiime,pdMS_TO_TICKS(1000));任务将在LastWakeTiime记录的时间点上延时10000ms。

delay和vTaskDelay的区别:
阻塞方式:vTaskDelay()函数是通过阻塞当前任务的方式来实现延时。它会使当前任务暂时释放处理器资源,并在延时时间结束后重新被调度执行。而传统的delay延时函数是通过循环等待的方式来实现延时,它会占用处理器资源,并在延时时间结束后继续执行后面的代码。
精确性:vTaskDelay()函数是基于FreeRTOS的任务调度器来实现的,可以提供相对较高的延时精确性。它可以在任务级别进行精确的延时,不受其他任务的影响。而传统的delay延时函数通常是基于处理器的时钟周期进行延时,受到处理器速度和其他任务的影响,精确性较差。
多任务处理:vTaskDelay()函数是FreeRTOS多任务操作系统的一部分,可以与其他任务共享处理器资源,并能够在多个任务之间进行切换。而传统的delay延时函数通常是在单任务环境下使用,不适用于多任务操作系统。
总的来说就是任务调度器和时钟节拍的机制

实例展示:

/* 任务一,演示相对延时函数 */
void task1( void * pvParameters )
{
    while(1)
    {
        LED0_TOGGLE();      /* PB1 */
        delay_ms(20);
        vTaskDelay(500);
    }
}

/* 任务二,演示绝对延时函数 */
void task2( void * pvParameters )
{
    TickType_t xLastWakeTime;
    xLastWakeTime = xTaskGetTickCount();
    while(1)
    {
        LED1_TOGGLE();      /* PB0 */
        delay_ms(20);
        vTaskDelayUntil(&xLastWakeTime,500);
    }
}

结果分析:任务一是相对延时函数vTaskDelay(),是相对于当前时间的延时,而任务二是绝对的延时函数vTaskDelayUntil(),是相对于整个系统时间的延时函数。

vTaskDelayUntil和xTaskDelayUntil的区别
xTaskDelayUntil具有返回值,用来判断是否执行了延时函数返回pdTRUE & pdFALSE,vTaskDelayUntil没有任何的返回值

八、获取任务的优先级

参数:任务的句柄,NULL表示本任务的句柄
返回值:任务的优先级
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask)

可以在中断函数中使用此函数获取任务的优先级
参数:任务的句柄,NULL表示会返回调用函数的任务的优先级
返回值:任务的优先级
UBaseType_t uxTaskPriorityGetFromISR( const TaskHandle_t xTask );

实例展示:

uxTaskPriorityGet(myTask01Handle);
	  uxTaskPriorityGet(NULL);
		printf("task01:%d task02:%d\r\n",(int)uxTaskPriorityGet(myTask01Handle),(int)uxTaskPriorityGet(NULL));
		if(uxTaskPriorityGet(NULL) > uxTaskPriorityGet(myTask01Handle))
			printf("task02 is height\r\n");

结果分析:返回了任务的优先级

九、消息队列(重点)

作用实现任务间以及中断的通信通常来说他们作为安全缓存区使用FIFO缓存区使用新数据被发送到队列后边尽管数据也可以发送到前面

1.创建消息队列

queue.h
configSUPPORT_DYNAMIC_ALLOCATION在FreeRTOSConfig.h 中被设置成1
 
动态创建消息队列:
uxQueueLength:队列可同时容纳的最大项目数
uxItemSize:存储队列中的每个数据项所需要的大小(数据项按副本排队,而不是按引用排队, 因此该值为每个排队项目将被复制的字节数。队列中每个数据项 必须大小相同)
返回值:成功获得,使用此队列的句柄,如果创建队列所需要的内存无法分配,则返回NULL
QueueHandle_t  xQueueCreate(UBaseType_t uxQueueLength,
                                 UBaseType_t uxItemSize);    

2.发送消息

发送消息:
xQueue:要发送到消息队列的句柄
pvItemToQueue:指向待入队数据项的指针(创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域)
 xTicksToWait:等待时间      
BaseType_t xQueueSend(QueueHandle_t xQueue,
                          const void * pvItemToQueue,
                          TickType_t xTicksToWait);

3.接收消息

接收消息队列:
xQueue:要发送到消息队列的句柄
pvBuffer:指向缓存区的地址
xTicksToWait:等待时间  
BaseType_t xQueueReceive(QueueHandle_t xQueue,
                               void *pvBuffer,
                               TickType_t xTicksToWait); 

4.删除消息队列

删除队列:
xQueue:要删除队列的句柄
void vQueueDelete( QueueHandle_t xQueue );

代码展示实例:

void Task1(void *pvParameters )
{
	uint32_t dataReceived;
  for(;;)
  {
	  if(xQueueReceive(xQueueHandle1, &dataReceived, portMAX_DELAY)) 
		{
					printf("%d\n",dataReceived);
		}
		vTaskSuspend(NULL);
	}		
}

void Task2(void *pvParameters )
{
	uint32_t toDataSend = 100;
	xQueueHandle1 = xQueueCreate(QUEUE_LENGTH,ITEM_SIZE);
	xQueueSend(xQueueHandle1,&toDataSend,portMAX_DELAY);
	for(;;)
	{
		printf("Task3\r\n");
		num1++;
		vTaskSuspend(NULL);
	}
}

结果:

看到这里你就可以进行进行FreeRTOS基本使用

祝你们越用越顺手。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值