FreeRTOS教程5 信号量

1、准备材料

正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0

Keil µVision5 IDE(MDK-Arm

野火DAP仿真器

XCOM V2.6串口助手

一个滑动变阻器

2、学习目标

本文主要学习 FreeRTOS 信号量的相关知识,包括创建/删除信号量、释放信号量、获取信号量等知识

3、前提知识

3.1、信号量概述

信号量是进程间用于通信的一种手段,其是基于队列实现的,信号量更适用于进程间同步,信号量包括二值信号量(Binary Semaphores)和计数信号量(Counting Semaphores)

二值信号量就是只有一个项的队列,该队列不为空则为满(所谓二值),二值信号量就像一个标志,适和用于进程间同步的通信

举个例子:ADC 的周期采集中断负责采集完成后将采集到的 ADC 的值写入数据缓存区中并且释放信号量,总是尝试获取信号量的数据处理任务在 ADC 采集中断释放信号量之后成功获取,然后退出阻塞状态对写入数据缓存区中采集到的 ADC 值进行处理,上述过程如下图所示:(注释1)

如下图所示为使用二值信号量来同步任务和中断的工作流程 (注释2)

计数信号量就是有固定长度的队列,队列中每个单元都是一个标志,其通常用于对多个共享资源的访问进行控制

举个例子:一家餐馆有 4 张可供用餐的桌子,我们创建一个长度为 4 ,初值为 4 的计数信号量来表示当前可供用餐的桌子数量,当有客人进来用餐时会 “获取” 一张餐桌,这时用来表示可供用餐的桌子数量的计数信号量就会减少一个,当有客人离开时会 “释放” 一张餐桌,这时用来表示可供用餐的桌子数量的计数信号量就会增加一个,上述过程如下图所示:

如下图所示为计数信号量的工作流程

3.2、创建信号量

信号量在使用之前也必须先创建,信号量被创建完之后是无效的,也即为 0 ,而由于信号量分为二值信号量和计数信号量两种,因此FreeRTOS也提供了不同的API函数,具体如下所述

/**
  * @brief  动态分配内存创建二值信号量函数
  * @param  xSemaphore:创建的二值信号量句柄
  * @retval None
  */
void vSemaphoreCreateBinary(SemaphoreHandle_t xSemaphore);

/**
  * @brief  静态分配内存创建二值信号量函数
  * @param  pxSemaphoreBuffer:指向一个StaticSemaphore_t类型的变量,该变量将用于保存信号量的状态
  * @retval 返回创建成功的信号量句柄,如果返回NULL则表示因为pxSemaphoreBuffer为空无法创建
  */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
									StaticSemaphore_t *pxSemaphoreBuffer);

/**
  * @brief  动态分配内存创建计数信号量函数
  * @param  uxMaxCount:可以达到的最大计数值
  * @param  uxInitialCount:创建信号量时分配给信号量的计数值
  * @retval 返回创建成功的信号量句柄,如果返回NULL则表示内存不足无法创建
  */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, 
										   UBaseType_t uxInitialCount);

/**
  * @brief  静态分配内存创建计数信号量函数
  * @param  uxMaxCount:可以达到的最大计数值
  * @param  uxInitialCount:创建信号量时分配给信号量的计数值
  * @param  pxSempahoreBuffer:指向StaticSemaphore_t类型的变量,该变量然后用于保存信号量的数据结构体
  * @retval 返回创建成功的信号量句柄,如果返回NULL则表示因为pxSemaphoreBuffer为空无法创建
  */
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
									UBaseType_t uxMaxCount,
									UBaseType_t uxInitialCount,
									StaticSemaphore_t pxSempahoreBuffer);

3.3、释放信号量

以下两个函数不仅仅可以用于释放二值信号量,还可以用于释放计数信号量和互斥量,具体如下所示

/**
  * @brief  释放信号量函数
  * @param  xSemaphore:要释放的信号量的句柄
  * @retval 如果信号量释放成功,则返回pdTRUE;如果发生错误,则返回pdFALSE
  */
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

/**
  * @brief  释放信号量的中断安全版本函数
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  * @retval 如果成功给出信号量,则返回pdTRUE,否则errQUEUE_FULL
  */
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, 
								 BaseType_t *pxHigherPriorityTaskWoken);

3.4、获取信号量

以下两个函数不仅仅可以用于获取二值信号量,还可以用于获取计数信号量和互斥量,具体如下所示

/**
  * @brief  获取信号量函数
  * @param  xSemaphore:正在获取的信号量的句柄
  * @param  xTicksToWait:等待信号量变为可用的时间
  * @retval 成功获得信号量则返回pdTRUE;如果xTicksToWait过期,信号量不可用,则返回pdFALSE
  */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
/**
  * @brief  获取信号量的中断安全版本函数
  * @param  xSemaphore:正在获取的信号量的句柄
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  * @retval 成功获取则返回pdTRUE,未成功获取则返回pdFALSE
  */
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, 
								 signed BaseType_t *pxHigherPriorityTaskWoken);

