본문 바로가기
Embedded/nRF52 BLE 개발 안내서

nRF52 BLE 개발하기 - pin_change_int

by 큐찡 2021. 1. 11.
728x90
반응형

시작하기

이번에는 버튼을 통해 GPIO 입력을 받을 수 있는 예제를 살펴보도록 하자.

예제는 \examples\peripheral\pin_change_int\pca10040\blank\ses 폴더에서 프로젝트를 실행시켜보자.

그리고 예제에 대한 설명을 확인하기 위해 노르딕 인포센터에서 Pin Change Interrupt Example 링크에 접속하자.

The Pin Change Interrupt Example demonstrates interrupts on PIN_IN change. PIN_OUT is configured as output and toggled in the PIN_IN change interrupt handler.

The application starts with configuring the pins and configuring GPIOTE to give an interrupt on PIN_IN change. The interrupt handler toggles PIN_OUT (LED 1) when PIN_IN (button 1) is pressed or released.

Setup
You can find the source code and the project file of the example in the following folder: <InstallFolder>\examples\peripheral\pin_change_int

LED assignments:
LED 1 is toggled by interrupt.

Button assignments:
Button 1 triggers interrupt that toggles LED 1.

Testing
Test the Pin Change Interrupt Example application by performing the following steps:
1. Compile and program the application.
2. Press button 1 and check if LED 1 toggles.

PIN_IN(버튼 1)은 버튼을 누르거나 놓을 때 인터럽트 핸들러가 동작하도록 설정하고 PIN_OUT(LED1)은 인터럽트 핸들러가 발생하면 토글 되도록 GPIOTE를 사용하여 설정했다고 한다.

그렇다면 GPIOTE가 어떤 것인지 먼저 살펴보도록 하자.

일단 GPIOTE는 GPIO Tasks and Events의 약자로 작업 및 이벤트를 사용하여 GPIO 핀에 액세스 하는 기능을 제공한다.

최대 32개의 I/O를 하나의 포트를 통해 액세스 및 제어할 수 있다고 한다.

즉, GPIO 인터럽트 핸들러 사용을 위해서는 GPIOTE 설정이 꼭 필요하다고 볼 수 있겠다.

이제 main.c의 본문 내용을 살펴보도록 하자.

#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

void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    nrf_drv_gpiote_out_toggle(PIN_OUT);
}
/**
 * @brief Function for configuring: PIN_IN pin for input, PIN_OUT pin for output,
 * and configures GPIOTE to give an interrupt on pin change.
 */
static void gpio_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_SIMPLE(false);

    err_code = nrf_drv_gpiote_out_init(PIN_OUT, &out_config);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}

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

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

main 함수는 gpio_init 함수 하나만을 호출하는 것으로 굉장히 간단해 보이니 바로 gpio_init 함수를 자세히 살펴보자.

가장 먼저 nrf_drv_gpiote_init 함수를 호출함으로써 GPIOTE를 사용할 수 있도록 하는 것으로 시작된다.

/** @brief Macro for configuring a pin to use as output. GPIOTE is not used for the pin. */
#define NRFX_GPIOTE_CONFIG_OUT_SIMPLE(init_high)                                                \
    {                                                                                           \
        .action     = NRF_GPIOTE_POLARITY_LOTOHI,                                               \
        .init_state = init_high ? NRF_GPIOTE_INITIAL_VALUE_HIGH : NRF_GPIOTE_INITIAL_VALUE_LOW, \
        .task_pin   = false,                                                                    \
    }
    
nrfx_err_t nrfx_gpiote_out_init(nrfx_gpiote_pin_t                pin,
                                nrfx_gpiote_out_config_t const * p_config)
{
    NRFX_ASSERT(nrf_gpio_pin_present_check(pin));
    NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_INITIALIZED);
    NRFX_ASSERT(p_config);

    nrfx_err_t err_code = NRFX_SUCCESS;

    if (pin_in_use(pin))
    {
        err_code = NRFX_ERROR_INVALID_STATE;
    }
    else
    {
        if (p_config->task_pin)
        {
            int8_t channel = channel_port_alloc(pin, NULL, true);

            if (channel != NO_CHANNELS)
            {
                nrf_gpiote_task_configure((uint32_t)channel,
                                          pin,
                                          p_config->action,
                                          p_config->init_state);
            }
            else
            {
                err_code = NRFX_ERROR_NO_MEM;
            }
        }
        else
        {
            pin_in_use_set(pin);
        }

        if (err_code == NRFX_SUCCESS)
        {
            if (p_config->init_state == NRF_GPIOTE_INITIAL_VALUE_HIGH)
            {
                nrf_gpio_pin_set(pin);
            }
            else
            {
                nrf_gpio_pin_clear(pin);
            }

            nrf_gpio_cfg_output(pin);
            pin_configured_set(pin);
        }
    }

    NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}

nrf_drv_gpiote_out_init 함수에 GPIOTE_CONFIG_OUT_SIMPLE(false) 값으로 PIN_OUT을 설정한다.

task_pin 값이 false이므로 pin_in_use_set 함수에 PIN_OUT(LED1, P0.17)이 전달 인자로 넘어간다.

