程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Mini2440裸机开发之中断控制器

一、S3C2440上的中断

1.1 中断概述

S3C2440A 中的中断控制器接受来自60 个中断源的请求。提供这些中断源的是内部外设,如DMA 控制器、 UART、IIC 等等。在这些中断源中,UARTn、AC97 和EINTn 中断对于中断控制器而言是“或”关系。

当从内部外设和外部中断请求引脚收到多个中断请求时,中断控制器在仲裁步骤后请求ARM920T 内核的FIQ 或IRQ。

1.2 中断过程

(1) 如果是不带子中断的内部中断

如果是不带子中断的内部中断发生后,中断发生后SRCPND相应位置1,如果没有被INTMSK屏蔽,那么等待进一步处理 。

(2) 如果是带子中断的内部中断

如果是带子中断的内部中断,中断发生后SUBSRCPND相应位置1,如果没有被INTSUBMSK屏蔽,那么SRCPND相应位置1,等待进一步处理。

几个SUBSRCPND可能对应同一个SRCPND,SRCPND和SUBSRCPND对应关系:

SRCPND SUBSRCPND
INT_UART0 INT_RXD0,INT_TXD0,INT_ERR0
INT_UART1 INT_RXD1,INT_TXD1,INT_ERR1
INT_UART2 INT_RXD2,INT_TXD2,INT_ERR2
INT_ADC INT_ADC_S, INT_TC
INT_CAM INT_CAM_C, INT_CAM_P
INT_WDT_AC97 INT_WDT, INT_AC97

(3) 如果是外部中断

 EINT0-EINT3发生后SRCPND相应位置1,如果没有被INTMSK屏蔽,那么等待进一步处理。

EINT4-EINT23发生后EINTPEND相应位置1,如果没有被EINTMASK屏蔽,那么SRCPND相应位EINT4-7或EINT8-23置1,如果没有被INTMSK屏蔽,等待进一步处理。

几个EINTPEND对应同一个SRCPND,对应表如下:

SRCPND EINTPEND
EINT0 EINT0
EINT1 EINT1
EINT2 EINT2
EINT3 EINT3
EINT4~7 EINT4-EINT7
EINT8~23 EINT8-EINT23

1.3 中断处理逻辑

三种中断都等待进一步处理了。接下来从SRCPND往下看,看INTMSK。如果中断被屏蔽了,就不用说了(注意:快中断也能被屏蔽)。如果没有被屏蔽,那看一下中断模式:

  • 如果中断模式是快中断,那么直接 出来进入FIQ;
  • 如果是普通中断,那么SRCPND可以有多位置1(FIQ 只能有一个),这时就会经过PRIORITY选出一个优先级高的,然后把根据选 出的中断把INTPND相应位置1(注意:只能选出一个),进入IRQ,让CPU处理。

1.4 中断开启

  • 如果是不带子中断的内部中断,只需设置INTMSK,让它不屏蔽中断就可以了;
  • 如果是带子中断的内部中断,需设置INTSUBMSK和INTMSK,让它门不屏蔽中断就可以了;
  • 如果是外部中断,对于EINT4-23需要设置EINTMASK和INTMSK。对于EINT0-EINT3只需设置INTMSK;

1.5 中断清除

  • 如果是不带子中断的内部中断,只需清除SRCPND、INTPND,注意清除需位置1。
  • 如果是带子中断的内部中断,需清除SRCPND和SUBSRCPND、INTPND,注意先清除SUBSRCPND,再清除SRCPND。 因为,如果你先清除SRCPND的话,然后在清除SUBSRCPND的过程中,SRCPND会以为又有中断发生, 又会置1,也就是说一次中断会响应两次。所以必须先掐断源头。
  • 如果是外部中断,对于EINT4-23需要清除EINTPEND和SRCPND、INTPND(同样注意顺序)。对于EINT0-EINT3只需清除SRCPND、INTPND。

二、中断寄存器

在阅读下面内容之前,你需要了解ARM的寄存器体系,其中主要包CPSR 和 SPSR 寄存器,如果不了解,可以看我之前的博客介绍,嵌入式Linux之常用ARM汇编

2.1 程序状态寄存器(PSR)的F位和I位

如果ARM920T CPU 中的PSR 的F位被置位为1,CPU 不会接受来自中断控制器的快中断请求(FIQ)。同 样的如果PSR 的I 位被置位为1,CPU 不会接受来自中断控制器的中断请求(IRQ)。因此,中断控制器可以通过清除PSR 的F位和I位为0,并且设置INTMSK 的相应位为0来接收中断。同理我们关中断也包含两步:

(1) 我们可以开启SVC模式,关闭fiq,irq中断,如下:

set_svcmode:
    mrs  r0,cpsr               /* r0 = cpsr */ 
    bic  r0,r0,#0x1f           /* M[4:0]清0 */
    orr  r0,r0,#0xd3           /* 设置为SVC模式,并关闭fiq,irq中断 */
    msr  cpsr,r0               /* cpsr = r0 */
    mov  pc,lr                 /* bl指令将下一条指令地址复制到了lr,子程序返回  */ 

我们先用bit指令将处理器模式为[4:0]请零,然后采用ORR或指定将模式位设置位10011,即SVC模式,同时将[7:6]位置1,关闭fiq、irq中断。

