分享

将中断向量表定位到RAM中,从RAM中引导执行中断服务

 刀首木 2021-02-08

最近在解决一个问题,看到一篇代码,将中断向量表定位到RAM中,代码所在的文章在这里:

https://www./community/mcu/32-bit/knowledge-base.entry.html/2017/05/09/emu_e110_-_potential-i2Pn

 

大家都知道,MCU的中断向量表通常是在flash的0x00000000地址,这一点从你编译出来的map文件中很容易看出来

比如我项目中的map文件:

  1. .text 0x00008000 0x2a580
  2. *(.vectors)
  3. .vectors 0x00008000 0xe0 ./CMSIS/EFM32LG/startup_gcc_efm32lg.o
  4. 0x00008000 __Vectors
  5. 0x000080e0 __Vectors_End = .
  6. 0x000000e0 __Vectors_Size = (__Vectors_End - __Vectors)
  7. 0x000080e0 __end__ = .

我这个项目的起始地址设置的是0x00008000上,所以可以看到就是在你的程序的起始地址

现在因为某些原因,我需要将中断向量表定位到RAM中

也就是

SCB->VTOR 的值由原来的0x00008000,指向RAM中新定位的地址

SCB->VTOR:

  1. /**
  2. \brief Structure type to access the System Control Block (SCB).
  3. */
  4. typedef struct
  5. {
  6. __IM uint32_t CPUID; /*!< Offset: 0x000 (R/ ) CPUID Base Register */
  7. __IOM uint32_t ICSR; /*!< Offset: 0x004 (R/W) Interrupt Control and State Register */
  8. __IOM uint32_t VTOR; /*!< Offset: 0x008 (R/W) Vector Table Offset Register */

具体方法:

1:定义一个数组(用来作为向量表,向量表中其实就是各个中断函数的地址,也就是定义一个uint32_t 类型的数组),并且确定数组的大小

要查找矢量表的大小,请在参考手册中查找设备的 IRQ 数量,然后为 Cortex-M 保留项加 16。例如,Giant Gecko 拥有 39 个 IRQ,因此矢量表是 55 个字或 220 个字节。

我用的是Leopard Gecko EFM32LG的MCU,有40个外部中断:

#define EXT_IRQ_COUNT 40 /**< Number of External (NVIC) interrupts */

所以定义#define VECTOR_SIZE (16 + EXT_IRQ_COUNT)

uint32_t vectorTableNew[VECTOR_SIZE]

显然我们要将SCB->VTOR指向这个数组,可以想象,这个数组是在程序的哪个内存区域?全局区?全局区的哪个区?BSS区吧,未初始化的全局变量,都是在BSS区域,当然已经初始化的是在data区域

这里就是我们需要增加的内容__attribute__ ((section (".noinit"), aligned (256) ));

uint32_t vectorTableNew[VECTOR_SIZE] __attribute__ ((section (".noinit"), aligned (256) ));

 

__attribute__注意不是C语言的关键字,他是GCC编译器的编译属性,或者说编译声明,

就是说我在这里立个flag,告诉编译器怎么处理我这段代码

__attribute__((section("seaction name"))

意思是将作用的函数或者变量放入到指定名为"section name"的段

当然__attribute__不仅仅可以为“段”做声明,也可以声明其他属性,比如aligned,

aligned是用来说明做多少个字节对齐的,比如__attribute__((aligned(4))做4字节对齐,__attribute__((aligned(20))做20字节对齐,__attribute__(aligned(256))做256字节对齐

经常用到的aligned((packed))取消字节对齐

比如你经常用到的结构体需要做字节对齐取消

 

struct your_struct{

uint32_t a;

char b;

}__attribute__((packed));

回来看,这个数组的定义:

uint32_t vectorTableNew[VECTOR_SIZE] __attribute__ ((section (".noinit"), aligned (256) ));

将数组放入.noinit段,并且做256字节对齐,注意这里是数组做256字节对齐,说明这个数组占256个字节,而不是数组中的元素每个做256个字节对齐

为什么是256,因为VECTORY_SIZE * 4(中断函数的地址size)

这里会有疑问,是否可以不去声明section名字?

答案是可以的!可以不用去定义section的名字,就针对当前的代码最终用意来说。

 

继续往下:

算了我把整个代码贴出来算了:

  1. #include "em_device.h"
  2. #include "em_ramfunc.h"
  3. #include "string.h"
  4. #include "moveIntVectorToRAM.h"
  5. uint32_t vectorTableNew[VECTOR_SIZE] __attribute__ ((section (".noinit"), aligned (256) ));
  6. volatile uint32_t Flash_Address;//volatile告诉编译器不要做指令优化,每次读取Flash_Address的值都是从它所在的内存地址上去取
  7. const uint32_t* ptrFlashVectorTable = VTOR_FLASH_ADDR;
  8. SL_RAMFUNC_DEFINITION_BEGIN//这个宏是告诉我们下面的这个函数是要放在RAM中
  9. static void CheckFlash_IRQHandler(void)
  10. {
  11. void (*fptr_irqhandler)(void) = NULL;//定义一个函数指针
  12. Flash_Address = ptrFlashVectorTable[VTABLE_EM2_ISR_OFFSET]; // Do a dummy read of the flash. It is important to make sure
  13. // the compiled version of the code retains this call and is not
  14. // optimized out.
  15. Flash_Address = ptrFlashVectorTable[__get_IPSR()]; // Read the actual address for the active interrupt request.
  16. fptr_irqhandler = (void(*)(void))Flash_Address; // Use the original vectorTable located in flash, with IRQ
  17. // offset (IPSR)
  18. (*fptr_irqhandler)();
  19. }
  20. SL_RAMFUNC_DEFINITION_END
  21. /*
  22. memcpy这很简单,是把原来在flash(存放代码的flash)上中断向量表copy到RAM中的vectorTableNew地址,注意一下size
  23. 按道理说这样就可以,直接把SCB->VTOR指向vectorTableNew就可以了。
  24. 中间这个vectorTableNew[VTABLE_EM2_ISR_OFFSET] = (uint32_t)CheckFlash_IRQHandler;是把出EM2之后要马上执行的中断处理函数地址
  25. 赋值给新的中断向量表(数组)对应的元素上,清楚这一点,通俗一点,就是把一个地址赋值给一个地址数组的某个元素
  26. CheckFlash_IRQHandler就是返回的这个地址。
  27. 想象一下,当从EM2唤醒的时候,先要出发唤醒源的中断处理函数,在这里就是触发中断向量表的vectorTableNew[VTABLE_EM2_ISR_OFFSET]这个地址,需要这个地址对应的函数去做执行
  28. 然后就调用的RAM中的函数CheckFlash_IRQHandler,然后看CheckFlash_IRQHandler的执行
  29. 1:先把Flash_Address赋值为ptrFlashVectorTable[VTABLE_EM2_ISR_OFFSET]; 处在flash上的对应中断函数的地址,
  30. 2:再把Flash_Address赋值为ptrFlashVectorTable[__get_IPSR()]; 看后面注释硬是获取真正的中断在向量表中的位置,
  31. 那么看下__get_IPSR())
  32. 我们知道IPSR寄存器是这个中断状态寄存器 IPSR(Interrupt Status Register),它包含了正在执行的中断服务的编号
  33. 所以拿到这个中断编号,就可以通过向量表首地址去定位这个中断服务的位置地址
  34. 当然我们把这个地址赋值给函数指针fptr_irqhandler,然后去执行这个函数,达到我们最终执行中断服务的目的
  35. 整个目的就是当从EM2唤醒的时候从RAM中去引导这个中断服务而不是从默认的Flash去引导
  36. */
  37. void moveInterruptVectorToRam(void)
  38. {
  39. // If we know the wake source from EM2, we can limit the size of the RAM vector table to be the size of the maximum vector location index
  40. memcpy(vectorTableNew, (uint32_t*)VTOR_FLASH_ADDR, sizeof(uint32_t) * (VECTOR_SIZE)); // Copy the flash vector table to RAM
  41. vectorTableNew[VTABLE_EM2_ISR_OFFSET] = (uint32_t)CheckFlash_IRQHandler; // The only location in the RAM based vector table required is at the index
  42. // of the IRQ handler that services the IRQ that exits EM2. If there are more
  43. // IRQs that can exit EM2 they need to be added as well (they are not shown here).
  44. SCB->VTOR = (uint32_t)vectorTableNew;
  45. }
  1. memcpy这很简单,是把原来在flash(存放代码的flash)上中断向量表copy到RAM中的vectorTableNew地址,注意一下size
  2. 按道理说这样就可以,直接把SCB->VTOR指向vectorTableNew就可以了。
  3. 中间这个vectorTableNew[VTABLE_EM2_ISR_OFFSET] = (uint32_t)CheckFlash_IRQHandler;是把出EM2之后要马上执行的中断处理函数地址
  4. 赋值给新的中断向量表(数组)对应的元素上,清楚这一点,通俗一点,就是把一个地址赋值给一个地址数组的某个元素
  5. CheckFlash_IRQHandler就是返回的这个地址。
  6. 想象一下,当从EM2唤醒的时候,先要出发唤醒源的中断处理函数,在这里就是触发中断向量表的vectorTableNew[VTABLE_EM2_ISR_OFFSET]这个地址,需要这个地址对应的函数去做执行
  7. 然后就调用的RAM中的函数CheckFlash_IRQHandler,然后看CheckFlash_IRQHandler的执行
  8. 1:先把Flash_Address赋值为ptrFlashVectorTable[VTABLE_EM2_ISR_OFFSET]; 处在flash上的对应中断函数的地址,
  9. 2:再把Flash_Address赋值为ptrFlashVectorTable[__get_IPSR()]; 看后面注释硬是获取真正的中断在向量表中的位置,
  10. 那么看下__get_IPSR())
  11. 我们知道IPSR寄存器是这个中断状态寄存器 IPSR(Interrupt Status Register),它包含了正在执行的中断服务的编号
  12. 所以拿到这个中断编号,就可以通过向量表首地址去定位这个中断服务的位置地址
  13. 当然我们把这个地址赋值给函数指针fptr_irqhandler,然后去执行这个函数,达到我们最终执行中断服务的目的
  14. 整个目的就是当从EM2唤醒的时候从RAM中去引导这个中断服务而不是从默认的Flash去引导

下面是.h文件

  1. #ifndef _MOVE_INT_VECTOR_TO_RAM_H_
  2. #define _MOVE_INT_VECTOR_TO_RAM_H_
  3. #define VECTOR_SIZE (16 + EXT_IRQ_COUNT)
  4. #define VTOR_FLASH_ADDR (0x8000)
  5. #define VTABLE_EM2_ISR_OFFSET GPIO_ODD_IRQn + 16 // This should equal the highest IRQ that
  6. // will be used to wake from EM2. In this
  7. // example, 17 is the GPIO EVEN IRQ.
  8. void moveInterruptVectorToRam(void);
  9. #endif
  1. /* Simplicity Studio, Atollic and vanilla armgcc */
  2. #define SL_RAMFUNC_DECLARATOR __attribute__ ((section(".ram")))
  3. #define SL_RAMFUNC_DEFINITION_BEGIN SL_RAMFUNC_DECLARATOR
  4. #define SL_RAMFUNC_DEFINITION_END

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多