ARM接口技术——中断控制器

2022/4/27 6:12:42

本文主要是介绍ARM接口技术——中断控制器,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

ARM中断的介绍

ARM中断

不同的处理器对中断的处理流程大体相同,但是具体的实现细节会差别。

ARM异常源

ARM中断也是异常的一种,ARM处理器有以下几种异常源:

  • FIQ
  • IRQ
  • Reset
  • Software Interrupt
  • Data Abort
  • Prefetch Abort
  • Undefined Instruction

中断虽然是异常,但不完全是错误,异常也不是,而是为了完成某些功能的东西,只有Undefined Instruction异常是真正的系统错误。

这些在ARM异常机制也介绍过,这里主要介绍的是IRQ。

ARM中断执行过程

在ARM处理器中,CPU收到一个中断时,以IRQ为例,大概会执行以下过程:

  • 修改状态寄存器CPSR从User模式切换到到IRQ模式;
  • 把中断IRQ禁止位打开,防止再次接收IRQ中断信号(FIQ不禁止,FIQ中断信号可以打断IRQ中断程序);
  • 跳转到中断向量表IRQ的指令,该指令如果写了中断执行函数,再跳转到该代码段;
  • 执行完之后再跳转回正常程序。
ARM中断产生的问题

在ARM处理器中,有上百个中断源,可能同时产生多个中断信号,会产生问题。

比如:

  • 同样是产生IRQ的中断源有很多个,但是中断向量表的IRQ只有一条跳转指令,那么处理器不知道是谁产生的中断信号,该执行什么中断处理程序;
  • 在处理器正在执行中断程序的时候,CPSR打开了IRQ禁止位,那么其它中断就会被忽略;
  • Exynos4412芯片集成了4个同样的ARM cortex A9处理器,产生的中断应该让哪个处理器处理。

在复杂封装的ARM处理器芯片中还会产生一系列问题,这时候就需要一个中断控制器GIC,去统一管理外部产生的中断信号。

Exynos4412下的中断控制器GIC

中断控制器做了至少四件事:

  1. 接收外部设备产生的中断信号;
  2. 多个中断同时产生时,对中断信号挂起,并按照优先级依次发给CPU;
  3. 为每个中断选择一个CPU进行处理;
  4. CPU处理中断程序时,可以查询中断控制器获取中断信号是由哪个硬件产生的。

这样就解决了前面提到的几个问题,除此之外中断控制器还做了一系列管理中断的事情。

中断实验

中断实验分析

Exynos4412这种SOC要比单片机功能强大很多,相关的寄存器配置也及其复杂,SOC一般会安装一个操作系统,这些事情一般都由操作系统内核去调度。

这里裸机实验就一切从简,只实现基本的中断功能,对于设置中断类型、优先级和选择处理器等不做配置。

原理图:

外围电路原理图:按键Key2连接的引脚是UART_RING。

核心板原理图:所连接的是GPX1_1引脚,同时它是外部中断9的中断源:XEINT9。

中断相关寄存器的配置

Exynos4412中断机制比较复杂,有160个中断,分为三种中断类型:

 

 

我们使用的是SPI,提供的SPI类型中断有128个(0-127)。

1. 首先是中断源的配置,即引脚相关寄存器:

GPX1CON GPIO配置寄存器,GPX1CON[1]设为中断功能,0xF。


 

前面提到我们使用的是外部中断9,但是它使用的中断配置寄存器叫EXT_INT41,不知道三星公司为什么这样设计,但是它正好在寄存器列表的GPX相关寄存器下面。

EXT_INT41_CON 中断配置寄存器,EXT_INT41_CON[1]设为下降沿触发,0x2。


 

EXT_INT41_MASK 中断屏蔽寄存器,默认是打开屏蔽,需要关闭屏蔽使能中断功能,EXT_INT41_MASK[1]设为0。

 

 


EXT_INT41_PEND,挂起状态寄存器

 

 

EXT_INT41_PEND寄存器在该引脚产生中断时,会自动挂起变成1,在CPU处理了这个中断后,这个寄存器的值不会改变,会不断的发出中断信号,所以中断处理程序在处理完这个中断之后需要修改这个寄存器的值。

这个寄存器很特殊,写0的效果是变成1,写1变成0,所以中断处理程序需要写1,它会变成0。

 

至此中断源就配置好了,但是Exynos4412的CPU不直接去接收中断信号,而是通过一个中断控制器去管理中断,所以还需要配置一系列中断控制器的配置。


 

2. 中断控制器相关配置:

首先要找到这个中断源对应的中断号是多少。

中断表的中断号是第二列ID,第一列只是中断类型为SPI的编号。

...

ID: 141-68略

...

 可以看到我们要使用的外部中断9的中断号是57,后面会用到。


 

ICDDCR寄存器

ICDDCR,整个中断控制器的总开关,让中断控制器能够接收外部的中断信号,必须要打开。ICDDCR = 1。


 

ICDISER_CPU

 

 

