历史上的今天
返回首页

历史上的今天

今天是:2025年07月28日(星期一)

正在发生

2021年07月28日 | FreeRTOS学习笔记01 | 移植FreeRTOS到小熊派开发板(STM32L431RC)

2021-07-28 来源:eefocus

一、移植准备

官方下载链接:下载官方发布的包,截至发文时间,最新发布的版本为FreeRTOSv202012.00.zip。

  • Github仓库地址:master分支为官方不断更新修改的包。

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

    官方下载链接:下载官方发布的包,截至发文时间,最新发布的版本为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内核,所以添加位于 FreeRTOSportableRVDSARM_CM4F 下的 port.c 文件:

    再添加位于 FreeRTOSportableMemMang 下的 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...rn");


        vTaskDelay(1000);

      }

    }


    void task2_entry(void *args)

    {

      /* Enter into a forever loop. */

      while(1)

      {

        printf("task 2 application running...rn");

        

        vTaskDelay(1000);

      }

    }


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


    /* USER CODE BEGIN 2 */

    printf("FreeRTOS port on BearPi board by mculover666rn");


    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都不相同,需要特别注意!


    推荐阅读

    史海拾趣

    Defense Logistics Agency公司的发展小趣事

    在成功整合供应链之后,DLA开始拓展其业务领域。除了为美军提供后勤保障服务外,DLA还开始向各州、当地政府组织、外国政府和国际组织提供后勤保障服务。这一拓展不仅拓宽了DLA的业务范围,也增强了其国际影响力。

    岑科(CENKER)公司的发展小趣事

    在过去,国内企业在车载信号传输领域的共模电感选择有限,主要依赖进口品牌。岑科意识到这一市场的巨大空缺后,决定借助自主研发设备的优势进行研发工作。经过两三年的攻关,岑科成功研发出了ACML系列共模电感。这一系列产品在温度特性上表现优异,可在-40℃到150℃的范围内正常工作,并适用于CAN-BUS、CAN-FD、A2B及以太网等多种场景。岑科的这一研发成果不仅填补了国内市场空白,还实现了国产替代,为汽车电子行业的发展做出了重要贡献。

    HANA Micron公司的发展小趣事

    随着线上及线下业务的快速增长,Hama面临着交货时效性的巨大挑战。为了应对这一挑战,公司决定在蒙海姆总部建造一座集仓储、拣选与发货一体的物流中心。该物流中心采用了先进的自动化和人工子系统相结合的设计方案,大大提高了物流运作的效率。新系统的启用使得Hama每天能够处理数以万计的订单,确保了产品的及时交付,进一步提升了客户满意度和品牌影响力。

    全鹏(CHAMPION)公司的发展小趣事

    全鹏公司一直重视研发和创新。为了不断满足市场需求,公司投入大量资源用于新技术和新产品的研发。通过引进高素质、高学历的研发人员,建立先进的研发实验室和测试中心,全鹏公司在产品研发方面取得了显著成果。这些新技术和新产品的推出不仅提升了全鹏公司的市场竞争力,也为客户带来了更好的使用体验。

    ENOCEAN公司的发展小趣事

    为了进一步推动其技术在建筑行业的应用,EnOcean发起并建立了EnOcean联盟。该联盟由来自建筑行业的400多家公司组成,致力于推广基于EnOcean无线标准的免维护无线解决方案。通过与联盟成员的合作,EnOcean不断拓展其市场份额,并为智慧楼宇及能源管理提供更稳定的硬件解决方案。

    ADI(亚德诺半导体)公司的发展小趣事

    2010年,EnOcean公司成功成为国际标准组织ISO/IEC的成员。这一里程碑事件标志着EnOcean的技术和产品在全球范围内得到了广泛认可。通过参与制定无线传感网络的国际标准,EnOcean进一步巩固了其在行业内的领先地位,为推广其无线无源传输技术奠定了坚实基础。

    问答坊 | AI 解惑

    软件无线电架构设计组件选择策略

      当选择ASIC、FPGA或DSP时,设计人员应当考虑可程序性、整合度、开发周期、性能,以及功率等五项重要的选择准则, 上述准则中的任何一条都会对设计人员选择DSP、ASIC或FPGA产生直接的影响。 采用ASIC、FPGA和DSP组件设计 ...…

    查看全部问答>

    【转】如何用keil在C中嵌入汇编

    本帖最后由 paulhyde 于 2014-9-15 03:44 编辑 有时候用到需要精确延时之类的子程序时,用C语言比较难控制,这时候就可以在C中嵌入汇编 比较常用的keil中嵌入汇编的方法如下所示: 如图一,在C文件中要嵌入汇编的地方用#pragma asm和#pragma end ...…

    查看全部问答>

    蚊子直升机

    这种直升机您肯定没有见过吧,是不是很出乎您的想像,直升机还可以设计成这样,可以说我们的设计师在设计的见解上非常独特,他总是有许多不一样的想法。正因为他跳出了常规的设计思路,所以每次给大家展现的东西,都让我们眼前一亮。这种思维的跳跃 ...…

    查看全部问答>

    关于DDK开发中的Build SoftICE Symbols的问题,我编译的工程生成了.sys文件,但没有生成.nms等用于调试的文件,不知是否和下面的出错信息

    以下是出错的内容,我不明白“Matching PDB file not found.”是什么意思,是不是说生成的.sys文件里面没有编译信息? --------------------Configuration: RepSample - Win32 Free-------------------- Compiling resources with DDK resource c ...…

    查看全部问答>

    linux内核移植到2440问题

    按照网上的教程交叉编译了一个linux的内核,在下载到arm2440上运行不了。显示的输出信息为: Read chip id = ec76 Nand flash status = c0, NandAddr=1 buf address :0x30008000 Set boot params = root=/dev/mtdblock2  load_ramdis ...…

    查看全部问答>

    关于I/O的困惑!

    因为想写一只关于SATA I/O的程序,这几天一直混在www.t13.org,但通读了ATA-1,并没有发现命令寄存器与端口(1f0-1f7)的对应关系.(文档中只解释了每个寄存器的含义)。根本没提及端口(1f0-1f7),只提到了针脚37对应的CS1FX。而CS1FX是什么东东 ...…

    查看全部问答>

    ARM的相关问题

    我刚接触单片机,能不能请各位高手解释一下ARM和单片机的关系。…

    查看全部问答>

    PLC输出电路硬件互锁的漏洞与可怕的后果

    在异步电动机的正反转控制的主电路中,两台接触器的主触点如果同时闭合,将会造成三相电源相间短路的事故,使熔断器熔断。 梯形图中的软件互锁电路并不保险,在电动机改变旋转方向的过程中,可能原来接通的接触器的主触点的电弧还没有熄灭,另一个 ...…

    查看全部问答>