天天看點

【.Net Micro Framework PortingKit - 08】GPIO驅動

要點亮LED燈或獲得輸入IO的狀态應該是比較容易的,打開端口時鐘,然後讀寫相關的GPIO寄存器就可以了,但是要實作一個輸入中斷,就要費些周折了。

對STM32(Cortex-M3)的晶片,要實作一個GPIO中斷一般需要如下幾步:

1、  配置時鐘控制器寄存器(RCC)的APB2RSTR,確定對應的GPIOA ~ GPIOG時鐘使能。

2、  對GPIO寄存器的CRL(或CRH)要設定正确的輸入模式,如浮空輸入模式(對接收IO中斷來說,當然要設定成輸入模式)。

3、  要通過AFIO寄存器配置中斷的輸入來源,對STM32晶片來說,具有19路EXTI中斷線,其中3路分别連接配接PVD輸出、RTC鬧鐘事件及USB喚醒事件,剩下的對GPIOA ~ GPIOG 7*16=112個IO點來說,同時隻能配置16路IO輸入中斷。

4、  接下來要配置EXIT寄存器,根據需要來配置是上升沿觸發中斷、還是下降沿觸發中斷或兩者都觸發。

5、  而後比較重要的是, 要配置NVIC的SETENA寄存器,讓對應的EXTI0、EXTI1、EXTI2、EXTI3、EXTI4、EXTI9_5或EXTI15_10中斷位使能,此外還要配置各中斷的優先級(前提是中斷優先級分組寄存器已配置完畢)。

6、  最後我們要設定中斷向量表(該向量表要重定位到記憶體中,以便于動态修改),在EXTI0、EXTI1、EXTI2、EXTI3、EXTI4、EXTI9_5或EXTI15_10對應的位置,放入我們的中斷函數的入口位址。

和PC平台程式開發不同,基本上你每做一步,都可以很直覺的看到你的進展和成果,但對嵌入式開發來說,如果上述幾步有任何一個環節出了問題,你的進展都是零,有時候你會花上一天的時間去反複核實每個寄存器的值是否正确,以期獲得你希望的結果。是以說嵌入式開發是驚喜的型的,要麼成,要麼不成,一線之隔!

接下來我們說一下GPIO實作的詳細步驟,首先在CortexM3.h頭檔案中添加GPIO相關的寄存器描述:

struct CortexM3_GPIO

{

  static const UINT32 c_Base = 0x40010800;

  static const UINT32 A = 0;

  static const UINT32 B = 1;

  static const UINT32 C = 2;

  static const UINT32 D = 3;

  static const UINT32 E = 4;

  static const UINT32 F = 5;

  static const UINT32 G = 6;

  static const UINT32 GPIO_Mode_NULL = 0x00;

  static const UINT32 GPIO_Mode_Speed_10MHz = 0x01;

  static const UINT32 GPIO_Mode_Speed_2MHz = 0x02;

  static const UINT32 GPIO_Mode_Speed_50MHz = 0x03;

  static const UINT32 GPIO_Mode_IN_FLOATING = 0x04;

    /****/ volatile UINT32   CRL;  //配置寄存器

  /****/ volatile UINT32   CRH;

  /****/ volatile UINT32   IDR;  //資料寄存器

  /****/ volatile UINT32   ODR;

  /****/ volatile UINT32   BSRR; //置位複位寄存器

  /****/ volatile UINT32   BRR;  //複位寄存器

  /****/ volatile UINT32   LCKR; //鎖定寄存器

};

struct CortexM3_EXTI

  static const UINT32 c_Base = 0x40010400;

  /****/ volatile UINT32  IMR;

  /****/ volatile UINT32  EMR;

  /****/ volatile UINT32  RTSR;

  /****/ volatile UINT32  FTSR;

  /****/ volatile UINT32  SWIER;

  /****/ volatile UINT32  PR;

struct CortexM3_AFIO

  static const UINT32 c_Base = 0x40010000; 

  /****/ volatile UINT32 EVCR;

  /****/ volatile UINT32 MAPR;

  /****/ volatile UINT32 EXTICR[4];

對.Net Micro Framework的架構來說,要實作如下幾個接口:

1、CPU_GPIO_Initialize

2、CPU_GPIO_Uninitialize

3、CPU_GPIO_Attributes

4、CPU_GPIO_DisablePin

5、CPU_GPIO_EnableOutputPin

6、CPU_GPIO_EnableInputPin

7、CPU_GPIO_EnableInputPin2

8、CPU_GPIO_GetPinState

9、CPU_GPIO_SetPinState

10、CPU_GPIO_GetPinCount

11、CPU_GPIO_GetPinsMap

12、CPU_GPIO_GetSupportedResistorModes

13、CPU_GPIO_GetSupportedInterruptModes

14、CPU_GPIO_PinIsBusy

15、CPU_GPIO_ReservePin

16、CPU_GPIO_GetDebounce

17、CPU_GPIO_SetDebounce

考慮到難易程度和篇幅,我們隻介紹CPU_GPIO_Initialize、CPU_GPIO_EnableOutputPin、 CPU_GPIO_EnableInputPin和EXTI_IRQHandler 中斷函數的具體實作。

BOOL GPIO_Driver::Initialize()

         CortexM3_AFIO &AFIO = CortexM3::AFIO();

    for(int i=0;i<4;i++)

    {

       AFIO.EXTICR[i]=0x0000;

    }       

CortexM3_EXTI &EXTI= CortexM3::EXTI();   

    EXTI.IMR = 0x00000000;

    EXTI.EMR = 0x00000000;

    EXTI.RTSR = 0x00000000;

    EXTI.FTSR = 0x00000000;

    EXTI.PR = 0x0007FFFF;

    //NVIC

    if(!CPU_INTC_ActivateInterruptEx(CortexM3_NVIC::c_IRQ_Index_EXTI0,(UINT32)(void *)EXTI_IRQHandler ))   return FALSE;

         //略

    return TRUE;

}

