(嵌入式Linux 7 )异常与中断

资料来源:韦东山嵌入式linux

目录

目录

CPU模式(Mode)_状态(State)与寄存器

操作模式与状态

异常

未定义模式异常示例

示例优化

swi软中断

外部按键中断

配置IO口寄存器

配置中断控制器

添加中断代码

定时器中断

定时器内部结构图

定时器配置

中断优化

 



CPU模式(Mode)_状态(State)与寄存器

操作模式与状态

ARM共有7种操作模式:

1.用户模式(user模式):运行的普通模式

2.系统模式(sys模式):使操作系统使用的一个有特权的用户模式

3.未定义模式(und模式):执行的指令无法识别的模式

4.超级用户模式(svc模式):操作系统的保护模式

5.异常中断模式(abt模式):分为指令预读取中断(运行当前指令,读取后面指令时出错)与数据访问中断(访问地址数据时出错)。

6.中断模式(irq模式):普通中断处理

7.快速中断模式(fiq模式):用于支持数据传输或通道处理

其中2-7可以称为异常模式,可以经过编程CPSR寄存器进入,而在用户模式中,不能进入其他模式

 

ARM共有两种状态指令:

1.ARM State:ARM指令集,每个指令7个byte

2.Thumb State:Thumb指令集,每个指令2个Byte

比如:mov R0,R1  

在ARM指令集下机器码占用4Byte

在Thumb指令集下机器码占用2Byte

 

在ARM状态下的寄存器分布:

在不同的工作模式下,CPU可访问的寄存器不同,比如在System或者User模式下,可访问r0-r15 寄存器,但是在FIQ模式下访问的r8与在User访问的r8不是一个寄存器,上图中带三角形状的寄存器是在每个特定模式下独有的。

ARM状态下程序状态寄存器CPSR,记录了程序当前运行的状态,同时每个状态都有对应的影子寄存器,在程序由User或者System状态切换过来时,保存当前的CPSR的值。

CPSR的格式:

其中M[4:0]代表程序当前状态模式:

bit5表示当前运行在ARM状态还是Thumb状态

bit6如果置1,禁止所有FIQ

bit7如果置1,禁止所有IRQ

bit[31:28]为条件位,比如:

cmp R0,R1     //影响Z位,如果R0==R1,Z=1

beq ......          //使用Z位,如果Z==1,则跳转

 

异常

当正常的程序执行流程被中断时,称为产生异常,进入异常的行为:

1.当一个异常行为发生时,将下一条指令的地址保存到lr寄存器中,根据异常的类型,数值为当前PC+4或PC+8(硬件完成).

2.将CPSR寄存器的值拷贝到SPSR。(硬件完成)

3.根据异常类型,修改CPRS模式位(M[4:0])的值,进入异常模式(硬件完成)

4.跳转到中断异常向量表,执行中断异常函数,中断异常函数保存在中断向量表里:

离开异常的行为:

1.将lr寄存器的值减去一定的偏移量,赋值给pc,偏移量由异常的类型决定:

2.拷贝SPSR到CPSR

3.清除中断标志位。

 

未定义模式异常示例

在Start.S中添加未定义指令:

.text
.global _start

_start:
    b reset  /* vector 0地址 : reset */
    b do_und /* vector 4地址 : und */
    
do_und:
    /* 执行到这里之前:
    * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_und保存有被中断模式的CPSR
    * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
    * 4. 跳到0x4的地方执行程序 
    */
        
    /* 栈sp_und未设置, 先设置它,设置在sdram顶部 */
    ldr sp, =0x34000000
        
    /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* lr是异常处理完后的返回地址, 也要保存,先减后存 */
    stmdb sp!, {r0-r12, lr}  
            
    /* 保存现场 */
    /* 处理und异常 */
    mrs r0, cpsr
    ldr r1, =und_string
    bl printfException
            
    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里,先存后加 */

/*定义一个字符串*/
und_string:
    .string "undefined instruction exception"
        