init_state 값이 NRF_GPIOTE_INITIAL_VALUE_LOW이므로 초기값으로 GPIO 핀을 clear 시켜주며 이 시점에서 LED1은 켜지게 된다.

마지막으로 GPIO 핀을 output으로 설정해주는 것으로 끝나게 된다.

/**
 * @brief Macro for configuring a pin to use a GPIO IN or PORT EVENT to detect any change on the pin.
 * @details Set hi_accu to true to use IN_EVENT.
 */
#define NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(hi_accu) \
{                                                   \
    .sense = NRF_GPIOTE_POLARITY_TOGGLE,            \
    .pull = NRF_GPIO_PIN_NOPULL,                    \
    .is_watcher = false,                            \
    .hi_accuracy = hi_accu,                         \
    .skip_gpio_setup = false,                       \
}

nrfx_err_t nrfx_gpiote_in_init(nrfx_gpiote_pin_t               pin,
                               nrfx_gpiote_in_config_t const * p_config,
                               nrfx_gpiote_evt_handler_t       evt_handler)
{
    NRFX_ASSERT(nrf_gpio_pin_present_check(pin));
    nrfx_err_t err_code = NRFX_SUCCESS;

    /* Only one GPIOTE channel can be assigned to one physical pin. */
    if (pin_in_use_by_gpiote(pin))
    {
        err_code = NRFX_ERROR_INVALID_STATE;
    }
    else
    {
        int8_t channel = channel_port_alloc(pin, evt_handler, p_config->hi_accuracy);
        if (channel != NO_CHANNELS)
        {
            if (!p_config->skip_gpio_setup)
            {
                if (p_config->is_watcher)
                {
                    nrf_gpio_cfg_watcher(pin);
                }
                else
                {
                    nrf_gpio_cfg_input(pin, p_config->pull);
                }
                pin_configured_set(pin);
            }

            if (p_config->hi_accuracy)
            {
                nrf_gpiote_event_configure((uint32_t)channel, pin, p_config->sense);
            }
            else
            {
                m_cb.port_handlers_pins[channel - GPIOTE_CH_NUM] |= (p_config->sense) <<
                                                                    POLARITY_FIELD_POS;
            }
        }
        else
        {
            err_code = NRFX_ERROR_NO_MEM;
        }
    }

    NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}

nrf_drv_gpiote_in_init 함수에 GPIOTE_CONFIG_IN_SENSE_TOGGLE(true) 값으로 PIN_IN과 in_pin_handler 함수를 설정한다.

channel_port_alloc 함수에 PIN_IN(버튼 1, P0.13)과 in_pin_handler, hi_accuracy 값을 전달 인자로 채널을 할당받는다.

skip_gpio_setup 값과 is_watcher 값이 false이므로 PIN_IN(버튼 1, P0.13)을 input으로 설정한다.

hi_accuracy 값이 true이므로 nrf_gpiote_event_configure 함수에 채널과 PIN_IN(버튼 1, P0.13), NRF_GPIOTE_POLARITY_TOGGLE 값으로 전달 인자로 넘기면서 핀이 변경(토글)되면 할당된 채널에 이벤트가 발생하도록 설정한다.

마지막으로 nrf_drv_gpiote_in_event_enable 함수로 PIN_IN과 true값을 전달 인자로 넘겨 이벤트를 활성시키는 것으로 끝난다.

즉, 버튼 1의 상태가 변경(토글)될 때마다 in_pin_handler 함수가 동작되면서 LED1의 출력이 변경(토글)되게 된다.

위 소스코드를 응용하여 (버튼 1:LED1), (버튼 2:LED2), (버튼 3:LED3), (버튼 4:LED4)이 동작하도록 수정해보자.

void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    if(pin == 13) nrf_drv_gpiote_out_toggle(17);
    else if(pin == 14) nrf_drv_gpiote_out_toggle(18);
    else if(pin == 15) nrf_drv_gpiote_out_toggle(19);
    else if(pin == 16) nrf_drv_gpiote_out_toggle(20);
    else  return;
}
/**
 * @brief Function for configuring: PIN_IN pin for input, PIN_OUT pin for output,
 * and configures GPIOTE to give an interrupt on pin change.
 */
static void gpio_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_SIMPLE(false);

    err_code = nrf_drv_gpiote_out_init(17, &out_config);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_gpiote_out_init(18, &out_config);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_gpiote_out_init(19, &out_config);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_gpiote_out_init(20, &out_config);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(13, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_gpiote_in_event_enable(13, true);

    err_code = nrf_drv_gpiote_in_init(14, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_gpiote_in_event_enable(14, true);

    err_code = nrf_drv_gpiote_in_init(15, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_gpiote_in_event_enable(15, true);

    err_code = nrf_drv_gpiote_in_init(16, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrf_drv_gpiote_in_event_enable(16, true);
}

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

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

앞으로

지금까지는 가장 기본적인 GPIO 출력과 입력에 대해 알아보았다.

다음에는 nRF52가 가지고 있는 여러 가지 종류의 타이머 사용 방법에 대해 알아보도록 하자.

 

728x90
반응형

댓글