nRF51/nRF52同時包含GPIO和GPIOTE兩種外設,經常有人將兩者搞混,今天我們就來介紹一下這2種外設有什么不同,及使用注意事項。
GPIO
GPIO和GPIOTE都屬於芯片外設,但兩者功能完全不一樣,使用過程中不要將兩者混淆。GPIO就是大家通常理解的普通IO口,用來對IO口進行讀寫等操作。因此,如果你需要讀某個IO口狀態,或者將某個IO口置1,那么請使用nrf_gpio.h里面的API,比如
nrf_gpio_cfg_input用來將IO口設為輸入模式
nrf_gpio_pin_set用來輸出1到IO口
Nordic GPIO口輸入模式可以配置為沒有pull,有上拉電阻,有下拉電阻,懸浮等4種狀態。GPIO輸出模式下驅動力靈活可配,可以配置為普通驅動力(2mA),高驅動力(10mA),甚至斷開狀態(跟開漏輸出很像)。
除此之外,Nordic GPIO模塊還有兩個非常重要的功能:
- sense功能。當系統進入sleep模式(也稱system OFF模式),只能通過IO口等特殊喚醒源來喚醒並產生復位。當某個IO口使能了sense功能,那么它就可以用來喚醒sleep模式了。Sense使能的時候,可以配置成高電平喚醒或者低電平喚醒。一般使用nrf_gpio_cfg_sense_input這個函數來使能IO口的sense功能。
- detect功能。Detect功能是sense功能的進一步擴展,sense除了可以喚醒sleep模式,還可以用來產生中斷,即detect功能。你可以把DETECT看成一個中斷標志位,這個中斷標志位是由每一個端口所有IO口進行或操作的結果,所以DETECT信號狀態直接跟隨外部IO口狀態,只要有一個外部IO口有效,那么DETECT信號就一直為高,只有所有外部IO口狀態都無效時,DETECT信號才會重新變成低。
GPIOTE
GPIOTE,全稱GPIO Tasks and Events,GPIOTE首先是一個外設模塊,因此它遵守芯片外設最基本規則:每一個時刻每一個GPIO口只能被一個外設使用,因此當某一個IO口被用做GPIOTE了,那么它就不能再作為普通GPIO來使用了,也就是上面提到的GPIO API將變得無效,此時必須使用nrf_drv_gpiote.h里面的API。Nordic將狀態機引入到每一個外設,也就是說,每一個外設都有自己的輸入(task),輸出(event)和狀態。GPIOTE的作用就是讓GPIO也具有task和event的功能,也就是說,對GPIOTE來說,將某一個IO口置1,其實是觸發TASKS_SET;檢測某一個IO口上升沿,其實是等待EVENTS_IN。讓IO口支持task和event機制,將為后面的PPI自動化操作打下基礎,關於PPI詳細說明,請參考“如何理解nRF5芯片外設PPI”。
GPIO模塊只能用來操作IO口輸入和輸出,如果需要處理IO口中斷,則必須通過GPIOTE模塊來做,GPIOTE支持兩種類型中斷:EVENTS_IN中斷以及EVENTS_PORT中斷。
- IN event中斷。EVENTS_IN用來檢測沿,即上升沿,下降沿或者雙沿。nRF52只有8個IN event channel(nRF51只有4個),它只能同時支持8路IN Event中斷,每一路IN Event中斷相互獨立,互不影響,基於此IN Event中斷可以用來同時捕獲多路IO口中斷。由於nRF51/52設計問題,一旦打開IN event中斷,nRF52將增加10到20微安電流,nRF51將增加幾百微安電流。(nRF53的IN event中斷和port event中斷兩者功耗差不多)
- Port event 中斷。EVENTS_PORT用來檢測IO口低電平或者高電平,從芯片本身來說,每一個IO口都可以產生Port Event中斷,但是GPIOTE驅動引入了一個宏:NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS,用來控制可以同時使能多少個IO口來產生Port Event中斷,Port Event中斷功耗非常低(低於1uA),基本可以忽略不計。Port event中斷雖然屬於GPIOTE模塊,但Port event的產生卻完全取決於GPIO模塊的DETECT信號,DETECT每產生一次上升沿生成一次Port event中斷。如前所述,DETECT信號是所有IO口相或的結果(832是32個IO口,840是48個IO口),只要其中某一個IO口有效,DETECT信號就一直為高,這里容易產生一個副作用:但一個IO口產生Port event中斷后,它還保持有效,那么這個時候DETECT信號就一直為高,此時如果另一個IO口從無效變成有效(產生中斷),由於DETECT信號已經為高電平,所以這個IO口的中斷將被忽略。為此,在處理port event中斷的時候,nRF5 SDK app_button模塊將每個port event的極性設為toggle,也就是每進入一次port event ISR handler,nRF5 SDK都會把DETECT的極性翻轉一次,比如將檢測為高有效變成檢測為低有效,這就相當於將DETECT信號清0了,這樣一旦外部有第2個IO口產生中斷,DETECT將再次由低變高,從而再次生成一次Port event中斷。如果應用邏輯允許,nRF52/51推薦使用port event處理IO口中斷。
EVENTS_IN和EVENTS_PORT兩種IO口中斷初始化區別如下所示:
nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false); //false表示Port event中斷,每個IO口都可以作為port event中斷輸入口 err_code = nrf_drv_gpiote_in_init(pin_no, &config, gpiote_event_handler); nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true); //true表示IN event中斷,nRF52總共有8個IN event中斷。注:這里檢測的是雙沿 err_code = nrf_drv_gpiote_in_init(pin_no, &config, gpiote_event_handler);
如上所述,IN event中斷和Port event中斷,兩者本質上是一樣的,唯一的區別是:IN event中斷每個中斷口是相互獨立的,而Port event中斷所有IO口共用同一個中斷標志位。
SDK自帶GPIOTE應用例程,感興趣的讀者請參考Keil5工程:SDK安裝目錄\examples\peripheral\pin_change_int\pca10040\blank\arm5_no_packs
SDK也自帶Sense例子,有興趣的讀者請參考Keil5工程:SDK安裝目錄\examples\peripheral\ram_retention\pca10040\blank\arm5_no_packs
關於Port event中斷使用例子,可以參考Nordic的app_button模塊,比如ble_app_hrs就會用到這個模塊,大家可以去看一下app_button是如何使用port event中斷的。
