浅谈嵌入式MCU软件开发之将应用程序代码重定向到系统栈(stack)上运行的实现原理和方法详解

内容提要

引言
1. 将应用程序代码重定向到stack运行的实现原理
1.1 C语言的堆栈(stack)工作原理和特性
1.2 C语言局部变量分配和初始化
1.3 基于C语言的结构体局部变量初始化特性实现重定向代码到stack运行的原理和方法
2. 将应用程序代码重定向到stack运行的实现实例和实现要点
2.1 基于GNU工具链(S32DS IDE)的ARM Cortex-M4F内核(S32K144)实现要点
2.2 基于CodeWarrior工具链(CodeWarrior 10.x/11.x IDE)的Power e200z内核(MPC5644A)实现要点
总结
引言
之前我写过一些文章介绍过在不同CPU架构的MCU在不同的软件开发工具链/IDE中实现代码重定向(relocate, 有时也称作remap,重映射)到RAM中运行,以避免代码单一分区(partition)Flash存储器上存储的Flash驱动程序或者片上总线矩阵(CrossBar)配置等关键代码执行时造成的Flash读写访问(RWW--Read While Write)冲突,感兴趣的读者可以点击下方文章标题,直接跳转阅读:
浅谈嵌入式MCU软件开发之S32K1xx系列MCU启动过程及重映射代码到RAM中运行方法详解》;
CodeWarrior IDE使用tips之prm链接文件详解(自定义存储器分区以及自定义RAM数据初始化与在RAM中运行函数)》;
《 CodeWarrior IDE使用Tips之如何通过prm文件指定汇编代码函数、全局变量和常量的储存地址》;
CodeWarrior IDE使用Tips之Qorivva MPC56xx新建应用工程选项、调试高级选项及下载过程控脚本详解》;
之前介绍的代码重定向实现原理和方法大致如下:
① 修改应用工程链接文件,预留特定地址区域的SRAM,并将用户自定义代码段放置到该预留SRAM地址区域,添加必要的链接信息生成符号(symbols);
② 在应用代码中,将需要重定向的应用程序代码/功能函数,通过软件开发工具链的特殊原语(#progma CODE_SEG <customized_func>)或者代码段属性指定(__attribute__((section('.code_ram'))) <customized_func> )等方法指定关键代码链接到用户自定义代码段;
③ 修改启动代码,利用链接生成的自定义代码段重定向信息(源和目的存储器起始地址和长度信息)将需要重定向的代码拷贝到SRAM中;
④ 调用原函数名和参数执行;
很明显,以上实现方法存在以下缺点:
① 要求工程师对目标MCU软件开发所使用的工具链/IDE的链接文件和启动代码以及存储器分布情况十分熟悉,并能够更加应用功能程序开发需求做必要的调整和修改;
② 需要占用应用程序编译结果存储Flash相同大小的SRAM地址空间,受限于目标MCU的SRAM大小。
若需要重定向的代码较多,对于SRAM size小的MCU来说,将很难实现,因为MCU的SRAM还需要分别用于存储应用程序C代码运行必须的全局变量(.data和.bss/.common段变量)和系统堆栈(heap和stack)。
那么有没有更好的办法实现应用代码重定向运行的问题呢?
答案是肯定的,那就是利用C语言局部变量分配和初始化的原理,将应用程序重定向到系统堆栈(stack)上执行。
下面我们就一起来看看具体的实现原理和方法。
1. 将应用程序代码重定向到stack运行的实现原理
1.1 C语言的堆栈(stack)工作原理和特性
C语言是目前嵌入式MCU应用软件程序开发最常用的编程语言,因为其不但功能强大,而且简单高效,易于学习和掌握。
C语言相对于汇编语言的一大特点就是,其运行时CPU内核能够更加工具链编译链接的结果自动分配和管理内核堆栈(stack),用于函数调用和内核异常/外设中断响应时的运行时上下文现场(context)保护,参数传递和临时变量分配。从而大大提高了代码的执行效率和SRAM的利用效率。
C语言的堆栈(stack)具有如下特点:
1. CPU内核的堆栈指针寄存器(SP-Stack Pointer)始终指向栈顶(stack top),所有的进栈(pop)和出栈(push)由内核自动管理,用户只需要在启动代码中初始化堆栈(将栈顶地址赋值给CPU内核的堆栈指针寄存器);
2. 栈(stack)内的数据都是先进后出或者后进先出(LIFO--Last In First Out);
3. 栈(stack)的生长方向由CPU内核大小端模式决定:
  • 小端内核, 栈(stack)向下(低地址)生长,比如ARM Cotrex-M系列CPU内核;

  • 大端内核, 栈(stack)向上(高地址)生长,比如PowerPC e200z系列CPU内核;

如下为向上生长的大端内核的stack压栈(push)和出栈(pop)示意图:
压栈(push)操作:
出栈(pop)操作:
4. 栈(stack)必须指向一段可读可写(RW)属性的RAM存储器,可以是MCU的SRAM或者内核的TCM(紧耦合存储器),不能是Flash或者EEPROM;因此其访问速度/效率通常是MCU片内存储器中最高的,零等待的。
Tips: 通常讲的堆栈,其实包含堆(heap)和栈(stack)两个不同的内存管理概念/技术,本文所讲堆栈,若无特别说明,均代表栈(stack);
Tips: stack是CPU内核自行管理的私有存储空间,在多核架构中其属性为non-shareable,可以放心使能其cache功能,以提高内核性能,不必担心stack的数据一致性问题。
Tips:  关于C语言的堆栈使用和注意事项,请参考如下公众号文章(点击文章标题即可直接跳转阅读):
《 浅谈嵌入式 MCU 软件开发之应用工程的堆与栈》;
1.2 C语言局部变量分配和初始化
C语言函数的局部变量,也称作临时变量,将被自动分配到系统stack上(若局部变量比较小,且有可用CPU内核通用寄存器,按照相应的嵌入式应用程序二进制接口标准(EABI-Embedded Application Binary Interface),该局部变量也可能被直接分配到内核通用寄存器,而非stack),从而实现函数调用时实现临时分配和使用,退出函数调用即销毁的目的。
按照ANSI-C的规定:
  • 有初始化值局部变量的初始化由C语言调用内置库函数(编译和链接过程决定),比如memset()自动实现;

  • 无初始化值的局部变量,其初始值为stack原有的值,是随机的。

1.3 基于C语言的结构体局部变量初始化特性实现重定向代码到stack运行的原理和方法
①. 定义需要重定向的目标函数相同原型(prototype)的函数指针类型;
②. 定义足够存储目标函数代码大小的RAM存储结构体类型;
③. 定义一个RAM存储结构体类型的局部变量,并将目标函数转换为RAM存储结构体类型并赋值初始化给新定义的RAM存储结构体类型局部变量,以实现目标代码的自动拷贝到stack;
④. 将新定义的RAM存储结构体类型局部变量,强制转换为目标函数原型(prototype)的函数指针类型,利用函数指针完成目标函数在stack上的调用和执行;
2. 将应用程序代码重定向到stack运行的实现实例和实现要点
下面以S32K144和Qorivva MPC5644A为例,介绍使用GNU工具链(S32DS IDE)和CodeWarrior IDE实现ARM Cortex-M4F内核和Power e200z4内核MCU的C语言应用程序代码重定向到stack上运行的实现要点,供大家参考学习。
2.1 基于GNU工具链(S32DS IDE)的ARM Cortex-M4F内核(S32K144)实现要点
以下代码为基于GNU工具链(S32DS IDE)的ARM Cortex-M4F内核的S32K144 MCU应用工程中,将应用功能函数Math_Func()重定向到栈上运行的具体实现代码:
    /* define a macro to use attribute to achieve 8 byte alignment */#define ALIGN_8B __attribute__((aligned(8)))
    typedef uint32_t (*ram_fuc)(uint32_t, uint32_t );
    typedef struct{ uint8_t code[0x256]; /* Structure required to copy code to ram memory */ /* Size of this structure needs to be at least (but best) the size of the FnCmdInRam_ */} FnCmdInRamStruct;
    uint32_t ALIGN_8B Math_Func(uint32_t a, uint32_t b){ uint32_t result = 0; result = (a *b 10) * (a b) *(a - b);
    return result;}int main(void){ /* Write your code here */
    uint32_t aa = 100; uint32_t bb = 36;
    uint32_t cc = 0;
    /* Create a copy of the function codes in RAM routine on stack */ FnCmdInRamStruct ALIGN_8B FnCmdInRam = *(FnCmdInRamStruct *)((uint32_t)Math_Func-1);
    /* run the function via a function pointer */ cc = ((ram_fuc)((uint32_t)&FnCmdInRam 1))(aa,bb);
    if(cc>100) { aa = Math_Func(bb,cc); } else { aa = Math_Func(cc,cc); }
    for(;;) { if(exit_code != 0) { break; } } return exit_code;}
    其实现要点如下:
    ① 通过GNU的__attribute__((aligned(8)))属性设置,保证重定向目标函数和结构体局部变量在栈上分配时,地址按照8字节对齐,以满足ARM Cortex-M内核对栈操作的地址对齐要求;
    ② 对结构体结构体赋值时,需要将目标函数地址减1,以保证目标函数代码能够被完整拷贝到stack上:
      /* Create a copy of the function codes in RAM routine on stack */FnCmdInRamStruct ALIGN_8B FnCmdInRam = *(FnCmdInRamStruct *)((uint32_t)Math_Func-1);
      ③ 使用函数指针调用目标函数时,需要将结构体临时变量地址加1,以保证BLX指令调用目标函数时,其地址最低位(LSB)为1,从而保持内核Thumb状态:
        /* run the function via a function pointer */cc = ((ram_fuc)((uint32_t)&FnCmdInRam 1))(aa,bb);
        2.2 基于CodeWarrior工具链(CodeWarrior 10.x/11.x IDE)的Power e200z内核(MPC5644A)实现要点
        以下代码是Qorrivva MPC5644A的CodeWarrior 10.x/11.x IDE应用工程中,将Flash控制器的指令和数据预取功能(相当于MCU的二级缓存)关闭和恢复/使能配置API函数重定向到stack中运行的具体实现代码:
          #include 'MPC5644A.h'typedef unsigned char BOOL;typedef signed char INT8;typedef unsigned char UINT8;typedef volatile signed char VINT8;typedef volatile unsigned char VUINT8;typedef signed short INT16;typedef unsigned short UINT16;typedef volatile signed short VINT16;typedef volatile unsigned short VUINT16;typedef signed long INT32;typedef unsigned long UINT32;typedef volatile signed long VINT32;typedef volatile unsigned long VUINT32;#define FLASH_A_FMC         0xC3F88000#define FLASH_B_FMC         0xC3F8C000#define FLASH_PFB_CR        0x0000001CU /* PFBIU Configuration Register for port 0 */#define FLASH_PFB_CR_BFEN   0x00000001U /* PFBIU Line Read Buffers Enable *//* Read and write macros */#define WRITE8(address, value)      (*(VUINT8*)(address) = (value))#define READ8(address)              ((UINT8)(*(VUINT8*)(address)))#define SET8(address, value)        (*(VUINT8*)(address) |= (value))#define CLEAR8(address, value)      (*(VUINT8*)(address) &= ~(value))#define WRITE16(address, value)     (*(VUINT16*)(address) = (value))#define READ16(address)             ((UINT16)(*(VUINT16*)(address)))#define SET16(address, value)       (*(VUINT16*)(address) |= (value))#define CLEAR16(address, value)     (*(VUINT16*)(address) &= ~(value))#define WRITE32(address, value)     (*(VUINT32*)(address) = (value))#define READ32(address)             ((UINT32)(*(VUINT32*)(address)))#define SET32(address, value)       (*(VUINT32*)(address) |= (value))#define CLEAR32(address, value)     (*(VUINT32*)(address) &= ~(value))/***************************************************************                          Disable Flash Cache                  ****************************************************************/void DisableFlashControllerCache(UINT32 *origin_pflash_pfcr1,                                 UINT32 *origin_pflash_pfcr2);/******************************************************************               Restore configuration register of FCM              *******************************************************************/void RestoreFlashControllerCache(UINT32 pflash_pfcr1,                                 UINT32 pflash_pfcr2);/* function pointer definition of the relocated function prototype */typedef UINT32 (*ram_fuc_DisableFlashControllerCache)(UINT32 *, UINT32 * );typedef UINT32 (*ram_fuc_RestoreFlashControllerCache)(UINT32, UINT32 );typedef struct{  UINT8 code[0x256];             /* Structure required to copy code to ram memory */  /* Size of this structure needs to be at least (but best) the size of the FnCmdInRam_ */} FnCmdInRamStruct;/***************************************************************                          Disable Flash Cache                  ****************************************************************/void DisableFlashControllerCache(UINT32 *origin_pflash_pfcr1,                                 UINT32 *origin_pflash_pfcr2){  /* disable the CPU core global interrupt to avoid Flash access during the configuration */  asm('wrteei 0');       /* Read the values of PFB_CR*/    *origin_pflash_pfcr1 = READ32(FLASH_A_FMC   FLASH_PFB_CR);    *origin_pflash_pfcr2 = READ32(FLASH_B_FMC   FLASH_PFB_CR);    /* Disable Caches */    CLEAR32(FLASH_A_FMC   FLASH_PFB_CR, FLASH_PFB_CR_BFEN);    CLEAR32(FLASH_B_FMC   FLASH_PFB_CR, FLASH_PFB_CR_BFEN);        /* re-enable the CPU core global interrupt */    asm('wrteei 1'); }/******************************************************************               Restore configuration register of FCM              *******************************************************************/void RestoreFlashControllerCache(UINT32 pflash_pfcr1,                                 UINT32 pflash_pfcr2){  /* disable the CPU core global interrupt to avoid Flash access during the configuration */  asm('wrteei 0');       WRITE32(FLASH_A_FMC   FLASH_PFB_CR, pflash_pfcr1);    WRITE32(FLASH_B_FMC   FLASH_PFB_CR, pflash_pfcr2);        /* re-enable the CPU core global interrupt */    asm('wrteei 1'); }/*****************************************************************Main function******************************************************************/void main(void){    UINT32 pflash_pfcr1, pflash_pfcr2;        /* structure used for stack RAM code run */     FnCmdInRamStruct FnCmdInRam;        /* Create a copy of the function codes in RAM routine on stack */    FnCmdInRam = *(FnCmdInRamStruct *)((UINT32)DisableFlashControllerCache);     ((ram_fuc_DisableFlashControllerCache)(&FnCmdInRam))(&pflash_pfcr1,&pflash_pfcr2);        /* Create a copy of the function codes in RAM routine on stack */    FnCmdInRam = *(FnCmdInRamStruct *)((UINT32)RestoreFlashControllerCache);     ((ram_fuc_RestoreFlashControllerCache)(&FnCmdInRam))(pflash_pfcr1,pflash_pfcr2);    while(1)    {        ;    }}
          其实现要点如下:
          ① 不同的目标函数,可以使用同一个结构体局部变量以介绍系统stack,但是需要定义和使用对应的函数原型函数指针类型进行重定向调用;
          ② 使用的结构体临时变量要足够存储目标函数,且应用工程系统堆栈要设置足够大(通过应用工程的链接文件进行配置)
          总结
          本文详细介绍了如何利用C语言对结构体类型局部变量的初始化机制和函数指针,实现将存储在Flash上的应用程序代码重定向(自动拷贝)到系统栈(stack)运行具体方法和步骤。
          该方法相较于传统的重定向实现方法有如下优势:
          ①能够更加高效的利用SRAM,大大节省了代码重定向到RAM中运行所需的SRAM存储器尺寸。从而,本方法可以适用于SRAM存储器尺寸较小的MCU平台;
          ② 不需要修改应用工程的链接文件和启动代码;
          相应的缺点如下:
          ① 需要占用额外的系统栈,必须确保应用工程的stack设置足够大,否则容易造成堆栈溢出;
          ② 每次调用时,都需要执行拷贝过程,会消耗一定的CPU loading。
          (0)

          相关推荐