第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_arraybit;

/* 清中断: 从源头开始清 */
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);
}

posted @ 2020-01-20 10:15  人民广场的二道贩子  阅读(344)  评论(0)    收藏  举报