[APM32F4] 【APM32F402R Micro-EVB开发板测评】在极海APM32F4上部署微软ThreadX系统的实践记录

[复制链接]
 楼主| hbzjt2011 发表于 2025-7-23 15:57 | 显示全部楼层 |阅读模式
本帖最后由 hbzjt2011 于 2025-7-23 16:00 编辑

#申请原创# #技术资源#@21小跑堂
前言
在使用APM32F402R Micro-EVB开发板时,发现官方提供了基于FreeRTOS的工程示例。既然APM32F4这颗芯片性能不错,资源如此丰富,为什么不试试跑一下微软的ThreadX呢?本文记录了在APM32F4上运行ThreadX RTOS的完整过程,仅供参考。

1.关于ThreadX这个系统
说到ThreadX,可能很多人没听过,但在某些领域它可是大名鼎鼎。这个系统最初是Express Logic公司开发的,后来被微软收购并开源了。
为什么说它厉害?主要是因为它的资质够硬:
  • 拿到了医疗器械的FDA认证
  • 通过了汽车功能安全的最高等级ASIL D
  • 满足航空级DO-178B标准
  • 符合核电站IEC-61508安全要求
简单来说,就是那些对可靠性要求极高的场合都在用它。虽然我们平时开发可能用不到这么高的标准,但能在自己的板子上跑一下这样的系统,还是挺有意思的。
相关链接:
https://github.com/eclipse-threadx/rtos-docs
2.准备工作
硬件环境
APM32F402R Micro-EVB开发板,最高工作频率120Mhz,128KB Flash,32KB SRAM,资源还算充裕。
软件准备
开发环境用的是Keil MDK,版本要求5.30以上。需要下载两个东西:
环境搭建思路
我的想法是以SDK里面的SysTick例程为基础,把ThreadX的代码集成进去。这样既能保留原有的硬件抽象层,又能快速验证ThreadX的功能。
动手实现代码结构整理
首先把ThreadX的源码复制到SDK的中间件目录下,然后新建一个工程目录,把需要的文件都整理好。
主要涉及三个部分的代码:
  • ThreadX的内核代码(通用部分)
  • Cortex-M4架构相关的移植代码
  • 我们自己写的应用代码
ThreadX对Cortex-M4有专门的适配,需要在编译器里设置好头文件路径和一些特殊的编译选项。特别是汇编器,需要加上预处理器支持,不然编译会报错。

3. 具体步骤:
1. 首先将ThreadX 源码拷贝到 ..\APM32F402_403_SDK_V1.0.1\Middlewares 文件夹下
257316880781236c3a.png
ThreadX源码目录结构:
  1. .
  2. ├── cmake                        # CMakelist files for building the project
  3. ├── common                       # Core ThreadX files
  4. ├── common_modules               # Core ThreadX module files
  5. ├── common_smp                   # Core ThreadX SMP files
  6. ├── docs                         # Documentation supplements
  7. ├── ports                        # Architecture and compiler specific files. See below for directory breakdown     
  8. │   ├── cortex_m7     
  9. │   │   ├── iar                  # Example IAR compiler sample project
  10. │   │   │   ├── example build    # IAR workspace and sample project files
  11. │   │   │   ├── inc              # tx_port.h for this architecture
  12. │   │   │   └── src              # Source files for this architecture
  13. │   │   ├── ac6                  # Example ac6/Keil sample project
  14. │   │   ├── gnu                  # Example gnu sample project
  15. │   │   └── ...
  16. │   └── ...        
  17. ├── ports_modules                # Architecture and compiler specific files for threadX modules
  18. ├── ports_smp                    # Architecture and compiler specific files for threadX SMP
  19. ├── samples                      # demo_threadx.c
  20. └── utility                      # Test cases and utilities
