嵌入式Linux应用开发完全手册(三)中断

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanchunsheng/article/details/77152522

9 中断体系结构

9.1 ARM中断体系

ARM CPU工作模式和状态

  • 工作模式,7种,1种用户模式,其他6选中特权模式
    • usr 用户模式,ARM处理器正常的工作模式
    • fiq 快速中断模式,高速数据传输或者通道处理
    • irq 中断模式,通用中断处理
    • svc 管理模式,操作系统使用的保护模式
    • abt 数据访问终止模式,数据或指令预取终止时进入该模式,用于虚拟存储及存储保护
    • sys 系统模式,运行具有特权的操作系统任务
    • und 未定义指令终止模式,未定义的指令得到执行的时候进入该模式,可用于支持硬件协处理器的软件仿真
      大多数程序运行于用户模式,进入特权模式是为了处理中断、异常,或者访问被保护的系统资源。
  • 工作状态,2种
    • ARM状态,处理器执行32位的ARM指令
    • Thumb状态,处理器执行16位的Thumb指令
      CPU一上电就处于ARM状态,无需关心CPU状态。
  • 寄存器
    • 同一时刻可见的寄存器有17个
      • r0 - r12
      • r13 sp
      • r14 lr
      • r15 PC
    • 不同模式下并不会复用所有的寄存器
      • fiq r8 - r14 是独立的
      • svc,abt,irq,und r13, r14 是独立的
      • fiq,svc,abt,irq,und SPSR是独立的
    • CPSR各位含义,详见CPSR
      • N 结果是否为负数
      • Z 运算结果是否为0
      • C 进位/错位/移位溢出
      • V 溢出
      • I,F IRQ中断禁止,FIQ中断禁止
      • T Thumb状态
      • mode CPU当前工作模式

异常进入和返回流程

  • 异常进入
    1. 异常工作模式下的lr保存进入前的下一个指令地址
    2. CPSR复制到SPSR
    3. 设置CPSR到当前异常的工作模式
    4. 设置PC到这个异常的异常向量表入口地址
  • 异常返回
    1. 异常模式下的LR,减去适当的值,设置到PC
    2. SPSR的值恢复到CPSR

中断处理过程

  1. 中断控制器汇集各类外设发出的中断信号,通知CPU
  2. CPU保存当前程序运行环境,调用中断服务程序ISR来处理这些中断
  3. ISR中通过读取中断控制器、外设的相关寄存器识别中断源,进行相应处理
  4. 清除中断,读写中断控制器和外设寄存器
  5. 恢复被打断的程序
    Markdown

2440中断处理流程

Markdown

  • 中断源
    • 含有自子中断的中断源,上图可以看到,子中断先过一遍mask,再设置SRCPND
    • 不含有子中断的中断源,直接设置SRCPND
    • 中断屏蔽
    • 子中断先经过INTSUBMASK判断
    • 不含有子中断和经过判断的子中断,再过一遍INTMASK
    • 优先级
    • 如果是FIQ,INTMOD是1,不用经过优先级选择,直接执行。FIQ只能分配一个。
    • 如果是IRQ,需要经过优先级选择,最高优先级的中断,被设置到INTPND
    • 中断执行
    • ISR(中断处理程序)读INTPND或者INTOFFSET确定中断源

9.2 2440中断控制寄存器

SUBSRCPND

Markdown

  • 2440有15个子中断源,能看到分为5组,分别是这5个中断的子中断
    • INT_WDT_AC97 看门狗
    • INT_CAM 摄像头
    • INT_ADC 触摸板
    • INT_UART0, UART1, UART2 串口
  • 发生中断的时候相应的位被自动设置成1
  • 想要清除的话,需要再写入对应位置的1,请注意不是清零,是写1

INTSUBPND

Markdown
某位被置1的时候,对应中断被屏蔽

SRCPND

Markdown
Markdown
处于pending状态的中断源。
来源有两类,参考上文,带有子中断的,和不带有子中断的。
清除某一位,需要对某一位写1而不是清0.

INTMSK

Markdown
Markdown
屏蔽SRCPND中的中断源。
某位写1即屏蔽该中断,只能屏蔽IRQ,不能屏蔽FIQ。

INTMOD

Markdown
Markdown
某位写1,即设定该中断位FIQ,见上文,同时只能有一个中断设置位FIQ。
一般设置最紧急的中断位FIQ。

PRIORITY

