细说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进程间通信技术及消息队列的应用方法-CSDN博客 细说STM32单片机FreeRTOS进程间通信技术及消息队列的应用方法-CSDN博客
(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 interruptHAL_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 semaphoreuint32_t attr_bits; ///< attribute bitsvoid *cb_mem; ///< memory for control blockuint32_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 valueBaseType_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 valueuint32_t Volt=3300 * tmpValue; //mVVolt=Volt>>12; //divided by 2^12printf("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
相关文章:
细说STM32单片机FreeRTOS信号量和互斥量及二值信号量的应用实例
目录 一、信号量和互斥量概述 1、二值信号量 2、计数信号量 3、互斥量 4、递归互斥量 5、相关函数概述 (1) 负责创建的函数 (2) 负责释放和获取的函数 (3)负责返回数据的函数 二、二值信号量使用…...
云原生之认识DDD
一、DDD是什么? 领域驱动设计(DDD) 做为一种软件工程的方法论,它可以帮助我们设计高质量的软件,或者说任何工程的设计都需要方法论,不论是城市设计、建筑设计、室内设计。 比如没有方法论的情况下楼是可以盖起来的,或许整个楼道和窗户上挂满了电话线、闭路线、电线?下水…...
Kingbase 数据库物理备份与恢复操作手册
版本环境:KingbaseES V8R6 适用对象:DBA / 运维工程师 / 技术支持人员 目标用途:生产环境灾备保障、全量迁移、异地容灾恢复 一、物理备份操作流程 物理备份是指直接对数据库实例的物理文件进行复制,具备完整性强、恢复速度快等特…...
高等数学同步测试卷 同济7版 试卷部分 上 做题记录 第四章 不定积分同步测试卷 A卷
第四章 不定积分同步测试卷 A卷 一、单项选择题(本大题共5小题,每小题3分,总计15分) 1. 2. 3. 4. 5. 二、填空题(本大题共5小题,每小题3分,总计15 分) 6. 7. 8. 9. 10. 三、求解下列各题(本大题共5小题,每小题6分,总计30…...
【刷题Day25】用户态和内核态、Reactor、虚拟内存(浅)
什么是用户态和内核态? 用户态(User Mode)和内核态(Kernel Mode)是操作系统中的两种运行模式,用于区分应用程序与操作系统内核的操作权限。 两者区别在于权限级别: 用户态:应用程…...
使用Qt Quick Controls创建自定义日历组件
目录 引言相关阅读1. DayOfWeekRow2. MonthGrid3. WeekNumberColumn 项目结构及实现工程结构图代码实现及解析1. 组件封装2. 主界面实现 运行效果 总结下载链接 引言 Qt6 Quick框架提供了一套丰富的日历相关组件,包括 MonthGrid、DayOfWeekRow 和 WeekNumberColumn…...
Java 富文本转word
前言: 本文的目的是将传入的富文本内容(html标签,图片)并且分页导出为word文档。 所使用的为docx4j 一、依赖导入 <!-- 富文本转word --><dependency><groupId>org.docx4j</groupId><artifactId>docx4j</artifactId&…...
基于 Spring Boot 瑞吉外卖系统开发(七)
基于 Spring Boot 瑞吉外卖系统开发(七) 新增菜品页面 菜品管理页面提供了一个“新增菜品”按钮,单击该按钮时,会打开新增菜品页面。 菜品分类列表 首先要获取分类列表数据。 请求路径/category/list,请求方法GE…...
react 子组件暴露,父组件接收
// Child.jsx import React, { forwardRef, useImperativeHandle, useState } from react; import { Form, Input } from antd;const Child forwardRef((props, ref) > {const [form] Form.useForm();const [customState, setCustomState] useState(默认值);useImperativ…...
如何在Spring Boot中配置自定义端口运行应用程序
Spring Boot 应用程序默认在端口 8080 上运行嵌入式 Web 服务器(如 Tomcat、Jetty 或 Undertow)。然而,在开发、测试或生产环境中,开发者可能需要将应用程序配置为在自定义端口上运行,例如避免端口冲突、适配微服务架构…...
5.第五章:数据分类的方法论
文章目录 5.1 传统分类方法5.1.1 基于规则的分类方法5.1.2 基于统计的分类方法5.1.3 传统分类方法的局限性 5.2 现代分类技术5.2.1 神经网络分类模型5.2.2 深度学习分类方法5.2.3 现代分类技术的优势 5.3 创新分类方法5.3.1 小样本学习方法5.3.2 零样本学习方法5.3.3 主动学习方…...
如何在 Unity 中导入 gltf /glb 文件
遗憾的是,默认情况下,Unity 无法导入 gltf 文件。 我们有 个好消息要告诉你 gltf,有一种方法可以将 glb 文件格式导入 Unity! 看完这篇文章后,让我们将 “gltf, glb” 文件放入 Unity 中,并将其…...
Docker部署一款开源的极简服务器监控工具Ward内网穿透远程使用
文章目录 前言1.关于Ward2.Docker部署3.简单使用ward4.安装cpolar内网穿透5. 配置ward公网地址6. 配置固定公网地址总结 前言 各位小伙伴们,你们是不是也遇到过这样的情况:每次打开服务器管理界面,密密麻麻的数据和图表看得你眼花缭乱&#…...
Day11(回溯法)——LeetCode79.单词搜索
1 前言 今天主要刷了一道热题榜中回溯法的题,现在的计划是先刷热题榜专题吧,感觉还是这样见效比较快。因此本文主要介绍LeetCode79。 2 LeetCode79.单词搜索(LeetCode79) OK题目描述及相关示例如下: 2.1 题目分析解决及优化 感觉回溯的方…...
数据结构-图
一、图的定义与基本术语 图(Graph)是一种非线性数据结构,由顶点(Vertex)和边(Edge)组成。它包含以下基本术语: 顶点(Vertex) :是图中的数据元素。…...
数据结构-选择排序(Python)
目录 选择排序算法思想 选择排序算法步骤 选择排序代码实现 选择排序算法分析 选择排序算法思想 选择排序(Selection Sort)基本思想: 将数组分为两个区间:左侧为已排序区间,右侧为未排序区间。每趟从未排序区间中…...
[特殊字符] 分布式定时任务调度实战:XXL-JOB工作原理与路由策略详解
在微服务架构中,定时任务往往面临多实例重复执行、任务冲突等挑战。为了解决这一问题,企业级调度框架 XXL-JOB 提供了强大的任务统一调度与执行机制,特别适合在分布式系统中使用。 本文将从 XXL-JOB 的核心架构入手,详细讲解其调…...
【前端】基于 Promise 的 HTTP 客户端工具Axios 详解
Axios 详解 1. 简介 定义:Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js 环境,简化 HTTP 请求的发送和处理。核心特点: 支持 Promise API,可链式调用。自动转换 JSON 数据。支持请求/响应拦截。可取…...
React Native 安卓端 android Image 播放gif webp 动态图
React Native 安卓端 android Image 播放gif webp 动态图 RN项目是0.78.2 React是19.0 基本介绍 Image 是 React Native 中用于显示各种类型图片的核心组件,支持显示网络图片、静态资源、本地图片以及 base64 编码的图片。在 Android 端,Image 组件还可…...
【mysql】windows mysql命令
终端配置环境变量,找到mysql地址放入环境变量-系统变量中 例如: C:\Program Files\MySQL\MySQL Server 8.0\bin win键R输入 sysdm.cpl 快速打开电脑变量-高级-环境变量 连接命令 mysql -u root -p 查看所有数据库 show databases; 选中数据库 …...
uniappx 打包配置32位64位x86安装包
{"app": {"distribute": {"android": {"abiFilters": ["armeabi-v7a","arm64-v8a","x86","x86_64"]}}} }...
【C++ 类和数据抽象】static 类成员
目录 一、static 类成员的基本概念 1.1 静态成员的定义 1.2 静态数据成员 1.3 静态成员函数 1.4 内存布局 1.5 访问控制 1.6 性能分析 1.7 C标准演进 二、static 类成员的特点 2.1 共享性 2.2 不依赖于对象 2.3 无 this 指针 三、静态成员的初始化规则 3.1 初始化…...
深入了解递归、堆与栈:C#中的内存管理与函数调用
在编程中,理解如何有效地管理内存以及如何控制程序的执行流程是每个开发者必须掌握的基本概念。C#作为一种高级编程语言,其内存管理和函数调用机制包括递归、堆与栈。本文将详细讲解这三者的工作原理、用途以及它们在C#中的实现和应用。 1. 递归 (Recur…...
声音分离人声和配乐-从头设计数字生命第5课, demucs——仙盟创梦IDE
demucs 伴奏提取人声分离技术具有多方面的重大意义,主要体现在以下几个领域: 音乐创作与制作 创作便利性提升:创作者能轻易获取无伴奏的人声轨道,便于对人声进行单独处理,如调整音准、音色、添加特效等,…...
基于PHP+Uniapp的互联网医院源码:电子处方功能落地方案
随着“互联网医疗”政策红利持续释放,互联网医院已成为推动医疗数字化转型的重要方向。在这一趋势下,电子处方功能模块作为核心环节,不仅直接关系到线上问诊闭环的实现,也成为系统开发中技术难度较高、业务逻辑最为复杂的一部分。…...
Linux 基础命令入门指南
在 Linux 系统中,命令行是高效操作和管理系统的核心方式。掌握一些基础命令,能够让我们更便捷地完成文件操作、系统监控、文本处理等任务。本文将为大家介绍常用的 Linux 基础命令,帮助新手快速入门。 一、文件和目录操作命令 1. ls&#x…...
(done) 吴恩达版提示词工程 3. 迭代 (控制输出长度、提取特定细节、输出 HTML 格式)
url: https://www.bilibili.com/video/BV1Z14y1Z7LJ?spm_id_from333.788.videopod.episodes&vd_source7a1a0bc74158c6993c7355c5490fc600&p3 别人的笔记 url: https://zhuanlan.zhihu.com/p/626966526 3. 迭代(Iterative) 当我使用大语言模型…...
学员答题pk知识竞赛小程序怎么做
制作学员答题PK知识竞赛小程序,主要有以下步骤: 一、规划设计 明确需求:确定小程序的使用场景是校园知识竞赛、培训机构考核还是企业内部培训等。答题功能,规定答题的具体规则,包括题目类型(单选、多选、…...
P1217 [USACO1.5] 回文质数 Prime Palindromes【python】
P1217 [USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 151 151 是回文质数。 写一个程序来找出范围 [ a , b ] ( 5 ≤ a < b ≤ 100 , 000 …...
搭建私人网站
第一章 阿里云服务器选购与配置 1.1 注册与实名认证 注册账号 访问阿里云官网,点击右上角"免费注册",填写邮箱/手机号,完成人机验证后获取验证码。 注意:企业用户需选择"企业实名认证",个人用…...
Nacos简介—1.Nacos使用简介
大纲 1.Nacos的在服务注册中心 配置中心中的应用 2.Nacos 2.x最新版本下载与目录结构 3.Nacos 2.x的数据库存储与日志存储 4.Nacos 2.x服务端的startup.sh启动脚本 5.Dubbo Nacos微服务RPC调用开发示例 6.Nacos对临时与持久化服务实例的健康检查机制 7.Nacos保护阈值机…...
【工具】使用 MCP Inspector 调试服务的完全指南
Model Context Protocol (MCP) Inspector 是一个交互式开发工具,专为测试和调试 MCP 服务器而设计。本文将详细介绍如何使用 Inspector 工具有效地调试和测试 MCP 服务。 1. MCP Inspector 简介 MCP Inspector 提供了直观的界面,让开发者能够ÿ…...
架构-项目管理
一、盈亏平衡分析 核心知识点: 基本公式 正常情况:销售额 固定成本 可变成本 税费 利润盈亏平衡时:销售额 固定成本 可变成本 税费(利润为0,即不赚不亏的临界点) 公式推导:利润 销售额…...
域控重命名导致无法登录
问题描述:公司新买了一个服务器用于替换旧服务器,旧服务器名称为server3为域控,降级后新装的服务器升级为了新域控。然后旧服务器更名为server5,新服务器server6更名为server3.重启新服务器后服务器无法登录。但是服务器相关功能都…...
C++内存管理那些事
一、C/C内存分布 【说明】: 栈又叫堆栈,是非静态局部变量、函数参数、返回值存放的区域,栈向下增长内存映射段是高效的IO映射方式,用于装载一个共享的动态内存库。用户可以使用系统接口创建共享内存,做进程间的通信堆…...
C++多态(实现部分)(一)
目录 1.多态的概念 1.1运行时多态 1.2 编译时多态 2.多态的定义以及实现 2.1 多态构成的条件 2.2 虚函数 2.3 虚函数的重写/覆盖 2.3.1 虚函数重写的两个例外 1.协变 2.析构函数的重写 2.4 override 和final关键字 2.5 重载/重写/隐藏的对比 编辑 3. 抽象类 和…...
HOW - Code Review 流程自动化
文章目录 前言流程自动化落地一、自动发起 MR(Merge Request)macOS 安装 glab方式一:使用 Homebrew(推荐) 其他平台安装方法Linux (apt)Windows(scoop 或 chocolatey) 使用示例:自动…...
自动化标注软件解析
关于PyQt5信号槽机制的解析 信号槽机制是 Qt 框架中用于对象间通信的核心机制,它基于发布-订阅模式,能够实现松耦合的组件交互。 1. 信号槽机制的基本概念 信号(Signal) 信号是对象发出的一种通知,表示某个事件发生…...
机器人结构认知与安装
机器人结构认知与安装 1. ES机器人系统结构与硬件组成 核心组件: OPPO ES5机器人系统由机器人本体、控制手柄、48V电源和OPPO Studio终端构成。一体化底座:包含控制主板、安全接口板、监测保护电路单元,支持外接急停开关,采用光耦…...
SQLMesh 模型选择指南:优化大型项目的模型更新
在处理大型 SQLMesh 项目时,模型之间的依赖关系可能会变得非常复杂。为了更有效地管理这些项目,SQLMesh 提供了一种模型选择机制,允许用户有针对性地选择需要更新的模型。本文将详细介绍如何使用 SQLMesh 的模型选择功能来优化项目更新过程。…...
linux:启动后,ubuntu屏幕变成红色了
屏幕启动后变成 红色背景 通常说明 显卡驱动出了问题,或者是 图形界面加载失败 使用了 fallback 模式。这种现象在 NVIDIA 驱动安装失败或显卡与驱动不兼容时常见。 🎯 先给你几个快速修复选项 ✅ 1. 进入 TTY 命令行界面 按下:Ctrl Alt …...
抖音的逆向工程获取弹幕(websocket和protobuf解析)
目录 声明前言第一节 获取room_id和ttwid值第二节 signture值逆向python 实现signature第三节 Websocket实现长链接请求protubuf反序列化pushFrame反序列化Response解压和反序列化消息体Message解析应答ack参考博客声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的…...
2194出差-节点开销Bellman-ford/图论
题目网址: 蓝桥账户中心 我先用Floyd跑了一遍,不出所料TLE了 n,mmap(int,input().split())clist(map(int,input().split()))INFfloat(inf) ma[[INF]*n for i in range(n)]for i in range(m):u,v,wmap(int,input().split())ma[u-1][v-1]wma[v-1][u-1]w#“…...
【hexo主题自定义】
主题下载安装 进入命令行,下载 NexT 主题,输入: git clone https://github.com/theme-next/hexo-theme-next themes/next 修改站点配置文件_config.yml,找到如下代码: ## Themes: https://hexo.io/themes/ theme: l…...
前后端部署
#在学习JavaWeb之后,进行了苍穹外卖的学习。在进行苍穹外卖的部署的时候,作者遇到了下面的问题# 1.前端工程nginx无法启动: 当我双击已经部署好的nginx工程中nginx.exe文件的时候,在服务中,并没有找到ngnix成功运行。…...
1.jdk+idea安装+HelloWorld项目创建
1.jdk1.8idea安装项目创建 jdk1.8安装配置环境变量 到华为镜像下载jdk,因为Oracle官网需要注册才可以下载jdk https://repo.huaweicloud.com/java/jdk/8u202-b08/ 直接下一步安装,配置环境变量 重启,执行java -version 和 javac idea下载 版本20…...
Puter部署指南:基于Docker的多功能个人云平台掌控自己的数据
前言:嗨,小伙伴们!每次开机是不是都要像参加点击大赛一样不停地敲击各种网盘和应用的登录按钮?更让人抓狂的是,这些科技巨头会不会偷偷翻阅我们的隐私数据呢?别担心,今天给大家安利一个超炫酷的…...
动态渲染页面智能嗅探:机器学习判定AJAX加载触发条件
本文提出了一种基于机器学习的智能嗅探机制,革新性地应用于自动判定动态渲染页面中AJAX加载的最佳触发时机。系统架构采用先进模块化拆解设计,由请求分析模块、机器学习判定模块、数据采集模块和文件存储模块四大核心部分构成。在核心代码示例中…...
探索 CameraCtrl模型:视频生成中的精确摄像机控制技术
在当今的视频生成领域,精确控制摄像机轨迹一直是一个具有挑战性的目标。许多现有的模型在处理摄像机姿态时往往忽略了精准控制的重要性,导致生成的视频在摄像机运动方面不够理想。为了解决这一问题,一种名为 CameraCtrl 的创新文本到视频模型…...
理解欧拉公式
1. 欧拉公式中的符号 欧拉公式 e i x cos x i sin x e^{ix}\cos xi\sin x eixcosxisinx当 x π x \pi xπ时 e i π 1 0 / / 欧拉恒等式 e^{i\:\pi}10 //欧拉恒等式 eiπ10//欧拉恒等式 e e e:自然对数的底 i i i:虚数, i 2 − 1 i^2 -1 i2−1 cos…...