stm32_<一文通>_cubemx_freertos

news2025/2/23 17:18:57

文章目录

  • 前言
  • 一、任务调度
    • 1.1 延时
      • 1.1.1 相对延时
      • 1.1.2 绝对延时
    • 1.2 挂起和恢复
      • 1.2.1 cmsis的挂起和恢复函数
      • 1.2.2 freertos的挂起和恢复函数
    • 1.3 删除
      • 1.3.1 cmsis的删除任务函数
      • 1.3.2 freertos的删除任务函数
  • 二、Freertos任务与中断
  • 三、消息队列
    • 3.1 写入和读取一个数据
    • 3.2 写入和读取一个段数据 ==(指针)== (分配内存法)
  • 四、二值信号量
  • 五、互斥量
  • 六、事件组
    • 6.1 设置位
    • 6.2 等待位
  • 七、任务通知
    • 7.1 CMSIS-RTOS API 线程标志
    • 7.2 Free-RTOS API 任务通知 ==(待填)==
  • 八、流缓冲区 ==(待填)==
  • 九、消息缓冲区 ==(待填)==
  • 十、软件定时器
    • 10.1 任务中开启定时器
      • 10.1.1 周期定时器
      • 10.1.2单次定时器
    • 10.2 中断中开启定时器 ==(待填)==
  • 十一、空闲任务与低功耗 ==(待填)==
  • 总结


前言

cubemx_freertos
在这里插入图片描述


一、任务调度

文件

1.1 延时

1.1.1 相对延时

vTaskDelay(ticks1);

void appTask_led1(void *argument)
{
  /* USER CODE BEGIN appTask_led1 */
	TickType_t ticks1=pdMS_TO_TICKS(1000);
  /* Infinite loop */
  for(;;)
  {
		HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
		
    vTaskDelay(ticks1);
  }
  /* USER CODE END appTask_led1 */
}

osDelay();

void appTask_led1(void *argument)
{
  /* USER CODE BEGIN appTask_led1 */
  /* Infinite loop */
  for(;;)
  {
		HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
		osDelay(1000);
  }
  /* USER CODE END appTask_led1 */
}

1.1.2 绝对延时

vTaskDelayUntil(&wake_time,ticks2);

void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */
	TickType_t ticks2=pdMS_TO_TICKS(2000);	
	TickType_t wake_time=xTaskGetTickCount();
	
  /* Infinite loop */
  for(;;)
  {
	HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
    vTaskDelayUntil(&wake_time,ticks2);		
  }
  /* USER CODE END appTask_led2 */
}

1.2 挂起和恢复

led1和led2程序不变 增加两个按键,当其中一个按键按下挂起任务,另一个按键按下恢复任务。

1.2.1 cmsis的挂起和恢复函数

osThreadSuspend

osThreadResume

void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(key0_GPIO_Port,key0_Pin)==GPIO_PIN_RESET)
		{
			osThreadSuspend(Task_led1Handle);
			
			
		}
    osDelay(20);
  }
  /* USER CODE END appTask_key1 */
}

void appTask_key2(void *argument)
{
  /* USER CODE BEGIN appTask_key2 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(key1_GPIO_Port,key1_Pin)==GPIO_PIN_RESET)
		{
			osThreadResume(Task_led1Handle);
		}
    osDelay(20);
  }
  /* USER CODE END appTask_key2 */
}

1.2.2 freertos的挂起和恢复函数

vTaskSuspend

vTaskResume

void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(key0_GPIO_Port,key0_Pin)==GPIO_PIN_RESET)
		{
			vTaskSuspend((TaskHandle_t)Task_led1Handle);
			
		}
    osDelay(20);
  }
  /* USER CODE END appTask_key1 */
}
void appTask_key2(void *argument)
{
  /* USER CODE BEGIN appTask_key2 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(key1_GPIO_Port,key1_Pin)==GPIO_PIN_RESET)
		{
			
			vTaskResume((TaskHandle_t)Task_led1Handle);

		}
    osDelay(20);
  }
  /* USER CODE END appTask_key2 */
}

1.3 删除

1.3.1 cmsis的删除任务函数

osThreadTerminate

void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(key0_GPIO_Port,key0_Pin)==GPIO_PIN_RESET)
		{
			osThreadTerminate(Task_led1Handle);
		}
    osDelay(20);
  }
  /* USER CODE END appTask_key1 */
}

