baremetal GPIO中断

本文主要用来学习GPIO中断原理,不深入理解GIC控制器、CP15协处理器等大模块
 
Cortex-A7中断系统使用中断控制器为GIC-V2

GIC-V2控制器

GIC-V2的基本框图如下:
0
GIC最多支持8个处理器(processor0~ processor7)。不同处理器的GIC功能是相同的,只看其中一个即可。 GIC主要分为分发器(Distributor)和CPU接口(CPU interface/Virtual CPU interface)。
 

分发器:

分发器用于管理CPU所有中断源,确定每个中断的优先级,管理中断的屏蔽和中断抢占。最终将优先级最高的中断转发到一个或者多个CPU接口。CPU的中断源分为三类,如下:
SGIs(software generated interrupts):最大 16 个软件可产生的中断,中断号0-15,这类中断是软件写分发器的软件生成中断寄存器GICD_SGIR产生的,通常用于实现处理器之间的中断IPI
PPIs(private peripheral interrupts):中断号16-31;这类中断是每个处理器的私有组件产生的,例如定时器中断,每个处理器都有各自私有定时器,每个私有定时器使用相同的硬件中断号,只能给自己的处理器发中断。
SPIs(shared peripheral interrupts):SPI中断号32~1020,这类中断是所有处理器共享的外设中断,这类中断一般是边沿触发或电平触发的。
分发器提供了一些编程接口或者说是寄存器,可以通过分发器的编程接口实现如下操作。
  • 全局的开启或关闭CPU的中断。
  • 控制任意一个中断请求的开启和关闭。
  • 设置每个中断请求的中断优先级。
  • 指定中断发生时将中断请求发送到那些CPU(i.MX 6U是单核)。
  • 设置每个”外部中断”的触发方式(边缘触发或者电平触发)。
 

CPU接口:

CPU接口为链接到GIC的处理器提供接口,与分发器类似它也提供了一些编程接口,可以通过CPU接口实现以下功能:
  • 开启或关闭向处理器发送中断请求.。
  • 确认中断(acknowledging an interrupt)。
  • 指示中断处理的完成。
  • 为处理器设置中断优先级掩码。
  • 定义处理器的抢占策略
  • 确定挂起的中断请求中优先级最高的中断请求。
 
 

CP15:

这里仅说明为啥需要CP15:
GIC接口寄存器讲解部分只给出了”偏移地址”,GIC的基地址保存在CP15协处理器中。 我们修改系统控制寄存器以及设置中断向量表地址都会用到CP15协处理器。
协处理器 CP15 中的 c0 寄存器是 ARM 处理器中的控制寄存器,用于配置和控制处理器的一些基本特性。这个寄存器通常被用来控制缓存、页表和系统控制等功能。具体的功能包括:
  1. 控制和配置缓存:c0 寄存器中的位可以用来控制处理器的 L1 缓存和 L2 缓存,例如启用或禁用缓存、配置缓存的大小、设置缓存的工作模式等。
  2. 系统控制:c0 寄存器中的部分位用来控制处理器的一些基本系统特性,例如设置处理器的工作模式(用户模式、特权模式等)、配置异常向量表的基地址、设置处理器的端序模式等。
  3. 控制页表和内存管理:c0 寄存器中的位也可以用来配置处理器的页表结构、启用或禁用 MMU(内存管理单元)以及设置内存访问权限等。
 
深入理解CP15可参考:
 

GPIO中断设置:

1、设置GPIO的中断触发方式,也就是GPIO_ICR1或者GPIO_ICR2寄存器。触发方式有低电平、高电平、上升沿和下降沿。

2、使能GPIO对应的中断,设置GPIO_IMR寄存器

3、处理完中断,需要清除中断标志位,也就是清除GPIO_ISR寄存器相应的bit位,GPIO_ISR寄存器是写1清零

4、使能GIC中断号,参考imx6ull的参考手册第三章,得到中断号为67+32 = 99,gpio1_16 - gpio1_31共用中断号99

名词缩写:

ICR:interrupt configuration register

IMR:interrupt mask register

ISR:interrupt status register

 

具体实现:

start.S
.global _start                  /* 全局标号 */

/*
 * 描述:    _start函数,首先是中断向量表的创建
 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)
 *              ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)
 */
_start:
    ldr pc, =Reset_Handler        /* 复位中断                     */    
    ldr pc, =Undefined_Handler    /* 未定义中断                     */
    ldr pc, =SVC_Handler        /* SVC(Supervisor)中断         */
    ldr pc, =PrefAbort_Handler    /* 预取终止中断                     */
    ldr pc, =DataAbort_Handler    /* 数据终止中断                     */
    ldr    pc, =NotUsed_Handler    /* 未使用中断                    */
    ldr pc, =IRQ_Handler        /* IRQ中断                     */
    ldr pc, =FIQ_Handler        /* FIQ(快速中断)未定义中断             */