3.5、删除信号量

/**
  * @brief  删除信号量,包括互斥锁型信号量和递归信号量
  * @param  xSemaphore:被删除的信号量的句柄
  * @retval None
  */
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);

3.6、工具函数

/**
  * @brief  获取信号量计数
  * @param  xSemaphore:正在查询的信号量的句柄
  * @retval 如果信号量是计数信号量,则返回信号量的当前计数值。如果信号量是二进制信号量,则当信号量可用时,返回1,当信号量不可用时,返回 0
  */
UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore);

4、实验一:二值信号量的应用

4.1、实验目标

  1. 创建一个二值信号量 BinarySem_ADC
  2. 配置 ADC1 IN5 在 500ms 的定时器驱动下周期采集 ADC 值,采集完成后将采集值写入缓存数组,然后释放二值信号量 BinarySem_ADC
  3. 创建一个任务 TASK_ADC,该任务总是尝试获取二值信号量 BinarySem_ADC,获取成功之后,将写入缓存数组的 ADC 采集值进行转换,然后通过 USART1 输出给用户

4.2、CubeMX相关配置

首先读者应按照 "FreeRTOS教程1 基础知识" 章节配置一个可以正常编译通过的 FreeRTOS 空工程,然后在此空工程的基础上增加本实验所提出的要求

本实验需要初始化 USART1 作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”,如下图所示

本实验需要设置 TIM3 作为 ADC1 IN5 触发源的单通道 ADC 采集,采集周期为 500ms ,因此需要配置 ADC1 和 TIM3,感兴趣读者可以阅读”STM32CubeMX教程13 ADC - 单通道转换“实验,如下图所示

由于我们将要在 ADC 采集完成中断中使用 FreeRTOS 的释放二值信号量的函数,因此需要将其优先级设置在 15~5 之间,在这里设置为 7,具体如下图所示

单击 Middleware and Software Packs/FREERTOS,在 Configuration 中单击 Tasks and Queues 选项卡双击默认任务修改其参数,具体如下图所示

然后在 Configuration 中单击 Timers and Semaphores ,在 Binary Semaphores 中单击 Add 按钮新增加一个名为 BinarySem_ADC 的二值信号量,具体如下图所示

配置 Clock Configuration 和 Project Manager 两个页面,接下来直接单击 GENERATE CODE 按钮生成工程代码即可

4.3、添加其他必要代码

按照 “STM32CubeMX教程9 USART/UART 异步通信” 实验 “6、串口printf重定向” 小节增加串口 printf 重定向代码,具体不再赘述

首先应该在 freertos.c 中添加信号量的头文件,如下所述

/*freertos.c中添加头文件*/
#include "semphr.h"

然后在该文件中重新实现 ADC 采集完成中断回调函数,在该函数中获取采集完成的 ADC 值,将其保存在全局变量 adc_value 中,然后释放二值信号量 BinarySem_ADC ,如下所述

/*转换完成中断回调*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    /*定时器中断启动单通道转换*/
    if(hadc->Instance == ADC1)
    {
        adc_value = HAL_ADC_GetValue(hadc);
		BaseType_t highTaskWoken = pdFALSE;
		if(BinarySem_ADCHandle != NULL)
		{
			xSemaphoreGiveFromISR(BinarySem_ADCHandle, &highTaskWoken);
			portYIELD_FROM_ISR(highTaskWoken);
		}
    }
}

接下来仍然在该文件中实现任务 TASK_ADC 的函数体内容,该任务函数总是尝试获取二值信号量,一旦获取成功表示 ADC 转换完成,就将 ADC 转换完成的值变为电压值,然后通过 USART1 输出给用户显示,如下所述

/*ADC任务函数*/
void TASK_ADC(void *argument)
{
	/* USER CODE BEGIN TASK_ADC */
	/* Infinite loop */
	for(;;)
	{
		if(xSemaphoreTake(BinarySem_ADCHandle, portMAX_DELAY) == pdTRUE)
		{
			uint32_t Volt = (3300 * adc_value)>>12;
			printf("val:%d, Volt:%d\r\n", adc_value, Volt);
		}
	}
	/* USER CODE END TASK_ADC */
}

最后在 main.c 文件主函数 main() 中以中断方式启动 ADC 转换即可,如下所述

//以中断方式启动ADC1
HAL_ADC_Start_IT(&hadc1);
//启动ADC1触发源定时器TIM3
HAL_TIM_Base_Start(&htim3);

4.4、烧录验证

烧录程序,打开串口助手,可以发现每隔一段时间就会输出当前 ADC1 IN5 通道采集到的 ADC 的值,将其接入一个滑动变阻器,当滑动变阻器从一端滑动到另一端时,串口输出的采集值也在从 0 逐渐变为最大值 4095 ,整个过程串口输出信息如下图所示

5、实验二:计数信号量的应用

