文章

FreeRTOS计数信号量

计数信号量

1. 计数信号量简介

计数型信号量有以下两种典型用法

  • 事件计数:每次事件发生,事件处理函数将释放信号量(信号量计数值加 1),其他处理任务会获取信号量(信号量计数值减 1)来处理事件。因此,计数值是事件发生的数量和事件处理的数量差值。计数信号量在创建时其值为 0
  • 资源管理:信号量表示有效的资源数目。任务必须先获取信号量才能获取资源控制权。当计数值减为零时表示没有的资源。当任务完成后,它会返还信号量(信号量计数值增加)。信号量创建时计数值应等于最大资源数目

计数信号量有释放信号量操作和获取信号量操作,释放信号量操作的时候计数器的值会加一,获取信号操作,计数器的值减一,如果减到 0 任务会进入到等待状态;具体操作方式有两种,如下图所示:

img

2. 计数信号量的 API 函数

2.1 创建计数信号量
1
2
3
4
5
6
7
8
9
/********************动态创建计数信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
										   UBaseType_t uxInitialCount)
/********************静态创建计数信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount,//信号量最大计数值
											 UBaseType_t uxInitialCount,//计数信号量初始值
								  StaticSemaphore_t * pxSemaphoreBuffer)//保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回计数信号量句柄;失败返回NULL

动态计数信号量创建函数是一个宏,最终是通过 xQueueCreateCountingSemaphore() 函数来完成,其源码如下:

1
2
3
4
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) 	\
xQueueCreateCountingSemaphore( (uxMaxCount),(uxInitialCount) )	
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
QueueHandle_t xQueueCreateCountingSemaphore(const UBaseType_t uxMaxCount,const UBaseType_t uxInitialCount){
	QueueHandle_t xHandle;
	/* 调用消息队列创建函数,创建队列长度为uxMaxCount,队列项长度为0,类型为计数信号量的队列 */
	xHandle = xQueueGenericCreate(uxMaxCount,queueSEMAPHORE_QUEUE_ITEM_LENGTH,queueQUEUE_TYPE_COUNTING_SEMAPHORE);
	if( xHandle != NULL ){
		/* 将计数信号量的初始值来设置uxMessagesWaiting,代表可用的资源数量 */
		( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
		traceCREATE_COUNTING_SEMAPHORE();
	}
	else{
		traceCREATE_COUNTING_SEMAPHORE_FAILED();
	}
	return xHandle;
}
2.2 释放和获取计数信号量

计数型信号量的释放与获取与二值信号量相同,可参考二值信号量中的 3.2 和 3.3 章节

3. 计数信号量的应用实例

本实例的功能需求是使用两个按键触发计数信号量的释放和获取,并打印出当前信号量的计数值

使用 STM32CubeMX 将 FreeRTOS移植到工程中,创建一个按键处理任务、一个计数信号量

Keyscan_Task:扫描按键状态,根据不同的键值释放或者获取信号量

3.1 STM32CubeMX 设置
  • RCC 设置外接 HSE,时钟设置为 72M
  • PC0 设置为 GPIO 推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1 选择为异步通讯方式,波特率设置为 115200Bits/s,传输数据长度为 8Bit,无奇偶校验,1 位停止位;开启串口中断
  • 激活 FreeRTOS,添加 Keyscan_Task 任务,设置任务名称、优先级、堆栈大小、函数名称等参数

img

  • 动态创建计数信号量,需要先使能 USE_COUNTING_SEMAPHORES

img

img

  • 使用 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,参考按键输入例程
  • 添加 KeyscanTask 任务函数代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void KeyscanTask(void const * argument){
  int8_t key;
  int8_t sem_value;
  BaseType_t err;
  for(;;){
	key = KEY_Scan(0);
	if(CountingSemHandle != NULL){
	  switch(key){
		case KEY_UP_PRES:
		  err = xSemaphoreTake(CountingSemHandle,portMAX_DELAY);
		  if(err == pdFALSE)
			printf("Semaphore Take failed!\r\n");
		  else{
			sem_value = uxSemaphoreGetCount(CountingSemHandle);
			printf("Semaphore Take successed, Semvalue = %d\r\n",sem_value);
			HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_RESET);
		  }
		  break;
		case KEY_DOWN_PRES:
		  err = xSemaphoreGive(CountingSemHandle);
		  if(err == pdFALSE)
			printf("Semaphore Give failed!\r\n");
		  else{
			sem_value = uxSemaphoreGetCount(CountingSemHandle);
			printf("Semaphore Give successed, Semvalue = %d\r\n",sem_value);
			HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_SET);
		  }
		  break;
	  }
	}
    osDelay(10);
  }
}
3.3 下载验证

编译无误下载到开发板后,按下 K_UP 按键获取信号,串口打印出当前信号量的计数值,同时点亮 LED0;按下 K_DOWN 按键释放信号量,串口打印出当前信号量的计数值,同时熄灭 LED0;信号量最大计数值设置为 10 了,因此信号量释放到 10 后,无法继续释放

img

以上转载自博主“安迪西嵌入式”的FreeRTOS专栏,仅作学习记录,如有侵权,请联系删除。

本文由作者按照 CC BY 4.0 进行授权