/* 复位中断 */    
Reset_Handler:

    cpsid i                        /* 关闭全局中断 */

    /* 关闭I,DCache和MMU 
     * 采取读-改-写的方式。
     */
    mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中                           */
    bic     r0,  r0, #(0x1 << 12)     /* 清除C1寄存器的bit12位(I位),关闭I Cache                */
    bic     r0,  r0, #(0x1 <<  2)     /* 清除C1寄存器的bit2(C位),关闭D Cache                    */
    bic     r0,  r0, #0x2             /* 清除C1寄存器的bit1(A位),关闭对齐                        */
    bic     r0,  r0, #(0x1 << 11)     /* 清除C1寄存器的bit11(Z位),关闭分支预测                    */
    bic     r0,  r0, #0x1             /* 清除C1寄存器的bit0(M位),关闭MMU                           */
    mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中                     */
    
    /* 设置各个模式下的栈指针,
     * 注意:IMX6UL的堆栈是向下增长的!
     * 堆栈指针地址一定要是4字节地址对齐的!!!
     * DDR范围:0X80000000~0X9FFFFFFF
     */
    /* 进入IRQ模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4     */
    orr r0, r0, #0x12     /* r0或上0x13,表示使用IRQ模式                    */
    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中                     */
    ldr sp, =0x80600000    /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */

    /* 进入SYS模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4     */
    orr r0, r0, #0x1f     /* r0或上0x13,表示使用SYS模式                    */
    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中                     */
    ldr sp, =0x80400000    /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */

    /* 进入SVC模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4     */
    orr r0, r0, #0x13     /* r0或上0x13,表示使用SVC模式                    */
    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中                     */
    ldr sp, =0X80200000    /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */

    cpsie i                /* 打开全局中断 */
    b main                /* 跳转到main函数                  */

/* 未定义中断 */
Undefined_Handler:
    ldr r0, =Undefined_Handler
    bx r0

/* SVC中断 */
SVC_Handler:
    ldr r0, =SVC_Handler
    bx r0

/* 预取终止中断 */
PrefAbort_Handler:
    ldr r0, =PrefAbort_Handler    
    bx r0

/* 数据终止中断 */
DataAbort_Handler:
    ldr r0, =DataAbort_Handler
    bx r0

/* 未使用的中断 */
NotUsed_Handler:

    ldr r0, =NotUsed_Handler
    bx r0

/* IRQ中断!重点!!!!! */
IRQ_Handler:
    push {lr}                    /* 保存lr地址 */
    push {r0-r3, r12}            /* 保存r0-r3,r12寄存器 */

    mrs r0, spsr                /* 读取spsr寄存器 */
    push {r0}                    /* 保存spsr寄存器 */

    mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
                                * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
                                * Cortex-A7 Technical ReferenceManua.pdf P68 P138
                                */                            
    add r1, r1, #0X2000            /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
    ldr r0, [r1, #0XC]            /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
                                 * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
                                 * 这个中断号来绝对调用哪个中断服务函数
                                 */
    push {r0, r1}                /* 保存r0,r1 */
    
    cps #0x13                    /* 进入SVC模式,允许其他中断再次进去 */
    
    push {lr}                    /* 保存SVC模式的lr寄存器 */
    ldr r2, =system_irqhandler    /* 加载C语言中断处理函数到r2寄存器中*/
    blx r2                        /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

    pop {lr}                    /* 执行完C语言中断服务函数,lr出栈 */
    cps #0x12                    /* 进入IRQ模式 */
    pop {r0, r1}                
    str r0, [r1, #0X10]            /* 中断执行完成,写EOIR */

    pop {r0}                        
    msr spsr_cxsf, r0            /* 恢复spsr */

    pop {r0-r3, r12}            /* r0-r3,r12出栈 */
    pop {lr}                    /* lr出栈 */
    subs pc, lr, #4                /* 将lr-4赋给pc */
    
    

/* FIQ中断 */
FIQ_Handler:

    ldr r0, =FIQ_Handler    
    bx r0
 
bsp_int.c
.
/*
 * @description    : 初始化中断服务函数表 
 * @param        : 无
 * @return         : 无
 */
void system_irqtable_init(void)
{
    unsigned int i = 0;
    irqNesting = 0;
    
    /* 先将所有的中断服务函数设置为默认值 */
    for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
    {
        system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);
    }
}

/*
 * @description            : 给指定的中断号注册中断服务函数 
 * @param - irq            : 要注册的中断号
 * @param - handler        : 要注册的中断处理函数
 * @param - usrParam    : 中断服务处理函数参数
 * @return                 : 无
 */
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) 
{
    irqTable[irq].irqHandler = handler;
      irqTable[irq].userParam = userParam;
}

/*
 * @description            : C语言中断服务函数,irq汇编中断服务函数会
                           调用此函数,此函数通过在中断服务列表中查
                           找指定中断号所对应的中断处理函数并执行。
 * @param - giccIar        : 中断号
 * @return                 : 无
 */
void system_irqhandler(unsigned int giccIar) 
{

   uint32_t intNum = giccIar & 0x3FFUL;
   
   /* 检查中断号是否符合要求 */
   if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
   {
         return;
   }
 
   irqNesting++;    /* 中断嵌套计数器加一 */

   /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
   irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
 
   irqNesting--;    /* 中断执行完成,中断嵌套寄存器减一 */

}
 
posted @ 2024-03-17 00:40  lethe1203  阅读(44)  评论(0)    收藏  举报