(2) 设置INTMASK屏蔽中断

disable_interrupt:
        mvn  r1,#0x00              /*   取反赋值 r1 = 0xffffffff   */
        ldr  r0,=0X4A000008
        str  r1,[r0]                        /*   INTMASK每一位均写1  屏蔽中断 
        mov  pc,lr

2.2 中断模式(INTMOD)

ARM920T有两种中断模式的类型:FIQ 或IRQ。所有中断源在中断请求时决定使用哪种类型。

INTMOD寄存器由32 位组成,其每一位都都涉及一个中断源。如果某个指定为被设置为1,则在FIQ(快中断)模式 中处理相应中断。否则则在IRQ 模式中处理。

寄存器 地址 R/W 描述 复位值
INTMOD 0X4A000004 R/W

中断模式寄存器

0:IRQ模式   1:FIQ模式

0x00

寄存器的每一位和2.5节每一个中断源一一对应,比如[31]:INT_ADC、[30]:INT_RTC。

注意:如果中断模式在INTMOD 寄存器中设置为FIQ 模式,则FIQ 中断将不会影响INTPND 和INTOFFSET 寄存 器。这种情况下,这2 个寄存器只对IRQ 中断源有效。

2.3 中断挂起寄存器(SRCPND、INTPND)

S3C2440A 有两个中断挂起寄存器:源挂起寄存器(SRCPND)和中断挂起寄存器(INTPND)。这些挂起寄存器表明一个中断请求是否为挂起。当中断源请求中断服务,SRCPND 寄存器的相应位被置位为1,并且同时在仲 裁步骤后INTPND 寄存器仅有1 位自动置位为1。如果屏蔽了中断,则SRCPND 寄存器的相应位被置位为1。这 并不会引起INTPND 寄存器的位的改变。当INTPND 寄存器的挂起位为置位,每当I 标志或F 标志被清除为0 中 断服务程序将开始。SRCPND 和INTPND 寄存器可以被读取和写入,因此服务程序必须首先通过写1 到SRCPND寄存器的相应位来清除挂起状态并且通过相同方法来清除INTPND 寄存器中挂起状态。

寄存器 地址 R/W 描述 复位值
SRCPND 0X4A000000 R/W

指示中断请求状态

0:中断未被请求  1:中断源声明了中断请求

0x00

寄存器的每一位和2.5节每一个中断源一一对应,比如[31]:INT_ADC、[30]:INT_RTC。

中断挂起寄存器中32 位的每一位都表明了是否相应未屏蔽并且正在等待中断服务的中断请求具有最高的优先 级。当INTPND 寄存器在优先级逻辑后被定位了,只有1 位可以设置为1 并且产生中断请求IRQ 给CPU。IRQ 的 中断服务程序中可以读取此寄存器来决定服务32 个中断源的哪个源。

寄存器 地址 R/W 描述 复位值
INTPND 0X4A000010 R/W

指示中断请求状态

0:中断未被请求  1:中断源声明了中断请求

0x7F

寄存器的每一位和2.5节每一个中断源一一对应,比如[31]:INT_ADC、[30]:INT_RTC。

注意:如果FIQ 模式中断发生,则INTPND 的相应位将不会打开因为INTPND 寄存器只对IRQ 模式中断可见。

2.4 中断屏蔽寄存器(INTMASK)

此寄存器表明如果中断相应的屏蔽位被置位为1 则禁止该中断。如果某个INTMSK 的中断屏蔽位为0,将正常 服务中断。如果INTMSK 的中断屏蔽位为1 并且产生了中断,将置位源挂起位。

寄存器 地址 R/W 描述 复位值
INTMASK 0X4A000008 R/W

决定屏蔽哪个中断源。被屏蔽的中断源将不会服务

0:中断服务可用  1:屏蔽中断服务

0xFFFFFFFF

寄存器的每一位和2.5节每一个中断源一一对应,比如[31]:INT_ADC、[30]:INT_RTC。

2.5 中断源

中断控制器支持60个(EINT4~8、EINT8~23算多个)中断源,并分为6个仲裁组ARB5、ARB4、ARB3、ARB2、ARB1、ARB0。

2.6 中断优先级

每个仲裁器可以处理基于1 位仲裁器模式控制(ARB_MODE)和选择控制信号(ARB_SEL)的2 位的6 个中断请求,如下:

  • 如果ARB_SEL 位为00b,优先级顺序为REQ0、REQ1、REQ2、REQ3、REQ4 和REQ5。
  • 如果ARB_SEL 位为01b,优先级顺序为REQ0、REQ2、REQ3、REQ4、REQ1 和REQ5。
  • 如果ARB_SEL 位为10b,优先级顺序为REQ0、REQ3、REQ4、REQ1、REQ2 和REQ5。
  • 如果ARB_SEL 位为11b,优先级顺序为REQ0、REQ4、REQ1、REQ2、REQ3 和REQ5。

请注意仲裁器的REQ0 的优先级总是最高并且REQ5 的优先级总是最低。此外,通过改变ARB_SEL 位,可以轮换REQ1 到REQ4 的顺序。

此处,如果ARB_MODE 位被设置为0,ARB_SEL 位不能自动改变,这使得仲裁器操作在固定优先级模式中 (注意即使在此模式中,也不能通过手动改变ARB_SEL 位来重新配制优先级)。另一方面,如果ARB_MODE 为 1,ARB_SEL 位会被轮换方式而改变,例如如果REQ1 被服务,ARB_SEL 位被自动改为01b 以便REQ1 进入到 最低的优先级。ARB_SEL 改变的详细结果如下:

  • 如果REQ0 或REQ5 被服务,ARB_SEL 位不会改变 ;
  • 如果REQ1 被服务,ARB_SEL 位被改为01b;
  • 如果REQ2 被服务,ARB_SEL 位被改为10b;
  • 如果REQ3 被服务,ARB_SEL 位被改为11b;
  • 如果REQ4 被服务,ARB_SEL 位被改为00b。

寄存器 地址 R/W 描述 复位值
PRIORITY 0X4A00000C R/W

IRQ 优先级控制寄存器

0x7F

寄存器位信息:

PRIORITY 描述 初始状态
ARB_SEL6 [20:19]

仲裁器组6 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_SEL5 [18:17]

仲裁器组5 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_SEL4 [16:15]

仲裁器组4 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_SEL3 [14:13]

仲裁器组3 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_SEL2 [12:11]

仲裁器组2 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_SEL1 [10:9]

仲裁器组1 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_SEL0 [8:7]

仲裁器组0 优先级顺序设置

00: REQ 0-1-2-3-4-5      01:REQ 0-2-3-4-1-5

10: REQ 0-3-4-1-2-5      11:REQ 0-4-1-2-3-5

00
ARB_MODE6 [6]

仲裁器组6 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1
ARB_MODE5 [5]

仲裁器组5 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1
ARB_MODE4 [4]

仲裁器组4 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1
ARB_MODE3 [3]

仲裁器组3 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1
ARB_MODE2 [2]

仲裁器组2 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1
ARB_MODE1 [1]

仲裁器组1 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1
ARB_MODE0 [0]

仲裁器组0 优先级轮换使能

0 :优先级不轮换  1:优先级轮换使能

1

2.7 中断偏移寄存器(INTOFFSET)

中断偏移寄存器中的值表明了是哪个IRQ 模式的中断请求在INTPND 寄存器中。此位可以通过清除SRCPND 和INTPND 自动清除。

寄存器 地址 R/W 描述 复位值
INTOFFSET 0x4A000014 R

指示IRQ 中断请求源

0x00

寄存器值和2.5节每一个中断源偏移量对应,比如31:INT_ADC、30:INT_RTC。

2.8 次级源挂起寄存器(SUBSRCPND)

可以通过写入数据到此寄存器来清除SUBSRCPND 寄存器的指定位。只有数据中那些被设置为1 的相应 SUBSRCPND 寄存器的位的位置才能被清除。数据中那些被设置为0 的相应位的位置则保持不变。

寄存器 地址 R/W 描述 复位值
SUBSRCPND 0X4A000018 R/W

指示中断请求状态

0:中断未被请求  1:中断源声明了中断请求

0x00

寄存器位信息:

SUBSRCPND 描述 初始状态
保留 [31:15] 未使用 0
INT_AC97 [14] 0:未请求    1:请求 0
INT_WDT [13] 0:未请求    1:请求 0
INT_CAM_P [12] 0:未请求    1:请求 0
INT_CAM_C [11] 0:未请求    1:请求 0
INT_ADC_S [10] 0:未请求    1:请求 0
INT_TC [9] 0:未请求    1:请求 0
INT_ERR2 [8] 0:未请求    1:请求 0
INT_TXD2 [7] 0:未请求    1:请求 0
INT_RXD2 [6] 0:未请求    1:请求 0
INT_ERR1 [5] 0:未请求    1:请求 0
INT_TXD1 [4] 0:未请求    1:请求 0
INT_RXD1 [3] 0:未请求    1:请求 0
INT_ERR0 [2] 0:未请求    1:请求 0
INT_TXD0 [1] 0:未请求    1:请求 0
INT_RXD0 [0] 0:未请求    1:请求 0

映射到SRCPND:

SRCPND SUBSRCPND
INT_UART0 INT_RXD0,INT_TXD0,INT_ERR0
INT_UART1 INT_RXD1,INT_TXD1,INT_ERR1
INT_UART2 INT_RXD2,INT_TXD2,INT_ERR2
INT_ADC INT_ADC_S, INT_TC
INT_CAM INT_CAM_C, INT_CAM_P
INT_WDT_AC97 INT_WDT, INT_AC97

2.9 中断次级屏蔽寄存器(INTSUBMSK)

此寄存器有11 位,其每一位都与一个中断源相联系。如果某个指定位被设置为1,则相应中断源的中断请求 不会被CPU 所服务(请注意即使在这种情况中,SRCPND 寄存器的相应位也设置为1)。如果屏蔽位为0,则可以 服务中断请求。

寄存器 地址 R/W 描述 复位值
INTSUBMSK 0X4A00001C R/W

决定屏蔽哪个中断源。被屏蔽的中断源将不会服务

0:中断服务可用  1:屏蔽中断服务

0xFFFF

寄存器每一位和2.8节中寄存器位信息一致。

INTSUBMSK 描述 初始状态
保留 [31:15] 未使用 0
INT_AC97 [14] 0:可服务   1:屏蔽 0
INT_WDT [13] 0:可服务   1:屏蔽 0
INT_CAM_P [12] 0:可服务   1:屏蔽 0
INT_CAM_C [11] 0:可服务   1:屏蔽 0
INT_ADC_S [10] 0:可服务   1:屏蔽 0
INT_TC [9] 0:可服务   1:屏蔽 0
INT_ERR2 [8] 0:可服务   1:屏蔽 0
INT_TXD2 [7] 0:可服务   1:屏蔽 0
INT_RXD2 [6] 0:可服务   1:屏蔽 0
INT_ERR1 [5] 0:可服务   1:屏蔽 0
INT_TXD1 [4] 0:可服务   1:屏蔽 0
INT_RXD1 [3] 0:可服务   1:屏蔽 0
INT_ERR0 [2] 0:可服务   1:屏蔽 0
INT_TXD0 [1] 0:可服务   1:屏蔽 0
INT_RXD0 [0] 0:可服务   1:屏蔽 0

三、外部中断

3.1 硬件资源

Mini2440开发板总共有6个用户测试用按键,它们均从CPU中断引脚直接引出,属于低电平触发,这些引脚也可以复用为GPIO和特殊功能口,为了用户把它们引出作为其他用途,这6个引脚也通过CON12引出,6个按键和CON12的定义如下:

  K1 K2 K3 K4 K5 K6
中断 EINT8 EINT11 EINT13 EINT14 EINT15 EINT19
复用GPIO GPG0 GPG3 GPG5 GPG6 GPG7 GPG11
特殊功能 - nSS1 SPIMISO1 SPIMOSI1 SPICLK1 TCLK1
对应CON12 1 2 3 4 5 6

3.2 端口G 控制寄存器(GPGCON,GPGDAT,GPGUP)

寄存器 地址 R/W 描述 复位值
GPGCON 0x56000060 R/W 配置端口G的引脚 0x00
GPGDAT 0x56000064 R/W 配置G的数据寄存器 -
GPGUP 0x56000068 R/W 端口G的上拉使能寄存器 0x00
保留 0x5600006C - 保留 -

GPGCON寄存器位信息

GPGCON 描述 初始状态
GPG15 [31:30] 00 = 输入 01 = 输出 10 = EINT[23] 11 = 保留 0
GPG14 [29:28] 00 = 输入 01 = 输出 10 = EINT[22] 11 = 保留 0
GPG13 [27:26] 00 = 输入 01 = 输出 10 = EINT[21] 11 = 保留 0
GPG12 [25:24] 00 = 输入 01 = 输出 10 = EINT[20] 11 = 保留 0
GPG11 [23:22] 00 = 输入 01 = 输出 10 = EINT[19] 11 = 保留 0
GPG10 [21:20] 00 = 输入 01 = 输出 10 = EINT[18] 11 = 保留 0
GPG9 [19:18] 00 = 输入 01 = 输出 10 = EINT[17] 11 = 保留 0
GPG8 [17:16] 00 = 输入 01 = 输出 10 = EINT[16] 11 = 保留 0
GPG7 [15:14] 00 = 输入 01 = 输出 10 =EINT[15] 11 = 保留 0
GPG6 [13:12] 00 = 输入 01 = 输出 10 = EINT[14]11 = 保留 0
GPG5 [11:10] 00 = 输入 01 = 输出 10 = EINT[13] 11 = 保留 0
GPG4 [9:8] 00 = 输入 01 = 输出 10 = EINT[12] 11 = 保留 0
GPG3 [7:6] 00 = 输入 01 = 输出 10 = EINT[11] 11 = 保留 0
GPG2 [5:4] 00 = 输入 01 = 输出 10 = EINT[10]11 = 保留 0
GPG1 [3:2] 00 = 输入 01 = 输出 10 = EINT[9]11 = 保留 0
GPG0 [1:0] 00 = 输入 01 = 输出 10 = EINT[8]11 = 保留 0

由上表可知,G端口的控制寄存器可以将每个引脚配置为四种模式:

  • 00:输入模式
  • 01:输出模式
  • 10:外部中断
  • 11:保留模式

配置端口G、K1~K6引脚功能复用为外部中断:

    //K1,K2,K3,K4,K5,K6 对应的6根引脚设为中断功能
    GPGCON &= ~((0x03 << 0) | (0x03 << 6) | (0x03 << 10) | (0x03 << 12) | (0x03 << 14) | (0x03 << 22));            /* 清零 */
    GPGCON |= (0x02 << 0) | (0x02 << 6) | (0x02 << 10) | (0x02 << 12) | (0x02 << 14) | (0x02 << 22);   

