banner
yono

yono

哈喽~欢迎光临
follow
github

Revisiting the ThreadX Porting

Introduction to ThreadX#

Some previous content can be found at The Most Comprehensive Certification RTOS——azure_threadX Porting Tutorial - Saturn Ring's Base.

Since ThreadX was donated to the Eclipse Foundation, it is no longer called azure_threadX but has been renamed to eclipse_threadX. But just like Hashimoto Arina changed her name to Shin Arina, and Arisuru Lulu changed her name to Lulu Tea, it's just a change of brand, and the substance remains the same.

Recently, my new CMAKE project structure has gradually improved, and it is time to introduce a relatively modern porting method. The IDE used in the past (specifically criticizing KEIL) was too outdated, usually requiring manual specification of all source files and include paths, while CMAKE can provide a more flexible and automated approach.

In previous blog posts, I mentioned that "most good C language libraries will provide a CMakeLists.txt that collects all source files," and excellent libraries will provide a CMakeLists.txt that includes compilation conditions and selects source files based on those conditions. Therefore, introducing the ThreadX software package into a CMAKE project will be very convenient.

Prerequisites#

First, readers should have a basic understanding of CMAKE and some practical experience. Before porting, first build a project that can blink an LED successfully, ensuring that the blinking frequency is roughly as expected.

  • A basic understanding of CMAKE subdirectories and the project concept.
  • Completion of a CMAKE project setup and compilation.

Those without this knowledge can first learn from CMAKE Literacy - Saturn Ring's Base.

General Approach#

The ThreadX software package already provides almost the most comprehensive support. We just need to bring the entire codebase into the project, provide a few parameters, then use it as a subdirectory, and add the link libraries ourselves to complete the porting.

Bringing the Codebase into the Project#

For beginners or engineers who do not want to use git to manage code, simply download and copy the entire codebase into the project.
eclipse-threadx/threadx: Eclipse ThreadX is an advanced real-time operating system (RTOS) designed specifically for deeply embedded applications.

For example, in my project, the 6_Rtos folder stores the RTOS-related source code, and the threadx-master folder is directly downloaded and extracted from the source package. Of course, if readers are familiar with git submodules or CMAKE online import methods, that would be great; I am just trying to keep the system setup as simple as possible.

image-20250526145554852

In the top-level CMakeLists.txt, use the following statement to introduce ThreadX. My top-level CMakeLists.txt is located in the src folder above, which is necessary according to the CMAKE project structure, as it needs to be at a higher level than the extracted source package.

Here, THREADX_ARCH specifies the kernel model, and THREADX_TOOLCHAIN specifies the compiler model. These two parameters will be automatically selected by the CMAKE script in threadx-master to choose the port file; details can be studied in the root CMakeLists.txt of the ThreadX library.

# threadx
set(THREADX_ARCH cortex_m7)
set(THREADX_TOOLCHAIN gnu)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # Add subdirectory

[!NOTE]

Once again, readers must have completed the setup and compilation of a CMAKE project, otherwise, they will not understand the following CMAKE basic script writing.

Since we introduced a subdirectory in the top-level CMakeLists.txt, we need to add link libraries in the top-level project. Use a statement similar to the following:

target_link_libraries( MY_CMAKE_PROJECT_NAME
	# Other link libraries omitted, such as user_src, added at the end
    azrtos::threadx
)

If you want to use the classic tx_user.h configuration file, you will also need to use a statement similar to the following before introducing the subdirectory. Pre-set a TX_USER_FILE parameter to point to your own created tx_user.h configuration file, and the CMake script within the library will perform different operations based on whether the TX_USER_FILE parameter is set; you can learn more about this.

set(TX_USER_FILE "${CMAKE_CURRENT_LIST_DIR}/6_Rtos/UserCfg/tx_user.h")
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/6_Rtos/threadx-master)     # Add subdirectory

A relatively complete top-level CMakeLists.txt is as follows:

# Specify the minimum required version of CMake as 3.22
cmake_minimum_required(VERSION 3.22)

#
# This file is the main build file called by cmake
# Users can freely modify this file as needed.
#

# Set compiler settings
set(CMAKE_C_STANDARD 11)            # Set C standard to C11
set(CMAKE_C_STANDARD_REQUIRED ON)   # Require the specified C standard
set(CMAKE_C_EXTENSIONS ON)          # Enable compiler extensions


