论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=93149
本章教程为大家讲解RTX5内核的AC5编译器移植。
4.1 初学者重要提示
4.2 移植RTX5内核整体说明
4.3 了解RTX5内核模板框架设计
4.4 第1步,安装MDK软件包版本
4.5 第2步,准备一个工程模板
4.6 第3步,添加RTX5源码
4.7 第4步,MPU和Cache配置文件bsp.c
4.8 第5步,更新bsp_timer.c和bsp.h文件
4.9 第6步,修改文件stm32h7xx_it.c
4.10 第7步,添加头文件的汇总文件includes.h
4.11 第8步,HAL库时间基准stm32h7xx_hal_timbase_tim.c
4.12 第9步,添加BSP驱动文件bsp_dwt.c
4.13 第10步,创建应用任务(重要,注意启动任务)
4.14 常见移植错误总结
4.15 实验例程
4.16 总结
移植之前,有必要对移植过程有个整体的认识:
总的来说,这4步就可以完成移植, 下面将STM32H7的移植步骤和注意事项为大家做个说明。
移植RTX5前,我们优先了解下移植好的RTX5内核模板,方面大家后面移植:
框图如下:
移植新版RTX5需要大家下载当前最新的MDK软件包版本(如果有最新版,推荐大家用最新版):
这些软件包的安装在STM32H7用户手册的第2章2.3小节有详细说明。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=100086
不管以后MDK的软件包版本如何升级,当前的软件包版本和以后的新版是可以同时安装的,也就是说可以安装多个不同版本,在这里可以选择指定版本:
首先准备好一个简单的裸机工程模板,已经为大家做好:V7-400_Base Template,准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):
RTX5可以方便的通过MDK的RTE环境添加进来。对于H7版本,MDK会强制运行一次CubeMX,并添加很多H7的HAL库文件,这些库文件我们可以使用,也可以不使用。教程配套的工程文件是不使用这些,因为前面的工程模板里面已经添加了。所以要将这些文件全部隔离出来。
点击OK按钮后,弹出如下界面:
点击Start STM32CubeMX,这里需要大家电脑上已经安装了STM32CubeMX,并且为其安装了H7的软件包。
打开后,用户仅需配置如下地方即可:
然后点击右上角的GENERATE CODE:
然后弹出如下对话框,点击Close即可,然后关闭STM32CubeMX。
重新回到MDK,会有一个对话框,点击是即可:
最后就可以看到RTX5源码已经添加到工程里面了:
添加的所有文件中,仅RTX5和文件stm32h7xx_hal_msp.c留下,其它文件全部隔离出来,隔离方法也比较简单,比如隔离生成的main.c函数,鼠标右击此文件选择Options for file ‘main.c’
然后取消掉include Target Build前的对勾,点击OK:
看到main.c文件上有个红色横杠,就表示已经隔离出来了:
同样的方法,将stm32h7xx_it.c文件也隔离出来,文件stm32h7xx_it.h不用管。
Device下面的这些文件也是同样的隔离方法:
只是鼠标右击弹出的界面有些不同:
注意stm32h7xx_hal_msp.c无需隔离,其它所有的文件全部隔离,stm32h7xx_hal_msp.c对应的隔离配置是STM32CubeMX,如果也隔离了,编译会有问题:
隔离后的效果如下:
剩下就是配置RTX5,设置RTX_Config.h文件即可,移植阶段先按照如下设置配置好,后面章节会专门为大家讲解每个参数的配置含义:
这个bsp.c文件也比较重要,移植阶段,直接将我们移植好的模板内容复制过去即可,这里把相关的内容为大家做个说明。
系统初始化,主要是MPU,Cache和系统时钟配置,需要在RTX5初始化之前调用。
/*
*********************************************************************************************************
* 函 数 名: System_Init
* 功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void System_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init();
/*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
}
硬件外设的初始化,这个函数在RTX5的启动任务里面调用。
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */
bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
bsp_InitTimer(); /* 初始化滴答定时器 */
}
这个函数主要是完成系统时钟配置。
/*
*********************************************************************************************************
* 函 数 名: SystemClock_Config
* 功能说明: 初始化系统时钟
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 400000000 (CPU Clock)
* HCLK(Hz) = 200000000 (AXI and AHBs Clock)
* AHB Prescaler = 2
* D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
* D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
* D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
* D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
* HSE Frequency(Hz) = 25000000
* PLL_M = 5
* PLL_N = 160
* PLL_P = 2
* PLL_Q = 4
* PLL_R = 2
* VDD(V) = 3.3
* Flash Latency(WS) = 4
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void SystemClock_Config(void)
{
省略未写
/* AXI SRAM的时钟是上电自动使能的,而D2域的SRAM1,SRAM2和SRAM3要单独使能 */
#if 1
__HAL_RCC_D2SRAM1_CLK_ENABLE();
__HAL_RCC_D2SRAM2_CLK_ENABLE();
__HAL_RCC_D2SRAM3_CLK_ENABLE();
__HAL_RCC_BKPRAM_CLKAM_ENABLE();
__HAL_RCC_D3SRAM1_CLKAM_ENABLE();
#endif
}
这里的RAM时钟初始化比较重要,这几个RAM的时钟都要单独使能。
RTX5例子默认采用AXI SRAM作为主RAM空间,因为空间比较大,方便我们后制作综合例子使用:
/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
/* 最高性能,读Cache和写Cache都开启 */
#if 1
/* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 最低性能,读Cache和写Cache都关闭 */
#else
/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif
/* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
这个函数里面默认有个按键扫描,如果大家移植的程序里面没有按键初始化,务必要把这个按键扫描函数注释掉。
/*
*********************************************************************************************************
* 函 数 名: bsp_RunPer10ms
* 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
* 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
bsp_KeyScan10ms();
}
更新bsp_timer.c文件是因为此文件跟RTX5都要使用滴答定时器,有冲突。所以大家直接将我们工程模板里面此文件覆盖移植的这个文件即可。
bsp.h文件里面要添加一个宏定义,因为bsp_timer.c文件里面做了些条件编译:
#define USE_RTX 1
另外,bsp.h文件将大部分头文件都添加进来了,大家可以根据需要,用到那些头文件,使能那些,用不到的,可以注释掉。当然,不注释也是没问题的:
/* 通过取消注释或者添加注释的方式控制是否包含底层驱动模块 */
//#include "bsp_msg.h"
//#include "bsp_user_lib.h"
#include "bsp_timer.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_dwt.h"
//#include "bsp_cpu_rtc.h"
//#include "bsp_cpu_adc.h"
//#include "bsp_cpu_dac.h"
#include "bsp_uart_fifo.h"
//#include "bsp_uart_gps.h"
//#include "bsp_uart_esp8266.h"
//#include "bsp_uart_sim800.h"
//#include "bsp_spi_bus.h"
//#include "bsp_spi_ad9833.h"
//#include "bsp_spi_ads1256.h"
//#include "bsp_spi_dac8501.h"
//#include "bsp_spi_dac8562.h"
//#include "bsp_spi_flash.h"
//#include "bsp_spi_tm7705.h"
//#include "bsp_spi_vs1053b.h"
//#include "bsp_fmc_sdram.h"
//#include "bsp_fmc_nand_flash.h"
//#include "bsp_fmc_ad7606.h"
//#include "bsp_fmc_oled.h"
#include "bsp_fmc_io.h"
//#include "bsp_i2c_gpio.h"
//#include "bsp_i2c_bh1750.h"
//#include "bsp_i2c_bmp085.h"
//#include "bsp_i2c_eeprom_24xx.h"
//#include "bsp_i2c_hmc5883l.h"
//#include "bsp_i2c_mpu6050.h"
//#include "bsp_i2c_si4730.h"
//#include "bsp_i2c_wm8978.h"
//#include "bsp_tft_h7.h"
//#include "bsp_tft_429.h"
//#include "bsp_tft_lcd.h"
//#include "bsp_ts_touch.h"
//#include "bsp_ts_ft5x06.h"
//#include "bsp_ts_gt811.h"
//#include "bsp_ts_gt911.h"
//#include "bsp_ts_stmpe811.h"
#include "bsp_beep.h"
#include "bsp_tim_pwm.h"
//#include "bsp_sdio_sd.h"
//#include "bsp_dht11.h"
//#include "bsp_ds18b20.h"
//#include "bsp_ps2.h"
//#include "bsp_ir_decode.h"
//#include "bsp_camera.h"
//#include "bsp_rs485_led.h"
//#include "bsp_can.h"
删除此文件里面带的如下函数,RTX5要使用,冲突了。
/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
void SVC_Handler(void)
{
}
/**
* @brief This function handles PendSVC exception.
* @param None
* @retval None
*/
void PendSV_Handler(void)
{
}
在User文件夹下添加文件incudes.h,直接从本章节教程配套例子的User文件夹复制即可。此文件主要用于RTX5的各种头文件汇总。
由于RTX5和HAL库需要一个时间基准,而且默认都是用的滴答定时器,所有要有一个选用其它的时间基准。当前的处理方案是为HAL库提供一个时间基准文件stm32h7xx_hal_timbase_tim.c。此文件
里面做了两套方案,一个是使用TIM7做时间基准,另一个是使用RTX5的API做时间基准,通过条件编译做选择。默认是采用RTX5的API做时间基准。
/*
*********************************************************************************************************
* 函 数 名: HAL_Delay
* 功能说明: 重定向毫秒延迟函数。替换HAL中的函数。因为HAL中的缺省函数依赖于Systick中断,如果在USB、SD
* 卡中断中有延迟函数,则会锁死。也可以通过函数HAL_NVIC_SetPriority提升Systick中断
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void HAL_Delay(uint32_t Delay)
{
bsp_DelayMS(Delay);
}
HAL_StatusTypeDef HAL_InitTick (uint32_t TickPriority)
{
return HAL_OK;
}
uint32_t HAL_GetTick (void)
{
static uint32_t ticks = 0U;
uint32_t i;
if (osKernelGetState () == osKernelRunning)
{
return ((uint32_t)osKernelGetTickCount ());
}
/* 如果RTX5还没有运行,采用下面方式 */
for (i = (SystemCoreClock >> 14U); i > 0U; i--)
{
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
}
return ++ticks;
}
添加bsp_dwt.c文件和bsp_dwt.h文件主要是因为第8步中的stm32h7xx_hal_timebase_tim.c文件里面的函数bsp_DelayMS要使用,此函数是基于DWT系统时钟周期计数器实现。
应用程序比较简单,大家可以直接复制本章教程配置例子的main.c文件中的内容到自己工程里面测试。主要创建了如下几个任务:
AppTaskUserIF任务 : 按键消息处理。
AppTaskLED任务 : LED闪烁。
AppTaskMsgPro任务 : 消息处理,暂未使用。
AppTaskStart任务 : 启动任务,也是最高优先级任务,这里实现按键扫描。
osRtxTimerThread任务: 定时器任务,暂未使用。
任务栈大小和任务控制块定义如下:
/*
**********************************************************************************************************
变量
**********************************************************************************************************
*/
/* 任务的属性设置 */
const osThreadAttr_t ThreadStart_Attr =
{
/* 未使用 */
// .cb_mem = &worker_thread_tcb_1,
// .cb_size = sizeof(worker_thread_tcb_1),
// .stack_mem = &worker_thread_stk_1[0],
// .stack_size = sizeof(worker_thread_stk_1),
// .priority = osPriorityAboveNormal,
// .tz_module = 0
.name = "osRtxStartThread",
.attr_bits = osThreadDetached,
.priority = osPriorityHigh4,
.stack_size = 2048,
};
const osThreadAttr_t ThreadMsgPro_Attr =
{
.name = "osRtxMsgProThread",
.attr_bits = osThreadDetached,
.priority = osPriorityHigh3,
.stack_size = 1024,
};
const osThreadAttr_t ThreadLED_Attr =
{
.name = "osRtxLEDThread",
.attr_bits = osThreadDetached,
.priority = osPriorityHigh2,
.stack_size = 512,
};
const osThreadAttr_t ThreadUserIF_Attr =
{
.name = "osRtxThreadUserIF",
.attr_bits = osThreadDetached,
.priority = osPriorityHigh1,
.stack_size = 1024,
};
任务创建:
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: 标准c程序入口。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int main (void)
{
/* HAL库,MPU,Cache,时钟等系统初始化 */
System_Init();
/* 内核开启前关闭HAL的时间基准 */
HAL_SuspendTick();
/* 内核初始化 */
osKernelInitialize();
/* 创建启动任务 */
ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);
/* 开启多任务 */
osKernelStart();
while(1);
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskCreate
* 功能说明: 创建应用任务
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);
ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);
}
这里我们重点看下启动任务,主要做了四个工作:
/*
*********************************************************************************************************
* 函 数 名: AppTaskStart
* 功能说明: 启动任务,这里用作BSP驱动包处理。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityHigh4
*********************************************************************************************************
*/
void AppTaskStart(void *argument)
{
const uint16_t usFrequency = 1; /* 延迟周期 */
uint32_t tick;
/* 初始化外设 */
HAL_ResumeTick();
bsp_Init();
/* 创建任务 */
AppTaskCreate();
/* 获取当前时间 */
tick = osKernelGetTickCount();
while(1)
{
/* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
bsp_ProPer1ms();
/* 相对延迟 */
tick += usFrequency;
osDelayUntil(tick);
}
}
常见的移植错误主要有下面几种情况:
Error: L6200E: Symbol PendSV_Handler multiply defined (by irq_cm4f.o and stm32h7xx_it.o).
Error: L6200E: Symbol SVC_Handler multiply defined (by irq_cm4f.o and stm32h7xx_it.o).
解决办法:这是函数重定义了,直接将stm32h7xx_it.c文件里面的PendSV_Handler和SVC_Handler删掉。
Error: L6218E: Undefined symbol bsp_DelayMS (referred from bsp_fmc_io.o).
解决办法:打开bsp_dwt.C文件中的条件编译。
本章节配套了如下几个例子供大家移植参考:
裸机模板,方便大家添加RTX5内核源码。
ThreadX内核模板。
MDK进入调试状态后,选择周期更新:
然后打开调试组件,注意和RTX4的调试组件位置不同:
然后点击MDK的全速运行,
至此,就可以动态实时查看RTX5的运行状态:
本章节为大家讲解了RTX5 在MDK AC5上的移植方法,移植涉及到的知识点比较多,初学的话,建议实际动手操作一遍。