一、消息队列

消息队列是一种常用于任务间通信的数据结构, 队列可以在任务与任务间、中断和任务间传递信息。读写队列均支持超时机制。

1、创建队列

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,//队列长度
							UBaseType_t uxItemSize );//队列中消息单元的大小,以字节为单位

2、删除队列

vQueueDelete()

3、队列发送

BaseType_t xQueueSend(QueueHandle_t xQueue,//队列句柄
				const void * pvItemToQueue,//指针,指向要发送到队列尾部的队列消息
				TickType_t xTicksToWait);//等待时间

4、在中断服务程序中使用的发送函数

BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
							const void *pvItemToQueue,
						    BaseType_t *pxHigherPriorityTaskWoken//为一个可选参数, 可以设置为 NULL。
						    );

5、向队列首发送消息

BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
						const void * pvItemToQueue,
							TickType_t xTicksToWait );

6、用于在中断服务程序中向消息队列队首发送一个消息

BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
							const void *pvItemToQueue,
				BaseType_t *pxHigherPriorityTaskWoken);

7、接收队列
从一个队列中接收消息并把消息从队列中删除

BaseType_t xQueueReceive(QueueHandle_t xQueue,
							void *pvBuffer,
					TickType_t xTicksToWait);

8、在中断中接收

xQueueReceiveFromISR()

9、 消息队列应用实例

QueueHandle_t Test_Queue =NULL;
#define  QUEUE_LEN    4   /*消息队列长度*/
#define  QUEUE_SIZE   4   /*每个消息大小 */

/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;
taskENTER_CRITICAL();     //进入临界区
  /* 创建Test_Queue */
  Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,(UBaseType_t ) QUEUE_SIZE);
  
 创建任务一:Send_Task
 创建任务二:Receive_Task

  vTaskDelete(AppTaskCreate_Handle); 
  taskEXIT_CRITICAL();  
}

/
  
//任务一:发送
static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;
  uint32_t send_data1 = 1;
  uint32_t send_data2 = 2;
  while (1)
  {
      xReturn = xQueueSend( Test_Queue,&send_data1,0 );       
      if(pdPASS == xReturn)
        printf("send_data1 发送成功!\n\n");
    vTaskDelay(20);
  }
}

//任务二:接收
static void Receive_Task(void* parameter)
{	
  BaseType_t xReturn = 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);
  }
}                             

二、信号量

实现任务之间同步或临界资源的互斥访问

1、创建二值信号量

 xSemaphoreCreateBinary()

2、创建计数信号量

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,//计数信号量的最大值
 UBaseType_t uxInitialCount//创建计数信号量的初始值
 );

3、信号量删除函数

vSemaphoreDelete()

4、信号量释放函数

释放的信号量对象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量。此外该函数不能在中断中使用

xSemaphoreGive( xSemaphore ) 

5、中断中释放信号量
用于释放一个信号量,带中断保护。它不能释放互斥量,这是因为互斥量
不可以在中断中使用。

xSemaphoreGiveFromISR()

6、信号量获取
获取一个信号量,可以是二值信号量、计数信号量、互斥量

xSemaphoreTake( xSemaphore, xBlockTime )

参数: xSemaphore:信号量句柄;xBlockTime:等待信号量可用的最大超时时间。

7、不带阻塞机制获取信号量的函数
不能用于互斥量

xSemaphoreTakeFromISR()

8、信号量应用实例

创建两个任务,一个是获取信号量任务,一个是释放互斥量任务

获取信号量任务是一直在等待信号量,其等待时间是 portMAX_DELAY,等到获取到信号量之后,任务开始执行任务代码。

SemaphoreHandle_t BinarySem_Handle =NULL;

static void Receive_Task(void* parameter)
{	
  BaseType_t xReturn = pdPASS;
  while (1) {
  	//获取二值信号量,没有获取则一直等待
	xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY); 
    if(pdTRUE == xReturn)
      printf("BinarySem_Handle  get successful |!\n\n");
  }
}


static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      xReturn = xSemaphoreGive( BinarySem_Handle );//给出信号量
      if( xReturn == pdTRUE )
        printf("BinarySem_Handle  释放成功\r\n");
      else
        printf("BinarySem_Handle  释放失败\r\n");
    } 
    vTaskDelay(20);
  }
}

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;
  taskENTER_CRITICAL(); 
  
  /* 创建二值信号量*/
  BinarySem_Handle = xSemaphoreCreateBinary();	 
  if(NULL != BinarySem_Handle)
    printf("BinarySem_Handle create successful!\r\n");

	创建任务一:Receive_Task
	创建任务二:Send_Task
  
  vTaskDelete(AppTaskCreate_Handle); 
  taskEXIT_CRITICAL();  
}