5.1、实验目标

  1. 创建一个计数信号量 CountingSem_Tables ,设置最大值为 5 ,初始值设为 5 ,表示饭店内初始有 5 张桌子
  2. 启动 RTC 周期唤醒中断,唤醒周期为 3s ,在 RTC 唤醒中断中释放信号量,模拟有客人离开饭店
  3. 创建任务 TASK_KEY2 ,当按键 KEY2 按下时尝试获取信号量,模拟客人进店,同时调用获取信号量计数查询函数,查询当前可用餐桌个数

5.2、CubeMX相关配置

首先读者应按照 "FreeRTOS教程1 基础知识" 章节配置一个可以正常编译通过的 FreeRTOS 空工程,然后在此空工程的基础上增加本实验所提出的要求

本实验需要初始化开发板上 KEY2 用户按键做普通输入,具体配置步骤请阅读 “STM32CubeMX教程3 GPIO输入 - 按键响应” ,注意虽开发板不同但配置原理一致,如下图所示

本实验需要初始化 USART1 作为输出信息渠道,具体配置步骤请阅读 “STM32CubeMX教程9 USART/UART 异步通信” ,如下图所示

本实验需要配置 RTC 周期唤醒中断,具体配置步骤和参数介绍读者可阅读”STM32CubeMX教程10 RTC 实时时钟 - 周期唤醒、闹钟A/B事件和备份寄存器“实验,此处不再赘述,这里参数、时钟配置如下图所示

由于需要在 RTC 周期唤醒中断中使用 FreeRTOS 的 API 函数,因此 RTC 周期唤醒中断的优先级应该设置在 15~5 之间,此处设置为 7 ,具体如下图所示

单击 Middleware and Software Packs/FREERTOS,在 Configuration 中单击 Tasks and Queues 选项卡,双击默认任务修改其参数,具体如下图所示

然后在 Configuration 中单击 Timers and Semaphores 选项卡,在最下方的 Counting Semaphores 中单击右下角的 Add 按钮增加一个计数信号量,具体如下图所示

配置 Clock Configuration 和 Project Manager 两个页面,接下来直接单击 GENERATE CODE 按钮生成工程代码即可

5.3、添加其他必要代码

按照 “STM32CubeMX教程9 USART/UART 异步通信” 实验 “6、串口printf重定向” 小节增加串口 printf 重定向代码,具体不再赘述

首先应该在 freertos.c 中添加信号量的头文件,如下所述

/*freertos.c中添加头文件*/
#include "stdio.h"
#include "semphr.h"

然后在该文件中重新实现周期唤醒回调函数,该函数用于周期释放计数信号量,具体如下所示

/*周期唤醒回调函数*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
	if(CountingSem_TablesHandle != NULL)
	{
		BaseType_t highTaskWoken = pdFALSE;
		//释放计数信号量
		xSemaphoreGiveFromISR(CountingSem_TablesHandle, &highTaskWoken);
		portYIELD_FROM_ISR(highTaskWoken);
	}
}

最后仍然在该文件中实现任务 TASK_KEY2 ,该任务负责当按键 KEY2 按下时尝试获取计数信号量,当无任何按键按下时不断输出当前计数信号量可用数量,具体如下所示

void TASK_KEY2(void *argument)
{
  /* USER CODE BEGIN TASK_KEY2 */
  /* Infinite loop */
  for(;;)
  {
	if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
	{
		//获取计数信号量
		BaseType_t result = xSemaphoreTake(CountingSem_TablesHandle, pdMS_TO_TICKS(100));
		if(result == pdTRUE) printf("Check In OK\r\n");
		else printf("Check In Fail\r\n");
		//按键消抖
		osDelay(pdMS_TO_TICKS(300));
	}
	else
	{
		UBaseType_t AvailableTables = uxSemaphoreGetCount(CountingSem_TablesHandle);
		printf("Now AvailableTables is : %d\r\n", (uint16_t)AvailableTables);
		osDelay(pdMS_TO_TICKS(10));	
	}
  }
  /* USER CODE END TASK_KEY2 */
}

5.4、烧录验证

烧录程序,打开串口助手,发现不断输出当前可用计数信号量数量,当按住按键 KEY2 不松开,连续模拟客人进店,可以发现在模拟 3 个客人进店之后,剩余可用计数信号量的数量变为了 2 个,然后每隔一段时间计算信号量的数量慢慢增加直到最大值 5 ,接着按住按键 KEY2 不松开,连续模拟 5 个客人进店,当 5 个客人进店之后,再次按下 KEY2 按键可以发现串口输出 ”Check In Fail“,表示当前已无剩余可用计数信号量,上述整个过程如下图所示

6、注释详解

注释1:图片来源于 STM32Cube高效开发教程(高级篇) 第五章

注释2:图片来源于 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

参考资料

STM32Cube高效开发教程(基础篇)

Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

热门相关:空即是色2   娜妲莉的情人   我经过风暴   妈妈的朋友6   驭房有术