【JZ2440笔记】裸机实验使用中断

目录

一、前言

二、实验目标

三、硬件连线

四、S3C2440中断体系

五、代码编写

六、实验总结

七、参考资料


一、前言

中断是打断当前程序执行,快速响应突发事件的一种机制。中断的触发源有很多种,比如外部引脚电平变化以及外设的各种事件中断等等,当中断发生时,S3C2440 CPU的PC指针跳往固定的中断向量地址处,执行中断处理函数,之后再返回到原先的程序断点处继续执行程序。

 

二、实验目标

采用按键中断的方式,通过JZ2440开发板上的三个独立按键分别控制开发板上的三个LED的亮灭。

 

三、硬件连线

独立按键连线如下:

 独立按键只用了三个,分别连接到了EINT0,EINT2,EINT11,即S3C2440的外部中断0,2和11。对应的真实物理引脚分别是GPF0,GPF2和GPG3。S3C2440的外部中断引脚不是随便选的,是定死了哪个引脚能用哪个外部中断的。这点上就比不上STM32这类单片机了,STM32是任意普通引脚都可以做外部中断的。而且STM32支持引脚功能的重映射,就是一个引脚上的各种功能可以映射到其他引脚上用,S3C2440貌似没看到有这个功能。又想了想,毕竟S3C2440是定位于跑操作系统的,和单片机的定位着重点不同,术业有专攻。

LED连线如下:

三个LED分别连接在GPF4,GPF5和GPF6上。

 

四、S3C2440中断体系

1、工作模式

ARM体系的CPU有以下7中工作模式:

(1)用户模式(usr):ARM处理器正常的程序执行状态。

(2)快速中断模式(fiq):用于高速数据传输或通道处理。

(3)中断模式(irq):用于通用的中断处理。

(4)管理模式(svc):操作系统使用的保护模式。

(5)数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储即存储保护。

(6)系统模式(sys):运行具有特权的操作系统任务。

(7)未定义指令终止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。

以上的模式可通过软件来进行切换,发生各类中断的时候CPU也会自动进入相应的模式。除用户模式外,其他6种工作模式都属于特权模式。大多数程序运行于用户模式,进入特权模式是为了处理中断、异常、或者访问被保护的系统资源。

在S3C2440中,这些不同的模式都有一组寄存器(r0到r15,CPSR和SPSR)与各自模式相对应,这些寄存器有些是共用的,有些是某个模式下独有的(如上图带三角阴影的寄存器),虽然寄存器名字都一样,但只是该模式下使用。这些寄存器除r15外都是通用寄存器,其中r13到r15这3个寄存器有特殊的意义,r13称为栈指针寄存器(SP),就是C语言用到的那个堆栈指针;r14称为程序连接寄存器(subroutime Link Register)或连接寄存器,当执行汇编指令BL进行子程序跳转的时候,r14中会得到r15(程序计数器PC)的备份,当子程序返回的时候,r14(lr寄存器)的值会赋给r15(PC指针),这就使得程序能够回到断点位置继续向下执行了。

每个模式都有独立的r13和r14寄存器,即每个模式都有独立的堆栈指针(SP)和连接寄存器(lr)。在不同模式间跳转的时候,需要将上一个模式的所有公用寄存器进行压栈操作,以便在之后可以再出栈恢复这些寄存器的值,达到模式恢复的效果。其中快中断模式fiq从r8到r15都是独立的,在进入该模式的时候需要保存的寄存器数目更少(r0到r7),所以进入该中断会更快。

还有第17个寄存器CPSR,即“当前程序状态寄存器”(Current Program Status Register)。里面保存了各种状态位。如下:

这里挑几个重点说明:

(1)I位和F位:中断禁止位和快中断禁止位,当这些位置位时,CPU不响应这些中断。

(2)T位:置位时CPU处于Thumb状态,运行Thumb指令集;否则为ARM状态,运行ARM指令集,不同指令集这里不详细分析,在JZ2440上做实验基本都是用ARM指令集。

(3)M0到M4:工作模式位,表示CPU处于什么工作模式,修改这些位,可以使CPU进入对应的工作模式。