reset:

	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

    bl uart0_init  /*首先初始化串口,因为进入未定义异常后需要printfException调用串口输出
   
/* 故意加入一条未定义指令 */
und_code:
    .word 0xdeadc0de  /* 未定义指令,执行到这里时,CPU发现是一条未定义指令,所以跳转到0x04地址。 */     

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt
	

添加异常处理函数:

exception.c

#include "uart.h"

void printfException(unsigned int cpsr,char *str)
{
    puts("Exception,CPSR=");
    printHex(cpsr);
    puts("  ");
    puts(str);
    puts("\r\n");
}

修改makefile

all: start.o led.o uart.o init.o main.o exception.o
	#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds $^ -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis
	
%.o : %.c
	arm-linux-gcc -c -o $@ $<

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

输出:

可以看出CPSR的值对应在M[4:0]为11011,未定义模式

 

程序运行流程:

1. 0地址运行,进入_start。跳转到reset

2. 在reset中,对系统进行初始化,包括关闭看门狗,设置时钟,设置栈,代码重定位,初始化串口。

3. 程序运行到未定义指令,此时发生异常,硬件完成以下步骤:

     * (1). lr_und保存有被中断模式中的下一条即将执行的指令的地址
     * (2). SPSR_und保存有被中断模式的CPSR
     * (3). CPSR中的M4-M0被设置为11011, 进入到und模式
     * (4). 跳到0x4的地方执行程序 

pc跳转到0x4地址,ldr pc, und_addr,读取und_addr地址的值加载到pc,执行und_addr的内容

4. 执行do_und,设置当前模式下的sp栈,保存现场,处理异常(输出cpsr的值,输出字符串printException),恢复现场

5. pc读取lr的值,继续执行后续的指令。

示例优化

从该示例可以看出在Start.s的文件中,如果出现了异常则调用printException,printException位于地址do_und,如果在4K外,此时NAND访问就会出错,所以,应该到sdram中去运行do_und.

.text
.global _start

_start:
	b reset  /* vector 0 : reset */
	ldr pc, und_addr /* vector 4 : und */

und_addr:
	.word do_und

do_und:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* sp_und未设置, 先设置它 */
	ldr sp, =0x34000000

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 4

reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss


	ldr pc, =sdram
sdram:
	bl uart0_init

	bl print1
	/* 故意加入一条未定义指令 */
und_code:
	.word 0xdeadc0de  /* 未定义指令 */
	bl print2

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt

程序示意图:

1.代码烧入Nor或者SRAM(Nand也要先复制4K到SRAM)中,代码地址

0:b reset   

 4:  ldr pc,und_addr 

 ....各异常地址   

代码段  

ldr pc, =sdram

2.从0开始执行,关闭看门狗,设置时钟,设置数据栈,设置sdram,重定位所有程序。重定位后,代码全部复制到sdram中

3.在Nor/SRAM中继续执行到ldr pc, =sdram, pc跳转到sdram中

4.sdram中执行串口初始化,发生异常,发生异常后,跳转到地址4的异常向量,回到Nor/SRAM中。

5.调用ldr pc, und_addr,读内存指令,读取und_addr地址赋值给pc,跳到sdram中。执行do_und。

6.异常处理完毕,继续在sdram中执行

swi软中断

程序运行时,大部分运行于User模式,在User模式下,不可访问硬件。如果想访问硬件,怎么办呢?必须切换模式,切换模式有三种方法:1.中断  2.异常  3 swi软件中断

示例代码:

.text
.global _start

//(Tep1)0地址运行,pc位于Nor中
_start:
	b reset          /* vector 0 : reset */
	ldr pc, und_addr /* vector 4 : und */
	ldr pc, swi_addr /* vector 8 : swi (Tep5) 读取sdram中swi_addr的地址到pc,pc跳转到sdram中执行swi_addr*/

und_addr:
	.word do_und

swi_addr:
	.word do_swi

do_und:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* sp_und未设置, 先设置它 */
	ldr sp, =0x34000000

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 4

