FreeRTOS的简单介绍

发布于:2025-06-04 ⋅ 阅读:(28) ⋅ 点赞:(0)

一、FreeRTOS介绍

FreeRTOS并不是实时操作系统,因为它是分时复用的

利用CubeMX快速移植

二、快速移植流程

1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何要如此配置?下文解说。

裸机的时钟源默认是 SysTick ,但是开启 FreeRTOS 后, FreeRTOS 会占用 SysTick (用来生成 1ms定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。

2. RCC 里的 HSE 设置为 Crystal/Ceramic Resonator

3. 打开串口

4. 时钟按下图配置

5. 选择 FREERTOS 选项,并将 Interface 改为 CMSIS_V1

6. 配置项目信息,并导出代码。

三、任务的创建与删除

实操

四、任务的调度

void StarttaskKEY1(void const * argument)
{
  /* USER CODE BEGIN StarttaskKEY1 */
  /* 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)
          {
          printf("KEY1被按下来\r\n");
          if(taskLED1Handle  ==  NULL)
          {
              printf("任务不存在,准备创建任务1!\r\n");
        
          osThreadDef(taskLED1, StarttaskLED1, osPriorityNormal, 0, 128);
          taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);
          if(taskLED1Handle != NULL )
          
             printf("任务创建完成!\r\n");

          }
          else
          {
          printf("删除任务!\r\n");
          osThreadTerminate(taskLED1Handle);
          taskLED1Handle = NULL;
          }

          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
      
      
      
      }
    osDelay(10);
  }
  /* USER CODE END StarttaskKEY1 */
}

/* USER CODE BEGIN Header_StarttaskKEY2 */
/**
* @brief Function implementing the taskKEY2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StarttaskKEY2 */
void StarttaskKEY2(void const * argument)
{
  /* USER CODE BEGIN StarttaskKEY2 */
    static int flag =0;
  /* 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)
          {
          printf("KEY2被按下来\r\n");
          if(flag == 0)
          {
          osThreadSuspend(taskLED2Handle);
              printf("任务2已暂停!\r\n");
              flag=1;
          }
         else
         {
         osThreadResume(taskLED2Handle);
         printf("任务2已恢复\r\n");
         flag=0;

         }

          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
      
      
      
      }
    osDelay(1);
  }
  /* USER CODE END StarttaskKEY2 */
}

六、队列

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息。

队列项目:队列中的每一个数据;

队列长度:队列能够存储队列项目的最大数量;

创建队列时,需要指定队列长度及队列项目大小

代码如下:

void StarttaskSend(void const * argument)
{
  /* USER CODE BEGIN StarttaskSend */
    uint16_t buf =100;
    BaseType_t status ;
  /* 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)
          {
            status=xQueueSend(myQueueHandle,&buf,0);    //第一个数据是句柄  第二个是到队列中的数据 第三个是超时
            if(status == pdTRUE)
                printf("写入队列成功,写入值为%d\r\n",buf);
            else
                printf("写入队列失败\r\n");
            
              
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
      
      
      
      }
    osDelay(1);
  }
  /* USER CODE END StarttaskSend */
}

/* USER CODE BEGIN Header_StarttaskReceive */
/**
* @brief Function implementing the taskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StarttaskReceive */
void StarttaskReceive(void const * argument)
{
  /* USER CODE BEGIN StarttaskReceive */
     uint16_t buf;
     BaseType_t status ;
  /* 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)
          {
            status=xQueueReceive(myQueueHandle,&buf,0);    //第一个数据是句柄  第二个是接收到的队列中的数据 第三个是超时
            if(status == pdTRUE)
                printf("读取队列成功,读取值为%d\r\n",buf);
            else
                printf("读取队列失败\r\n");
            
              
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
      
      
      
      }
      
    osDelay(1);
  }
  /* USER CODE END StarttaskReceive */
}

七、二值信号量

八、计数型信号量

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(1);
  }
  /* USER CODE END StarttaskGive */
}

/* USER CODE BEGIN Header_StarttaskTake */
/**
* @brief Function implementing the taskTake thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StarttaskTake */
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)    //portMAX_DELAY  死等
                printf("计数型信号量获取成功\r\n");
            else
                printf("计数型信号量获取失败\r\n");
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);

      
      }
    osDelay(1);
  }
  /* USER CODE END StarttaskTake */
}

