FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理

1. 二值信号量简介(386.11)

什么是信号量?

  • 信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代码段不被并发调用。
  • 信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还可以用来表示资源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有两个状态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。
  • 信号量也是队列的一种。

什么是二值信号量?

  • 二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我们用它来进行互斥访问或任务同步。
  • 互斥访问:比如门钥匙,只有获取到钥匙才可以开门
  • 任务同步:比如我录完视频你才可以看视频
    在这里插入图片描述

二值信号量相关 API 函数

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第1张图片

  1. 创建二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary( void )
  • 参数:
  • 返回值:
    • 成功,返回对应二值信号量的句柄;
    • 失败,返回 NULL 。
  1. 释放二值信号量
BaseType_t  xSemaphoreGive( SemaphoreHandle_t xSemaphore )
  • 参数:
    • xSemaphore:要释放的信号量句柄
  • 返回值:
    • 成功,返回 pdPASS ;
    • 失败,返回 errQUEUE_FULL 。
  1. 获取二值信号量
BaseType_t  xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
  • 参数:
    • xSemaphore:要获取的信号量句柄
    • xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;
  • 返回值:
    • 成功,返回 pdPASS ;
    • 失败,返回 errQUEUE_FULL 。

2. 二值信号量实操(387.12)

实验需求

  • 创建一个二值信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。

cubeMX配置(基于1.muban)

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第2张图片
FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第3张图片

代码实现

  • 代码(5.semaphore_binary_test)