1.3.2 freertos的删除任务函数

直接使用vTaskDelete会导致程序卡死,进入HardFault_Handler函数

二、Freertos任务与中断

介绍Freertos与硬件中断的关系以及如何正确使用硬件中断。

FreeRTOS中的任务与中断的关系:
在FreeRTOS中,任务(Task)和中断服务例程(Interrupt Service Routine,ISR)是两种不同的执行环境。任务通常用于执行应用级别的功能,比如处理用户输入,执行通信协议等,而ISR则用于处理硬件中断,比如外部中断,定时器中断等。
在FreeRTOS中,中断优先级是高于任何任务的。这意味着,当中断事件发生时,不论CPU正在执行何种任务,都会立刻停下来,跳转到相应的ISR来处理中断。一旦ISR处理完成,CPU就会恢复之前被打断的任务继续执行。
因此,为了保证实时性,通常我们会把需要快速响应的操作放在ISR中处理。但由于ISR的执行会打断任务的运行,如果ISR执行时间过长,可能会导致任务延时,影响系统的实时性。所以,通常我们建议尽量减少在ISR中执行的操作,比如可以在ISR中只做一些简单的标记或者数据传递,然后通过任务来做实际的处理。

FreeRTOS中使用任务与中断需要注意的点:
不同的API: FreeRTOS为任务和ISR提供了不同的API。比如任务可以使用xQueueSend来发送消息,而ISR需要使用xQueueSendFromISR。尽管这两个函数的功能类似,但后者是为中断环境特别设计的,可以确保在中断中安全使用。
中断嵌套: 在某些高性能的MCU中,支持中断嵌套,也就是说一个ISR可以被另一个优先级更高的ISR打断。在这种情况下,你需要正确地设置你的中断优先级,确保你的中断能被正确地响应。
临界区保护: 在任务中,如果你需要访问一些共享资源,或者执行一些不能被打断的操作,你需要使用临界区来保护这些操作。在FreeRTOS中,你可以使用taskENTER_CRITICAL和taskEXIT_CRITICAL来定义一个临界区。在这个区域内的代码不会被任何中断打断。但注意临界区不应过长,否则会影响中断的响应。
中断同步: 很多时候,我们需要在ISR和任务之间进行一些同步操作,比如任务需要等待某个中断发生。在FreeRTOS中,你可以使用二值信号量(Binary Semaphore)或者直接任务通知(Task Notification)来实现这种同步。

避免的问题:
避免在ISR中执行过多的操作: 如上所述,ISR的执行会打断任务,如果ISR执行时间过长,可能会导致任务延时,影响系统的实时性。
避免在ISR中调用可能阻塞的函数: 这一点非常重要,因为ISR不能被阻塞。如果你在ISR中调用了可能阻塞的函数(比如xQueueSend),可能会导致系统崩溃。
避免在中断中直接操作共享资源: 如果你在ISR中直接操作一些在任务中也会被访问的共享资源,可能会导致数据不一致。在这种情况下,你可以使用队列(Queue)或者二值信号量(Binary Semaphore)来在任务和ISR之间传递数据。
避免频繁的中断: 如果你的系统中有过多的中断发生,可能会导致CPU大部分时间都在处理中断,而无法执行任务。这通常会导致系统的性能下降,甚至崩溃。你需要合理地设计你的硬件和软件,以减少不必要的中断。

三、消息队列

文件

队列(Queue)是一种非常重要的通信机制,可以用来在任务之间,或者任务与中断之间传递数据
消息队列在FreeRTOS或CMSIS-RTOS中每次发送或接收的都是一个消息。这个“消息”可以是一个整数,也可以是一个指针。

在这里插入图片描述

osMessageQueuePut() and osMessageQueueGet() 都可以在中断服务程序中使用。

3.1 写入和读取一个数据

osMessageQueuePut 将一个新的项目发送到队列的末尾
osMessageQueueGet 从队列的前面读取一个(读取以后会自动删除那个)

void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */

	uint32_t data = 0;		
  /* Infinite loop */
  for(;;)
  {
		data++;	
		osMessageQueuePut(myQueue01Handle,&data,0,0);
		osDelay(1000);
  }
  /* USER CODE END appTask_led2 */
}
void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
	uint32_t receivedData = 0;
  /* Infinite loop */
  for(;;)
  {
		osMessageQueueGet(myQueue01Handle, &receivedData, 0, osWaitForever);
		/* 处理数据,这里我们只是简单地打印出来 */
		printf("Received data: %d\n", receivedData);

  }
  /* USER CODE END appTask_key1 */
}

osMessageQueuePuttimeout参数是一个表示操作超时时间的毫秒数,这用于在尝试将消息放入队列时设置阻塞时间。以下是一些常用的设定值:

  1. 如果你希望调用osMessageQueuePut后立即返回,无论消息是否成功放入队列,你可以设定timeout为0。这是非阻塞调用的方式。
  2. 如果你希望osMessageQueuePut在队列满的情况下阻塞,直到有空间可以放入新的消息,可以设定timeoutosWaitForever。这将会在队列空间可用之前一直阻塞当前任务。
  3. 如果你希望osMessageQueuePut在队列满的情况下阻塞一段特定的时间,可以设定timeout为这个时间的毫秒数。例如,timeout设定为1000,表示如果队列满,osMessageQueuePut将会阻塞最多1秒钟。如果在这1秒钟内队列有空间可用,它会立即返回成功。否则,1秒后它会因超时而返回失败。

osMessageQueueGet() 函数有一个参数 osWaitForever。这意味着如果队列中没有数据,appTask_key1将会一直阻塞等待,直到有数据到达队列。
在这种情况下,FreeRTOS会自动将osMessageQueueGet() 置入阻塞状态,其CPU时间片会被其他任务或者空闲任务(idle task)所使用。只有当队列中有数据到达时,osMessageQueueGet() 才会从阻塞状态被唤醒并开始运行。
因此,即使appTask_key1并没有使用 osDelay() 函数来主动让出CPU,但由于使用了 osMessageQueueGet() 函数的阻塞等待,也能保证其在没有数据可处理时不会占用CPU资源。

3.2 写入和读取一个段数据 (指针) (分配内存法)

创建用于存储数据的缓冲区。这个缓冲区可以是静态分配的,也可以是动态分配的,具体取决于你的需求和系统资源。
当需要发送数据时,将数据复制到缓冲区,并发送指向缓冲区的指针作为消息队列的消息。
接收任务收到指针后,可以直接访问缓冲区中的数据。
当接收任务完成对数据的处理后,如果缓冲区是动态分配的,应该释放它。

osMessageQueuePut
osMessageQueueGet

pvPortMalloc
vPortFree

#define ARRAY_SIZE 20
void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */


  /* Infinite loop */
  for(;;)
  {
		uint32_t* array = (uint32_t*)pvPortMalloc(ARRAY_SIZE * sizeof(uint32_t));
    for (int i = 0; i < ARRAY_SIZE; i++) 
		{
        array[i] = i;
    }

		osMessageQueuePut(myQueue01Handle, &array, 0, 0);
		osDelay(1000);
  }
  /* USER CODE END appTask_led2 */
}
void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
  /* Infinite loop */
  for(;;)
  {
		uint32_t* receivedArray;
    osMessageQueueGet(myQueue01Handle, &receivedArray, 0, osWaitForever);
    // Process the array
    for (int i = 0; i < ARRAY_SIZE; i++) {
        printf("Received: %d\n", receivedArray[i]);
			HAL_Delay(10);
    }
		vPortFree(receivedArray);

  }
  /* USER CODE END appTask_key1 */
}

appTask_led2 中,我们使用 pvPortMalloc 分配了一块新的内存,并将指针指向这块内存。然后我们把这个指针放入消息队列。在 appTask_key1 中,我们从队列中获取指针,并把它指向 uint32_t* receivedArray,这个指针现在指向 appTask_led2 分配的那块内存。
appTask_key1 完成处理这块内存后,它应该使用 vPortFree 释放这块内存,这样它就可以被系统再次使用。这个过程不断重复,每次 appTask_led2 都会分配新的内存块,并把它的地址发送给 appTask_key1

四、二值信号量

文件

二值信号量可以作用与进程间的通信,
如果不使用二值信号量而是自定义一个全局变量来实现同步,则任务需要不停的查询全局变量的值,而二值信号量可以使任务直接进入阻塞态等待。二值信号量进行同步的效率高

在这里插入图片描述

osSemaphoreAcquire(): 获取一个信号量。如果信号量当前计数为0,则线程将阻塞(等待)直到信号量变得可用。
osSemaphoreRelease(): 释放一个信号量,增加信号量的计数值。

