0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

按鍵中斷實(shí)驗(yàn)是什么

汽車電子技術(shù) ? 來源:程序猿搬磚 ? 作者:壞人 ? 2023-03-02 16:21 ? 次閱讀

前面的按鍵實(shí)驗(yàn)是通過死循環(huán)一直讀取按鈕電平值來判斷是否有按下按鈕,接下來將使用另外一個更優(yōu)雅的方式實(shí)驗(yàn)按鍵按下功能-中斷。

CPU在正常處理指令的時候會遇到外設(shè)打斷當(dāng)前執(zhí)行邏輯,我們稱為異常中斷。一系列中斷處理集中在一起管理,我們稱為異常中斷向量表。

中斷向量表

Coretex-A系列的中斷向量表就是存放在程序起始位置(鏈接起始地址)的一組由4字節(jié)組成的一組數(shù)據(jù),Coretex-A 32位處理器每一條指令長度就是4個字節(jié),所以本質(zhì)上這個中斷向量表是一組固定地址的指令。Coretext-A系統(tǒng)CPU總共支持8個中斷:

圖片圖片

這8個中斷里面需要特別注意也是需要開發(fā)的主要是復(fù)位中斷IRQ中斷復(fù)位中斷在上電或者按下Reset按鈕后硬件加載程序同時PC寄存器位置重置為0x0或者鏈接起始地址時觸發(fā),IRQ中斷則是外設(shè)觸發(fā)。每一個中斷發(fā)生時PC寄存器會被設(shè)置成一個固定的地址,而這個地址則對應(yīng)中斷向量表中一條指令。

中斷向量表添加到匯編最開始的位置:

/* 從鏈接起始地址開始,8條4字節(jié)的指令組成了ARM的中斷向量表 */
/* 中斷向量表放在最開始的位置,每一條指令對應(yīng)了具體的中斷處理 */
/* 當(dāng)發(fā)生對應(yīng)中斷時,硬件會把對應(yīng)的地址設(shè)置到pc寄存器,從而執(zhí)行對應(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ù)訪問中止中斷 */
ldr pc, =Not_Used_Handler                       /* 0x14: 未使用的中斷 */
ldr pc, =IRQ_Handler                            /* 0x18: 外部設(shè)備中斷 */
ldr pc, =FIQ_Handler                            /* 0x1c: 快速中斷 */

復(fù)位中斷服務(wù)函數(shù)

上電后第一個要觸發(fā)的則是復(fù)位中斷,通過向量表中定義的指令可以將程序切換到Reset_Handler處開始執(zhí)行

  • 關(guān)閉IRQ
  • 關(guān)閉I,D Cache,以及MMU
  • 設(shè)置中斷的起始地址,即設(shè)置成鏈接起始地址(因?yàn)槌绦蚴菑逆溄悠鹗嫉刂烽_始運(yùn)行的)
  • 設(shè)置IRQ,SVC以及SYS模式下C語言的運(yùn)行環(huán)境(C語言的SP指針棧頂)
  • 打開IRQ
  • 調(diào)轉(zhuǎn)到C語言的main函數(shù)開始運(yùn)行
cpsid i                             /* 關(guān)閉IRQ,  此時IRQ還沒有配置完成,所以關(guān)閉*/

/* 
    在設(shè)備上電啟動時,執(zhí)行的代碼訪問的外設(shè)都是實(shí)際地址,
    mmu與cache此時的意義不大,
    這個時候?yàn)榱朔乐筩ache與mmu可能導(dǎo)致的問題會先將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)存屏障指令進(jìn)隔斷,保證前后讀取指令都是正常的地址 */
isb                                  /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進(jìn)隔斷,保證前后讀取指令都是正常的地址 */
MCR p15, 0, r0, c12, c0, 0          /* 將r0的數(shù)據(jù)寫入到VBAR寄存器中,表示向量偏移地址是0x87800000 */
dsb                                 /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進(jìn)隔斷,保證前后讀取指令都是正常的地址 */
isb                                 /* 這里涉及到了改變讀取內(nèi)存的地址起始地址,需要使用內(nèi)存屏障指令進(jìn)隔斷,保證前后讀取指令都是正常的地址 */