3.3 EXINTn(外部中断控制寄存器n)

24 个外部中断可以由多种信号触发方式所请求。EXTINT 寄存器为外部中断配制信号触发方式为电平触发或边沿触发,同时还配制信号触发极性。

为了确认电平中断,由于噪声滤波必须保持EXTINTn 引脚上有效逻辑电平至少40ns。

寄存器 地址 R/W 描述 复位值
EXINT0 0x56000088 R/W 外部中断控制寄存器0 0x000000
EXINT1 0x5600008C R/W 外部中断控制寄存器1 0x000000
EXINT2 0x56000090 R/W 外部中断控制寄存器2 0x000000

EXTINT0位信息:

EXTINT0 描述 初始状态
EINT7 [30:28]

设置EINT7 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT6 [26:24] 设置EINT6 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT5 [22:20] 设置EINT5 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT4 [18:16] 设置EINT4 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT3 [14:12] 设置EINT3 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT2 [10:8] 设置EINT2 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT1 [6:4] 设置EINT1 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
EINT0 [2:0] 设置EINT0 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000

EXTINT1位信息:

EXTINT1 描述 初始状态
FLTEN15 [31]

EINT15 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT15 [30:28]

设置EINT15 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN14 [27]

EINT14 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT14 [26:24] 设置EINT14 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN13 [23]

