ThreadX 再紹介#
以前の内容は最全認証 RTOS——azure_threadX 移植教程 - 土星環の基地で見ることができます。
threadX が eclipse 財団に寄付されたため、現在は azure_threadX ではなく eclipse_threadX と呼ばれています。橋本有菜が新有菜に改名し、有栖露露が露露茶に改名したように、ただブランド名が変わっただけで、内容に大きな違いはありません
最近、私の新しい CMAKE プロジェクト構造が徐々に整ってきたので、比較的現代的な移植方法を紹介する時が来ました。以前使用していた IDE(特に KEIL を批判します)はあまりにも古く、通常はすべてのソースコードファイルと includPath を手動で指定する必要がありましたが、CMAKE はより自由で自動化された方法を提供できます。
以前のブログで述べたように、「ほとんどの良い C 言語ライブラリは、すべてのソースコードファイルを収集した CMakeLists.txt を提供します。」優れたライブラリは、コンパイル条件やコンパイル条件に基づいてソースファイルを選択する CMakeLists.txt を提供します。したがって、CMAKE プロジェクトに threadX ソフトウェアパッケージを導入するのは非常に便利です。
前提条件#
まず、読者は基本的な CMAKE の認識と簡単な実践経験を持っている必要があります。移植の前に、基本的な LED 点灯ができるプロジェクトを構築し、成功裏に書き込む必要があります。LED の点灯頻度が概ね予想通りであることを確認してください。
- CMAKE のサブディレクトリとプロジェクトの概念について基本的な理解があること
- CMAKE プロジェクトの構築とコンパイルを完了したこと
この分野の知識がない場合は、まずCMAKE 扫盲 - 土星環の基地を学んでください。
大まかな考え方#
threadX ソフトウェアパッケージは、ほぼ最も包括的なサポートを提供しています。私たちは、全コードベースをプロジェクトに取り込み、少しのパラメータを指定し、その後それをサブディレクトリとして扱い、自分でリンクライブラリを追加するだけで移植が完了します。
コードベースをプロジェクトに取り込む#
初心者や git でコードを管理したくないエンジニアは、このコードベースを完全にダウンロードしてプロジェクトにコピーするだけで済みます。
eclipse-threadx/threadx: Eclipse ThreadX は、深く埋め込まれたアプリケーション専用に設計された高度なリアルタイムオペレーティングシステム(RTOS)です。
例えば、私のこのプロジェクトでは、6_Rtos
フォルダに RTOS 関連のソースコードを格納し、threadx-master
フォルダは直接ダウンロードしたソースパッケージを解凍したものです。もちろん、読者が git サブモジュールや CMAKE のオンラインインポート方法に精通している場合も素晴らしいですが、私はできるだけシンプルな最小システムの構築を行います。
トップレベルのCMakeLists.txt
でこのような文を使用することで、threadX を導入できます。ここでのトップレベルCMakeLists.txt
は、上のsrc
フォルダの下にあります。CMAKE プロジェクト構造に基づいて、解凍したソースパッケージよりも上の階層にする必要があります。
ここでのTHREADX_ARCH
はコアモデルを指定し、THREADX_TOOLCHAIN
はコンパイラモデルを指定します。この 2 つのパラメータは、threadx-master
内の CMAKE スクリプトによって自動的にポートファイルが選定されます。詳細は 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環境変数が設定されていません。デフォルトのプロジェクト名を使用します。")
set(CMAKE_PROJECT_NAME "DefaultProjectName")
endif()
# コンパイルコマンド生成を有効にして、他のツールがインデックスを作成できるようにする
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) # compile_commands.jsonを生成し、IDEやツールが使用できるようにする
# コアプロジェクト設定
project(${CMAKE_PROJECT_NAME}) # プロジェクトを定義し、以前に設定したプロジェクト名を使用
message("ビルドタイプ: " ${CMAKE_BUILD_TYPE}) # ビルドタイプを出力するメッセージ
# CMakeによるASMおよびC言語のサポートを有効にする
enable_language(C ASM) # Cおよびアセンブリ(ASM)言語のサポートを有効にする
# 2つの実行可能オブジェクトを作成
# 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_Handler
とPendSV_Handler
の 2 つの割り込みを引き継ぐため、元のプロジェクト内のこれら 2 つの割り込み関数の定義をコメントアウトする必要があります。
次に、これらの割り込みの実装は、threadx はライブラリ内に直接導入しませんが、自分で実装する必要があります。
推奨されるのは、tx_initialize_low_level.S
ファイルを自分のプロジェクトの他のフォルダに作成し、threadX ソフトウェアパッケージ内で適切な同名ファイル(異なるアーキテクチャにサンプルがあります)を見つけて、このファイルを自分が作成したものにコピーすることです。彼のサンプルを直接導入すると、ソースパッケージの独立性が損なわれます。
その後、コンパイルエラーに基づいて自分のファイルを修正します。主にリンクシンボルの一致の問題です。例えば、割り込みベクターテーブルの名前などです。
以下は簡単な例です。実際には、彼が提供するすべての割り込み関数を置き換えてマスクする必要がありますが、自分の割り込み関数名を変更する必要はなく、必要な 2 つの割り込み関数だけを彼に引き継がせれば良いです。他の関数はそれほど重要ではありません。
/**************************************************************************/
/* */
/* 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_CLOCK、SYSTICK_CYCLESの 2 つのパラメータを修正し、主周波数と期待されるタスク時間分解能に一致させてください。例えば、私のこのファイルでは、主周波数は 600M で、tx_thread_sleep (1) は 1ms を期待しています。
GNU 部分#
gcc コンパイラチェーンは AC コンパイラの MicroLib とは異なり、多くのシステムレベルのインターフェースを自分で実装する必要があります。古典的な io 関数は printf であり、インターフェースを実装しないと、コンパイラチェーンは通過できません。プロジェクトに以下の 2 つのファイルを追加するだけで済みます。もちろん、私が提供するソースコードには 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