nRF52

nRF52840 DK 보드 GPIO 기본 설정부터 인터럽트 활용까지

임베디드 친구 2024. 8. 29. 17:18
반응형

1. GPIO 입력/출력 제어 (nRF52840 DK 보드)

nRF52840 DK 보드에는 4개의 버튼과 4개의 LED가 있으며, 이들은 모두 GPIO를 통해 제어할 수 있습니다. LED는 P0.13부터 P0.16까지 연결되어 있으며, 버튼은 P0.11, P0.12, P0.24, P0.25에 연결되어 있습니다. 이러한 GPIO 핀 번호는 소스 코드의 pca10056.h 헤더 파일에 정의되어 있습니다.

button schematic
nRF52840 Development Kit의 버튼 LED 회로

1.1. GPIO 설정

nrf_gpio.h 파일에는 GPIO 설정을 위한 함수들이 정의되어 있습니다. 이 함수들을 사용하면 GPIO를 입력 또는 출력으로 설정할 수 있으며, 추가적인 세부 설정도 가능합니다. 아래는 nrf_gpio_cfg 함수를 사용한 예시입니다.

__STATIC_INLINE void nrf_gpio_cfg(
    uint32_t             pin_number,
    nrf_gpio_pin_dir_t   dir,
    nrf_gpio_pin_input_t input,
    nrf_gpio_pin_pull_t  pull,
    nrf_gpio_pin_drive_t drive,
    nrf_gpio_pin_sense_t sense)
{
    NRF_GPIO_Type * reg = nrf_gpio_pin_port_decode(&pin_number);

    reg->PIN_CNF[pin_number] = ((uint32_t)dir << GPIO_PIN_CNF_DIR_Pos)
                               | ((uint32_t)input << GPIO_PIN_CNF_INPUT_Pos)
                               | ((uint32_t)pull << GPIO_PIN_CNF_PULL_Pos)
                               | ((uint32_t)drive << GPIO_PIN_CNF_DRIVE_Pos)
                               | ((uint32_t)sense << GPIO_PIN_CNF_SENSE_Pos);
}

1.1.1. 매개변수 설명

  • GPIO 핀 방향 설정: GPIO 핀이 입력 또는 출력으로 사용될지를 정의합니다.
/** @brief Pin direction definitions. */
typedef enum
{
    NRF_GPIO_PIN_DIR_INPUT  = GPIO_PIN_CNF_DIR_Input, ///< Input.
    NRF_GPIO_PIN_DIR_OUTPUT = GPIO_PIN_CNF_DIR_Output ///< Output.
} nrf_gpio_pin_dir_t;

-입력 버퍼 연결 설정: GPIO를 입력으로 설정할 때, 값을 읽기 전에 임시로 버퍼에 저장할지를 선택합니다.

/** @brief Connection of input buffer. */
typedef enum
{
    NRF_GPIO_PIN_INPUT_CONNECT    = GPIO_PIN_CNF_INPUT_Connect,   ///< Connect input buffer.
    NRF_GPIO_PIN_INPUT_DISCONNECT = GPIO_PIN_CNF_INPUT_Disconnect ///< Disconnect input buffer.
} nrf_gpio_pin_input_t;
  • GPIO 핀 풀업/풀다운 설정: GPIO 핀의 초기 상태를 설정합니다 (풀업 또는 풀다운).
/**
 * @brief Enumerator used for selecting the pin to be pulled down or up at the time of pin
 * configuration.
 */
typedef enum
{
    NRF_GPIO_PIN_NOPULL   = GPIO_PIN_CNF_PULL_Disabled, ///<  Pin pull-up resistor disabled.
    NRF_GPIO_PIN_PULLDOWN = GPIO_PIN_CNF_PULL_Pulldown, ///<  Pin pull-down resistor enabled.
    NRF_GPIO_PIN_PULLUP   = GPIO_PIN_CNF_PULL_Pullup,   ///<  Pin pull-up resistor enabled.
} nrf_gpio_pin_pull_t;
  • 출력 드라이브 모드 설정: GPIO를 출력으로 설정할 때, 전류, 속도, 드라이브 강도를 설정합니다.
    cpp
/** @brief Enumerator used for selecting output drive mode. */
typedef enum
{
    NRF_GPIO_PIN_S0S1 = GPIO_PIN_CNF_DRIVE_S0S1, ///< !< Standard '0', standard '1'.
    NRF_GPIO_PIN_H0S1 = GPIO_PIN_CNF_DRIVE_H0S1, ///< !< High-drive '0', standard '1'.
    NRF_GPIO_PIN_S0H1 = GPIO_PIN_CNF_DRIVE_S0H1, ///< !< Standard '0', high-drive '1'.
    NRF_GPIO_PIN_H0H1 = GPIO_PIN_CNF_DRIVE_H0H1, ///< !< High drive '0', high-drive '1'.
    NRF_GPIO_PIN_D0S1 = GPIO_PIN_CNF_DRIVE_D0S1, ///< !< Disconnect '0' standard '1'.
    NRF_GPIO_PIN_D0H1 = GPIO_PIN_CNF_DRIVE_D0H1, ///< !< Disconnect '0', high-drive '1'.
    NRF_GPIO_PIN_S0D1 = GPIO_PIN_CNF_DRIVE_S0D1, ///< !< Standard '0', disconnect '1'.
    NRF_GPIO_PIN_H0D1 = GPIO_PIN_CNF_DRIVE_H0D1, ///< !< High-drive '0', disconnect '1'.
} nrf_gpio_pin_drive_t;
  • 감지 레벨 설정: GPIO를 입력으로 사용할 때, 감지할 전압 레벨을 설정합니다.
