求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
要资料
 
追随技术信仰

随时听讲座
每天看新闻
 
 
FreeRTOS源码分析及应用开发
1. FreeRTOS系列 | FreeRTOS简介
2. FreeRTOS系列 | 开发环境
3. FreeRTOS系列 | 任务基础知识
4. FreeRTOS系列 | 任务创建和删除
5. FreeRTOS系列 | 任务挂起和恢复
6. FreeRTOS系列|多任务调度
7. FreeRTOS系列|时间管理
8. FreeRTOS系列|中断管理和临界段
9. FreeRTOS系列|任务堆栈
10. FreeRTOS系列 | 处理器利用率
11. FreeRTOS系列|任务相关API函数
12. FreeRTOS系列 | 列表和列表项
13. FreeRTOS系列|消息队列一
14. FreeRTOS系列|消息队列二
15. FreeRTOS系列 | 二值信号量
16. FreeRTOS系列 | 互斥信号量
17. FreeRTOS系列 | 计数信号量
18. FreeRTOS系列 | 递归互斥信号量
19. FreeRTOS系列 | 事件标志组
20. FreeRTOS系列 | 软件定时器
21. FreeRTOS系列 | 低功耗管理
22. FreeRTOS系列 | 内存管理一
23. FreeRTOS系列 | 内存管理二
 

 
目录
FreeRTOS系列|事件标志组
作者:安迪西嵌入式
268 次浏览
8次  

事件标志组

1. 事件标志组介绍

信号量只能实现任务与单个事件或任务间的同步。但是某些任务可能会需要与多个事件或任务进行同步,此时就可以使用事件标志组来解决。事件标志组能够实现某个任务与多个事件或任务间的同步。

  • 事件位:用来表明某个事件是否发生,通常用作事件标志
  • 事件组:一组事件位组成一个事件组,事件组中的事件位通过编号来访问

事件标志组的数据类型为 EventGroupHandle_t ,事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中;该变量为16位数据类型时,事件标志组可以存储8个事件位;该变量为32位数据类型时,事件标志组可以存储24个事件位(高8位均有其他用途)

/***************EventBits_t在event_groups.h中定义**********/
typedef TickType_t EventBits_t;
/***************TickType_t在portmacro.h中定义**************/
#if( configUSE_16_BIT_TICKS == 1 )
    typedef uint16_t TickType_t;//事件标志组可以存储8个事件位
    #define portMAX_DELAY ( TickType_t ) 0xffff
#else
    typedef uint32_t TickType_t;//事件标志组可以存储24个事件位
    #define portMAX_DELAY ( TickType_t ) 0xffffffffUL
    #define portTICK_TYPE_IS_ATOMIC 1
#endif

 

2. 递归互斥信号量的API函数

2.1 创建递归互斥信号量

/********************动态创建事件标志组**********************************************/
EventGroupHandle_t xEventGroupCreate(void)
/********************静态创建事件标志组**********************************************/
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t * pxEventGroupBuffer)
//参数:pxEventGroupBuffer指向一个StaticEventGroup_t类型的变量,用来保存事件标志组结构体
返回值:创建成功返回事件标志组句柄;失败返回NULL

 

2.2 设置事件位

/****************将指定的事件位清零,用在任务中***************************************/
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,//要操作的事件标志组句柄
nst EventBits_t uxBitsToClear)//要清零的事件位
/****************将指定的事件位置1,用在任务中***************************************/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,//要操作的事件标志组句柄
nst EventBits_t uxBitsToSet)//要置1的事件位
返回值:将指定事件位清零之前的事件组值;将指定事件位置1后的事件组值
/****************将指定的事件位清零,用在中断服务函数中********************************/
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
onst EventBits_t uxBitsToClear)//要清零的事件位						   
/****************将指定的事件位置1,用在中断服务函数中********************************/
//#define configUSE_TRACE_FACILITY需要配置为1
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, 
onst EventBits_t uxBitsToSet,//要置1的事件位
aseType_t *pxHigherPriorityTaskWoken)//标记退出后是否切换任务
返回值:清零或置1成功返回pdPASS;清零或置1失败返回pdFALSE						 

 

