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

猜你喜欢

转载自www.cnblogs.com/huangdengtao/p/12183201.html