还有一个寄存器叫SPSR寄存器(Saved Program Status Register),功能是保存前一个工作模式的CPSR值,这样当要返回一个工作模式时,可以将SPSR的值恢复到CPSR中。

 

2、中断响应流程

有两类中断请求源:

(1)“Request sources(without sub register)”中的中断源被触发之后,SRCPND(中断请求状态)寄存器中相应位被置1,如果INTMSK(中断屏蔽控制)寄存器未将相应中断屏蔽的话,判断该中断是否是快速中断,如果且快速中断模式是使能的,CPU响应快速中断;如果是普通的中断,PRIORITY(IRQ优先级控制)寄存器的相应位被置1表示产生了该中断,之后INTPND(中断请求状态)寄存器挑出PRIORITY寄存器中的优先级最高的中断进行响应,注意,PRIORITY中可能标记了很多的中断需要处理,但是INTPND一次只挑出一个优先级最高的进行处理,在一个中断没处理完的时候,不响应其他的中断,

(2)“Request sources(with sub register)”这个中断源和(1)的区别可以理解为(1)的中断源是那些大模块比如DMA、定时器,串口等等的某个模块的总的中断请求,而串口模块又存在了RX或者TX中断,这些中断称为中断次级源。该类中断源发生时SUBSRCPND的相应位置位,如果SUBMASK的对应位没有置位来屏蔽这个中断,那么会引发SRCPND的对应位置位,之后的处理流程和(1)中一样。

注意点:

(1)S3C2440的快速中断(fiq)可以打断普通中断(irq),反之不行,普通中断不可以打断普通中断,快速中断也不可以打断快速中断,快速中断(fiq)只能设置一个,即不存在中断嵌套。快速中断(fiq)不引起INTPND和INTOFFSET寄存器的变化,所以这两个寄存器只在IRQ中断中有效。

(2)SUBSRCPND、SRCPND、PRIORITY、INTPND中相应位被置位后,CPU响应了对应的中断过后并不会自动清除这些位,所以需要我们在程序中人为清除对应的位,清除的方法是往对应位写1清除,简单的写法是“INTPND = INTPND”不清除的话该次中断结束之后会立刻进入相同的中断。

(3)不论SUBMASK或者MASK寄存器的相应位有没有置位屏蔽,只要产生了对应的中断,中断请求状态寄存器中都会置位表示有这个中断产生了,上不上报是后续流程的事。

(4)fiq中断由于只能设置一个,一旦产生用户就必定知道是哪个中断源引发的。irq中断有很多个,可以通过读取INTPND寄存器或者INTOFFSET寄存器来确定中断源。

 

3、中断优先级

S3C2440的中断优先级设置是要遵循固定的模式的,并不像STM32那样可以随意设置。这里的中断优先级设置只是对IRQ中断而言的,FIQ中断不受这个规则限制。

中断优先级通过7个仲裁器完成(ARBITER0到ARBITER6),包括6个一级仲裁器(ARBITER0到ARBITER5)和1个二级仲裁器(ARBITER6),说白了就是二级仲裁器从6个一级仲裁器中挑一个优先级最高的中断报上去给CPU处理,其中REQ0和QEQ5的优先级顺序是固定的,一个最高,一个最低,REQ1到REQ4可以通过配置更改它们的内部优先级顺序,下面贴上一张PRIORITY寄存器的定义表来说明:

通过设置ARB_SELn可以设置REQ1到REQ4的内部顺序,通过设置ARB_MODEn可以设置是否需要自动更改排序,就是处理完该组的一次中断请求后,把对应ARB_SELn的值加1取余4,自动轮换一下优先级顺序。为什么要搞个这个功能呢?我猜应该是解决某些中断长期占用导致另外一些中断难以响应的问题,试想一下如果开始优先级是REQ1到REQ4,用户把这四个中断同等重要来看待,然后REQ1有个中断一直都有请求,REQ2这时也来了个中断请求,但是由于REQ1优先级更高霸占了中断请求的处理资源,然后REQ2就一直得不到响应了。

