產品中需要用到GPIO外部中斷功能,可以使用芯片的GPIOTE功能實現。但是自己配置GPIOTE有點麻煩,於是偷個懶,直接用例程中的Button相關代碼,把外部中斷當成是按鍵輸入。
實際上Button模型底層也是通過GPIOTE加定時器實現的,只不過SDK做了一些封裝,加了延時消抖功能。使用起來確實很方面,但是也有弊端,那就是容易“丟中斷”,意思就是外部中斷信號並不能保證百分之百被芯片(準確的說是上層應用)檢測到,這對於一些對外部中斷次數要求嚴格的場合是不能接受的。可是爲什麼會發生這個問題呢?下面就來探究一下。
首先看一下Button模型的工作原理:
1.當按鍵按下,或者外部中斷信號輸入時,芯片產生一個GPIOTE事件(中斷),對應的服務函數是gpiote_event_handler()
2.在GPIOTE中斷服務函數中,SDK自動開啓一個定時器,這個定時器的超時時間由用戶在初始化Button時設置,默認是50ms。定時時間到後再次檢測GPIO狀態,如果仍然爲有效,則產生一個按鍵事件,對應的回調函數是bsp_event_handle,通過函數的入參,可以知道是哪個通道的中斷髮生了。
3.如果在50ms定時時間到達之前,又來了一次GPIOTE中斷,則SDK自動停止當前的定時器,接着立即啓動當前定時器,相當於重置了一次定時器。
上述過程3的動作每執行一次,會在定時器隊列中額外增加一個節點,雖然無效的定時器節點會很快被清除掉,但是如果GPIOTE事件連續而且密集地到達的話,可能會導致SDK來不及清理無效定時器,而使定時器隊列爆滿,導致定時器重啓失敗。而從上述過程2中可以得知,底層的GPIOTE事件是靠定時器超時來傳遞給上層應用的,如果定時器失效的話,就無法超時,也就無法將事件傳遞到上層了。對於上層應用來說,就是本次按鍵事件丟失。
其實,SDK開發者在註釋裏面對這個問題已有說明,原文如下:
static void gpiote_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
uint32_t err_code;
uint32_t pin_mask = 1 << pin;
// Start detection timer. If timer is already running, the detection period is restarted.
// NOTE: Using the p_context parameter of app_timer_start() to transfer the pin states to the
// timeout handler (by casting event_pins_mask into the equally sized void * p_context
// parameter).
err_code = app_timer_stop(m_detection_delay_timer_id);
if (err_code != NRF_SUCCESS)
{
// The impact in app_button of the app_timer queue running full is losing a button press.
// The current implementation ensures that the system will continue working as normal.
return;
}
// ...
}
所以,如果想解決這個問題,還是自己配置GPIOTE,自己從底層實現消抖爲好。