STM32移植FreeRTOS(Cmake+CubeMX)

说明:基于STM32F103C8T6最小系统开发板

一、CubeMX创建STM32工程

1.基础配置

使能高速外部时钟HSE

Debug烧录调试选择Serial Wire,TimeBase Source把SysTick改为任一定时器(TIM2)

注:SysTick需要让给FreeRTOS作为心跳帧,TIM2作为HAL的时基

PC13选择输出模式,用板载PC13引脚LED进行验证

2.配置时钟树

将时钟树的HCLK改为72Mhz并确认OK

3.工程配置

Toolchain选择Cmake并勾选.c.h分离

最后点击GENERATE CODE创建完成

二、VSCode点灯

用VSCode打开刚才创建的STM32FreeRTOS_Temp文件夹,系统识别到项目点击"是",并点击Debug

然后移植自己的json通用模板

编写闪烁代码并编译,编译通过并烧录,点灯成功

三、FreeRTOS裁剪

FreeRTOS官网下载2024年版

FreeRTOS目录的FreeRTOS-Kernel 结构如下图所示,我们只到FreeRTOS-Kernel

将整个工程复制到STM32FreeRTOS_Temp工程

1.根目录下把非.c.h文件删除

2.portable文件夹只留下GCC和MemMang,其余均删除

GCC留下ARM_CM3,其余均删除

MenMang只留下heap4.c,其余均删除

3.include文件夹把非点.c.h文件夹删除

4.examples文件夹只保留FreeRTOSConFig.h

将STM32FreeRTOS_Temp\FreeRTOS\examples\template_configuration目录下的FreeRTOSConFig.h剪切到main.c同目录下,并删掉整个examples文件夹

最终FreeRTOS裁剪如下图所示

四、CmakeLists.txt添加添加.c文件和指定头文件路径

移植完FreeRTOS之后,如何将VScode添加.c.h文件和指定头文件路径?

VScode.c.h文件和指定头文件路径的添加只与以上两行代码有关

方法一:一个一个添加

cpp 复制代码
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    User/BSP/led.c
    User/BSP/key.c
    User/APP/app.c
)

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    ${CMAKE_SOURCE_DIR}/User/BSP
    ${CMAKE_SOURCE_DIR}/User/APP
)

方法二(推荐):自动识别.c文件

添加file指令,指定头文件路径的方法照常一个一个添加

cpp 复制代码
file(GLOB_RECURSE USER_SOURCES
    CONFIGURE_DEPENDS
    "${CMAKE_SOURCE_DIR}/FreeRTOS/*.c"
)

# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
    ${USER_SOURCES}
)

方法三: 自动识别头文件路径(未验证)

CubeMX 同样不会自动扫描你新建的头文件目录。

如果希望所有包含 .h 文件的子目录都自动加入头文件搜索路径,可以添加:

cpp 复制代码
file(GLOB_RECURSE USER_HEADERS
    CONFIGURE_DEPENDS
    "${CMAKE_SOURCE_DIR}/User/*.h"
)

set(USER_INCLUDE_DIRS "")

foreach(HEADER ${USER_HEADERS})
    get_filename_component(HEADER_DIR ${HEADER} DIRECTORY)
    list(APPEND USER_INCLUDE_DIRS ${HEADER_DIR})
endforeach()

list(REMOVE_DUPLICATES USER_INCLUDE_DIRS)

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    ${USER_INCLUDE_DIRS}
)

我的最终添加效果如下图

cpp 复制代码
file(GLOB_RECURSE FREERTOS_SOURCES
    CONFIGURE_DEPENDS
    "${CMAKE_SOURCE_DIR}/FreeRTOS/*.c"
)
# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
    ${FREERTOS_SOURCES}
)

# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined include paths
    ${CMAKE_SOURCE_DIR}/FreeRTOS/include
    ${CMAKE_SOURCE_DIR}/FreeRTOS/portable/GCC/ARM_CM3
)

五、修改FreeRTOSConfig.h配置

1.将configTICK_TYPE_WIDTH_IN_BITS宏定义从64修改为32