/* 設(shè)置不同模式下的sp指針,每一個模式的sp對應(yīng)不同的物理地址,當(dāng)進(jìn)入不同工作模式時C語言會在不同的物理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ù)寫入到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ù)寫入到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ù)寫入到cpsr_c中      */
ldr sp, =0x80200000                 /* 設(shè)置棧指針    */

cpsie i                             /* 打開IRQ */
b main                    /* 跳轉(zhuǎn)到C語言main函數(shù)    */

IRQ外設(shè)中斷服務(wù)函數(shù)

當(dāng)一個外設(shè)觸發(fā)中斷(比如按鍵按下后)會執(zhí)行IRQ_Handler函數(shù)。

  • 中斷發(fā)生是首先保護(hù)現(xiàn)場(lr, r0-r12寄存器, 保存spsr寄存器數(shù)據(jù))
  • 讀取GIC寄存器組的起始地址
  • 通過對GIC寄存器組基地址偏移得到CPU Interface寄存器組
  • 通過對CPU Interface基地址進(jìn)行偏移得到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ù)完成中斷收尾工作
  • 恢復(fù)spsr寄存器數(shù)據(jù)
  • 恢復(fù)中斷執(zhí)行前的現(xiàn)場(lr, r0-r12)
  • 將lr地址減4字節(jié)再給到pc寄存器,恢復(fù)中斷前的執(zhí)行指令
/* 
    中斷發(fā)生時, IRQ模式下的lr(LR_svc物理)寄存器保存中斷時刻的PC寄存器
    通過使用push命令將lr的值壓入棧,這樣的目的是為了在執(zhí)行完當(dāng)前中斷服務(wù)函數(shù)
    后可以順利的返回到中斷前的執(zhí)行位置,因?yàn)樵趫?zhí)行中斷服務(wù)函數(shù)的時候lr里面的值可能發(fā)生變化
    比如: 在內(nèi)部使用了blx調(diào)用其它函數(shù),新的IRQ中斷進(jìn)入
*/
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中,恢復(fù)現(xiàn)場的時候只需要入棧即可
 */   
push {r0-r12}
// push {r0-r3, r12}
/* 
    將spsr(SPSR_irq)寄存器的值壓入棧,spsr保存了中斷發(fā)生時的cpsr寄存器的值,
    中斷執(zhí)行完成之后需要恢復(fù)
*/
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號(多核時使用),
    通過中斷號即可明確具體的中斷來源并對中斷進(jìn)行響應(yīng)
 */
