一张纸了解freertos的使用(2)

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, 
TickType_t const xTicksToWait );

队列集

要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得 到数据后再分别转换为游戏的控制键。

InputTask 如何及时读取到多个队列的数据?要使用队列集。

队列集的本质也是队列,只不过里面存放的是“队列句柄”。

使用过程如下:

a. 创建队列A,它的长度是n1

b. 创建队列B,它的长度是n2

c. 创建队列集S,它的长度是“n1+n2”

d. 把队列A、B加入队列集S

e. 这样,写队列A的时候,会顺便把队列A的句柄写入队列集S

f. 这样,写队列B的时候,会顺便把队列B的句柄写入队列集S

g. InputTask 先读取队列集 S,它的返回值是一个队列句柄,这样就可以知道哪个队列有 有数据了;然后InputTask再读取这个队列句柄得到数据。创建队列集

 创建队列集

函数原型如下:

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

把队列加入队列集

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, 
QueueSetHandle_t xQueueSet );

读取队列集

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, 
TickType_t const xTicksToWait );

示例: 

如何创建队列集和添加队列

static QueueHandle_t xQueueHandle1 = NULL;  /*队列1句柄*/
static QueueHandle_t xQueueHandle2 = NULL;  /*队列2句柄*/
static QueueSetHandle_t xQueueSet  = NULL;  /*队列集句柄*/
	
	/*实验说明:
	 *将两个队列添加到队列集中,任务一二使用队列发送信息
	 *任务三使用队列集进行监测信息*/
	
	/*队列集实验流程*/
	
	/* 1. 创建两个队列 */
	/* 2. 创建一个个队列集 */
	/* 3. 把队列添加到队列集中 */
	/* 4. 创建三个任务 */
	
	
	/*创建队列以及队列集合*/
	void creat_queue(void)
	{
		
		/*创建第一个队列*/
    xQueueHandle1=xQueueCreate(2,sizeof(int));/* 创建队列1 */
    (xQueueHandle1 == NULL)?  
     printf("error create queue1\r\n"):/*创建失败*/
     printf(" create queue1 ok! \r\n");/*创建成功*/
    
    /*创建第二个队列*/
    xQueueHandle2=xQueueCreate(2,sizeof(int));/* 创建队列2 */
    (xQueueHandle2 == NULL)?
     printf("error create queue2\r\n"):/*创建失败*/
     printf(" create queue2 ok! \r\n");/*创建成功*/
     
    /*创建队列集*/
     xQueueSet= xQueueCreateSet(4);            /* 创建队列集   参数:队列总长度   当前为:2+2 */
    (xQueueSet == NULL)?/*创建失败*/
     printf("error create queue set \r\n"):
     printf(" create queue set ok! \r\n");
		
		
		/* 3. 把2个queue添加进queue set */
	  xQueueAddToSet(xQueueHandle1, xQueueSet);
	  xQueueAddToSet(xQueueHandle2, xQueueSet);
    	
}
	

 读取队列集

static int value3;
QueueSetMemberHandle_t handle;
/*  读取队列集 选择 队列数据 */
handle = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);/*死等*/
/* 读取队列*/ 
xQueueReceive(handle, &value3, 0);
/* 将读取到的数据打印出来*/
printf("get data : %d\r\n", value3); 

 如果两个队列的内容不同怎么办?可以学习韦东山

static void InputTask(void *params)
{
	QueueSetMemberHandle_t xQueueHandle;
	
	while (1)
	{
		/* 读队列集, 得到有数据的队列句柄 */
		xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);
		
		if (xQueueHandle)
		{
			/* 读队列句柄得到数据,处理数据 */
			if (xQueueHandle == g_xQueueIR)
			{
				ProcessIRData();
			}
			else if (xQueueHandle == g_xQueueRotary)
			{
				ProcessRotaryData();
			}			
			else if (xQueueHandle == g_xQueueMPU6050)
			{
				ProcessMPU6050Data();
			}			
		}
	}
}

信号量

简单介绍

