最近在解決一個問題,看到一篇代碼,將中斷向量表定位到RAM中,代碼所在的文章在這裏:
大家都知道,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