• 292查看
  • 0回复

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

[复制链接]


该用户从未签到

发表于 9-3-2024 22:03:09 | 显示全部楼层 |阅读模式

汽车零部件采购、销售通信录       填写你的培训需求,我们帮你找      招募汽车专业培训老师


内容提要
引言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的利用效率。
浅谈嵌入式MCU软件开发之将应用程序代码重定向到系统栈(stack)上运行的实现原理和方法详解w1.jpg

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)操作:
浅谈嵌入式MCU软件开发之将应用程序代码重定向到系统栈(stack)上运行的实现原理和方法详解w2.jpg

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

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原有的值,是随机的。

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

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)))
typedefuint32_t(*ram_fuc)(uint32_t, uint32_t );
typedefstruct{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_tALIGN_8B Math_Func(uint32_t a, uint32_t b){uint32_t result = 0;  result = (a *b + 10) * (a + b) *(a - b);
return result;}intmain(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);
浅谈嵌入式MCU软件开发之将应用程序代码重定向到系统栈(stack)上运行的实现原理和方法详解w5.jpg

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。
以上就是今天想要给大家分享的知识点,希望对大家有所帮助。

快速发帖

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|手机版|小黑屋|Archiver|汽车工程师之家 ( 渝ICP备18012993号-1 )

GMT+8, 1-2-2025 07:55 , Processed in 0.301922 second(s), 31 queries .

Powered by Discuz! X3.5

© 2001-2013 Comsenz Inc.