PPI,英文全稱Programmable Peripheral Interconnect,是Nordic獨有的外設,其設計目的是讓CPU處於idle模式下外設與外設之間也能完成相應通信,從而降低系統功耗。為此,很多人會把PPI類比成DMA,的確PPI和DMA兩者在設計最終目的上有一定的相似性,但兩者的功能和原理完全不相同。講解PPI原理之前,先大概闡述一下Nordic芯片一個獨特的設計理念。
Nordic芯片每個外設都可以看做一個狀態機,所以每個外設都有輸入(task),輸出(event)以及狀態。比較常見的task比如啟動外設,清0寄存器等,常見的event比如數據發送完畢,外設關閉等。實現上,每個task和event都是一個獨立的32-bit寄存器,這跟傳統的芯片不一樣,傳統的芯片都是用寄存器的某一個bit來啟動,或者某一個bit來顯示狀態。換句話說,Nordic把傳統芯片1bit要做的事情換成一個完整的32-bit寄存器來實現。這樣做的好處是,每個task和event都是一個寄存器,他們都可以用一個獨立而唯一的地址來標識,這就為PPI打下了堅實的基礎。比如Timer模塊的寄存器列表如下所示,里面就包含了各種task和event寄存器:

再比如ADC模塊的寄存器列表如下所示,里面也包含了各種task和event寄存器:

現在開始講PPI,首先PPI也是一個外設,因此PPI也有自己的寄存器定義,如下圖所示,其主要用來配置PPI通道等。簡言之,PPI就是一個數字邏輯系統,它可以通過某一個PPI通道把外設1的event跟外設2的task相連,這樣一旦外設1的event置起(相關寄存器為1),將會自動觸發外設2的task(將相關寄存器自動置1)。

下面以一個實際例子來加深大家對PPI的理解,假設我們要實現如下功能:啟動timer,定時10ms,10ms到后啟動ADC模塊。這里我們以兩種方式來實現該例子要求:傳統方式和PPI方式,大家仔細比較兩者的區別,以理解PPI的積極作用。
傳統方式
傳統方式大致需要如下步驟:
- 初始化Timer模塊和ADC模塊——(CPU工作)
- 啟動Timer——(CPU工作)
- 等待Timer中斷——(CPU不工作)
- 10ms到,進入Timer timeout handler,啟動ADC——(CPU工作)
PPI方式
PPI方式大致需要如下步驟:
- 初始化Timer模塊,ADC模塊,以及PPI模塊,將Timer模塊的timeout event和ADC模塊的start task相連——(CPU工作)
- 啟動Timer——(CPU工作)
- 等待Timer timeout,10ms到,PPI將自動啟動ADC——(CPU不工作)
通過比較傳統方式和PPI方式,PPI方式可以少進入一次timeout handler,從而減少CPU工作時間,降低系統功耗。由於不需要進入timeout handler,因此啟動ADC的操作就不存在被其他高優先級中斷打斷的可能,這是PPI帶來的第二個好處:系統實時性更好。
除了一個event對應一個task,PPI通過fork機制可以讓一個event同時啟動2個task,即把一個event同時和一個task以及task對應的fork相連,以實現一個event到來,2個task同時啟動的目的。
大家可以參考SDK自帶例子(Keil5工程):
SDK安裝目錄\examples\peripheral\ppi\pca10040\blank\arm5_no_packs
或者
SDK安裝目錄\examples\peripheral\gpiote\pca10040\blank\arm5_no_packs
來進一步理解PPI的工作原理和編程注意事項。
下面為一段PPI使用代碼示例,它實現的功能是:當定時時間到,通過PPI自動去操作IO口
err_code = nrf_drv_ppi_init(); APP_ERROR_CHECK(err_code); err_code = nrf_drv_ppi_channel_alloc(&ppi_channel); APP_ERROR_CHECK(err_code); compare_evt_addr = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0); gpiote_task_addr = nrf_drv_gpiote_out_task_addr_get(GPIO_OUTPUT_PIN_NUMBER); err_code = nrf_drv_ppi_channel_assign(ppi_channel, compare_evt_addr, gpiote_task_addr); APP_ERROR_CHECK(err_code); err_code = nrf_drv_ppi_channel_enable(ppi_channel); APP_ERROR_CHECK(err_code);