接下来还是要提一点,REQ0到REQ5的优先级顺序只有4种排列方式,个人感觉还是不太灵活,能像STM32那样自由配置就好了。

 

4、中断处理代码

在进入或者退出中断时需要保存和恢复程序的运行环境。示例代码如下:

(1)IRQ中断,进入和退出的代码如下:

sub     lr, lr, #4        @计算返回地址
stmdb   sp!, {r0-r12,lr}  @保存使用到的寄存器
... ...                   @处理中断
ldmia   sp!, {r0-r12,pc}^ @中断返回
                          @^表示将spsr的值赋给cpsr,恢复之前的工作模式

(2)FIQ中断,进入和退出的代码如下:

sub     lr, lr, #4        @计算返回地址
stmdb   sp!, {r0-r7,lr}   @保存使用到的寄存器
... ...                   @处理快速中断
ldmia   sp!, {r0-r7,pc}^  @中断返回
                          @^表示将spsr的值赋给cpsr,恢复之前的工作模式

 

五、代码编写

接下来是具体的实验代码,分为以下几个文件:

head.S:启动文件。

main.c:初始化及各种C函数。

Makefile:编译代码。

各个文件的具体内容如下:

head.S

@*************************************************************************
@ File:head.S
@*************************************************************************       
.text
.global _start
_start:
@******************************************************************************       
@ 中断向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************       
    b   Reset

@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
    b   HandleUndef 
 
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
    b   HandleSWI

@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
    b   HandlePrefetchAbort

@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
    b   HandleDataAbort

@ 0x14: 保留
HandleNotUsed:
    b   HandleNotUsed

@ 0x18: 中断模式的向量地址
    b   HandleIRQ

@ 0x1c: 快中断模式的向量地址
HandleFIQ:
    b   HandleFIQ
    
Reset: 
	ldr	    sp, =4096                       @设置堆栈,因为要调用C语言函数 
	bl	    disable_watch_dog               @关WATCH DOG

    msr cpsr_c, #0xd2       @ 进入中断模式
    ldr sp, =3072           @ 设置中断模式栈指针

    msr cpsr_c, #0xd3       @ 进入管理模式
    ldr sp, =4096           @ 设置管理模式栈指针,
                            @ 其实复位之后,CPU就处于管理模式,
                            @ 前面的“ldr sp, =4096”完成同样的功能,此句可省略

    bl  init_led            @ 初始化LED的GPIO管脚
    bl  init_irq            @ 调用中断初始化函数
    msr cpsr_c, #0x53       @ 设置I-bit=0,开IRQ中断

    ldr lr, =halt_loop      @ 设置返回地址
    ldr pc, =main           @ 调用main函数

halt_loop:
    b   halt_loop

HandleIRQ:
    sub lr, lr, #4                  @ 计算返回地址
    stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器
                                    @ 注意,此时的sp是中断模式的sp
                                    @ 初始值是上面设置的3072
    
    ldr lr, =int_return             @ 设置调用ISR即EINT_Handle函数后的返回地址  
    ldr pc, =EINT_Handle            @ 调用中断服务函数

int_return:
    ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将spsr的值复制到cpsr

main.c


#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int 

/* WOTCH DOG register */
#define REG_WTCON               (*(volatile unsigned long *)0x53000000)


/* GPIO Configure*/
#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 REG_EXTINT0	(*(volatile unsigned long *)0x56000088)
#define REG_EXTINT1	(*(volatile unsigned long *)0x5600008C)

/* interrupt Configure */

#define REG_EINTMASK	(*(volatile unsigned long *)0x560000A4)
#define REG_INTMSK		(*(volatile unsigned long *)0X4A000008)
#define REG_INTOFFSET	(*(volatile unsigned long *)0x4A000014)

#define REG_EINTPEND	(*(volatile unsigned long *)0x560000A8)
#define REG_SRCPND		(*(volatile unsigned long *)0X4A000000)
#define REG_INTPND		(*(volatile unsigned long *)0X4A000010)

// #define REG_INTSUBMSK	(*(volatile unsigned long *)0x560000A8)
// #define REG_SUBSRCPND	(*(volatile unsigned long *)0X4A000018)

