前面的按鍵實驗是通過死循環(huán)一直讀取按鈕電平值來判斷是否有按下按鈕,接下來將使用另外一個更優(yōu)雅的方式實驗按鍵按下功能-中斷。
CPU在正常處理指令的時候會遇到外設(shè)打斷當前執(zhí)行邏輯,我們稱為異常中斷。一系列中斷處理集中在一起管理,我們稱為異常中斷向量表。
中斷向量表
Coretex-A系列的中斷向量表就是存放在程序起始位置(鏈接起始地址)的一組由4字節(jié)組成的一組數(shù)據(jù),Coretex-A 32位處理器每一條指令長度就是4個字節(jié),所以本質(zhì)上這個中斷向量表是一組固定地址的指令。Coretext-A系統(tǒng)CPU總共支持8個中斷:
這8個中斷里面需要特別注意也是需要開發(fā)的主要是復位中斷
與IRQ中斷
。復位中斷
在上電或者按下Reset按鈕后硬件加載程序同時PC
寄存器位置重置為0x0或者鏈接起始地址時觸發(fā),IRQ中斷
則是外設(shè)觸發(fā)。每一個中斷發(fā)生時PC
寄存器會被設(shè)置成一個固定的地址,而這個地址則對應(yīng)中斷向量表中一條指令。
中斷向量表添加到匯編最開始的位置:
/* 從鏈接起始地址開始,8條4字節(jié)的指令組成了ARM的中斷向量表 */
/* 中斷向量表放在最開始的位置,每一條指令對應(yīng)了具體的中斷處理 */
/* 當發(fā)生對應(yīng)中斷時,硬件會把對應(yīng)的地址設(shè)置到pc寄存器,從而執(zhí)行對應(yīng)的中斷服務(wù)函數(shù) */
ldr pc, =Reset_Handler /* 0x00: 復位中斷 */
ldr pc, =Undefine_Instruction_Handler /* 0x04: 未定義中斷指令 */
ldr pc, =Software_Interrupt_Handler /* 0x08: 軟中斷, SVC特權(quán)模式 */
ldr pc, =Prefetch_Abort_Handler /* 0x0c: 指令預(yù)取中止中斷 */
ldr pc, =Data_Abort_Handler /* 0x10: 數(shù)據(jù)訪問中止中斷 */
ldr pc, =Not_Used_Handler /* 0x14: 未使用的中斷 */
ldr pc, =IRQ_Handler /* 0x18: 外部設(shè)備中斷 */
ldr pc, =FIQ_Handler /* 0x1c: 快速中斷 */
復位中斷服務(wù)函數(shù)
上電后第一個要觸發(fā)的則是復位中斷
,通過向量表中定義的指令可以將程序切換到Reset_Handler
處開始執(zhí)行
- 關(guān)閉IRQ
- 關(guān)閉I,D Cache,以及MMU
- 設(shè)置中斷的起始地址,即設(shè)置成鏈接起始地址(因為程序是從鏈接起始地址開始運行的)
- 設(shè)置IRQ,SVC以及SYS模式下C語言的運行環(huán)境(C語言的SP指針棧頂)
- 打開IRQ
- 調(diào)轉(zhuǎn)到C語言的main函數(shù)開始運行
cpsid i /* 關(guān)閉IRQ, 此時IRQ還沒有配置完成,所以關(guān)閉*/
/*
在設(shè)備上電啟動時,執(zhí)行的代碼訪問的外設(shè)都是實際地址,
mmu與cache此時的意義不大,
這個時候為了防止cache與mmu可能導致的問題會先將mmu與cache關(guān)閉
*/
/* CP15: SCTLR */
/* 關(guān)閉 I-Cache, D-Cache, MMU */
MRC p15, 0, r0, c1, c0, 0 /* 將SCTLR寄存器讀取到r0寄存器 */
bic r0, r0, #(1 << 0) /* 關(guān)閉MMU */
bic r0, r0, #(1 << 1) /* 關(guān)閉對齊 */
bic r0, r0, #(1 << 11) /* 關(guān)閉分支預(yù)測 */
bic r0, r0, #(1 << 12) /* 關(guān)閉i-cache */
bic r0, r0, #(1 << 2) /* 關(guān)閉d-cache */
MCR p15, 0, r0, c1, c0, 0 /* 將r0寄存器數(shù)據(jù)寫入到SCTLR寄存器 */
/* 設(shè)置中斷向量偏移,在發(fā)生中斷之前設(shè)置即可,也可以在C語言中設(shè)置 */
ldr r0, =0x87800000 /* 將0x87800000這個立即數(shù)寫入到 r0寄存器, 也就是鏈接起始地址*/
dsb /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進隔斷,保證前后讀取指令都是正常的地址 */
isb /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進隔斷,保證前后讀取指令都是正常的地址 */
MCR p15, 0, r0, c12, c0, 0 /* 將r0的數(shù)據(jù)寫入到VBAR寄存器中,表示向量偏移地址是0x87800000 */
dsb /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進隔斷,保證前后讀取指令都是正常的地址 */
isb /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進隔斷,保證前后讀取指令都是正常的地址 */
/* 設(shè)置不同模式下的sp指針,每一個模式的sp對應(yīng)不同的物理地址,當進入不同工作模式時C語言會在不同的物理sp指針指向的棧內(nèi)存上工作 */
/* 進入IRQ模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 將r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x12 /* r0或上0x12,表示使用IRQ模式 */
msr cpsr, r0 /* 將r0 的數(shù)據(jù)寫入到cpsr_c中 */
ldr sp, =0x80600000 /* 設(shè)置棧指針 */
/* 進入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 將r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x1f /* r0或上0x1f,表示使用SYS模式 */
msr cpsr, r0 /* 將r0 的數(shù)據(jù)寫入到cpsr_c中 */
ldr sp, =0x80400000 /* 設(shè)置棧指針 */
/* 進入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 將r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
msr cpsr, r0 /* 將r0 的數(shù)據(jù)寫入到cpsr_c中 */
ldr sp, =0x80200000 /* 設(shè)置棧指針 */
cpsie i /* 打開IRQ */
b main /* 跳轉(zhuǎn)到C語言main函數(shù) */
IRQ外設(shè)中斷服務(wù)函數(shù)
當一個外設(shè)觸發(fā)中斷(比如按鍵按下后)會執(zhí)行IRQ_Handler
函數(shù)。
- 中斷發(fā)生是首先保護現(xiàn)場(lr, r0-r12寄存器, 保存spsr寄存器數(shù)據(jù))
- 讀取GIC寄存器組的起始地址
- 通過對GIC寄存器組基地址偏移得到CPU Interface寄存器組
- 通過對CPU Interface基地址進行偏移得到GICC_IAR寄存器,它保存了觸發(fā)中斷的CPU號與中斷號
- 讀取中斷號(目前只有一個CPU內(nèi)核,可以不管CPU號)放入r0寄存器,調(diào)用對應(yīng)的C語言函數(shù)執(zhí)行中斷
- 在執(zhí)行中斷前,首先需要將模式切換到SVC,這樣在執(zhí)行中斷的時候可以允許新的IRQ中斷觸發(fā)
- 執(zhí)行C語言的中斷邏輯后切換到IRQ模式,繼續(xù)完成中斷收尾工作
- 恢復spsr寄存器數(shù)據(jù)
- 恢復中斷執(zhí)行前的現(xiàn)場(lr, r0-r12)
- 將lr地址減4字節(jié)再給到pc寄存器,恢復中斷前的執(zhí)行指令
/*
中斷發(fā)生時, IRQ模式下的lr(LR_svc物理)寄存器保存中斷時刻的PC寄存器
通過使用push命令將lr的值壓入棧,這樣的目的是為了在執(zhí)行完當前中斷服務(wù)函數(shù)
后可以順利的返回到中斷前的執(zhí)行位置,因為在執(zhí)行中斷服務(wù)函數(shù)的時候lr里面的值可能發(fā)生變化
比如: 在內(nèi)部使用了blx調(diào)用其它函數(shù),新的IRQ中斷進入
*/
push {lr}
/*
保存中斷發(fā)生時的執(zhí)行現(xiàn)場(r0-r12)
從User/Sys模式切換到IRQ模式,r0-r12寄存器是通用的,所以需要將這些寄存器都壓入棧保存起來,
由于在執(zhí)行IRQ中斷函數(shù)時模式已經(jīng)切換,此時的sp指針已經(jīng)是IRQ模式下的棧地址了,所以r0-r12保存到了
IRQ對應(yīng)的??臻g中,恢復現(xiàn)場的時候只需要入棧即可
*/
push {r0-r12}
// push {r0-r3, r12}
/*
將spsr(SPSR_irq)寄存器的值壓入棧,spsr保存了中斷發(fā)生時的cpsr寄存器的值,
中斷執(zhí)行完成之后需要恢復
*/
mrs r0, spsr
push {r0}
/* GIC寄存器組的基地址(起始地址,通過起始地址可以訪問得所有的GIC寄存器) */
/* 將GIC基地址讀取到r1寄存器中 */
MRC p15, 4, r1, c15, c0, 0 // Read Configuration Base Address Register
/* 0x2000 - 0x3FFF 是GIC中CPU Interface的范圍 */
/* 將r1中保存的GIC基地址偏移0x2000再保存到r1中 */
/* 此時r1中保存的是CPU Interface的基地址 */
add r1, r1, #0x2000
/*
r1(CPU Interface基地址)偏移0x0c得到GICC_IAR寄存器地址,
將GICC_IAR寄存器的值讀取到r0中,
GICC_IAR保存了IRQ中斷的中斷號與CPU號(多核時使用),
通過中斷號即可明確具體的中斷來源并對中斷進行響應(yīng)
*/
ldr r0, [r1, #0x0c]
/*
由于要進入到SVC模式了,需要將r0, r1兩個通用寄存器的數(shù)據(jù)保存到棧里,
防止在SVC模式下后r0,r1數(shù)據(jù)丟失
此時r0, r1保存到的是IRQ模式下的棧空間,
*/
push {r0, r1}
/*
將CPSR寄存器的M[4:0]值改成10011, 讓CPU進入到SVC模式,
進行SVC模式之后,當我們處理當前中斷時,
系統(tǒng)可以再次響應(yīng)IRQ中斷
*/
cps #0x13 // 進入到SVC
/*
進入到svc模式后先將lr的數(shù)據(jù)壓入棧,執(zhí)行完后再恢復
因為接下來要使用blx調(diào)用C語言函數(shù),會改變lr寄存器的數(shù)據(jù)
*/
push {lr}
ldr r2, =system_irq_handler // 將C語言寫的中斷服務(wù)函數(shù)的地址加載到r2寄存器
blx r2 // 調(diào)用C語言的中斷處理函數(shù), r0為函數(shù)參數(shù)
pop {lr} // 調(diào)用完具體中斷處理函數(shù)后,lr恢復
cps #0x12 // 進入到IRQ,執(zhí)行完中斷服務(wù)函數(shù)后進入IRQ不能再次響應(yīng)IRQ中斷,直到當前的IRQ中斷完成
pop {r0, r1} // 恢復IRQ模式下r0,r1寄存器的數(shù)據(jù)
/*
此時r0保存的是GICC_IAR寄存器的數(shù)據(jù),
將GICC_IAR的數(shù)據(jù)寫入到GICC_EOIR寄存器,表示當前IRQ中斷處理完成
*/
str r0, [r1, #0x10]
pop {r0} // 將棧頂?shù)臄?shù)據(jù)(此時棧頂保存的是spsr寄存器的值)出棧到r0寄存器
/// spsr_cxsf其中(cxsf表示4個不同的8bit位數(shù)據(jù),后續(xù)表示此次命令影響的數(shù)據(jù)位), spsr_cxsf等于spsr
msr spsr_cxsf, r0 // 恢復spsr寄存器數(shù)據(jù)
pop {r0-r12} // 恢復r0-r12寄存器的數(shù)據(jù)
pop {lr} // 恢復lr寄存器的數(shù)據(jù)
subs pc, lr, #4 // 將lr - 4字節(jié)賦值給lc, 恢復中斷前的執(zhí)行命令繼續(xù)執(zhí)行
IRQ中斷服務(wù)通用邏輯處理函數(shù)
我們需要編寫一個通用的中斷處理函數(shù),從參數(shù)(r0寄存器中的GICC_IAR寄存器的數(shù)據(jù))中提取中斷號,根據(jù)對應(yīng)的中斷號再調(diào)用注冊進來的具體的中斷函數(shù),比如: 按鍵中斷函數(shù)
void system_irq_handler(unsigned int gicciar)
{
uint32_t irqNum = gicciar & 0x3FF;
if (irqNum >= NUMBER_OF_INT_VECTORS)
return;
Interrupt_Irq_Count++;
Interrupt_Irq_Data iid = _irqInterruptTables[irqNum];
iid.handler(irqNum, iid.context);
Interrupt_Irq_Count--;
}
外設(shè)中斷驅(qū)動
- GPIO復用以及配置電氣屬性
- 配置GPIO的輸入與輸出
- 初始化GPIO中斷
void GPIO_Init_Interrupt(GPIO_Type *base, int pin, GPIO_INTERRUPT_MODE mode)
{
/// 首先將GPIO的edge_sel寄存器對應(yīng)pin位清0,如果為1則會使ICR寄存器的配置無效
base->EDGE_SEL &= ~(1 << pin);
/// 對應(yīng)ICR的索引(按2位為一個單元)
int icrOffset = pin;
/// 具體的icr寄存器地址
__IO uint32_t *p_icr = NULL;
if (pin < 16)
{
p_icr = &(base->ICR1);
}
else
{
p_icr = &(base->ICR2);
icrOffset -= 16;
}
switch (mode)
{
case GPIO_INTERRUPT_MODE_NO_INTERRUPT:
break;
case GPIO_INTERRUPT_MODE_LOW:
*p_icr &= ~(3 << (2 * icrOffset));
break;
case GPIO_INTERRUPT_MODE_HIGH:
*p_icr &= ~(3 << (2 * icrOffset));
*p_icr |= 1 << (2 * icrOffset);
break;
case GPIO_INTERRUPT_MODE_RISING_EDGE:
*p_icr &= ~(3 << (2 * icrOffset));
*p_icr |= 2 << (2 * icrOffset);
break;
case GPIO_INTERRUPT_MODE_FALLING_EDGE:
*p_icr &= ~(3 << (2 * icrOffset));
*p_icr |= 3 << (2 * icrOffset);
break;
case GPIO_INTERRUPT_MODE_RISING_AND_RALLING_EDGE:
*p_icr &= ~(3 << (2 * icrOffset));
base->EDGE_SEL |= (1 << pin);
break;
}
}
- I.MX6ULL的GIC使能對應(yīng)中斷ID的中斷
/// 使用GPIO1的IO18對應(yīng)的IRQ中斷ID(GPIO1_Combined_16_31_IRQn)
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
- 注冊對應(yīng)中斷ID的中斷服務(wù)處理函數(shù)
/// 注冊對應(yīng)IRQ中斷號的中斷服務(wù)函數(shù)
Interrupt_Irq_Handler_Register(GPIO1_Combined_16_31_IRQn, (Interrupt_Irq_Handler)Key0_Interrupt_Irq_Handler, NULL);
/// 使用GPIO01_IO18中斷
- GPIO使能中斷
/// 使用GPIO01_IO18中斷
GPIO_Enable_Interrupt(GPIO1, 18);
- 在中斷服務(wù)處理函數(shù)中處理中斷
void Key0_Interrupt_Irq_Handler(unsigned int gicciar, void *context)
{
/// 中斷服務(wù)函數(shù)要求快進快出,這里沒有定時器
/// 為了處理抖動暫時使用Delay來解決
/// 以后使用定時器來處理
Delay(10);
if (GPIO_ReadValue(GPIO1, 18) == 0)
{
Beep_On();
Led_On();
Delay(350);
Beep_Off();
Led_Off();
}
/// 中斷處理完成后,清楚中斷標志位
GPIO_Clean_Interrupt_Flag(GPIO1, 18);
}
發(fā)布評論請先 登錄
相關(guān)推薦
評論