这组寄存器的作用是使能某个中断与CPU的接口(它的表述上是打开中断与CPU的接口,实际上只是打开中断与中断控制器的接口,真正与CPU交互的是中断控制器)。

Exynos4412有160个中断,每个寄存器是32位,所以可以控制32个中断,一共需要5个寄存器。

(但是它提供了8个寄存器,后面3个是与PPI和SGI相关寄存器,与SPI中断无关)

它的对应关系如下:

 

 

57号中断所在的寄存器地址偏移量为0x0104,结合上一张图0x0104对应的寄存器名为ICDISER1_CPU0,在三星提供的头文件中寄存器名是ICDISER1。

所以打开中断57与CPU0的接口就是设置 ICDISER1_CPU0[25]=1。


 

ICDIPTR_CPU

ICDIPTR_CPU寄存器设置用于告诉中断控制器这个中断该转发给哪个CPU。

它的数量很多对应关系比较复杂,直接给出结果:ICDIPTR14[15:8] = 0x01。


 

ICCICR_CPU

至此只是告诉中断控制器这个中断转发给哪个CPU,但是中断控制器与这个CPU之间的接口还没打开。

刚刚设置了要转发给CPU0,那么需要打开GIC与CPU0的接口:ICCICR_CPU0 = 1。


 

ICCEOIR

 

 

这个寄存器的作用是当中断处理程序执行完以后,CPU告诉GIC当前处理的中断号程序已经处理完,GIC如果还有挂起的中断,就可以发给CPU了。

在中断处理程序结束之前:ICCEOIR_CPU0 = 57(告诉GIC处理完57号中断)。


 

配置完以上寄存器就配置了以下功能:

  • 打开中断控制器总开关
  • 中断控制器对某个中断号的使能
  • 配置某个中断发生后该发往哪个处理器
  • 打开中断控制器与某个处理器直接的接口

3. 除此之外,还有其它一系列寄存器设置,前面也提到只实现基本的中断功能,所以其它寄存器就不做设置。

中断实验代码

C语言执行环境的修改:

中断实验需要修改中断向量表,另外一篇会介绍裸机实验的工程模板,还没写好,这里不作介绍。

启动汇编程序start.S(这里只需要关注7-14行和93-98行):

  1 .text
  2 .global _start
  3 _start:
  4     /*
  5      * Vector table
  6      */ 
  7     b reset
  8     b .
  9     b .
 10     b .
 11     b .
 12     b .
 13     b irq_handler
 14     b .
 15 
 16 reset:
 17     /*
 18      * Set vector address in CP15 VBAR register
 19      */ 
 20     ldr    r0, =_start
 21     mcr    p15, 0, r0, c12, c0, 0    @Set VBAR
 22 
 23     /*
 24      * Set the cpu to SVC32 mode, Disable FIQ/IRQ
 25      */  
 26     mrs r0, cpsr
 27     bic r0, r0, #0x1f
 28     orr    r0, r0, #0xd3
 29     msr    cpsr ,r0
 30 
 31     /*
 32      * Defines access permissions for each coprocessor
 33      */  
 34     mov    r0, #0xfffffff
 35     mcr    p15, 0, r0, c1, c0, 2      
 36 
 37     /*
 38      * Invalidate L1 I/D                                                                                                                   
 39      */
 40     mov    r0, #0                    @Set up for MCR
 41     mcr    p15, 0, r0, c8, c7, 0    @Invalidate TLBs
 42     mcr    p15, 0, r0, c7, c5, 0    @Invalidate icache
 43     
 44     /*
 45      * Set the FPEXC EN bit to enable the FPU
 46      */ 
 47     mov r3, #0x40000000
 48     fmxr FPEXC, r3
 49     
 50     /*
 51      * Disable MMU stuff and caches
 52      */
 53     mrc    p15, 0, r0, c1, c0, 0
 54     bic    r0, r0, #0x00002000        @Clear bits 13 (--V-)
 55     bic    r0, r0, #0x00000007        @Clear bits 2:0 (-CAM)
 56     orr    r0, r0, #0x00001000        @Set bit 12 (---I) Icache
 57     orr    r0, r0, #0x00000002        @Set bit 1 (--A-) Align
 58     orr    r0, r0, #0x00000800        @Set bit 11 (Z---) BTB
 59     mcr    p15, 0, r0, c1, c0, 0
 60 
 61     /*
 62      * Initialize stacks                                                                                                                  
 63      */
 64 init_stack:     
 65     /*svc mode stack*/
 66     msr cpsr, #0xd3
 67     ldr sp, _stack_svc_end
 68 
 69     /*undef mode stack*/
 70     msr cpsr, #0xdb
 71     ldr sp, _stack_und_end
 72 
 73     /*abort mode stack*/    
 74     msr cpsr,#0xd7
 75     ldr sp,_stack_abt_end
 76 
 77     /*irq mode stack*/    
 78     msr cpsr,#0xd2
 79     ldr sp, _stack_irq_end
 80     
 81     /*fiq mode stack*/
 82     msr cpsr,#0xd1
 83     ldr sp, _stack_fiq_end
 84     
 85     /*user mode stack, enable FIQ/IRQ*/
 86     msr cpsr,#0x10
 87     ldr sp, _stack_usr_end
 88 
 89     /*Call main*/
 90     b main
 91 
 92 /* IRQ的异常处理程序 */    
 93 irq_handler:
 94     /* 因为产生IRQ异常时LR自动保存的是当前执行指令的下下条指令 */
 95     sub lr,lr,#4     /* 修正LR位为中断前执行指令的下条指令 */
 96     stmfd sp!,{r0-r12,lr}    /* 压栈保护现场 */
 97     bl do_irq    /* 跳转到中断处理程序 */
 98     ldmfd sp!,{r0-r12,pc}^    /* 出栈恢复现场,同时恢复中断之前的模式 */
 99 