EINT13 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

 
EINT13 [22:20] 设置EINT13 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN12 [19]

EINT12 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT12 [18:16] 设置EINT12 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN11 [15]

EINT11 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT11 [14:12] 设置EINT11 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN10 [11]

EINT10 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT10 [10:8] 设置EINT10 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN9 [7]

EINT9 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT9 [6:4] 设置EINT9 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN8 [3]

EINT8 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT8 [2:0] 设置EINT8 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000

EXTINT2位信息:

EXTINT2 描述 初始状态
FLTEN23 [31]

EINT15 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT23 [30:28]

设置EINT15 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN22 [27]

EINT14 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT22 [26:24] 设置EINT14 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN21 [23]

EINT13 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

 0
EINT21 [22:20] 设置EINT13 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN20 [19]

EINT12 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT20 [18:16] 设置EINT12 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN19 [15]

EINT11 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT19 [14:12] 设置EINT11 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN18 [11]

EINT10 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT18 [10:8] 设置EINT10 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN17 [7]

EINT9 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT17 [6:4] 设置EINT9 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000
FLTEN16 [3]

EINT8 的滤波器使能

0 = 滤波器禁止 1 = 滤波器使能

0
EINT16 [2:0] 设置EINT8 的信号触发方式

000 = 低电平 001 = 高电平 01x = 下降沿触发
10x = 上升沿触发  11x = 双边沿触发

 000

