第三講 ecos中斷操作zz

發信人: gdtyy (gdtyy), 信區: Embedded
標  題: 第三講 ecos中斷操作
發信站: 水木社區 (Mon Jun 25 23:30:25 2007), 站內

***********************
* 第三講 ecos中斷操作 *
***********************
    2006/12/30  [email protected]  www.armecos.com

    ecos的中斷處理到底比我們自己寫的高明在哪裏呢?
    如果我們自己寫中斷處理程序,那麼,針對lpc2210、s3c44b0x、2410、2440等具體硬
件,每一個片子都要重寫ISR。對於不同的體系結構(如ARM、MIPS、X86、POWERPC......)、
變種(同體繫結構不同型號)、硬件平臺(不同公司的開發板),中斷機制不盡相同,程序員要
花費大量的時間糾纏於細微的差異,心情不會愉快,代碼複用性也不強。
    ecos採用了一種通用的中斷處理機制,抽象出中斷最本質的特點,用硬件抽象層HAL來
抹平各種中斷硬件體系實現的細節,程序員只要針對ecos抽象出來的通用虛擬中斷系統編程
就可以了。

    那麼ecos到底是如何“抽象”中斷的呢?暫且不表,先來說說中斷響應時間。

    如果你寫過彙編中斷處理程序,或者用過ucos,那麼你對ISR的實現方式一定不會陌生
,先填好中斷向量表,再寫好ISR處理程序,一旦中斷就通過向量表找到ISR函數地址,跳到
那裏執行。
    而ecos的虛擬中斷系統是ISR+DSR,當然Linux也是ISR+tasklet。很明顯,中斷分成了
兩個部分,一部分是中斷服務程序ISR,關中斷執行,儘可能短,很多內核函數用不了;另
一部分是延遲服務程序DSR,不關中斷,可以進行比較耗時的處理,比ISR能用更多的內核函
數。
    爲什麼這樣做?有什麼好處呢?
    我們知道,中斷是隨時都有可能異步發生的,中斷處理程序和被中斷程序的“併發”執
行,會引起重入、同步、臨界資源保護等諸多問題。中斷髮生時會保存現場,返回時恢復,
故不會對被中斷的無臨界資源的應用線程產生影響,但如果被中斷的線程正在執行內核調用
,則有可能破壞內核數據,因爲內核函數需要原子操作,不能被打斷。避免這一問題的方法
之一是在處於臨界區的內核函數中禁止中斷。例如ucos的內核函數就經常開關中斷。不過有
時這種原子操作的時間可能相當長,對於中斷響應時間這個指標有不良影響。改善性能的方
法就是不使用開關中斷的方法避免衝突,而是使用鎖、信號量等方式實現內核數據保護。這
樣就約定在ISR裏限制使用不安全的絕大部分內核API,內核API也不再頻繁開關中斷,代之
以鎖、信號量。當我們的ISR可以寫得很短,內核API又不會關中斷的情況下,中斷響應時間
就會變小,RTOS的實時性指標就會更好看。因此,採用ISR+DSR的ecos中斷響應比ucos快。
可能您現在還不太習慣這種把ISR分成兩半兒的寫法,沒關係,我們下面提供一個萬能中斷
處理模板,您只要套用就可以了。把精力集中在中斷事件處理上,再不用爲搭架子發愁。

    想看中斷如何被抽象嗎?我們結合一個題目邊看邊講。

    總的思路是:創建中斷句柄,掛接中斷,使能中斷,正常工作後一旦發生中斷,先調用
ISR,再調用DSR(可選)。其中涉及到中斷應答,中斷清除、外部中斷清除、中斷觸發方式配
置、中斷禁止/使能、中斷創建/刪除、中斷掛接/解掛等概念。

    題目:用KEY1按鍵控制蜂鳴器鳴響。低電平有效,按下發聲,再按停止,再按又發聲,
周而復始。(別忘了先裝好跳線再實驗)

    全部源碼如下所示。多任務編程第一講已經講過了,I/O寄存器讀寫在第二講也已經講