三、互斥量

它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理,是用于保护资源的互锁。不能用于中断函数中

与信号量区别
二值信号量:用于实现同步(任务之间或者任务与中断之间)

应用场景
可能会引起优先级翻转的情况

递归互斥量更适用于:
任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造成死锁的问题。

1、互斥量创建 ,只能被同一个任务获取一次
xSemaphoreCreateMutex()

2、递归互斥量创建, 它可以被同一个任务获取很多次,获取多少次就需要释放多少次
xSemaphoreCreateRecursiveMutex()

3、互斥量删除
vSemaphoreDelete()

4、互斥量获取
xSemaphoreTake()

5、递归互斥量获取函
xSemaphoreTakeRecursive( xMutex, xBlockTime )

6、互斥量释放
xSemaphoreGive()

7、递归互斥量释放
xSemaphoreGiveRecursive()

8、互斥量应用实例
实验验证了在低优先级任务运行的时候,中优先级任务无法抢占低优先级的任务。

SemaphoreHandle_t MuxSem_Handle =NULL;

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/
  
  taskENTER_CRITICAL();  
  /* MuxSem */
  MuxSem_Handle = xSemaphoreCreateMutex();	 
  if(NULL != MuxSem_Handle)
    printf("MuxSem_Handle 创建成功¦!\r\n");

  xReturn = xSemaphoreGive( MuxSem_Handle );//给出信号量

	创建任务一:LowPriority_Task
	创建任务二:MidPriority_Task
	创建任务三:HighPriority_Task

  vTaskDelete(AppTaskCreate_Handle); 
  
  taskEXIT_CRITICAL(); 
}


static void LowPriority_Task(void* parameter)
{	
  static uint32_t i;
  BaseType_t xReturn = pdPASS;
  while (1)
  {
  	//获取信号量,没有则一直等待
  	printf("LowPriority_Task获取互斥量\n");
	xReturn = xSemaphoreTake(MuxSem_Handle,
                              portMAX_DELAY); 
    if(pdTRUE == xReturn)
  	  printf("LowPriority_Task Running\n\n");
    
    for(i=0;i<2000000;i++)//占用低级任务互斥量
	{
			taskYIELD();//调度
	}
    
    printf("LowPriority_Task释放信号量!\r\n");
    xReturn = xSemaphoreGive( MuxSem_Handle );//释放信号量

    vTaskDelay(1000);
  }
}

static void MidPriority_Task(void* parameter)
{	 
  while (1)
  {
   printf("MidPriority_Task Running\n");
   vTaskDelay(1000);
  }
}

static void HighPriority_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;
  while (1)
  {
	xReturn = xSemaphoreTake(MuxSem_Handle,
                              portMAX_DELAY); 
    printf("HighPriority_Task  获取互斥量!\r\n");                          
    if(pdTRUE == xReturn)
      printf("HighPriority_Task Running\n");

    printf("HighPriority_Task 释放互斥量!\r\n");
    xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
    vTaskDelay(1000);
  }
}

输出打印

HighPriority_Task 获取互斥量
HighPriority_Task Running
HighPriority_Task 释放互斥量!

MidPriority_Task Running

LowPriority_Task 获取互斥量
LowPriority_Task Running
LowPriority_Task 释放互斥量!

HighPriority_Task 获取互斥量

四、事件

可以用事件来做标志位,判断某些事件是否发生了。

1、事件创建函数 xEventGroupCreate()

2、事件删除函数 vEventGroupDelete()

3、事件组置位函数 xEventGroupSetBits()(任务)

4、事件组置位函数 xEventGroupSetBitsFromISR()(中断)

5、等待事件函数 xEventGroupWaitBits()

6、xEventGroupClearBits()与 xEventGroupClearBitsFromISR()

等待事件函数

EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
									 const EventBits_t uxBitsToWaitFor,//按位或的值
									 const BaseType_t xClearOnExit,//pdRTUE 系统将清除由形参 uxBitsToWaitFor 指定的事件标志位
									 const BaseType_t xWaitForAllBits,//pdFALSE:uxBitsToWaitFor 指定的位有其中任意一个置位;pdTRUE  uxBitsToWaitFor 指定的位都置位的时候 
									 TickType_t xTicksToWait );//等待时间

