资料来源:韦东山嵌入式linux
目录
目录
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();