過了,現在來看看中斷編程。

    我在創建taska線程時傳遞了一個參數CYGNUM_HAL_INTERRUPT_EXT3,這是KEY1對應的外
部中斷號,沒什麼特別的目的,只是想用線程參數存放我的私有數據(priv_data),總得找
個地兒放我自己的東西不是,得,就放這兒了。CYGNUM_HAL_INTERRUPT_EXT3的意思是符合
CYG公司標準的數字---位於硬件抽象層---中斷用途---外部中斷3號。

    線程taska中首先設置引腳,雖然ecos抽象了中斷,但中斷畢竟還是和硬件緊密相關,
有些芯片爲了引腳複用或低功耗等目的,多種功能對應在同一個引腳上/多個引腳對應在同
一個功能上,使用時必須根據需要選擇。例如:SMARTARM2200平臺的KEY1使用的是EINT3中
斷,該中斷由P0.9、P0.20、P0.30複用,要屏蔽P0.9(網卡8019中斷)和P0.30(ZLG7290中斷
),以免衝突。感興趣的讀者可以試試允許P0.9和P0.30對應EINT3會發生什麼現象。(低電平
觸發時,中斷信號線相與;高電平觸發時,中斷信號線相或。)

    設置BEEP控制引腳方向爲輸出,禁止剛一開始就BEEP鳴響,上電缺省輸出是低電平,導
致一開始就鳴叫,此處明確關掉聲音。

    中斷觸發方式有4種:高電平、低電平、上升沿、下降沿。ecos使用
cyg_interrupt_configure函數設置中斷觸發方式。
    cyg_interrupt_configure(中斷號,電平/邊沿選擇,上下/高低選擇)
    其中:
          電平/邊沿選擇:0---電平;1---邊沿
          上下/高低選擇:0---低下;1---高上
    此處設置爲(priv_data,0,0),即低電平觸發方式。priv_data是線程參數傳遞過來的
EINT3中斷號。
    用中斷觸發配置函數是不是比直接寫EMODE,EPOLAR寄存器要方便很多呢!
    邊沿觸發的中斷不用清中斷源,但可能會丟中斷;電平觸發的中斷需要清除外部中斷源
,不會丟失中斷指示,而且可以共享同一根中斷線。

    清除priv_data(即EINT3)中斷標誌,等效於1<<3。這句要放在
cyg_interrupt_configure後面,好象改EMODE和EPOLAR會影響EINT。最好在使用中斷前明確
清除以前的中斷指示,以免發生不能預料的隨機事件,這是好習慣。

    ecos的虛擬中斷體系要求把需要的中斷處理函數掛接到對應的中斷號上,而不必關心具
體的硬件映射細節。中斷句柄可以創建多個,但只能掛接其中的一個。
    cyg_interrupt_create(
        中斷號,
        中斷優先級,
        傳遞的中斷參數,
        ISR函數,
        DSR函數,
        被返回的中斷句柄,
        存放與此中斷相關的內核數據的變量空間);

    cyg_interrupt_attach(中斷句柄);

    創建了中斷句柄並且掛接後,中斷服務程序就和中斷向量建立了聯繫,每當中斷後,先
執行向量服務程序VSR,再調用已註冊的中斷服務程序ISR,然後執行延遲服務程序DSR。至
此,中斷已經準備就緒。
    剛掛接的中斷是被屏蔽的,要調用解除屏蔽函數,中斷才能正式開始工作。這麼做是爲
了在中斷註冊後,還能有一段時間繼續準備其他需要做的工作,而這些工作不希望被中斷幹
擾。如果都準備好了,就可以解除屏蔽,讓中斷正常工作。
    cyg_interrupt_unmask(中斷號);//使能中斷

    “while(1);”是爲了不退出taska線程,你可以試試去掉這一句會發生什麼現象。

    ISR程序非常短小,也不調用絕大部分內核函數,不過中斷應答和返回值是必須的。
    cyg_interrupt_acknowledge(中斷號);
    中斷應答應該在ISR快結束時執行一次寫操作(寫入的值一般爲0),以便更新優先級硬件
