2440的中断体系结构
一、预备知识
(一)ARM 的7种工作模式:
User(usr用户模式) : 正常 ARM 程序执行状态
FIQ(fiq快中断模式) : 为支持数据传输或通道处理设计
IRQ(irq中断模式) : 用于一般用途的中断处理
Supervisor(svc管理模式) :当复位或软中断指令执行时将会进入这种模式
Abort(ab终止模式t) : 数据或指令预取中止后进入
Undefined(und未定义模式) : 当执行未定义指令时会进入这种模式
System(sys系统模式) : 操作系统的特权用户模式
除User(用户模式)是Normal(普通模式)外,其他6种都是Privilege(特权模式)。 Privilege中除Sys模式外,其余5种为异常模式。 各种模式的切换,可以是程序员通过代码主动切换(通过写CPSR寄存器);也可以是CPU在某些情况下自动切换。
(二)ARM 的37种通用寄存器
各种模式下权限和可以访问的寄存器不同(带有阴影表示该模式下特有的,所以在中断发生时候数据不需要保存)
(三)异常和中断的区别和联系
(1)针对SoC来说,发生复位、软中断、中断、快速中断、取指令异常、数据异常等,我们都统一叫异常。所以说:中断其实是异常的一种。
(2)异常的定义就是突发事件,打断了CPU的正常常规业务,CPU不得不跳转到异常向量表中去执行异常处理程序;中断是异常的一种,一般特指SoC内的内部外设产生的打断SoC常规业务,或者外部中断(SoC的GPIO引脚传回来的中断)。
(四)当CPU复位之后,进入SVC模式
当 nRESET 信号端变为低电平时,ARM920T 将放弃正在处理的指令,然后从递增(incrementing)字地址继续取指令。
当 nRESET 信号端变为高电平时,ARM920T 将:
1. 复制当前 PC 和 CPSR 的值覆盖到 R14_svc 和 SPSR_svc 中。未定义保存的 PC 和 SPSR 的值
2. 强制将 M[4:0]设置为 10011(管理模式),置位 CPSR 中的 I 和 F 位并清除 CPSR 的 T 位。
3. 强制 PC 从 0x00 (内部RAM)地址开始对下一条指令的取指。
4. 在 ARM 状态恢复执行。
二、如何让异常发生?
我们以IRQ和FIQ为例,讲解如何通过设置中断控制寄存器让中断顺利执行。
首先看下面的中断处理流程。
所有的中断源分为两类,一类是带有次寄存器的(with sub -register),另一类是不带次寄存器的(without sub -register)。
(1)、当带有次寄存器的中断源被触发,次级源挂起(SUBSRCPND)寄存器中相应的位自动置1。向该寄存器写数时清零。
同时检查中断次级屏蔽(INTSUBMSK)寄存器,看这种类型的中断源是否被屏蔽(写1屏蔽)
(2)当不带有次寄存器的中断源被触时,可以直接置位(SRCPND)寄存器的相关位
NOTE:SRCPND这个寄存器的既包含带有次寄存器的中断源,还包括不带次寄存器中断源的相关位。只不过带有次寄存器的中断源需要经过SUBSRCPND和INTSUBMSK寄存器才能到达SRCPND寄存器,而另一种可以直接到达SRCPND寄存器
其中,有6个是来源于带有次寄存器的中断,如下所示
同理,中断屏蔽(INTMSK)寄存器可以决定SRCPND寄存器中对应的中断请求是否被屏蔽I(写1屏蔽)。于此同时,INTERRUPT MODE (INTMOD)寄存器可以决定中断的类型,FIQ或者IRQ
如果是FIQ模式就可以直接被执行了,如果是IRQ模式还需要继续向下处理,主要包括以下步骤:
经过PRIORITY REGISTER 寄存器进行优先级判别(不进行详细展开,参考数据手册即可),进过中断优先级寄存器选出最高的中断之后,这个中断在INTPND寄存器中的相应位被置1,随后CPU进入中断模式处理他。同一时间内,该寄存器仅仅有一位被置1。在中断服务程序中可以根据这个位确定那个中断发生。向该寄存器写1清除中断位。其实还可以读取中断偏移(INTOFFSET)寄存器来判断中断服务程序正在处理哪个中断。这个寄存器被用来表示 INTPND寄存器中哪位被置1了,即 INTPND寄存器中位[X]为1时,INTOFFSET寄存器的值为x(x为0~31)在清除 SRCPND、 INTPND寄存器时, INTOFFSET寄存器被自动清除。
三、异常发生和结束的时候CPU会帮我们做哪些事情
- 发生异常时,我们的CPU会自动做以下几件事情:
1把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址)
它有可能是PC+4有可能是PC+8,到底是那种取决于不同的情况(这个地方跟流水线有关系,但由于是CPU自动运行的,所以不必深究)
2把CPSR保存在SPSR里面(当切换到某种异常模式的时候,spsr保存上一个工作模式的CPSR值,这样,当返回前一个工作模式的时候,再将spsr的值回复到cpsr中)
3修改CPSR的模式为进入异常模式(修改CPSR的M4~MO进入异常模式)
4跳到向量表(PC赋值,跳转到一个特定的地址去执行指令,用户可以在这个地址写跳转指令,跳转到真正的异常处理程序)
异常向量表如下:
- 当异常结束,异常处理程序将会:
1. 前面进入异常工作模式的时候,链接寄存器中保存了前一个工作模式的一个指令地址,将他减去一个适当的值之后赋值给PC寄存器
2. 复制 SPSR 的内容返回给 CPSR 中。
3. 如果在异常进入时置位了中断禁止标志位异常,清除中断禁止标志位。
四、在CPU自动帮助我们做的工作基础之上,我们还需要做哪些内容?
- 保护现场
- 设置IRQ模式下的栈
- 编写ISR(中断服务程序)
- 恢复现场
(1)保护现场关键是保存:中断处理程序的返回地址(保存到LR),r0-r12(cpsr是自动保存的)
(2)恢复现场主要是恢复:r0-r12,pc,cpsr
五、外部中断实例
1.makefile
all:
arm-linux-gcc -c -o start.o start.S
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o serial.o serial.c
arm-linux-gcc -c -o interrupt.o interrupt.c
arm-linux-ld -Ttext 0 start.o main.o init.o serial.o interrupt.o -o led_on.elf
arm-linux-objcopy -O binary -S led_on.elf led_on.bin
clean:
rm *.bin *.o *.elf
2.start.S
.text
.global _start
_start:
b reset /* vector 0 : reset */
b halt /* vector 4 : und */
b halt /* vector 8 : swi */
b halt /* vector 0x0c : prefetch aboot */
b halt /* vector 0x10 : data abort */
b halt /* vector 0x14 : reserved */
ldr pc, irq_addr /* vector 0x18 : irq */
b halt /* vector 0x1c : fiq */
irq_addr:
.word do_irq#注意理解.word伪指令的意思
reset:
//关闭看门狗
ldr r0, =0
ldr r1, =0x53000000
str r0, [r1]
//设置SVC模式的栈
ldr sp, =4096
//初始化时钟
bl clock_init
//跳转到MAIN函数执行
bl main
b .
halt:
b halt
.align 4#注意理解伪指令
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* sp_irq未设置, 先设置它 */
ldr sp, =3096
/* 保存现场 */
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr-4是异常处理完后的返回地址, 也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/* 处理irq异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
3.main.c
#include "s3c24xx.h"
void delay(void);
// 该函数要实现led闪烁效果
int main(void)
{
unsigned char c;
uart0_init(); // 波特率115200,8N1(8个数据位,无校验位,1个停止位)
// led初始化
/* 设置GPFCON让GPF4/5/6配置为输出引脚 */
GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
GPFCON |= ((1<<8) | (1<<10) | (1<<12));
interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
__asm__("mrs r0, cpsr");
__asm__("bic r0, r0, #(1<<7)");
__asm__("msr cpsr, r0"); // /* 清除I位, 使能总中断 */
while (1)
{
putc('a');
delay();
}
return 0;
}
void delay(void)
{
unsigned int i = 200000;
while (i--);
}
4.serial.c
#include "s3c24xx.h"
void uart0_init(void);
void putc(unsigned char c);
unsigned char getc(void);
int puts(const char *s);
#define TXD0READY (1<<2)
#define RXD0READY (1)
#define PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz
#define UART_CLK PCLK // UART0的时钟源设为PCLK
#define UART_BAUD_RATE 115200 // 波特率
#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
/*
* 初始化UART0
* 115200,8N1,无流控
*/
void uart0_init(void)
{
GPHCON &= ~((3<<4) | (3<<6));
GPHCON |= ((2<<4) | (2<<6));// GPH2,GPH3用作TXD0,RXD0
GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UFCON0 = 0x00; // 不使用FIFO
UMCON0 = 0x00; // 不使用流控
UBRDIV0 = UART_BRD; // 波特率为115200
}
/*
* 发送一个字符
*/
void putc(unsigned char c)
{
/* 等待,直到发送缓冲区中的数据已经全部发送出去 */
while (!(UTRSTAT0 & TXD0READY));
/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
UTXH0 = c;
}
/*
* 接收字符
*/
unsigned char getc(void)
{
/* 等待,直到接收缓冲区中的有数据 */
while (!(UTRSTAT0 & RXD0READY));
/* 直接读取URXH0寄存器,即可获得接收到的数据 */
return URXH0;
}
int puts(const char *s)
{
while (*s)
{
putc(*s);
s++;
}
}
5.interrupt.c
#include "s3c24xx.h"
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTMSK 用来屏蔽中断, 1-masked
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
* bit0-eint0
* bit2-eint2
* bit5-eint8_23
*/
/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
*/
/* 初始化中断控制器 */
void interrupt_init(void)
{
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */
/* 设置中断触发方式: 上升下降沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,19 */
EINTMASK &= ~((1<<11) | (1<<19));
}
/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
* 清除中断时, 写EINTPEND的相应位
*/
void key_eint_irq(int irq)
{
unsigned int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
if (irq == 0) /* eint0 : s2 控制 D12 */
{
if (val1 & (1<<0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1<<6);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<6);
}
}
else if (irq == 2) /* eint2 : s3 控制 D11 */
{
if (val1 & (1<<2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1<<5);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<5);
}
}
else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
{
if (val & (1<<11)) /* eint11 */
{
if (val2 & (1<<3)) /* s4 --> gpf4 */
{
/* 松开 */
GPFDAT |= (1<<4);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<4);
}
}
else if (val & (1<<19)) /* eint19 */
{
if (val2 & (1<<11))
{
/* 松开 */
/* 熄灭所有LED */
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
}
else
{
/* 按下: 点亮所有LED */
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
}
}
}
EINTPEND = val;
}
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数 */
if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
{
key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
}
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
6.init.c
/*
* init.c: 进行一些初始化
*/
#include "s3c24xx.h"
void clock_init(void);
#define S3C2440_MPLL_400MHZ ((92<<12)|(1<<4)|(1<<0))
/*
* 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV
* 有如下计算公式:
* S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
* 其中: m = MDIV + 8, p = PDIV + 2, s = SDIV
* 对于本开发板,Fin = 12MHz
* 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:4:8,
* FCLK=400MHz,HCLK=100MHz,PCLK=50MHz
*/
void clock_init(void)
{
LOCKTIME = 0xFFFFFFFF; // 使用默认值即可
CLKDIVN = 0x05; // FCLK:HCLK:PCLK=1:4:8
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0\n" /* 读出控制寄存器 */
"orr r1, r1, #0xc0000000\n" /* 设置为“asynchronous bus mode” */ //R1_nF:OR:R1_iA
"mcr p15, 0, r1, c1, c0, 0\n" /* 写入控制寄存器 */
);
MPLLCON = S3C2440_MPLL_400MHZ; /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
7.s3c24xx.h
/* WOTCH DOG register */
#define WTCON (*(volatile unsigned long *)0x53000000)
/* SDRAM regisers */
#define MEM_CTL_BASE 0x48000000
#define SDRAM_BASE 0x30000000
#define EXTINT0 (*(volatile unsigned long *)0x56000088) //External interrupt control register 0
#define EXTINT1 (*(volatile unsigned long *)0x5600008C) //External interrupt control register 1
#define EXTINT2 (*(volatile unsigned long *)0x56000090) //External interrupt control register 2
/* NAND Flash registers */
#define NFCONF (*(volatile unsigned int *)0x4e000000)
#define NFCMD (*(volatile unsigned char *)0x4e000004)
#define NFADDR (*(volatile unsigned char *)0x4e000008)
#define NFDATA (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT (*(volatile unsigned char *)0x4e000010)
/*GPIO registers*/
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPFUP (*(volatile unsigned long *)0x56000058)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
#define GPGUP (*(volatile unsigned long *)0x56000068)
#define GPHCON (*(volatile unsigned long *)0x56000070)
#define GPHDAT (*(volatile unsigned long *)0x56000074)
#define GPHUP (*(volatile unsigned long *)0x56000078)
/*UART registers*/
#define ULCON0 (*(volatile unsigned long *)0x50000000)
#define UCON0 (*(volatile unsigned long *)0x50000004)
#define UFCON0 (*(volatile unsigned long *)0x50000008)
#define UMCON0 (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
#define UTXH0 (*(volatile unsigned char *)0x50000020)
#define URXH0 (*(volatile unsigned char *)0x50000024)
#define UBRDIV0 (*(volatile unsigned long *)0x50000028)
/*interrupt registes*/
#define SRCPND (*(volatile unsigned long *)0x4A000000)
#define INTMOD (*(volatile unsigned long *)0x4A000004)
#define INTMSK (*(volatile unsigned long *)0x4A000008)
#define PRIORITY (*(volatile unsigned long *)0x4A00000c)
#define INTPND (*(volatile unsigned long *)0x4A000010)
#define INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define SUBSRCPND (*(volatile unsigned long *)0x4A000018)
#define INTSUBMSK (*(volatile unsigned long *)0x4A00001c)
/*external interrupt registers*/
#define EINTMASK (*(volatile unsigned long *)0x560000a4)
#define EINTPEND (*(volatile unsigned long *)0x560000a8)
/*clock registers*/
#define LOCKTIME (*(volatile unsigned long *)0x4c000000)
#define MPLLCON (*(volatile unsigned long *)0x4c000004)
#define UPLLCON (*(volatile unsigned long *)0x4c000008)
#define CLKCON (*(volatile unsigned long *)0x4c00000c)
#define CLKSLOW (*(volatile unsigned long *)0x4c000010)
#define CLKDIVN (*(volatile unsigned long *)0x4c000014)
/*PWM & Timer registers*/
#define TCFG0 (*(volatile unsigned long *)0x51000000)
#define TCFG1 (*(volatile unsigned long *)0x51000004)
#define TCON (*(volatile unsigned long *)0x51000008)
#define TCNTB0 (*(volatile unsigned long *)0x5100000c)
#define TCMPB0 (*(volatile unsigned long *)0x51000010)
#define TCNTO0 (*(volatile unsigned long *)0x51000014)
#define GSTATUS1 (*(volatile unsigned long *)0x560000B0)
实验现象:按下按键对应的灯亮,抬起对应的灯灭
上一篇: 原生JS封装变速移动函数