do_swi:
	/* 执行到这里之前:
	 * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_svc保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
	 * 4. 跳到0x08的地方执行程序 
	 */

	/* sp_svc未设置, 先设置它 */
	ldr sp, =0x33e00000

	/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理swi异常 */
	mrs r0, cpsr
	ldr r1, =swi_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
swi_string:
	.string "swi exception"

.align 4

//(Tep2)配置看门狗,时钟,配置sdram,设置数据栈,代码重定位,此时pc位于nor中
reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
		
	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

//(Tep3)切换模式,pc位于nor中

	/* 复位之后, cpu处于svc模式
	 * 现在, 切换到usr模式
	 */
	mrs r0, cpsr      /* 读出cpsr */
	bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
	msr cpsr, r0

	/* 设置 sp_usr */
	ldr sp, =0x33f00000

//(Tep4)修改pc的位置到sdram中,在sdram中运行串口初始化,未定义指令
	ldr pc, =sdram
	
sdram:
	bl uart0_init

	bl print1
	/* 故意加入一条未定义指令 */
und_code:
	.word 0xdeadc0de  /* 未定义指令 */
	bl print2

//(Tep5)触发swi软中断,跳回nor地址0x8中

	swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt

运行步骤:

1.0地址运行,pc位于Nor中

2.配置看门狗,时钟,配置sdram,设置数据栈,代码重定位,此时pc位于nor中

3.切换模式,pc位于nor中

4.修改pc的位置到sdram中,在sdram中运行串口初始化,未定义指令

5.触发swi软中断,跳回nor地址0x8中

6.读取sdram中swi_addr的地址到pc,pc跳转到sdram中执行swi_addr

外部按键中断

程序目标:

1.初始化程序

     初始化中断源

     初始化中断控制器

     使能中断

2.main循环

     检测到按键后打印字符串,并且电灯

 

外部中断系统描述:

在GPIO中,很多引脚可以设置为外部中断引脚,当GPIO复用为外部中断时,首先通过GPIO的控制寄存器EINTMASK,再通过独立的中断控制器。

在电路图中,四个按键对应的外部中断分别是S2-EINT0-GPF0,S3-EINT2-GPF2,S4-EINT11-GPG3,S5-EINT19-GPG11

配置IO口寄存器

首先配置IP口的功能,将对应的IO口配置为中断方式,设置中断触发方式为边沿触发,始能外部中断:

interrupt.c

/*初始化按键,设为中断源*/
void key_eint_init(void)
{
    /*配置GPIO为中断引脚,GPF0,GPF2,GPG3.GPG11*/
    GPFCON &= ~((3<<0)|(3<<4));//S2,S3按键,寄存器先清0
    GPFCON |= ((2<<0)|(2<<4));//配置中断引脚

    GPGCON &= ~((3<<6)|(3<<11));//S4,S5
    GPGCON |= ((2<<6)|(2<<11));//配置中断引脚
    /*设置中断触发方式:双边沿触发*/
    EXTINT0 |=(7<<0)|(7<<8);//配置双边触发S2,S3,EINT0,EINT1
    EXTINT1 |=(7<<12);//S3,EINT11
    EXTINT2 |=(7<<12);//S4,EINT12

    /*设置EINTMASK,始能外部中断eint11,eint19,外部中断0-3不需要设置始能*/
    EINTMASK &= ~((1<<11)|(1<<19))
}

EINTMASK为外部中断屏蔽寄存器,其中外部中断0-3不需要配置屏蔽与否

同样的还有EINTPEND寄存器,用来判断在eint4-13中哪个中断产生,清除中断时,需要配置相应寄存器。

配置中断控制器

配置好IO口后,接下来配置中断控制器,中断控制器运行流程如图:

有两种中断源:sub(源)与without sub(子源),子源在产生中断后,要先经过SUBSRCPND寄存器,再经过SUBMASK寄存器。源经过SRCPND寄存器,然后经MASK或者MODE,到达IRQ或FIQ。

外部中断属于子源 中断,不是快速中断FIQ,所以需要配置:

SRCPND用来显示哪个中断产生了,需要清除对应位

