FreeRTOS高效应用实战

FreeRTOS高效应用实战

基于STM32CubeIDE生成对芯片移植好的FreeRTOS工程,使用HAL库编写FreeRTOS应用程序,实现FreeRTOS高效应用实战

image-20250225230431635

引入函数句柄的概念

函数句柄(Function Handle)是编程中用于间接引用和操作函数的一种机制,其本质是将函数作为数据来传递和存储。以下是关于函数句柄的详细说明:


核心概念解析

特性 描述
间接调用 通过变量调用函数,而非直接使用函数名
运行时绑定 允许在程序运行时动态确定要执行的函数
数据化函数 函数可像普通变量一样被赋值、传递和返回

在FreeRTOS中函数句柄大量使用,需对其有一定理解

任务

FreeRTOS 中的 任务(Task) 是系统调度的基本单元,类似于操作系统中的线程。每个任务代表一个独立的执行流程,拥有自己的堆栈空间和优先级

任务的核心特性

特性 说明
独立性 每个任务拥有独立堆栈和程序计数器(PC)
优先级驱动 系统基于优先级进行抢占式调度(0 最低,configMAX_PRIORITIES-1 最高)
状态管理 任务在运行、就绪、阻塞、挂起状态间转换
资源隔离 任务通过信号量、队列等机制安全共享资源
低延迟切换 FreeRTOS 任务切换时间通常在微秒级(取决于硬件)

任务的五种状态

  1. 运行(Running):当前正在 CPU 上执行的任务
  2. 就绪(Ready):已准备好执行,等待调度器分配 CPU
  3. 阻塞(Blocked):因等待事件(如信号量、延迟)暂停执行
  4. 挂起(Suspended):被显式挂起,不参与调度(需手动恢复)
  5. 删除(Deleted):任务已终止,等待资源回收

任务调度机制