。如果不應答,硬件不能正常工作,而且一定要在ISR裏應答。
    如果ISR返回CYG_ISR_HANDLED,則不再調用DSR,如果返回CYG_ISR_CALL_DSR,則還要
調用DSR。
    此處屏蔽中斷的目的是避免重入,因爲我不希望中斷嵌套,那樣會搞亂數據。如果沒有
衝突的數據,允許嵌套,就可以不用此語句屏蔽中斷。注意不要在ISR裏用printf函數。

    DSR裏執行了大部分的處理工作,可以使用更多的內核函數。這段程序的主要作用是取
反BEEP控制,讓蜂鳴器隨着KEY1按鍵中斷控制在響與不響之間切換。解除中斷屏蔽是對應
ISR裏的中斷屏蔽,此時中斷已經執行完畢,可以使能新的中斷。
    需要注意的是:電平中斷的處理過程稍微煩瑣一些。先撤消外部中斷源指示(例如等待
KEY1按鍵擡起/讀空8019網卡輸出緩衝區),然後撤消內部EINT中斷標誌位(對應位寫1就可以
清除)。如果不先撤消外部指示就清除內部標誌/不清除內部標誌,那麼會反覆不斷地陷入中
斷,直至出錯死機。感興趣的讀者可以試試註釋掉“
HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0));”
看看中斷能否退出,是否死機。去掉while中的KEY1按鍵擡起判斷,看看按下去時的現象有
何差別。

    以上是面向ecos虛擬中斷體系編程的講解,可以看出,ecos的中斷更加抽象,再也不用
關心什麼VIC,一大堆中斷寄存器了,套用這個模板就可以了。

    如果你想改成KEY1鍵高電平觸發,只要寫成“cyg_interrupt_configure(priv_data,0,
1);”即可。此時,一運行就鳴叫,按一下停止,再按一下又鳴叫......結果正確。DSR裏同
低電平觸發時一樣等待外部中斷源指示失效才退出,此時若信號保持爲高電平,中斷標誌會
一直置1。

    如果想改成邊沿觸發,要將以下語句:
    HAL_READ_UINT8(LPC2XXX_SYSCON_EINT,flag);
    while((flag&1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)) != 0)
        {
        HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,
1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0));
        HAL_READ_UINT8(LPC2XXX_SYSCON_EINT,flag);
        }
    改爲一句:
    HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)
);
    不用再判斷外部中斷源的情況,邊沿觸發不需要清除外部中斷源,因爲邊沿不會一直保
持。EINT寫“1”清除還是必須的,不然還會反覆陷入中斷。

    寫成“cyg_interrupt_configure(priv_data,1,0);”就是下降沿觸發,按下KEY1鍵會
響,再按下停止......
    寫成“cyg_interrupt_configure(priv_data,1,1);”就是上升沿觸發,按下後擡起
KEY1鍵,會響,再按下擡起停止......

    套用這個摸板,寫基於中斷的8019網卡驅動程序十分方便,8019中斷也是用的EINT3,
高電平觸發,感興趣的讀者可以試試。(提示:改引腳分配PINSEL0和PINSEL1,ecos中有
dp83902a參考源碼)

    很多人可能不用ARM或者ecos,那也沒有關係,這裏介紹的主要是編程思路和中斷本質
抽象,有了思路,細節的解決只是時間問題,您完全可以把這裏的中斷處理思路用在2410、
X86、MIPS......上。初學者如果一上來就接觸大量硬件細節,難免會暈掉,只見樹木不見
森林,ecos增值軟件包使你能集中精力掌握思路,花最少的時間,取得最大的效果。

以下是中斷程序源碼

#include <cyg/kernel/kapi.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/plf_io.h>

#define STACK_SIZE 4096
#define BEEPCON 0x0000080

