天天看點

第9課,按鍵中斷和定時器中斷

注:以下内容學習于韋東山老師arm裸機第一期視訊教程

一.中斷的處理

    1.1 中斷初始化

        1.1.1 設定中斷源,讓他能夠發出中斷信号

      a.以按鍵中斷為例,按鍵原理圖如下,4個按鍵分别接到EINT0,EINT2,EINT11,ENIT19對應GPF0,GPF2,GPG3,GPG11
第9課,按鍵中斷和定時器中斷
            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發出中斷

                根據下圖進行相關的配置

第9課,按鍵中斷和定時器中斷
                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,對于外部中斷其對應關系如下
第9課,按鍵中斷和定時器中斷
                  bit0-eint0    1-中斷産生,需要清除
                  bit2-eint2
                  bit5-eint8-23
                  某一位等于1時,表示中斷發生了,對于eint8-23需要再次讀取EINTPEND寄存器判斷哪個中斷發生了
                 g.INTOFFSET寄存器,顯示哪一個中斷正在等待處理,即用來顯示INTPND中哪一位被設定為1,可以讀取INTOFFSET寄存器或者INTPND來判斷中斷源,對應關系如下
第9課,按鍵中斷和定時器中斷
                   這個寄存器不需要清除,在清除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 工作原理如下圖

第9課,按鍵中斷和定時器中斷

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

繼續閱讀