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

时间:2023-02-07 18:47:46

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. 恢复被打断的程序
    嵌入式Linux应用开发完全手册(三)中断

2440中断处理流程

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

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

9.2 2440中断控制寄存器

SUBSRCPND

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

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

INTSUBPND

嵌入式Linux应用开发完全手册(三)中断
某位被置1的时候,对应中断被屏蔽

SRCPND

嵌入式Linux应用开发完全手册(三)中断
嵌入式Linux应用开发完全手册(三)中断
处于pending状态的中断源。
来源有两类,参考上文,带有子中断的,和不带有子中断的。
清除某一位,需要对某一位写1而不是清0.

INTMSK

嵌入式Linux应用开发完全手册(三)中断
嵌入式Linux应用开发完全手册(三)中断
屏蔽SRCPND中的中断源。
某位写1即屏蔽该中断,只能屏蔽IRQ,不能屏蔽FIQ。

INTMOD

嵌入式Linux应用开发完全手册(三)中断
嵌入式Linux应用开发完全手册(三)中断
某位写1,即设定该中断位FIQ,见上文,同时只能有一个中断设置位FIQ。
一般设置最紧急的中断位FIQ。

PRIORITY

嵌入式Linux应用开发完全手册(三)中断
嵌入式Linux应用开发完全手册(三)中断
- 优先级寄存器控制优先级仲裁器的行为
嵌入式Linux应用开发完全手册(三)中断

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

INTPND

嵌入式Linux应用开发完全手册(三)中断
嵌入式Linux应用开发完全手册(三)中断
经过优先级筛选之后,从SRCPND中选出的中断,在INTPND中设置相应的位为1.
同一时间,只能有一位是1.
要清除这个中断,需要将相应的位置1,而不是清零,通过INTPND = INTPND 操作即可。

INTOFFSET

嵌入式Linux应用开发完全手册(三)中断
跟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