其中比較重要的是CPU_INTC_ActivateInterruptEx函數,它可動态設定c_IRQ_Index_EXTI0中斷所對應的中斷函數的入口位址。

void GPIO_Driver::EnableOutputPin(GPIO_PIN pin, BOOL initialState)

    ASSERT(pin < c_MaxPins);       

    UINT32 port = PinToPort(pin);

         UINT32 bit = PinToBit(pin);

    UINT32 pos = (bit % 8)<<2;

         CortexM3_GPIO &GPIO= CortexM3::GPIO(port);   

    //通用推挽輸出模式

    if(bit<8)

    {       

       GPIO.CRL = (GPIO.CRL & ~(0x0F << pos)) | (CortexM3_GPIO::GPIO_Mode_Speed_50MHz << pos);

    }

         else

         {

            GPIO.CRH = (GPIO.CRH & ~(0x0F << pos)) | (CortexM3_GPIO::GPIO_Mode_Speed_50MHz << pos);

         }

    //初值

    if(initialState) GPIO.BSRR = 0x1 << bit;

    else GPIO.BRR = 0x1 << bit;

輸出預設為通用推挽輸出模式,你也可以根據實際需要進行必要的調整。

BOOL GPIO_Driver::EnableInputPin(GPIO_PIN pin, BOOL GlitchFilterEnable, GPIO_INTERRUPT_SERVICE_ROUTINE ISR, void *pinIsrParam, GPIO_INT_EDGE intEdge, GPIO_RESISTOR resistorState)

    //浮空輸入

       GPIO.CRL = (GPIO.CRL & ~(0x0F << pos))| (CortexM3_GPIO::GPIO_Mode_IN_FLOATING << pos);

            GPIO.CRH = (GPIO.CRH & ~(0x0F << pos))| (CortexM3_GPIO::GPIO_Mode_IN_FLOATING << pos);

    //中斷輸入源配置(AFIO)

    CortexM3_AFIO &AFIO = CortexM3::AFIO();

    AFIO.EXTICR[bit >> 2] &= ~(0x0F << (0x04 * (bit & 0x03)));

    AFIO.EXTICR[bit >> 2] |= port << (0x04 * (bit & 0x03));

         CortexM3_EXTI &EXTI=CortexM3::EXTI();

    if(ISR)

        switch(intEdge)

        {

            case GPIO_INT_NONE:   //無中斷

                EXTI.IMR &= ~(0x1<<bit);                                   

                return FALSE;

            case GPIO_INT_EDGE_LOW:   //下降沿中斷

            case GPIO_INT_LEVEL_LOW:

                EXTI.IMR |= 0x1<<bit;    

                                     EXTI.FTSR |= 0x1<<bit;        //下降沿有效

                                     EXTI.RTSR &= ~(0x1<<bit);  //上升沿無效

                break;

            //略

            default:

                ASSERT(0);

        }

    }                

GPIO輸入的實作比較繁瑣一些,可以根據需要僅把端口配置成輸入模式,而不配置相應的中斷參數。這樣可以通過不斷掃描的方式獲得輸入信号。

void GPIO_Driver::ISR(void *Param)

    UINT32 interruptsActive = EXTI.PR;

    UINT32 bitMask  = 0x1, bitIndex = 0;

         while(interruptsActive)

        while((interruptsActive & bitMask) == 0)

            bitMask  <<= 1;

            ++bitIndex;

        CortexM3_AFIO &AFIO = CortexM3::AFIO();

        UINT32 port = (AFIO.EXTICR[bitIndex >> 2]>>(0x04 * (bitIndex & 0x03))) & 0xF;

        GPIO_PIN pin = BitToPin( bitIndex, port);

        PIN_ISR_DESCRIPTOR& pinIsr = g_GPIO_Driver.m_PinIsr[ pin ];

        pinIsr.Fire( (void*)&pinIsr );

        interruptsActive ^= bitMask;

                   EXTI.PR |= bitMask;

在中斷函數中,根據相關寄存器的值來判斷哪一個(或同時哪一些)GPIO發生的中斷,并由此執行業已配置好的異步中斷處理函數。

好了,寫完了GPIO驅動程式,我們就可以漂漂亮亮的在NativeSample中寫我們的測試程式了:

void ISR( GPIO_PIN Pin, BOOL PinState, void* Param )

    if(PinState)    // released, up

      CPU_GPIO_SetPinState(GPIO_Driver::PF7,0x0);

    else            // pressed, down

    {

       CPU_GPIO_SetPinState(GPIO_Driver::PF7,0x1);

void ApplicationEntryPoint()

{  

    //LED D1 D2 D3 D4  

    CPU_GPIO_EnableOutputPin(GPIO_Driver::PF7,FALSE);

         CPU_GPIO_EnableOutputPin(GPIO_Driver::PF8,FALSE);

         //user按鈕 = 0x1

         CPU_GPIO_EnableInputPin(GPIO_Driver::PG8,FALSE,ISR,GPIO_INT_EDGE_BOTH,RESISTOR_PULLDOWN);                   

    while(TRUE)    

    {  

           CPU_GPIO_SetPinState(GPIO_Driver::PF8,!CPU_GPIO_GetPinState(GPIO_Driver::PF8));

           Events_WaitForEvents( 0, 1000 );      

隻要細心 + 耐心,其實嵌入式開發還是比較容易的,并且功能實作那一刻喜悅的“強度”,是作為PC平台軟體開發者所難以企及的。

繼續閱讀