第9课,按键中断和定时器中断

注:以下内容学习于韦东山老师arm裸机第一期视频教程


一.中断的处理

    1.1 中断初始化

        1.1.1 设置中断源,让他能够发出中断信号

      a.以按键中断为例,按键原理图如下,4个按键分别接到EINT0,EINT2,EINT11,ENIT19对应GPF0,GPF2,GPG3,GPG11

            b.配置GPFCON,GPGCON使得GPF0,GPF2,GPG3,GPG11被配置为外部中断引脚
            c.配置EXTINTX寄存器设置中断触发方式为双边沿触发
                其中EXTINT0寄存器对应EINT0-EINT7
                EXTINT1寄存器对应EINT8-EINT15
                EXTINT2寄存器对应EINT16-EINT23
            d.配置EINTMASK寄存器允许EINT0,EINT2,EINT11,ENIT19向中断控制器发生中断
                其中EINT0-EINT3的中断信号不需要配置,可以直接到达中断控制器
            

           相关码如下:   

                /* 初始化按键, 设为中断源 */
                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));
                }

        1.1.2 设置中断控制器,让他可以向CPU发出中断

                根据下图进行相关的配置

                
                a.多个中断产生经过优先级只会有一个通知CPU,可以读取INTPND寄存器来判断是哪一个中断产生了
                  bit0-eint0    1-中断产生,需要进行清除
                  bit2-eint2
                  bit5-eint8-23

                b.Priority表示中断优先级,暂时不设置
                c.MODE用来设置某个中断为快中断或者普通的中断,我们使用默认值,发出IRQ信号
                d.需要设置MASK寄存器,某一位被设置为1,会屏蔽掉这个中断
                  bit0-eint0    
                  bit2-eint2
                  bit5-eint8-23
                
                e.对于中断源,有的中断源包括子中断源,例如串口中断包括接受中断,发送中断和出错的中断
                  假设串口0产生了TX0子中断SUBSRCPND寄存器的某一位就会被置1,然后进行过滤,通过SUBMASK进行处理,
                  SUBMASK寄存器每一位对应一子中断源,如果我们将这里面的某一位置1,就会过滤掉这一位对应的中断
                  
                f.如果是没有子中断源的中断,会直接进入SRCPND,用来显示某一个中断是否发生了,执行完成之后需要清除SRCPND寄存器
                  外部中断可以没有子中断,会直接到达SRCPND,对于外部中断其对应关系如下
                  
                  bit0-eint0    1-中断产生,需要清除
                  bit2-eint2
                  bit5-eint8-23
                  某一位等于1时,表示中断发生了,对于eint8-23需要再次读取EINTPEND寄存器判断哪个中断发生了
                  
                 g.INTOFFSET寄存器,显示哪一个中断正在等待处理,即用来显示INTPND中哪一位被设置为1,可以读取INTOFFSET寄存器或者INTPND来判断中断源,对应关系如下
                
                   这个寄存器不需要清除,在清除SRCPND和INTPND时会自动清除
                   

                相关代码如下

                    /* 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)
                    {
                        INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
                    }

        1.1.3 设置CPU,CPSR中的I位是中断的总开关,需要清0才能打开中断

            相关代码如下:

                mrs r0, cpsr         /* 读出cpsr */
                bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
                bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
                msr cpsr, r0

    1.2 处理中断    

        1.2.1 分辨中断源

            读取INTOFFSET寄存器,表示INTPND中的值哪一位被设置为1

            0-eint0

            2-eint2

            5-eint8-23

                

        1.2.2 调用对应的处理函数,并且清除中断源

            清中断时想EINTPND写入1清除对应的中断(读的时候1表示中断发生,写入1清除中断,很奇怪,2440手册上是这么描述的,见下图)

            

            相关代码如下:

                void key_eint_irq(int irq)
                {
                    unsigned int val = EINTPEND;
                    unsigned int val1 = GPFDAT;
                    unsigned int val2 = GPGDAT;
                    
                    if (irq == 0) /* eint0 : s2 控制 D12 */
                    {
                        ...
                    }
                    else if (irq == 2) /* eint2 : s3 控制 D11 */
                    {
                        ...    
                    }
                    else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
                    {
                        if (val & (1<<11)) /* eint11 */
                        {
                            ...
                        }
                        else if (val & (1<<19)) /* eint19 */
                        {    
                            ...
                        }
                    }

                    EINTPEND = val;
                }

    1.3 处理完毕后要清中断

        清除中断时从前向后清,即先清SRCPND,再清INTPND。否则前面的还会影响后面

        相关代码如下:    

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

    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)
    {
        INTMSK &= ~((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));
        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));
    }

    /* 读EINTPEND分辨率哪个EINT产生(eint4~23)
     * 清除中断时, 写EINTPEND的相应位
     */


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

 

二.定时器(参考2440第10章)

    2.1 工作原理如下图

        

        2.1.1 每次来一个时钟信号,TCNTn减1

        2.1.2 damh TCNTn == TCMPn时,不会产生中断,可以让对应的PWM引脚翻转

        2.1.3 TCNTn继续减1,当TCNTn等于0时可以产生中断,PWM引脚再次翻转    

                TCNTn和TCMPn的初值来自于TCNTBn和TCMPn寄存器

        2.1.4 TCNTn等于0时,可以自动加载初值

    

    2.2 定时器的使用

        2.2.1 设置时钟

        2.2.2 设置初值

        2.2.3 加载初值,启动Timer

        2.2.4 设置为自动加载

        2.2.5 设置中断

        

    2.3 代码示例

        /* timer.c */
        
        #include "s3c2440_soc.h"

        void TimerFunc(int irq)
        {
            static int cnt = 3;
        #if 0
            if (GPFDAT & (1 << 4))
                GPFDAT &= ~(1 << 4);
            else
                GPFDAT |= (1 << 4);
        #endif
            cnt++;
            GPFDAT &= ~(1 << cnt);
            if (cnt == 7)
            {
                cnt = 3;
                GPFDAT |= (0x7 << 4);                
            }

        }

        void TimerInit(void)
        {
            /* 1.Set clock */
            /*
             * Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
             *                               = 50000000 / (99 + 1) / 16
             *                               = 31250
             */
            TCFG0 = 99;        /* Prescaler 0 = 99 */
            TCFG1 |= 0x3;     /* divider value = 16 */
            
            /* 2.Set TCNTn init val */
            TCNTB0 = 15625; /* 0.5s */
            
            /* 3.Load init val*/
            TCON |= (1 << 1);

            /* 4.Set Auto update, and start timer  */
            TCON &= ~(1 << 1);
            TCON |= (1 << 0) | (1 << 3);

            /* timer0的中断号是10 */
            register_irq(10, TimerFunc);
        }
        
        /* interrupt.c */
        
        #include "s3c2440_soc.h"
        #include "interrupt.h"


        void handle_irq(void)
        {
            /* 1.Resolved interrupt source */
            int bit = INTOFFSET;

            /* 2.handle irq */
            irq_arr[bit](bit);

            /* 3.clear irq */
            SRCPND = (1 << bit);
            INTPND = (1 << bit);
        }


        /* 以中断号为下标放到irq_arr指针数组中去 */
        void register_irq(int irq, irq_func func)
        {
            irq_arr[irq] = func;
            INTMSK &= ~(1 << irq);
        }

   
阅读更多
个人分类: arm裸机学习笔记
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