异常与中断2--und异常,swi异常,按键中断,定时器中断

文章目录

    • 1. und异常模式程序示例
    • 2.swi异常模式程序示例
    • 3. 按键中断程序示例_概述与初始化
    • 4.按键中断程序示例_完善
    • 5. 定时器中断程序示例


1. und异常模式程序示例

实例:我们可以故意引入一条未定义指令,让他发生未定义指令异常,如下:

先看printException,当发生异常时候,打印cpsr寄存器的值:

#include "uart.h"

void printException(unsigned int cpsr, char *str)
{
     
	puts("Exception! cpsr = ");
	printHex(cpsr);
	puts(" ");
	puts(str);
	puts("\n\r");//换行回车
}

首先看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未设置, 先设置它 (重新设置栈)*/
	ldr sp, =0x34000000(指向64Msdram的最高地址)

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {
     r0-r12, lr}  //db:把栈先减4,再存
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr	//把cpsr的值读入r0(r:regist寄存器)
	ldr r1, =und_string	//把字符串的地址赋给r1
	bl printException	//注意这是c函数,需要用到栈
	
	/* 恢复现场 */
	//前面保存在栈中,这里读出来就好了,ia:先读后加
	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

	/*  print1 print2 为打印调试信息,用来看未定义的指令是否是正常指令 */
	bl print1
	/* 故意加入一条未定义指令 */
und_code:
	.word 0xdeadc0de  /* 未定义指令 ,CPU 不能进行识别*/
	bl print2

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

halt:
	b halt
void print1(void)
{
     
	puts("abc\n\r");
}

void print2(void)
{
     
	puts("123\n\r");
}

分析:可以看到执行完clean_bss之后就执行下一跳指令,然而CPU根本不知道这条指令能否执行就读进去了,然后解析,CPU发现没办法执行的时候就会发生und异常,此时就会跳到do_und函数处,处理异常。

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 $@ $<	

同时注意到:我们使用的是b命令来进行跳转(相对跳转),后面用到的是bl指令来进行跳转,如果我们是nand启动,printException函数是在4k之外的,那么指令就必然出错,因此我们要跳到sdram中去执行这些处理函数。

以下进行改进:

_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

实现流程画图分析:
异常与中断2--und异常,swi异常,按键中断,定时器中断_第1张图片

2.swi异常模式程序示例

swi: software interrupt

通过前面的学习我们知道ARM CPU有7种模式,除了用户模式以外,其他6种都是特权模式,这些特权模式可以直接修改CPSR进入其他模式

usr用户模式不能修改CPSR进入其他模式

Linux应用程序一般运行于用户模式

APP运行于usermode,(受限模式,不可访问硬件)

APP想访问硬件,必须切换模式,怎么切换?

发生异常3种模式
(1)中断是一种异常
(2) und也是
(3)swi + 某个值(使用软中断切换模式)

现在start.S把要做的事情列出来

/*1
    /* 复位之后, cpu处于svc模式
     * 现在, 切换到usr模式
     * 设置栈
     * 跳转执行
 */

/*2 故意引入一条swi指令*/

/*3 需在_start这里放一条swi指令*/

查看异常向量表swi异常的向量地址是0x8,我们先切换到usr模式下,usr模式下的 M0 ~ M4是10000

/**5 先进入usr模式*/
mrs r0, cpsr      /* 读出cpsr 读到r0 */
/使用bic命令 bitclean 把低4位清零/
bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
msr cpsr, r0

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

编译运行

发现可以处理und指令

添加 swi异常,仿照未定义指令做

完整的start.s代码如下:


.text
.global _start

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

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·	/* 表明下面的reset标号要放在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

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

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

	ldr pc, =sdram
sdram:
	bl uart0_init

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

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

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

halt:
	b halt

swi可以根据应用程序传入的val来判断为什么调用swi指令,我们的异常处理函数能不能把这个val值读出来

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}  

/* 2
我们要把lr拿出来保存
因为bl printException会破坏lr

mov rX, lr

我把lr保存在那个寄存器?

这个函数 bl printException 可能会修改某些寄存器,但是又会恢复这些寄存器,我得知道他会保护那些寄存器

我们之前讲过ATPCS规则
对于 r4 ~ r11在C函数里他都会保存这几个寄存器,如果用到的话就把他保存起来,执行完C函数再把它释放掉
我们把lr 保存在r4寄存器里,r4寄存器不会被C语言破坏
*/
    mov r4, lr

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException


/*1
跳转到printSWIVal
如何才能知道swi的值呢?
我们得读出swi 0x123指令,这条指令保存在内存中,我们得找到他的内存地址
执行完0x123指令以后,会发生一次异常,那个异常模式里的lr寄存器会保存下一条指令的地址
我们把lr寄存器的地址减去4就是swi 0x123这条指令的地址
*/

