RTOS篇——手把手带你完成FreeRTOS源码移植
本文最后更新于85 天前,其中的信息可能已经过时,如有错误请发送邮件到527388734@qq.com

一、引言:开启FreeRTOS之旅

在上一章节中,我们共同探讨了RTOS系统在嵌入式开发中的核心价值,也初步领略了实时操作系统的独特魅力。那么从本章开始,我们算是正式进入RTOS的学习阶段啦!我将以FreeRTOS为例为大家深入挖掘其底层内核的运行机制,一步一步揭开他的神秘面纱,那么在这一切的开始之前我们必须先要“利其器”——完成关于FreeRTOS的移植工作。

二、何为FreeRTOS?

可能有些小伙伴是第一次接触FreeRTOS,心中难免会有疑问:为什么选择FreeRTOS?它到底好在哪儿?哈哈别急,在动手移植之前,我们先花几分钟认识一下这位即将与我们长期并肩作战的“伙伴”。

简单来说,FreeRTOS是一个专为微控制器和小型嵌入式系统设计的实时操作系统内核。它的名字就揭示了它的核心特质:Free(自由/免费) + RTOS(实时操作系统)。凭借其开源免费、高度可裁剪、稳定可靠的特点,它已成为全球嵌入式开发者中最受欢迎的RTOS之一,从工业控制到智能家居,从穿戴设备到物联网终端,处处都有它的身影。

那么他到底好在哪了呢?这就不得不提到他的三大核心优势了:轻量、可移植性、强大的生态社区。

  • 轻量——仅需几KB的ROM和几百字节的RAM即可运行,这代表着它能够在资源极其有限的MCU上”挥洒自如“。
  • 可移植性——这是本章“移植”的核心基础,FreeRTOS的架构设计极为清晰,将核心调度逻辑与硬件底层(如时钟、中断、任务栈)完全解耦。这意味着我们只需关注并实现少量的硬件接口函数,就能将它轻松“嫁接”到新的芯片平台上。
  • 强大的生态与社区——FreeRTOS拥有异常丰富的文档,在学习过程遇到问题可以很方便的找到答案。

理解FreeRTOS的价值,不仅在于掌握一个工具,更在于领悟其背后的设计哲学——如何在有限的资源中实现最大的确定性。这种对实时性、可靠性和效率的追求,正是嵌入式开发的精髓所在。正是基于这些特性,FreeRTOS成为了我们学习RTOS的绝佳起点。好了,我们话不多说,接下来就开始步入我们移植的正题啦!

三、FreeRTOS源码组织结构

3.1 FreeRTOS源码获取

移植源码的第一步肯定是要获取到一份可靠的源码,给大家提供一下FreeRTOS的官网,可以到官网上下载到最新的源码资源。FreeRTOS 官网不仅提供了源码,还有非常全面的文档和教程,非常适合新手学习和查阅。在后续学习 API 或进行开发时,建议大家多借助官网资源,效率会更高。

官网地址:https://www.freertos.org

可以看到目前的最新版本是2024.06.04发行的,但是本博客主要是以2022.12.01发布的版本为例,展开移植的讲解,我也会在文章末尾附上我使用环境资源,便于随步骤操作,大家自行选择源码版本即可。

3.2 FreeRTOS源码结构

解压后的FreeRTOS源码如下,这里面最关键移植的目录是FreeRTOS以及FreeRTOS-Plus。

FreeRTOS-Plus可以理解为是“升级版”,里面提供了更为丰富的组件。例如TCP、命令行接口、 I/O 抽象层和文件系统组件等等。我们在学习移植阶段只关注FreeRTOS的最基础的功能。所以FreeRTOS-Plus这里面的源码结构我们可以不过多关注。

进入到FreeRTOS下面,Source就是我们接下来所要移植的关键部分了,里面是FreeRTOS的核心源码