INTMSK 用来屏蔽中断  如果某位置 1,则CPU不会服务相应中断源的中断请求(注意SRCPND的相应位还是会被置 1)。如果屏蔽位为 0,中断请求可以被服务。

INTPND 显示是否相应的中断请求有最高优先级,其中断请求未屏蔽且在等待中断服务。

INTOFFSET:中断偏移寄存器中的值显示了哪个IRQ模式的中断请求在INTPND寄存器中,该位可以通过清除SRCPND和INTPND寄存器被自动清除。INTOFFSET中保存的是对应中断请求的偏移量,不是数值大小,比如0100,保存的是2(偏移量),不是4

初始化中断控制器:

interrupt.c

void interrupt_init(void)
{
    INTMSK &= ~((1<<0)|(1<<2)|(1<<5));
}

添加中断代码

Start.S添加中断地址:

_start:
	b reset          /* vector 0 : reset */
	ldr pc, und_addr /* vector 4 : und */
	ldr pc, swi_addr /* vector 8 : swi (Tep6) 读取sdram中swi_addr的地址到pc,pc跳转到sdram中执行swi_addr*/
    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 */
und_addr:
	.word do_und

swi_addr:
	.word do_swi

irq_addr:
    .word do_irq

添加中断IRQ操作:

do_irq:
    /* 执行到这里之前:
    * 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_irq保存有被中断模式的CPSR
    * 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
    * 4. 跳到0x18的地方执行程序 
    */
    
   /* sp_irq未设置, 先设置它 */
    ldr sp, =0x33d00000
    
    /* 保存现场 */
   /* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
   /* lr-4是异常处理完后的返回地址, 也要保存 */
    sub lr,lr,#4
    stmdb sp!, {r0-r12, lr}  
            
    /* 处理swi异常 */
    bl handle_irq_c
        
     /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */

始能中断

    bic r0, r0, #(1<<7)/*清除I位,始能中断*/

编写完善中断函数:Interrupt.c

#include "s3c2440_soc.h"
/*SRCPND用来显示哪个中断产生了,需要清除对应位
* bit[0]~eint0
* bit[2]~eint2
* bit[5]~eint8~23
*/


/*INTMSK 用来屏蔽中断  1-MASK屏蔽
* bit[0]~eint0
* bit[2]~eint2
* bit[5]~eint8~23
*/


/*INTPND 用来显示当前优先级最高的正在发生的中断,需要清除对应位
* bit[0]~eint0
* bit[2]~eint2
* bit[5]~eint8~23
*/

/*INTOFFSET:用来显示INTPND哪一位被设置为1
*
*/

/*初始化中断控制器*/
void interrupt_init(void)
{
    INTMSK &= ~((1<<0)|(1<<2)|(1<<5));
}


/*初始化按键,设为中断源*/
void key_eint_init(void)
{
    /*配置GPIO为中断引脚,GPF0,GPF2,GPG3.GPG11*/
    GPFCON &= ~((3<<0)|(3<<4));//S2,S3按键,寄存器先清0
    GPFCON |= ((2<<0)|(2<<4));//配置中断引脚

    GPGCON &= ~((3<<6)|(3<<11));//S4,S5
    GPGCON |= ((2<<6)|(2<<11));//配置中断引脚
    /*设置中断触发方式:双边沿触发*/
    EXTINT0 |=(7<<0)|(7<<8);//配置双边触发S2,S3,EINT0,EINT1
    EXTINT1 |=(7<<12);//S3,EINT11
    EXTINT2 |=(7<<12);//S4,EINT12

    /*设置EINTMASK,始能外部中断eint11,eint19,外部中断0-3不需要设置始能*/
    EINTMASK &= ~((1<<11)|(1<<19));
}

/*读EINTPEND可以分辨哪个中断产生(EINT4~13)
 *清除中断时,写寄存器响应位*/

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);
		}
		
	}

	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 */
	}
    /*清中断:从源头开始清
    *清中断方法:通过对相应位置1来清除相应位。如果你对相应位写0,则该位的数值保持不变
    */
    SRCPND = (1<<bit);
	INTPND = (1<<bit);
}

