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

《DNK210使用指南 -CanMV版 V1.0》第十五章 按鍵中斷實(shí)驗(yàn)

#硬聲創(chuàng)作季 #Linux 學(xué)Linux-4.13.4 按鍵中斷實(shí)驗(yàn)驅(qū)動(dòng)編寫(xiě)1-1

#硬聲創(chuàng)作季 #Linux 學(xué)Linux-4.13.4 按鍵中斷實(shí)驗(yàn)驅(qū)動(dòng)編寫(xiě)1-2

#硬聲創(chuàng)作季 #Linux 學(xué)Linux-4.13.5 按鍵中斷實(shí)驗(yàn)驅(qū)動(dòng)編寫(xiě)2-1

#硬聲創(chuàng)作季 #Linux 學(xué)Linux-4.13.5 按鍵中斷實(shí)驗(yàn)驅(qū)動(dòng)編寫(xiě)2-2

第13.4講 Linux中斷實(shí)驗(yàn) 按鍵中斷實(shí)驗(yàn)驅(qū)動(dòng)編寫(xiě)上 - 第1節(jié) #硬聲創(chuàng)作季
【正點(diǎn)原子FPGA連載】第四章GPIO之MIO按鍵中斷實(shí)驗(yàn)-領(lǐng)航者 ZYNQ 之嵌入式開(kāi)發(fā)指南
【正點(diǎn)原子FPGA連載】第四章按鍵中斷實(shí)驗(yàn)--摘自【正點(diǎn)原子】達(dá)芬奇之Microblaze 開(kāi)發(fā)指南
按鍵中斷實(shí)驗(yàn)概述
鍵盤(pán)與按鍵中斷實(shí)驗(yàn)相關(guān)資料推薦
STM32——中斷、EXTI、按鍵中斷實(shí)驗(yàn)

Vision Board系列教程 | 按鍵中斷實(shí)驗(yàn)

評(píng)論