接下来,我将具体介绍一下Source层,每一个文件的具体所代表的是什么:

  • include:为头文件目录,可以看到里面全都是一些头文件:
    • 红色标注:核心必备头文件
      • task.h – 任务管理API(如 xTaskCreate()
      • queue.h – 队列通信API
      • semphr.h – 信号量API
      • 这些是移植时必须关注的关键组件
    • 蓝色标注:可选功能头文件
      • timers.h – 软件定时器
      • event_groups.h – 事件组
    • 绿色标注:特殊功能与兼容性
  • portable:为对接硬件平台的目录,包含了 FreeRTOS 针对不同编译器和处理器架构的移植层代码。
    • 编译器相关移植目录
      • GCC:用于 GNU GCC 编译器(STM32 CubeIDE、Eclipse 等常用)
      • Keil:用于 ARM Keil MDK 编译器
      • ARMClang:用于 ARM Compiler 6(ARMv8 架构)
      • MSVC-MingW:用于 Windows 模拟器开发
      • CCS:用于 Texas Instruments Code Composer Studio
      • SDCC:用于 8051 等 8 位单片机
      • RVDS:Keil软件下的MCU内核,包括M3、M4、M0等移植内核(我们后面移植的平台也主要是Keil,这个目录需要留意一下)
    • MemMang内存管理实现(很重要!我们接下来会选择下面的一种方案进行移植)
      • heap_1.c – 最简单的分配,不支持释放
      • heap_2.c – 最佳匹配算法,支持释放但会产生碎片
      • heap_3.c – 包装标准 malloc/free(需要编译器支持)
      • heap_4.c – 推荐方案,支持释放、碎片合并
      • heap_5.c – heap_4 的增强版,支持非连续内存区域
    • 第三方和特殊平台
      • ThirdParty:社区贡献的移植代码
      • Rowley:CrossWorks for ARM 编译器
      • Tasking:Tasking 编译器
      • Renesas:瑞萨单片机
      • MPLAB:Microchip PIC 单片机
      • MikroC:mikroC 编译器(PIC、AVR 等)
  • croutine.c:协程功能源文件
  • event_groups.c:事件标志组源文件
  • list.c:列表和列表项源文件
  • queue.c:消息队列源文件
  • stream_buffer.c:流缓存
  • task.c:任务管理源文件
  • timer.c:软件定时器源文件

需要在这里提一嘴,在我们移植FreeRTOS到工程的时候,同样需要我们自己创建一个FreeRTOSConfig.h文件。他是 FreeRTOS 的用户配置文件,也是整个 FreeRTOS 移植和定制的核心文件。比如我们我们想要开启FreeRTOS的定时器功能,可以通过改变这个文件里的宏来控制,大家在此有个印象先,为我们后面的移植打下基础。


四、FreeRTOS移植

好了,铺垫到这里就可以正式开启我们的任务啦!我将从创建Keil工程开始,一步一步带你完成FreeRTOS的移植,我们话不多说,开始干活!

1.Keil工程创建

“万里长征”从这里迈出第一步!我们将创建一个基础的STM32工程框架。

2.HAL库移植

HAL库是我们能够让我们硬件运行起来的基石,因此这一步也是重中之重。我们在安装Cortex-M4内核软件包的时候,里面就会附带有HAL库的源码。我的软件包版本选择的是2.12.0,因此HAL库的版本同样是2.12.0,我会把我使用到的所以环境版本附到百度网盘上,大家可以跟我的环境保持一致进行移植,同样大家也可以自行选择版本。

HAL库的源码在我们安装完软件包之后,就会集成到我们keil的安装目录下面,

在移植过程中,由于编译器环境、HAL库版本等差异,可能会出现与我不同的编译错误。遇到报错不要慌张,这完全是移植过程中的正常现象。我们需要耐心地逐一解决每个问题。相信我,当你最终看到”0 Errors, 0 Warnings”时,那种成就感绝对会让你觉得一切努力都值得!

3.FreeRTOS源码移植

我们前面已经完成了所有准备工作,接下来终于到了我们的重头戏!FreeRTOS的源码移植!

到这里我们的全部代码就算移植完成啦!接下来就是上板测试了,我会给到大家一个demo,可以实际烧录到我们的STM32F407IGT6开发板上面,看看能不能成功运行!

#include "stdio.h"
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"

//初始化系统时钟配置
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 25;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
//    Error_Handler();
    }

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
//    Error_Handler();
    }
}

void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();
    /*新版开发板为 PF8、PF9、PF10、PE2,根据自己板子原理图进行引脚配置*/
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET);

    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

}



void USART1_Init(void)
{
    //串口引脚初始化
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    //串口配置初始化
    UART_HandleTypeDef huart1;
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;

  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
//    Error_Handler();
  }
}

void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOI_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();

    /*Configure GPIO pin : PI9 */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);

    /*Configure GPIO pin : PF11 */
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    /* EXTI interrupt init*/
    HAL_NVIC_SetPriority(EXTI9_5_IRQn, 5, 1);
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 1);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

}

//printf重定向
int fputc(int ch,FILE* F)
{
    while(!(USART1->SR&(1<<7)));
    USART1->DR = (uint8_t)ch;

    return ch;
}


int main(void)
{
    uint32_t cnt = 0;
    /*    HAL_Init()调用的是 stm32f4xx_hal_timebase_tim_template.c 文件里的 HAL_InitTick()
        内部初始化的是TIM6,初始化以后可以调用 HAL_Delay 函数 */
    HAL_Init();
    /*    SystemClock_Config() 初始化时钟以后也调用了 stm32f4xx_hal_timebase_tim_template.c 文件里的 HAL_InitTick()
        但并没有初始化和开启systick中断*/
    SystemClock_Config();        
    LED_Init();
    USART1_Init();

    while(1)
    {
        HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);
        HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_2);
        printf("cnt = %u\r\n",cnt++);
        HAL_Delay(1000);

    }
}
//因为上边调用开启了SysTick中断,所以要写一个实现函数
//另外将操作系统实现的systick中断函数给注释掉,避免多重定义
void SysTick_Handler(void)
{


}

void EXTI9_5_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
}

void EXTI15_10_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_9)
    {
        if(HAL_GPIO_ReadPin(GPIOI, GPIO_PIN_9) == GPIO_PIN_RESET)
        {
            HAL_Delay(20);        //如果在中断中使用 HAL_Delay 它的优先级一定要比此中断优先级高
            if(HAL_GPIO_ReadPin(GPIOI, GPIO_PIN_9) == GPIO_PIN_RESET)
            {
                printf("scan : key1\r\n");
            }
        }
    }
    else if(GPIO_Pin == GPIO_PIN_11)
    {
        if(HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_11) == GPIO_PIN_RESET)
        {
            HAL_Delay(20);        //如果在中断中使用 HAL_Delay 它的优先级一定要比此中断优先级高
            if(HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_11) == GPIO_PIN_RESET)
            {
                printf("scan : key2\r\n");
            }
        }
    }
}

我们在移植的工程中一定要有着不断试错的心态,很少情况下是可以一次性很顺利的移植成功的,都会出现各种各样的报错,但请记住,每一次解决错误都是一次宝贵的学习机会。,在此过程中我们不但能够获取到移植的经验,同样可以也可以锻炼一个平稳的心性。

如果在移植过程中遇到与我不同的报错或问题,非常欢迎大家与我交流讨论!后面我会将本文章所有用到的软件环境提到网盘。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