细说STM32单片机FreeRTOS信号量和互斥量的基础知识及二值信号量的编程实例

目录

一、信号量和互斥量概述

1、二值信号量

2、计数信号量

3、互斥量

4、递归互斥量

5、相关函数概述

(1) 负责创建的函数

(2) 负责释放和获取的函数

(3)负责返回数据的函数

二、二值信号量使用示例

1、二值信号量操作相关函数详解

(1)创建二值信号量

(2)释放二值信号量

(3)获取二值信号量

2、示例功能和CubeMX项目设置

(1)RCC、SYS、Code Generator、USART3、TIM6

(2)TIM3

(3)设置ADC3_IN6

(4)设置FreeRTOS

(5)设置NVIC

3、程序功能实现

(1)主程序

(2)FreeRTOS对象初始化

(3)ADC3的中断处理

(4)数据读取与显示任务函数

三、运行与调试


        队列的功能是将进程间需要传递的数据存在其中,所以在有的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wenchm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值