STM32 HAL库使用笔记:FreeRTOS任务管理和中断处理详解

一、简介

1.FreeRTOS简介

    RTOS全称为:Real Time OS,就是实时操作系统,强调的是:实时性。而Free显而易见体现的是其免费性。总的来说这是一个免费的嵌入式实时操作系统。

    其特点是:免费开源、可剪裁(独立性强,适应范围广)、简单、优先级/任务不限(但是受到不同开发环境和硬件的限制,一般受限)、支持三种方式的任务调度。

    与裸机的区别:裸机的应用程序整体来看放在整个大循环里,很多时候资源浪费即“空等待”;而RTOS是多个优先级相同的任务每个任务执行一个时间片(时间长度可以调节),来回切换,最终效果是所有优先级相同的任务同时进行,而且有任务被“阻塞”时,会释放cpu资源。

2.任务调度简介

一共支持三种方式:

    1)抢占式调度:主要是针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务。

    2)时间片调度:主要针对优先级相同的任务,当多个任务的优先级相同时, 任务调度器会在每一次系统时钟节拍到的时候切换任务。

    3)协程式调度:当前执行任务将会一直运行,同时高优先级的任务不会抢占低优先级任务 FreeRTOS现在虽然还支持,但是官方已经表示不再更新协程式调度。仅作了解。

3.任务状态简介

FreeRTOS总共存在4种任务状态:

    运行态:正在占据CPU资源,正在运行的任务。每一时刻只存在一个任务处于运行态。

    就绪态:简单概括,此任务若准备工作都已经做完,即可进入就绪态。

    阻塞态:如果一个任务因延时或等待外部事件发生,那么这个任务就处于阻塞态。

    挂起态:类似暂停,调用函数 vTaskSuspend() 进入挂起态,需要调用解挂函数vTaskResume() 才可以进入就绪态。

    注意挂起和删除的区别,删除是将此任务从任务堆栈中完全移除,挂起只是暂时停止运行。

    所有的任务想进入运行态必须要先进入就绪态,而从运行态可以进入任何状态。如下图表示:

在FreeRTOS中有许多Include可以配置,主要是使能一些功能。包括任务挂起、恢复、中断恢复等等,在需要时使能即可。

二、HAL库配置

1.时钟树的配置

参考:STM32 hal库使用笔记(一)GPIO的使用—流水灯_乱码小伙的博客-CSDN博客

     由于FreeRTOS的系统定时器要用滴答定时器,而CubeMX不推荐共用,所以需要修改Timebase Source的来源于其他的定时器。

 2.FreeRTOS的配置
2.1 动态任务的配置(代码对应3.1)

    实验目标:添加两个任务,分别是task1、task2,task1中实现LED0的定时翻转,task2中实现LED1的定时翻转。当按键1按下时,删除任务2。

    使用CubeMX配置后,会自动剪裁FreeRTOS,只保留必要文件,由于只是实验动态任务,所有大部分默认即可。

    添加任务1和任务2,注意,这两个任务的级别可以相同也可以不同,因为LED翻转会有阻塞态,会释放CPU资源给另一个LED。

 配置完成后生成代码即可。

2.2 静态任务的配置

    静态任务的创建与静态任务的创建的最大区别就是,静态任务需要用户自己分配任务堆栈,步骤麻烦一些,不过利用CubeMX,只需修改任务创建方式即可。

    任务控制块的作用:用来保存任务信息,在任务切换时保存任务状态,方便恢复现场。

其实验效果和原理与动态基本一致,就不再演示实验效果和代码部分。

    关于任务删除有几点补充:在一个任务中删除的任务要不是这个任务本身,要不就是其他的任务,如果是其他任务,则会在本任务中将被删除的任务释放;若是任务本身,则需要在空闲任务中删除本任务,所以FreeRTOS会自动创建空闲任务。(这是针对的动态任务,静态任务需要用户进行删除。)

2.3 任务挂起和恢复的配置(包括中断恢复,对应代码3.2)

    实验目的:配置两个任务和一个外部中断,由于用LED灯来显示的话与上个任务的现象差不多,所以改用串口来检测,按键1在任务中挂起任务,按键2在任务中恢复任务,按键3在中断中恢复任务。

串口的配置参考:STM32 hal库使用笔记(二)中断—串口中断_乱码小伙的博客-CSDN博客

外部中断的配置参考:STM32 hal库使用笔记(二)中断—外部中断_乱码小伙的博客-CSDN博客  

    先说一些中断恢复的注意问题,为了任务调度时任务的优先级清楚、明晰、好分配所以CubeMX会自动将中断分组设为4,这样就没有响应优先级的冲突,由抢占优先级起到决定作用。而想在中断中使用API函数,中断的优先级必须在FreeRTOS的管理范围内,这个范围可以配置,默认是5-15。如果优先级分组/优先级不是如上所说的配置,在中断中调用API函数时会发生很多问题,老版本会不产生作用,但是我的实测是直接卡死,所以还是建议按照分组和优先级的规则进行配置。

     在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数。

