【FreeRTOS學習01】CubeIDE快速整合FreeRTOS創建第一個任務


整個專欄主要是博主結合自身對FreeRTOS的實戰學習以及源碼分析,基於STM32F767 Nucleo-144平台,在CubeIDE下進行開發,結合官方的HAL庫,將硬件環節的問題減少到最小,將精力主要放在RTOS的學習上.

相關文章

【FreeRTOS實戰匯總】小白博主的RTOS學習實戰快速進階之路(持續更新)

1 FreeRTOS

FreeRTOS是免費的嵌入式實時系統,可以訪問官網,至少目前是免費,並且社區相當活躍,但是以后會不會被割韭菜就不清楚了,所以感覺還是支持國產比較好,大家可以參考一下RT-Thread

1.1 獲取源碼

我們可以登陸到官網下載源碼的壓縮包,如下圖所示;
在這里插入圖片描述
FreeRTOS還將源碼托管到github上,登陸官網可以看到下圖的兩個倉庫;

在這里插入圖片描述

  • FreeRTOS-Kernel:這個倉庫是RTOS最核心的東西,很純凈,包括調度算法,信號量,內存管理等等,它同時作為一個子模塊存在於FreeRTOS倉庫中;
  • FreeRTOS:這個倉庫是比較全的源碼,除了核心的部分,還包括對各個芯片平台的支持以及各個主流IDE的demo,因此如果要移植的話,主要還是下載這個源碼;

但是本文暫不會介紹如何移植RTOS,在遠古時期,因為第三方的支持力度不夠,因此有一些平台只能自己移植,現在第三方的支持相當於給力,直接拿來用即可.

1.2 源碼結構

自動生成的代碼中找到FreeRTOS的源碼如下;


在這里插入圖片描述


對於相應的文件夾和源碼文件做一下介紹;

  • CMSIS_RTOS_V2:這是API的版本,對應的還有CMSIS_RTOS_V1;
  • portable:這里主要是移植的時候需要修改的一些文件,針對不同的MCU以及不同的編譯器,需要對這塊進行修改;
    • portmacro.h:定義編譯器相關的數據類型和中斷處理的宏定義;
    • port.c:實現任務的堆棧初始化systick任務上下文切換
  • MemMang:內存管理的文件,目前主要用heap_1.c,heap_2.c,heap_3.c,heap_4.c,heap_5.c
  • list.c:雙向鏈表的實現,主要供給內核調度器使用;
  • queue.c: 隊列的實現,主要支持中斷環境和信號量控制;
  • croutine.c :任務使用同一個堆棧,這樣使得減小RAM的使用,但是在使用上會受到相當大的限制;
  • task.c:每個任務都有各自的獨立堆棧,支持完全的搶占式調度

2 CubeMX 整合 RTOS

  1. 在配置工程的時候或者打開后綴名為.ioccubemx配置文件;
  2. 點擊菜單欄Pinout&Configuration
  3. 點擊Middleware選擇FREERTOS

具體如下圖所示;

在這里插入圖片描述

最終生成的文件結構如下圖所示;
在這里插入圖片描述
這時候已經將FreeRTOS集成到工程中了,*****,簡直不能再方便了;

3 新建RTOS任務

直接生成的工程中,系統已經創建好了一個任務StartDefaultTask;
整體的main.c代碼如下;

#include "main.h"
#include "cmsis_os.h"

/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
  .name = "defaultTask",
  .priority = (osPriority_t) osPriorityNormal,
  .stack_size = 128 * 4
};

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void StartDefaultTask(void *argument);

int main(void)
{
  HAL_Init();
  
  SystemClock_Config();

  MX_GPIO_Init();

  osKernelInitialize();

  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
  /* Start scheduler */
  osKernelStart();
  /* We should never get here as control is now taken by the scheduler */
  while (1)
  {

  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
  /** Initializes the CPU, AHB and APB busses clocks */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}
static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();

}
void StartDefaultTask(void *argument)
{
  for(;;)
  {
    osDelay(1);
  }
}

void Error_Handler(void)
{

}

#ifdef USE_FULL_ASSERT
/** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */
void assert_failed(uint8_t *file, uint32_t line)
{ 

}
#endif /* USE_FULL_ASSERT */

這里可以看到整體的流程為兩部分HAL層和OS層;

HAL

  • 硬件初始化 HAL_Init
  • 時鍾配置 SystemClock_Config
  • BSP初始化 MX_GPIO_Init

OS

  • 內核初始化 osKernelInitialize
  • 創建任務/線程 osThreadNew
  • 任務調度 osKernelStart

整體流程圖如下所示;

Created with Raphaël 2.2.0 開始 HAL_Init SystemClock_Config BSP_Init osKernelInitialize osThreadNew osKernelStart 結束

從OS的設計上來講,`Thread`擁有自己的`id`,並且可以分配`timer` 並且擁有一個類似線程的任務函數或者稱為任務回調函數(可能不太嚴謹,下面統一稱之為任務回調函數),因此這里定義一個FreeRTOS任務需要三點;
  1. osThreadId_t defaultTaskHandle;定義一個變量,后面創建任務會分配一個id作為該任務的唯一標識符/身份證;
  2. osThreadAttr_t defaultTask_attributes;作為定義一個任務時的參數,包括任務的優先級,所需要分配的棧空間大小.以及任務的名字等等;具體結構體osThreadAttr_t如下所示;
typedef struct {
  const char                   *name;   ///< name of the thread
  uint32_t                 attr_bits;   ///< attribute bits
  void                      *cb_mem;    ///< memory for control block
  uint32_t                   cb_size;   ///< size of provided memory for control block
  void                   *stack_mem;    ///< memory for stack
  uint32_t                stack_size;   ///< size of stack
  osPriority_t              priority;   ///< initial thread priority (default: osPriorityNormal)
  TZ_ModuleId_t            tz_module;   ///< TrustZone module identifier
  uint32_t                  reserved;   ///< reserved (must be 0)
} osThreadAttr_t;
  1. 創建任務回調函數,這里有一個生命周期的問題,通常在任務中放一個死循環,以便於任務保持存活,可以被一直調度;
void StartDefaultTask(void *argument)
{
  	for(;;)
  	{
    	osDelay(1);
  	}
}

最后,調用osThreadNew創建任務;該函數聲明如下所示;

osThreadId_t osThreadNew (	osThreadFunc_t func, 
							void *argument, 
							const osThreadAttr_t *attr);

4 總結

通過cube自動生成了一個FREERTOS工程,簡單分析了一下如何創建一個任務,后續需要在實踐中深入到源碼中學習FreeRTOS


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM