nRF52でPPIを使ってGPIOTE EventでTIMER Taskを起動する

# GPIOTE EventでTIMER Taskを起動する

Nordic SDK 14.20のサンプルプロジェクトpin_change_intとppiを参考に
PPIを使って、GPIOTEのIO変化を契機にTIMERを開始するサンプルを書いてみた。
以下にコードと各関数の処理概要を記す。

input_sampling_event_init関数では、GPIOTEとTIMER、PPIの初期設定を行う。
GPIOTEは、PIN_IN(DK Boardのボタン1)のH→L変化を捉えて、in_pin_handlerが呼ばれる。
TIMER0は、1ms毎にtimer_handlerが呼ばれる。
PPIで、GPIOTEとTIMER0を連携する。

input_sampling_event_enable関数では、input_sampling_event_init関数で設定したPPIを有効化する。これで、GPIOTEによるEventでTIMER0のTaskが起動する。
なお、今サンプルでは、TIEMR0のTaskは開始するが停止については書いていない。

main関数では、input_sampling_event_init関数とinput_sampling_event_enable関数を呼び、空ループに入る。

```c:main.c
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "app_error.h"
#include "boards.h"

#ifdef BSP_BUTTON_0
    #define PIN_IN BSP_BUTTON_0
#endif
#ifndef PIN_IN
    #error "Please indicate input pin"
#endif

#ifdef BSP_LED_0
    #define PIN_OUT BSP_LED_0
#endif
#ifndef PIN_OUT
    #error "Please indicate output pin"
#endif


static const nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(0);
static nrf_ppi_channel_t     m_ppi_channel;

static uint32_t m_counter = 0;


void timer_handler(nrf_timer_event_t event_type, void * p_context)
{
    m_counter++;
}


void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    m_counter = 0;
}


static void input_sampling_event_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;
    in_config.hi_accuracy = false;
    err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);

    
    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;

    err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
    APP_ERROR_CHECK(err_code);

    /* setup m_timer for compare event every 1ms */
    uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 1);
    nrf_drv_timer_extended_compare(&m_timer,
                                   NRF_TIMER_CC_CHANNEL0,
                                   ticks,
                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                   true);

    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    APP_ERROR_CHECK(err_code);

    uint32_t gpiote_in_event_addr = nrf_drv_gpiote_in_event_addr_get(PIN_IN);
    uint32_t timer_task_addr      = nrf_drv_timer_task_address_get(&m_timer, NRF_TIMER_TASK_START);

    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                          gpiote_in_event_addr,
                                          timer_task_addr);
    APP_ERROR_CHECK(err_code);


    nrf_drv_gpiote_in_event_enable(PIN_IN, true);

}


void input_sampling_event_enable(void)
{
    ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);

    APP_ERROR_CHECK(err_code);
}


/**
 * @brief Function for application main entry.
 */
int main(void)
{
    input_sampling_event_init();
    input_sampling_event_enable();

    while (true)
    {
        // Do nothing.
    }
}


/** @} */
```


実は少し前、pin_change_intを参考に、IO変化のコールバック関数内でTIMERの開始を行うコードを書いていたのだが、ふとPPIを使っても出来ると思ったのでやってみた。
積極的にPPI(ハードウェア処理)に任せていくのがいいのだと思う。

応用すれば、GPIOTE(Event)→PPI→TIMER(Task/Event)→PPI→SAADC(Task)
のような数珠繋ぎにして、スイッチを押したら定期ADサンプリングを開始
するような書き方も出来る。