Config的配置同上就行,再就是确保以下的两个函数使能即可(才能打开对应的寄存器)。

 2.4 任务中断管理配置(代码参考3.3)

    实验目的:检验中断管理的作用,属于FreeRTOS管理的范围内(5~15)的中断,可以利用portDISABLE_INTERRUPTS()函数进行关闭。利用串口进行检验,将定时器2、3的抢占优先级配为4和6,建立一个任务,在任务中利用按键1和按键2来开启和关闭中断,串口打印信息进行反馈。

定时器配置参考:STM32 hal库使用笔记(二)中断—定时器中断_乱码小伙的博客-CSDN博客

按键配置参考:STM32 hal库使用笔记(一)GPIO的使用—按键控制LED_乱码小伙的博客-CSDN博客

CubeMX的配置同上,打开两个函数的使能,配置一个任务即可。

三、代码编写

3.1

只需要编写任务即可。

任务1:

void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  {
     if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
    {
      HAL_Delay(20);
      if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
      {
          while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0);         
          if(myTask03Handle!=NULL){
             
             vTaskDelete(myTask03Handle);
             myTask03Handle=NULL;              
          }
      }          
    }
    osDelay(500);
    HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
    k++;if(k==4)printf("task1正在运行\r\n");
    if(k>7)k=0;
    //uxTaskGetStackHighWaterMark(myTask02Handle);
  }
  /* USER CODE END StartTask02 */
}

任务2:

void StartTask03(void const * argument)
{
  /* USER CODE BEGIN StartTask03 */
  /* Infinite loop */
  for(;;)
  {
      osDelay(500);
      if(k==7)printf("task2正在运行\r\n");
      HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
  }
  /* USER CODE END StartTask03 */
}

串口部分不用配置,删除即可。

实现现象:

任务的建立和删除实验

3.2

编写任务1和任务2,以及外部中断更新回调函数即可。

任务1:

void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(500);
    HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
    count1++;
    if(count1==9)
    {
      printf("-----task1正在运行-----\r\n");
      count1=0;
    }
     if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
    {
      HAL_Delay(20);
      if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
      {
          while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0);         
          vTaskSuspend(myTask03Handle);
          printf("!!!--------task2被挂起--------!!!\r\n");
      }          
    }
    else if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)==0)
    {
      HAL_Delay(20);
      if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)==0)
      {
          while(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0);         
          vTaskResume(myTask03Handle);
          printf("!!!--------task2已在任务中恢复--------!!!\r\n");
      }          
    }
  
  }
  /* USER CODE END StartTask02 */
}

任务2:

void StartTask03(void const * argument)
{
  /* USER CODE BEGIN StartTask03 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(500);
    HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
    count2++;
    if(count2==5)
    {
       printf("-----task2正在运行-----\r\n");
      count2=0;
    }
  }
  /* USER CODE END StartTask03 */
}

中断回调函数,注意抢占优先级必须在5~15:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    BaseType_t xYieldRequired;
    if(GPIO_Pin == KEY_UP_Pin)
    {
      xYieldRequired = xTaskResumeFromISR( myTask03Handle ); 
      if(xYieldRequired == pdTRUE )//需要任务切换
      {
       portYIELD_FROM_ISR( xYieldRequired );
      }
      printf("!!!----已经在中断中恢复task2----!!!");    
    }
}

有个问题需要说明,printf函数占时较长,在中断中不宜使用,下个实验已经更换。

实现现象:

3.3

同理,只需要编写任务逻辑和中断更新回调函数即可。

任务编写:

void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  {
     if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
    {
      HAL_Delay(20);
      if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
      {
          while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0);
          portDISABLE_INTERRUPTS();          
          printf("!!!--------中断关闭--------!!!\r\n");
      }          
    }
    else if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)==0)
    {
      HAL_Delay(20);
      if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)==0)
      {
          while(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0);
           portENABLE_INTERRUPTS();          
          printf("!!!--------中断开启--------!!!\r\n");
      }          
    }
  
  }
  /* USER CODE END StartTask02 */
}

 中断回调函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM6) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
    if (htim->Instance == TIM2)
    {
        
        uint8_t MyArray[] = {"定时器2正在运行\r\n"};
        HAL_UART_Transmit(&huart1, MyArray, sizeof(MyArray), 10000);
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
    }
   if (htim->Instance == TIM3)
    {   
        
        uint8_t MyArray2[] = {"定时器3正在运行\r\n"};
       HAL_UART_Transmit(&huart1, MyArray2, sizeof(MyArray2), 10000);
        HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
    }
  /* USER CODE END Callback 1 */
}

实验现象:

    可以看到,中断关闭,在管理范围内的定时器3已经停止工作,不在管理范围内的定期器2仍在工作。

欢迎大家交流和指正!!!不胜欣喜!!!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库使用笔记:FreeRTOS任务管理和中断处理详解

发表评论