/** @brief Enumerator used for selecting the pin to sense high or low level on the pin input. */
typedef enum
{
    NRF_GPIO_PIN_NOSENSE    = GPIO_PIN_CNF_SENSE_Disabled, ///<  Pin sense level disabled.
    NRF_GPIO_PIN_SENSE_LOW  = GPIO_PIN_CNF_SENSE_Low,      ///<  Pin sense low level.
    NRF_GPIO_PIN_SENSE_HIGH = GPIO_PIN_CNF_SENSE_High,     ///<  Pin sense high level.
} nrf_gpio_pin_sense_t;

1.2. GPIO 입력 설정

GPIO 핀을 입력으로 설정하기 위해 nrf_gpio_cfg_input 함수를 사용합니다. 이 함수는 핀 번호와 풀업/풀다운 설정을 매개변수로 받습니다.

__STATIC_INLINE void nrf_gpio_cfg_input(uint32_t pin_number, nrf_gpio_pin_pull_t pull_config)
{
    nrf_gpio_cfg(
        pin_number,
        NRF_GPIO_PIN_DIR_INPUT,
        NRF_GPIO_PIN_INPUT_CONNECT,
        pull_config,
        NRF_GPIO_PIN_S0S1,
        NRF_GPIO_PIN_NOSENSE);
}

1.3. GPIO 출력 설정

GPIO 핀을 출력으로 설정하기 위해 nrf_gpio_cfg_output 함수를 사용합니다. 이 함수는 핀 번호만을 매개변수로 받으며, 기본적인 출력 설정을 적용합니다.

__STATIC_INLINE void nrf_gpio_cfg_output(uint32_t pin_number)
{
    nrf_gpio_cfg(
        pin_number,
        NRF_GPIO_PIN_DIR_OUTPUT,
        NRF_GPIO_PIN_INPUT_DISCONNECT,
        NRF_GPIO_PIN_NOPULL,
        NRF_GPIO_PIN_S0S1,
        NRF_GPIO_PIN_NOSENSE);
}

1.4. 버튼과 LED 제어 예제

버튼은 MCU 입장에서 입력이므로 nrf_gpio_cfg_input 함수를 이용해 GPIO 입력으로 설정하며, LED는 출력이므로 nrf_gpio_cfg_output 함수를 이용해 GPIO 출력으로 설정합니다. 아래 코드에서는 gpio_check_task_function을 FreeRTOS 태스크로 등록하여 버튼 1을 읽고, 눌리면 LED 1이 켜지도록 설정했습니다. nRF52840 DK 보드에서 LED와 버튼은 Low Active로 설계되어 있습니다. 즉, 버튼을 누르면 GPIO가 0으로 설정되고, LED는 GPIO 레지스터에 0을 입력하면 켜집니다.

#define INTI_LED_1          NRF_GPIO_PIN_MAP(0,13)
#define INTI_BUTTON_1       NRF_GPIO_PIN_MAP(0,11)

static void gpio_check_task_function (void * pvParameter)
{
    UNUSED_PARAMETER(pvParameter);
    while (true)
    {
        // 버튼이 눌리면 0의 값을 가짐.
        if(0 == nrf_gpio_pin_read(INTI_BUTTON_1))
            nrf_gpio_pin_write(INTI_LED_1, 0); // GPIO값이 Low 일때 LED가 켜짐.
        else
            nrf_gpio_pin_write(INTI_LED_1, 1); // GPIO값이 High 일때 LED가 꺼짐.

        /* Delay a task for a given number of ticks */
        vTaskDelay(TASK_DELAY);
    }
}

// GPIO를 용도에 맞게 설정해줌.
void gpio_in_out_config(void)
{
    // 버튼 1을 위한 GPIO Input 설정, 기본값은 PULL UP, 버튼을 누르면 Low 즉 0의 값을 가짐.
    nrf_gpio_cfg_input(INTI_BUTTON_1, NRF_GPIO_PIN_PULLUP);

    // LED_1응 위한 GPIO Output 설정.
    nrf_gpio_cfg_output(INTI_LED_1);
}

int main(void)
{
    ret_code_t err_code;

    gpio_in_out_config();

    /* Create task for LED */
    UNUSED_VARIABLE(xTaskCreate(gpio_check_task_function, "LED0", configMINIMAL_STACK_SIZE + 200, NULL, 2, NULL));

    /* Activate deep sleep mode */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    /* Start FreeRTOS scheduler. */
    vTaskStartScheduler();

    while (true)
    {
        /* FreeRTOS should not be here... FreeRTOS goes back to the start of stack
         * in vTaskStartScheduler function. */
    }
}

