FreeRTOS学习笔记(七)
在STM32F407ZGT6上用计数信号量实现控制资源访问
前言
在上一篇中,我们提到了信号量的核心是“控制资源访问”与“任务同步”,并使用实例用二值信号量实现了一个任务同步的实例。在这一篇中,我们围绕计数信号量和资源管理展开。
在上一篇的实验结果中,可以明显的发现串口输出的同步信息数量明显少于中断次数,这是由于TIM2中断更新时间短。在使用二值信号量同步任务和中断时,信号的传输还没有完成,新的中断又发生了。显然,二值信号量无法保存多次中断的状态,而采用计数信号量可以解决这个问题。
什么是计数信号量?其特点是什么?
计数型信号量与二值信号量类似,二值信号量相当于队列长度为 1 的队列,因此二值信号 量只能容纳一个资源,这也是为什么命名为二值信号量。
计数信号量相当于长度大于1的队列,因此计数型信号量能够容纳多个资源,故其具有如下特点:
1.维护一个计数值,表示可用资源的数量或已发生事件的次数。
2.当任务获取信号量时,计数值递减;当释放信号量时,计数值递增。
3.计数值可以大于1,当计数值为0时,尝试获取信号量的任务可能会被阻塞。
计数信号量的使用场景是什么?
计数型信号量主要用于事件的计数和资源管理。
1.事件计数
在这种场合下,每次事件发生后,在处理函数中释放计数型信号量**(计数型信号量的资源数加 1)其他等待事件发生的任务获取计数型信号量(计数型信号量的资源数减 1)**。
这么等待事件的任务在事件一发生就可以在成功获取到计数型信号量并执行相应的操作。在这种场合 下,计数型信号量的资源数一般在创建时设置为 0。
2.资源管理
在这种场合下,计数型信号量的资源数代表着共享资源的可用数量。以停车场停车为例: 停车场有固定数量车位。入口显示屏实时显示剩余车位数。每当有车辆进入,剩余车位减1;车辆离开时,剩余车位加1。
在这里的剩余车位数就是计数信号量的计数值,如果停的车(申请计数信号量的任务数)超过了计数值,最后一个开进来的车没有车位(申请失败),只有当之前有车开出去(计数信号量被释放),后进入的车才能开进来(申请成功)。
计数信号量具体如何使用?
计数信号量相关 API 函数
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateCounting() | 使用动态方式创建计数信号量 |
| xSemaphoreCreateCountingStatic() | 使用静态方式创建计数信号量 |
| xSemaphoreGive() | 释放信号量 |
| xSemaphoreGiveFromISR() | 在中断中释放信号量 |
| xSemaphoreTake() | 获取信号量 |
| xSemaphoreTakeFromISR() | 在中断中获取信号量 |
| vSemaphoreDelete() | 删除信号量 |
根据上面的表中的函数可以发现,计数信号量的释放和获取与二值信号量完全相同,故在这里不多做阐述,具体来看一下创建计数信号量的宏,其定义如下:
#if(configSUPPORT_DYNAMIC_ALLOCATION==1)#definexSemaphoreCreateCounting(uxMaxCount,uxInitialCount)xQueueCreateCountingSemaphore((uxMaxCount),(uxInitialCount))#endif该宏有两个参数uxMaxCount和uxInitialCount,用于给创建的计数信号量指定最大计数值和初值,实际创建计数信号量的函数是xQueueCreateCountingSemaphore()函数,创建成功会返回句柄,失败会返回NULL值。
还有一个使用静态方式创建计数信号量xSemaphoreCreateCountingStatic(),在使用该宏创建计数信号量时需要由用户分配所需内存。
接下来还是和之前一样,举一个实例来加深对计数信号量使用方式的理解。这里使用了上面停车场的例子设计实例,模拟停车场的运行。
创建两个任务,优先级都为3,一个是获取信号量任务,另一个是接收信号量任务。初始化5个可用的信号量,开启任务调度器,让两个任务独立运行。
在获取信号量任务中,如果按下KEY0按键,模拟停车场停车操作,在串口输出相应的信息。
在释放信号量任务中,如果按下KEY1按键,模拟停车场取车操作,在串口输出相应的信息。
具体代码如下:
appTask.c
/*FreeRTOS配置*//* GiveTask 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidGiveTask(void*pvParameters);/* 任务函数 */staticTaskHandle_t GiveTaskHandle=NULL;/* 任务句柄 */#defineGiveTask_PRIO3/* 任务优先级 */#defineGiveTask_STK_SIZE128/* 任务堆栈大小 *//* TakeTask 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidTakeTask(void*pvParameters);/* 任务函数 */staticTaskHandle_t TakeTaskHandle=NULL;/* 任务句柄 */#defineTakeTask_PRIO3/* 任务优先级 */#defineTakeTask_STK_SIZE128/* 任务堆栈大小 */SemaphoreHandle_t CountSem_Handle;/*二值信号量句柄*//******************************************************************************************************//** * @brief appStartTask FreeRTOS入口函数 * @param 无 * @retval 无 */voidappStartTask(void){taskENTER_CRITICAL();/*进入临界段,关中断*/CountSem_Handle=xSemaphoreCreateCounting(5,5);if(CountSem_Handle!=NULL){printf("计数信号量创建成功\r\n");}xTaskCreate((TaskFunction_t)TakeTask,/* 任务函数 */(constchar*)"TakeTask",/* 任务名称 */(uint16_t)TakeTask_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)TakeTask_PRIO,/* 任务优先级 */(TaskHandle_t*)&TakeTaskHandle);/* 任务句柄 */xTaskCreate((TaskFunction_t)GiveTask,/* 任务函数 */(constchar*)"GiveTask",/* 任务名称 */(uint16_t)GiveTask_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)GiveTask_PRIO,/* 任务优先级 */(TaskHandle_t*)&GiveTaskHandle);/* 任务句柄 */taskEXIT_CRITICAL();/*退出临界段,开中断*/vTaskStartScheduler();/*开启任务调度器*/}/** * @brief TakeTask * @param pvParameters : 传入参数 * @retval 无 */staticvoidTakeTask(void*pvParameters){/*定义一个创建信息返回值,默认为pdPASS*/BaseType_t xReturn=pdTRUE;while(1){if(key_scan()==KEY0_PRES){xReturn=xSemaphoreTake(CountSem_Handle,/*计数信号量句柄*/0);/*等待时间:0*/if(pdTRUE==xReturn){printf("key0按键按下,成功申请到停车位\r\n");}else{printf("key0按键按下,车位已满\r\n");}}vTaskDelay(pdMS_TO_TICKS(20));/*延时20ms*/}}/** * @brief GiveTask * @param pvParameters : 传入参数 * @retval 无 */staticvoidGiveTask(void*pvParameters){/*定义一个创建信息返回值,默认为pdPASS*/BaseType_t xReturn=pdTRUE;while(1){/*获取一个计数信号量*/if(key_scan()==KEY1_PRES){/*给出计数信号量*/xReturn=xSemaphoreGive(CountSem_Handle);if(pdTRUE==xReturn){printf("key1按键被按下,释放1个车位\r\n");}else{printf("key1按键被按下,但车场已空!\r\n");}}vTaskDelay(pdMS_TO_TICKS(20));/*延时20ms*/}}实例串口输出结果
可以看到申请了5个车位后,再次按下按键申请车位,显示车位已满;只有释放了车位,才能再次显示申请车位成功!