banner
yono

yono

哈喽~欢迎光临
follow
github

再談 threadX 移植

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 管理代碼的工程師,只要將這個代碼庫完全地下載和複製到項目中即可。
eclipse-threadx/threadx: Eclipse ThreadX is an advanced real-time operating system (RTOS) designed specifically for deeply embedded applications.

例如我的這個項目,使用 6_Rtos 文件夾存放 RTOS 相關的源碼,threadx-master 文件夾就是直接下載源碼包並且解壓得到的。當然如果讀者熟悉 git 子模塊或者 CMAKE 在線導入的方法,也是極好的,我這裡只做盡可能簡單的最小系統搭建。

image-20250526145554852

在頂層的 CMakeLists.txt 中使用這樣的語句,即可引入 threadX。我這裡的頂層 CMakeLists.txt 在上一層的 src 文件夾下,反正依據 CMAKE 項目結構,需要比解壓的源碼包更上層。

其中 THREADX_ARCH 指定了內核型號,THREADX_TOOLCHAIN 指定了編譯器型號,這兩個參數將由 threadx-master 中的 CMAKE 腳本自動選定 port 文件,詳細可以自行研究 threadX 庫的根 CMakeLists.txt 。

# threadx
set(THREADX_ARCH cortex_m7)
set(THREADX_TOOLCHAIN gnu)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # 添加子目錄

添加鏈接庫#

[!NOTE]

再次提醒,讀者必須完成過一個 CMAKE 項目的搭建和編譯,否則以下的 CMAKE 基礎腳本寫法也不會懂的

由於我們在頂層 CMakeLists.txt 引入的子目錄,所以需要在頂層項目中添加鏈接庫。使用類似如下的語句

target_link_libraries( MY_CMAKE_PROJECT_NAME
	# 省略其他的鏈接庫,例如 user_src,在最後添加
    azrtos::threadx
)

如果希望使用經典的 tx_user.h 配置文件。那么還需要在引入子目錄前使用類似如下的語句。預先設置一個 TX_USER_FILE 參數用於導向我們自己創建的 tx_user.h 配置文件,而庫內的 cmake 腳本會依據 TX_USER_FILE 參數是否被設置進行不同的操作,可以自行了解。

set(TX_USER_FILE "${CMAKE_CURRENT_LIST_DIR}/6_Rtos/UserCfg/tx_user.h")
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # 添加子目錄

一個相對完整的頂層 CMakeLists.txt 如下

# 指定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")

一些芯片级的支持#

中断部分#

首先 threadX 會接管 SysTick_HandlerPendSV_Handler 兩個中斷,所以需要將原工程中的這兩個中斷函數的定義給註釋掉。

其次這些中斷的實現,threadx 不會直接地在庫中引入,而是需要自己實現。

推薦是創建一個 tx_initialize_low_level.S 文件在自己工程的其他文件夾,然後在 threadX 軟件包中找一個合適的同名文件 (不同架構都有示例),將這個文件複製出來到自己創建的裡面。不要直接引入他的示例,會破壞源碼包的獨立性。

然後依據編譯報錯修改自己的文件,主要是鏈接符號的匹配問題,例如中斷向量表的名字。

以下是一個簡單的示例。其實講道理是所有他給出的中斷函數都應該進行替換屏蔽,但是要修改自己的中斷函數名沒必要,只屏蔽必要的兩個中斷函數由其接管就好,其他函數也沒什麼很大的意義。

/**************************************************************************/
/*                                                                        */
/*       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

注意修改其中的 SYSTEM_CLOCKSYSTICK_CYCLES 兩個參數,與主頻和期望的任務時間分辨力匹配。例如我這個文件中,主頻是 600M,tx_thread_sleep (1) 期望是 1ms。

GNU 部分#

gcc 編譯鏈與 AC 編譯器的 MicroLib 不同,需要自己實現許多系統級接口。一個經典的 io 函數是 printf ,如果不實現接口,編譯鏈是無法通過的。在項目中增加以下兩個文件就好,當然我給出的源碼並沒有 printf 的接口支持。

#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;
}

#include <errno.h>
#include <stdint.h>
#include <stddef.h>

/**
 * Pointer to the current high watermark of the heap usage
 */
static uint8_t *__sbrk_heap_end = NULL;

/**
 * @brief _sbrk() allocates memory to the newlib heap and is used by malloc
 *        and others from the C library
 *
 * @verbatim
 * ############################################################################
 * #  .data  #  .bss  #       newlib heap       #          MSP stack          #
 * #         #        #                         # Reserved by _Min_Stack_Size #
 * ############################################################################
 * ^-- RAM start      ^-- _end                             _estack, RAM end --^
 * @endverbatim
 *
 * This implementation starts allocating at the '_end' linker symbol
 * The '_Min_Stack_Size' linker symbol reserves a memory for the MSP stack
 * The implementation considers '_estack' linker symbol to be RAM end
 * NOTE: If the MSP stack, at any point during execution, grows larger than the
 * reserved size, please increase the '_Min_Stack_Size'.
 *
 * @param incr Memory size
 * @return Pointer to allocated memory
 */
void *_sbrk(ptrdiff_t incr)
{
    extern uint8_t  _end;            /* Symbol defined in the linker script */
    extern uint8_t  _estack;         /* Symbol defined in the linker script */
    extern uint32_t _Min_Stack_Size; /* Symbol defined in the linker script */
    const uint32_t  stack_limit = (uint32_t)&_estack - (uint32_t)&_Min_Stack_Size;
    const uint8_t  *max_heap    = (uint8_t *)stack_limit;
    uint8_t        *prev_heap_end;

    /* Initialize heap end at first call */
    if(NULL == __sbrk_heap_end)
    {
        __sbrk_heap_end = &_end;
    }

    /* Protect heap from growing into the reserved MSP stack */
    if(__sbrk_heap_end + incr > max_heap)
    {
        errno = ENOMEM;
        return (void *)-1;
    }

    prev_heap_end = __sbrk_heap_end;
    __sbrk_heap_end += incr;

    return (void *)prev_heap_end;
}

此文由 Mix Space 同步更新至 xLog
原始鏈接為 https://www.yono233.cn/posts/novel/25_5_23_threadX


載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。