2. 拷贝官方SDK示例文件夹中的SysTick副本,并命名为ThreadX作为工程文件夹
72835688078de3ea51.png
3. 拷贝ThreadX源码目录中APM32F402_403_SDK_V1.0.1\Middlewares\threadx-6.4.2_rel\ports\cortex_m4\ac6\example_build\sample_threadx文件夹下的tx_initialize_low_level.S 文件,到我们的工程目录源文件夹下APM32F402_403_SDK_V1.0.1\Examples\Board_APM32F402_Tiny\TheadX\ThreadX_APM32\Source:
5019468807a9aec937.png
4. 在工程目录中创建ThreadX/ports文件夹,并添加APM32F402_403_SDK_V1.0.1\Middlewares\threadx-6.4.2_rel\ports\cortex_m4\ac6\src目录下的所有.S文件以及工程源码目录下的tx_initialize_low_level.S文件到该文件夹下:
8178268807c44475e2.png
5. 在工程目录中创建ThreadX/common文件夹,并将ThreadX源码目录下的common文件夹下src文件全部添加到该文件夹中,此文件夹为ThreadX系统实现的核心源文件:
5961568807dae220a6.png
6. 在工程目录中的Application文件下夹创建tx_application_ entry.c文件,作为后续主程序入口:
1427968807e686c8df.png
7. 更改默认编译器为AC6
9796068807ecbadfef.png
8. 添加如下头文件目录:
  1. 1. "... \... \... \... \... \... \Middlewares\threadx-6.4.2_rel\ports\cortex_m4\ac6\inc"
  2. 2. "... \... \... \... \... \Middlewares\threadx-6.4.2_rel\common\inc"