九、互斥量

在多数情况下,互斥型信号量和二值型信号量非常的相似,但是从功能上二值型信号量用于同步,而互斥型信号量用于资源保护。

互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效地解决优先级反转的现象。

互斥量的特点
唯一访问:即互斥量保证在任意时间段,只有一个任务能够持有互斥量,即独占访问共享资源
优先级继承:当一个高优先级任务因为等待互斥量而被阻塞时,此时正持有互斥量的低优先级任务会临时继承高优先级任务的优先级,以防止优先级反转问题。
递归锁定:同一个任务可以多次获得同一个互斥量(递归互斥),每次成功获取后需要对应次数的释放操作。

任务优先级反转示例:
设有三个任务:

任务A:高优先级

任务B:中优先级

任务C:低优先级

步骤:

任务C获取资源:低优先级任务C获取一个共享资源并开始执行;
任务A被阻塞:高优先级任务需要访问该资源,但由于任务C持有资源,任务A被阻塞并等待资源释放;
任务B运行:此时,中等优先级任务B开始运行,因为它不需要访问任务C持有的资源,因此它不会被阻塞。由于任务B的优先级高于任务C,任务B持续运行,占用了CPU时间。
任务C无法运行:任务C被任务B抢占,无法继续执行,也就无法释放高优先级任务A所需的资源。
任务A优先级被反转:结果是高优先级任务A被中等优先级任务B间接阻塞,直到任务B完成执行并且任务C重新获得CPU时间并释放资源。

十、事件标志组

事件标志位:用一个位,来表示事件是否发生

事件标志组:一组事件标志位的集合,可以简单的理解时间标志组,就是一个整体。

事件标志租的特点:

        它的每一个位表示一个时间(高8位不算);

        每一个事件的含义,由用户自己决定,如:bit0表示按键是否按下,bit1表示是否接收到消息...(这些位的值为1:表示事件发生了;值为0,表示事件未发生)

        任意任务或中断都可以读写这些位;

        可以等待某一位成立,或者等待多位同时成立;

1. 创建事件标志组
EventGroupHandle_t xEventGroupCreate ( void );
参数:
返回值:
成功,返回对应事件标志组的句柄; 失败,返回 NULL
2. 设置事件标志位
EventBits_t xEventGroupSetBits ( EventGroupHandle_t xEventGroup , const EventBits_t uxBitsToSet );
参数:
xEventGroup :对应事件组句柄。 uxBitsToSet :指定要在事件组中设置的一个或多个位的按位
值。
返回值:
设置之后事件组中的事件标志位值。
3. 清除事件标志位
EventBits_t xEventGroupClearBits ( EventGroupHandle_t xEventGroup , const EventBits_t uxBitsToClear );
参数:
xEventGroup :对应事件组句柄。 uxBitsToClear :指定要在事件组中清除的一个或多个位的按位
值。
返回值:
清零之前事件组中事件标志位的值。
4. 等待事件标志位
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 :超时
返回值:
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位 其他值:等待事件标志位
失败,返回事件组中的事件标志位

十一、任务通知

模拟二值信号量
void StartTasksend(void const * argument)
{
  /* USER CODE BEGIN StartTasksend */
  /* 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)
          {
            xTaskNotifyGive(TaskreceiveHandle);      //任务通知,不带通知值
            printf("任务通知,模拟二值信号量发送成功 ! \r\n");
            

          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
      
      
      
      }
    osDelay(10);
  }
  /* USER CODE END StartTasksend */
}

/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{
  /* USER CODE BEGIN StartTaskreceive */
    uint32_t rev = 0;
    
  /* 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)
          {
            rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY);      //任务通知,不带通知值
              if(rev !=0)     //非0:接收成功,返回任务通知的通知值
                 printf("任务通知,模拟二值信号量接收成功 ! \r\n");

          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
      }
    osDelay(1);
  }
  /* USER CODE END StartTaskreceive */
}
模拟计数型信号量
void StartTasksend(void const * argument)
{
  /* USER CODE BEGIN StartTasksend */
  /* 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)
          {
            xTaskNotifyGive(TaskreceiveHandle);      //任务通知,不带通知值
            printf("任务通知,模拟计数型信号量发送成功 ! \r\n");
            

          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
      
      
      
      }
    osDelay(10);
  }
  /* USER CODE END StartTasksend */
}