myBinarySemHandle = xSemaphoreCreateBinary();
void StartTaskGive(void const * argument)
{
  /* USER CODE BEGIN StartTaskGive */
  /* Infinite loop */
  for(;;)
  {
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				if(xSemaphoreGive(myBinarySemHandle) == pdTRUE)
					printf("二值信号量放入成功\r\n");
				else
					printf("二值信号量放入失败\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
  /* USER CODE END StartTaskGive */
}
void StartTaskTake(void const * argument)
{
  /* USER CODE BEGIN StartTaskTake */
  /* Infinite loop */
  for(;;)
  {
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				if(xSemaphoreTake(myBinarySemHandle, portMAX_DELAY) == pdTRUE)
					printf("二值信号量获取成功\r\n");
				else
					printf("二值信号量获取失败\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
  /* USER CODE END StartTaskTake */
}

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第4张图片

3. 计数型信号量简介及实操(388.13)

什么是计数型信号量?

  • 计数型信号量相当于队列长度大于 1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第5张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第6张图片

计数型信号量相关 API 函数

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第7张图片

  • 计数型信号量的释放和获取与二值信号量完全相同!
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
  • 参数:
    • uxMaxCount:可以达到的最大计数值
    • uxInitialCount:创建信号量时分配给信号量的计数值
  • 返回值:
    • 成功,返回对应计数型信号量的句柄;
    • 失败,返回 NULL 。

实验需求

  • 创建一个计数型信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。

cubeMX配置(基于5.semaphore_binary_test)

  • 将 Config parameters 标签里的 USE_COUNTING_SEMAPHORES 设置为 Enabled 。
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第8张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第9张图片

代码实现

  • 代码(6.semaphore_counting_test)
myCountingSemHandle = xSemaphoreCreateCounting(3, 0);
void StartTaskGive(void const * argument)
{
 /* USER CODE BEGIN StartTaskGive */
 /* Infinite loop */
 for(;;)
{
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
   {
      osDelay(20);
      if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
     {
        if (xSemaphoreGive(myCountingSemHandle) == pdTRUE)
          printf("计数信号量放入成功\r\n");
        else
          printf("计数信号量放入失败\r\n");
     }
      while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
   }
  osDelay(10);
}
 /* USER CODE END StartTaskGive */
}
void StartTaskTake(void const * argument)
{
 /* USER CODE BEGIN StartTaskTake */
 /* Infinite loop */
 for(;;)
{
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
   {
      osDelay(20);
      if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
     {
        if (xSemaphoreTake(myCountingSemHandle, 0 ) == pdTRUE)
          printf("计数信号量获取成功\r\n");
        else
          printf("计数信号量获取失败\r\n");
     }
      while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
   }
  osDelay(10);
}
 /* USER CODE END StartTaskTake */
}

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第10张图片

4. 互斥量简介(389.14)

什么是互斥量?

  • 在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上二值型信号量用于同步,而互斥型信号量用于资源保护。
  • 互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象。

什么是优先级翻转?

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第11张图片

  • 以上图为例,系统中有 3 个不同优先级的任务 H/M/L,最高优先级任务 H 和最低优先级任务 L 通过信号量机制,共享资源。目前任务 L 占有资源,锁定了信号量,Task H 运行后将被阻塞,直到 Task L 释放信号量后,Task H 才能够退出阻塞状态继续运行。但是 Task H 在等待 Task L 释放信号量的过程中,中等优先级任务 M 抢占了任务 L,从而延迟了信号量的释放时间,导致 Task H 阻塞了更长时间,这种现象称为优先级倒置或反转。
  • 优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
  • 优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。

互斥量相关 API 函数

  • 互斥信号量不能用于中断服务函数中!
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第12张图片
SemaphoreHandle_t xSemaphoreCreateMutex( void )
  • 参数:
  • 返回值:
    • 成功,返回对应互斥量的句柄;
    • 失败,返回 NULL 。

5. 互斥量实操(390.15)

实验需求 1. 演示优先级翻转

  • cubeMX配置(基于1.muban)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第13张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第14张图片
  • 代码(7.mutex_test)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第15张图片

实验需求 2. 使用互斥量优化优先级翻转问题

  • cubeMX配置(基于7.mutex_test)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第16张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第17张图片
  • 代码(7.mutex_test2)
void StartTaskH(void const * argument)
{
  /* USER CODE BEGIN StartTaskH */
  /* Infinite loop */
  for(;;)
  {
		xSemaphoreTake(myMutexHandle, portMAX_DELAY);
		printf("TaskH: 我开始进入厕所,发功中。。。\r\n");
		HAL_Delay(3000);
		printf("TaskH: 我上完厕所了,真舒服。。。\r\n");
		xSemaphoreGive(myMutexHandle);
    osDelay(1000);
    osDelay(1);
  }
  /* USER CODE END StartTaskH */
}
void StartTaskM(void const * argument)
{
  /* USER CODE BEGIN StartTaskM */
  /* Infinite loop */
  for(;;)
  {
		printf("TaskM: 我就是为了占有CPU资源,带女朋友去兜风~~~\r\n");
    osDelay(1000);
  }
  /* USER CODE END StartTaskM */
}
void StartTaskL(void const * argument)
{
  /* USER CODE BEGIN StartTaskL */
  /* Infinite loop */
  for(;;)
  {	
		xSemaphoreTake(myMutexHandle, portMAX_DELAY);//获取互斥量
		printf("TaskL: 我开始进入厕所,发功中。。。\r\n");
		HAL_Delay(3000);
		printf("TaskL: 我上完厕所了,真舒服。。。\r\n");
		xSemaphoreGive(myMutexHandle);//释放互斥量
    osDelay(1);
  }
  /* USER CODE END StartTaskL */
}

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第18张图片

6. 事件标志组简介(391.16)

什么是事件标志组?

  • 事件标志位: 表明某个事件是否发生,联想:全局变量 flag。通常按位表示,每一个位表示一个事件(高 8 位不算)
  • 事件标志组 是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数。
  • 事件标志组本质是一个 16 位或 32 位无符号的数据类型 EventBits_t ,由 configUSE_16_BIT_TICKS 决定。
  • 虽然使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高 8 位用作存储事件标志组的控制信息,低 24 位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第19张图片

事件标志组相关 API 函数

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第20张图片

  1. 创建事件标志组
EventGroupHandle_t xEventGroupCreate( void );
  • 参数:
  • 返回值:
    • 成功,返回对应事件标志组的句柄;
    • 失败,返回 NULL 。
  1. 设置事件标志位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
  • 参数:
    • xEventGroup:对应事件组句柄。
    • uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。
  • 返回值:
    • 设置之后事件组中的事件标志位值。
  1. 清除事件标志位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
  • 参数:
    • xEventGroup:对应事件组句柄。
    • uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。
  • 返回值:
    • 清零之前事件组中事件标志位的值。
  1. 等待事件标志位
EventBits_t xEventGroupWaitBits(
           const EventGroupHandle_t xEventGroup,
           const EventBits_t uxBitsToWaitFor,
           const BaseType_t xClearOnExit,
           const BaseType_t xWaitForAllBits,
           TickType_t xTicksToWait );
  • 参数:
    • xEventGroup:对应的事件标志组句柄
    • uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值
    • xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除
    • xWaitForAllBits:pdTRUE——所有等待事件位全为 1(逻辑与),pdFALSE——等待的事件位有一个为 1(逻辑或)
    • xTicksToWait:超时
  • 返回值:
    • 等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
    • 其他值:等待事件标志位失败,返回事件组中的事件标志位

7. 事件标志组实操(392.17)

实验需求

  • 创建一个事件标志组和两个任务( task1 和 task2),task1 检测按键,如果检测到 KEY1 和 KEY2 都按过,则执行 task2 。

代码实现

  • 基于1.muban
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第21张图片
  • 代码(8.events_test)
EventGroupHandle_t  eventgroup_handle;
eventgroup_handle = xEventGroupCreate();
void StartTask1(void const * argument)
{
  /* USER CODE BEGIN StartTask1 */
  /* Infinite loop */
  for(;;)
  {
		// 等待 KEY1 按下
		if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				xEventGroupSetBits(eventgroup_handle, 0x01);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
		// 等待 KEY2 按下
		if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				xEventGroupSetBits(eventgroup_handle, 0x02);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(1);
  }
  /* USER CODE END StartTask1 */
}
void StartTask2(void const * argument)
{
  /* USER CODE BEGIN StartTask2 */
	EventBits_t event_bit =0;
  /* Infinite loop */
  for(;;)
  {
  	event_bit = xEventGroupWaitBits(eventgroup_handle, 0x01 | 0x02, pdTRUE, pdFALSE, portMAX_DELAY);
    printf("返回值:%#x,请假成功,可以去玩啦!\r\n", event_bit);
	osDelay(1);
  }
  /* USER CODE END StartTask2 */
}

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第22张图片

8. 任务通知简介(393.18)

什么是任务通知?

  • FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队列)。
  • 在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!

任务通知值的更新方式

  • FreeRTOS 提供以下几种方式发送通知给任务 :
    • 发送消息给任务,如果有通知未读, 不覆盖通知值
    • 发送消息给任务,直接覆盖通知值
    • 发送消息给任务,设置通知值的一个或者多个位
    • 发送消息给任务,递增通知值
  • 通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。

任务通知的优势和劣势

  • 任务通知的优势
  1. 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
  2. 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
  • 任务通知的劣势
  1. 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
  2. 通知只能一对一,因为通知必须指定任务。
  3. 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
  4. 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。

任务通知相关 API 函数

1. 发送通知

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第23张图片

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
  • 参数:
    • xTaskToNotify:需要接收通知的任务句柄;
    • ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
    • eAction:一个枚举,代表如何使用任务通知的值;
      FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第24张图片
  • 返回值:
    • 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回 pdPASS。
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue );
  • 参数:
    • xTaskToNotify:需要接收通知的任务句柄;
    • ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
    • eAction:一个枚举,代表如何使用任务通知的值;
    • pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。
  • 返回值:
    • 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回 pdPASS。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
  • 参数:
    • xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
  • 返回值:
    • 总是返回 pdPASS。

2. 等待通知

等待通知API函数只能用在任务,不可应用于中断中!
FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第25张图片

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
  • 参数:
    • xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零(二值信号量);pdFALSE:把通知值减一(计数型信号量);
    • xTicksToWait:阻塞等待任务通知值的最大时间;
  • 返回值:
    • 0:接收失败
    • 非0:接收成功,返回任务通知的通知值
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
  • 参数:
    • ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。
    • ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参*pulNotificationValue 中。
    • pulNotificationValue:用于保存接收到的任务通知值。 如果 不需要使用,则设置为 NULL 即可 。
    • xTicksToWait:等待消息通知的最大等待时间。

9. 任务通知实操(394.19)

  1. 模拟二值信号量
  • 基于1.muban
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第26张图片
  • 代码(9.notify_binary_test)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第27张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第28张图片
  1. 模拟计数型信号量
  • 代码(9. notify_counting_test)(基于9.notify_binary_test修改)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第29张图片

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第30张图片
3. 模拟事件标志组

  • 代码(9. notify_events_test)(基于9.notify_counting_test修改)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第31张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第32张图片
  1. 模拟邮箱
  • 代码(9. notify_queue_test)(基于9.notify_events_test修改)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第33张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第34张图片

10. 延时函数(395.20)

  • 延时函数分类
    • 相对延时:vTaskDelay
    • 绝对延时:vTaskDelayUntil
      FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第35张图片

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第36张图片

  • vTaskDelay 与 HAL_Delay 的区别
    • vTaskDelay 作用是让任务阻塞,任务阻塞后,RTOS 系统调用其它处于就绪状态的优先级最高的任务来执行。
    • HAL_Delay 一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出,故其占用了全部 CPU 时间。

      FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第37张图片

11. 软件定时器简介(396.21)

什么是定时器?

  • 简单可以理解为闹钟,到达指定一段时间后,就会响铃。
  • STM32 芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成 PWM 、输入捕获、输出比较,等等,功能强大,但是由于硬件的限制,个数有限。
  • 软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理信息。

软件定时器优缺点

  • 优点:
  1. 简单、成本低;
  2. 只要内存足够,可创建多个;
  • 缺点:
    • 精度较低,容易受中断影响。在大多数情况下够用,但对于精度要求比较高的场合不建议使用。

软件定时器原理

  • 定时器是一个可选的、不属于 FreeRTOS 内核的功能,它是由定时器服务任务来提供的。
  • 在调用函数 vTaskStartScheduler() 开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。
  1. 负责软件定时器超时的逻辑判断
  2. 调用超时软件定时器的超时回调函数
  3. 处理软件定时器命令队列
  • FreeRTOS提供了很多定时器有关的API函数,这些API函数大多都使用FreeRTOS的队列发送命令给定时器服务任务。这个队列叫做定时器命令队列。 定时器命令队列是提供给FreeRTOS的软件定时器使用的,用户不能直接访问!
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第38张图片

软件定时器相关配置

软件定时器有一个定时器服务任务和定时器命令队列,这两个东西肯定是要配置的,相关的配置也是放到文件 FreeRTOSConfig.h 中的,涉及到的配置如下:

1、configUSE_TIMERS

  • 如果要使用软件定时器的话宏configUSE_TIMERS一定要设置为1,当设置为1的话定时器服务任务就会在启动FreeRTOS调度器的时候自动创建。

2、configTIMER_TASK_PRIORITY

  • 设置软件定时器服务任务的任务优先级,可以为0~(configMAX_PRIORITIES-1)。优先级一定要根据实际的应用要求来设置。如果定时器服务任务的优先级设置的高的话,定时器命令队列中的命令和定时器回调函数就会及时的得到处理。

3、configTIMER_QUEUE_LENGTH

  • 此宏用来设置定时器命令队列的队列长度。

4、configTIMER_TASK_STACK_DEPTH

  • 此宏用来设置定时器服务任务的任务堆栈大小。

单次定时器和周期定时器

  • 单次定时器: 只超时一次,调用一次回调函数。可手动再开启定时器;
  • 周期定时器: 多次超时,多次调用回调函数。

软件定时器相关 API 函数

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第39张图片

  1. 创建软件定时器
TimerHandle_t xTimerCreate
        ( const char * const pcTimerName,
         const TickType_t xTimerPeriod,
         const UBaseType_t uxAutoReload,
         void * const pvTimerID,
         TimerCallbackFunction_t pxCallbackFunction );
  • 参数:
    • pcTimerName:软件定时器名称
    • xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍。宏 pdMS_TO_TICKS() 可用于将以毫秒为单位指定的时间转换为以 tick 为单位指定的时间。
    • uxAutoReload:定时器模式, pdTRUE:周期定时器, pdFALSE:单次定时器
    • pvTimerID:软件定时器 ID,用于多个软件定时器公用一个超时回调函数
    • pxCallbackFunction:软件定时器超时回调函数
  • 返回值:
    • 成功:定时器句柄
    • 失败:NULL
  1. 开启软件定时器
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xBlockTime );
  • 参数:
    • xTimer:待开启的软件定时器的句柄
    • xTickToWait:发送命令到软件定时器命令队列的最大等待时间
  • 返回值:
    • pdPASS:开启成功
    • pdFAIL:开启失败
  1. 停止软件定时器
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xBlockTime );
  • 参数与返回值同上。
  1. 复位软件定时器
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xBlockTime );
  • 参数与返回值同上。
  • 该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时。
  1. 更改软件定时器定时时间
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xBlockTime );
  • xNewPeriod:新的定时超时时间,单位:系统时钟节拍。
  • 其余参数与返回值同上。