void disable_watch_dog();
void init_led();
void init_irq();
void EINT_Handle();

/*上电后,WATCH DOG默认是开着的,要把它关掉 */
void disable_watch_dog()
{
	REG_WTCON	= 0;
}

void init_led()
{
	GPFCON &= ~((DWORD)(3 << (2 * 4)) | (3 << (2 * 5)) | (3 << (2 * 6)));
	GPFCON |= ((DWORD)(1 << (2 * 4)) | (1 << (2 * 5)) | (1 << (2 * 6)));	//GPF4、GPF5、GPF6输出模式

	GPFDAT |= (1 << 4) | (1 << 5) | (1 << 6);   //输出高电平,LED全灭
}

void init_irq()
{
	//配置为外部中断模式
	GPFCON &= ~((DWORD)(3 << (2 * 0)) | (3 << (2 * 2)));
	GPFCON |= ((DWORD)(2 << (2 * 0)) | (2 << (2 * 2)));

	GPGCON &= ~((DWORD)(3 << (2 * 3)));
	GPGCON |= ((DWORD)(2 << (2 * 3)));

	//上拉
	GPFUP |=  (1 << 0) | (1 << 2);
	GPGUP |= (1 << 3);

	//设置下降沿触发
	REG_EXTINT0 &= ~((DWORD)(7 << 0) | (7 << 8));
	REG_EXTINT0 |= ((DWORD)(2 << 0) | (2 << 8));

	REG_EXTINT1 &= ~((DWORD)(7 << 12));
	REG_EXTINT1 |= ((DWORD)(2 << 12));

	//使能EINT11中断
	REG_EINTMASK &= ~((DWORD)1<<11);

	//使能EINT0、EINT2、EINT11中断
	REG_INTMSK &= ~((DWORD)(1 << 0) | (1 << 2) |  (1 << 5));
}

void EINT_Handle()
{
	BYTE bIntOffset = REG_INTOFFSET;

	switch(bIntOffset)
	{
		case 0:
			GPFDAT ^= ((DWORD)1 << 4);	//电平翻转
			break;

		case 2:
			GPFDAT ^= ((DWORD)1 << 5);	//电平翻转
			break;

		case 5:
			if(REG_EINTPEND & (1 << 11))
			{
				GPFDAT ^= ((DWORD)1 << 6);	//电平翻转
			}

			break;

		default:
			break;
	}

	if(bIntOffset == 5)
	{
		//清子中断
		REG_EINTPEND = (DWORD)1 << 11;
	}

	REG_SRCPND = (DWORD)1 << REG_INTOFFSET; 
	REG_INTPND = (DWORD)1 << REG_INTOFFSET; 
}

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

Makefile

objs := head.o main.o

interrupt.bin: $(objs)

	arm-linux-ld -Ttext 0x0000000 -g -o interrupt_elf $^
	arm-linux-objcopy -O binary -S interrupt_elf $@
	arm-linux-objdump -D -m arm interrupt_elf > interrupt.dis
	
%.o:%.c
	arm-linux-gcc -Wall -O2 -c -o $@ $<

%.o:%.S
	arm-linux-gcc -Wall -O2 -c -o $@ $<

clean:
	rm -f interrupt.bin interrupt_elf interrupt.dis *.o	

linux下执行make命令后将bin文件烧写到NandFlash中,随后选择开发板Nand启动,然后可以看到三个独立按键可以分别控制三个LED灯的亮灭。

 

六、实验总结

本次实验加深了对ARM汇编的理解,熟悉了ARM的7种工作模式以及各种工作模式的特点,知道怎么在各种模式下进行切换,掌握了中断处理机制以及现场保存和恢复的编程实现,可谓是收获颇丰。

 

七、参考资料

《嵌入式Linux应用开发完全手册》

《可能是最通俗易懂的方式讲解ARM中断原理以及中断嵌套》https://blog.csdn.net/thisway_diy/article/details/78056764

《第014课 Jz2400_ARM异常与中断体系详解》https://blog.csdn.net/thisway_diy/article/details/79397343

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章