ldr r0, [r1, #0x0c]
/*
    由于要進(jìn)入到SVC模式了,需要將r0, r1兩個通用寄存器的數(shù)據(jù)保存到棧里,
    防止在SVC模式下后r0,r1數(shù)據(jù)丟失
    此時r0, r1保存到的是IRQ模式下的??臻g,
 */
push {r0, r1}
/*
    將CPSR寄存器的M[4:0]值改成10011, 讓CPU進(jìn)入到SVC模式,
    進(jìn)行SVC模式之后,當(dāng)我們處理當(dāng)前中斷時,
    系統(tǒng)可以再次響應(yīng)IRQ中斷
 */
cps #0x13                       // 進(jìn)入到SVC
/*
    進(jìn)入到svc模式后先將lr的數(shù)據(jù)壓入棧,執(zhí)行完后再恢復(fù)
    因?yàn)榻酉聛硪褂胋lx調(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恢復(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ù)
/*
    此時r0保存的是GICC_IAR寄存器的數(shù)據(jù),
    將GICC_IAR的數(shù)據(jù)寫入到GICC_EOIR寄存器,表示當(dāng)前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               // 恢復(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ù)

我們需要編寫一個通用的中斷處理函數(shù),從參數(shù)(r0寄存器中的GICC_IAR寄存器的數(shù)據(jù))中提取中斷號,根據(jù)對應(yīng)的中斷號再調(diào)用注冊進(jìn)來的具體的中斷函數(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復(fù)用以及配置電氣屬性
  • 配置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ù)要求快進(jìn)快出,這里沒有定時器
    /// 為了處理抖動暫時使用Delay來解決
    /// 以后使用定時器來處理
    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);
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
收藏 人收藏

    評論

    相關(guān)推薦

    基于RoboMasterC板的RT-Thread使用分享—按鍵中斷實(shí)驗(yàn)

    說起中斷,我們常常就會提到一個經(jīng)典的例子,就是我們在家里處理手頭上事情的時候,熱水煮開了,這時候我們就需要放下手頭的事情,去關(guān)掉煤氣爐。這個就是中斷
    的頭像 發(fā)表于 10-16 15:31 ?1553次閱讀
    基于RoboMasterC板的RT-Thread使用分享—<b class='flag-5'>按鍵</b><b class='flag-5'>中斷</b><b class='flag-5'>實(shí)驗(yàn)</b>

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

    第十五章 按鍵中斷實(shí)驗(yàn) 本章實(shí)驗(yàn)將介紹如何使用CanMV讓Kendryte K210通過中斷的方式獲取板載
    發(fā)表于 10-12 09:20

    【正點(diǎn)原子FPGA連載】第四章GPIO之MIO按鍵中斷實(shí)驗(yàn)-領(lǐng)航者 ZYNQ 之嵌入式開發(fā)指南

    原子公眾號,獲取最新資料第四章GPIO之MIO按鍵中斷實(shí)驗(yàn)中斷是一種當(dāng)滿足要求的突發(fā)事件發(fā)生時通知處理器進(jìn)行處理的信號。中斷可以由硬件處理單
    發(fā)表于 08-29 16:21

    【正點(diǎn)原子FPGA連載】第四章按鍵中斷實(shí)驗(yàn)--摘自【正點(diǎn)原子】達(dá)芬奇之Microblaze 開發(fā)指南

    官方B站:https://space.bilibili.com/3946208905)對正點(diǎn)原子FPGA感興趣的同學(xué)可以加群討論:9056247396)關(guān)注正點(diǎn)原子公眾號,獲取最新資料第四章按鍵中斷實(shí)驗(yàn)
    發(fā)表于 10-17 18:24

    STM32按鍵中斷實(shí)驗(yàn)

    按鍵中斷實(shí)驗(yàn)實(shí)驗(yàn)2是按鍵查詢一、實(shí)驗(yàn)原理1、按鍵使
    發(fā)表于 08-13 06:05

    按鍵中斷實(shí)驗(yàn)概述

    按鍵中斷實(shí)驗(yàn)概述1.1 資源概述開發(fā)板:正點(diǎn)原子STM32F103zet6精英開發(fā)板控芯片型號:STM32F103ZET6開發(fā)板資料1.2實(shí)現(xiàn)功能key1:紅燈亮,再按一下紅燈滅key2:綠燈亮
    發(fā)表于 01-11 08:08

    鍵盤與按鍵中斷實(shí)驗(yàn)相關(guān)資料推薦

    這里寫自定義目錄標(biāo)題鍵盤與按鍵中斷實(shí)驗(yàn)代碼圖像使用控件鍵盤與按鍵中斷實(shí)驗(yàn)4X4鍵盤(
    發(fā)表于 01-13 06:18

    INT0中斷實(shí)驗(yàn)

    INT0中斷實(shí)驗(yàn)。 1、按鍵中斷實(shí)驗(yàn)。低電平中斷,在
    發(fā)表于 06-30 11:22 ?3049次閱讀

    STM32——中斷、EXTI、按鍵中斷實(shí)驗(yàn)

    STM32中斷——總結(jié)及實(shí)操一、中斷是什么?1.1 中斷的含義1.2 中斷的作用(了解即可)1.3 中斷的流程二、
    發(fā)表于 01-14 15:48 ?4次下載
    STM32——<b class='flag-5'>中斷</b>、EXTI、<b class='flag-5'>按鍵</b><b class='flag-5'>中斷</b><b class='flag-5'>實(shí)驗(yàn)</b>

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

    準(zhǔn)備工作win10/11系統(tǒng)的電腦建議自備1根Type-C數(shù)據(jù)線在正式進(jìn)行開發(fā)前,需要安裝紅色框中的三個軟件?。?!安裝RT-ThreadStudioIDE1.進(jìn)入下面網(wǎng)站,
    的頭像 發(fā)表于 10-11 08:06 ?102次閱讀
    Vision Board系列教程 | <b class='flag-5'>按鍵</b><b class='flag-5'>中斷</b><b class='flag-5'>實(shí)驗(yàn)</b>