指定事件位清零函数xEventGroupClearBits的源码如下示:

EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
	onst EventBits_t uxBitsToClear){
	EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
	EventBits_t uxReturn;
	taskENTER_CRITICAL();//进入临界段
	{
		/* 获取当前事件标志位 */
		uxReturn = pxEventBits->uxEventBits;
		/* 清除要设置的事件标志位 */
		pxEventBits->uxEventBits &= ~uxBitsToClear;
	}
	taskEXIT_CRITICAL();//退出临界段
	return uxReturn;//返回事件标志组值
}

 

指定事件位置1函数xEventGroupSetBits的源码如下示:

EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
	nst EventBits_t uxBitsToSet){
	ListItem_t *pxListItem, *pxNext;
	ListItem_t const *pxListEnd;
	List_t *pxList;
	EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;
	EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
	BaseType_t xMatchFound = pdFALSE;
	/* 获取事件列表头 */
	pxList = &( pxEventBits->xTasksWaitingForBits );
	/* 获取列表尾节点 */
	pxListEnd = listGET_END_MARKER( pxList ); 
	vTaskSuspendAll();//挂起调度器
	{	
		pxListItem = listGET_HEAD_ENTRY( pxList );//获取头节点
		pxEventBits->uxEventBits |= uxBitsToSet;//设置事件标志位
		/* 循环遍历整个列表项,直到列表头节点等于尾节点(指针) */
		while(pxListItem != pxListEnd){			
			pxNext = listGET_NEXT(pxListItem);//获取下个列表项
			/* 获取当前列表项的值 */
			uxBitsWaitedFor = listGET_LIST_ITEM_VALUE(pxListItem);		
			xMatchFound = pdFALSE;//标记是否找到需要处理的节点
			/* 拆分 */
			uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;
			uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;
			if((uxControlBits & eventWAIT_FOR_ALL_BITS) == (EventBits_t)0){
				/* 或逻辑,等待位已经置位 */
				if((uxBitsWaitedFor & pxEventBits->uxEventBits) != (EventBits_t)0){			
					xMatchFound = pdTRUE;//找到了已经触发的节点
				}
				else{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			/* 表示所有等待的位都已经触发 */
			else if((uxBitsWaitedFor&pxEventBits->uxEventBits) == uxBitsWaitedFor){		
				xMatchFound = pdTRUE;//找到触发的节点
			}
			else{
				/* Need all bits to be set, but not all the bits were set. */
			}

			if( xMatchFound != pdFALSE ){
				/* 判断是否需要清除 */
				if((uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT)!=(EventBits_t)0){
					uxBitsToClear |= uxBitsWaitedFor;//做个标记
				}
				else{
					mtCOVERAGE_TEST_MARKER();
				}
				/* 把任务从事件列表中移除	*/
				(void)xTaskRemoveFromUnorderedEventList(pxListItem,pxEventBits->uxEventBits|eventUNBLOCKED_DUE_TO_BIT_SET);
			}
			pxListItem = pxNext;//当前列表项指向下个,继续遍历
		}
		pxEventBits->uxEventBits &= ~uxBitsToClear;//清除设置后的标志位
	}	
	(void) xTaskResumeAll();//开启调度器
	return pxEventBits->uxEventBits;
}

 

2.3 获取事件标志组值

/***获取当前事件标志组的值,用在任务中****/
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup)//要操作的事件标志组句柄
/**获取当前事件标志组的值,用在中断服务函数中****/
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup)
返回值:当前事件标志组的值

 

用在任务中获取当前事件标志组的值函数是一个宏定义,如下示:

#define xEventGroupGetBits(xEventGroup)    xEventGroupClearBits(xEventGroup, 0)

 

用在中断服务函数获取当前事件标志组的值函数源码如下示:

EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup){
	UBaseType_t uxSavedInterruptStatus;
	EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
	EventBits_t uxReturn;	
	/* 禁止中断,带返回值 */
	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{		
		uxReturn = pxEventBits->uxEventBits;//获取事件标志位
	}
	/* 恢复中断,在进入禁止之前的状态 */
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
	return uxReturn;
}

 

2.4 等待指定的事件位