100 
101 _stack_svc_end:      
102     .word stack_svc + 512
103 _stack_und_end:      
104     .word stack_und + 512
105 _stack_abt_end:      
106     .word stack_abt + 512
107 _stack_irq_end:      
108     .word stack_irq + 512
109 _stack_fiq_end:
110     .word stack_fiq + 512
111 _stack_usr_end:      
112     .word stack_usr + 512
113 
114 .data
115 stack_svc:      
116     .space 512
117 stack_und:
118     .space 512
119 stack_abt:      
120     .space 512
121 stack_irq:      
122     .space 512
123 stack_fiq:      
124     .space 512
125 stack_usr:      
126     .space 512

interface.c:

/*
 * 按下K2,串口打印信息
 * 按下K3,LED点亮,再按熄灭
 * */

#include "exynos_4412.h"

void do_irq()
{
    /* 从中断控制器中获取当前中断的中断号 */
    unsigned int IrqNum = CPU0.ICCIAR & 0x3FF;
    switch(IrqNum)
    {
        case 57:
            printf("Key2 pressed\n");  /* 另一个文件实现的重定向到UART的printf */
            EXT_INT41_PEND = (1<<1);    /* 把中断控制器的挂起清除 */
            /* 告诉中断控制器当前中断已经处理完成 */
            /* 把当前中断的中断号写回中断控制器 */
            CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3FF) | 57;
            break;
        case 58:
            LED2_Turn();
            EXT_INT41_PEND = (1<<2);    /* 把中断控制器的挂起清除 */
            /* 告诉中断控制器当前中断已经处理完成 */
            /* 把当前中断的中断号写回中断控制器 */
            CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3FF) | 58;

            break;
        default:
            break;
    }
}

void KEY2_INT_Init()
{
    /* 外围层次 */
    /* GPX1_1设为中断功能 */
    GPX1.CON = GPX1.CON | (0xF<<4);
    /* 下降沿触发 */
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7<<4)) | (0x2<<4);
    /* 打开中断,写 0 Enable */
    EXT_INT41_MASK = EXT_INT41_MASK & (~(0x1<<1));


    /* SOC内层 中断控制器 */
    /* 中断控制器全局使能,让中断控制器接收外部的中断信号 */
    ICDDCR = ICDDCR | 1;
    /* 中断控制器中使能57号中断 */
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<25);
    /* 设置57号中断发给CPU0处理 */
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF<<8)) | (0x01<<8);
    /* 打开中断控制器与CPU0的接口 */
    CPU0.ICCICR = CPU0.ICCICR | 1;

}

void KEY3_INT_Init()
{
    /* 外围层次 */
    /* GPX1_1 设为中断功能 */
    GPX1.CON = GPX1.CON | (0xF<<8);
    /* EXT_INT41_CON[2] 设为下降沿触发 */
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7<<8)) | (0x2<<8);
    /* EXT_INT41_MASK[2] 打开中断,写 0 Enable */
    EXT_INT41_MASK = EXT_INT41_MASK & ~(0x1<<2);


    /* SOC内层 中断控制器 */
    /* 中断控制器全局使能,让中断控制器接收外部的中断信号 */
    ICDDCR = ICDDCR | 1;
    /* 中断控制器中使能58号中断 */
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<26);
    /* 设置58号中断发给CPU0处理 */
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF<<16)) | (0x1<<16);
    /* 打开中断控制器与CPU0的接口 */
    CPU0.ICCICR = CPU0.ICCICR | 1;

}


void LED2_Init()
{
    GPX2.CON = GPX2.CON & (~(0xF<<28)) | (0x1<<28);
}

void LED2_On()
{
    GPX2.DAT |= (1<<7);
}

void LED2_Off()
{
    GPX2.DAT &= (~(1<<7));
}

void LED2_Turn()
{
    if(GPX2.DAT & (1<<7))/* 条件为真,则关掉LED */
        LED2_Off();
    else
        LED2_On();
}

int main()
{
    KEY2_INT_Init();
    KEY3_INT_Init();
    LED2_Init();
    while(1);
    return 0;
}

实验现象

按下K2,终端会打印信息: Key2 pressed。

按下K3,LED2会打开,再次按下LED2会熄灭。

 



这篇关于ARM接口技术——中断控制器的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程