你们好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给你们分享的是IVT里的不一样entry设置可能会形成i.MXRT1xxx系列启动App后发生异常跑飞问题的分析解决经验。html
事情缘起恩智浦官方论坛上的一个疑问帖 《RT1015 dev_cdc_vcom_freertos reset entry failed》,这是客户QISDA遇到的问题,由痞子衡的同事 - 很是细心负责的Kerry小姐姐将问题整理出来并发了贴,帖子里作了详尽的问题描述以及各类测试结果。看完长帖后,痞子衡第一猜测就是跟App栈设置有关,最终也确实是这个缘由。那么为何栈设置会出问题呢?且听痞子衡细聊:git
让咱们先来整理一下帖子里的问题现象,客户在RT1015-EVK上测试了恩智浦官方SDK里的两个例程,一个是简单的hello_world,另外一个是复杂的dev_cdc_vcom_freertos,这两个例程在不一样IDE、IVT中entry值组合下现象不一致:微信
测试App | IVT中entry | 测试IDE | App运行结果 |
---|---|---|---|
hello_world | 中断向量表起始地址/ 复位向量函数地址 |
IAR EWARM/ MCUXpresso IDE |
正常 |
dev_cdc_vcom_freertos | 中断向量表起始地址 | IAR EWARM/ MCUXpresso IDE |
正常 |
dev_cdc_vcom_freertos | 复位向量函数地址 | IAR EWARM | 正常 |
dev_cdc_vcom_freertos | 复位向量函数地址 | MCUXpresso IDE | 异常跑飞 |
根据上表结果,其实咱们很可贵出一个有效推论,只能说这个异常结果在特定的App, entry值, MCUXpresso IDE下才能复现。并发
既然暂时看不出缘由,那咱们先作一些准备工做吧。咱们把三个影响因子(App, entry值, IDE)的差别先整理出来:app
两个App都来自SDK,是通过官方详尽测试的,因此咱们不去怀疑App自己的功能异常。它们的差别主要在连接分配上。以IAR为例,咱们只看flexspi_nor build,在连接文件中默认分配的堆、栈大小均为1KB:ide
/* Sizes */ if (isdefinedsymbol(__stack_size__)) { define symbol __size_cstack__ = __stack_size__; } else { define symbol __size_cstack__ = 0x0400; } if (isdefinedsymbol(__heap_size__)) { define symbol __size_heap__ = __heap_size__; } else { define symbol __size_heap__ = 0x0400; }
hello_world例程由于比较简单,因此用直接用了默认的堆栈大小,而dev_cdc_vcom_freertos例程比较复杂,堆栈作了额外调整,栈增大到了8KB。函数
此外咱们还注意到hello_world例程将其RW, ZI, 堆栈所有放进了32KB的DTCM;而dev_cdc_vcom_freertos例程则将RW, ZI放入了64KB OCRAM,只将堆栈放进了DTCM:测试
define symbol m_data_start = 0x20000000; define symbol m_data_end = 0x20007FFF; define symbol m_data2_start = 0x20200000; define symbol m_data2_end = 0x2020FFFF; define region DATA_region = mem:[from m_data_start to m_data_end-__size_cstack__]; define region DATA2_region = mem:[from m_data2_start to m_data2_end]; define region CSTACK_region = mem:[from m_data_end-__size_cstack__+1 to m_data_end]; // 适用hello_world例程 place in DATA_region { block RW }; place in DATA_region { block ZI }; place in DATA_region { last block HEAP }; place in DATA_region { block NCACHE_VAR }; place in CSTACK_region { block CSTACK }; // 适用dev_cdc_vcom_freertos例程 place in DATA2_region { block RW }; place in DATA2_region { block ZI }; place in DATA_region { last block HEAP }; place in DATA_region { block NCACHE_VAR }; place in CSTACK_region { block CSTACK };
再说IVT中的entry,痞子衡在i.MXRT1xxx系列启动那些事系列文章中的 《Bootable image格式与加载》 的3.2节介绍过IVT结构以及其做用,IVT是关键启动头,指导了BootROM去搬移App以及加载执行,其中entry成员主要用于跳转执行。flex
为何这个entry值既能够是中断向量表(Vector Table)起始地址也能够是复位向量(Reset_Handler)函数地址呢?这取决于BootROM中是怎么利用这个entry值的,下面函数便是BootROM中最终跳转函数:ui
void jump_to_entry(uint32_t entry) { typedef void (*application_callback_t)(void); static application_callback_t s_app_callback; pu_irom_mpu_disable(); __DMB(); __DSB(); __ISB(); // The entry point is the absolute address of the call back function if ((uint32_t)entry & 1) { s_app_callback = (application_callback_t)entry; } // The entry point is the base address of vector table else { static uint32_t s_stack_pointer; // Ensure Core read vector table for destination instead of register volatile uint32_t *vector_table = (volatile uint32_t *)entry; s_stack_pointer = vector_table[0]; s_app_callback = (application_callback_t)vector_table[1]; // Update Stack pointer __set_MSP(s_stack_pointer); __set_PSP(s_stack_pointer); } __DSB(); __ISB(); // Jump to user application in the end s_app_callback(); // Should never reach here __NOP(); __NOP(); }
从上面的跳转函数jump_to_entry()实现能够看出,entry值若是是复位函数地址(即奇地址),那么BootROM直接跳转到复位函数执行;若是entry值是中断向量表首地址(即偶地址),BootROM会先将当前SP重设到App指定的栈顶,而后再跳转到复位函数。
好的,如今咱们知道了IVT中不一样的entry值差别在哪了。
由于涉及到两个不一样IDE,即IAR和MCUXpresso IDE,因此咱们分别看一下这两个IDE下的startup实现。咱们知道main函数以后的代码基本是IDE无关的,而startup倒是因编译器而异。
痞子衡以i.MXRT1010的SDK2.8.2包里的例程为例,先用IAR打开其中的dev_cdc_vcom_freertos例程,找到工程下的startup_MIMXRT1011.s文件,看它的Reset_Handler实现:
__vector_table DCD sfe(CSTACK) DCD Reset_Handler DCD NMI_Handler ;NMI Handler DCD HardFault_Handler ;Hard Fault Handler ; ... __Vectors_End THUMB PUBWEAK Reset_Handler SECTION .text:CODE:REORDER:NOROOT(2) Reset_Handler CPSID I ; Mask interrupts LDR R0, =0xE000ED08 LDR R1, =__vector_table STR R1, [R0] LDR R2, [R1] MSR MSP, R2 LDR R0, =SystemInit BLX R0 CPSIE I ; Unmask interrupts LDR R0, =__iar_program_start BX R0
IAR版本Reset_Handler主要分四步: 重设VTOR、重设SP、执行SystemInit(关看门狗,关Systick,处理Cache)、执行IAR库函数__iar_program_start(data/bss/ramfunc段初始化,跳转到main)。
再用MCUXpresso IDE打开一样的dev_cdc_vcom_freertos例程,找到工程下的startup_mimxrt1011.c文件,看它的ResetISR实现:
extern void _vStackTop(void); __attribute__ ((used, section(".isr_vector"))) void (* const g_pfnVectors[])(void) = { // Core Level - CM7 &_vStackTop, // The initial stack pointer ResetISR, // The reset handler NMI_Handler, // The NMI handler HardFault_Handler, // The hard fault handler // ... }; /* End of g_pfnVectors */ __attribute__ ((section(".after_vectors.reset"))) void ResetISR(void) { __asm volatile ("cpsid i"); SystemInit(); // Copy the data sections from flash to SRAM. unsigned int LoadAddr, ExeAddr, SectionLen; unsigned int *SectionTableAddr; // Load base address of Global Section Table SectionTableAddr = &__data_section_table; // Copy the data sections from flash to SRAM. while (SectionTableAddr < &__data_section_table_end) { LoadAddr = *SectionTableAddr++; ExeAddr = *SectionTableAddr++; SectionLen = *SectionTableAddr++; data_init(LoadAddr, ExeAddr, SectionLen); } // At this point, SectionTableAddr = &__bss_section_table; // Zero fill the bss segment while (SectionTableAddr < &__bss_section_table_end) { ExeAddr = *SectionTableAddr++; SectionLen = *SectionTableAddr++; bss_init(ExeAddr, SectionLen); } __asm volatile ("cpsie i"); // Call the Redlib library, which in turn calls main() __main(); while (1); }
MCUXpresso IDE版本ResetISR主要分三步: 执行SystemInit(重设VTOR,关看门狗,关Systick,处理Cache)、data/bss/ramfunc段初始化、跳转到main。
通过上面对比,看出差别没有?MCUXpresso IDE相比IAR的startup少了一步重设SP的动做。
有了前面三节的分析基础,咱们基本能够得出dev_cdc_vcom_freertos例程异常跑飞的缘由是发生了栈错误。为何会发生栈错误?这是因为MCUXpresso下的startup中没有重设SP操做,因此当IVT中的entry是复位向量时,BootROM跳转到App后依旧延用BootROM中的栈,根据芯片参考手册System Boot章节里的信息,BootROM的栈放在了OCRAM空间(0x20200000 - 0x202057FF),可是dev_cdc_vcom_freertos例程又把RW, ZI段也放进了OCRAM中,所以随着App的运行对栈的利用(函数调用、局部变量定义)有可能与App中的RW, ZI段数据(全局变量)互相破坏,程序发生未知跑飞也在乎料之中。
解决问题的方法是什么?固然是在MCUXpresso IDE的startup流程中加入重设SP操做,保持与IAR startup流程一致。
__attribute__ ((section(".after_vectors.reset"))) void ResetISR(void) { __asm volatile ("cpsid i"); /* 新增SP重设代码 */ __asm volatile ("MSR msp, %0" : : "r" (&_vStackTop) : ); __asm volatile ("MSR psp, %0" : : "r" (&_vStackTop) : ); SystemInit(); // ... }
至此,IVT里的不一样entry设置可能会形成i.MXRT1xxx系列启动App后发生异常跑飞问题的分析解决经验痞子衡便介绍完毕了,掌声在哪里~~~
文章会同时发布到个人 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就能够在手机上第一时间看了哦。