要點亮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平台軟體開發者所難以企及的。