cpp 复制代码
/*时间以"时钟节拍"(ticks)来衡量,即自实时操作系统内核启动以来,时钟中断执行的次数。  
时钟计数保存在名为 TickType_t 的变量中。  
configTICK_TYPE_WIDTH_IN_BITS 控制 TickType_t 的类型(从而决定其位宽):  
将 configTICK_TYPE_WIDTH_IN_BITS 定义为 TICK_TYPE_WIDTH_16_BITS 时,TickType_t 将被定义(typedef)为无符号 16 位类型。  
将 configTICK_TYPE_WIDTH_IN_BITS 定义为 TICK_TYPE_WIDTH_32_BITS 时,TickType_t 将被定义(typedef)为无符号 32 位类型。  
将 configTICK_TYPE_WIDTH_IN_BITS 定义为 TICK_TYPE_WIDTH_64_BITS 时,TickType_t 将被定义(typedef)为无符号 64 位类型。*/
#define configTICK_TYPE_WIDTH_IN_BITS              TICK_TYPE_WIDTH_32_BITS

修改到此处,编译已经0报错了,如下图

2.修改中断优先级修改前:

修改后:

3.修改系统时钟和心跳帧

配置 修改值 作用
configCPU_CLOCK_HZ 72000000 CPU 时钟
configTICK_RATE_HZ 1000 系统时钟节拍(FreeRTOS心跳帧)

4.修改栈溢出检查宏定义由2为0

cpp 复制代码
#define configCHECK_FOR_STACK_OVERFLOW        0

六、SysTick、PendSV、SVC函数处理

这三个函数被OS系统复用了

注释stm32f1xx_it.c的SysTick、PendSV、SVC函数,然后FreeRTOSConfig.h的末尾处添加以下宏定义

cpp 复制代码
#define vPortSVCHandler        SVC_Handler
#define xPortPendSVHandler     PendSV_Handler
#define xPortSysTickHandler    SysTick_Handler

七、创建任务编写代码

mian.c

添加头文件

cpp 复制代码
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */

在main函数之前编写两个Task任务

