本帖最后由 kai迪皮 于 2022-12-8 14:38 编辑
#申请原创# @21小跑堂
前言
最近在玩APM32F407IG的板子,发现官方提供了两个RTOS的demo,FreeRTOS和RT-thread。想着APM32F4这么丰富的资源,是不是也可以跑一下ThreadX RTOS看看。想着就动手,本文记录了APM32F4一直ThreadX RTOS的过程,供各位做个参考,权当抛砖引玉了。
1 ThreadX简介
说道RTOS,大家可能会想到FreeRTOS和我们国产的RT-Thread,这两者的知名度都非常高。因为它们商用免费,组件也十分丰富。那ThreadX RTOS可能就“鲜为人知”了,但是在一些行业内,它可是“如雷贯耳”,因为它上过天。O(∩_∩)O哈哈~,下面就简单介绍一些ThreadX。
ThreadX全名是Azure RTOS ThreadX(这里是它的官方网站:https://learn.microsoft.com/zh-cn/azure/rtos/threadx/)。它是专门为深度嵌入式实时 IoT 应用程序设计的。 Azure RTOS ThreadX 提供高级计划、通信、同步、计时器、内存管理和中断管理功能。 此外,Azure RTOS ThreadX 具有许多高级功能,包括 picokernel™ 体系结构、preemption-threshold™ 计划、event-chaining™、执行分析、性能指标和系统事件跟踪。 Azure RTOS ThreadX 非常易于使用,适用于要求极其苛刻的嵌入式应用程序。 Azure RTOS ThreadX 在各种产品(包括消费者设备、医疗电子设备和工业控制设备)上的部署次数已达数十亿次。(节选自其官网)
为什么说它厉害呢?以上内容也仅是它的应用介绍,这个RTOS的真正厉害之处在于其通过了各项安全认证。以下便是其通过的安全认证:
1. 医疗 - FDA510(k),IEC-62304 Class C,IEC-60601,ISO-14971
2. 工业 - UL-1998,IEC-61508 SIL 4
3. 运输/铁路 - EN50128 SIL 4,BS50128, 49CFR236,IEC-61508
4. 航空航天设备 - DO-178B,ED-12B,DO-278
5. 汽车 - IEC-61508 ASIL D
6. 核应用 - IEC-61508
7. 家电 - UL/IEC 60730/60335
其实它之前是收费闭源的,自被微软收购后就开源出来了,而且我们出于学习评估的目的,是不会被限制的。这个RTOS的厉害之处,大家可以查阅他们的官网,这里就不赘述了。
2 源码获取
移植前我们需要准备一些源码
1. APM32F407的工程模板,这个可以在他们官网获取:https://geehy.com/support/apm32?id=311
2. ThreadX源码,这个可以在他们的开源仓库获取:https://github.com/azure-rtos/threadx
需要注意的是,由于我们本次环境使用的是MDK环境,我们需要使用5.30以上的MDK。
3 文件移动与复制
准备好两个源码后,我们需要进行一些文件移动与复制工作以完成我们工程创建的前期准备。
1. 的把ThreadX源码复制至APM32F4xx_SDK\Middlewares文件夹中,准备后续工程文件的使用。
2. 然后创建\Examples\SysTick的例程副本,重新命名为“ThreadX_Template”。用以作为我们的基础模板,我们将在该模板上建立APM32F4的ThreadX的demo。
3. 复制ThreadX源码中的“\ports\cortex_m4\ac5\example_build\tx_initialize_low_level.s”文件至我们的例程源码目录“ThreadX_Template\ThreadX_Template\Source”并改名为“tx_initialize_low_level_ac5.s”,因为我们后续要改动该文件。这里复制出一个副本出来用于后续修改。
4 添加源码
这里我们选择我们刚刚复制的ThreadX_Template的MDK工程中添加相应的源文件。打开MDK工程,在现有目录下添加“ThreadX/ports”,“ThreadX/common”结构,我们添加相应的源码文件。
4.1 ThreadX/port结构
在该结构下,我们添加AC5编译器使用的.s文件。
1. 即“threadx-6.2.0_rel\ports\cortex_m4\ac5\src”下的所有 .s 文件。
2. 添加我们刚刚复制出来的tx_initialize_low_level_ac5.s文件。
最终添加文件如下:
4.2 ThreadX/common结构
在该结构下,我们添加ThreadX核心文件。即“threadx-6.2.0_rel\common\src”下的.c文件。
4.3 Application结构
在原Application结构下添加我们一会要创建的线程文件:tx_application_entry.c。我们一会修改该文件内容。
5 选项卡设置
5.1 编译器设置
由于我们使用的是AC5的相关支持文件,我们这里选择使用AC5编译器,并勾选“Use MicroLIB”以使用printf。
5.2 C/C++选项卡
在设置中的“C/C++”选项卡下的头文件设置中添加头文件路径。
1. “..\..\..\..\..\Middlewares\threadx-6.2.0_rel\ports\cortex_m4\ac5\inc”
2. “..\..\..\..\..\Middlewares\threadx-6.2.0_rel\common\inc”
5.3 汇编头文件包含
在设置中的“Asm”选项卡需要完成以下设置:
1. 头文件设置出添加头文件路径:“..\..\..\..\..\Middlewares\threadx-6.2.0_rel\ports\cortex_m4\ac5\inc”。
2. 在Misc Controls栏中填写“--cpreproc”。以解决编译.s文件报错问题。
6 修改源文件
由于ThreadX需要接管一些中断,并且使用ThreadX我们需要创建一些线程,这里我们要对我们的工程里面的源码进行编辑。
6.1 tx_initialize_low_level_ac5.s
该文件是ThreadX RTOS用于完成处理器的底层初始化,包括:
1. 设置中断向量表
2. 设置用于产生时钟节拍的定位器(Systick)
3. 保存系统栈顶指针给中断程序使用
4. 寻找RAM中首块可用地址传入tx_application_define函数供使用,也就是first_unused_memory指针的值
5. ThreadX在v6版本及以后,在这个文件中接管原有的处理器启动文件。
用于我这里还是想使用原有的处理器启动文件“startup_apm32f40x.s”,所以需要对“tx_initialize_low_level_ac5.s”文件进行一番修改。
1. 将没有用到的标号注释,手动添加_Vectors和__initial_sp标号,分别是APM32F4启动文件中导出的中断向量表和栈顶指针初始值:
2. 设置时钟频率(168Mhz)和时钟节拍(1ms),该值用来初始化Systick定时器:
3. 将设置堆栈的代码全部注释(堆栈环境已经在APM32启动文件中设置了):
4. 将 ThreadX 定义的中断向量表全部注释(使用APM32F4启动文件中定义的向量表):
5. 注释ThreadX定义的复位处理程序(使用APM32F4启动文件中的复位程序):
6. 修改ThreadX底层初始化函数:
7. 注释用不到的函数:
完成以上操作之后便是完成了对tx_initialize_low_level_ac5.s文件的修改。
6.2 apm32f4xx_int.c
由于ThreadX接管了部分中断,apm32f4xx_int.c里面的一些中断就必须进行屏蔽(或删除)处理,以免编译器报重复定义的错误。
即屏蔽PendSV_Handler和SysTick_Handler函数。
6.3 tx_application_entry.c
在该文件中我们创建两个线程并运行,需要使用到ThreadX的一些知识,这里就不赘述,直接上源码。
#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
我们需要在main函数中初始化相应的外设(LED、USART)后启动ThreadX。由于在tx_application_entry.c中使用了printf,我们还需要对printf进行重定向。并删除之前SysTick工程中一些操作,这里也闲话少叙,直接上源码。
/* 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
@{
*/
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] 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)
{
}
}
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] 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 编译与下载
最后我们编译工程,编译结果如下。
然后我们下载程序进板子后,LED2和LED3闪烁,若连接串口可以查看到如下信息:
我这里也分享一下我的工程供各位小伙伴参考(论坛文件大小限制,我删除了一些无关的文件)
APM32F4xx_SDK_ThreadX_v0.1.zip
(3.57 MB)
,如果这个帖子对您有一点点帮助,请点个赞鼓励我一下下把(*^▽^*)。
|
@21小跑堂 :Thanks♪(・ω・)ノ,感谢肯定。
国产芯片对于操作系统的适配还不尽完善,需要广大开发者一起努力,完善国产芯片的开发生态。作者不满足现状,自己动手适配ThreadX RTOS,并将整个过程详细分享,感谢您的贡献分享。