2. GPIO 인터럽트

GPIO 인터럽트를 활용하기 위해서는 GPIOTE 라이브러리를 사용해야 합니다. GPIOTE는 "GPIO Task and Events"의 약자로, GPIO 이벤트를 위한 라이브러리입니다. 아래 예제에서는 Button 3에 대해 인터럽트를 등록하였고, 버튼을 누를 때 인터럽트가 발생하며, 인터럽트 핸들러에서 플래그를 true로 변경합니다. 이후 태스크 핸들러에서 LED 3이 5번 깜빡인 후 플래그를 초기화하도록 설계하였습니다.

bool g_butt3_int = false;
int g_led_blink_count = 0;

static void gpio_check_task_function (void * pvParameter)
{
    UNUSED_PARAMETER(pvParameter);
    while (true)
    {
        // Button 3 Interrupt 발생 시 LED를 5번 깜빡임.
        if(g_butt3_int && 10 > g_led_blink_count++)
            nrf_gpio_pin_toggle(INTI_LED_3);
        else
        {
            g_butt3_int = false;
            g_led_blink_count = 0;
        }

        /* Delay a task for a given number of ticks */
        vTaskDelay(TASK_DELAY);
    }
}

void button3_irq_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    // GPIO 값이 0이 아닌 경우 아무런 처리도 하지 않음.
    if(true == nrf_drv_gpiote_in_is_set(INTI_BUTTON_3))
        return;

    // LED 깜빡이는 중에 Interrupt 발생 시 무시
    if(true == g_butt3_int)
        return;

    // 버튼 Interrupt Flag 설정
    g_butt3_int = true;
    g_led_blink_count = 0;
}

void gpiote_interrupt_enable(void)
{
    ret_code_t err_code = NRF_SUCCESS;

    // GPIOTE 모듈을 초기화 해줌.
    nrf_drv_gpiote_init();

    // GPIOTE Input 설정을 해줌.
    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;

    // Button 3GPIO을 GPIOTE Input 용으로 설정해줌.
    err_code = nrf_drv_gpiote_in_init(INTI_BUTTON_3, &in_config, button3_irq_handler);
    if(NRF_SUCCESS != err_code)
        NRF_LOG_ERROR("GPIOTE Init Failed");

    // Button 3 GPIO에 대해서 Event 설정(Interrupt Enable)을 해줌.
    nrf_drv_gpiote_in_event_enable(INTI_BUTTON_3, true);

    // LED 3 GPIO를 Out 용도로 설정하고 초기화 해줌.
    nrf_gpio_cfg_output(INTI_LED_3);
    nrf_gpio_pin_set(INTI_LED_3);
}

int main(void)
{
    gpiote_interrupt_enable();

    /* Create task for LED */
    UNUSED_VARIABLE(xTaskCreate(gpio_check_task_function, "LED0", configMINIMAL_STACK_SIZE + 200, NULL, 2, NULL));

    /* Activate deep sleep mode */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    /* Start FreeRTOS scheduler. */
    vTaskStartScheduler();

    while (true)
    {
        /* FreeRTOS should not be here... FreeRTOS goes back to the start of stack
         * in vTaskStartScheduler function. */
    }
}

GPIO는 시스템에서 디지털 신호를 처리하는 데 사용되며, 디지털 입력 또는 출력을 통해 다양한 작업을 수행할 수 있습니다. 위에서 설명한 GPIO 설정과 인터럽트 활용 방법을 통해, nRF52840 DK 보드에서 버튼과 LED를 제어할 수 있는 기본적인 방법을 익힐 수 있습니다.

3. 결론

이 글에서는 nRF52840 DK 보드에서 GPIO를 활용하여 입력 및 출력을 설정하고, 이를 통해 버튼과 LED를 제어하는 방법을 다루었습니다. GPIO 설정의 기본 개념부터, 이를 이용해 버튼을 누르면 LED를 켜고 끄는 간단한 예제, 그리고 GPIOTE 라이브러리를 이용한 GPIO 인터럽트 설정 방법까지 설명했습니다.

nRF52840 DK 보드와 같은 임베디드 시스템에서 GPIO는 매우 중요한 역할을 합니다. 디지털 신호를 처리하고 다양한 외부 장치와의 상호작용을 가능하게 하며, 이를 통해 시스템의 확장성과 유연성을 제공합니다. 이 문서에서 설명한 내용을 바탕으로, 개발자는 nRF52840 DK 보드를 활용한 다양한 응용 프로그램을 설계하고 구현할 수 있을 것입니다.

특히, 인터럽트를 활용한 GPIO 제어는 시스템의 효율성을 높이는 데 중요한 역할을 하므로, 복잡한 시스템에서는 이를 적극적으로 활용하는 것이 좋습니다.

반응형