欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

2440的中断体系结构

程序员文章站 2022-03-13 17:24:42
...

一、预备知识

(一)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种通用寄存器

各种模式下权限和可以访问的寄存器不同(带有阴影表示该模式下特有的,所以在中断发生时候数据不需要保存)

2440的中断体系结构

(三)异常和中断的区别和联系


(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为例,讲解如何通过设置中断控制寄存器让中断顺利执行。

首先看下面的中断处理流程。

2440的中断体系结构

所有的中断源分为两类,一类是带有次寄存器的(with sub -register),另一类是不带次寄存器的(without sub -register)。

(1)、当带有次寄存器的中断源被触发,次级源挂起(SUBSRCPND)寄存器中相应的位自动置1。向该寄存器写数时清零。

2440的中断体系结构

同时检查中断次级屏蔽(INTSUBMSK)寄存器,看这种类型的中断源是否被屏蔽(写1屏蔽)

2440的中断体系结构

(2)当不带有次寄存器的中断源被触时,可以直接置位(SRCPND)寄存器的相关位

NOTE:SRCPND这个寄存器的既包含带有次寄存器的中断源,还包括不带次寄存器中断源的相关位。只不过带有次寄存器的中断源需要经过SUBSRCPND和INTSUBMSK寄存器才能到达SRCPND寄存器,而另一种可以直接到达SRCPND寄存器

2440的中断体系结构

其中,有6个是来源于带有次寄存器的中断,如下所示

2440的中断体系结构

 

同理,中断屏蔽(INTMSK)寄存器可以决定SRCPND寄存器中对应的中断请求是否被屏蔽I(写1屏蔽)。于此同时,INTERRUPT MODE (INTMOD)寄存器可以决定中断的类型,FIQ或者IRQ

2440的中断体系结构

如果是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赋值,跳转到一个特定的地址去执行指令,用户可以在这个地址写跳转指令,跳转到真正的异常处理程序)

异常向量表如下:

2440的中断体系结构

 

  • 当异常结束,异常处理程序将会:

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)

实验现象:按下按键对应的灯亮,抬起对应的灯灭

相关标签: ARM