/***等待指定的事件位***/
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, //要等待的事件标志组
cnst EventBits_t uxBitsToWaitFor, //要等待的事件位
cnst BaseType_t xClearOnExit, //退出是否要清除位
cnst BaseType_t xWaitForAllBits, //与逻辑还是或逻辑
TckType_t xTicksToWait) //阻塞等待时间
参  数:xClearOnExit若为pdTURE,则表示设置的位在函数退出时会被清零;
aitForAllBits若为pdTURE,则表示所有要设置的位都置1或阻塞时间到,函数才会返回
为pdFALSE,则表示只要要设置的某一位置1或阻塞时间到,函数就会返回
返回值:返回所等待的事件位置1后的事件标志组的值,或返回阻塞时间到

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, 
    const EventBits_t uxBitsToWaitFor, 
    const BaseType_t xClearOnExit, 
    const BaseType_t xWaitForAllBits, 
    TickType_t xTicksToWait){
	EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
	EventBits_t uxReturn, uxControlBits = 0;
	BaseType_t xWaitConditionMet, xAlreadyYielded;
	BaseType_t xTimeoutOccurred = pdFALSE;	
	vTaskSuspendAll();//挂起调度器
	{
		/* 获取当前的事件标志位 */
		const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;
		/* 检查是否触发 */
		xWaitConditionMet = prvTestWaitCondition(uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits);
		if( xWaitConditionMet != pdFALSE ){			
			/* 已经触发 */
			uxReturn = uxCurrentEventBits;
			xTicksToWait = ( TickType_t ) 0;
			/* 清楚已经触发的标志 */
			if( xClearOnExit != pdFALSE ){
				pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
			}
			else{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else if(xTicksToWait == (TickType_t) 0){
			/* 不需要超时,直接返回标志位. */
			uxReturn = uxCurrentEventBits;
		}
		else{
			/* 事件没有触发,并且需要超时*/
			if( xClearOnExit != pdFALSE ){
				uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;
			}
			else{
				mtCOVERAGE_TEST_MARKER();
			}

			if( xWaitForAllBits != pdFALSE ){
				//uxControlBits = 0x05000000UL;
				uxControlBits |= eventWAIT_FOR_ALL_BITS;
			}
			else{
				mtCOVERAGE_TEST_MARKER();
			}

			/* 把任务添加到事件列表中	*/
			vTaskPlaceOnUnorderedEventList(&( pxEventBits->xTasksWaitingForBits), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait );
			uxReturn = 0;
			traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );
		}
	}
	xAlreadyYielded = xTaskResumeAll();//恢复调度器	
	if(xTicksToWait != (TickType_t ) 0){//再次判断是否需要超时
		if( xAlreadyYielded == pdFALSE ){
			portYIELD_WITHIN_API();//进行上下文切换 ->pendSV
		}
		else{
			mtCOVERAGE_TEST_MARKER();
		}
		/* 任务已经恢复,则复位列表项中的值  复位为任务有优先级 */
		uxReturn = uxTaskResetEventItemValue();
		/* 是不是通过事件置位解除的任务 */
		if((uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET) == (EventBits_t)0){		
			taskENTER_CRITICAL();//进入临界段
			{
				
				uxReturn = pxEventBits->uxEventBits;// 获取当前事件位
				/* 再此判断是否已经置位 */
				if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE ){
					/* 如果需要清除,清除触发后的标志位 */
					if( xClearOnExit != pdFALSE ){
						pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
					}
					else{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			taskEXIT_CRITICAL();
			xTimeoutOccurred = pdFALSE;
		}
		else{
			/* The task unblocked because the bits were set. */
		}
		/* 返回当前事件标志位 */
		uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
	}
	traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );
	return uxReturn;
}

 

3. 事件标志组应用实例

本实例介绍 FreeRTOS 事件标志组的创建、将相应的事件位置1、等待相应事件位置1等函数的操作与使用

使用STM32CubeMX将FreeRTOS移植到工程中,创建三个任务、一个事件标志组、开启一个按键中断

  • EventSetBit_Task:读取按键值,根据不同的键值将相应的事件位置1
  • EventGroup_Task:等待事件标志组的多个事件位都置1后执行相应处理
  • EventQuery_Task:查询事件组的值,即各个事件位的值

3.1 STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M
  • PA0设置为GPIO外部中断、下拉模式、并开启中断;PE2/PE3/PE4设置为GPIO输入模式、上拉模式
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位;
  • 激活FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数

  • 使能xEventGroupSetBitsFromISR()函数

  • 使用FreeRTOS操作系统,需要将HAL库的Timebase Source从SysTick改为其他定时器,选好定时器后,系统会自动配置TIM
  • 输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h' files per IP ;点击GENERATE CODE,生成工程代码

3.2 MDK-ARM软件编程

  • 创建按键驱动文件key.c和key.h,参考 按键输入 例程
  • 在freertos.c文件中,添加一个事件标志组(STM32CubeMX中不提供添加方法),定义事件位;并在freertos初始化函数MX_FREERTOS_Init()中创建事件标志组
EventGroupHandle_t EventGroupHandler;	//事件标志组句柄
#define EVENTBIT_0	(1<<0)	//事件位
#define EVENTBIT_1	(1<<1)
#define EVENTBIT_2	(1<<2)

/*****MX_FREERTOS_Init()中创建事件标志组*****/
EventGroupHandler = xEventGroupCreate();
if(EventGroupHandler == NULL)
  printf("EventGroup Create Failed!\r\n");

 

  • 添加EventSetBitTask、EventGroupTask和EventQueryTask任务函数代码
/***EventSetBitTask***/
void EventSetBitTask(void const * argument){
  uint8_t key;
  for(;;){	
	if(EventGroupHandler != NULL){
	  key = KEY_Scan(0);
	  switch(key){
		case KEY_LEFT_PRES :
		  xEventGroupSetBits(EventGroupHandler,EVENTBIT_1);
		  printf("EVENTBIT_1 Set Successed!\r\n");
		  break;
		case KEY_RIGHT_PRES :
		  xEventGroupSetBits(EventGroupHandler,EVENTBIT_2);
		  printf("EVENTBIT_2 Set Successed!\r\n");
		  break;
	  }
	}
    osDelay(10);
  }
}
/***EventGroup_Task***/
void EventGroup_Task(void const * argument){
  EventBits_t EventValue;
  for(;;){
    if(EventGroupHandler != NULL){
      EventValue = xEventGroupWaitBits(EventGroupHandler,
      EVENTBIT_0|EVENTBIT_1|EVENTBIT_2,要设置的位
      pdTRUE,//退出时清除位
      pdTRUE,//要设置的位都置1或阻塞时间到,函数才会返回
      portMAX_DELAY);	阻塞时间
      printf("WaitBits successed!Value of EventGroup is %d\r\n",EventValue);	
    }
    else{
	  osDelay(10);
	}
  }
}
/***EventQuery_Task***/
void EventQuery_Task(void const * argument){
  EventBits_t NewValue, LastValue;
  for(;;){
    if(EventGroupHandler != NULL){
      NewValue = xEventGroupGetBits(EventGroupHandler);
      if(NewValue != LastValue){
		LastValue = NewValue;
		printf("The Query Value of EventGroup is %d\r\n",LastValue);
	  }
    }
    osDelay(50);
  }
}

 

  • 添加按键PA0的中断处理函数,在中断中设置事件位
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
  BaseType_t ret;
  HAL_Delay(10);
  if(GPIO_Pin == GPIO_PIN_0){
	ret = xEventGroupSetBitsFromISR(EventGroupHandler,EVENTBIT_0,NULL);
	if(ret != pdFAIL)
	  printf("EVENTBIT_0 Set from ISR successed!\r\n");
	}
}

 

3.3 下载验证

编译无误下载到开发板后,打开串口调试助手,依次按KEY_UP、KEY_LEFT和KEY_RIGHT按键,串口输出如下调试信息:

  • 按下KEY_UP,在中断中将EVENTBIT_0位置1
  • 按下KEY_LEFT和KEY_RIGHT,EVENTBIT_1和EVENTBIT_2位置1
  • 三个位都置1后,xEventGroupWaitBits将各位清除

 

 

 


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



268 次浏览
8次