*This content has been reposted with the approval of the original author.
Preface
Recently I was using the boards of APM32F407IG and found that two official RTOS demos are provided, FreeRTOS and RT-thread. Thinking that APM32F4 is so rich in resources, I thought it can also run ThreadX RTOS, in order to try the effect, so I did it. This article records the process of running ThreadX RTOS on APM32F4, just for your reference.
1 Introduction of ThreadX
When it comes to RTOS, you may think of FreeRTOS and our Chinese RT-Thread, both of which are very well known because they are commercially available for free and have a great variety of components. The ThreadX RTOS may be “lesser known”, but in some industries, it is “well-known” because it has been to outer space. Here is a brief introduction to ThreadX.
The full name of ThreadX is Azure RTOS ThreadX (here is its official website: https://learn.microsoft.com/zh-cn/azure/rtos/threadx/ ). It is specifically designed for deeply embedded real-time IoT applications. Azure RTOS ThreadX provides advanced planning, communication, synchronization, timer, memory management, and interrupt management features. In addition, Azure RTOS ThreadX has many advanced features, including picokernel™ architecture, preemption-threshold™ planning, event-chaining™, execution analysis, performance metrics, and system event tracking. Azure RTOS ThreadX is extremely easy to use and suitable for extremely demanding embedded applications. Azure RTOS ThreadX has been deployed billions of times on a variety of products, including consumer devices, medical electronics and industrial control devices. (Excerpt from Azure RTOS ThreadX website)
Why is it powerful? The above is only an introduction to its applications, but the real power of this RTOS is that it has passed various security certifications. The following are the security certifications it has passed:
- Medical - FDA510(k), IEC-62304 Class C, IEC-60601, ISO-14971
- Industrial - UL-1998, IEC-61508 SIL 4
- Transportation/Railway - EN50128 SIL 4, BS50128, 49CFR236, IEC-61508
- Aerospace Equipment - DO-178B, ED-12B, DO-278
- Automotive - IEC-61508 ASIL D
- Nuclear applications - IEC-61508
- Home Appliances - UL/IEC 60730/60335
It was actually closed source for a fee before and has been open source out since it was acquired by Microsoft. Since we are here for the purpose of learning assessment, we will not be restricted. You can check their official website for the greatness of this RTOS, so I won’t go into details here.
2 Acquisition of source code
We need to prepare some source code before migration.
The project template of APM32F407, this can be obtained from their official website:
https://geehy.com/support/apm32?id=311
ThreadX source code, which can be obtained in their open source repository:
https://github.com/azure-rtos/threadx
Note that since we are using the MDK environment in this case, we need to use MDK of 5.30 or higher.
3 File moving and copying
Once the two source codes are ready, we need to do some file moving and copying to complete the prep for our project creation.
Copy the ThreadX source code to the APM32F4xx _ SDK \ Middlewares folder to prepare for the use of subsequent project files.
Then create a copy of \Examples\SysTick routine and rename it “ThreadX_Template” as our base template, on which we will build the APM32F4 ThreadX demo.
Copy the file “\ports\cortex_m4\ac5\example_build\tx_initialize_low_level.s” from the ThreadX source code to our routine source code directory “\ports\cortex_m4\ac5\example_build\tx_initialize_low_level.s”. ThreadX_Template\ThreadX_Template\Source" and rename it to “tx_initialize_low_level_ac5.s”. Since we are going to change this file later, we can make a copy of it here for subsequent changes.
Then create a copy of \Examples\SysTick routine and rename it “ThreadX_Template” as our base template, on which we will build the APM32F4 ThreadX demo.
Copy the file “\ports\cortex_m4\ac5\example_build\tx_initialize_low_level.s” from the ThreadX source code to our routine source code directory “\ports\cortex_m4\ac5\example_build\tx_initialize_low_level.s”. ThreadX_Template\ThreadX_Template\Source" and rename it to “tx_initialize_low_level_ac5.s”. Since we are going to change this file later, we can make a copy of it here for subsequent changes.
4. Adding source code
Here we select the MDK project of ThreadX_Template that we just copied and add the corresponding source files. Open the MDK project and add the “ThreadX/ports” and “ThreadX/common” structures to the existing directory and add the corresponding source files.
4.1 The ThreadX/port structure
Under this structure, we add the .s file used by the AC5 compiler.
- i.e. all .s files under “threadx-6.2.0_rel\ports\cortex_m4\ac5\src”.
- Add the tx_initialize_low_level_ac5.s file we just copied.
The final added file is as follows:
4.2 The ThreadX/common structure
Under this structure, we add the ThreadX core file. That is, the .c file under “threadx-6.2.0_ rellcommon\src”.
4.3 Application structure
Under the original Application structure, add the thread file we will create later: tx application_ entry.c. We will modify the contents of this file later.
5 Tab settings
5.1 Compiler settings
Since we are using AC5 support files, we choose to use the AC5 compiler here and check “Use MicroLIB” to use printf.
5.2 C/C++ tab
Add a header file path to the header file settings under the “C/C++” tab in the settings.
- “… ... ... ... ... ... \Middlewares\threadx-6.2.0_rel\ports\cortex_m4\ac5\inc”
- “… ... ... ... ... \Middlewares\threadx-6.2.0_rel\common\inc”
5.3 The assembly header file contains
In the “Asm” tab of the settings, the following settings need to be completed:
- The header file is set to add the header file path: “..........\Middlewares\threadx-6.2.0_rel\ports\cortex_m4\ac5\inc”.
- Fill in the Misc Controls field with “–cpreproc” to solve the error problem of compiling the .s file.
6 Modify the source file
Since ThreadX needs to take over some interrupts and we need to create some threads to use ThreadX, here we have to edit the source code inside our project.
6.1 tx_initialize_low_level_ac5.s
This file is used by ThreadX RTOS to complete the underlying initialization of the processor and includes:
1.Setting interrupt vector table
- Setting the Systick used to generate the clock beat
- save the top of stack pointer of the system for the interrupt program
- Find the first available address in RAM to pass to the “tx_application_define” function for use, which is the value of the “first_unused_memory” pointer
- ThreadX takes over the original processor boot file in this file in v6 and later.
I still want to use the original processor boot file “startup_apm32f40x.s”, so I need to modify the “tx_initialize_low_level_ac5.s” file.
- Annotate the unused markers and manually add the _Vectors and __initial_sp labels, which are the initial values of the interrupt vector table and top of stack pointer exported in the APM32F4 boot file, respectively:
- Setting the clock frequency (168Mhz) and clock beat (1ms), the value is used to initialize the Systick timer:
- Annotate all the code for setting the stack (the stack environment has been set in the APM32 boot file):
- Annotate all interrupt vector tables defined by ThreadX (using the vector table defined in the APM32F4 boot file):
- Annotate the reset handler defined by ThreadX (using the reset program in the APM32F4 boot file):
- Modify the underlying initialization function of ThreadX:
- Annotate functions that are not used:
After completing the above operation, the modification of the tx_initialize_low_level_ac5.s file is completed.
6.2 apm32f4xx_int.c
Since ThreadX takes over some of the interrupts, some interrupts inside apm32f4xx_int.c must be masked (or removed) to avoid repeated definition errors from the compiler.
i. e. block PendSV_Handler and SysTick_Handler functions.
6.3 tx_application_entry.c
In this file, we create two threads and run them, which requires some knowledge of ThreadX, and let’s cut to the chase and look at the source code directly.
#include <stdio.h>
#include “Board.h”
#include “tx_api.h”
#include “main.h”
#define TX_APPLICATION1_PRIO 3
#define TX_APPLICATION1_STACK_SIZE 1024
static TX_THREAD tx_application1;
uint8_t tx_application1_stack[TX_APPLICATION1_STACK_SIZE];
#define TX_APPLICATION2_PRIO 2
#define TX_APPLICATION2_STACK_SIZE 1024
static TX_THREAD tx_application2;
uint8_t tx_application2_stack[TX_APPLICATION2_STACK_SIZE];
void my_tx_application1_entry(ULONG thread_input)
{
/* Enter into a forever loop. /
while(1)
{
printf(“ThreadX 1 application running…\r\n”);
APM_MINI_LEDToggle(LED2);
/ Sleep for 1500 tick. */
tx_thread_sleep(1500);
}
}
void my_tx_application2_entry(ULONG thread_input)
{
/* Enter into a forever loop. /
while(1)
{
printf(“ThreadX 2 application running…\r\n”);
APM_MINI_LEDToggle(LED3);
/ Sleep for 1000 tick. */
tx_thread_sleep(1000);
}
}
void tx_application_define(void first_unused_memory)
{
/ Create thread */
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);
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);
}
6.4 main.c
We need to start ThreadX after initializing the corresponding peripherals ( LED, USART ) in the main function. As printf is used in tx_application_entry.c, we also need to redirect printf and remove some operations from the previous SysTick project, and let’s cut to the chase and look at the source code directly.
/* Includes */
#include “main.h”
#include “Board.h”
#include <stdio.h>
#include “tx_api.h”
/** @addtogroup Examples
@{
*/
/** @addtogroup SysTick_TimeBase
@{
*/
/** @addtogroup SysTick_TimeBase_Macros Macros
@{
*/
/** printf using USART1 */
#define DEBUG_USART USART1
/**@} end of group SysTick_TimeBase_Macros*/
/** @defgroup SysTick_TimeBase_Functions Functions
@{
*/
/*!
@brief Main program
*
@param None
*
@retval None
*/
int main(void)
{
USART_Config_T usartConfigStruct;
usartConfigStruct.baudRate = 115200;
usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
usartConfigStruct.mode = USART_MODE_TX;
usartConfigStruct.parity = USART_PARITY_NONE;
usartConfigStruct.stopBits = USART_STOP_BIT_1;
usartConfigStruct.wordLength = USART_WORD_LEN_8B;
APM_MINI_COMInit(COM1, &usartConfigStruct);
APM_MINI_LEDInit(LED2);
APM_MINI_LEDInit(LED3);
printf(“ThreadX RTOS on APM32F407 IG MINI Board\r\n”);
tx_kernel_enter();
while (1)
{
}
}
/*!
@brief Redirect C Library function printf to serial port.
After Redirection, you can use printf function.
*
@param ch: The characters that need to be send.
*
@param *f: pointer to a FILE that can recording all information
needed to control a stream
*
@retval The characters that need to be send.
*/
int fputc(int ch, FILE *f)
{
/** send a byte of data to the serial port */
USART_TxData(DEBUG_USART,(uint8_t)ch);
/** wait for the data to be send */
while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
return (ch);
}
7 Compile and download
Finally, we compile the project and the compilation result is as follows:
After we download the program into the board, LED2 and LED3 blink, and if we connect the serial port, we can see the following information:
I also share my project here for your reference, if this post is a little bit helpful to you. Please click a like to encourage me a bit.