zoukankan      html  css  js  c++  java
  • 第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);
    }
  • 相关阅读:
    观察者模式
    简单工厂
    一个数组先按值排序,如果它的值有相同,就再按键排序(转)
    Python 一些好玩的函数
    python 一些基础知识
    python3 写CSV文件多一个空行的解决办法
    pandas学习笔记
    pycharm2017.1破解方法
    python的Debug调试
    python中字典的陷阱
  • 原文地址:https://www.cnblogs.com/huangdengtao/p/12183201.html
Copyright © 2011-2022 走看看