信号量,本质是用队列实现的。但是,我们刚学的队列是用来传递数据,但是信号量顾名思义是传递信号的。

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

 信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制 信号量;如果最大值不是1,它就是计数型信号量。

二进制信号量/计数型信号量

创建

二进制信号量
/* 创建一个二进制信号量,返回它的句柄。 
 * 此函数内部会分配信号量结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateBinary( void ); 
 
/* 创建一个二进制信号量,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的
指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuf
 fer ); 
 计数型信号
/* 创建一个计数型信号量,返回它的句柄。 
 * 此函数内部会分配信号量结构体  
 * uxMaxCount: 最大计数值 
 * uxInitialCount: 初始计数值 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t 
uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的
指针 
 * uxMaxCount: 最大计数值 
 * uxInitialCount: 初始计数值 
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,  
                                                 UBaseType_t uxInitialCount,  
                                                 StaticSemaphore_t *pxSemaphore
 Buffer ); 

删除

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。 vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下: 

/* 
 * xSemaphore: 信号量句柄,你要删除哪个信号量 
 */ 
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); 

give/take函数

二进制信号量、计数型信号量的give、take操作函数是一样的。这些函数也分为2个版 本:给任务使用,给ISR使用。列表如下:

 

 

示例: 

SemaphoreHandle_t BinarySemaphore;  //定义
BinarySemaphore=xSemaphoreCreateBinary();//创建动态二进制信号量
xSemaphoreGive(BinarySemaphore); //give



/*某个任务take信号量*/ 
void OLED_Test(void)
{
	int cnt = 0;
	
	 BaseType_t err = pdPASS;
   OLED_Init();
	// 清屏
	OLED_Clear();
    
	while (1)
	{
		
		err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY);
//		   if(err == pdPASS)
//			 {
//				 
//			 }
	
		// 在第0列第2页打印一个字符串"Hello World!"
		OLED_PrintString(0, 0, "Hello World!");
		OLED_PrintSignedVal(0, 2, cnt++);
		
	}
}


/*某个中断give信号量*/ 
//回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *nhrtc) //句柄需要取新名避免与hrtc冲突
{
	 xSemaphoreGiveFromISR(BinarySemaphore,NULL);
}

互斥信号量

互斥量是一种特殊的二进制信号量。

创建

使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。 创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

* 创建一个互斥量,返回它的句柄。 
 * 此函数内部会分配互斥量结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateMutex( void ); 
 
/* 创建一个互斥量,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的
指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

##define configUSE_MUTEXES 1

其他函数

要注意的是,互斥量不能在ISR中使用。 各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

注意

互斥量可以解决优先级翻转的问题(这个可以自己去了解)

问题:低优先级任务获得了锁,但是它优先级太低而无法运行。

互斥型号量怎么解决:低优先级任务获得了锁,但是它优先级太低而无法运行。

示例

/* 互斥量句柄 */ 
SemaphoreHandle_t xMutex;
 /* 创建互斥量 */ 
 xMutex = xSemaphoreCreateMutex( );

atic void vSenderTask( void *pvParameters ) 
{ 
 const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );  
 int cnt = 0; 
 int task = (int)pvParameters; 
 int i; 
 char c; 
  
 /* 无限循环 */ 
 for( ;; ) 
 {  
  /* 获得互斥量: 上锁 */ 
  xSemaphoreTake(xMutex, portMAX_DELAY); 
   
  printf("Task %d use UART count: %d, ", task, cnt++); 
  c = (task == 1 ) ? 'a' : 'A'; 
  for (i = 0; i < 26; i++) 
   printf("%c", c + i);
printf("\r\n"); 
/* 释放互斥量: 开锁 */ 
xSemaphoreGive(xMutex); 
vTaskDelay(xTicksToWait); 
} 

 递归锁

递归锁解决什么问题?

比如,自我死锁

怎么解决这类问题? 

/* 创建一个递归锁,返回它的句柄。 
 * 此函数内部会分配互斥量结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); 
 
 
/* 释放 */ 
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore ); 
 
