• 移植RT-Thread Nano到STM32F407ZGT6上运行


    1. RT-Thread Nano简单介绍

    RT-Thread Nano 是国内开源的一个实时内核库,RAM与ROM占用极小,而且该有的内核功能如:线程管理、线程同步与通信、时钟管理、中断管理、内存管理等功能一个不少。

    下图是 RT-Thread Nano 的软件框架:
    在这里插入图片描述
    Finsh组件是 RT-Thread Nano 的控制台命令系统,可实现用户命令交互。

    RT-Thread Nano 它与标准版本的区别就是它只留下实时内核相关的代码,去除了标准版的 device 框架和各种组件,还有不使用 Scons 构建系统,也没有了 Kconfing 已经 ENV 配置工具,所以代码会更简单。

    2. 移植准备工作

    3. 移植哪些文件

    移植过程主要分为两个部分:libcpu 移植与板级移植。

    实际上对于不同架构要移植的代码,RT-Thread 官方也已经帮我们把常见的CPU架构相关的代码写好了,我们到 libcpu 目录下找到对应自己芯片架构的代码即可。所以真正要我们移植提供的代码就只是板级硬件初始化相关的代码。

    • libcpu 移植

      libcpu 向下提供了一套统一的 CPU 架构移植接口,是针对CPU架构(比如ARM,RISC-V)的移植。这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容,RT-Thread 支持的 cpu 架构在源码的 libcpu 文件夹下。

    • 板级移植

      板级移植主要是针对 rt_hw_board_init() 函数内容的实现,该函数在板级配置文件 board.c 中,函数中做了许多系统启动必要的工作,其中包含:

      1. 配置系统时钟。
      2. 实现 OS 节拍。(其中步骤 1 和 2 为 3.1.5 版本中 #error TODO 1 的部分:#error "TODO 1: OS Tick Configuration."
      3. 初始化外设:如 GPIO/UART 等等,若需要请在此处调用。
      4. 初始化系统内存堆,实现动态堆内存管理。
      5. 板级自动初始化,使用 INIT_BOARD_EXPORT() 自动初始化的函数会在此处被初始化。
      6. 其他必要的初始化,如 MMU 配置(需要时请自行在 rt_hw_board_init 函数中调用应用函数实现)。

    4. 动手移植

    4.1 添加 Nano 源码到keil工程

    1、把 RT-Thread Nano 下面目录的源码复制到我们准备好的裸机工程目录 rtthread_nano 下。

    • Nano 源码中的 include、libcpu、src 文件夹。
    • 配置文件:源码代码 rtthread/bsp 文件夹中的两个文件:board.crtconfig.h
      在这里插入图片描述

    2、使用 keil 打开我们事先准备好的裸机工程,新建 rtthread_nano 分组,并在该分组下添加以下源码:

    • 添加工程下 rtthread/src/ 文件夹中所有文件到工程;

    • 添加工程下 rtthread/libcpu/ 文件夹中相应内核的 CPU 移植文件及上下文切换文件: cpuport.c 以及 context_rvds.S

      注意:该目录下选择我们对应芯片架构的文件目录,比如我使用的是 STM32F407 ,那么就选择 Cortex-M4 架构里面的文件。

    • 添加 rtthread/ 文件夹下的 board.c

      在这里插入图片描述

    4.2 添加头文件路径

    因为源码目录只有两个目录(include和bsp目录)下有头文件,只添加这两个头文件路径即可。
    在这里插入图片描述

    4.3 解决编译报错

    添加文件和头文件路径后,编译报如下错误:

    STM32F407_FreeRTOS_Template\STM32F407_FreeRTOS_Template.axf: Error: L6200E: Symbol HardFault_Handler multiply defined (by context_rvds.o and stm32f4xx_it.o).
    
    • 1

    报错说重复定义了。实际上 RT-Thread 接管异常处理函数 HardFault_Handler() 和悬挂处理函数 PendSV_Handler(),这两个函数已由 RT-Thread 实现,所以我们删除 stm32f4xx_it.c 文件定义的这两个中断函数(如果该文件没有定义这两个文件,则不用管)。

    4.4 配置系统时钟和外设初始化

    在 RT-Thread 启动的入口函数 rtthread_startup(void) 中,会调用一个初始化板级硬件的函数 rt_hw_board_init() ,这个函数就是需要我们在 board.c 中实现的板级初始化相关的代码。比如要完成系统的时钟配置,设备上要到的一些外设初始化等等,都要在这个函数里面实现。

    我使用的是 CubeMX 生成的工程,已经生成了系统时钟的配置函数了,函数复制到 rt_hw_board_init() 调用即可,而且还用到了GPIOB和UART外设,所以初始化函数也一起放到这个函数里面调用。如下代码:

    /**
     * This function will initial your board.
     */
    void rt_hw_board_init()
    {
    	HAL_Init();						// HAL库初始化
    	SystemClock_Config();			// 系统时钟配置
    	MX_GPIO_Init();					// GPIO外设初始化
    	MX_USART1_UART_Init();			// USART外设初始化
        /* System Clock Update */
        SystemCoreClockUpdate();
        
        /* System Tick Configuration */
        _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
    
        /* Call components board initial (use INIT_BOARD_EXPORT()) */
    #ifdef RT_USING_COMPONENTS_INIT
        rt_components_board_init();
    #endif
    
    #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
        rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
    #endif
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    另外,board.c 文件中已经实现了系统嘀嗒中断服务函数,为 RT-Thread 提供心跳。

    void SysTick_Handler(void)
    {
        /* enter interrupt */
        rt_interrupt_enter();
    
        rt_tick_increase();
    
        /* leave interrupt */
        rt_interrupt_leave();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果编译报错重复定义了这个函数的话,需要删除原来实现的 SysTick_Handler 中断服务程序,保留RT-Thread Nano 提供的这个函数。

    4.5 内存堆初始化配置

    系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 是默认不开启内存堆功能。

    当开启了内存堆功能之后,就可以使用动态内存分配了,也可以动态创建线程等等。RT-Thread Nano 实现了一套动态内存分配接口函数,如 rt_malloc、rt_free 等。

    我们要使用内存堆功能,只需要在 rtconfig.h 文件中定义这个 RT_USING_HEAP 宏即可,然后内存堆初始化函数就会在 rt_hw_board_init 中被调用。

    在这里插入图片描述

    内存堆的默认设置大小,使用的是一个数组给定的一个固定大小了的。如下代码:

    #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    #define RT_HEAP_SIZE 1024
    static uint32_t rt_heap[RT_HEAP_SIZE];     // heap default size: 4K(1024 * 4)
    RT_WEAK void *rt_heap_begin_get(void)
    {
        return rt_heap;
    }
    
    RT_WEAK void *rt_heap_end_get(void)
    {
        return rt_heap + RT_HEAP_SIZE;
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面代码实际上只是定义了1024字节大小的数组作为内存堆,但是我们可以通过散列文件或者链接脚本得知还剩下多少内存空间,可以作为内存堆使用。

    获取内存堆最大可用RAM大小的方法

    实际上 ZI数据段 之后的所有内存空间,都可以作为内存堆使用,这样就这样设置最大的内存堆大小了。关于ZI数据段的结束地址,我们可以通过链接脚本来获取。对于 keil 来说,代码如下:

    #define STM32_SRAM1_START              (0x20000000)						
    #define STM32_SRAM1_END                (STM32_SRAM1_START + 20 * 1024)   // 结束地址 = 0x20000000(基址) + 20K(RAM大小)
    
    #if defined(__CC_ARM) || defined(__CLANG_ARM)  // 编译器判断
    // RW_IRAM1就是 keil 散列文件定义的RAM空间大小,ZI就是获取该数据段的结束地址。从ZI段之后的所有内存空间都可以作为内存堆使用
    extern int Image$$RW_IRAM1$$ZI$$Limit;                   				
    #define HEAP_BEGIN      ((void *)&Image$$RW_IRAM1$$ZI$$Limit)			// 获取到 ZI 数据段的结束地址,作为内存堆的起始地址
    #endif
    
    #define HEAP_END                       STM32_SRAM1_END					// 内存堆结束地址就是芯片可以RAM空间的最大地址
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5. 移植验证

    当 RT-Thread Nano 启动起来之后,会创建一个 main 线程的,我们在main函数中添加实验代码。如下:

    int main(void)
    {
        while (1)
        {
            rt_thread_mdelay(1000);
            HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_4);
            HAL_UART_Transmit(&huart1, "hello world.\r\n", strlen("hello world.\r\n"), 10);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上面 main 函数里面没没有调用系统时钟配置,外设初始化等函数。这是因为在调用 main 函数之前,就先调用了 rt_hw_board_init() 函数,在这个函数里面配置了系统时钟、板级外设的初始化等工作了。而 main 函数现在其实是 RT-Thread Nano 创建的一个main线程函数了,它参与线程的调度过程。

    上面代码就只是让LED闪烁,和向串口输出一行字符串功能。打开串口终端,可以看到打印出的字符串如下:

    在这里插入图片描述

    说明 RT-Thread Nano 正常运行起来了。

  • 相关阅读:
    IOS常见的bug
    git 常用命令
    Spring基础与核心概念
    【31】c++设计模式——>模板方法模式
    FFplay文档解读-13-设备选项,输入设备一
    如何估算transformer模型的显存大小
    大数据常用的Linux命令
    PHP排序sort()、asort() 和 ksort() 的区别及用法
    嵌入式Linux开发面试题和答案
    Spring读书笔记——bean创建(上)
  • 原文地址:https://blog.csdn.net/luobeihai/article/details/126436693