char stack[2][STACK_SIZE];
static cyg_thread thread_data[2];
static cyg_handle_t thread_handle[2];

// This ISR is called when the ethernet interrupt occurs
static cyg_uint32
yy_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    int priv_data = (int)data;

    cyg_interrupt_mask(priv_data);       //屏蔽中斷
    cyg_interrupt_acknowledge(priv_data);//中斷應答,應該在ISR快結束時執行一次寫
操作(寫入的值一般爲0),以便更新優先級硬件。
    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR如果ISR返回
CYG_ISR_HANDLED,則不再調用DSR,如果返回CYG_ISR_CALL_DSR,則還要調用DSR。
}

static void
yy_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    int priv_data = (int)data;
    int i;
    unsigned char flag;

    HAL_READ_UINT32(LPC2XXX_GPIO_IO0SET,i);

    if((i&BEEPCON) == 0 )
        {
        HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0SET,BEEPCON);
        }
    else
        {
        HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0CLR,BEEPCON);
        }

    HAL_READ_UINT8(LPC2XXX_SYSCON_EINT,flag);
    while((flag&1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)) != 0)
        {
        HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,
1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0));
        HAL_READ_UINT8(LPC2XXX_SYSCON_EINT,flag);
        }

    cyg_interrupt_unmask(priv_data);//解除中斷屏蔽
}

void taska(cyg_addrword_t data)
{
    int priv_data = (int) data;
    cyg_handle_t handle;
    cyg_interrupt intr;

    printf("irq=%d/n",priv_data);

    //引腳設置,KEY1接EINT3
    HAL_WRITE_UINT32(LPC2XXX_PIN_SEL0,0x55500005);
    HAL_WRITE_UINT32(LPC2XXX_PIN_SEL1,0x00000300);

    //設置輸出到BEEP
    HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0DIR,BEEPCON);

    //禁止剛一開始就BEEP,上電缺省輸出是低電平,導致一開始就鳴叫。
    HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0SET,BEEPCON);

    //配置中斷觸發方式,低電平觸發
    cyg_interrupt_configure(priv_data,0,0);

    //清除priv_data(即EINT3)中斷標誌,等效於1<<3。這句要放在
cyg_interrupt_configure後面,好象改EMODE和EPOLAR會影響EINT。
    HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)
);

    //創建句柄
    cyg_interrupt_create(
        priv_data,
        0,                         // Priority - unused
        (cyg_addrword_t)priv_data, // Data item passed to ISR & DSR
        yy_isr,                    // ISR
        yy_dsr,                    // DSR
        &handle,                   // handle to intr obj
        &intr );                   // space for int obj

    //將中斷掛接到中斷句柄上
    cyg_interrupt_attach(handle);

    //使能中斷
    cyg_interrupt_unmask(priv_data);

    printf("Starting.../n");

    while(1);
}

void
test(cyg_addrword_t data)
{
    printf("/n/n/n");
    printf("/t    *******************************/n");
    printf("/t    *     Hello! The world.       */n");
    printf("/t    *******************************/n/n/n");

    cyg_thread_create(10,                // Priority - just a number
                      taska,             // entry
                      CYGNUM_HAL_INTERRUPT_EXT3, // entry parameter
                      "taska",           // Name
                      &stack[1],         // Stack
                      STACK_SIZE,        // Size
                      &thread_handle[1], // Handle
                      &thread_data[1]    // Thread data structure
            );
    cyg_thread_resume(thread_handle[1]); // Start it
}

void
cyg_start(void)
{
    cyg_thread_create(10,                // Priority - just a number
                      test,              // entry
                      0,                 // entry parameter
                      "test",            // Name
                      &stack[0],         // Stack
                      STACK_SIZE,        // Size
                      &thread_handle[0], // Handle
                      &thread_data[0]    // Thread data structure
            );
    cyg_thread_resume(thread_handle[0]); // Start it
    cyg_scheduler_start();
}

--

※ 來源:·水木社區 http://newsmth.net·[FROM: 61.149.56.*]
 

發佈了29 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章