我们的按键K1~K6按下去全部是低电平,EINT8、EINT11、EINT13、EINT14、EINT15、EINT19、默认低电平触发方式即可。

3.4 EINTMASK(外部中断屏蔽寄存器)

 寄存器信息:

寄存器 地址 R/W 描述 复位值
EINTMASK 0x560000A4 R/W 外部中断屏蔽寄存器 0x000FFFFF

寄存器位信息: 

EINTMASK 描述 初始状态
EINT23 [23]  0 = 使能中断 1 = 禁止中断  0
EINT22 [22]  0 = 使能中断 1 = 禁止中断  0
EINT21 [21] 0 = 使能中断 1 = 禁止中断  0
EINT20 [20]

0 = 使能中断 1 = 禁止中断

0
EINT19 [19] 

0 = 使能中断 1 = 禁止中断

 0
EINT18 [18]

0 = 使能中断 1 = 禁止中断

 0
EINT17 [17]

0 = 使能中断 1 = 禁止中断

0
EINT16 [16] 0 = 使能中断 1 = 禁止中断  0
EINT15 [15]

0 = 使能中断 1 = 禁止中断

 0
EINT14 [14] 0 = 使能中断 1 = 禁止中断  0
EINT13 [13]

0 = 使能中断 1 = 禁止中断

0
EINT12 [12] 0 = 使能中断 1 = 禁止中断  0
EINT11 [11]

0 = 使能中断 1 = 禁止中断

0
EINT10 [10] 0 = 使能中断 1 = 禁止中断  0
EINT9 [9]

0 = 使能中断 1 = 禁止中断

0
EINT8 [8] 0 = 使能中断 1 = 禁止中断  0
EINT7 [7]

0 = 使能中断 1 = 禁止中断

0
EINT6 [6] 0 = 使能中断 1 = 禁止中断  0
EINT5 [5]

0 = 使能中断 1 = 禁止中断

0
EINT4 [4] 0 = 使能中断 1 = 禁止中断  0
Reserved [3:0] 保留  

使能外部中断:

// 对于EINT8,11,13,14,15,19,需要在EINTMASK寄存器中使能它 清零,中断使能
    EINTMASK &= ~((1 << 8) | (1 << 11) | (1 << 13) | (1 << 14)) | (1 << 15)) | (1 << 19));

3.5 INTMASK

ENT8、ENT11、ENT13、ENT14、ENT15、ENT19、对应的中断源为EINT8_23,位于INTMASK的第五位:

     // EINT8~EINT23中断使能
    INTMSK &= ~BIT_EINT8_23;

3.6 外部中断初始化

将之前介绍的步骤总结下来,初始化代码如下:

 /*************************************************************
 *
 *  Function  : 初始化GPIO引脚为外部中断
 *              GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
 *              K1        K2        K3        K4        K5        K6
 *        中断    EINT8    EINT11    EINT13    EINT14    EINT15    EINT19
 *    复用GPIO    GPG0    GPG3    GPG5    GPG6    GPG7    GPG11
 *
 **************************************************************/
