FreeRTOS学习笔记六在STM32F407ZGT6上用二值信号量实现任务同步前言在前一篇中我们使用了消息队列来实现任务间的消息传递。但在实际的开发项目中由于消息队列使用的内存过大或者有时不同任务间任务与中断间只需要简单的传递给状态不涉及到具体的数据值freeRTOS系统引入了信号量这一同步机制。队列的核心是“传递消息”而信号量的核心是“控制资源访问”与“任务同步”。什么是信号量信号量Semaphore是在多任务环境下使用的一种机制用在多任务访问同一资源时的资源管理例如可以用来保证两个或多个关键代码段不被并发调用。FreeRTOS中如何使用信号量信号量的类型有哪些由于信号量可以是二进制的也可以是计数型的所以信号量可以分为二值信号量和计数信号量。互斥信号量看成一种特殊的二值信号量。和二值信号量不同的是互斥信号量具有优先级继承机制而二值信号量没有。互斥信号量不能在中断服务函数中使用而二值信号量可以。递归互斥信号量可以看作是特殊的互斥信号量与互斥信号量不同的是递归互斥信号量 在被获取后可以被其持有者重复获取当然递归互斥信号量的持有者需要释放递归互斥信号 量与之获取递归互斥信号量相同的次数递归互斥信号量才算被释放。二值信号量具体如何使用任务一般通过xSemaphoreTake()函数试图获取信号量如果二值信号量无效任务进入阻塞态。在任务阻塞的过程中有中断发生在该中断的中断服务函数中用xSemaphoreGiveFromISR() 函数释放信号量二值信号量由无效变为有效。这时任务获取信号量成功任务解除阻塞开始执行任务处理程序。由于任务函数通常是一个大循环在任务处理完相关事务后又会再次调用xSemaphoreTake()函数试图获取信号量但由于这时二值信号量无效已经从有效变为无效任务再次进入阻塞态。下面简单的介绍一下创建二值信号量的相关函数后面会再创建一个示例完成信号量在 FreeRTOS 中的标准使用流程。二值信号量相关 API 函数函数描述xSemaphoreCreateBinary()使用动态方式创建二值信号量xSemaphoreCreateBinaryStatic()使用静态方式创建二值信号量xSemaphoreGive()释放信号量xSemaphoreGiveFromISR()在中断中释放信号量xSemaphoreTake()获取信号量xSemaphoreTakeFromISR()在中断中获取信号量vSemaphoreDelete()删除信号量创建二值信号量创建二值信号量可以使用两个宏xSemaphoreCreateBinary()和xSemaphoreCreateBinaryStatic()。创建二值信号量所需的内存由 FreeRTOS 从 FreeRTOS 管理的堆中进行分配。这两个宏最终被队列创建函数xQueueGenericCreate()替换这在 semphr.h 文件中有定义如下所示#if(configSUPPORT_DYNAMIC_ALLOCATION1)#definexSemaphoreCreateBinary()xQueueGenericCreate((UBaseType_t)1,semSEMAPHORE_QUEUE_ITEM_LENGTH,queueQUEUE_TYPE_BINARY_SEMAPHORE)#endif#if(configSUPPORT_STATIC_ALLOCATION1)#definexSemaphoreCreateBinaryStatic(pxStaticSemaphore)xQueueGenericCreateStatic((UBaseType_t)1,semSEMAPHORE_QUEUE_ITEM_LENGTH,NULL,pxStaticSemaphore,queueQUEUE_TYPE_BINARY_SEMAPHORE)#endif根据上面代码所示要使用上面两个宏创建二值信号量需要将宏configSUPPORT_STATIC_ALLOCATION设置为1。释放二值信号量xSemaphoreGive() 也是一个宏用于释放二值信号量具体的实现功能的是调用队列发送函数xQueueGenericSend()。*endcode*\defgroup xSemaphoreGive xSemaphoreGive*\ingroup Semaphores*/#definexSemaphoreGive(xSemaphore)xQueueGenericSend((QueueHandle_t)(xSemaphore),NULL,semGIVE_BLOCK_TIME,queueSEND_TO_BACK)xSemaphoreGiveFromISR() 和xSemaphoreGive() 类似也是一个宏要是感兴趣可以去 semphr.h 中查找这里不多做阐述。获取二值信号量xSemaphoreTakeFromISR()是用来获取信号量的中断版本在中断服务函数中使用。他也是一个宏用于获取二值信号量该宏可以用于获取计数信号量和二值信号量但不能用于互斥信号量。该宏的定义如下#definexSemaphoreTakeFromISR(xSemaphore,pxHigherPriorityTaskWoken)xQueueReceiveFromISR((QueueHandle_t)(xSemaphore),NULL,(pxHigherPriorityTaskWoken))xSemaphoreTake()和xSemaphoreTakeFromISR()类似也是一个宏用于获取二值信号量该宏还可以用于获取计数信号量和互斥信号量。要是感兴趣可以去 semphr.h 中查找这里不多做阐述。实例用二值信号量进行任务同步 本实例是通过对二值信号量的操作实现任务与任务之间以及任务与中断间的同步。使用appStartTask()函数创建4个任务 任务1的功能是使LED0闪烁优先级为3通过二值信号量实现任务与任务之间的同步输出信息到串口。 任务2的功能是使LED1闪烁优先级为3通过二值信号量实现任务与中断之间的同步输出信息到串口。 任务3是串口守护函数优先级为3其功能是将通过队列传输过来的字符信息在串口上输出任何情况下只有串口守护函数能访问串口。 任务4是按键扫描函数优先级为4其功能是按键扫描并根据返回的键值执行释放二值信号量、启动定时器等操作。WAKEUP按键用于释放任务与任务间的同步的二值信号量KEY0用于启动TIM2利用TIM2的更新中断释放任务和中断之间同步的二值信号量KEY1用于停止TIM2。 具体代码如下appTask.c#includefreertos_demo.h#includeled.h#includekey.h#includeusart.h#includestdio.h/*********************************************FreeRTOS*************************************************/#includeFreeRTOS.h#includetask.h#includequeue.h#includesemphr.h/*FreeRTOS配置*//* Led0Task 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidLed0Task(void*pvParameters);/* 任务函数 */staticTaskHandle_t Led0TaskHandleNULL;/* 任务句柄 */#defineLed0Task_PRIO3/* 任务优先级 */#defineLed0Task_STK_SIZE128/* 任务堆栈大小 *//* Led1Task 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidLed1Task(void*pvParameters);/* 任务函数 */staticTaskHandle_t Led1TaskHandleNULL;/* 任务句柄 */#defineLed1Task_PRIO3/* 任务优先级 */#defineLed1Task_STK_SIZE128/* 任务堆栈大小 *//* printTASK 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidprintTASK(void*pvParameters);/* 任务函数 */staticTaskHandle_t printTask_HandlerNULL;/* 任务句柄 */#defineprintTask_PRIO3/* 任务优先级 */#defineprintTask_STK_SIZE128/* 任务堆栈大小 *//* keyTASK 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidkeyTASK(void*pvParameters);/* 任务函数 */staticTaskHandle_t keyTask_HandlerNULL;/* 任务句柄 */#definekeyTask_PRIO4/* 任务优先级 */#definekeyTask_STK_SIZE128/* 任务堆栈大小 */staticcharpcToPrint[80];/*串口打印内容缓冲区*/xQueueHandle xQueuePrint;/*用于创建队列及其他函数*/volatileuint8_tuIRQCounter;/*用于统计中断次数*/SemaphoreHandle_t binKeySemaphore;/*任务与任务之间同步二值信号量句柄*/SemaphoreHandle_t binIRQSemaphore;/*任务与中断之间同步二值信号量句柄*//******************************************************************************************************//** * brief appStartTask FreeRTOS入口函数 * param 无 * retval 无 */voidappStartTask(void){xQueuePrintxQueueCreate(2,sizeof(pcToPrint));binIRQSemaphorexSemaphoreCreateBinary();binKeySemaphorexSemaphoreCreateBinary();if(xQueuePrint!NULL){taskENTER_CRITICAL();/*进入临界段关中断*/xTaskCreate((TaskFunction_t)Led0Task,/* 任务函数 */(constchar*)Led0Task,/* 任务名称 */(uint16_t)Led0Task_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)Led0Task_PRIO,/* 任务优先级 */(TaskHandle_t*)Led0TaskHandle);/* 任务句柄 */xTaskCreate((TaskFunction_t)Led1Task,/* 任务函数 */(constchar*)Led1Task,/* 任务名称 */(uint16_t)Led1Task_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)Led1Task_PRIO,/* 任务优先级 */(TaskHandle_t*)Led1TaskHandle);/* 任务句柄 */xTaskCreate((TaskFunction_t)printTASK,/* 任务函数 */(constchar*)PrintTask,/* 任务名称 */(uint16_t)printTask_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)printTask_PRIO,/* 任务优先级 */(TaskHandle_t*)printTask_Handler);/* 任务句柄 */xTaskCreate((TaskFunction_t)keyTASK,/* 任务函数 */(constchar*)keyTask,/* 任务名称 */(uint16_t)keyTask_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)keyTask_PRIO,/* 任务优先级 */(TaskHandle_t*)keyTask_Handler);/* 任务句柄 */taskEXIT_CRITICAL();/*退出临界段开中断*/vTaskStartScheduler();/*开启任务调度器*/}}/** * brief Led0Task * param pvParameters : 传入参数 * retval 无 */staticvoidLed0Task(void*pvParameters){while(1){LED0_TOGGLE();/*LED0闪烁*/vTaskDelay(pdMS_TO_TICKS(500));/*延时500ms*//*若获取二值信号量成功则执行一些操作本例为发送相应信息到串口*/if(xSemaphoreTake(binKeySemaphore,10)pdTRUE){/*生成待打印输出信息*/sprintf(pcToPrint,按键任务通过二值信号量同步任务1\r\n);/*打印信息不直接输出通过队列发送给串口守护函数*/xQueueSendToBack(xQueuePrint,pcToPrint,0);}}}/** * brief Led1Task * param pvParameters : 传入参数 * retval 无 */staticvoidLed1Task(void*pvParameters){while(1){LED1_TOGGLE();/*LED1闪烁*/vTaskDelay(pdMS_TO_TICKS(500));/*延时500ms*//*若获取二值信号量成功则执行一些操作本例为发送相应信息到串口*/if(xSemaphoreTake(binIRQSemaphore,10)pdTRUE){/*生成待打印输出信息*/sprintf(pcToPrint,TIM2中断同步任务2中断%3d次\r\n,uIRQCounter);/*打印信息不直接输出通过队列发送给串口守护函数*/xQueueSendToBack(xQueuePrint,pcToPrint,0);}}}/** * brief printTASK * param pvParameters : 传入参数 * retval 无 */staticvoidprintTASK(void*pvParameters){charpcToWrite[80];while(1){xQueueReceive(xQueuePrint,pcToWrite,portMAX_DELAY);printf(%s,pcToWrite);}}/** * brief keyTASK * param pvParameters : 传入参数 * retval 无 */staticvoidkeyTASK(void*pvParameters){uint8_tkeyValue;//键值externTIM_HandleTypeDef htim2;while(1){keyValuekey_scan();//获取键值if(keyValueWKUP_PRES){sprintf(pcToPrint,WAKEUP按键按下发送同步信号.....\r\n);xQueueSendToBack(xQueuePrint,pcToPrint,0);xSemaphoreGive(binKeySemaphore);}elseif(keyValueKEY0_PRES){HAL_TIM_Base_Start_IT(htim2);sprintf(pcToPrint,KEY0按键按下启动TIM2更新中断.....\r\n);xQueueSendToBack(xQueuePrint,pcToPrint,0);}elseif(keyValueKEY1_PRES){HAL_TIM_Base_Stop_IT(htim2);xSemaphoreTake(binIRQSemaphore,10);sprintf(pcToPrint,KEY1按键按下停止TIM2.....\r\n);xQueueSendToBack(xQueuePrint,pcToPrint,0);}vTaskDelay(pdMS_TO_TICKS(100));}}main.c/** * brief Period elapsed callback in non blocking mode * note This function is called when TIM6 interrupt took place, inside * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment * a global variable uwTick used as application time base. * param htim : TIM handle * retval None */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){/* USER CODE BEGIN Callback 0 *//* USER CODE END Callback 0 */if(htim-InstanceTIM6){HAL_IncTick();}/* USER CODE BEGIN Callback 1 */externSemaphoreHandle_t binIRQSemaphore;/*任务与中断之间同步二值信号量句柄*/externvolatileuint8_tuIRQCounter;/*用于统计中断次数*/BaseType_t xHighPriorityTaskWokenpdFALSE;/*推出中断后是否进行任务切换*/if(htim-InstanceTIM2){uIRQCounter;/*通过发送二值信号量给任务2进行同步使用freeRTOS的API函数*/xSemaphoreGiveFromISR(binIRQSemaphore,xHighPriorityTaskWoken);/*如果有更高优先级的任务退出中断后执行任务切换*/portYIELD_FROM_ISR(xHighPriorityTaskWoken);}/* USER CODE END Callback 1 */} 实例的结果如图所示