/* 获得 */ 
BaseType_t xSemaphoreTakeRecursive( 
                   SemaphoreHandle_t xSemaphore, 
                   Ti

 

不建议使用

所以没有例子

事件组

事件组是啥?我就不说了,我只说怎么用.

创建

使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪 个事件组。 有两种创建方法:动态分配内存、静态分配内存。函数原型如下:

/* 创建一个事件组,返回它的句柄。 
 * 此函数内部会分配事件组结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
EventGroupHandle_t xEventGroupCreate( void ); 
 
/* 创建一个事件组,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的
指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer
 ); 

删除

 对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。 vEventGroupDelete可以用来删除事件组,函数原型如下:

/* 
 * xEventGroup: 事件组句柄,你要删除哪个事件组 
 */ 
void vEventGroupDelete( EventGroupHandle_t xEventGroup ) 

设置事件

可以设置事件组的某个位、某些位,使用的函数有2个:

⚫ 在任务中使用xEventGroupSetBits()

⚫ 在ISR中使用xEventGroupSetBitsFromISR()

有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤 醒。 函数原型如下: 

/* 设置事件组中的位 
 * xEventGroup: 哪个事件组 
 * uxBitsToSet: 设置哪些位?  
 *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为
1 
 *               可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0 
 * 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了) 
 */ 
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, 
                                    const EventBits_t uxBitsToSet ); 
 
 
/* 设置事件组中的位 
 * xEventGroup: 哪个事件组 
 * uxBitsToSet: 设置哪些位?  
 *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为
1 
 *               可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有, p
 dFALSE-没有 
 * 返回值: pdPASS-成功, pdFALSE-失败 
 */ 
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, 
           const EventBits_t uxBitsToSet, 
           BaseType_t * pxHigherPriorityTaskWoken ); 

值得注意的是,ISR中的函数,比如队列函数xQueueSendToBackFromISR、信号量函 数xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。

但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以 xEventGroupSetBitsFromISR函数不是直接去设置事件组,而是给一个FreeRTOS后台任务 (daemon task)发送队列数据,由这个任务来设置事件组。

如果后台任务的优先级比当前被中断的任务优先级高,xEventGroupSetBitsFromISR 会设置*pxHigherPriorityTaskWoken为pdTRUE。

如果daemon task成功地把队列数据发送给了后台任务,那么 xEventGroupSetBitsFromISR的返回值就是pdPASS。

等待事件

 使用xEventGroupWaitBits来等待事件,可以等待某一位、某些位中的任意一个, 也可以等待多位;等到期望的事件后,还可以清除某些位。

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, 
                                 const EventBits_t uxBitsToWaitFor, 
                                 const BaseType_t xClearOnExit, 
                                 const BaseType_t xWaitForAllBits, 
                                 TickType_t xTicksToWait );

 

示例:

这3个任务的代码和执行流程如下:

  • A:"炒菜任务"优先级最高,先执行。它要等待的2个事件未发生:洗菜、生火,进入阻塞状态
  • B:"生火任务"接着执行,它要等待的1个事件未发生:洗菜,进入阻塞状态
  • C:"洗菜任务"接着执行,它洗好菜,发出事件:洗菜,然后调用F等待"炒菜"事件
  • D:"生火任务"等待的事件满足了,从B处继续执行,开始生火、发出"生火"事件
  • E:"炒菜任务"等待的事件满足了,从A出继续执行,开始炒菜、发出"炒菜"事件
  • F:"洗菜任务"等待的事件满足了,退出F、继续执行C

要注意的是,代码B处等待到"洗菜任务"后并不清除该事件,如果清除的话会导致"炒菜任务"无法执行。

 

同步点

EventBits_t xEventGroupSync(    
EventGroupHandle_t xEventGroup, 
const EventBits_t uxBitsToSet, 
const EventBits_t uxBitsToWaitFor, 
TickType_t xTicksToWait );

示例

/* 表示我做好了, 还要等别人都做好 */
		xEventGroupSync(xEventGroup, COOKING, ALL, portMAX_DELAY);

  • 18
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值