目录
(1)RCC、SYS、Code Generator、USART3、TIM6
队列的功能是将进程间需要传递的数据存在其中,所以在有的RTOS系统里,队列也被称为“邮箱”。有的时候,进程间需要传递的只是一个标志,用于进程间同步或对一个共享资源的互斥性访问,这时就可以使用信号量或互斥量。信号量和互斥量的实现都是基于队列的,信号量更适用于进程间同步,互斥量更适用于共享资源的互斥性访问。
一、信号量和互斥量概述
信号量(semaphore)和互斥量(mutex)都可应用于进程间通信,它们都是基于队列的基本数据结构,但是信号量和互斥量又有一些区别。从队列派生出来的信号量和互斥量的分类如下图所示:
1、二值信号量
二值信号量(binary semaphore)就是只有一个项(成员或元素)的队列,这个队列要么是空的,要么是满的,所以相当于只有0和1两种值。二值信号量就像一个标志,适合用于进程间同步的通信。例如,图示的是使用二值信号量在ISR和任务之间进行同步的示意图。
- 图中有两个进程,ADC中断ISR负责读取ADC转换结果并写入缓冲区,数据处理任务负责读取缓冲区的内容并进行处理。
- 数据缓冲区是两个任务之间需要进行同步访问的对象,为了简化原理分析,假设数据缓冲区只存储一次的转换结果数据。ADC中断ISR读取ADC转换结果后,写入数据缓冲区,并且释放(give)二值信号量,二值信号量变为有效,表示数据缓冲区里已经存入了新的转换结果数据。
- 数据处理任务总是获取(take)二值信号量。如果二值信号量是无效的,任务就进入阻塞状态等待,可以一直等待,也可以设置等待超时时间。如果二值信号量变为有效的,数据处理任务立刻退出阻塞状态,进入运行状态,之后就可以读取缓冲区的数据并进行处理。
如果不使用二值信号量,而是使用一个自定义标志变量来实现以上的同步过程,则任务需要不断地查询标志变量的值,而不是像使用二值信号量那样,可以使任务进入阻塞等待状态。所以,使用二值信号量进行进程间同步的效率更高。
2、计数信号量
计数信号量(counting semaphore)就是有固定长度的队列,队列的每个项是一个标志。计数信号量通常用于对多个共享资源的访问进行控制。比如:
- 一个计数信号量被创建时设置为初值4,实际上是队列中有4个项,表示可共享访问的4个资源,这个值只是个计数值。可以将这4个资源类比为上图中的4个餐桌,客人就是访问资源的ISR或任务。
- 当有客人进店时,就是获取(take)信号量,如果有1个客人进店了(假设1个客人占用1张桌子),计数信号量的值就减1,计数信号量的值变为3,表示还有3张空余桌子。如果计数信号量的值变为0,表示4张桌子都被占用了,再有客人要进店时就得等待。在任务中申请信号量时,可以设置等待超时时间,在等待时,任务进入阻塞状态。
- 如果有1个客人用餐结束离开了,就是释放(give)信号量,计数信号量的值就加1,表示可用资源数量增加了1个,可供其他要进店的人获取。
由计数信号量的工作原理可知,它适用于管理多个共享资源,例如,ADC连续数据采集时,一般使用双缓冲区,就可以使用计数信号量来管理。
3、互斥量
互斥量是针对二值信号量的一种改进。使用二值信号量时,可能会出现优先级翻转(priority inversion)的问题,使系统的实时性变差。互斥量引入了优先级继承(priority inheritance)机制,可以减缓优先级翻转问题,但不能完全消除。
下图是使用互斥量控制互斥型资源访问的示意图,可解释互斥量的工作原理和特点。
- 两个任务要互斥性地访问串口,也就是在任务A访问串口时,其他任务不能访问串口。
- 互斥量相当于管理串口的一把钥匙。一个任务可以获取(take)互斥量,获取互斥量后,将独占对串口的访问,访问完后要释放(give)互斥量。
- 一个任务获取互斥量后,对资源进行访问时,其他想要获取互斥量的进程只能等待。
二值信号量和互斥量可以用于相同的应用场景,但是二值信号量更适用于进程间同步,互斥量更适用于控制对互斥型资源的访问。二值信号量没有优先级继承机制,将二值信号量用于互斥型资源的访问时,容易出现优先级翻转问题,而互斥量有优先级继承机制,可以减缓优先级翻转问题。
互斥量不能在ISR中使用,因为互斥量具有任务的优先级继承机制,而ISR不是任务。另外ISR中不能设置阻塞等待时间,而获取互斥量时,经常是需要等待的。
4、递归互斥量
递归互斥量(recursive mutex)是一种特殊的互斥量,可以用于需要递归调用的函数中。一个任务在获取一个互斥量之后,就不能再次获取这个互斥量了;而一个任务在获取递归互斥量之后,还可以再次获取这个递归互斥量,当然,每次获取必须与一次释放配对使用。递归互斥量同样不能在ISR中使用。
5、相关函数概述
信号量和互斥量相关的常量和函数定义都在头文件semphr.h中,函数都是宏函数,都是调用文件queue.c中的一些函数实现的。这些函数按功能可以划分为3组,见表:
分组 | 函数 | 功能 |
创建与 | xSemaphoreCreateBinary() | 创建二值信号量 |
xSemaphoreCreateBinaryStatic() | 创建二值信号量,静态分配内存 | |
xSemaphoreCreateCounting() | 创建计数信号量 | |
xSemaphoreCreateCountingStatic() | 创建计数信号量,静态分配内存 | |
xSemaphoreCreateMutex() | 创建互斥量 | |
xSemaphoreCreateMutexStatic() | 创建互斥量,静态分配内存 | |
xSemaphoreCreateRecursiveMutex() | 创建递归互斥量 | |
xSemaphoreCreateRecursiveMutexStatic() | 创建递归互斥量,静态分配内存 | |
vSemaphoreDelete() | 删除这4种信号量或互斥量 | |
获取与 | xSemaphoreGive() | 释放二值信号量、计数信号量、互斥量 |
xSemaphoreGiveFromISR() | xSemaphoreGive()的ISR版本,但不能用于互斥量 | |
xSemaphoreGiveRecursive() | 释放递归互斥量 | |
xSemaphoreTake() | 获取二值信号量、计数信号量、互斥量 | |
xSemaphoreTakeFromISR() | xSemaphoreTake()的ISR版本,但不用于互斥量 | |
xSemaphoreTakeRecursive() | 获取递归互斥量 | |
其他操作 | uxSemaphoreGetCount() | 返回计数信号量或二值信号量当前的值 |
xSemaphoreGetMutexHolder() | 返回互斥量的当前持有者 | |
xSemaphoreGetMutexHolderFromISR() | xSemaphoreGetMutexHolder()的ISR版本 |
(1) 负责创建的函数
每一种对象的创建都有专门的函数,例如,xSemaphoreCreateBinary()用于以动态分配内存方式创建二值信号量;xSemaphoreCreateBinaryStatic()则是以静态分配内存方式创建二值信号量的函数。
(2) 负责释放和获取的函数
信号量和互斥量的主要操作是释放和获取。
- 函数xSemaphoreGive()可以用于释放二值信号量、计数信号量和互斥量,但是对应的ISR版本xSemaphoreGiveFromISR()只能释放二值信号量和计数信号量,不能用于互斥量,因为互斥量不能在ISR中使用。xSemaphoreTake()和xSemaphoreTakeFromISR()的操作对象的区别也是如此。
- 递归互斥量的释放和获取有专门的函数,xSemaphoreGiveRecursive()和xSemaphoreTake Recursive(),递归互斥量不能在ISR中使用。
(3)负责返回数据的函数
- uxSemaphoreGetCount(xSemaphore)返回信号量xSemaphore的当前值,xSemaphore可以是计数信号量或二值信号量。如果是二值信号量,返回的值是1(信号量有效)或0(信号量无效);如果是计数信号量,返回值就是计数信号量当前的值,也就是表示剩余可用资源的个数。
- xSemaphoreGetMutexHolder(xMutex)用于在任务中获取一个互斥量xMutex的当前持有者(holder),也就是获取了互斥量xMutex,但还没有释放它的任务的句柄。这个函数通常用来确定当前任务是不是某个互斥量的持有者。
要在FreeRTOS中使用计数信号量、互斥量或递归互斥量,需要将相应的“config”参数设置为1。这几个参数在CubeMX里可以设置,且默认都是Enabled:
二、二值信号量使用示例
1、二值信号量操作相关函数详解
(1)创建二值信号量
在使用二值信号量之前,需要先创建一个二值信号量。以动态分配内存方式创建二值信号量的函数是xSemaphoreCreateBinary(),这是一个宏函数,其原型定义如下:
* \defgroup xSemaphoreCreateBinary xSemaphoreCreateBinary
* \ingroup Semaphores
*/
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary()
xQueueGenericCreate( ( UBaseType_t ) 1,semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
它调用了创建队列函数xQueueGenericCreate(),xSemaphoreCreateBinary()调用这个函数时,传递了如下几个参数。
- 第1个参数:数值1,是队列长度。
- 第2个参数:符号常数semSEMAPHORE_QUEUE_ITEM_LENGTH,其值实际为0,二值信号量的队列里存储的具体是什么类型的数据由FreeRTOS处理。
- 第3个参数:符号常数queueQUEUE_TYPE_BINARY_SEMAPHORE,表示创建的是二值信号量。
函数xSemaphoreCreateBinary()返回的数据类型是QueueHandle_t,实际上就是void类型的指针,也就是创建的二值信号量的句柄。
(2)释放二值信号量
二值信号量被创建后是无效的,相当于值为0。释放二值信号量的目的就是使其有效,相当于使其变为1。在任务中释放二值信号量的函数是xSemaphoreGive(),其原型定义如下:
#define xSemaphoreGive( xSemaphore )
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
xSemaphoreGive()也调用了队列写入函数xQueueGenericSend(),xQueueSendToBack()调用的底层函数,xSemaphoreGive()调用这个函数时,传递了如下的几个参数。
- 第1个参数:xSemaphore,是二值信号量的句柄。
- 第2个参数:数值NULL。这个参数是需要向队列写入的数据,对于二值信号量来说,不需要写数据到队列,由FreeRTOS内部处理。
- 第3个参数:宏定义常量semGIVE_BLOCK_TIME,其数值为0。这个参数是等待的节拍数,释放二值信号量无须等待,所以数值为0。
- 第4个参数:宏定义常量queueSEND_TO_BACK,表示写入队列的方向。
如果成功释放了二值信号量,函数xSemaphoreGive()返回pdTRUE;否则,返回pdFALSE。
函数xSemaphoreGive()不仅可以释放二值信号量,还可以释放计数信号量和互斥量,所以参数xSemaphore可以是这3种对象的句柄。
在中断ISR中,释放信号量的函数是xSemaphoreGiveFromISR(),其原型定义如下:
#define xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)
xQueueGiveFromISR(( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ))
函数xSemaphoreGiveFromISR()调用了函数xQueueGiveFromISR(),后者的原型定义如下:
BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
第一个参数xQueue是二值信号量或计数信号量的句柄,不能是互斥量,因为在ISR里不能使用互斥量。
第二个参数pxHigherPriorityTaskWoken是BaseType_t类型的指针,是一个返回数据,返回值为pdTRUE或pdFALSE。如果释放信号量导致一个任务解锁,而解锁的任务比当前任务优先级高,则参数pxHigherPriorityTaskWoken返回值为pdTRUE,这就需要在退出ISR之前申请进行任务调度,以便及时执行解锁的高优先级任务。执行函数portYIELD_FROM_ISR()可以申请进行任务调度。
如果函数xSemaphoreGiveFromISR()的返回值是pdTRUE,则表示信号量被成功释放。在ISR中调用xSemaphoreGiveFromISR()的示例代码如下:
BaseType_t highTaskWoken=pdFALSE;
if(BinSem_DataReadyHandle!=NULL)
{
xSemaphoreGiveFromISR(BinSem_DataReadyHandle,&highTaskWoken);
portYIELD_FROM_ISR(highTaskWoken); //申请进行一次任务调度
}
(3)获取二值信号量
在任务中获取二值信号量的函数是xSemaphoreTake(),其原型定义如下:
#define xSemaphoreTake( xSemaphore, xBlockTime )
xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
这个函数调用了函数xQueueSemaphoreTake(),传递的是以下两个参数。
- 参数xSemaphore,二值信号量的句柄。
- 参数xBlockTime,阻塞等待的节拍数。在获取二值信号量时,如果二值信号量无效,可以设置一个超时等待时间。如果是常数portMAX_DELAY,则表示一直等待;如果是0,则表示不等待;如果是其他有限数值,则表示超时等待的节拍数。
如果成功获取了二值信号量,函数xSemaphoreTake()返回pdTRUE;否则,返回pdFALSE。函数xSemaphoreTake()不仅可以用于获取二值信号量,还可以用于获取计数信号量和互斥量,所以参数xSemaphore可以是这3种对象的句柄。
在ISR中获取二值信号量的函数是xSemaphoreTakeFromISR(),其原型定义如下:
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )
xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )
函数xSemaphoreTakeFromISR()中的两个参数的意义如下。
- 参数xSemaphore,二值信号量或计数信号量的句柄,不能是互斥量。
- 参数pxHigherPriorityTaskWoken,传递指针的返回数据,返回值为pdTRUE或pdFALSE,表示是否需要在退出ISR之前进行任务调度申请。
2、示例功能和CubeMX项目设置
设计一个示例演示二值信号量的使用。本示例是一个典型的进程间同步的应用,其主要功能和工作流程如下。
- 创建一个二值信号量BinSem_DataReady。
- ADC3的IN6通道在定时器TIM3的触发下,进行周期为500ms的ADC数据采集。在ADC的ISR里,将转换结果写入缓存变量,并释放信号量BinSem_DataReady。
- 一个任务总是尝试获取信号量BinSem_DataReady。在获取到信号量后,读取ADC转换结果缓存变量,然后在串口助手上显示数据。
- 继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。一些内容可以参考本文作者写的其他文章:细说STM32单片机FreeRTOS进程间通信技术及消息队列的应用方法-优快云博客 细说STM32单片机FreeRTOS进程间通信技术及消息队列的应用方法-优快云博客
(1)RCC、SYS、Code Generator、USART3、TIM6
- 同参考文章。
- 特别地,可以将HCLK设置为100MHz,将APB1和APB2定时器时钟频率都设置为50MHz。
- 在SYS组件模式设置中,选择TIM6作为HAL基础时钟源。
(2)TIM3
APB1和APB2定时器时钟频率=50MHz。TIM3定时周期为500ms,并且以更新事件(Update Event)作为触发输出信号TRGO,TIM3的TRGO信号作为ADC3的外部触发信号。
(3)设置ADC3_IN6
ADC3的输入通道选择IN6,对应开发板上的电位器输出,使用12位精度,数据右对齐。外部触发源(External Trigger Conversion Source)选择Timer 3 Trigger Out event。在中断模式下进行ADC3连续数据转换,还需要在ADC3的NVIC Settings设置页面中启用ADC3全局中断。
(4)设置FreeRTOS
启用FreeRTOS,将接口设置为CMSIS_v2,所有“config”和“INCLUDE_”参数保持默认值。在Tasks and Queues页面设计任务,将默认任务修改为Task_Show:
Timers and Semaphores页面用于设计软件定时器、二值信号量和计数信号量,在其中创建一个二值信号量BinSem_DataReady,使用动态分配内存方式。如果使用静态分配内存方式创建二值信号量,还需要设置Control Block Name,即设置控制块的变量名称。
(5)设置NVIC
无须启用TIM3的中断,只需启用ADC3的中断。由于要在ADC3的中断ISR里调用FreeRTOS的函数xSemaphoreGiveFromISR(),因此其抢占优先级不能高于5。
3、程序功能实现
(1)主程序
完成设置后,在CubeMX中生成代码。在CubeIDE中打开项目,自动生成主程序main.c代码。
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "adc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC3_Init();
MX_TIM3_Init();
MX_USART3_UART_Init();
/* Init scheduler */
osKernelInitialize();
/* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在其中的代码对中,手动添加用户代码,用于一次性显示的标题,和初始化ADC3和TIM3:
/* USER CODE BEGIN 2 */
uint8_t startstr[] = "Demo5-1_Semaphore: test Binary Semaphore.\r\n\r\n";
HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);
HAL_ADC_Start_IT(&hadc3); //start ADC3 interrupt
HAL_TIM_Base_Start(&htim3); //start TIM3
/* USER CODE END 2 */
(2)FreeRTOS对象初始化
CubeMX导出的文件freertos.c包含函数MX_FREERTOS_Init(),用于创建CubeMX中定义的任务和信号量,还包含任务Task_Show的任务函数框架。FreeRTOS对象初始化的代码(含任务函数框架)是自动生成的。
在freertos.c中手动添加用户代码:
/* USER CODE BEGIN Includes */
#include "semphr.h"
#include <stdio.h>
#include "usart.h"
//#include "adc.h"
/* USER CODE END Includes */
/* USER CODE BEGIN Variables */
uint32_t adc_value; //global Variables
/* USER CODE END Variables */
程序中与二值信号量BinSem_DataReady相关的句柄变量和属性变量的定义如下:
/* Definitions for BinSem_DataReady */
osSemaphoreId_t BinSem_DataReadyHandle;
const osSemaphoreAttr_t BinSem_DataReady_attributes = {
.name = "BinSem_DataReady"
};
其中,二值信号量的句柄变量类型是osSemaphoreId_t,这是文件cmsis_os2.h中定义的类型,其原型定义如下:
typedef void *osSemaphoreId_t;
osSemaphoreId_t是CMSIS-RTOS定义的标准类型,与FreeRTOS自己定义的类型QueueHandle_t实质是一样的,所以可以作为二值信号量的句柄变量。
其中,二值信号量的属性变量是结构体类型osSemaphoreAttr_t,是在文件cmsis_os2.h中定义的结构体,其定义如下,各成员变量的意义见注释:
/// Attributes structure for semaphore.
typedef struct {
const char *name; ///< name of the semaphore
uint32_t attr_bits; ///< attribute bits
void *cb_mem; ///< memory for control block
uint32_t cb_size; ///< size of provided memory for control block
} osSemaphoreAttr_t;
在使用动态分配内存方式创建二值信号量时,我们只需设置信号量名称即可。结构体osSemaphoreAttr_t还可用于定义计数信号量的属性。
创建二值信号量使用的是CMSIS-RTOS的接口函数osSemaphoreNew(),这个函数不仅可以创建二值信号量,还可以创建计数信号量。函数osSemaphoreNew()的原型定义如下:
osSemaphoreId_t osSemaphoreNew (uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr)
{
//
}
其中,参数max_count是最多可用标志(token)个数,也就是队列存储项的个数;参数initial_count是初始可用标志个数;attr是信号量的属性。
示例中创建二值信号量的代码如下:
BinSem_DataReadyHandle =osSemaphoreNew(1,1,&BinSem_DataReady_attributes);
传递的参数max_count的值为1,initial_count的值也是1,因为二值信号量实际上是长度为1的队列。
函数osSemaphoreNew()既可创建二值信号量,又可以创建计数信号量,它内部就是根据max_count的值决定创建哪种信号量。如果max_count值为1,则创建二值信号量;否则,创建计数信号量。在创建二值信号量时,函数osSemaphoreNew()内部还会根据属性设置自动调用xSemaphoreCreateBinary()或xSemaphoreCreateBinaryStatic()。
(3)ADC3的中断处理
ADC3采用TIM3外部触发方式进行ADC转换,在ADC3的转换完成事件中断里,读取转换结果数据。ADC转换完成事件中断的回调函数是HAL_ADC_ConvCpltCallback(),为了便于使用freertos.c中定义的信号量以及全局的缓存变量,我们就在文件freertos.c中实现这个回调函数。文件freertos.c中新增的一些代码以及这个回调函数的代码如下:
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (hadc->Instance == ADC3)
{
adc_value=HAL_ADC_GetValue(hadc); //ADC convert initiate value
BaseType_t highTaskWoken=pdFALSE;
if (BinSem_DataReadyHandle != NULL)
{
xSemaphoreGiveFromISR(BinSem_DataReadyHandle, &highTaskWoken);
portYIELD_FROM_ISR(highTaskWoken); //Task Scheduling
}
}
}
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END Application */
设置ADC3在TIM3的周期触发下每500ms进行一次ADC转换,在一次转换完成后,会触发中断,执行回调函数HAL_ADC_ConvCpltCallback()。这个函数实现的功能就是读取ADC转换结果,保存到全局变量adc_value里,然后调用函数xSemaphoreGiveFromISR()释放信号量,表示有新的转换结果数据了,以便任务Task_Show读取新的转换结果数据并显示。
在调用函数xSemaphoreGiveFromISR()传递参数highTaskWoken时,采用的是传地址方式。参数highTaskWoken用于获取一个返回值(pdTRUE或pdFALSE),表示在退出ISR前是否需要进行一次任务调度申请。执行portYIELD_FROM_ISR(highTaskWoken)会根据参数highTaskWoken的值自动决定是否进行任务调度申请。
(4)数据读取与显示任务函数
任务Task_Show的功能是尝试获取二值信号量BinSem_DataReady,如果这个二值信号量变为有效,则表示有新的转换结果数据了。在文件freertos.c中,为Task_Show的任务函数添加代码,完成后的任务函数代码如下:
/* USER CODE BEGIN Header_AppTask_Show */
/**
* @brief Function implementing the Task_Show thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_Show */
void AppTask_Show(void *argument)
{
/* USER CODE BEGIN AppTask_Show */
/* Infinite loop */
for(;;)
{
if (xSemaphoreTake(BinSem_DataReadyHandle, portMAX_DELAY)==pdTRUE)
{
uint32_t tmpValue=adc_value;
printf("ADC Value = %ld.\r\n",tmpValue); //show ADC initiate value
uint32_t Volt=3300 * tmpValue; //mV
Volt=Volt>>12; //divided by 2^12
printf("Voltage(mV)= %ld.\r\n",Volt); //show ADC initiate value
}
}
/* USER CODE END AppTask_Show */
}
任务函数使用函数xSemaphoreTake()获取二值信号量,设置的等待时间portMAX_DELAY。如果信号量无效,任务就一直处于阻塞状态;如果信号量有效,任务就立刻退出阻塞状态,执行if条件成立时的代码段。其功能就是读取adc_value的值,再转换为毫伏表示的电压值,将原始值和电压值显示在串口助手上。
三、运行与调试
这个示例是个典型的进程间同步的应用。为了简化,ADC转换结果只用一个变量保存,在实际的ADC连续高速数据采集中,一般使用双缓冲区交替保存数据,也仍然可以使用二值信号量进行进程间同步,在一个缓冲区存满数据后及时通知数据处理任务进行处理。
下载后,串口助手立刻收到初始化显示标题信息,然后连续显示任务函数采集到的ADC3_IN6的电位器值,旋转电位器角度,可以改变显示的数值。
串口助手能连续接收到ADC数值,说明任务函数AppTask_Show()根据二值信号量BinSem_DataReadyHandle的提示,真实接收到了ISR采集到的ADC数据。
总结:信号量与ADC数据没有直接的联系,他只是一个信号(标志,FLAG),在ISR里发出(give)信号量,告诉任务数据已经准备完毕,在任务里,接受到这个信号(take),就执行任务。
思维流程:定义→创建信号→ISR里发信号give→任务里收信号take