前言
本次学习的目的是通过按按键触发按键中断,调用相关的中断服务函数,实现蜂鸣器鸣响。通过裸机学习能使自己对SoC的运行环境,开发环境有更好的了解。
软件实现流程
1)在start.S启动汇编中实现中断向量表,及irq处理函数
2)实现irq中断结构体数组,irq通用中断服务函数注册函数(中断号和中断服务函数的联系)
3)实现gic中断控制器初始化函数
4)实现gpio初始化函数,复用-中断配置
流程具体
1、异常向量表和IRQ_Handler一般都在start.S里面编写,因为需要做一些中断触发时保护现场和恢复现场的工作。
_start:
/*DDR范围:0x40000000~0x80000000 1GB*/
/*异常向量表 */
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handelr /* 未定义中断 */
ldr pc, =SVC_Handelr /* SVC(Supervisor)中断 */
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 未使用中断 */
ldr pc, =IRQ_Handler /* IRQ中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
IRQ_Handler:
ldr sp,=0x7FC00000 /*设置irq模式的sp指针 */
/*保护现场 */
push {lr}
push {r0-r3,r12}
mrs r0, spsr /*访问SPSR寄存器,保存cpsr */
push {r0}
/****************************************************** */
mrc p15,1,r1,c15,c3,0 /*读取GIC的CPU接口端基地址 GICC_CTLR*/
ldr r0, [r1,#0xC] /*基地址偏移0xC,访问GICC_IAR寄存器 */
/*GICC_IAR能够获取到中断ID号 */
push {r0,r1}
cps #0x3 /*进入svc模式 */
push {lr} /*保存svc的lr寄存器 */
//ldr r2, =system_irqhandler
blx system_irqhandler /*跳转到c程序的中断处理函数中,带上一个参数r0(中断ID)*/
pop {lr} /*lr出栈 */
cps #0x2 /*进入irq模式 */
pop {r0,r1}
str r0, [r1,#0x10] /*中断执行完成,把中断ID写入EOIR寄存器 */
/*恢复现场 */
pop {r0}
msr spsr, r0 /*恢复cpsr */
pop {r0-r3,r12}
pop {lr}
/********************************** */
subs pc, lr, #4 /*返回触发中断时,下一条指令的位置 */
其中恢复现场的subs pc, lr, #4 是因为cpu是三级流水线工作的,取址,译址,执行。比如当前第一条指令是执行,第二条指令是译址,第三条指令是取址,那么当中断触发时,pc所指向的是取址,而恢复现场则需要从第二条指令那里开始恢复即pc-4的位置。
2、实现irq中断结构体数组,(中断号和中断服务函数的联系)
/*中断服务函数的形式*/
typedef void (*system_irq_handler_t)(unsigned int giccIar,void *param);
/*中断服务函数结构体*/
typedef struct _sys_irq_handle
{
system_irq_handler_t irqhandler; /*中断服务函数*/
void *userParam; /*中断服务函数参数*/
}sys_irq_handle_t;
static sys_irq_handle_t irqTable[160]; /*数组大小为160,每一个元素的下标对应一个中断ID*/
有了这个结构体数组,就实现了中断号和中断服务函数的联系了,下一步就是把中断服务函数往里面注册就可以了。
3、irq通用中断服务函数注册函数
/*给指定的中断号注册中断服务函数 */
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
irqTable[irq].irqhandler = handler;
irqTable[irq].userParam = userParam;
}
这个就是往结构体里面注册中断服务函数了,自己编写好最终的中断服务函数,作为参数handler往里面存。
4、上面start.S的IRQ_Handler会跳进这个函数,根据中断ID执行最终的中断服务函数
/*具体的中断服务函数*/
void system_irqhandler(unsigned int giccIar)
{
unsigned int intNum = giccIar & 0x3FF; /*GICC_IAR的bit[9:0]为中断ID*/
if(intNum < NUMBER_OF_INT_VECTORS)
{
irqNesting++;
irqTable[intNum].irqhandler(intNum, irqTable[intNum].userParam);
irqNesting--;
}
else
return;
}
5、gic的初始化
参考数据手册,得到需要用到的寄存器GICD_TYPER、GICD_ISCENABLERn、GICC_PMR、GICC_BPR、GICD_CTRL、GICC_CTRL、GICD_ISENABLERn、GICD_IPRIORITYRn,寄存器的具体作用我在下面代码里面也作了注释,或者去查看手册。
/*GIC初始化*/
void GIC_init(void)
{
unsigned int i;
unsigned int irqRegs;
/*读取bit[0:4]为N,支持的中断ID最大值:(32(N+1)-1=159)*/
irqRegs = (GIC->D_TYPER & 0x1F) + 1;
/*关闭所有SGI,PPI,SPI中断*/
for(i=0; i<irqRegs; i++)
GIC->D_ISCENABLERn[i] |= 0xFFFFFFFF;
/*设置支持32个优先级,即bit[7:0]=11111000*/
GIC->C_PMR |= (0xFF << 3);
//GIC->C_PMR |= 0xFF;
/*设置5级抢占优先级bit[2:0]=2,即所有中断优先级位都为抢占优先级*/
GIC->C_BPR |= (2<<0);
/*使能组0的中断分发控制*/
GIC->D_CTRL |= (0x1<<0);
/*使能组0的中断发送到CPU Core的中断请求信号*/
GIC->C_CTRL |= (0x1<<0);
}
/*使能指定中断,并设置优先级*/
void GIC_EnableIRQ(IRQn_Type IRQn, unsigned int pri)
{
/*IRQn/32: 取到0~4寄存器里对应的寄存器*/
/*IRQn%32: 取到对应寄存器里相应的bit*/
GIC->D_ISENABLERn[IRQn/32] |= (1<<(IRQn%32));
GIC->D_IPRIORITYRn[IRQn/4] &= ~(0xFF<<((IRQn%4)*8));
GIC->D_IPRIORITYRn[IRQn/4] |= (pri<<((IRQn%4)*8)); /*设置指定中断的优先级*/
//GIC->D_ITARGETSRn[21] &= (~(0xFF<<16));
//GIC->D_ITARGETSRn[21] |= (0x1<<16); /*设置指定中断的CPU接口*/
}
6、gpio的中断设置
通过查看数据手册得到需要使用的寄存器有GPIOXINTENB、GPIOXDETENB、GPIOXDETMODE0、GPIOXDETMODE1、GPIOXDETMODEEX、GPIOXDET,具体功能也是在下面代码的注释里面有说明
/*使能GPIO口的中断功能*/
void gpio_intEnable(GPIO_Type *base, int pin)
{
base->GPIO_INTENB |= (1<<pin); /*中断使能*/
base->GPIO_DETENB |= (1<<pin); /*检测使能*/
}
/*设置gpio的中断配置功能*/
/*GPIOXDETMODE0或者GPIOXDETMODE1需要跟GPIOXDETMODEEX配合一起使用,
因为配置中断检测方式即高/低电平触发,上升/下降沿触发等需要用到3bit数来配置,
其中高1bit由GPIOXDETMODEEX提供,低2bit由GPIOXDETMODE0或者GPIOXDETMODE1提供*/
void gpio_intconfig(GPIO_Type *base, int pin, GPIO_Interrupt_Mode_t intmode)
{
volatile unsigned int *DetMode;
unsigned int pin_t;
pin_t = pin;
if (pin<16)
{
DetMode = &(base->GPIO_DETMODE0); /*0~15引脚*/
}
else
{
DetMode = &(base->GPIO_DETMODE1); /*16-31引脚*/
pin_t -= 16;
}
switch(intmode)
{
case GPIO_IntLowLevel:
base->GPIO_DETMODEEX &= ~(1<<pin); /*000的高(1-bit)*/
*DetMode &= ~(3<<(2 * pin_t)); /*000的低(2-bit)*/
break;
case GPIO_IntHighLevel:
base->GPIO_DETMODEEX &= ~(1<<pin); /*001的高(1-bit)*/
*DetMode = (*DetMode & (~(3<<(2 * pin_t)))) | (1<<(2 * pin_t)); /*001的低(2-bit)*/
break;
case GPIO_IntFallingEdge:
base->GPIO_DETMODEEX &= ~(1<<pin); /*010的高(1-bit)*/
*DetMode = (*DetMode & (~(3<<(2 * pin_t)))) | (2<<(2 * pin_t)); /*010的低(2-bit)*/
break;
case GPIO_IntRisingEdge:
base->GPIO_DETMODEEX &= ~(1<<pin); /*011的高(1-bit)*/
*DetMode = (*DetMode & (~(3<<(2 * pin_t)))) | (3<<(2 * pin_t)); /*011的低(2-bit)*/
break;
case GPIO_IntFallingorRisingEdge:
base->GPIO_DETMODEEX |= (1<<pin); /*100的高(1-bit)*/
*DetMode &= ~(3<<2 * pin_t); /*100的低(2-bit)*/
break;
default:
break;
}
}
/*清除中断标注位*/
void gpio_clearintflags(GPIO_Type *base, int pin)
{
base->GPIO_DET |= (1<<pin); /*往里面写1即可清除标志位*/
}
小结
总体来说整个中断的执行顺序大概是:按下按键 -> 触发irq中断信号 -> gic控制器 -> CPU -> 异常向量表 -> IRQ异常处理 -> 执行最终的中断服务程序
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)