定时器中断

定时器内部结构图

定时器配置

使用timer0,首先设置8-bitPrescaler(8位预分频器),设置MUX经过分频,再设置初值。

编程:(实现0.5s间隔闪烁灯)

1.初始化timer,并在主函数调用  timer.c

void timer_init(void)
{
    /*设置Timer0时钟*/
    /*Timer clock =PCLK/(prescaler value+1)/(divider value)
                  =50 000 000/(99+1)/16
                  =31250
    */
    TCFG0=99;//prescaler0=99,用于timer0,timer1
    TCFG1 &= ~0xf;
    TCFG1 |= 3;//MUX0=0011,表示divider=1/16
    /*设置初值*/
    TCNTB0=31250/2;//0.5s中断一次
    /*加载初值,*/
    TCON|=(1<<1);//手工更新加载值,该位使用后要清0
    TCON&=~(1<<1);
    /*设置自动加载,启动timer*/
    TCON|=(1<<0)|(1<<3);//bit0:开启  bit3:自动重装
    /*设置中断*/
}

2.初始化中断控制器,开启中断

void interrupt_init(void)
{
	INTMSK &= ~((1<<0) | (1<<2) | (1<<5));//开启外部中断
	INTMSK &= ~(1<<10);//开启timer0中断
}

3.定时器开启后,中断进入irq_addr,调用handle_irq_c,添加调用定时器中断

void handle_irq_c(void)
{
	/* 分辨中断源 */
	int bit = INTOFFSET;

	/* 调用对应的处理函数 */
	if (bit == 0 || bit == 2 || bit == 5)  /* eint0,2,eint8_23 */
	{
		key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
	}
    else if(bit==10)//定时器中断
    {
        timer_irq();
    }
	/* 清中断 : 从源头开始清 */
	SRCPND = (1<<bit);
	INTPND = (1<<bit);	
}

4.编写定时器中断

void timer_irq(void)
{
    //puts("IRQ\r\n");
    int Data=GPFDAT&(1<<4);
    Data=Data>>4;
    if(Data==0)//亮
    {
        GPFDAT|=(1<<4);
        
    }
    else if(Data==1)//灭
    {
        GPFDAT&=~(1<<4);
    }

}

中断优化

上面的代码中每次添加新中断都要去interrupt_init设置,可以总结到一个注册函数中,在每个外设初始化时注册中断函数。

1.添加函数指针,声明了一个函数指针数组,数组的每一项中有一个指针,指向对应中断标号的中断函数

typedef void(*irq_func)(int);//函数指针,函数带参数
irq_func irq_array[32];//函数指针数组

2.添加注册函数

//注册函数
void register_irq(int irq, irq_func fp)
{
	irq_array[irq] = fp;

	INTMSK &= ~(1<<irq);
}

3.修改.S中,遇到中断后调用的中断函数

void handle_irq_c(void)
{
	/* 分辨中断源 */
	int bit = INTOFFSET;

	/* 调用对应的处理函数 */
	irq_array[bit](bit);
    
	/* 清中断 : 从源头开始清 */
	SRCPND = (1<<bit);
	INTPND = (1<<bit);	
}

中断回调时,直接在timer.c中添加回调

void timer_init(void)
{
    /*设置Timer0时钟*/
    /*Timer clock =PCLK/(prescaler value+1)/(divider value)
                  =50 000 000/(99+1)/16
                  =31250
    */
    TCFG0=99;//prescaler0=99,用于timer0,timer1
    TCFG1 &= ~0xf;
    TCFG1 |= 3;//MUX0=0011,表示divider=1/16
    /*设置初值*/
    TCNTB0=31250/2;//0.5s中断一次
    /*加载初值,*/
    TCON|=(1<<1);//手工更新加载值,该位使用后要清0
    TCON&=~(1<<1);
    /*设置自动加载,启动timer*/
    TCON|=(1<<0)|(1<<3);//bit0:开启  bit3:自动重装
    /*设置中断*/
    register_irq(10, timer_irq);
}

这样主函数初始化时不用调用interrupt_init(); 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值