一、移植准备

1. 硬件准备

本文中使用的开发板为小熊派IoT开发板,主控为STM32L431RCT6:

 

 

2. 下载FreeRTOS源码

FreeRTOS源码分为两种,一种是FreeRTOS包,另一种是FreeRTOS LTS Release包。

 

2.1. FreeRTOS版本

这种版本包含FreeRTOS内核的源码和示例工程,还有FreeRTOS+的一些扩展库。可以通过下面的两种方式下载:

 

  • 官方下载链接:下载官方发布的包,截至发文时间,最新发布的版本为FreeRTOSv202012.00.zip。Github仓库地址:master分支为官方不断更新修改的包。

 

这里我从官方下载,下载解压之后如图:

 

 

2.2. FreeRTOS LTS版本

LTS即long term support(长期支持包),这种版本包含FreeRTOS LTS发布版本的源码,包括内核、TCP/IP、MQTT、OTA和更多支持的库。也可以通过下面的两种方式下载:

 

  • 官方下载链接:下载官方发布的包,截至发文时间,最新发布的版本为FreeRTOSv202012.01-LTS.zip。Github仓库地址:master分支为官方不断更新修改的包。

 

这里我从官方下载,解压之后如图:

 

 

3. 裸机工程准备

请准备一份可以正常使用printf串口输出的裸机工程,本文中我使用cubemx生成。

 

二、添加源码到工程中

1. 复制文件

在工程目录下新建FreeRTOS文件夹,将FreeRTOS官方源码复制过来,如图:

 

 

接着将portable文件夹下面的文件夹部分删除,只保留以下几个文件夹,如图。其中Gcc、IAR、RVDS(Keil)是分别适配这三种编译器的,MemMang是FreeRTOS提供的内存管理算法。

 

 

2. 添加文件到MDK工程

一个RTOS无非就三类文件:底层移植文件、内核实现文件、配置文件,所以在MDK分组中我们按照如下来管理。

 

2.1. 添加底层移植文件

新建 FreeRTOS/port 分组,因为这里我们是MDK移植环境,STM32L431RCT6属于带FPU的Cortex-M4内核,所以添加位于 FreeRTOS\portable\RVDS\ARM_CM4F 下的 port.c 文件:

 

 

再添加位于 FreeRTOS\portable\MemMang 下的 heap_4.c 文件,为FreeRTOS提供一种动态内存管理算法:

 

 

2.2. 添加FreeRTOS内核源码

新建 FreeRTOS/kernel 分组,添加位于 FreeRTOS 文件夹下的所有c文件:

 

 

2.3. 添加FreeRTOS配置文件

FreeRTOS的配置文件属于和实际硬件相关的文件,在我们复制过来的文件中并没有,所以要去FreeRTOS源码中提供的demo工程下找份最相关的文件,复制过来:

 

 

为了便于修改,添加到MDK分组中:

 

 

3. 添加头文件路径

 

 

此时编译,检查是否有错误:

 

 

可以看到编译器提示 INCLUDE_xTaskGetCurrentTaskHandle 函数没有实现,全局搜索检查一下该函数的定义:

 

 

可以看到只有定义了这两个宏定义中的任意一个,该函数才会定义,所以在配置文件中添加宏定义,开启使用互斥锁:

 

 

再次编译,编译成功。

 

三、修改FreeRTOS配置文件

之前我们添加的配置文件 FreeRTOSConfig.h 文件是从官方提供给STM32F103的demo中复制过来的,本实验中用的是STM32L431RCT6,需要进行修改。

 

1. 修改内核基本配置

因为STM32 HAL中定义了芯片的时钟(SystemCoreClock),所以此处使用一个c语言extern声明此变量在外部,但这是头文件,为了不被汇编器所汇编,可以使用如下宏定义:

 

/* Ensure definitions are only used by the compiler, and not by the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  #include 
  extern uint32_t SystemCoreClock;
#endif

 

接着修改内核基本时钟配置:

 

 

修改一些内核的API功能是否提供:

 

 

2. 修改中断配置

这部分是FreeRTOS的一个特色,将中断部分修改为如下配置:

 

/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
 /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
 #define configPRIO_BITS         __NVIC_PRIO_BITS
#else
 #define configPRIO_BITS         4
#endif

/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY   ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY  ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* This is the value being used as per the ST library which permits 16
priority values, 0 to 15.  This must correspond to the
configKERNEL_INTERRUPT_PRIORITY setting.  Here 15 corresponds to the lowest
NVIC value of 255. */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15

 