12. 软件定时器实操(397.22)

实验需求

  • 创建两个定时器:
    • 定时器1,周期定时器,每1 秒打印一次 Jessie shuai
    • 定时器2,单次定时器,启动后 2 秒打印一次 Hi,Jessie!

cubeMX配置

  • 基于1.muban修改
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第40张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第41张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第42张图片
  • 代码(10. timer_test)
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第43张图片
    FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第44张图片

13. 中断管理(398.23)

中断定义

  • 请参考 51 及 STM32 中断相关课程。

中断优先级

  • 任何中断的优先级都大于任务!
  • 在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的优先级并不是从 0~15 ,默认情况下它是从 5~15 ,0~4 这 5 个中断优先级不是 FreeRTOS 控制的(5是取决于 configMAX_SYSCALL_INTERRUPT_PRIORITY)。

相关注意

  1. 在中断中必需使用中断相关的函数;
  2. 中断服务函数运行时间越短越好。

实验需求

  • 创建一个队列及一个任务,按下按键 KEY1 触发中断,在中断服务函数里向队列里发送数据,任务则阻塞接收队列数据。

cubeMX配置

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第45张图片
FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第46张图片

代码实现

  • stm32f1xx_it.c
#include "cmsis_os.h"
extern osMessageQId myQueue01Handle;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  uint32_t snd = 6;
  xQueueSendFromISR(myQueue01Handle, &snd, NULL);
}
  • freertos.c
void StartDefaultTask(void const * argument)
{
 /* USER CODE BEGIN StartDefaultTask */
  uint32_t rev = 0;
 /* Infinite loop */
 for(;;)
{
    if (xQueueReceive(myQueue01Handle, &rev, portMAX_DELAY) == pdTRUE)
      printf("rev = %d\r\n", rev);
  	osDelay(1);
}
 /* USER CODE END StartDefaultTask */
}

FreeRTOS第2天:信号量、互斥量、时间标志组、任务通知、延时函数、软件定时器、中断管理_第47张图片

你可能感兴趣的:(stm32)