重點:
1.GIC:(Generic??Interrupt??Controller)通用中斷控制器,是ARM架構中用于管理中斷的核心模塊,主要用于現代多核處理器系統。它負責接收,分發并分發中斷請求,減輕CPU負擔,使其專注于計算任務。目前GIC有四個版本,V1~V4,現在只有V2~V4正在大量使用,IMX6ULL搭載的是V2的GIC控制器。
2.協處理器是一種專門用于輔助CPU執行特定任務的硬件模塊,通過分擔CPU的運算負荷來提升系統整體效率
AMR?Cortex??A7內核配備了16個協處理器
3.將異常向量表重映射的原因:默認情況下,異常向量表在低地址(0x00000000),但系統運行時,可能需要把異常向量表放到其他的地址(比如更高的內存地址,放在操作系統或應用程序靈活管理內存空間),這樣能更合理的利用內存,也便于對異常處理程序進行組織和管理。
重映射異常向量表的方法:通過協處理器(CP15)中的相關寄存器(比如向量基地址寄存器),將異常向量表的基地址修改為目標地址,是的處理器在處理異常時,能到新的地址去獲得異常處理程序的入口
一.實現按鍵功能
相關代碼:
#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"void init_key(void)
{IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); //設置IO復用功能IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF0B0); //配置該引腳的電氣屬性struct GPIO_Type_t t ={.direction = gpio_input };init_gpio(GPIO1,18,&t); //按鍵為輸出方式
}
int key_pressed(void)
{if(read_gpio(GPIO1,18) == 0) //判斷該引腳是高電平還是低電平,低電平為按下按鍵返回0,高電平為沒按按鍵返回1{return 1;}else{return 0;}}
#ifndef __KEY_H__
#define __KEY_H__
#include "gpio.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"extern void init_key(void);extern int key_pressed(void);
#endif
int main(void)
{enable_clocks();system_interrupt_init();init_beep();init_led();init_key();while(1){delay(0xFFFFF);}return 0;
}
匯編代碼與上一節一樣
二.中斷
1.中斷步驟:
? ? ? ? 1)中斷源發出中斷請求;
? ? ? ? 2)內核檢查是否響應相應中斷以及判斷該中斷是否屏蔽;
? ? ? ? 3)內核檢查中斷的優先級;
? ? ? ? 4)保護現場
? ? ? ? 5)執行中斷服務函數;
? ? ? ? 6)恢復現場
2.GIC中斷控制器
GIC:(Generic??Interrupt??Controller)通用中斷控制器,是ARM架構中用于管理中斷的核心模塊,主要用于現代多核處理器系統。它負責接收,分發并分發中斷請求,減輕CPU負擔,使其專注于計算任務。目前GIC有四個版本,V1~V4,現在只有V2~V4正在大量使用,IMX6ULL搭載的是V2的GIC控制器。
GIC能處理1200個中斷,分為三大類:
1.SPI:共享外設中斷? ? ? ? 32~1019
2.PPI:私有外設中斷,內核與內核之間的通信中斷? ? ? ? 16~31號
3.SGI:軟件產生中斷? ? ? ? 0~15號
IMX6ULL中有160個中斷
IAR:中斷通知寄存器? ? ? ? 將中斷編號傳給內核
EOIR:中斷結束寄存器? ? ? ? 清除中斷
3.協處理器(Techinal手冊)
ARM內核支持16個協處理器,CP0~CP15,
SCTLR:映射異常向量表
VBVR:異常向量的基地址
mrc:用來讀的目標寄存器(CP15)的值放到通用寄存器當中去
mcr:用來將通用寄存器里的值寫到(CP15)目標寄存器當中去
MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
cond:指令執行的條件碼,就是之前我們使用過的指令條件,eq,lt什么的。如果忽略的話就表
示無條件執行;
p15:表示要讀取的是CP15當中的某個寄存器;
opc1:協處理器要執行的操作碼1,其實就是一個數,要做什么將來查表;
Rt:ARM 目標寄存器,讀出來的數據放到哪個ARM寄存器里。CP15
CRn:CP15 協處理器的目標寄存器,就是你要讀取CP15的哪個寄存器(C0~C15);
CRm:協處理器中附加的目標寄存器或者源操作數寄存器,如果不需要附加信息就將CRm 設置
為 C0,否則結果不可預測。?
opc2:可選的協處理器特定操作碼2,使用時查表。
4.實現的代碼
#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"
#include "interrupt.h"
#include "led.h"
void init_key(void)
{IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); //設置IO復用功能IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF0B0); //配置該引腳的電氣屬性struct GPIO_Type_t t ={.direction = gpio_input };init_gpio(GPIO1,18,&t); //按鍵為輸出方式GPIO1->ICR2 |= (3 << 4); //將GPIO1當中的18號引腳設置為下降沿觸發方式 ICR為中斷方式的寄存器GPIO1->IMR |= (1 << 18); //將GPIO1當中的18號引腳置1,打開18號引腳的中斷屏蔽寄存器 IMR為中斷屏蔽寄存器system_interrupt_register(GPIO1_Combined_16_31_IRQn,key_interrupt_handler);GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); //打開指定的中斷,該位置打開編號為99的中斷GIC_SetPriority(GPIO1_Combined_16_31_IRQn,0); //設置中斷優先級為0,即優先級最高
}void key_interrupt_handler(void)
{if((GPIO1->ISR &(1 << 18)) != 0) //判斷中斷標記寄存器中產生中斷是由GPIO1的18號引腳造成的{led_nor();GPIO1->ISR |= (1 << 18); //清除中斷標記寄存器}
}
int key_pressed(void)
{if(read_gpio(GPIO1,18) == 0) //判斷該引腳是高電平還是低電平,低電平為按下按鍵返回0,高電平為沒按按鍵返回1{return 1;}else{return 0;}}
#include "interrupt.h"
#include "MCIMX6Y2.h"
#include "core_ca7.h"static irq_handler_t interrupt_vector_table[160]; //定義一個函數的指針的數組,用來存放中斷void system_interrupt_init(void)
{GIC_Init(); //對GIC控制器進行初始化操作
}void system_interrupt_register(int num, irq_handler_t handler) //注冊中斷服務函數
{interrupt_vector_table[num] = handler;
}void system_interrupt_handler(int num) //中斷服務函數,num為哪一個中斷發出的中斷請求,傳遞其中斷號
{if(interrupt_vector_table[num] != NULL) //什么原因造成的中斷,該處為中斷編號為99的中斷{interrupt_vector_table[num](); //調用這個函數的指針數組}}
.global _start_start:ldr pc, =_reset_handlerldr pc, =_undefine_handlerldr pc, =_svc_handlerldr pc, =_prefetch_abort_handlerldr pc, =_data_abort_handlerldr pc, =_reserved_handlerldr pc, =_irq_handlerldr pc, =_fiq_handler_undefine_handler:ldr pc, =_undefine_handler_svc_handler:ldr pc, =_svc_handler_prefetch_abort_handler:ldr pc, =_prefetch_abort_handler_data_abort_handler:ldr pc, =_data_abort_handler_reserved_handler:ldr pc, =_reserved_handler
//在main函數當中初始化irq中斷函數
_irq_handler:subs lr, lr, #4 //通過查表,將鏈接寄存器的值減4(異常狀態返回地址)mrc p15, 4, r1, c15, c0, 0 //將GIC的基地址放到r1當中去add r1, r1, #0x2000 //得到的是C_CTLR的基地址ldr r0, [r1, #0x0C] //將r1當中的基地址偏移0x0C的地址(C-IAR(發出中斷請求的寄存器))存(讀)到r0寄存器當中stmfd sp!, {r0-r12, lr} //保護現場stmfd sp!, {r0, r1} //將r0和r1入棧cps #0x1F //切換為system模式stmfd sp!, {lr} //保護system模式下的lrbl system_interrupt_handler //調用C語言函數ldmfd sp!, {lr} //恢復system模式下的lrcps #0x12 //切換為irq模式ldmfd sp!, {r0, r1} //將第38行的r0和r1出棧str r0, [r1, #0x10] //將r0當中的值寫到r1偏移0x10的地址上去(C_EOIR(清除中斷標志的寄存器))ldmfd sp!, {r0-r12, pc}^ //恢復現場并且改變模式
_fiq_handler:ldr pc, =_fiq_handler_reset_handler:
/* mrs r0,cpsrbic r0, r0, #0x1Forr r0, r0, #0x12 //irp(10010)bic r0, r0, #(1 << 7) //打開irq中斷msr cpsr, r0ldr sp, =0x86000000mrs r0,cpsrbic r0, r0, #0x1Forr r0, r0, #0x1F //system(11111)msr cpsr, r0ldr sp, =0x84000000
*/cps #0x12 //irq(10010)模式ldr sp, =0x86000000 cps #0x1F //system(11111)模式cpsie ildr sp, =0x84000000//先通過CP15寫處理器打開設置基地址的選項,再通過映射異常向量表bl _enable_icahce bl _set_vbar bl _bss_clear b main //跳轉至main函數當中//處理CP15協處理器(SCTLR)
_enable_icahce: mrc p15, 0, r0, c1, c0, 0 //讀目標寄存器的值放到通用寄存器r0上bic r0, r0, #(1 << 13) //根據查表,將SCTLR的第13位清0,此位用來選擇異常向量的基地址,目的是軟件可以通過VBAR來重新設置這個基地址orr r0, r0, #(1 << 12) //根據查表,將SCTLR的第12位置1,此位用來將指令cache進來打開,默認是關閉狀態mcr p15, 0, r0, c1, c0, 0 //將修改后的目標寄存器的值重新寫入目標寄存器bx lr
//設置異常向量表基地址(VBAR)
_set_vbar:ldr r0, =0x87800000 //將異常向量表的基地址存入到r0當中mcr p15, 0, r0, c12, c0, 0 //根據查表,將r0當中的基地址通過cp15協處理器寫入CBAR當中,當作GIC的基地址bx lr_bss_clear:ldr r0, =__bss_startldr r2, =__bss_end
loop:mov r1, #0str r1, [r0]add r0, r0, #4cmp r0, r2blt loopbx lrfinished:b finished
#include "beep.h"
#include "led.h"
#include "key.h"
#include "core_ca7.h"
#include "interrupt.h"void enable_clocks(void) //將所有時鐘都打開
{CCM->CCGR0 = 0xFFFFFFFF;CCM->CCGR1 = 0xFFFFFFFF;CCM->CCGR2 = 0xFFFFFFFF;CCM->CCGR3 = 0xFFFFFFFF;CCM->CCGR4 = 0xFFFFFFFF;CCM->CCGR5 = 0xFFFFFFFF;CCM->CCGR6 = 0xFFFFFFFF;
}void delay(unsigned int n)
{while(n--); //進行延時}int main(void)
{enable_clocks();system_interrupt_init();init_beep();init_led();init_key();while(1){delay(0xFFFFF);}return 0;
}