cpp 复制代码
/* USER CODE BEGIN 0 */
volatile uint32_t Number1 = 0;
volatile uint32_t Number2 = 0;
void LED_Task1(void *argument)
{
    while (1)
    {
        Number1++;
        
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void LED_Task2(void *argument)
{
    while (1)
    {
        Number2++;
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
/* USER CODE END 0 */

在初始化后创建任务

cpp 复制代码
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
  xTaskCreate(LED_Task1,"LED_Task1",128,NULL,1,NULL);// 创建 LED_Task1 任务
  xTaskCreate(LED_Task2,"LED_Task2",128,NULL,1,NULL);// 创建 LED_Task2 任务
  vTaskStartScheduler();// 启动 FreeRTOS 调度器
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)//正常情况下不会执行到这里
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

八、最终效果

最后Debug,两个变量正常记数,LED正常闪烁,自此STM32移植FreeRTOS完成

九、FreeRTOS相关知识补充

1.FreeRTOS任务数字越大优先级越高,最低优先级为0

cpp 复制代码
/*
 * configMAX_PRIORITIES 设置可用的任务优先级数量。
 * 任务优先级范围为 0 到 (configMAX_PRIORITIES - 1),0 为最低优先级。
 */
#define configMAX_PRIORITIES                       5

该宏定义的含义是任务的优先级可以在0、1、2、3、4设置

2.中断优先级NVIC分组

FreeRTOS需要把NVIC全部设置为抢占优先级,选择分组4

记忆方法:

分组4,2^4=16 抢抢抢抢 抢占优先级0-15,响应优先级0

分组3,2^3=8 抢抢抢响 抢占优先级0-7, 响应优先级0-1

分组2,2^2=4 抢抢响响 抢占优先级0-3, 响应优先级0-3

分组1,2^1=2 抢响响响 抢占优先级0-1, 响应优先级0-7

分组0,2^0=1 响响响响 抢占优先级0, 响应优先级0-15

3.相关优先级边界

cpp 复制代码
#define configPRIO_BITS 4  //MCU为4位中断优先级
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //定义可以安全调用 FreeRTOS 中断 API 的最高抢占优先级边界
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //定义可以安全调用 FreeRTOS 中断 API 的最高抢占优先级边界

#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) //15<<4 = 0xF0

#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) //5<<4 = 0x50

#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY

注:后两个带移位的宏是给 Cortex-M 底层使用的编码值

中断优先级分为NVIC中断优先级和FreeRTOS中断优先级,两套相互关联但不完全相同的机制

举例子

configPRIO_BITS = 4 表示MCU实现了4位NVIC优先级,意味着MCU有16 个可编码的优先级等级,FreeRTOS要求NVIC全部设置为抢占优先级,因此选择NVIC分组4,0-15均为抢占优先级。

在这16个可编码优先级的框架下,5为边界,5-15表示这个优先级范围,FreeRTOS的API函数才可以调用,0-4用于非OS中断,遵循数字越小优先级越高,这与任务优先级相反

4、实际工程里应该怎么分配中断优先级?

实例,假设 FreeRTOS 系统调用边界为 5

中断 NVIC 优先级示例 是否允许 FromISR
极低延迟的硬件采样中断 2 不允许
高速定时器中断 3 不允许
USART1 接收中断 6 允许
DMA1 通道中断 7 允许
EXTI 按键中断 10 允许
FreeRTOS SysTick 15 内核专用

5.SysTick、PendSV、SVC 三个异常

异常 主要作用
SysTick 产生操作系统节拍
PendSV 执行任务上下文切换
SVC 用于启动第一个任务等内核操作
复制代码
#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

关系:SysTick中断---SVC检查内核是否需要调度----PendSV切换任务

6.任务严格按照固定周期执行标准写法

cpp 复制代码
TickType_t lastWakeTime = xTaskGetTickCount();

while (1)
{
    LED_Task();
    vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(10));
}

十一、附录

宏定义 推荐起点 作用
configUSE_PREEMPTION 1 开启抢占式调度
configUSE_TIME_SLICING 1 同优先级任务时间片轮转
configCPU_CLOCK_HZ SystemCoreClock CPU 时钟频率
configTICK_RATE_HZ 1000 系统 Tick 频率
configMAX_PRIORITIES 5 最大任务优先级数量
configMINIMAL_STACK_SIZE 128 空闲任务最小栈深度
configTOTAL_HEAP_SIZE 4096 FreeRTOS 堆大小,字节
configMAX_TASK_NAME_LEN 16 任务名称最大长度
configUSE_16_BIT_TICKS 0 旧版配置中选择 32 位 Tick
configUSE_MUTEXES 1 启用互斥锁
configUSE_COUNTING_SEMAPHORES 1 启用计数信号量
configUSE_TIMERS 1 启用软件定时器
configTIMER_TASK_PRIORITY 3 软件定时器服务任务优先级
configTIMER_QUEUE_LENGTH 10 软件定时器命令队列长度
configTIMER_TASK_STACK_DEPTH 256 定时器任务栈深度
configCHECK_FOR_STACK_OVERFLOW 2 开启栈溢出检查
configUSE_MALLOC_FAILED_HOOK 1 启用内存申请失败钩子
configUSE_IDLE_HOOK 0 是否启用空闲任务钩子
configUSE_TICK_HOOK 0 是否启用 Tick 钩子
configSUPPORT_DYNAMIC_ALLOCATION 1 允许动态创建任务
configSUPPORT_STATIC_ALLOCATION 0 是否允许静态创建任务
相关推荐
물の韜2 小时前
ZYNQ/FMQL系列Linux之I2C开发实战
linux·arm开发·嵌入式硬件·设备树·iic·zynq
Zw-awa2 小时前
屏幕不止一种:OLED、LCD1602字符屏、TFT/IPS图形屏和串口屏怎么用,这与TTY和PTY又有什么关系
stm32
modelmd4 小时前
离线安装与使用 vscode Remote SSH插件
ide·vscode·ssh
无敌贵点大王4 小时前
从寄存器层面重学STM32(7)——定时器
stm32·单片机·嵌入式硬件
碧海银沙音频科技研究院5 小时前
粉色笔记本电脑每次要等把电池电耗尽才能开机问题解决方法
嵌入式硬件
wuyk55519 小时前
Keil工具链-第05讲-启动文件与__main到底做了什么
c语言·stm32·单片机·嵌入式硬件·物联网
shijuejianceliu1 天前
传统规则视觉与嵌入式AI智能相机的技术差异
人工智能·嵌入式硬件·视觉检测·相机
Zw-awa1 天前
OLED为什么能显示字:软件I2C换成硬件I2C
stm32
不脱发的程序猿1 天前
GD32串口数据收进内存之后DMA如何处理?
单片机·嵌入式硬件·dma·嵌入式·串口通信·gd32