第14课.异常与中断
1.ARM异常与中断的处理流程
硬件实现部分
1.初始化
a.设置中断源,让它可以产生中断
b.设置中断控制器(屏蔽,优先级)
c.设置CPU总开关(使能中断)
2.执行程序
3.产生中断
4.CPU每执行一条代码都会检查有无中断异常
5.发现有异常时,开始处理
对于不同的异常,跳到不同的地址去执行;这个地址上,只是一条跳转指令。跳去执行某个函数。
软件实现部分
6.这些函数处理
a.保存现场:(各种寄存器)
b.处理异常:
分辨中断源
再调用不同的函数
c.恢复现场
2.CPU模式,状态与寄存器
CPU的7种mode
普通模式
1.usr:用户模式
不可直接进入其他模式
特权模式
2.sys:系统模式
3.异常:
a.und:未定义指令模式
b.SVC:管理模式
c.abt:中止模式
.指令预期中止(运行,解析,读取中的读取)
.数据访问中止
d.IRQ:中断模式
e.FIQ:快中断模式
特权模式可以通过编程操作COSR直接进入其他模式
ARM状态下CPU 7种mode寄存器的使用详细
注:带三角型的为当前mode下的专属寄存器(备份寄存器)
专属寄存器在其工作的mode下用自己的副本,当其切换到其他mode下时,不需要备份。
其他的通用寄存器,在使用它时,需要备份
CPU的状态
ARM STtate : 使用ARM指令集,指令为4byte
Thumb State:使用Thumb指令集,指令为2byte
Thumb指令集可看做ARM指令集的压缩指令集,但效率不如ARM指令集,也不完整
CPSR, SPSR
CPSR: 当前程序状态寄存器
SPSR: 程序状态保存寄存器
[5]: CPU状态位:ARM, Thumb
当发生一个异常,将切换进入相应的工作模式时的流程
进入异常处理
1.r14(lr)中保存前一个工作模式的下一条代码地址。对于ARM状态,这个值是当前pc值加4或加8(详情见下图)
2.将CPSR的值复制到SPSR
3.令CPSR的工作模式为设置为这个异常对应的工作模式
4.令pc值等于这个异常模式在异常向量表中的地址。即跳转去执行异常向量表中的相应指令
退出异常处理
1.lr寄存器中保存了前一工作模式的下一条指令地址,将它减去一个适当的值(详情见下图)后赋给pc寄存器
2.将SPSR的值复制会CPSR
3.清中断
中断向量表
3.中断框图
SUBSRCPND和SRCPND:寄存器表明有哪些中断被触发了,正在等待处理
SUBMASK(INTSUBMASK寄存器)和MASK(INTMASK寄存器):用来屏蔽某些中断
1.Request sources(without sub-register)中的中断源被触发后,SRCPND寄存器中的相应位被置为1,如果此中断没有被INTMSK寄存器屏蔽或者快速中断(FIQ),它将进一步处理
2.Request sources(with sub-register)中的中断源被触发之后,SUBSRCPND寄存器中的相应位被置为1,如果此中断没有被INTSUBMASK寄存器屏蔽的话,它在SRCPND寄存器中的相应位也被置为1,之后的处理过程就和Request sources(without sub-register)一样
3.如果触发的中断中有快中断(FIQ)----MOOD(INTMOD寄存器)中为1位的位对应的中断是FIQ,则CPU进入快速中断模式进行处理
4.对于一般中断IRQ,可能同时有几个中断被触发,未被INTMASK寄存器屏蔽的中断经过比较后,选出优先级最高的中断,此中断在INTPND寄存器中的相应位被置为1,然后CPU进入中断模式进行处理。中断服务程序可以通过读取INTPND寄存器或者INTOFFSET寄存器来确定中断源
中断的优先级通过PRIORITY寄存器进行设置
SUBSRCPND,SRCPND,INTPND: 三个状态寄存器使用完毕后需要被清零(写入1)。从左到右为其清零的顺序。
4.寄存器讲解
SRCPND
每一位被用来表示一个(或一类)中断是否已经发生。若想清楚某一位往此位写入1
INTMSK
INTMSK寄存器只能屏蔽被设置为IRQ的中断,不能屏蔽为设置为FIQ的中断
INTMOD
当INTMOD寄存器中某位被设置为1时,它对应的中断为FIQ,即此中断发生时,CPU将进入快速中断模式。
PRIORITY:优先级寄存器
INTPND
INTOFFSET
SUBSRCPND: 次级源挂起寄存器
INTSUBMSK
5.代码实现及其讲解
知识补充
.word
在汇编中.word的用法是,在当前位置放入一个word型的值,这个就是word后面的内容
.word und_addr
bic
清除操作数为1的位,把其置为0
bic r0, r0, #0x1f //清除r0的[0-4]
mrs
mrs r0, cpsr // 把CPSR的值读入r0
msr cpsr, r0 // 把r0的值写入cpsr
.string
.string "xxx" // 定义一个字符串xxx
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 */
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: /* 防止代码位于4k之外 */
.word do_und
swi_addr:
.word do_swi
irq_addr:
.word do_irq
do_und:
/* 在执行到这里之前:
* 1.lr_und保存有被中断模式中的下一条即将执行的指令地址
* 2.SPSR_und保存有被中断模式的CPSR
* 3.CPSR中的M4-M0被设置为11011,进入到und模式
* 4.跳到0x4的地方执行程序
*/
/* sp_und未设置,先设置它
* 因为后续使用到了c语言,必须设置栈
*/
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_swi保存有被中断模式中的下一条即将执行的指令地址
* 2.SPSR_swi保存有被中断模式的CPSR
* 3.CPSR中的M4-M0被设置为11111,进入到svc模式
* 4.跳到0x8的地方执行程序
*/
/* sp_und未设置,先设置它
* 因为后续使用到了c语言,必须设置栈
*/
ldr sp, =0x33e00000
/* 在und异常处理函数中可能会修改r0-r12,所以先保存 */
/* lr是异常处理完后的返回地址,也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr寄存器的值恢复到cpsr */
swi_string:
.string "swi exception"
.align 4
do_irq:
/* 在执行到这里之前:
* 1.lr_irq保存有被中断模式中的下一条即将执行的指令地址
* 2.SPSR_irq保存有被中断模式的CPSR
* 3.CPSR中的M4-M0被设置为10010,进入到irq模式
* 4.跳到0x18的地方执行程序
*/
/* sp_und未设置,先设置它
* 因为后续使用到了c语言,必须设置栈
*/
ldr sp, =0x33d00000
/* 在und异常处理函数中可能会修改r0-r12,所以先保存 */
/* lr是异常处理完后的返回地址,也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/* 处理und异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr寄存器的值恢复到cpsr */
reset:
...
/* 复位之后,CPU位于SVC模式
* 现在切换到usr模式
*/
mrs r0, 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:
...
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)
{
INTMASK &= ~((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));
GPGCOM |= ((2<<6) | (2<<22)); /* S4, S5被设置为中断引脚 */
/* 设置中断触发方式: 双边沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2, S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,eint19
* 因为eint0-eint3能直接传到中断控制器
*/
EINTMASK &= ~((1<<11) | (1<<19));
}
/* 读EINTPEND分辨那个eint产生(eint4~eint23)
* 清中断时,写EINTPEND的相应位
*/
void key_eint_irq(int irq)
{
unsigned int val = EINTPEND;
unsigned int val2 = GPFDAT;
unsigned int val3 = GPGDAT;
if(irq == 0) /* s2 --> gpf6 */
{
if(val1 & (1<<0))
{
/* 松开 */
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);
}
main.c
...
int main(void)
{
led_init();
interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
...
}
6.定时器简介
S3C2440共有5个16位的定时器。其中定时器0,1,2,3有PWM功能。定时器4没有PWM功能输出引脚
定时器器件的时钟源为PCLK,首先通过两个8位的预分频器降低频率:定时器0,1共用第一个预分频器,定时器2,3,4共用一个预分频器。预分频器的输出将进入第二级分频器,它们输出5种频率的时钟:2分频,4分频,8分频,16分频或者外部时钟TCLK0/TCLK1。每个定时器的工作时钟可以从这5种频率中选择
这两个预分频都可以通过TCFGO寄存器来设置,每个定时器工作在那种频率下也可以通过TCFG1寄存器来选择
7.定时器内部控制逻辑
1.每来一个时钟(clk)TCNTn减1
2.当TCNTn = TCMPn时,可以让对应的PWM引脚翻转
3.TCNTn继续减1,当TCNTn = 0时。可以产生中断,PWM引脚再次翻转
4.TCNTn = 0时,可自动加载初值
注:
Timer clk = PCLK / (prescaler valuer + 1) / (divider valuer)
TCFG0(0~255) 1,2,4,8,16
eg:
50MHz / (99 + 1) / 16 = 31500
每计数一次用时:1/31500
用时0.5s需计数15625次
8.定时器的使用逻辑
1.设置时钟
2.设置初值
3.加载初值,启动Timer
4.设置为自动加载
5.中断相关设置
9.定时器寄存器
TCFG0
位[7:0]、位[15:8]分别被用于控制预分频器0、1,它们的值为0~255
TCFG1
经过预分频器得到的时钟被2分频、4分频、8分频、16分频、除这4种频率外,额外地,定时器0、1还可以工作在外接的TCLK0时钟下,定时器2、3、4还可以工作在外接的TCLK1时钟下
TCNTBn/TCMPBn
这两个寄存器都只用到了[15:0],它们在启动定时器时就被传到定时器内部寄存器TCNTn、TCMPn中。没有TCMPB4,因为定时器4没有PWM输出引脚。
TCNTBn中保存定时器的初始计数值;
TCMPBn中保存比较值。
TCNTOn
定时器n被启动后,内部寄存器TCNTn在其工作时钟下不断减1计数,可以通过读取TCNTOn寄存器得知其值
TCON
注:
第一次使用定时器时,需要设置“手动更新”位为1以使TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中。下一次如果还要设置这一位需要先将它清0。
10.代码实现
interrupt.c
typedef void(*irq_fun)(int); /* 这里typedef定义了一个irq_func的类型 */
irq_func irq_array[32];
...
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数 */
irq_array[bit](bit);
/* 清中断: 从源头开始清 */
SRCPND = (1 << bit );
INTPND = (1 << bit );
}
void register_irq(int irq, irq_fun fb)
{
irq_array[irq] = fb;
INTMSK &= ~(1<<irq); /* 使能中断 */
}
/* 初始化按键, 设为中断源 */
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_irq(void)
{
/* 点灯计数 */
static int cnt = 0;
int tmp;
cnt++;
tmp = ~cnt;
tmp &= 7;
GPFDAT &= ~(7<<4);
GPFDAT |= (tmp<<4);
}
void timer_init(void)
{
/* 设置TIMER0的时钟 */
/* Timer clk = PCLK / (prescaler value + 1) / (divider value)
* = 50000000 / (99 + 1) / 16
* = 31250
*/
TCFG0 = 99; /* Prescaler 0 = 99,用于定时器0,1 */
TCFG1 &= ~0xf; /* 清零 */
TCFG1 |= 3; /* 16分频 */
/* 设置TIMER0的初值 */
TCNTB0 = 15625; /* 0.5s中断一次*/
/* 加载初值,启动timer0 */
TCON |= (1 << 1); /* 此时为手动设置加载值 */
/* 设置为自动加载并启动
* bit0: start
* bit3: auto reload
*/
TCON &= ~(1<<1);
TCON |= (1<<0) | (1<3);
/* 设置中断 */
register_irq(10, timer_irq);
}