將中斷向量表定位到RAM中,從RAM中引導執行中斷服務

最近在解決一個問題,看到一篇代碼,將中斷向量表定位到RAM中,代碼所在的文章在這裏:

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

 

大家都知道,MCU的中斷向量表通常是在flash的0x00000000地址,這一點從你編譯出來的map文件中很容易看出來

比如我項目中的map文件:

.text           0x00008000    0x2a580
 *(.vectors)
 .vectors       0x00008000       0xe0 ./CMSIS/EFM32LG/startup_gcc_efm32lg.o
                0x00008000                __Vectors
                0x000080e0                __Vectors_End = .
                0x000000e0                __Vectors_Size = (__Vectors_End - __Vectors)
                0x000080e0                __end__ = .

我這個項目的起始地址設置的是0x00008000上,所以可以看到就是在你的程序的起始地址

現在因爲某些原因,我需要將中斷向量表定位到RAM中

也就是

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

SCB->VTOR:

/**
  \brief  Structure type to access the System Control Block (SCB).
 */
typedef struct
{
  __IM  uint32_t CPUID;                  /*!< Offset: 0x000 (R/ )  CPUID Base Register */
  __IOM uint32_t ICSR;                   /*!< Offset: 0x004 (R/W)  Interrupt Control and State Register */
  __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的名字,就針對當前的代碼最終用意來說。

 

繼續往下:

算了我把整個代碼貼出來算了:

#include "em_device.h"
#include "em_ramfunc.h"
#include "string.h"
#include "moveIntVectorToRAM.h"


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

volatile uint32_t Flash_Address;//volatile告訴編譯器不要做指令優化,每次讀取Flash_Address的值都是從它所在的內存地址上去取
const uint32_t* ptrFlashVectorTable = VTOR_FLASH_ADDR;

SL_RAMFUNC_DEFINITION_BEGIN//這個宏是告訴我們下面的這個函數是要放在RAM中
static void CheckFlash_IRQHandler(void)
{
  void (*fptr_irqhandler)(void) = NULL;//定義一個函數指針

  Flash_Address = ptrFlashVectorTable[VTABLE_EM2_ISR_OFFSET];     // Do a dummy read of the flash.  It is important to make sure
                                                                  // the compiled version of the code retains this call and is not
                                                                  // optimized out.
  Flash_Address = ptrFlashVectorTable[__get_IPSR()];              // Read the actual address for the active interrupt request.
  fptr_irqhandler = (void(*)(void))Flash_Address;                 // Use the original vectorTable located in flash, with IRQ
                                                                  // offset (IPSR)

  (*fptr_irqhandler)();
}
SL_RAMFUNC_DEFINITION_END
/*
 memcpy這很簡單,是把原來在flash(存放代碼的flash)上中斷向量表copy到RAM中的vectorTableNew地址,注意一下size
 按道理說這樣就可以,直接把SCB->VTOR指向vectorTableNew就可以了。
 中間這個vectorTableNew[VTABLE_EM2_ISR_OFFSET] = (uint32_t)CheckFlash_IRQHandler;是把出EM2之後要馬上執行的中斷處理函數地址
 賦值給新的中斷向量表(數組)對應的元素上,清楚這一點,通俗一點,就是把一個地址賦值給一個地址數組的某個元素
 CheckFlash_IRQHandler就是返回的這個地址。
 
 想象一下,當從EM2喚醒的時候,先要出發喚醒源的中斷處理函數,在這裏就是觸發中斷向量表的vectorTableNew[VTABLE_EM2_ISR_OFFSET]這個地址,需要這個地址對應的函數去做執行
 然後就調用的RAM中的函數CheckFlash_IRQHandler,然後看CheckFlash_IRQHandler的執行
 1:先把Flash_Address賦值爲ptrFlashVectorTable[VTABLE_EM2_ISR_OFFSET]; 處在flash上的對應中斷函數的地址,
 2:再把Flash_Address賦值爲ptrFlashVectorTable[__get_IPSR()]; 看後面註釋硬是獲取真正的中斷在向量表中的位置,
 那麼看下__get_IPSR())
 我們知道IPSR寄存器是這個中斷狀態寄存器 IPSR(Interrupt Status Register),它包含了正在執行的中斷服務的編號
 所以拿到這個中斷編號,就可以通過向量表首地址去定位這個中斷服務的位置地址
 
 當然我們把這個地址賦值給函數指針fptr_irqhandler,然後去執行這個函數,達到我們最終執行中斷服務的目的
 
 整個目的就是當從EM2喚醒的時候從RAM中去引導這個中斷服務而不是從默認的Flash去引導
*/
void moveInterruptVectorToRam(void)
{
  // 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
  memcpy(vectorTableNew, (uint32_t*)VTOR_FLASH_ADDR, sizeof(uint32_t) * (VECTOR_SIZE));  // Copy the flash vector table to RAM
  vectorTableNew[VTABLE_EM2_ISR_OFFSET] = (uint32_t)CheckFlash_IRQHandler;   // The only location in the RAM based vector table required is at the index 
                                                                             // of the IRQ handler that services the IRQ that exits EM2.  If there are more
										// IRQs that can exit EM2 they need to be added as well (they are not shown here).
  SCB->VTOR = (uint32_t)vectorTableNew; 
}
 memcpy這很簡單,是把原來在flash(存放代碼的flash)上中斷向量表copy到RAM中的vectorTableNew地址,注意一下size
 按道理說這樣就可以,直接把SCB->VTOR指向vectorTableNew就可以了。
 中間這個vectorTableNew[VTABLE_EM2_ISR_OFFSET] = (uint32_t)CheckFlash_IRQHandler;是把出EM2之後要馬上執行的中斷處理函數地址
 賦值給新的中斷向量表(數組)對應的元素上,清楚這一點,通俗一點,就是把一個地址賦值給一個地址數組的某個元素
 CheckFlash_IRQHandler就是返回的這個地址。
 
 想象一下,當從EM2喚醒的時候,先要出發喚醒源的中斷處理函數,在這裏就是觸發中斷向量表的vectorTableNew[VTABLE_EM2_ISR_OFFSET]這個地址,需要這個地址對應的函數去做執行
 然後就調用的RAM中的函數CheckFlash_IRQHandler,然後看CheckFlash_IRQHandler的執行
 1:先把Flash_Address賦值爲ptrFlashVectorTable[VTABLE_EM2_ISR_OFFSET]; 處在flash上的對應中斷函數的地址,
 2:再把Flash_Address賦值爲ptrFlashVectorTable[__get_IPSR()]; 看後面註釋硬是獲取真正的中斷在向量表中的位置,
 那麼看下__get_IPSR())
 我們知道IPSR寄存器是這個中斷狀態寄存器 IPSR(Interrupt Status Register),它包含了正在執行的中斷服務的編號
 所以拿到這個中斷編號,就可以通過向量表首地址去定位這個中斷服務的位置地址
 
 當然我們把這個地址賦值給函數指針fptr_irqhandler,然後去執行這個函數,達到我們最終執行中斷服務的目的
 
 整個目的就是當從EM2喚醒的時候從RAM中去引導這個中斷服務而不是從默認的Flash去引導

下面是.h文件

#ifndef _MOVE_INT_VECTOR_TO_RAM_H_
#define _MOVE_INT_VECTOR_TO_RAM_H_

#define VECTOR_SIZE (16 + EXT_IRQ_COUNT)

#define VTOR_FLASH_ADDR (0x8000)

#define VTABLE_EM2_ISR_OFFSET   GPIO_ODD_IRQn + 16 // This should equal the highest IRQ that
                                   // will be used to wake from EM2.  In this
                                   // example, 17 is the GPIO EVEN IRQ.

void moveInterruptVectorToRam(void);

#endif

/* Simplicity Studio, Atollic and vanilla armgcc */
#define SL_RAMFUNC_DECLARATOR          __attribute__ ((section(".ram")))
#define SL_RAMFUNC_DEFINITION_BEGIN    SL_RAMFUNC_DECLARATOR
#define SL_RAMFUNC_DEFINITION_END

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章