void eint8_23_int_init()
{

    //K1,K2,K3,K4,K5,K6 对应的6根引脚设为中断功能
    GPGCON &= ~((0x03 << 0) | (0x03 << 6) | (0x03 << 10) | (0x03 << 12) | (0x03 << 14) | (0x03 << 22));            /* 清零 */
    GPGCON |= (0x02 << 0) | (0x02 << 6) | (0x02 << 10) | (0x02 << 12) | (0x02 << 14) | (0x02 << 22);               /* 设置为中断功能 */

    // 对于EINT8,11,13,14,15,19,需要在EINTMASK寄存器中使能它 清零,中断使能
    EINTMASK &= ~((1 << 8) | (1 << 11) | (1 << 13) | (1 << 14)) | (1 << 15)) | (1 << 19));

    /*
     * 看2440手册Figure 14-2. Priority Generating Block
     * EINT8~EINT23的优先级是一样的
     * 所以不用设置PRIORITY了
     */
     // EINT8~EINT23中断使能
    INTMSK &= ~BIT_EINT8_23;
}

3.7 CPSR开启IRQ中断

将CPSR的bit[7] I位清零,开启IRQ总中断:

void  vector_enable()
{
    __asm__(
      "mrs r0,cpsr\n"                       /* r0 = cpsr */
      "bic r0,r0,#(0x01<<7 )\n"             /* cpsr 的I位清零  */
      "msr cpsr_c,r0\n"                     /* cpsr = r0 */
    );
}

3.8 EINT8_23_IRQHandler

EINT8~23外部中断触发后,我们需要跳到对应的中断处理程序(至于怎么跳到对应的中断处理程序后面介绍),这里想做以下事情:

  • 按下K1,LED1亮;
  • 按下K2,LED2亮;
  • 按下K3,LED3亮;
  • 按下K4,LED4亮,
  • 按下K5,所有LED熄灭;
  • 按下K6,所有LED点亮;
            
/*************************************************************************
 *
 * Function   : 中断源5  外部中断8至23
 *              执行之前,必须先始化led
 *
 *************************************************************************/ 
void  EINT8_23_IRQHandler()
{
    if (EINTPEND & (1 << 8))
    {
        // K1被按下
        GPBDAT &= ~(1 << 5);      // LED1点亮
    }

    if (EINTPEND & (1 << 11))
    {
        // K2被按下
        GPBDAT &= ~(1 << 6);      // LED2点亮
    }

    if (EINTPEND & (1 << 13))
    {
        // K3被按下
        GPBDAT &= ~(1 << 7);      // LED3点亮
    }

    if (EINTPEND & (1 << 14))
    {
        // K4被按下
        GPBDAT &= ~(1 << 8);      // LED4点亮
    }

    if (EINTPEND & (1 << 15))
    {
        // K5被按下
        GPBDAT |= (0xF << 5);    // 所有LED熄灭
    }

    if (EINTPEND & (1 << 19))
    {
        // K6被按下
        GPBDAT &= ~(0xF << 5);      // 全部点亮
     }

    //清中断
    EINTPEND |= (1 << 8) | (1 << 11) | (1 << 13) | (1 << 14);          /* 清中断标志位  */

    SRCPND |= BIT_EINT8_23;                                            /* 清中断标志位  */
    INTPND |= BIT_EINT8_23;                                            /* 清中断标志位  */
}

在中断处理函数的最后,我们进行了中断清除。其实在外部中断初始化函数中我们最好也进行一次中断清除。

四、串口中断

我们这里仍然以UART0为例,UART0中断包括:

SRCPND SUBSRCPND
INT_UART0 INT_RXD0,INT_TXD0,INT_ERR0

这里我们以开启串口0发送和接收中断为例进行讲解。

4.1 UART0中断初始化步骤

设定INTSUBMSK:

    // 测试发现,这俩必须同时开启才生效
    INTSUBMSK &= ~BIT_SUB_RXD0;                /* 打开UART0接收中断,使能中断 */
    INTSUBMSK &= ~BIT_SUB_TXD0;                /* 打开UART0发送中断,使能中断 */

设定INTMSK:

    INTMSK &= ~BIT_UART0;                      /* 关闭UART0中断屏蔽,总中断  */

4.2 串口中断初始化

这里我们在初始化函数开头进行了中断清除,初始化代码如下:

void uart_int_init()
{
    SUBSRCPND |= BIT_SUB_RXD0 | BIT_SUB_TXD0 | BIT_SUB_ERR0;       /* 向相应位置写1清除次级源挂起寄存器 */
    SRCPND |= BIT_UART0;                                            /* 向相应位置写1清除源挂起寄存器 */
    INTPND |= BIT_UART0;                                            /* 向相应位置写1清除挂起寄存器 */

    // 测试发现,这俩必须同时开启才生效
    INTSUBMSK &= ~BIT_SUB_RXD0;                /* 打开UART0接收中断,使能中断 */
    INTSUBMSK &= ~BIT_SUB_TXD0;                /* 打开UART0发送中断,使能中断 */
    INTMSK &= ~BIT_UART0;                      /* 关闭UART0中断屏蔽,总中断  */
}

4.3 CPSR开启IRQ中断

将CPSR的bit[7] I位清零,开启IRQ总中断:

void  vector_enable()
{
    __asm__(
      "mrs r0,cpsr\n"                       /* r0 = cpsr */
      "bic r0,r0,#(0x01<<7 )\n"             /* cpsr 的I位清零  */
      "msr cpsr_c,r0\n"                     /* cpsr = r0 */
    );
}