/*3
我再把r4的寄存器赋给r0让后打印
我们得写出打印函数

mov r0, r4

指令地址减4才可以
swi 0x123
下一条指令bl main 减4就是指令本身
*/
    sub r0, r4, #4
    bl printSWIVal

    /* 恢复现场 */
    ldmia sp!, {
     r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */

swi_string:
.string "swi exception"

```c
在uart.c添加printSWIVal打印函数

void printSWIVal(unsigned int *pSWI)
{
     

    puts("SWI val = ");
    printHEx(*pSWI & ~0xff000000); //高8位忽略掉  
    puts("\n\r");

}

3. 按键中断程序示例_概述与初始化

首先看下图:
异常与中断2--und异常,swi异常,按键中断,定时器中断_第2张图片

按键可以产生中断,那么中断的处理流程是?

1.初始化
(1)设置中断,让他能够发出中断信号

(2)设置中断控制器,让他能够产生中断发给CPU

(3)设置CPU,可以发现cpsr有I为,他是中断的总开关
即bit7中断的总开关,需要将它清0,当bit7 == 1时,CPU无法相应任何中断
异常与中断2--und异常,swi异常,按键中断,定时器中断_第3张图片

2.进行处理,要分辨中断源

3.处理完清中断

现在进行程序逻辑分析:
1.初始化:
(1)中断源:设置按键为中断引脚
(2)中断控制器
(3)cpu,spsr,使能中断

2.main循环(打印字符串)
按下按键,产生中断,执行中断处理函数(点灯),处理函数执行完之后会再次返回到main函数,继续打印字符串

下面进行编程
增加一个文件interrupt.c
按照步骤

1.初始化按键, 设为中断源

(1)配置GPIO为中断引脚

定义一个函数:key_eint_init
看到下面的按键图:

异常与中断2--und异常,swi异常,按键中断,定时器中断_第4张图片
通过分析芯片手册得知按键0,2涉及到GPFCON 配置寄存器

异常与中断2--und异常,swi异常,按键中断,定时器中断_第5张图片
将GPF0 GPF2的对应位设置为10,先清0,再设置为01

	/* 配置GPIO为中断引脚 */
	GPFCON &= ~((3<<0) | (3<<4));
	GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置为中断引脚 */

通过分析芯片手册得知按键11,19涉及到GPFGON 配置寄存器
将GPG3 GPG11的对应位设置为10,先清0,再设置为01
异常与中断2--und异常,swi异常,按键中断,定时器中断_第6张图片

	GPGCON &= ~((3<<6) | (3<<22));
	GPGCON |= ((2<<6) | (2<<22));   /* S4,S5被配置为中断引脚 */

(2)设置中断触发方式: 双边沿触发
双边沿:从高电平->低电平或者低电平->高电平两种都支持

进行设置:
异常与中断2--und异常,swi异常,按键中断,定时器中断_第7张图片
异常与中断2--und异常,swi异常,按键中断,定时器中断_第8张图片
异常与中断2--und异常,swi异常,按键中断,定时器中断_第9张图片

	EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */
	EXTINT1 |= (7<<12);             /* S4 */
	EXTINT2 |= (7<<12);             /* S5 */

(3)设置EINTMASK使能eint11,19
EINTMASK:外部中断屏蔽寄存器,当某一位设置为1时候,就禁止CPU控制器发出中断信号,只有为0时,才有能力发信号给中断控制器,需要将寄存器的某些位设置为0
异常与中断2--und异常,swi异常,按键中断,定时器中断_第10张图片
可以发现0~3可以直达中断控制器,其他的则需要通过设置EINTMASK才能被通过
即:

	EINTMASK &= ~((1<<11) | (1<<19));

2.分析中断控制器
异常与中断2--und异常,swi异常,按键中断,定时器中断_第11张图片

/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */
/* INTMSK 用来屏蔽中断, 1-masked,即使外部中断到我这里,我也不会发给CPU
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */
* 多个中断最后通过优先级之后只会有一个通知CPU,可以通过读INTPND来看哪一个优先级最高
 * INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

注意:SRCPND 中某一位为1时候,假设是INT_UART0 == 1,他的来源可能有多个,这是串口0的中断,有可能是接收到的数据,有可能是发送的数据,也有可能是产生的错误,
异常与中断2--und异常,swi异常,按键中断,定时器中断_第12张图片

那么到底是哪一个,我们需要去读他的SUBSRCPND(下一级的寄存器),可以发现只用设置INTMSK即可
异常与中断2--und异常,swi异常,按键中断,定时器中断_第13张图片

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

即完整的程序如下:



/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTMSK 用来屏蔽中断, 1-masked,即使外部中断到我这里,我也不会发给CPU
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* 多个中断最后通过优先级之后只会有一个通知CPU,可以通过读INTPND来看哪一个优先级最高
 * INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
 * 这个位不需要清除,因为当我们清除SRCPND和INTPND时候,这个位会自动清除
 */

/* 初始化中断控制器 */
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<<11));
	GPGCON |= ((2<<6) | (2<<11));   /* 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的相应位
 */

main函数添加这两个函数即可。

4.按键中断程序示例_完善

上面只是进行简单的分析,下面进行程序的完善
通过观察中断向量表可以看到中断的话跳到0x18的地方执行

除了irq之外的即使没有没有涉及到,也要预留,一旦发生异常b halt卡死

	b reset          /* vector 0 : reset */
	ldr pc, und_addr /* vector 4 : und */
	ldr pc, swi_addr /* 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 */

注意中断也是一种异常,因此可以仿照do_und来进行编写do_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}  
	
	/* 处理irq异常 */
	bl handle_irq_c
	
	/* 恢复现场 */
	ldmia sp!, {
     r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */

下面分析处理irq异常的C函数handle_irq_c
INTOFFSET里面的值表示EINTPEND中哪一位被设置为1

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<);
	INTPND = (1<);	
}

清中断的时候从源头开始清,先清除中断源EINTPEND,再清除SRCPND,再清除 INTPND ,写入某一位就可以清除哪一位

下面来看相应的中断处理函数key_eint_irq

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

和之前的led点亮程序类似,可以查看之前写的程序
led 汇编与机器码 C语言内部机制

接下来对led进行初始化key_eint_init

/* 初始化按键, 设为中断源 */
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));
}

main函数中进行添加这三个函数即可
Makefile添加新增文件即可

综上 interrupt.c代码如下:

#include "s3c2440_soc.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<);
	INTPND = (1<);	
}

5. 定时器中断程序示例

参考书籍<<嵌入式Linux应用开发完全手册>>第10章
目的:使用定时器来点灯实现计数

先看定时器的工作原理:
异常与中断2--und异常,swi异常,按键中断,定时器中断_第14张图片
大致步骤:
1.每来一个clk,TCNTn都要-1
2.当TCNTn == TCMPn时,可以让对应的PWM引脚反转
3.TCNTn继续-1,当TCNTn == 0时,可以产生中断,PWM引脚再次反转
4.TCNTn == 0时,可以自动加载初值

那么怎么使用定时器timer呢
1.设置时钟
2.设置初值
3.加载初值,启动timer
4.设置为自动加载
5.中断相关

注意到:JZ2440中没有引出PWM引脚,PWM可调制脉冲,即让一个引脚输出高电平,一段时间输出低电平,再高电平。。。可以控制不同频率,不同占空比的波形,在控制电机相关非常有用,因此最终实现的功能就是TCNTn == 0时,在中断服务程序中点灯。

异常与中断2--und异常,swi异常,按键中断,定时器中断_第15张图片
可以看到需要设置8-Bit Prescaler(分频器),5选1设置时钟

编程如下:

void timer_init(void)
{
     
	/* 设置TIMER0的时钟 */
	/* Timer clk = PCLK / {prescaler value+1} / {divider value} 
	             = 50000000/(99+1)/16
	             = 31250
	 */
	TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */
	TCFG1 &= ~0xf;
	TCFG1 |= 3;  /* MUX0 : 1/16 */

	/* 设置TIMER0的初值 */
	TCNTB0 = 15625;  /* 0.5s中断一次 */

	/* 加载初值, 启动timer0 */
	TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */

	/* 设置为自动加载并启动 */
	TCON &= ~(1<<1);
	TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */

	/* 设置中断 只需设置中断控制器*/
}

在中断控制器中,第二条语句就是:

/* 初始化中断控制器 */
void interrupt_init(void)
{
     
	INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
	INTMSK &= ~(1<<10);  /* 将定时器的对应位清0即可  enable timer0 int */
}

然后再提供一个中断处理函数timer_irq进行点灯操作

void timer_irq(void)
{
     
	/* 点灯计数 */
	static int cnt = 0;
	int tmp;

	cnt++;

	tmp = ~cnt;
	tmp &= 7;
	GPFDAT &= ~(7<<4);
	GPFDAT |= (tmp<<4);
}

我们可以发现每添加一个中断,都要添加很多函数,可不可以保持原有的interrupt.c文件不变??

在interrupt.c中定义函数指针数组
typedef void(*irq_func)(int);
定义一个数组,我们来卡看下这里有多少项,一共32位,我们想把每一个中断的处理函数都放在这个数组里面来,当发生中断时,我们可以得到这个中断号,让后我从数组里面调用对应的中断号就可以了

irq_func irq_array[32];

那么我们得提供一个注册函数


void register_irq (int irq, irq_func fp)
{
     
    irq_array[irq] = fp;
    INTMASK &= ~(1 << irq)
}

以后我直接调用对应的处理函数

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


/* 调用对应的处理函数 */
irq_array[bit](bit);

    /* 清中断 : 从源头开始清 */
    SRCPND = (1<);
    INTPND = (1<);  
}

//按键中断初始化函数需要注册

    /* 初始化按键, 设为中断源 */
    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));

register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
    }

在timer.c中也需要设置中断

    void timer_init(void)
    {
     
    /* 设置TIMER0的时钟 */
    /* Timer clk = PCLK / {prescaler value+1} / {divider value} 
                 = 50000000/(99+1)/16
                 = 31250
     */
    TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */
    TCFG1 &= ~0xf;
    TCFG1 |= 3;  /* MUX0 : 1/16 */

    /* 设置TIMER0的初值 */
    TCNTB0 = 15625;  /* 0.5s中断一次 */

    /* 加载初值, 启动timer0 */
    TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */

    /* 设置为自动加载并启动 */
     TCON &= ~(1<<1);
     TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */

       /* 设置中断 */
        register_irq(10, timer_irq);
    }

把interrupt.c中按键的初始化放在最后面

我们来看看我们做了什么事情,
(1)我们定义了一个指针数组
typedef void(*irq_func)(int);

这个指针数组里面放有各个指针的处理函数
irq_func irq_array[32];

当我们去初始化按键中断时,我们给这按键注册中断函数
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);

这个注册函数会做什么事情,他会把这个数组放在注册函数里面,同时使能中断

void register_irq(int irq, irq_func fp)
{
     
    irq_array[irq] = fp;

    INTMSK &= ~(1<);
}
//我们的timer.c中

timer_init();
//也会注册这个函数
    /* 设置中断 */
    register_irq(10, timer_irq);

把这个中断irq放在第10项里同时使能中断,以后我们只需要添加中断号,和处理函数即可,再也不需要修改函数
烧写执行

我们从start.s开始看,
一上电从 b reset运行做一列初始化

.text
.global _start

_start:
    b reset          /* vector 0 : reset */
    ldr pc, und_addr /* vector 4 : und */
    ldr pc, swi_addr /* vector 8 : swi */
    b halt           /* vector 0x0c : prefetch aboot */
    b halt           /* vector 0x10 : data abort */
    b halt           /* vector 0x14 : reserved */


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

    /* 复位之后, cpu处于svc模式
     * 现在, 切换到usr模式
     */
    mrs r0, cpsr         /* 读出cpsr */
    bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
    bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
    msr cpsr, r0

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

    ldr pc, =sdram
sdram:
    bl uart0_init

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

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

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

halt:
    b halt

进入main.c做一系列初始化


int main(void)
{
     
    led_init();
    //interrupt_init();  /* 初始化中断控制器 */
    key_eint_init();   /* 初始化按键, 设为中断源 */
    timer_init();

    puts("\n\rg_A = ");
    printHex(g_A);
    puts("\n\r");

进入按键初始化程序 interrupt.c
初始化按键, 设为中断源

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

/*注册中断控制器*/
    register_irq(0, key_eint_irq);
    register_irq(2, key_eint_irq);
    register_irq(5, key_eint_irq);
}


时钟初始化程序 timer_init();

void timer_init(void)
{
     
    /* 设置TIMER0的时钟 */
    /* Timer clk = PCLK / {prescaler value+1} / {divider value} 
                 = 50000000/(99+1)/16
                 = 31250
     */
    TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */
    TCFG1 &= ~0xf;
    TCFG1 |= 3;  /* MUX0 : 1/16 */

    /* 设置TIMER0的初值 */
    TCNTB0 = 15625;  /* 0.5s中断一次 */

    /* 加载初值, 启动timer0 */
    TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */

    /* 设置为自动加载并启动 */
    TCON &= ~(1<<1);
    TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */

    /* 设置中断 */
    register_irq(10, timer_irq);
}

然后main.c函数一直循环执行
输出串口信息

 while (1)
 {
     
    putchar(g_Char);
    g_Char++;

    putchar(g_Char3);
    g_Char3++;
    delay(1000000);
    //printHex(TCNTO0);
 }

定时器减到0的时候就会产生中断,start.S
跳到 0x18的地方执行

    ldr pc, irq_addr /* vector 0x18 : irq */
    b halt           /* vector 0x1c : fiq */
.align 4

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}  

    /* 处理irq异常 */
    bl handle_irq_c

    /* 恢复现场 */
    ldmia sp!, {
     r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */

看看怎么处理irq

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

    /* 调用对应的处理函数执行 */
    irq_array[bit](bit);

    /* 清中断 : 从源头开始清 */
    SRCPND = (1<);
    INTPND = (1<);  
}

你可能感兴趣的:(ARM,und异常,swi异常,按键中断,定时器中断,arm)