风过空庭,字句正徐来。
关于关于本站关于我给我点钱
更多时间线友链文件服务wiki
联系写留言发邮件GitHub
© 2024-2026 yono. | RSS 订阅 | 站点地图 | | Stay hungry. Stay foolish.
Powered by Mix Space&
白い
.
| 粤 ICP 备2024284785号-1 |
正在被0人看爆
纸白微明,未成篇章。

再谈 threadX 移植

(已编辑)
/ ,
232
1
AI·GEN

关键洞察

这篇文章上次修改于,可能部分内容已经不适用,如有疑问可询问作者。

再谈 threadX 移植

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • ThreadX 再介绍

    一些此前的内容可见最全认证 RTOS——azure_threadX 移植教程 - 土星环的基地。

    由于 threadX 捐赠给 eclipse 基金会,现在已经不叫 azure_threadX 改为 eclipse_threadX 。

    最近我的新的 CMAKE 工程结构逐渐趋于稳定和完善,也是时候介绍一下相对现代的移植方式了。过去使用的 IDE(点名批评KEIL) 过于落后,通常只能手动指定所有的源码文件和 includPath,而 CMAKE 则可以提供更加自由更加自动化的方式。

    在以前的博文里提到过,“大多数好的 c 语言库都会提供搜集了所有源码文件的 CMakeLists.txt”,而优秀的库会提供包括编译条件和按照编译条件选择源码文件的 CMakeLists.txt。所以在一个 CMAKE 项目中引入 threadX 软件包将非常方便。

    前提条件

    首先读者应该有基本的 CMAKE 认知以及简单的实践经验,移植前先构建一个能基础闪灯的工程并且烧录成功,保证闪灯的频率大致与预想相同。

    • 对于 CMAKE 子目录和 project 的概念有基本的理解
    • 完成过一个 CMAKE 项目的搭建和编译

    没有这方面知识的可以先学习CMAKE 扫盲 - 土星环的基地。

    大致思路

    threadX 软件包已经提供了几乎最全面的支持。我们只要将整个代码库弄进工程,少量给出几个参数,随后以其为子目录,再自行添加链接库,就算是完成移植了。

    将代码库弄进工程

    对于初学者或者不想要使用 git 管理代码的工程师,只要将这个代码库完全地下载和复制到项目中即可。

    例如我的这个项目,使用 6_Rtos 文件夹存放 RTOS 相关的源码,threadx-master 文件夹就是直接下载源码包并且解压得到的。当然如果读者熟悉 git 子模块或者 CMAKE 在线导入的方法,也是极好的,我这里只做尽可能简单的最小系统搭建。

    image-20250526145554852

    image-20250526145554852

    在顶层的 CMakeLists.txt 中使用这样的语句,即可引入 threadX。我这里的顶层 CMakeLists.txt 在上一层的 src 文件夹下,反正依据 CMAKE 项目结构,需要比解压的源码包更上层。

    其中 THREADX_ARCH 指定了内核型号,THREADX_TOOLCHAIN 指定了编译器型号,这两个参数将由 threadx-master 中的 CMAKE 脚本自动选定 port 文件,详细可以自行研究 threadX 库的根 CMakeLists.txt 。

    CodeBlock Loading...

    添加链接库

    Note

    再次提醒,读者必须完成过一个 CMAKE 项目的搭建和编译,否则以下的 CMAKE 基础脚本写法也不会懂的

    由于我们在顶层 CMakeLists.txt 引入的子目录,所以需要在顶层项目中添加链接库。使用类似如下的语句

    CodeBlock Loading...

    如果希望使用经典的 tx_user.h 配置文件。那么还需要在引入子目录前使用类似如下的语句。预先设置一个 TX_USER_FILE 参数用于导向我们自己创建的 tx_user.h 配置文件,而库内的 cmake 脚本会依据 TX_USER_FILE 参数是否被设置进行不同的操作,可以自行了解。

    CodeBlock Loading...

    一个相对完整的顶层 CMakeLists.txt 如下

    CodeBlock Loading...

    一些芯片级的支持

    中断部分

    首先 threadX 会接管 SysTick_Handler 和 PendSV_Handler 两个中断,所以需要将原工程中的这两个中断函数的定义给注释掉。

    其次这些中断的实现,threadx 不会直接地在库中引入,而是需要自己实现。

    推荐是创建一个 tx_initialize_low_level.S 文件在自己工程的其他文件夹,然后在 threadX 软件包中找一个合适的同名文件(不同架构都有示例),将这个文件复制出来到自己创建的里面。不要直接引入他的示例,会破坏源码包的独立性。

    然后依据编译报错修改自己的文件,主要是链接符号的匹配问题,例如中断向量表的名字。

    以下是一个简单的示例。其实讲道理是所有他给出的中断函数都应该进行替换屏蔽,但是要修改自己的中断函数名没必要,只屏蔽必要的两个中断函数由其接管就好,其他函数也没什么很大的意义。

    CodeBlock Loading...

    注意修改其中的 SYSTEM_CLOCK、 SYSTICK_CYCLES 两个参数,与主频和期望的任务时间分辨力匹配。例如我这个文件中,主频是600M,tx_thread_sleep(1) 期望是 1ms。

    GNU 部分

    gcc 编译链与 AC 编译器的 MicroLib 不同,需要自己实现许多系统级接口。一个经典的 io 函数是 printf ,如果不实现接口,编译链是无法通过的。在项目中增加以下两个文件就好,当然我给出的源码并没有 printf 的接口支持。

    printf 建议使用私有实现,比如以下的库。

    CodeBlock Loading...
    CMAKE
    # threadx
    set(THREADX_ARCH cortex_m7)
    set(THREADX_TOOLCHAIN gnu)
    add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # 添加子目录
    
    CMAKE
    target_link_libraries( MY_CMAKE_PROJECT_NAME
        # 省略其他的链接库,例如 user_src,在最后添加
        azrtos::threadx
    )
    
    CMAKE
    set(TX_USER_FILE "${CMAKE_CURRENT_LIST_DIR}/6_Rtos/UserCfg/tx_user.h")
    add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # 添加子目录
    
    CMAKE
    # 指定CMake的最低版本要求为3.22
    cmake_minimum_required(VERSION 3.22)
    
    #
    # 该文件是cmake调用的主构建文件
    # 用户可以根据需要自由修改此文件。
    #
    
    # 设置编译器设置部分
    set(CMAKE_C_STANDARD 11)            # 设置C标准为C11
    set(CMAKE_C_STANDARD_REQUIRED ON)   # 要求使用指定的C标准
    set(CMAKE_C_EXTENSIONS ON)          # 启用编译器扩展
    
    
    # set(CMAKE_BUILD_TYPE "Release")
    # 定义构建类型
    if(NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE "Debug")   # 如果未设置CMAKE_BUILD_TYPE,则默认设置为"Debug"。该参数可以在使用类似"cmake ../"生成原生构建系统时添加-DCMAKE_BUILD_TYPE=Release指定
    endif()
    
    # 包含工具链文件
    include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/gcc-arm-none-eabi.cmake")
    
    # 设置项目名称
    # set(CMAKE_PROJECT_NAME H7_GCC_BASE)  # 设置项目名称
    if(DEFINED ENV{PROGRAM_NAME})
        set(CMAKE_PROJECT_NAME $ENV{PROGRAM_NAME})
    else()
        message(WARNING "PROGRAM_NAME environment variable is not set. Using default project name.")
        set(CMAKE_PROJECT_NAME "DefaultProjectName")
    endif()
    
    
    # 启用编译命令生成,以便于其他工具进行索引例如clangd
    set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) # 生成compile_commands.json,以便IDE或工具使用
    
    # 核心项目设置
    project(${CMAKE_PROJECT_NAME})                  # 定义项目,使用之前设置的项目名称
    message("Build type: " ${CMAKE_BUILD_TYPE})     # 消息输出构建类型
    
    # 启用CMake对ASM和C语言的支持
    enable_language(C ASM)              # 启用C和汇编(ASM)语言支持
    
    # 创建两个可执行对象
    # add_executable(${CMAKE_PROJECT_NAME})    # 不携带BL部分
    add_executable(${CMAKE_PROJECT_NAME}_BL) # 携带BL部分
    
    foreach(target IN ITEMS 
    # ${CMAKE_PROJECT_NAME} 
    ${CMAKE_PROJECT_NAME}_BL)
        # 链接目录设置
        target_link_directories(${target} PRIVATE
            # 添加用户定义的库搜索路径
            # e.g., "/path/to/libs"
        )
    
        # 向可执行目标添加源文件
        target_sources(${target} PRIVATE
            # 添加额外的源文件
            # e.g., "src/main.c"
        )
    
        # 添加包含路径
        target_include_directories(${target} PRIVATE
            # 添加用户定义的包含路径
            # e.g., "include"
        )
    
        # 添加项目符号(宏)
        target_compile_definitions(${target} PRIVATE
            # 添加用户定义的符号
            # e.g., "MY_MACRO=1"
        )
    
        # 添加链接库
        target_link_libraries(${target}
            user_src # 链接user_src库 实际上也是以project()项目的形式存在
            Dataflow
            azrtos::threadx
            # modbusx
            # 添加用户定义的库
            # e.g., "mylib"
        )
    endforeach()
    
    # target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
    #     -T "${CMAKE_SOURCE_DIR}/5_PhysicalChip/CPU/GNU/GD32H7xx.ld"
    # )
    target_link_options(${CMAKE_PROJECT_NAME}_BL PRIVATE
        -T "${CMAKE_SOURCE_DIR}/5_PhysicalChip/CPU/GNU/GD32H7xx.ld"
    )
    
    # 添加子目录部分,这会自动处理子目录中的CMakeLists.txt文件
    add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake)     # 添加子目录
    # Dataflow GNU
    add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/7_Exlib/Dataflow-main/common)     # 添加子目录
    # threadx
    set(THREADX_ARCH cortex_m7)
    set(THREADX_TOOLCHAIN gnu)
    set(TX_USER_FILE "${CMAKE_CURRENT_LIST_DIR}/6_Rtos/UserCfg/tx_user.h")
    add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # 添加子目录
    # modbusx GNU
    # add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/7_Exlib/modbusX/common)     # 添加子目录
    
    
    
    # 为单独的文件添加编译标签
    include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake/extra-compile-flags.cmake")
    
    # 运行一下构建后任务
    include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake/post-build-tasks.cmake")
    
    
    ASM
    /**************************************************************************/
    /*                                                                        */
    /*       Copyright (c) Microsoft Corporation. All rights reserved.        */
    /*                                                                        */
    /*       This software is licensed under the Microsoft Software License   */
    /*       Terms for Microsoft Azure RTOS. Full text of the license can be  */
    /*       found in the LICENSE file at https://aka.ms/AzureRTOS_EULA       */
    /*       and in the root directory of this software.                      */
    /*                                                                        */
    /**************************************************************************/
    
    
    /**************************************************************************/
    /**************************************************************************/
    /**                                                                       */
    /** ThreadX Component                                                     */
    /**                                                                       */
    /**   Initialize                                                          */
    /**                                                                       */
    /**************************************************************************/
    /**************************************************************************/
    
    
        .global     _tx_thread_system_stack_ptr
        .global     _tx_initialize_unused_memory
        .global     __RAM_segment_used_end__
        .global     _tx_timer_interrupt
        .global     __main
        .global     __gVectors
        .global     __tx_NMIHandler                     // NMI
        .global     __tx_BadHandler                     // HardFault
        .global     __tx_DBGHandler                     // Monitor
        .global     __tx_PendSVHandler                  // PendSV
        .global     __tx_SysTickHandler                 // SysTick
        .global     __tx_IntHandler                     // Int 0
    
    SYSTEM_CLOCK      =   600000000
    SYSTICK_CYCLES    =   ((SYSTEM_CLOCK / 1000) -1)
    
        .text 32
        .align 4
        .syntax unified
    /**************************************************************************/
    /*                                                                        */
    /*  FUNCTION                                               RELEASE        */
    /*                                                                        */
    /*    _tx_initialize_low_level                          Cortex-M7/GNU     */
    /*                                                           6.1.2        */
    /*  AUTHOR                                                                */
    /*                                                                        */
    /*    William E. Lamie, Microsoft Corporation                             */
    /*                                                                        */
    /*  DESCRIPTION                                                           */
    /*                                                                        */
    /*    This function is responsible for any low-level processor            */
    /*    initialization, including setting up interrupt vectors, setting     */
    /*    up a periodic timer interrupt source, saving the system stack       */
    /*    pointer for use in ISR processing later, and finding the first      */
    /*    available RAM memory address for tx_application_define.             */
    /*                                                                        */
    /*  INPUT                                                                 */
    /*                                                                        */
    /*    None                                                                */
    /*                                                                        */
    /*  OUTPUT                                                                */
    /*                                                                        */
    /*    None                                                                */
    /*                                                                        */
    /*  CALLS                                                                 */
    /*                                                                        */
    /*    None                                                                */
    /*                                                                        */
    /*  CALLED BY                                                             */
    /*                                                                        */
    /*    _tx_initialize_kernel_enter           ThreadX entry function        */
    /*                                                                        */
    /*  RELEASE HISTORY                                                       */
    /*                                                                        */
    /*    DATE              NAME                      DESCRIPTION             */
    /*                                                                        */
    /*  09-30-2020     William E. Lamie         Initial Version 6.1           */
    /*  11-09-2020     Scott Larson             Modified comment(s),          */
    /*                                            resulting in version 6.1.2  */
    /*                                                                        */
    /**************************************************************************/
    // VOID   _tx_initialize_low_level(VOID)
    // {
        .global  _tx_initialize_low_level
        .thumb_func
    _tx_initialize_low_level:
    
        /* Disable interrupts during ThreadX initialization.  */
        
        CPSID   i
    
        /* Set base of available memory to end of non-initialised RAM area.  */
    
        LDR     r0, =_tx_initialize_unused_memory       // Build address of unused memory pointer
        LDR     r1, =__RAM_segment_used_end__           // Build first free address
        ADD     r1, r1, #4                              // 
        STR     r1, [r0]                                // Setup first unused memory pointer
    
        /* Setup Vector Table Offset Register.  */
        
        MOV     r0, #0xE000E000                         // Build address of NVIC registers
        LDR     r1, =__gVectors                           // Pickup address of vector table
        STR     r1, [r0, #0xD08]                        // Set vector table address
    
        /* Enable the cycle count register.  */
    
    //    LDR     r0, =0xE0001000                         // Build address of DWT register
    //    LDR     r1, [r0]                                // Pickup the current value
    //    ORR     r1, r1, #1                              // Set the CYCCNTENA bit
    //    STR     r1, [r0]                                // Enable the cycle count register 
    
        /* Set system stack pointer from vector value.  */
    
        LDR     r0, =_tx_thread_system_stack_ptr        // Build address of system stack pointer
        LDR     r1, =__gVectors                           // Pickup address of vector table
        LDR     r1, [r1]                                // Pickup reset stack pointer
        STR     r1, [r0]                                // Save system stack pointer
    
        /* Configure SysTick.  */
    
        MOV     r0, #0xE000E000                         // Build address of NVIC registers
        LDR     r1, =SYSTICK_CYCLES
        STR     r1, [r0, #0x14]                         // Setup SysTick Reload Value
        MOV     r1, #0x7                                // Build SysTick Control Enable Value
        STR     r1, [r0, #0x10]                         // Setup SysTick Control
    
        /* Configure handler priorities.  */
    
        LDR     r1, =0x00000000                         // Rsrv, UsgF, BusF, MemM
        STR     r1, [r0, #0xD18]                        // Setup System Handlers 4-7 Priority Registers
    
        LDR     r1, =0xFF000000                         // SVCl, Rsrv, Rsrv, Rsrv
        STR     r1, [r0, #0xD1C]                        // Setup System Handlers 8-11 Priority Registers
                                                        // Note: SVC must be lowest priority, which is 0xFF
    
        LDR     r1, =0x40FF0000                         // SysT, PnSV, Rsrv, DbgM
        STR     r1, [r0, #0xD20]                        // Setup System Handlers 12-15 Priority Registers
                                                        // Note: PnSV must be lowest priority, which is 0xFF
    
        /* Return to caller.  */
    
        BX      lr 
    // }
    
    
    /* Define shells for each of the unused vectors.  */
    
        .global  __tx_BadHandler
        .thumb_func
    __tx_BadHandler:
        B       __tx_BadHandler
    
    /* added to catch the hardfault */
    
        .global  __tx_HardfaultHandler
        .thumb_func
    __tx_HardfaultHandler:
        B       __tx_HardfaultHandler
    
    /* Generic interrupt handler template */
        .global  __tx_IntHandler
        .thumb_func
    __tx_IntHandler:
    // VOID InterruptHandler (VOID)
    // {
        PUSH    {r0, lr}
    #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
        BL      _tx_execution_isr_enter             // Call the ISR enter function
    #endif       
    
        /* Do interrupt handler work here */
        /* BL <your C Function>.... */
    
    #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
        BL      _tx_execution_isr_exit              // Call the ISR exit function
    #endif
        POP     {r0, lr}
        BX      LR
    // }
    
    /* System Tick timer interrupt handler */
        .global  __tx_SysTickHandler
        .global  SysTick_Handler
        .thumb_func
    __tx_SysTickHandler:
        .thumb_func
    SysTick_Handler:
    // VOID TimerInterruptHandler (VOID)
    // {
    
        PUSH    {r0, lr}
    #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
        BL      _tx_execution_isr_enter             // Call the ISR enter function
    #endif
        BL      _tx_timer_interrupt
    #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
        BL      _tx_execution_isr_exit              // Call the ISR exit function
    #endif
        POP     {r0, lr}
        BX      LR
    // }
    
    
    /* NMI, DBG handlers */
        .global  __tx_NMIHandler 
        .thumb_func
    __tx_NMIHandler:
        B       __tx_NMIHandler
    
        .global  __tx_DBGHandler
        .thumb_func
    __tx_DBGHandler:
        B       __tx_DBGHandler
    
    
    #include <sys/stat.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <stdio.h>
    #include <signal.h>
    #include <time.h>
    #include <sys/time.h>
    #include <sys/times.h>
    
    /* Variables */
    // #undef errno
    extern int errno;
    extern int __io_putchar(int ch) __attribute__((weak));
    extern int __io_getchar(void) __attribute__((weak));
    
    // register unsigned char *__stack_ptr (__ASM("sp"));
    
    // register unsigned char *__stack_ptr asm("sp");
    
    char  *__env[1] = {0};
    char **environ  = __env;
    
    /* Functions */
    void initialise_monitor_handles( )
    {
    }
    
    int _getpid(void)
    {
        return 1;
    }
    
    int _kill(int pid, int sig)
    {
        errno = EINVAL;
        return -1;
    }
    
    void _exit(int status)
    {
        _kill(status, -1);
        while(1)
        {
        } /* Make sure we hang here */
    }
    
    __attribute__((weak)) int _read(int file, char *ptr, int len)
    {
        int DataIdx;
    
        for(DataIdx = 0; DataIdx < len; DataIdx++)
        {
            *ptr++ = __io_getchar( );
        }
    
        return len;
    }
    
    __attribute__((weak)) int _write(int file, char *ptr, int len)
    {
        int DataIdx;
    
        for(DataIdx = 0; DataIdx < len; DataIdx++)
        {
            __io_putchar(*ptr++);
        }
        return len;
    }
    
    int _close(int file)
    {
        return -1;
    }
    
    int _fstat(int file, struct stat *st)
    {
        st->st_mode = S_IFCHR;
        return 0;
    }
    
    int _isatty(int file)
    {
        return 1;
    }
    
    int _lseek(int file, int ptr, int dir)
    {
        return 0;
    }
    
    int _open(char *path, int flags, ...)
    {
        /* Pretend like we always fail */
        return -1;
    }
    
    int _wait(int *status)
    {
        errno = ECHILD;
        return -1;
    }
    
    int _unlink(char *name)
    {
        errno = ENOENT;
        return -1;
    }
    
    int _times(struct tms *buf)
    {
        return -1;
    }
    
    int _stat(char *file, struct stat *st)
    {
        st->st_mode = S_IFCHR;
        return 0;
    }
    
    int _link(char *old, char *new)
    {
        errno = EMLINK;
        return -1;
    }
    
    int _fork(void)
    {
        errno = EAGAIN;
        return -1;
    }
    
    int _execve(char *name, char **argv, char **env)
    {
        errno = ENOMEM;
        return -1;
    }