FreeRTOS 采用 抢占式调度时间片轮转 的混合策略:

  1. 抢占规则

    • 高优先级任务就绪时 立即抢占 低优先级任务
    • 同优先级任务按 时间片轮转(默认为 1 个系统节拍)
  2. 调度触发条件

    • 系统节拍中断(Tick Interrupt)
    • 任务主动释放 CPU(taskYIELD()
    • 资源释放(如发送信号量、队列)

声明任务头文件

#include "FreeRTOS.h"
#include "task.h"

声明任务句柄(x_Handle),定义任务属性(任务名称,堆栈大小,优先级)(x_attributes)

/* myTask_01_led1 的定义 */
osThreadId_t myTask_01_led1Handle; // 声明myTask_01_led1 的句柄
const osThreadAttr_t myTask_01_led1_attributes = { // myTask_01_led1 的属性
  .name = "myTask_01_led1", // 任务名称
  .stack_size = 128 * 4, // 堆栈大小为 512 字节
  .priority = (osPriority_t) osPriorityLow, // 优先级为低
};

/* myTask02_led2_ 的定义 */
osThreadId_t myTask02_led2_Handle; // myTask02_led2_ 的句柄
const osThreadAttr_t myTask02_led2__attributes = { // myTask02_led2_ 的属性
  .name = "myTask02_led2_", // 任务名称
  .stack_size = 128 * 4, // 堆栈大小为 512 字节
  .priority = (osPriority_t) osPriorityLow, // 优先级为低
};

任务执行函数(for死循环或while死循环)

/* USER CODE END Header_myTask01_led1 */
void myTask01_led1(void *argument)
{
  /* USER CODE BEGIN myTask01_led1 */
  /* Infinite loop */
  for(;;)
  {
      /*用户编写执行程序
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
    osDelay(200);
    */
  }
  /* USER CODE END myTask01_led1 */
}

创建任务

使用osThreadNew()函数创建任务,传入任务入口函数指针,任务入口函数参数指针(无则NULL),任务属性结构体指针

osThreadNew()返回值保存到任务句柄(x_Handle)

 /* creation of myTask_01_led1 */	
  myTask_01_led1Handle = osThreadNew(myTask01_led1, NULL, &myTask_01_led1_attributes);
 //任务句柄                         	// 任务函数                    任务属性

  /* creation of myTask02_led2_ */
  myTask02_led2_Handle = osThreadNew(myTask02_led2, NULL, &myTask02_led2__attributes);

FreeRTOS初始化末尾创建任务,在初始化结束后,相同优先级任务按照时间片轮转执行,宏观则表现为任务同时执行

信号量

二值信号量:

二值信号量,可以理解为标志位

二值信号量(Binary Semaphore)在 FreeRTOS 中通常用于任务间的同步和互斥。
它的值只能是 0 或 1,适合用于实现简单的同步机制。以下是二值信号量的使用方法:

声明信号量头文件

#include "semphr.h"//操作信号量的头文件

声明二值信号量,创建二值信号量

SemaphoreHandle_t xBinarySemaphore;
xBinarySemaphore = xSemaphoreCreateBinary();

获取信号量

任务函数中:

if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {//
    // 成功获取信号量,执行相关操作
    // ...
}

释放信号量

xSemaphoreGive(xBinarySemaphore);

使用示例

//1. **任务 A**:等待信号量
void vTaskA(void *pvParameters) {
    while (1) {
        if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {
            // 处理任务
            // ...

            // 释放信号量,通知其他任务
            xSemaphoreGive(xBinarySemaphore);
        }
    }
}

//2. **任务 B**:释放信号量
void vTaskB(void *pvParameters) {
    while (1) {
        // 执行某些操作
        // ...

        // 释放信号量,通知任务 A
        xSemaphoreGive(xBinarySemaphore);

        // 等待一段时间
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

注意事项

  • 初始化: 二值信号量需要在使用前通过 xSemaphoreCreateBinary() 创建。
  • 首次释放: 在创建后,信号量默认是不可用的(值为0)。可以使用 xSemaphoreGive() 初始化为可用状态(值为1)。
  • 同步: 使用二值信号量可以确保任务的同步,比如在任务 A 中等待任务 B 释放信号量来继续执行。

这种信号量在实现任务间的简单同步时非常有用。

但可能出现优先级翻转问题

优先级翻转现象图解

TEXT任务优先级:Task_H(高) > Task_M(中) > Task_L(低)

时间轴 | 事件流
----------------------------------------
t0     Task_L 获取信号量
t1     Task_H 请求信号量 → 阻塞
t2     Task_M 就绪 → 抢占Task_L
t3     Task_M 长时间运行
t4     Task_L 无法继续执行 → 不能释放信号量
t5     Task_H 持续阻塞 → 系统实时性破坏

互斥信号量(Mutex)

优化方案:使用互斥信号量可以减少优先级翻转问题

解决办法:

使用互斥量替代二值信号量

定义互斥信号量句柄,属性

osMutexId_t tokenHandle;// 定义一个互斥锁句柄变量,用于后续操作互斥锁
const osMutexAttr_t token_attributes = {// 定义一个常量结构体,用于设置互斥锁的属性
  // 设置互斥锁的名称为 "token",便于调试和识别
  .name = "token"
};

创建互斥信号量

  // 创建一个新的互斥锁,并返回其句柄
  tokenHandle = osMutexNew(&token_attributes);
  // tokenHandle 变量,用于存储互斥锁的句柄
  // osMutexNew 函数,用于创建一个新的互斥锁
  // &token_attributes 指向互斥锁属性的指针,用于配置互斥锁的行为

获取信号量,释放信号量

	  // 尝试获取信号量tokenHandle,等待时间为portMAX_DELAY(无限等待直到获取成功)
	  if (xSemaphoreTake(tokenHandle, portMAX_DELAY) == pdTRUE) //
	  {
		 		 USART1_printf("%s\r\n",strHigh);
	  			HAL_Delay(10);
	  			// 释放信号量tokenHandle
	  			xSemaphoreGive(tokenHandle);
	  }

二值信号量与互斥量对比

特性 二值信号量 互斥量
优先级继承 不支持 支持
递归获取 不可
初始状态 可用
适用场景 事件通知、简单同步 资源互斥访问

计数信号量

定义计数信号量

/* Definitions for Sem_Tables */
osSemaphoreId_t Sem_TablesHandle;// 假设 Sem_Tables_attributes 是预定义的信号量属性结构体
const osSemaphoreAttr_t Sem_Tables_attributes = {
  .name = "Sem_Tables" // 设置信号量的名称
};

创建计数信号量

CountingSemHandle = osSemaphoreNew(5, 5, &CountingSem_attributes);
  • 第一个参数 5:信号量的最大计数值。

  • 第二个参数 5:信号量的初始计数值。

  • 第三个参数 &CountingSem_attributes:信号量的属性。

  • CountingSemHandle:这是信号量的句柄,用于在代码中引用该信号量。

从计数信号量内获取一个信号量

xSemaphoreTake(CountingSemHandle, pdMS_TO_TICKS(100)//(计数信号量(剩余)数值减少)

释放计数信号量

xSemaphoreGiveFromISR(CountingSemHandle, &highTaskWoken);//(计数信号量(剩余)数值增加)

注意:_FromISR后缀函数是在中断服务程序(ISR)中运行的函数,确保中断安全

事件组(EvenGroup)

声明事件组头文件

#include "event_groups.h"

先设置事件组掩码(二进制),相当于标志位

#define BITMASK_KEY_LEFT	(0b00000001<<2)//04     2事件位掩码
#define BITMASK_KEY_RIGHT	(0b00000001<<0)//01     0
#define BITMASK_uart_bit    (0b00000001<<1)//02      1

定义事件组句柄和属性。

eventGroupHandle = osEventFlagsNew(&eventGroup_attributes);//事件组句柄

/* Definitions for eventGroup */
osEventFlagsId_t eventGroupHandle;
const osEventFlagsAttr_t eventGroup_attributes = {
  .name = "eventGroup"
};

设置事件组标志位

//设置事件组标志位,例如BITMASK_KEY_LEFT标志位				
xEventGroupSetBits(eventGroupHandle, BITMASK_KEY_LEFT);//参数1:设置的事件组。参数2:设置的事件位

清除事件组掩码(事件组标志位)

//清除事件组掩码,相当于清除标志位
xEventGroupClearBits(eventGroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT);

事件组运用——阻塞等待,事件组成立后执行用户程序

		//以阻塞态等待事件组掩码
	  xEventGroupWaitBits(eventGroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT,
			pdTRUE, pdTRUE, portMAX_DELAY);//等待事件组中一个或多个事件位的设置,portMAX_DELAY处于阻塞状态一直等待
	/*
	 * eventGroupHandle 是创建事件组时获得的句柄
		BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT 是要等待的事件位掩码,这表示等待 BITMASK_KEY_LEFT 和 BITMASK_KEY_RIGHT 事件位被设置。
		pdTRUE 表示在返回时清除匹配的事件位。
		pdTRUE 表示等待所有指定的事件位被设置。	
		xWaitForAllBits,
		pdTRUE 等待所有目标位被设置
		仅需等待其中任何一个目标位被设置(pdFALSE)
		portMAX_DELAY 表示无限等待直到事件位被设置。
	 */

//等待事件组条件成立退出等待,执行用户程序,例闪烁led
	for (int i = 0; i < 10; ++i) 
	{
		USART1_printf("keyleft+keydowm\r\n");

		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);
		vTaskDelay(pdMS_TO_TICKS(500));
	}

二进制数打印打印事件组事件位
uint8_t temp_str[20];
//USART1_printf("Current event bits = %02X\r\n", xEventGroupGetBits(eventGroupHandle));
//获取 eventGroupHandle 事件组中当前设置的事件位,并以十六进制格式输出到 USART1。%02X 格式说明符确保输出的值是两位的十六进制数。
printBinary(xEventGroupGetBits(eventGroupHandle));//将事件掩码以二进制数打印

总结:先定义事件(组)掩码(标志位),
设置事件掩码:按键按下后将对应事件位值设为1 #define BITMASK_KEY_LEFT (0b00000001<<2)//04事件位掩码
在任务中阻塞读取事件位/组,成功读取则执行对应事件/事件组 xEventGroupWaitBits(eventGroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT,
清除事件位 xEventGroupClearBits(eventGroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT);

多任务同步(EventSync)

基于事件组

在任务进入循环前,使用事件组等待函数,让函数等待事件组事件位成立才进入循环,运行任务。以此达到多任务同步

多任务同步,
等待各个任务的事件位都置1,同时开始各各任务

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,  事件组句柄,指定你要操作的事件组。
const EventBits_t uxBitsToSet,   在事件组中设置的事件标志位掩码。这些位将被设置为 1const EventBits_t uxBitsToWaitFor,   你希望等待的事件标志位掩码。函数会等待这些位被设置为 1。
 TickType_t xTicksToWait )等待的时间,单位是 tick。可以使用 portMAX_DELAY 表示无限等待。
 
这个函数用于在 FreeRTOS 中同步事件组。它的参数说明如下:

xEventGroupSync(eventGroupHandle, BITMASK_KEY_RIGHT, BITMASK_SYNC, portMAX_DELAY);
这个 `xEventGroupSync` 函数用于同步 FreeRTOS 事件组。你传入的参数是:

- `eventGroupHandle`:事件组句柄。
- `BITMASK_KEY_RIGHT`:等待的事件标志位。
- `BITMASK_SYNC`:要设置的事件标志位。
- `portMAX_DELAY`:等待时间(无限等待)。

该程序同步点是三个按键都按下,
同步事件标志位为 按键一按下事件位|按键二按下事件位|按键三按下事件位

随意按下三个按键,同步事件开始,三个事件同时执行(多任务同步)

消息缓存区(MessageBuff)

消息缓存区是一种用于在任务之间传递数据的机制,它允许任务发送和接收不定长的消息。消息缓存区可以用于实现任务间的通信,而不需要预先知道消息的确切长度。

特点:

  1. 动态长度:消息缓存区可以处理任意长度的消息,这使得它非常适合于处理变长数据。
  2. 灵活的接口:消息缓存区提供了多种发送和接收函数,如 xMessageBufferSendxMessageBufferReceive,可以根据需要选择合适的接口。
  3. 阻塞和非阻塞操作:消息缓存区支持阻塞和非阻塞操作,可以根据任务的具体需求选择合适的操作模式。
  4. 内存管理:消息缓存区内部管理了所需的内存,用户不需要手动分配和释放内存。

消息缓存区的创建和使用通常包括以下步骤:

  1. 创建消息缓存区:使用 xMessageBufferCreate 函数创建一个消息缓存区,并返回一个 MessageBufferHandle_t 类型的句柄。
  2. 发送消息:使用 xMessageBufferSend 函数将消息发送到消息缓存区。该函数接受消息缓存区句柄、消息指针和消息长度作为参数。
  3. 接收消息:使用 xMessageBufferReceive 函数从消息缓存区接收消息。该函数接受消息缓存区句柄、接收缓冲区指针、缓冲区长度和等待时间作为参数。

定义消息缓冲区句柄,创建一个消息缓存区

#define MSG_BUFFER_LEN	50//消息缓冲区大小
MessageBufferHandle_t msgBuffer;//创建消息缓冲区句句柄
msgBuffer = xMessageBufferCreate(MSG_BUFFER_LEN);//创建消息缓冲区

实现用消息缓存区发送接收字符串并打印

发送
	uint8_t bytesCount = strlen(temp_str);//计算的字符串的长度,即字符串中字符的数量
  if (msgBuffer != NULL) {//判断消息缓存区是否成功创建
  				uint16_t realCnt =//返回值是成功发送的字节数
  						xMessageBufferSend(msgBuffer,temp_str,bytesCount,portMAX_DELAY);//消息缓冲区对象句柄,待发送数据的指针,数据长度,等待时间
  				USART1_printf("Write bytes = %d\r\n", realCnt);//打印成功发的数据的字节数
  			}
接收
uint8_t receive_data[MSG_MAX_LEN];//接收缓存区
	
//接收消息缓存区数据	 
 uint16_t realCnt = xMessageBufferReceive( msgBuffer, receive_data, MSG_MAX_LEN, portMAX_DELAY);//消息缓冲区句句柄,接收缓存区指针,接收消息缓存区的最大长度,等待时间
//realCnt:返回成功接收的数据长度
		

if (realCnt > 0) {
       			 // 确保 receive_data 以 null 结尾,或打印字节数据
                  if (realCnt < MSG_MAX_LEN) 
                  {
                    receive_data[realCnt] = '\0'; // 添加 null 终止符
                    USART1_printf("get Data = %s\r\n", receive_data); // 打印接收到的数据
                  } 
 USART1_printf("Read message bytes = %d\r\n", realCnt); // 打印接收到的字节数
}

任务通知

作用其一:模拟信号量

定义消息变量(32位)

uint32_t Notify_num=0;//创建要发送的任务通知变量(模拟消息队列)(只能是32位变量)
发布任务通知:
void Task_Send_Notify(void *argument)
{
  /* USER CODE BEGIN Task_Send_Notify */
	uint32_t Notify_num=0;//要发送的任务通知(模拟消息队列)

  /* Infinite loop */
  for(;;)
  {
	  Notify_num++;
			if (Task_2_Wait_GetHandle != NULL) //判断接收任务通知句柄是否存在
			{
				//BaseType_t highTaskWoken = pdFALSE;
				xTaskNotify(Task_2_Wait_GetHandle, Notify_num, eSetValueWithOverwrite);
				//任务通知,Task_ShowHandle为通知任务的句柄,
                 //Notify_num为通知的数据,
			    //通知操作类型。eSetValueWithOverwrite ,通知覆盖上一次通知值

			//	portYIELD_FROM_ISR(highTaskWoken);//用于在中断服务例程 (ISR) 结束时决定是否需要进行上下文切换的函数。
			}
			osDelay(200);
  }
  /* USER CODE END Task_Send_Notify */
}

此循环表示每200ms使变量Notify_num自增1,同时发布任务通知,通知接收任务(Task_2_Wait_GetHandle)

接收任务通知:
void Task_2_Wait_Get_Notify(void *argument)
{
  /* USER CODE BEGIN Task_2_Wait_Get_Notify */
	uint32_t pulNotification_Notify_num = 0;//同来接受通知值的变量
    
     uint32_t ulBitsToClearOnEntry = 0x00000000;//进入时要清除的通知位掩码,设置为0表示不清除任何位。
	 uint32_t ulBitsToClearOnExit = 0xFFFFFFFF;//退出时要清除的通知位掩码,这里设置为F,表示在接收到通知后清除所有位。

  /* Infinite loop */
  for(;;)
  {
	  		//if (xTaskNotifyWait(ulBitsToClearOnEntry, ulBitsToClearOnExit,
	  		//		&pulNotificationValue, portMAX_DELAY) == pdPASS)
	  			if (xTaskNotifyWait(0x00000000, 0xFFFFFFFF,&pulNotification_Notify_num, portMAX_DELAY) == pdPASS) //等待成功接收到通知,并将通知值写入pulNotificationValue变量
	  		{
	  			uint32_t Notify_num = pulNotification_Notify_num;//定义变量Notify_num接收任务通知传输的数据pulNotification_Notify_num
	  			uint8_t temp_str[20];
	  			USART1_printf("Notify_num= %d     \r\n", Notify_num);
	  		}
  }
  /* USER CODE END Task_2_Wait_Get_Notify */
}

if (xTaskNotifyWait(0x00000000, 0xFFFFFFFF,&pulNotification_Notify_num, portMAX_DELAY) == pdPASS)
//等待成功接收到通知,
pulNotification_Notify_num:存储任务通知的变量

需要设置变量将任务通知的变量拷贝出来,防止覆盖
uint32_t Notify_num = pulNotification_Notify_num;
//定义变量Notify_num接收任务通知传输的数据pulNotification_Notify_num

作用其二:模拟计数信号量

任务通知————模拟计数信号量

作用:计数信号量为0时
ulTaskNotifyTake(pdFALSE,portMAX_DELAY)函数阻塞等待
用以控制任务的执行次数


		xTaskNotifyGive(Task_2_Wait_GetHandle);
		//向Task_2_Wait_GetHandle句柄的任务发送任务通知,任务通知值加1

		
		//阻塞等待计数信号量
		BaseType_t xClearCountOnExit = pdFALSE;//作计数信号量,退出时通知值减1
	  	//BaseType_t xClearCountOnExit = pdTRUE;//作二值信号量,退出时通知值清0
	  	
		ulTaskNotifyTake(xClearCountOnExit,portMAX_DELAY);//阻塞等待计数信号量增加为非0
		

消息队列

作用:用于任务间通信,交换变量值,相当于库函数全局变量

声明头文件

#include "queue.h" // 操作队列的头文件

声明句柄,设置消息队列属性(在此为命名)

osMessageQueueId_t myQueue_NUMHandle;
const osMessageQueueAttr_t myQueue_NUM_attributes = {
  .name = "myQueue_NUM"
};

创建消息队列

  myQueue_NUMHandle = osMessageQueueNew (16, sizeof(uint16_t), &myQueue_NUM_attributes);

写入消息队列

	BaseType_t err = xQueueSendToBack(myQueue_NUMHandle, &num1, pdMS_TO_TICKS(50));	//将num1值写入消息队列myQueue_NUMHandle
	if (err == errQUEUE_FULL)//判断是否写入队列,写入失败则是队列已满
		{//写入失败
		xQueueReset(myQueue_NUMHandle);//清空队列
		}

接收消息队列

	uint16_t num;//定义变量接消息队列信息
  
	if (xQueueReceive(myQueue_NUMHandle, &num, pdMS_TO_TICKS(50)) != pdTRUE) {
			continue; //用num变量接收myQueue_NUMHandle消息队列,判断是否接收成功,和等待时间
	}//从队列中接收一条消息。如果接收失败(例如超时),则跳过当前循环的剩余部分并重新尝试接收。

	USART1_printf("num = %d\r\n", num);//打印接收到的消息队列信息

打印消息队列属性

	uint8_t queueName[30];
	USART1_printf("Queue Name = %s\r\n", pcQueueGetName(myQueue_NUMHandle));	//打印消息队列名字

	uint8_t queueSizeString[30];
	USART1_printf("Queue size = %d\r\n", uxQueueSpacesAvailable(myQueue_NUMHandle));	//打印队列中当前等待的消息数量

软件定时器

分为周期定时器和单次定时器

周期定时器:每个定时周期结束执行一次回调函数

单次定时器:单个周期,周期结束执行单次回调函数
需要重启定时器才能重新开始定时周期

声明头文件

#include "timers.h"//软件定时器头文件

声明句柄,设置属性(在此为命名)

    /* Definitions for Timer_Periodic */
osTimerId_t Timer_PeriodicHandle;
const osTimerAttr_t Timer_Periodic_attributes = {
  .name = "Timer_Periodic"
};
/* Definitions for Timer_Once */
osTimerId_t Timer_OnceHandle;
const osTimerAttr_t Timer_Once_attributes = {
  .name = "Timer_Once"
};

设置软件定时器周期

xTimerChangePeriod(Timer_PeriodicHandle, pdMS_TO_TICKS(1000), portMAX_DELAY);//设置周期定时器周期1000ms
xTimerChangePeriod(Timer_OnceHandle, pdMS_TO_TICKS(5000), portMAX_DELAY//设置单次定时器周期5000ms=5s

开启软件定时器

 xTimerStart(Timer_PeriodicHandle, portMAX_DELAY);
 xTimerStart(Timer_OnceHandle, portMAX_DELAY);

定时器回调函数

//周期定时器回调函数
 void Callback_Timer_Periodic(void *argument)
{
  /* USER CODE BEGIN Callback_Timer_Periodic */
	counter++;
	USART1_printf("Second=%d\r\n",counter);
	if(counter%10==0)xTimerReset(Timer_OnceHandle, portMAX_DELAY);//重启单次定时器
  /* USER CODE END Callback_Timer_Periodic */
}
 
  //单次定时器回调函数
 /* Callback_Timer_Once function */
void Callback_Timer_Once(void *argument)//5s后执行的单次定时器回调函数
{
  /* USER CODE BEGIN Callback_Timer_Once */
	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);
   // HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);//
  /* USER CODE END Callback_Timer_Once */
}

操作定时器

判断单次定时器是否处于工作状态

if (xTimerIsTimerActive(Timer_OnceHandle) == pdFALSE) {

}

重启单次定时器

xTimerReset(Timer_OnceHandle, portMAX_DELAY);
posted @   沁拒离  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示