/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{
  /* USER CODE BEGIN StartTaskreceive */
    uint32_t rev = 0;
    
  /* 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)
          {
            rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY);      //任务通知,不带通知值   特别注意这里pdTRUE是二值  pdFALSE 是计数型
              if(rev !=0)     //非0:接收成功,返回任务通知的通知值
                 printf("任务通知,模拟计数型信号量接收成功 ! rev=  %d \r\n" , rev);

          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
      }
    osDelay(1);
  }
  /* USER CODE END StartTaskreceive */
}
模拟事件标志组
void StartTasksend(void const * argument)
{
  /* USER CODE BEGIN StartTasksend */
  /* 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)
          {
           printf("将bit0位置置1\r\n");
           xTaskNotify(TaskreceiveHandle,0x01,eSetBits);
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
      
      
      
      }
      

            if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
      {
       osDelay(20);
          if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
          {
           printf("将bit1位置置1\r\n");
           xTaskNotify(TaskreceiveHandle,0x02,eSetBits);
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
      
      
      
      }
   
    osDelay(10);
  }
  /* USER CODE END StartTasksend */
}

/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{
  /* USER CODE BEGIN StartTaskreceive */
    uint32_t notify_val = 0;
    uint32_t event_bit =0;
    
  /* Infinite loop */
  for(;;)
  {
       xTaskNotifyWait(0,0xFFFFFFFF,&notify_val,portMAX_DELAY);
       if(notify_val & 0x01)     //判断bit0位是否为0
           event_bit |= 0x01;
       if(notify_val & 0x02)     //判断bit1位是否为1
           event_bit |=0x02;
       if(event_bit ==(0x03))     //只有两个位都置为1时才可以
       {
       printf("任务通知模拟事件标志组接收成功! \r\n");
           event_bit =0;
       
       }
       
  }
  /* USER CODE END StartTaskreceive */
}

模拟邮箱
void StartTasksend(void const * argument)
{
  /* USER CODE BEGIN StartTasksend */
  /* 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)
          {
           printf("按键1按下\r\n");
           xTaskNotify(TaskreceiveHandle,1,eSetValueWithOverwrite);     //eSetValueWithOverwrite 带覆写  eSetValueWithoutOverwrite  不带覆写
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
      
      
      
      }
      

            if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
      {
       osDelay(20);
          if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
          {
           printf("按键2按下\r\n");
           xTaskNotify(TaskreceiveHandle,2,eSetValueWithOverwrite);
          }
          while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
      
      
      
      }
   
    osDelay(10);
  }
  /* USER CODE END StartTasksend */
}

/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{
  /* USER CODE BEGIN StartTaskreceive */
    uint32_t notify_val = 0;
    uint32_t event_bit =0;
    
  /* Infinite loop */
  for(;;)
  {
      xTaskNotifyWait(0,0xFFFFFFFF,&notify_val,portMAX_DELAY);
      printf("接收到的通知值为: %d\r\n",notify_val);
      osDelay(1);
  }
  /* USER CODE END StartTaskreceive */
}

十二、延时函数

十三、软件定时器

void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN StartDefaultTask */
    //osTimerStart(myTimer01Handle,1000);          //周期
    xTimerChangePeriod(myTimer01Handle,pdMS_TO_TICKS(1000),0);        //官方的定时函数      
    osTimerStart(myTimer02Handle,2000);          //单次
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}

/* Callback01 function */
void Callback01(void const * argument)
{
  /* USER CODE BEGIN Callback01 */
    printf("lvshiyan shuai\r\n");
  /* USER CODE END Callback01 */
}

/* Callback02 function */
void Callback02(void const * argument)
{
  /* USER CODE BEGIN Callback02 */
    printf("lujingjing mei\r\n");

  /* USER CODE END Callback02 */
}

十四、中断

在stm32f1xx_it.c 文件中

void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  /* USER CODE BEGIN EXTI0_IRQn 1 */
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    uint32_t snd=1;
    xQueueSendFromISR(myQueue01Handle,&snd,NULL);       //在中断中往队列的尾部写入信息


  /* USER CODE END EXTI0_IRQn 1 */
}

在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 */
}


网站公告

今日签到

点亮在社区的每一天
去签到