重点:
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;
}