Markdown
Markdown
- 优先级寄存器控制优先级仲裁器的行为
Markdown

  • 2440的优先级仲裁,分为2个阶段
    • 1级仲裁,即仲裁器0到仲裁器5,小组赛,选择出优胜者
    • 2级仲裁,即仲裁器6,决赛,各 小组选出的中断再经过决赛选择总冠军
  • 每组的优先级顺序可以配置,通过ARB_SLE
  • 每组的优先级模式可以配置,是固定不变的,还是轮转的
    • 固定不变的,按照ARB_SLE的配置顺序
    • 轮转的,已经被服务的中断,其优先级降到倒数第二,仅高于REQ5
    • 即使轮转,最高和最低,REQ0和REQ5都不会变化

INTPND

Markdown
Markdown
经过优先级筛选之后,从SRCPND中选出的中断,在INTPND中设置相应的位为1.
同一时间,只能有一位是1.
要清除这个中断,需要将相应的位置1,而不是清零,通过INTPND = INTPND 操作即可。

INTOFFSET

Markdown
跟INTPND是联动的,INTPND对应位置1表示有中断等待处理,INTOFFSET这时设置成一个数字,跟INTPND表示的是同一个意思,编号为多少的中断正在等待处理。
清除SRCPND,INTPND寄存器的时候,INTOFFSET寄存器被自动清除。

9.3 实例