设置函数

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
								 const EventBits_t uxBitsToSet );//事件的标志位

7、 事件应用实例
创建两个任务,一个是设置事件任务,一个是等待事件任务

设置事件任务 通过检测按键的按下情况设置不同的事件标志位,
等待事件任务 则获取这两个事件标志位,并且判断两个事件是否都发生,若是则输出相应信息

static EventGroupHandle_t Event_Handle =NULL;

#define KEY1_EVENT  (0x01 << 0) 
#define KEY2_EVENT  (0x01 << 1) 

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;
  taskENTER_CRITICAL(); 
  
  /* 创建事件 Event_Handle */
  Event_Handle = xEventGroupCreate();	 
  if(NULL != Event_Handle)
    printf("Event_Handle创建成功\r\n");
    
	创建任务一:LED_Task
	创建任务二:KEY_Task

  vTaskDelete(AppTaskCreate_Handle); 
  taskEXIT_CRITICAL(); 
}

static void LED_Task(void* parameter)
{	
    EventBits_t r_event;
    while (1)
	{
    r_event = xEventGroupWaitBits(Event_Handle, 
                                  KEY1_EVENT|KEY2_EVENT,//事件
                                  pdTRUE,pdTRUE,  portMAX_DELAY);
                        
    if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) 
    {
      printf ( "收到事件\n");		
    }
    else
      printf ( "事件 错误\n");	
  }
}

static void KEY_Task(void* parameter)
{	 
  while (1)
  {
       if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )  {
			xEventGroupSetBits(Event_Handle,KEY1_EVENT); //触发事件一 					
		}
   
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )  {
			xEventGroupSetBits(Event_Handle,KEY2_EVENT); 触发事件二				
		}
		vTaskDelay(20); 
  }
}

五、通知

每个任务都有一个 32 位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列;任务通知的使用无需创建队列;

发送任务通知函数 
xTaskGenericNotify()

获取任务通知 
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, //任务句柄
                                      uint32_t ulValue, //值
                                      eNotifyAction eAction ); 
     eNotifyAction 的取值                                 
       * eNoAction = 0//通知任务而不更新其通知值
       * eSetBits//设置任务通知值中的值
       * eIncrement//增加任务的通道值
       * eSetvaluewithoverwrite//覆盖当前通知
       * eSetValueWithoutoverwrite//不覆盖当前通知

等待任务通知
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,//
							uint32_t ulBitsToClearOnExit,
							uint32_t *pulNotificationValue,//值
							TickType_t xTicksToWait );//事件

1、 通知应用实例(代替消息队列)

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;
  taskENTER_CRITICAL(); 

  创建任务一: Receive1_Task
  创建任务二 :Send_Task
  
  vTaskDelete(AppTaskCreate_Handle);
  taskEXIT_CRITICAL();  
}

//接收任务
static void Receive1_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;
  uint32_t r_num;
  xReturn=xTaskNotifyWait(0x0,			//进入函数的时候不清除任务bit
                            ULONG_MAX,	  //退出函数的时候清除所有bit
                            (uint32_t *)&r_char,		  //任务通知值
                            portMAX_DELAY);	//阻塞事件
    if( pdTRUE == xReturn )
      printf("Receive1_Task 任务通知消息 %d \n",r_num);                      
}
//发送任务
static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;
  uint32_t send1 = 1;
  
  while (1)
  {

    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {

      xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
                             (uint32_t)&test_str1, /*任务内容 */
                             eSetValueWithOverwrite );/*覆盖当前通知*/
      
      if( xReturn == pdPASS )
        printf("Receive1_Task_Handle ÈÎÎñ֪ͨÏûÏ¢·¢Ëͳɹ¦!\r\n");
    } 
    vTaskDelay(20);
  }
}

2、通知应用实例(代替信号量)

static void Receive1_Task(void* parameter)
{	
  while (1)
  {
  	//获取任务通知,没有则一直等待
    ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
    printf("Receive1_Task !\n\n");
  }
}

static void Send_Task(void* parameter)
{	 
  BaseType_t xReturn = pdPASS;
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);//任务句柄
      if( xReturn == pdTRUE )
        printf("Receive1_Task_Handle ÈÎÎñ֪ͨ·¢Ëͳɹ¦!\r\n");
    } 
    vTaskDelay(20);
  }
}