3. 定义断言

/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );} 

 

4. 配置中断接口

RTOS需要配置的中断有两个:一个是用于任务切换的pendSV中断(或者SVC中断),另一个是用于提供时钟节拍的Systick中断。

 

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler

/* IMPORTANT: This define is commented when used with STM32Cube firmware, when the timebase source is SysTick,
              to prevent overwriting SysTick_Handler defined within STM32Cube HAL */
 
/* #define xPortSysTickHandler SysTick_Handler */

 

刚刚这两个宏设置了pendSV和SVC中断处理程序的名称,将这两个处理程序交由FreeRTOS实现,但这会与stm32l4xx_it.c中默认的中断处理程序冲突,将其屏蔽:

 

 

最后处理Systick中断函数,因为Systick中断处理函数中还有HAL库的时钟节拍处理,所以并没有交由FreeRTOS实现,而是选择在Systick的中断处理函数中调用FreeRTOS的节拍处理函数。

 

首先在stm32l4xx_it.c的开始包含FreeRTOS头文件:

 

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */

接着修改Systick中断处理程序:

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
extern void xPortSysTickHandler(void);
  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
  if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
  {
#endif /* INCLUDE_xTaskGetSchedulerState */
  xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
  }
#endif /* INCLUDE_xTaskGetSchedulerState */
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

 

添加之后会发现INCLUDE_xTaskGetSchedulerState这个宏没有开,导致在中断处理程序中不会检测调度器状态,所以在配置文件中配置开启该API:

 

 

至此,移植全部完成。

 

四、测试内核是否可以正常运行

1. 开启支持静态内存分配

创建一个静态任务需要内核开启静态内存分配支持,在配置文件中添加如下宏定义:

 

#define configSUPPORT_STATIC_ALLOCATION    1

 

 

当这个宏开启之后,需要用户实现 vApplicationGetIdleTaskMemory 函数,来提供一块静态内存空间作为IDLE任务的内存空间,这里我在main.c中实现,如下:

 

/* GetIdleTaskMemory prototype (linked to static allocation support) */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
  
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
  *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
  *ppxIdleTaskStackBuffer = &xIdleStack[0];
  *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}   

 

2. 创建两个测试任务

首先在main.c中创建任务1和任务2的TCB控制块内存空间、任务栈空间,并创建两个任务的任务入口函数:

 

#define TASK1_STACK_SIZE    512
#define TASK2_STACK_SIZE    512

StaticTask_t task1_tcb_buffer;
StaticTask_t task2_tcb_buffer;

StackType_t task1_stack[TASK1_STACK_SIZE];
StackType_t task2_stack[TASK2_STACK_SIZE];

void task1_entry(void *args)
{
  /* Enter into a forever loop. */
  while(1)
  {
    printf("task 1 application running...\r\n");

    vTaskDelay(1000);
  }
}

void task2_entry(void *args)
{
  /* Enter into a forever loop. */
  while(1)
  {
    printf("task 2 application running...\r\n");
    
    vTaskDelay(1000);
  }
}

 

接下来在main函数中创建这两个任务,并启动内核:

 

/* USER CODE BEGIN 2 */
printf("FreeRTOS port on BearPi board by mculover666\r\n");

task1_handle = xTaskCreateStatic(task1_entry,"task1", TASK1_STACK_SIZE, NULL, 1, task1_stack, &task1_tcb_buffer);
            
task2_handle = xTaskCreateStatic(task2_entry,"task2", TASK2_STACK_SIZE, NULL, 2, task2_stack, &task2_tcb_buffer);          

vTaskStartScheduler();
/* USER CODE END 2 */

 

编译、下载,在串口助手中查看结果,可以看到两个任务交替运行,每隔1s打印一次日志:

 

 

有意思的一点是,我设置的task1优先级是1,task2优先级是2,从日志里明显是task2先跑,难道移植出了问题???

 

实则不然,FreeRTOS中优先级数值越低,优先级等级越低,空闲任务的优先级为0,这一点和很多RTOS都不相同,需要特别注意!

 

除此之外还有:

移植uc/OS-III最新版到小熊派开发板

 

在小熊派上移植threadX操作系统

 

STM32标准库工程中移植TencentOS-tiny