void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */
  /* Infinite loop */
  for(;;)
  {
		osSemaphoreAcquire(Bin_key0_readyHandle,osWaitForever);
		printf("hello world\n");
		
  }
  /* USER CODE END appTask_led2 */
}
void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(key0_GPIO_Port,key0_Pin)==GPIO_PIN_RESET)
		{
			osSemaphoreRelease(Bin_key0_readyHandle);
		}
  }
  /* USER CODE END appTask_key1 */
}

本代码段的本意是按下按键会存入二值信号量,在appTask_led2中进行读取当有二值信号量的时候就会执行打印。

但是

由于cubemx生成的代码中导致二值信号量初始状态就已经存入了一个信号量,导致appTask_led2中打印程序会执行一次。
目前cubemx还无法修改这个值只能在void MX_FREERTOS_Init(void) 函数中将 Bin_key0_readyHandle = osSemaphoreNew(1, 1, &Bin_key0_ready_attributes);中的第二个参数1修改为0
即如下 Bin_key0_readyHandle = osSemaphoreNew(1, 0, &Bin_key0_ready_attributes);
但是这样会导致cubemx下次生成代码的时候把这里重置。目前无完美解决方案。

有效方案是:
在使用那个二值信号量的任务主循环之前先调用一次, osSemaphoreAcquire(Bin_key0_readyHandle,0);
要注意位置!!

void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */
  /* Infinite loop */
	osSemaphoreAcquire(Bin_key0_readyHandle,0);
  for(;;)
  {
		osSemaphoreAcquire(Bin_key0_readyHandle,osWaitForever);
		printf("hello world\n");
  }
  /* USER CODE END appTask_led2 */
}

五、互斥量

互斥量(mutex)在实时操作系统中常常被用于保护共享资源,避免在并发访问时出现问题。使用互斥量可以确保一次只有一个任务访问特定的共享资源。

在这里插入图片描述

互斥量不可以在ISR中使用
osMutexAcquire(): 获取互斥量,如果互斥量不可用,任务会被阻塞(根据指定的超时时间)。
osMutexRelease(): 释放互斥量,使其他任务能获取它。

void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */
  /* Infinite loop */

  for(;;)
  {
		osMutexAcquire(usart_MutexHandle, osWaitForever);
		// Use the UART here...
		printf("hello world user cc\n");		
		osMutexRelease(usart_MutexHandle);
		osDelay(20);
		
  }
  /* USER CODE END appTask_led2 */
}
void appTask_key1(void *argument)
{
  /* USER CODE BEGIN appTask_key1 */
  /* Infinite loop */
  for(;;)
  {
		osMutexAcquire(usart_MutexHandle, osWaitForever);
		printf("hello\n");		
		osMutexRelease(usart_MutexHandle);
		osDelay(5);
  }
  /* USER CODE END appTask_key1 */
}

实际上,互斥量的真正价值在于它能够保护那些可能被多个任务同时访问的共享资源,例如,当你在实现一个驱动程序,或者需要访问一个共享数据结构时,互斥量是非常有用的。
再者,互斥量提供了一种方法来处理优先级反转的问题。如果一个低优先级的任务获取了互斥量,而此时一个高优先级的任务也需要这个互斥量,那么FreeRTOS会临时提高低优先级任务的优先级,以防止高优先级任务被无限期地阻塞。这是一个非常重要的特性,可以在复杂的实时系统中确保任务的可响应性。

六、事件组

文件
在这里插入图片描述

事件组(Event Groups)是一种同步机制,允许一个任务等待一个或多个位在一个事件组中被设置。当任务等待一组事件时,可以选择等待所有事件位都被设置,或者任何一个事件位被设置

osEventFlagsNew:创建一个新的事件标志组。
osEventFlagsSet:在一个事件标志组中设置一个或多个位。可在中断中使用
osEventFlagsClear:在一个事件标志组中清除一个或多个位。可在中断中使用
osEventFlagsWait:等待一个或多个位在一个事件标志组中被设置。

6.1 设置位

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if (huart->Instance == USART1)
	{
		memcpy(MainBuf_1, RxBuf_1, Size);	//将接收缓冲区的数据复制到主缓冲区
		
		/* 再次启动 DMA */
		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *) RxBuf_1, RxBuf_SIZE_1);
		__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
		
		if(MainBuf_1[0]==1)
		{
			osEventFlagsSet(myEvent01Handle,EVENT_0_BIT);
		}
		if(MainBuf_1[0]==2)
		{
			osEventFlagsSet(myEvent01Handle,EVENT_1_BIT);
		}
	}

}

6.2 等待位

当两个标志位都被置为的时候才能进行任务。
flags 得到的值是EVENT_0_BIT和EVENT_1_BIT相与的值。

所以

#define EVENT_0_BIT (1 << 0) // Event bit 0, which corresponds to event 1

#define EVENT_1_BIT (1 << 1) // Event bit 1, which corresponds to event 2

其他的就应该 (1<<?)

  for(;;)
  {
		
		uint32_t flags = osEventFlagsWait(myEvent01Handle,EVENT_0_BIT|EVENT_1_BIT,osFlagsWaitAll,osWaitForever);

		if (flags & EVENT_0_BIT) 
		{
      // Event 1 has occurred
			printf("over 1\n");
    	}
    		if (flags & EVENT_1_BIT) {
      		// Event 2 has occurred
			printf("over 2\n");
    	}
		printf("over 5\n");		
		
  	}

当只有一个事件需要等待的时候填osFlagsWaitAllosFlagsWaitAny是一样的效果
如下
osEventFlagsWait(myEvent01Handle, EVENT_0_BIT, osFlagsWaitAny, osWaitForever);
`osEventFlagsWait(myEvent01Handle, EVENT_0_BIT, osFlagsWaitAll, osWaitForever);

osFlagsNoClear 是一个选项,当你使用 osEventFlagsWait() 函数时,你可以选择是否在等待的事件标志位被检测到后自动清除这些标志位。
如果你使用了 osFlagsNoClear 选项,那么当事件标志位被 osEventFlagsWait() 检测到后,这些标志位不会被自动清除,它们将保持设置的状态。
如果你没有使用 osFlagsNoClear 选项,那么在 osEventFlagsWait() 检测到事件标志位后,这些标志位会被自动清除。
所以,osFlagsNoClear 的使用取决于你的具体需求。如果你希望事件标志位在被一个任务检测到后仍然保持设置的状态,那么你可以使用 osFlagsNoClear。如果你希望事件标志位在被一个任务检测到后立即被清除,那么你就不应该使用 osFlagsNoClear

七、任务通知

任务通知是FreeRTOS为每个任务提供的一种轻量级、低延迟的机制,可以用来向任务发送一个事件或者唤醒一个任务。FreeRTOS任务通知功能的优点在于它比队列、信号量等内核对象消耗更少的RAM,并且速度更快。任务通知可以被看作是只有一个数据槽的邮箱。

7.1 CMSIS-RTOS API 线程标志

无需配置cubemx

任务通知的线程标志(Thread Flags)

osThreadFlagsSet(): 用于给指定任务发送通知。可在中断中使用
osThreadFlagsClear(): 用于清除任务的通知标志。
osThreadFlagsWait(): 用于让任务等待接收通知。

#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#define Thread_0_BIT (1 << 0)  // Event bit 0, which corresponds to event 1
#define Thread_1_BIT (1 << 1)  // Event bit 1, which corresponds to event 2

extern osThreadId_t Task_led2Handle;

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if (huart->Instance == USART1)
	{
		memcpy(MainBuf_1, RxBuf_1, Size);	//将接收缓冲区的数据复制到主缓冲区
		/* 再次启动 DMA */
		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *) RxBuf_1, RxBuf_SIZE_1);
		__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
		if(MainBuf_1[0]==1)
		{
			osThreadFlagsSet(Task_led2Handle,Thread_0_BIT);
			//xTaskNotifyFromISR()
		}
		if(MainBuf_1[0]==2)
		{
			osThreadFlagsSet(Task_led2Handle,Thread_1_BIT);
		}
	}
}

void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */
  /* Infinite loop */
#define Thread_0_BIT (1 << 0)  // Event bit 0, which corresponds to event 1
#define Thread_1_BIT (1 << 1)  // Event bit 1, which corresponds to event 2
  for(;;)
  {
		uint32_t flags = osThreadFlagsWait(Thread_0_BIT|Thread_1_BIT,osFlagsWaitAny,osWaitForever);
		if (flags & Thread_0_BIT) 
		{
		// Event 1 has occurred
			printf("over 1\n");
		}
		if (flags & Thread_1_BIT) {
				// Event 2 has occurred
			printf("over 2\n");
		}
		printf("over 5\n");		
		
  }
  /* USER CODE END appTask_led2 */
}

任务通知(线程标志)和事件组在功能上确实有很多相似之处,都可以用于通知线程某种状态的发生。然而,它们之间还是有一些关键的区别:

适用对象不同: 任务通知是针对特定任务的,而事件组则可以被多个任务共享。也就是说,任务通知只能由任务自身或者其他任务来设置、清除和等待,而事件组可以被任何拥有其句柄的任务设置、清除和等待。
性能: 任务通知通常具有更高的效率。在FreeRTOS中,任务通知是直接实现在TCB(任务控制块)中的,这使得它在处理速度和内存使用上优于事件标志。
复杂性: 任务通知比事件标志简单,不支持“自动清除”和“多任务等待同一事件”等高级功能。如果你的需求比较简单,可能会更喜欢任务通知的简洁性。然而,如果你需要更复杂的同步机制,可能会发现事件标志更有用。

7.2 Free-RTOS API 任务通知 (待填)

在原生的FreeRTOS API中,任务通知(Task Notification)功能是非常强大的。除了基本的二值信号量和事件组功能外,它还可以做到"直接到任务"的通信,包括发送整数值,设置/清除/切换二进制位等等。这使得任务通知在很多场景下可以替代队列、信号量、事件组等,而且由于其实现简单直接,效率更高。
然而,在CMSIS-RTOS API中,为了通用性和简化,这个复杂的功能被划分为线程标志(Thread Flags)和事件标志(Event Flags)。其中的线程标志基本上等价于原生FreeRTOS中的事件组功能,即每一位代表一个事件,可以单独设置、清除、等待。这确实减少了FreeRTOS任务通知的一些功能。

八、流缓冲区 (待填)

九、消息缓冲区 (待填)

十、软件定时器

软件定时器是一个非常有用的功能,它允许你在指定的时间后执行某个函数,或者每隔指定的时间执行某个函数。这个功能非常适合用来执行周期性的任务,比如每隔一秒钟闪烁一次LED,或者每隔一分钟检查一次温度等。

osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks): 启动或重新启动一个软件定时器。
timer_id: 这是你要启动的定时器的ID,这个ID是osTimerNew()函数返回的。
ticks: 这是定时器的时间长度(以系统时钟周期为单位)。
osStatus_t osTimerStop (osTimerId_t timer_id): 停止一个软件定时器。
timer_id: 这是你要停止的定时器的ID。
osStatus_t osTimerDelete (osTimerId_t timer_id): 删除一个软件定时器。
timer_id: 这是你要删除的定时器的ID。

10.1 任务中开启定时器

10.1.1 周期定时器

在这里插入图片描述

#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#define Thread_0_BIT (1 << 0)  // Event bit 0, which corresponds to event 1
#define Thread_1_BIT (1 << 1)  // Event bit 1, which corresponds to event 2

extern osThreadId_t Task_led2Handle;

extern osTimerId_t myTimer01Handle;

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if (huart->Instance == USART1)
	{
		memcpy(MainBuf_1, RxBuf_1, Size);	//将接收缓冲区的数据复制到主缓冲区
		
		/* 再次启动 DMA */
		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *) RxBuf_1, RxBuf_SIZE_1);
		__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
		
		if(MainBuf_1[0]==1)
		{
			osThreadFlagsSet(Task_led2Handle,Thread_0_BIT);
			
		}
		if(MainBuf_1[0]==2)
		{
			osThreadFlagsSet(Task_led2Handle,Thread_1_BIT);
		}
		
	}

}
void appTask_led2(void *argument)
{
  /* USER CODE BEGIN appTask_led2 */
  /* Infinite loop */
#define Thread_0_BIT (1 << 0)  // Event bit 0, which corresponds to event 1
#define Thread_1_BIT (1 << 1)  // Event bit 1, which corresponds to event 2
  for(;;)
  {
		uint32_t flags = osThreadFlagsWait(Thread_0_BIT,osFlagsWaitAny,osWaitForever);
		osTimerStart(myTimer01Handle,1000);
		
  }
  /* USER CODE END appTask_led2 */
}
void Callback01(void *argument)
{
  /* USER CODE BEGIN Callback01 */
		HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
		HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
  /* USER CODE END Callback01 */
}

由于直接在中断函数里面调用osTimerStart(myTimer01Handle,1000);不成功所以直接用第七章的任务通知去一个任务,在另一个任务中来开启软件定时器。

10.1.2单次定时器

对于单次定时器,定时器在到期并执行回调函数后会自动停止,除非再次调用 osTimerStart()
代码与上面的相同

在这里插入图片描述

10.2 中断中开启定时器 (待填)

十一、空闲任务与低功耗 (待填)


总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/729518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6阶高清视频滤波驱动MS1681

MS1681 是一个单通道视频缓冲器&#xff0c;它内部集成6dB 增益的轨到轨输出驱动器和6 阶输出重建滤波器。MS1681 的-3dB 带宽为35MHz&#xff0c;压摆率为160V/us。MS1681 比无源LC 滤波器与外加驱动的解决方案能提供更好的图像质量。它单电源供电范围为2.5V 到5.5V&#xff0…

什么是提示词工程师?

前言 你可能听说过人工智能模型&#xff0c;但你是否知道&#xff0c;背后的神奇之处源自于那些执着于提示设计和优化的专业人员&#xff1f;提示词工程师是引导我们与机器对话的幕后英雄&#xff0c;他们通过精心构造的提示&#xff0c;让模型理解我们的意图、解答问题&#…

React + TypeScript 实践

主要内容包括准备知识、如何引入 React、函数式组件的声明方式、Hooks、useRef<T>、useEffect、useMemo<T> / useCallback<T>、自定义 Hooks、默认属性 defaultProps、Types or Interfaces、获取未导出的 Type、Props、常用 Props ts 类型、常用 React 属性类…

zabbix基础4——自定义监控案例

文章目录 一、监控进程二、监控日志三、监控mysql主从四、监控mysql延迟 一、监控进程 示例&#xff1a;监控客户端上的httpd服务进程&#xff0c;当进程书少于1时&#xff0c;说明服务已经挂掉&#xff0c;需要及时处理。 1.客户端开启自定义监控功能。 vim /usr/local/etc/…

YApi-高效、易用、功能强大的可视化接口管理平台——(一)使用 Docker 本地部署

Docker 本地部署 YApi 安装 Docker安装设置 USTC 镜像启动 Docker Docker 安装 MongoDBDocker 安装 YApi登录 YApi 本内容以虚拟机【系统&#xff1a;Centos7】为例&#xff0c;云服务器步骤相同。使用Docker 的方式搭建 YApi&#xff0c;拉取 MongoDB 镜像和 YApi 镜像即可。 …

SpringBoot学习——追根溯源servlet是啥,tomcat是啥,maven是啥 springBoot项目初步,maven构建,打包 测试

目录 引出追根溯源&#xff0c;过渡衔接servlet是啥&#xff1f;tomcat是啥&#xff1f; 前后端开发的模式1.开发模式&#xff1a;JavaWeb&#xff1a;MVC模型2.Web&#xff1a;Vue&#xff0c;MVVC模型3.后端相关3.1 同步与异步3.2 Controller层3.3 Service层&#xff1a;要加…

【C语言基础】函数(2)

在函数&#xff08;1&#xff09;中我们已经讲过了函数的定义&#xff0c;形参与实参&#xff0c;函数的调用&#xff0c;局部变量与栈内存 接下来还有几个要强调的函数相关知识。 一、静态函数 静态函数是在函数声明前加上关键字 static 的函数。静态函数在C语言中具有以…

让计算机讲话

std ::cout是什么&#xff1f; 首先 std是命名空间后面会讲。STD&#xff08;standard标准&#xff09; cout和printf就呢让计算机说话 最早的使用打印机&#xff0c; std::cout相当于调用了一个特殊的函数。 后面需要加<<隔开。 知道最基本的知识就OK了 c的风格 函数…

API接口在软件开发中扮演着重要的角色

随着互联网的快速发展&#xff0c;API&#xff08;Application Programming Interface&#xff09;接口在软件开发中扮演着重要的角色。调试API接口是确保系统正常运行的关键步骤之一。本文将介绍如何选择适合的方法进行API接口调试&#xff0c;以确保开发过程的高效进行和应用…

20.光敏传感器

1.光敏传感器介绍&#xff1a; 光敏二极管(光敏电阻),作为光敏传感器&#xff1b;光敏二极管也称光电二极管&#xff1b;光敏二极管与半导体二极管在结构上类似&#xff0c;其管芯是一个具有光敏特征的PN结&#xff0c;具有单向导电性&#xff0c;因此工作时需要加上反向电压。…

【ECharts系列】ECharts 鼠标悬停线格式化

问题描述&#xff1a; 折线图有很多数据&#xff0c;鼠标悬停时&#xff0c;针对X轴&#xff0c;Y轴数据进行格式化&#xff0c;例如X的时间戳&#xff0c;格式化为时分秒&#xff0c;Y轴保留两位小时 希望效果图&#xff1a; 方案1&#xff1a; 在这个示例中&#xff0c;X轴依…

指针进阶(二)

目录 函数指针数组 指向函数指针数组的指针 回调函数 回调函数模拟实现qsort&#xff08;快速排序&#xff09; 整型数组的排序 结构题排序 按年龄排序 按名字排序 模拟实现qsort函数&#xff08;冒泡排序&#xff09; 函数指针数组 数组是一个存放相同类型数据的存储空…

【Linux】gcc/g++的使用 自动化构建工具make/makefile的使用

一.gcc的使用 在学习C语言时&#xff0c;我们了解了预处理阶段要做的事&#xff1a;->预处理 这其中会经历两个阶段&#xff1a;编译和链接 而编译又分为三个阶段&#xff1a;预编译&#xff0c;编译&#xff0c;汇编 通过不同的选项&#xff0c;可以让gcc停在以上相应的阶段…

ArcGis Pro如何通过C#进行插件开发?

文章目录 0.引言1.开发工具准备2.VS&#xff08;C#&#xff09;创建ArcGIS Pro模块加载项3.编译并使用ArcGis Pro插件 0.引言 ArcGIS Pro插件&#xff08;Add-ins&#xff09;可以让用户更加容易的自定义和扩展ArcGIS Pro应用程序&#xff0c;它创建一系列自定义工具提供了一个…

ISO文件boot、dvd、minimal的区别

在centos的下载中&#xff0c;有分为boot、dvd、minimal的iso文件&#xff0c;那么他们之间有什么区别呢&#xff1f; boot.iso 这个版本大小不会超过1G ,只有最基本的启动引导等内容&#xff0c;各类包均需从线上下载&#xff0c;需要快速安装且有可靠网络的前提下&#xff0c…

linux中的数据库

目录 1.安装MySQL 2.创建数据库 3.删除数据库 4.查询创建数据库的语句 5使用数据库的语句 6.查询当前默认的数据库 7.查询使用的编码方式和校验规则 8.创建表的语句 9.表的物理存储结构 10.表的数据类型 11.总结 引言&#xff1a; 数据库是现代应用开发中不可或缺的组…

获取视频 RTMP 推流web播放

工作需要研究下市面上显示实时视频方案。这里介绍下RTMP协议。 需求获取USB摄像头&#xff0c;手机谁摄像头。显示到web网页上。 一、 采集摄像头 这个使用opencvSharp来采集&#xff1a; nuget: var task Task.Run(() >{var capture new VideoCapture(0);VideoCaptur…

PHP实战开发25-电商网站系统缓存设计方案系统讲述

文章目录 一、前言-缓存的作用1.1 提高性能1.2 减轻服务端压力1.3 减少网络流量1.4 改善用户体验1.5 支持离线访问1.6 降低数据传输成本 二、浏览器缓存2.1 强缓存2.2 协商缓存 三、CDN 缓存3.1 使用CDN缓存的好处3.1.1 加速网站加载时间3.1.2 节省服务器带宽3.1.3 提高可用性和…

快速计算多项式相乘系数 FFT快速傅里叶变换

快速计算多项式相乘系数 FFT快速傅里叶变换 快速傅里叶变换(FFT)——有史以来最巧妙的算法&#xff1f; 正常求两个多项式乘积 A ( x ) ∑ i 0 n A i x i , B ( x ) ∑ i 0 n B i x i C ( x ) ∑ i 0 n ∑ j 0 n A i B j x i j A(x)\sum_{i0}^{n}{A_ix^i},B(x)\sum_{…

Java多线程基础-11:工厂模式及代码案例之线程池

在Java中&#xff0c;xx池的概念是很常见的&#xff0c;比如之前遇到过的常量池、数据库连接池等等。 线程池是一种常用的多线程处理方式&#xff0c;它可以重复利用已创建的线程&#xff0c;从而减少线程的创建和销毁开销&#xff0c;并提高程序的性能。 通俗来说&#xff…