jz2440开发板上有4个按键,分别对应外部中断EINT0,EINT2,EINT11,EINT19,通过这4个按键操作LED灯。

  • 程序结构
    • 启动文件 head.s
    • C程序
      • 中断初始化 init.c
      • 中断例程 interrrupt.c
      • 主程序 main.c
  • 中断向量
    • 发生异常的时候,ARM核按照一个向量表决定如何跳转到响应的例程
    • 当前的CPU处理的方式是在向量表的对应入口存放跳转指令
    • 较新的CPU,或者第三方定制的CPU,也有可能存放的是例程地址,而不是指令
    • 如果是指令,可以接受的指令如下
      • b [address]
      • ldr pc,[pc,#offset]
      • ldr pc,[pc,#-ff0]
      • mov pc,#immediate
    • 向量位置
      • reset svc 0x00
      • undef und 0x04
      • SWI svc 0x08
      • I-abt abt 0x0C
      • D-abt abt 0x10
      • 未定义 未定义 0x14
      • IRQ IRQ 0x18
      • FIQ FIQ 0x1C

head.s

[head.s]
.extern main
.text
.global _start
_start:                     @ 这个例子里边,因为使用了IRQ,所以开始的部分按照IRQ的要求设置中断向量,从地址0开始
    b Reset                 @ 0x00, reset异常,上电首先执行
HandleUndef:    
    b HandleUndef           @ 0x04, 未定义异常,一下发生的异常,如果没有处理例程,就直接在对应位置死循环
HandleSWI:  
    b HandleSWI             @ 0x08, SWI软中断异常
HandlePreFetchAbort:
    b HandlePreFetchAbort   @ 0x0C, 预取指异常
HandleDataAbort:
    b HandleDataAbort       @ 0x10, 数据访问异常
HandleReserved:
    b HandleReserved        @ 0x14,未定义
    b HandleIRQ             @ 0x18, 中断处理
HandleFIQ:
    b HandleFIQ             @ 0x1c,快速中断

Reset:
    ldr r0,=0x53000000      @ 关闭看门狗
    mov r1,#0
    str r1,[r0]
    ldr sp,=4096            @ 设置栈指针,因为下面都是C函数,所以需要设置栈指针
    msr cpsr_c,#0xd2        @ 进入中断模式,CPSR 11010010
                            @ 为什么不是cpsr,而是cpsr_c,因为cpsr_c表示cpsr的低8位
                            @ IF都设置上了,IRQ和FIQ屏蔽,模式10010,IRQ模式
    ldr sp,3072             @ 设置IRQ模式的栈指针,见上文,寄存器的拷贝
    msr cpsr_c,#0xdf        @ 进入系统模式,模式11111
    ldr dp,=4096            @ 设置系统模式的栈指针,CPU复位之后,处于系统模式

    bl init_led
    bl init_irq
    msr cpsr_c,#5f          @ 打开I,IRQ去掉屏蔽

    ldr lr,=halt_loop
    ldr pc,=main
halt_loop:
    b halt_loop
HandleIRQ:
    sub lr,lr,#4            @ 返回地址
    stmdb sp!,{r0-r12,lr}   @ 保存使用到的寄存器,!的含义是sp随着数据传送的改变而改变
                            @ 现在sp,已经是IRQ模式的sp
                            @ IRQ模式的sp,比系统模式少1000字节,啥意思,是怕覆盖了系统模式下的栈数据?
    ldr lr,=int_return
    ldr pc,=EINT_Handle     @ ISR例程,在interrupt.c中实现
int_return:
    ldmia sp!,{r0-r12,lr}^  @ 中断返回,^的意思是将SPSR恢复到CPSR 

init.c

init.c程序中设置中断寄存器的状态。
用到的定义,放在头文件int_key_led.h中。

[int_key_led.h]
#ifndef _INT_KEY_LED_H

#define _INT_KEY_LED_H

/*
 * LED1,LED2,LED4对应GPF4、GPF5、GPF6
 */
#define GPF_OUT(x)  (1 << ((x) * 2))
#define GPF_MSK(x)  (3 << ((x) * 2))
#define GPG_MSK(x)  GPF_MSK(x)

/*
 * S2,S3,S4对应GPF0、GPF2、GPG3
 */
#define GPF_EINT(x) (2 << ((x) * 2))
#define GPG_EINT(x) GPF_EINT(x)

#define GPFCON      (*(volatile unsigned long *)0x56000050)
#define GPFDAT      (*(volatile unsigned long *)0x56000054)
#define GPGCON      (*(volatile unsigned long *)0x56000060)
#define INTOFFSET   (*(volatile unsigned long *)0x4a000014)
#define EINTMASK    (*(volatile unsigned long *)0x560000a4)
#define PRIORITY    (*(volatile unsigned long *)0x4a00000c)
#define INTMSK      (*(volatile unsigned long *)0x4a000008)
#define EINTPEND    (*(volatile unsigned long *)0x560000a8)
#define SRCPND      (*(volatile unsigned long *)0x4a000000)
#define INTPND      (*(volatile unsigned long *)0x4a000010)

/* LED灯对应的二进制数字,可以表示0到7 */
#define LED_NUM(x)  (~(((x) & ~((~0) << 3)) << 4))
#define LED_MSK(x)  (1 << ((x) + 4))

#endif
/*
 * init.c: 进行一些初始化
 */ 

#include "int_key_led.h"

void init_led(void)
{
    // LED1,LED2,LED4对应的3根引脚设为输出
    GPFCON &= ~(GPF_MSK(4) | GPF_MSK(5) | GPF_MSK(6));
    GPFCON |= GPF_OUT(4) | GPF_OUT(5) | GPF_OUT(6);
}

/*
 * 初始化GPIO引脚为外部中断
 * GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
 */ 
void init_irq( )
{
    // S2,S3对应的2根引脚设为中断引脚 EINT0,ENT2
    GPFCON &= ~(GPF_MSK(0) | GPF_MSK(2));
    GPFCON |= GPF_EINT(0) | GPF_EINT(2);

    // S4对应的引脚设为中断引脚EINT11
    GPGCON &= ~GPF_MSK(3);
    GPGCON |= GPF_EINT(3);

    // 对于EINT11,需要在EINTMASK寄存器中使能它
    EINTMASK &= ~(1 << 11);

    /*
     * 设定优先级:
     * ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ3,即EINT0 > EINT2
     * 仲裁器1、6无需设置
     * 最终:
     * EINT0 > EINT2 > EINT11即K2 > K3 > K4
     */
    PRIORITY = (PRIORITY & ((~1) | (3 << 7))) | (0 << 7) ;

    // EINT0、EINT2、EINT8_23使能
    INTMSK &= (~(1 << 0)) & (~(1 << 2)) & (~(1 << 5));
}

interrupt.c

函数EINT_Handle的实现。

#include "int_key_led.h"

void EINT_Handle()
{
    unsigned long oft = INTOFFSET;
    unsigned long val;

    GPFDAT &= ~(LED_MSK(0) | LED_MSK(1) | LED_MSK(2));
    GPFDAT |= LED_NUM(oft);

    //清中断
    if( oft == 5 ) 
        EINTPEND = (1<<11);   // EINT8_23合用IRQ5
    SRCPND = 1 << oft;
    INTPND = 1 << oft;
}

main.c

死循环。

int main()
{
    while(1);
    return 0;
}

Makefile

src := $(shell ls *.c *.s)
obj := $(patsubst %.s, %.o, $(src))
obj := $(patsubst %.c, %.o, $(obj))
int_key_led.bin : $(obj)
    arm-linux-ld -Ttext 0x00000000 $^ -o int_key_led_elf
    arm-linux-objcopy -O binary -S int_key_led_elf int_key_led.bin
%.o : %.c
    arm-linux-gcc -c -o $@ $<
%.o : %.s
    arm-linux-gcc -c -o $@ $<
clean :
    rm *.o *_elf *.bin

猜你喜欢

转载自blog.csdn.net/guanchunsheng/article/details/77152522