4.4 UART0_IRQHandler

在中断处理程序中,我们将接收到的数据保存到了UART0_RX_BUF缓冲区,我们定义缓冲区的最大长度UART_RX_LEN=1024:

u16  UART0_RX_STA = 0;         /* 自定义接收状态寄存器 */

/*************************************************************************************************
 *
 *       UARTx_RX_BUF[UART_RX_LEN]:保存接收到的数据
 *     UARTx_RX_STA             :自定义16位接收状态寄存器
                                   bit15            bit14              bit13~0
                                   接收完成标志位     接收到0x0D标志位   接收到有效数据的个数
                                   当接收到Enter(Enter由两个字节组成,回车符0x0D和换行符0x0A)时,
 *
 **************************************************************************************************/
u8 UART0_RX_BUF[UART_RX_LEN] = { 0 };        /* 接收缓冲区 */

并且定了一个接收状态寄存器,当状态寄存器最高位为1是,表示数据接收完成,当接收到0x0D,将位14位置1,位13~0为接收到有效数据的个数。

串口中断程序如下:

/*************************************************************************
 *
 * Function   : 中断源28   UART0 interrupt
 *              当接收到回车(回车由两个字节组成,回车符0x0D和换行符0x0A)时,
                表示一次数据接收完成
 *
 *************************************************************************/
void  UART0_IRQHandler()
{
    /***********************************************************************/
   /* MODEM [3] Modem interrupt generated.    0
      TXD [2] Transmit interrupt generated.   0
      ERROR [1] Error interrupt generated.    0
      RXD [0] Receive interrupt generated.    0
      Whenever one of above 4 bits is logical high (‘1’), each UART channel generates interrupt.
      This register has to be cleared in the interrupt service routine.
      You can clear specific bits of UINTP register by writing 1’s to the bits that you want to clear
    */
    /* 接收中断 */
    u8 temp;
    if (SUBSRCPND & BIT_SUB_RXD0)
    {
        temp = URXH0 & 0xFF;                         /* 获取接收到的数据 */
        if ((UART0_RX_STA & (1 << 15)) == 0)                  /* 接收数据未完成 */
        {
            if (UART0_RX_STA & (1 << 14))           /* 已经接收到了0x0D 回车符 */
            {
                if (temp != 0x0A)                    /* 接收到的数据有误 */
                    UART0_RX_STA = 0;                        /* 状态寄存器清零 */
                else
                {
                    UART0_RX_STA |= (1 << 15);         /* 已经接收到了0x0A 换行符,一次数据接收完成 */
                }
            }
            else                                                 /* 没有接收到0x0D 回车符 */
            {
                if (temp == 0x0D)                     /* 接收回车第一个字符 0x0D */
                {
                    UART0_RX_STA |= (1 << 14);          /* 设置状态寄存器 */
                }
                else                                    /* 接收到的是数据 */
                {
                    UART0_RX_BUF[UART0_RX_STA & 0x3FFF] = temp;       /* 保存数据 */
                    UART0_RX_STA++;                 /*  计算接收到的数据长度 */
                    if (UART0_RX_STA > (UART_RX_LEN - 1))
                        UART0_RX_STA = 0;               /* 接收数据超过最大长度,丢失数据,重新接收 */
                }
            }
        }
        /********************************************************/
        /* 用户自定义任务  */




        /*******************************************************/
    }


    /* 错误中断 */
    if (SUBSRCPND & BIT_SUB_ERR0)
    {
        
    }

    /* 发送中断 */
    if (SUBSRCPND & BIT_SUB_TXD0)
    {
        
    }


    SUBSRCPND |= BIT_SUB_ERR0 | BIT_SUB_TXD0 | BIT_SUB_RXD0;                     /* 清中断标志位  */
    SRCPND |= BIT_UART0;                                                         /* 清中断标志位  */
    INTPND |= BIT_UART0;                                                         /* 清中断标志位  */
}

需要注意的是,如果一行数据接收完成,我们没有去清自定义状态寄存器的最高位,那么接下来的数据无法接收到。

4.5 main

#include "led.h"
#include "uart.h"

int main()
{
    int i = 0;
    // 开启中断
    vector_enable();

    // 初始化
    led_init();
    uart_init();

    // 开启中断
    uart_int_init();
    eint8_23_int_init();

    while(1)
    {
        // 数据接收完成
        while (UART0_RX_STA & 0x8000)
        {
            // 遍历数据
            for (i = 0; i < (UART0_RX_STA & 0x3FFF); i++)
            {
                uart_putchar(UART0_RX_BUF[i]);
            }
            uart_putchar('\r\n');
            // 清空接收标志位 
            UART0_RX_STA = 0;
        }
  }
   return 0;
}

五 自定义中断向量

由于S3C2440不支持向量模式,因此我们只能自己去模拟一个中断向量表。什么是向量模式、非向量模式

  • 非向量模式:就是当中断产生时,都跳转到中断异常去,然后在这个中断异常中,编写程序,判断是哪一个中断产生,然后执行对应的中断处理程序;
  • 向量模式:每个中断源都有对应的中断地址,这样当中断产生时,直接跳转到对应的中断服务程序;

5.1 异常向量表