# set(CMAKE_BUILD_TYPE "Release")
# Define build type
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")   # If CMAKE_BUILD_TYPE is not set, default to "Debug". This parameter can be added when generating the native build system using something like "cmake ../" with -DCMAKE_BUILD_TYPE=Release specified.
endif()

# Include toolchain file
include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/gcc-arm-none-eabi.cmake")

# Set project name
# set(CMAKE_PROJECT_NAME H7_GCC_BASE)  # Set project name
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()


# Enable compile command generation for indexing by other tools like clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) # Generate compile_commands.json for IDE or tools

# Core project settings
project(${CMAKE_PROJECT_NAME})                  # Define project using previously set project name
message("Build type: " ${CMAKE_BUILD_TYPE})     # Message output for build type

# Enable CMake support for ASM and C languages
enable_language(C ASM)              # Enable support for C and assembly (ASM) languages

# Create two executable targets
# add_executable(${CMAKE_PROJECT_NAME})    # Without BL part
add_executable(${CMAKE_PROJECT_NAME}_BL) # With BL part

foreach(target IN ITEMS 
# ${CMAKE_PROJECT_NAME} 
${CMAKE_PROJECT_NAME}_BL)
    # Link directory settings
    target_link_directories(${target} PRIVATE
        # Add user-defined library search paths
        # e.g., "/path/to/libs"
    )

    # Add source files to executable target
    target_sources(${target} PRIVATE
        # Add additional source files
        # e.g., "src/main.c"
    )

    # Add include paths
    target_include_directories(${target} PRIVATE
        # Add user-defined include paths
        # e.g., "include"
    )

    # Add project symbols (macros)
    target_compile_definitions(${target} PRIVATE
        # Add user-defined symbols
        # e.g., "MY_MACRO=1"
    )

    # Add link libraries
    target_link_libraries(${target}
        user_src # Link user_src library, which also exists as a project()
        Dataflow
        azrtos::threadx
        # modbusx
        # Add user-defined libraries
        # 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"
)

# Add subdirectory part, which will automatically handle CMakeLists.txt files in subdirectories
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake)     # Add subdirectory
# Dataflow GNU
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/7_Exlib/Dataflow-main/common)     # Add subdirectory
# 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)     # Add subdirectory
# modbusx GNU
# add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/7_Exlib/modbusX/common)     # Add subdirectory



# Add compile flags for individual files
include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake/extra-compile-flags.cmake")

# Run post-build tasks
include("${CMAKE_CURRENT_LIST_DIR}/8_WorkSpace/CMake/toolCmake/post-build-tasks.cmake")

Some Chip-Level Support#

Interrupts#

First, ThreadX will take over the SysTick_Handler and PendSV_Handler interrupts, so you need to comment out the definitions of these two interrupt functions in the original project.

Secondly, the implementation of these interrupts will not be directly included in the library by ThreadX; you need to implement them yourself.

It is recommended to create a tx_initialize_low_level.S file in another folder of your project, then find a suitable file with the same name in the ThreadX package (examples are available for different architectures) and copy that file into your created folder. Do not directly include their example, as it will compromise the independence of the source package.

Then modify your file according to the compilation errors, mainly focusing on matching the link symbols, such as the names of the interrupt vector table.

Here is a simple example. Technically, all the interrupt functions they provide should be replaced and masked, but it is unnecessary to change the names of your interrupt functions; just mask the two necessary interrupt functions for them to take over, as the others are not very significant.

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

Note to modify the SYSTEM_CLOCK and SYSTICK_CYCLES parameters to match the main frequency and expected task timing resolution. For example, in this file, the main frequency is 600M, and tx_thread_sleep(1) expects 1ms.

GNU Part#

The gcc compilation chain differs from the AC compiler's MicroLib, requiring the implementation of many system-level interfaces. A classic I/O function is printf; if the interface is not implemented, the compilation chain will not pass. Just add the following two files to the project; of course, the source code I provided does not support the printf interface.

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

This article is synchronized and updated to xLog by Mix Space. The original link is https://www.yono233.cn/posts/novel/25_5_23_threadX

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.