840268807f4f934b0.png
9. 在Options->Asm下添加如下头文件目录。
821766880930556bac.png
10. 修改tx_initialize_low_level.s中的时钟、堆栈以及中断向量表设置:
  1. @/***************************************************************************
  2. [url=home.php?mod=space&uid=72445]@[/url] * Copyright (c) 2024 Microsoft Corporation
  3. [url=home.php?mod=space&uid=72445]@[/url] *
  4. @ * This program and the accompanying materials are made available under the
  5. @ * terms of the MIT License which is available at
  6. @ * https://opensource.org/licenses/MIT.
  7. @ *
  8. @ * SPDX-License-Identifier: MIT
  9. @ **************************************************************************/
  10. @
  11. @
  12. @/**************************************************************************/
  13. @/**************************************************************************/
  14. @/**                                                                       */
  15. @/** ThreadX Component                                                     */
  16. @/**                                                                       */
  17. @/**   Initialize                                                          */
  18. @/**                                                                       */
  19. @/**************************************************************************/
  20. @/**************************************************************************/
  21. @
  22. @
  23.     .global     _tx_thread_system_stack_ptr
  24.     .global     _tx_initialize_unused_memory
  25.     .global     _tx_timer_interrupt
  26.     .global     __main
  27.     .global     __tx_SVCallHandler
  28.     .global     __tx_PendSVHandler
  29.     .global     __tx_NMIHandler                     @ NMI
  30.     .global     __tx_BadHandler                     @ HardFault
  31.     .global     __tx_SVCallHandler                  @ SVCall
  32.     .global     __tx_DBGHandler                     @ Monitor
  33.     .global     __tx_PendSVHandler                  @ PendSV
  34.     .global     __tx_SysTickHandler                 @ SysTick
  35.     .global     __tx_IntHandler                     @ Int 0
  36.         
  37.         .extern __initial_sp
  38.         .extern __Vectors
  39.         .global vector_table
  40.         vector_table = __Vectors

  41. @
  42. @
  43. SYSTEM_CLOCK      =   16000000
  44. SYSTICK_CYCLES    =   ((SYSTEM_CLOCK / 100) -1)

  45.     .text 32
  46.     .align 4
  47.     .syntax unified
  48. @/**************************************************************************/
  49. @/*                                                                        */
  50. @/*  FUNCTION                                               RELEASE        */
  51. @/*                                                                        */
  52. @/*    _tx_initialize_low_level                          Cortex-M4/AC6     */
  53. @/*                                                           6.1          */
  54. @/*  AUTHOR                                                                */
  55. @/*                                                                        */
  56. @/*    William E. Lamie, Microsoft Corporation                             */
  57. @/*                                                                        */
  58. @/*  DESCRIPTION                                                           */
  59. @/*                                                                        */
  60. @/*    This function is responsible for any low-level processor            */
  61. @/*    initialization, including setting up interrupt vectors, setting     */
  62. @/*    up a periodic timer interrupt source, saving the system stack       */
  63. @/*    pointer for use in ISR processing later, and finding the first      */
  64. @/*    available RAM memory address for tx_application_define.             */
  65. @/*                                                                        */
  66. @/*  INPUT                                                                 */
  67. @/*                                                                        */
  68. @/*    None                                                                */
  69. @/*                                                                        */
  70. @/*  OUTPUT                                                                */
  71. @/*                                                                        */
  72. @/*    None                                                                */
  73. @/*                                                                        */
  74. @/*  CALLS                                                                 */
  75. @/*                                                                        */
  76. @/*    None                                                                */
  77. @/*                                                                        */
  78. @/*  CALLED BY                                                             */
  79. @/*                                                                        */
  80. @/*    _tx_initialize_kernel_enter           ThreadX entry function        */
  81. @/*                                                                        */
  82. @/*  RELEASE HISTORY                                                       */
  83. @/*                                                                        */
  84. @/*    DATE              NAME                      DESCRIPTION             */
  85. @/*                                                                        */
  86. @/*  09-30-2020     William E. Lamie         Initial Version 6.1           */
  87. @/*                                                                        */
  88. @/**************************************************************************/
  89. [url=home.php?mod=space&uid=34493]@void[/url]   _tx_initialize_low_level(VOID)
  90. @{
  91.     .global  _tx_initialize_low_level
  92.     .thumb_func
  93. _tx_initialize_low_level:
  94. @
  95. @    /* Disable interrupts during ThreadX initialization.  */
  96. @
  97.     CPSID   i
  98. @
  99. @    /* Set base of available memory to end of non-initialised RAM area.  */
  100. @
  101.     LDR     r0, =_tx_initialize_unused_memory       @ Build address of unused memory pointer
  102.         LDR         r1, =__initial_sp                                                @ Build first free address
  103.     ADD     r1, r1, #4                              @
  104.     STR     r1, [r0]                                @ Setup first unused memory pointer
  105. @
  106. @    /* Setup Vector Table Offset Register.  */
  107. @
  108.     MOV     r0, #0xE000E000                         @ Build address of NVIC registers
  109.     LDR     r1, =vector_table                       @ Pickup address of vector table
  110.     STR     r1, [r0, #0xD08]                        @ Set vector table address
  111. @
  112. @    /* Set system stack pointer from vector value.  */
  113. @
  114.     LDR     r0, =_tx_thread_system_stack_ptr        @ Build address of system stack pointer
  115.     LDR     r1, =vector_table                       @ Pickup address of vector table
  116.     LDR     r1, [r1]                                @ Pickup reset stack pointer
  117.     STR     r1, [r0]                                @ Save system stack pointer
  118. @
  119. @    /* Enable the cycle count register.  */
  120. @
  121.     LDR     r0, =0xE0001000                         @ Build address of DWT register
  122.     LDR     r1, [r0]                                @ Pickup the current value
  123.     ORR     r1, r1, #1                              @ Set the CYCCNTENA bit
  124.     STR     r1, [r0]                                @ Enable the cycle count register
  125. @
  126. @    /* Configure SysTick for 100Hz clock, or 16384 cycles if no reference.  */
  127. @
  128.     MOV     r0, #0xE000E000                         @ Build address of NVIC registers
  129.     LDR     r1, =SYSTICK_CYCLES
  130.     STR     r1, [r0, #0x14]                         @ Setup SysTick Reload Value
  131.     MOV     r1, #0x7                                @ Build SysTick Control Enable Value
  132.     STR     r1, [r0, #0x10]                         @ Setup SysTick Control
  133. @
  134. @    /* Configure handler priorities.  */
  135. @
  136.     LDR     r1, =0x00000000                         @ Rsrv, UsgF, BusF, MemM
  137.     STR     r1, [r0, #0xD18]                        @ Setup System Handlers 4-7 Priority Registers

  138.     LDR     r1, =0xFF000000                         @ SVCl, Rsrv, Rsrv, Rsrv
  139.     STR     r1, [r0, #0xD1C]                        @ Setup System Handlers 8-11 Priority Registers
  140.                                                     @ Note: SVC must be lowest priority, which is 0xFF

  141.     LDR     r1, =0x40FF0000                         @ SysT, PnSV, Rsrv, DbgM
  142.     STR     r1, [r0, #0xD20]                        @ Setup System Handlers 12-15 Priority Registers
  143.                                                     @ Note: PnSV must be lowest priority, which is 0xFF

  144. @
  145. @    /* Return to caller.  */
  146. @
  147.     BX      lr
  148. @}
  149. @

  150. @/* Define shells for each of the unused vectors.  */
  151. @
  152.     .global  __tx_BadHandler
  153.     .thumb_func
  154. __tx_BadHandler:
  155.     B       __tx_BadHandler

  156. @ /* added to catch the hardfault */

  157.     .global  __tx_HardfaultHandler
  158.     .thumb_func
  159. __tx_HardfaultHandler:
  160.     B       __tx_HardfaultHandler


  161. @ /* added to catch the SVC */

  162.     .global  __tx_SVCallHandler
  163.     .thumb_func
  164. __tx_SVCallHandler:
  165.     B       __tx_SVCallHandler


  166. @ /* Generic interrupt handler template */
  167.     .global  __tx_IntHandler
  168.     .thumb_func
  169. __tx_IntHandler:
  170. @ VOID InterruptHandler (VOID)
  171. @ {
  172.     PUSH    {r0, lr}
  173. #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
  174.     BL      _tx_execution_isr_enter             @ Call the ISR enter function
  175. #endif

  176. @    /* Do interrupt handler work here */
  177. @    /* BL <your C Function>.... */

  178. #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
  179.     BL      _tx_execution_isr_exit              @ Call the ISR exit function
  180. #endif
  181.     POP     {r0, lr}
  182.     BX      LR
  183. @ }

  184. @ /* System Tick timer interrupt handler */
  185.     .global  __tx_SysTickHandler
  186.     .global  SysTick_Handler
  187.     .thumb_func
  188. __tx_SysTickHandler:
  189.     .thumb_func
  190. SysTick_Handler:
  191. @ VOID TimerInterruptHandler (VOID)
  192. @ {
  193. @
  194.     PUSH    {r0, lr}
  195. #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
  196.     BL      _tx_execution_isr_enter             @ Call the ISR enter function
  197. #endif
  198.     BL      _tx_timer_interrupt
  199. #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
  200.     BL      _tx_execution_isr_exit              @ Call the ISR exit function
  201. #endif
  202.     POP     {r0, lr}
  203.     BX      LR
  204. @ }


  205. @ /* NMI, DBG handlers */
  206.     .global  __tx_NMIHandler
  207.     .thumb_func
  208. __tx_NMIHandler:
  209.     B       __tx_NMIHandler

  210.     .global  __tx_DBGHandler
  211.     .thumb_func
  212. __tx_DBGHandler:
  213.     B       __tx_DBGHandler
11. 编写tx_application_ entry.c源程序,创建两个线程,并设置优先级:
为了验证系统工作正常,我写了两个简单的任务:
  • 第一个任务控制LED2,每0.5秒闪烁一次
  • 第二个任务控制LED3,每1秒闪烁一次
  • 两个任务都会通过串口输出运行状态
代码实现上,ThreadX的API还是比较直观的:

  1. /* Includes ***************************************************************/
  2. #include "main.h"
  3. #include "board_apm32f402_403_tiny.h"
  4. #include <stdio.h>
  5. #include "tx_api.h"

  6. #define TX_APPLICATION1_PRIO 2
  7. #define TX_APPLICATION1_STACK_SIZE 1024

  8. static TX_THREAD tx_application1;

  9. uint8_t tx_application1_stack[TX_APPLICATION1_STACK_SIZE];

  10. #define TX_APPLICATION2_PRIO 3

  11. #define TX_APPLICATION2_STACK_SIZE 1024

  12. static TX_THREAD tx_application2;

  13. uint8_t tx_application2_stack[TX_APPLICATION2_STACK_SIZE];

  14. void my_tx_application1_entry(ULONG thread_input)
  15. {

  16.         /* Enter into a forever loop. */

  17.         while(1)
  18.         {

  19.                 printf("ThreadX 1 application running...\r\n");

  20.                 BOARD_LED_Toggle(LED2);

  21.                 /* Sleep for 1500 tick. */

  22.                 tx_thread_sleep(500);

  23.                 }

  24.         }

  25. void my_tx_application2_entry(ULONG thread_input)
  26. {

  27.         /* Enter into a forever loop. */
  28.         while(1)
  29.         {

  30.                 printf("ThreadX 2 application running...\r\n");

  31.                 BOARD_LED_Toggle(LED3);

  32.                 /* Sleep for 1000 tick. */

  33.                 tx_thread_sleep(1000);
  34.         }

  35. }

  36. void tx_application_define(void *first_unused_memory)
  37. {

  38.         /* Create thread */

  39.         tx_thread_create(&tx_application1, "thread 1", my_tx_application1_entry, 0, &tx_application1_stack[0], TX_APPLICATION1_STACK_SIZE, TX_APPLICATION1_PRIO, TX_APPLICATION1_PRIO, TX_NO_TIME_SLICE, TX_AUTO_START);

  40.         tx_thread_create(&tx_application2, "thread 2", my_tx_application2_entry, 0, &tx_application2_stack[0], TX_APPLICATION2_STACK_SIZE, TX_APPLICATION2_PRIO, TX_APPLICATION2_PRIO, TX_NO_TIME_SLICE, TX_AUTO_START);

  41. }

  42. void _tx_thread_stack_error_handler(void)
  43. {
  44.     while(1);  
  45. }
12. 编写主程序,重定向串口打印:
  1. /* Includes ***************************************************************/
  2. #include "main.h"
  3. #include "board_apm32f402_403_tiny.h"
  4. #include <stdio.h>
  5. #include "tx_api.h"

  6. /* Private includes *******************************************************/

  7. /* Private macro **********************************************************/
  8. /** printf using USART1 */
  9. #define DEBUG_USART USART1

  10. /* Private typedef ********************************************************/

  11. /* Private variables ******************************************************/

  12. /* Private function prototypes ********************************************/
  13. void Delay(uint32_t cnt);

  14. /* External variables *****************************************************/
  15. volatile uint32_t delayCnt = 0;

  16. /* External functions *****************************************************/

  17. /**
  18. * [url=home.php?mod=space&uid=247401]@brief[/url]   Main program
  19. *
  20. * @param   None
  21. *
  22. * @retval  None
  23. */
  24. int main(void)
  25. {
  26.                 USART_Config_T usartConfigStruct;

  27.                 usartConfigStruct.baudRate = 115200;
  28.                 usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
  29.                 usartConfigStruct.mode = USART_MODE_TX;
  30.                 usartConfigStruct.parity = USART_PARITY_NONE;
  31.                 usartConfigStruct.stopBits = USART_STOP_BIT_1;
  32.                 usartConfigStruct.wordLength = USART_WORD_LEN_8B;

  33.                 BOARD_COM_Config(COM1, &usartConfigStruct);

  34.                 BOARD_LED_Config(LED2);
  35.                 BOARD_LED_Config(LED3);

  36.                 printf("ThreadX RTOS on APM32F402R Micro-EVB Board\r\n");

  37.                 tx_kernel_enter();

  38.     /* Infinite loop */
  39.     while (1)
  40.     {
  41.       
  42.     }
  43. }

  44. void Delay(uint32_t cnt)
  45. {
  46.     delayCnt = cnt;
  47.     while(delayCnt);
  48. }

  49. /*!

  50. * Redirect C Library function printf to serial port.

  51. * After Redirection, you can use printf function.

  52. *

  53. * @param ch: The characters that need to be send.

  54. *

  55. * @param *f: pointer to a FILE that can recording all information

  56. * needed to control a stream

  57. *

  58. * @retval The characters that need to be send.

  59. */

  60. int fputc(int ch, FILE *f)

  61. {

  62. /** send a byte of data to the serial port */

  63.         USART_TxData(DEBUG_USART, (uint8_t)ch);

  64. /** wait for the data to be send */

  65. while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);

  66. return (ch);

  67. }


13. 编译下载程序:
75479688094c11574e.png

14. 查看串口输出及LED灯的变化:
99226688094eec3f50.png

注意事项:
中断冲突处理
ThreadX需要接管几个系统级中断,特别是PendSV和SysTick。需要在APM32的中断处理文件里把这两个函数注释掉,避免重复定义。
一些感受技术层面
ThreadX的代码质量确实不错,注释详细,架构清晰。虽然移植过程中遇到了一些细节问题,但总体来说还是比较顺利的。
系统的实时性表现也很好,任务切换开销很小,对于大多数嵌入式应用来说完全够用。
实用价值
对于一般的项目,ThreadX可能有点"大材小用"了,毕竟FreeRTOS已经能满足大部分需求。但如果你的项目对可靠性要求特别高,或者需要通过某些安全认证,ThreadX确实是个不错的选择。
另外,学习ThreadX也有助于理解高质量RTOS的设计思路,对提升技术水平还是有帮助的。
后续计划
目前只是跑通了基本功能,后面还可以尝试:
  • 集成ThreadX的网络协议栈
  • 测试一下文件系统组件
  • 研究一下它的调试和分析工具
  • 看看能不能移植到其他芯片平台
小结
在APM32F4上跑ThreadX整体来说是可行的,虽然移植过程需要一些底层知识,但最终效果还是令人满意的。对于想要体验企业级RTOS的朋友,不妨动手试试。

代码我已经整理好了,有需要的朋友可以留言交流。希望这个分享对大家有所帮助!




您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:欢迎参与LabVIEW版块的讨论学习! 点我一键即达

256

主题

2827

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部
个人签名:欢迎参与LabVIEW版块的讨论学习! 点我一键即达

256

主题

2827

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部