异常向量:不同的异常有不同的入口函数,那么这个异常入口函数的地址就是存放在该异常向量的位置。从该异常向量读取到的数据就是异常入口函数的地址。

异常向量表就是由异常向量组成的集合。2440异常向量表如下:

.text
.global _start
_start:    b   reset
    /* https://blog.csdn.net/shenlong1356/article/details/104602535 */ 
    /* 没有使用伪指令ldr  pc,=undefined_instruction,位置有关指令,获取标号地址,  该地址与链接地址有关 */
    /* 因为该指令会在内存开辟一空间,把undefined_instruction的地址(这个地址取决于链接地址)存放在内存空间中,然后ldr从内存[pc,#xx]取值赋值给pc   */
    /* ldr  pc,_undefined_instruction,位置无关指令   获取标号的值 */
    ldr   pc,_undefined_instruction   /*  从内存取und异常函数地址到PC  */
    ldr   pc,_software_instruction    /*  从内存取swi异常函数地址到PC */
    ldr   pc,_prefetch_abort          /*  从内存取预取指令异常异常函数地址到PC */
    ldr   pc,_data_abort              /*  从内存取访问内存异常函数地址到PC */
    ldr   pc,_not_used                /*  保留 */
    ldr   pc,_irq                     /*  从内存取irq异常函数地址到PC */
    ldr   pc,_fiq                     /*  从内存取fiq异常函数地址到PC */

异常向量表对应的地址如下图:

5.2 中断处理流程

当我们的按键中断、串口中断发生时,将会产生IRQ中断,cpu强制跳转到异常向量表上对应的_irq异常向量(0x18)去读取指令(这个是CPU强制执行的,不需要我们去控制)。

当跳转到irq异常向量(0x18)后,发现该处是一条跳转指令“ldr pc, _irq”, 那么会通过ldr绝对跳转指令跳到到真正的中断处理函数irq去执行。

/*  IRQ正常中断请求处理程序地址  */
_irq:        
    .word    irq 

进入irq中断处理程序前:

  •  LR_异常=PC + offset(具体多少看下图)
  • SPSR_irq=被中断前的程序模式CPSR;
    • CPSR:当前程序状态寄存器(Current Program Status Register) 反映程序处在那种状态;
    • SPSR:CPSR的备份寄存器 (Saved Program Status Register) 用来保存"被中断前的CPSR";
  • CPSR被设置成对应的异常模式;
  • 跳转到对应的异常向量去执行;

5.2 IRQ异常函数

我们先来看一下irq处代码:

/*  IRQ正常中断请求处理程序  二级向量表实现中断跳转  */
irq:        
     sub    sp,sp,#4              /* 栈地址-4 */
     stmdb  sp!,{r0-r1}           /* *(sp-4) = r1, *(sp-8) = r0, sp = sp - 8 */  
     ldr    r1,=INTOFFSET
       ldr    r1,[r1]               /* r1 = *INTOFFSET */
     mov    r0,#31                /* r0=31 */
     sub    r1,r0,r1              /* r1=31-r1 这一步是因为我的向量表对应的中断源编号是反向的 */
     ldr    r0,=vector            /* r0 = &vector */
     add    r0,r0,r1,LSL #2       /* r0 = r0 + r1 * 4  r0保存具体中断地址 */
     ldr    r0,[r0]               /* r0 = [r0] */
     str    r0,[sp,#8]            /* *(sp+8) = r0 */
     ldmia  sp!,{r0-r1,pc}        /* r0 = *sp、r1=sp+4、pc=*(sp+0x08) 跳转到中断处理程序 并且sp = sp + x0x08*/

这里主要是利用INTOFFSET得到我们中断的编号,然后到自定义的二级中断向量表查找对应的中断的地址,并到中断地址压入栈底,最后利用出栈跳转到中断处理地址。以串口0中断为例,流程大致如下图:

在UART_IRQ中,先将r0~r12、lr的值保存到栈中,其中lr是异常处理完后的返回地址。然后利用bl UART0_IRQHandler,将函数返回地址存储到LR中,然后跳转到UART0_IRQHandler。

UART0_IRQHandler函数是C语言写的,我们反汇编二进制文件,查看UART0_IRQHandler代码最开始和结束:

30000e58:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
30000e5c:    e28db000     add    fp, sp, #0    ; 0x0
30000e60:    e24dd014     sub    sp, sp, #20    ; 0x14
...
30000fe8:    e28bd000     add    sp, fp, #0    ; 0x0
30000fec:    e8bd0800     pop    {fp}
30000ff0:    e12fff1e     bx    lr

我们只关注最后一句指定bx lr,也就是返回到ldmfd sp!,{r0-r12,lr}指令处,这里对r0~r2、lr寄存器进行了恢复。

最后执行pc=lr-0x04,返回到恢复到异常前的指令地址。subs指令同时把spsr的值恢复到cpsr里。

六、代码下载

Young / s3c2440_project【5.int】

这个代码已经远超过4kb,并判断是NOR还是NAND启动,如果是NAND启动会将NAND代码拷贝到SDRAM中,并跳转到SDRAM中运行,这个代码只可以下载到NAND,以NAND方式启动运行。

参考文章:

[1]s3c2440裸机-异常中断(一. 异常、中断的原理与流程)

posted @ 2021-10-23 18:26  大奥特曼打小怪兽  阅读(322)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步