计数信号量
1. 计数信号量简介
计数型信号量有以下两种典型用法
- 事件计数:每次事件发生,事件处理函数将释放信号量(信号量计数值加1),其他处理任务会获取信号量(信号量计数值减1)来处理事件。因此,计数值是事件发生的数量和事件处理的数量差值。计数信号量在创建时其值为0
- 资源管理:信号量表示有效的资源数目。任务必须先获取信号量才能获取资源控制权。当计数值减为零时表示没有的资源。当任务完成后,它会返还信号量(信号量计数值增加)。信号量创建时计数值应等于最大资源数目
计数信号量有释放信号量操作和获取信号量操作,释放信号量操作的时候计数器的值会加一,获取信号操作,计数器的值减一,如果减到0任务会进入到等待状态;具体操作方式有两种,如下图所示:
2. 计数信号量的API函数
2.1 创建计数信号量
/********************动态创建计数信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount)
/********************静态创建计数信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount,//信号量最大计数值
UBaseType_t uxInitialCount,//计数信号量初始值
StaticSemaphore_t * pxSemaphoreBuffer)//保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回计数信号量句柄;失败返回NULL
动态计数信号量创建函数是一个宏,最终是通过xQueueCreateCountingSemaphore()函数来完成,其源码如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \
xQueueCreateCountingSemaphore( (uxMaxCount),(uxInitialCount) )
#endif
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任务,设置任务名称、优先级、堆栈大小、函数名称等参数
- 动态创建计数信号量,需要先使能USE_COUNTING_SEMAPHORES
- 使用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任务函数代码
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后,无法继续释放
关注我的公众号,在公众号里发如下消息,即可获取相应的工程源代码:
FreeRTOS计数信号量实例