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

nRF52 BLE 개발하기 - simple_timer

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

시작하기

이번에는 simple_timer 예제를 살펴보도록 하자.

\examples\peripheral\simple_timer\pca10040\blank\ses 폴더에서 프로젝트를 실행시키고 링크를 통해 예제에 대한 설명을 살펴보자.

The Simple Timer Example demonstrates the usage of the Simple timer. It demonstrates the two modes of the module (single shot mode and repeated mode) to blink two LEDs.

When the application starts, the GPIO pins are configured as outputs to drive the LEDs.
The application then uses the simple timer driver library and alternates between the two timer modes.
During startup, both LEDs are lit for one second.
Then the application enters single shot mode for 2 seconds before repeated timer mode is started. After 2 seconds, both LEDs are lit again and the application loops.

See the following illustration for details.

Test the Simple Timer Example application by performing the following steps:

1. Compile and program the application.
2. Observe that the LED state is changed as expected:
- Both LEDs are lit for one second.
- LED 1 blinks for 2 seconds.
- LED 2 blinks for 2 seconds.
- The application loops.

simple_timer라는 예제의 제목에 비해 뭔가 구조가 복잡해 보이지만 천천히 분석해보도록 하자.

노르딕 인포센터의 설명에 따르면 LED1, 2가 1초 동안 켜져 있다가 LED1이 2초 동안 깜빡이고 LED2가 2초 동안 깜빡인다고 되어 있다.

이제 main.c 소스코드를 살펴보자.

#define TIMEOUT_VALUE                    50000                          /**< 50 mseconds timer time-out value. */
#define TOGGLE_LED_COUNTER               (500 / (TIMEOUT_VALUE / 1000)) /**< Interval for toggling a LED. Yields to 500 mseconds. */
#define STATE_TRANSIT_COUNTER_INIT_VALUE (4 * TOGGLE_LED_COUNTER)       /**< Initial value for the state transition counter.  */
#define GENERIC_DELAY_TIME               1000                           /**< Generic delay time used by application. */

/**@brief Application states. */
typedef enum
{
    APP_STATE_SINGLE_SHOT,                                              /**< Application state where single shot timer mode is tested. */
    APP_STATE_REPEATED                                                  /**< Application state where repeated timer mode is tested. */
} state_t;

static volatile uint32_t m_state_transit_counter = 0;                            /**< State transition counter variable. */
static volatile uint32_t m_toggle_led_counter    = 0;                            /**< Led toggling counter variable. */
static volatile state_t  m_state;                                                /**< Current application state. */

void timeout_handler(void * p_context);


void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{
    bsp_board_leds_off();

    for (;;)
    {
        nrf_delay_ms(GENERIC_DELAY_TIME);

        bsp_board_led_invert(BSP_BOARD_LED_0);
        bsp_board_led_invert(BSP_BOARD_LED_1);
    }
}


/**@brief Function for toggling a LED and starting a timer.
 *
 * @param[in] led_id     ID of the LED to toggle.
 * @param[in] timer_mode Timer mode @ref timer_mode_t.
 */
static void led_and_timer_control(uint32_t led_id, app_simple_timer_mode_t timer_mode)
{
    uint32_t err_code;

    bsp_board_led_invert(led_id);

    m_state_transit_counter = STATE_TRANSIT_COUNTER_INIT_VALUE;
    m_toggle_led_counter    = TOGGLE_LED_COUNTER;

    err_code = app_simple_timer_start(timer_mode, timeout_handler, TIMEOUT_VALUE, NULL);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for executing the state entry action.
 */
static __INLINE void state_entry_action_execute(void)
{
    switch (m_state)
    {
        case APP_STATE_SINGLE_SHOT:
            led_and_timer_control(BSP_BOARD_LED_0, APP_SIMPLE_TIMER_MODE_SINGLE_SHOT);
            break;

        case APP_STATE_REPEATED:
            led_and_timer_control(BSP_BOARD_LED_1, APP_SIMPLE_TIMER_MODE_REPEATED);
            break;

        default:
            APP_ERROR_HANDLER(m_state);
            break;
    }
}


/**@brief Function for changing the state of the state machine.
 *
 * @param[in] new_state  State to which the state machine transitions.
 */
static void state_machine_state_change(state_t new_state)
{
    m_state = new_state;
    state_entry_action_execute();
}


void timeout_handler(void * p_context)
{
    switch (m_state)
    {
        uint32_t err_code;

        case APP_STATE_SINGLE_SHOT:
            if (--m_state_transit_counter != 0)
            {
                if (--m_toggle_led_counter == 0)
                {
                    m_toggle_led_counter = TOGGLE_LED_COUNTER;
                    bsp_board_led_invert(BSP_BOARD_LED_0);
                }

                err_code = app_simple_timer_start(APP_SIMPLE_TIMER_MODE_SINGLE_SHOT,
                                       timeout_handler,
                                       TIMEOUT_VALUE,
                                       NULL);
                APP_ERROR_CHECK(err_code);
            }
            else
            {
                state_machine_state_change(APP_STATE_REPEATED);
            }
            break;

        case APP_STATE_REPEATED:
            if (--m_state_transit_counter != 0)
            {
                if (--m_toggle_led_counter == 0)
                {
                    m_toggle_led_counter = TOGGLE_LED_COUNTER;
                    bsp_board_led_invert(BSP_BOARD_LED_1);
                }
            }
            else
            {
                bsp_board_led_on(BSP_BOARD_LED_0);
                bsp_board_led_on(BSP_BOARD_LED_1);

                err_code = app_simple_timer_stop();
                APP_ERROR_CHECK(err_code);

                nrf_delay_ms(GENERIC_DELAY_TIME);

                state_machine_state_change(APP_STATE_SINGLE_SHOT);
            }
            break;

        default:
            APP_ERROR_HANDLER(m_state);
            break;
    }
}


/**@brief Function for the Power Management.
 */
static void power_manage(void)
{
    // Use directly __WFE and __SEV macros since the SoftDevice is not available.

    // Wait for event.
    __WFE();

    // Clear Event Register.
    __SEV();
    __WFE();
}


int main(void)
{
    uint32_t err_code = app_simple_timer_init();
    APP_ERROR_CHECK(err_code);

    bsp_board_init(BSP_INIT_LEDS);
    bsp_board_led_on(BSP_BOARD_LED_0);
    bsp_board_led_on(BSP_BOARD_LED_1);

    nrf_delay_ms(GENERIC_DELAY_TIME);

    state_machine_state_change(APP_STATE_SINGLE_SHOT);

    for (;;)
    {
        power_manage();
    }
}

가장 먼저 app_simple_timer_init 함수를 호출함으로 써 simple timer를 사용할 수 있게 한다.

저번 글의 timer 예제와 비슷하며 사용될 타이머로 SIMPLE_TIMER 값이 전달되는데 예제에서는 Timer 1로 정의되어 있다.

nrf_drv_timer_init 함수에 NRF_TIMER_FREQ_1MHz, NRF_TIMER_MODE_TIMER, NRF_TIMER_BIT_WIDTH_16 값을 전달 인자로 넘겨 설정한다. 이렇게 simple timer는 1us마다 동작하게 된다.

uint32_t app_simple_timer_init(void)
{
    uint32_t err_code = NRF_SUCCESS;
    nrf_drv_timer_config_t t_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    t_cfg.mode = NRF_TIMER_MODE_TIMER;
    t_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16;
    t_cfg.frequency = (nrf_timer_frequency_t)SIMPLE_TIMER_CONFIG_FREQUENCY;
    err_code = nrf_drv_timer_init(&SIMPLE_TIMER, &t_cfg, app_simple_timer_event_handler);

    if (NRF_SUCCESS == err_code)
    {
        m_simple_timer_state = SIMPLE_TIMER_STATE_INITIALIZED;
    }

    return err_code;
}

LED1, LED2를 켜주고 1초의 시간 지연을 주고 state_machine_state_change 함수의 전달 인자로 APP_STATE_SINGLE_SHOT을 넘겨준다.

state_machine_state_change 함수는 전달받은 매개 변수를 m_state에 저장하고 state_entry_action_execute 함수를 호출한다.

state_entry_action_execute 함수는 m_state 값에 따라 led_and_timer_control 함수를 호출한다.

여기서는 전달 인자로 BSP_BOARD_LED_0와 APP_SIMPLE_TIMER_MODE_SINGLE_SHOT이 넘어가게 된다.

led_and_timer_control 함수에서 LED1을 토글 해준다.

m_state_transit_counter 값은 40, m_toggle_led_counter 값은 10을 저장한다.

app_simple_timer_start 함수에 APP_SIMPLE_TIMER_MODE_SINGLE_SHOT, timeout_handler, TIMEOUT_VALUE(50000)를 전달 인자로 넘겨준다.

uint32_t app_simple_timer_start(app_simple_timer_mode_t            mode,
                                app_simple_timer_timeout_handler_t timeout_handler,
                                uint16_t                           timeout_ticks,
                                void *                             p_context)
{
    uint32_t err_code = NRF_SUCCESS;
    nrf_timer_short_mask_t timer_short;

    VERIFY_PARAM_NOT_NULL(timeout_handler);

    if (APP_SIMPLE_TIMER_MODE_REPEATED == mode)
    {
        timer_short = NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK;
    }
    else if (APP_SIMPLE_TIMER_MODE_SINGLE_SHOT == mode)
    {
        timer_short = NRF_TIMER_SHORT_COMPARE0_STOP_MASK;
    }
    else
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    if (SIMPLE_TIMER_STATE_IDLE == m_simple_timer_state)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    if (SIMPLE_TIMER_STATE_STARTED == m_simple_timer_state)
    {
        err_code = app_simple_timer_stop();
        APP_ERROR_CHECK(err_code);
    }

    if (SIMPLE_TIMER_STATE_STOPPED == m_simple_timer_state)
    {
        nrf_drv_timer_clear(&SIMPLE_TIMER);
    }

    m_mode                      = mode;
    m_timeout_handler           = timeout_handler;
    mp_timeout_handler_context  = p_context;

    nrf_drv_timer_extended_compare(
            &SIMPLE_TIMER, NRF_TIMER_CC_CHANNEL0, (uint32_t)timeout_ticks, timer_short, true);

    if (m_simple_timer_state == SIMPLE_TIMER_STATE_STOPPED)
    {
        nrf_drv_timer_resume(&SIMPLE_TIMER);
    }
    else
    {
        nrf_drv_timer_enable(&SIMPLE_TIMER);
    }


    m_simple_timer_state = SIMPLE_TIMER_STATE_STARTED;

    return NRF_SUCCESS;
}

simple timer의 mode는 APP_SIMPLE_TIMER_MODE_SINGLE_SHOT, APP_SIMPLE_TIMER_MODE_REPEATED 중 하나로 동작한다.

APP_SIMPLE_TIMER_MODE_SINGLE_SHOT 모드는 타이머가 한 번만 동작하는 것이고 APP_SIMPLE_TIMER_MODE_REPEATED 모드는 타이머가 계속 반복되는 것이다.

nrf_drv_timer_extended_compare 함수에 Timer 1, Timer CC channel 0, 50000, Shortcut resister에 COMPARE0_STOP_MASK를 전달 인자로 넘겨 설정한다.

 

정리해보면 main 함수에서의 state_machine_state_change(APP_STATE_SINGLE_SHOT)는 50ms에 timeout_handler 함수가 호출되게 한다.

APP_SIMPLE_TIMER_MODE_SINGLE_SHOT 모드는 타이머가 한 번만 동작하므로 timeout_handler 함수가 호출되고 나면 simple timer가 종료되지만 timeout_handler 함수에서 simple timer를 다시 시작시켜줌으로 50ms마다 반복되도록 하고 있다.

m_toggle_led_counter 값이 10이므로 0.5초마다 LED1을 토글 하며 m_state_transit_counter 값이 40이므로 2초동안 반짝이게 된다.

2초 후에는 state_machine_state_change(APP_STATE_REPEATED) 함수가 실행된다.

APP_SIMPLE_TIMER_MODE_REPEATED 모드는 타이머가 계속 반복하므로 simple timer를 재시작하지 않아도 50ms마다 timeout_handler 함수를 호출한다.

2초 후에 LED1, 2를 켜고 simleapp_simple_timer_stop 함수를 호출해 simple timer를 멈추고 1초의 시간 지연을 준다.

마지막으로 state_machine_state_change(APP_STATE_SINGLE_SHOT) 함수를 통해 다시 위의 과정을 반복하게 한다.

지금까지 알아본 내용을 바탕으로 노르딕에서 제공하는 simple_timer 예제보다 좀 더 심플하게 응용해 보도록 하자.

최초에 LED1, 2를 켜고 simple timer를 APP_SIMPLE_TIMER_MODE_SINGLE_SHOT 모드, 1ms 타임아웃으로 동작시킨다.

1초가 되면 LED2를 끄고 simple timer를 APP_SIMPLE_TIMER_MODE_REPEATED 모드로 바꾸면 1초마다 LED1이 깜빡이게 된다.

uint32_t repeated_count = 0;
uint32_t single_count = 0;
uint8_t state = 0;

void timeout_handler(void * p_context)
{
    uint32_t err_code;

    if(state == APP_SIMPLE_TIMER_MODE_REPEATED)
    {
        if(repeated_count == 1000)
        {
            bsp_board_led_invert(BSP_BOARD_LED_0);
            repeated_count = 0;
        }
        else
        {
            repeated_count++;
        }
    }
    else if(state == APP_SIMPLE_TIMER_MODE_SINGLE_SHOT)
    {
        if(single_count == 1000)
        {
            bsp_board_led_invert(BSP_BOARD_LED_1);
            single_count = 0;

            state = APP_SIMPLE_TIMER_MODE_REPEATED;
            err_code = app_simple_timer_start(state, timeout_handler, 1000, NULL);
            APP_ERROR_CHECK(err_code);
        }
        else
        {
            single_count++;

            err_code = app_simple_timer_start(APP_SIMPLE_TIMER_MODE_SINGLE_SHOT, timeout_handler, 1000, NULL);
            APP_ERROR_CHECK(err_code);
        }
    }
}

int main(void)
{
    uint32_t err_code = app_simple_timer_init();
    APP_ERROR_CHECK(err_code);

    bsp_board_init(BSP_INIT_LEDS);
    bsp_board_led_on(BSP_BOARD_LED_0);
    bsp_board_led_on(BSP_BOARD_LED_1);
    
    state = APP_SIMPLE_TIMER_MODE_SINGLE_SHOT;
    err_code = app_simple_timer_start(state, timeout_handler, 1000, NULL);
    APP_ERROR_CHECK(err_code);
}

앞으로

심플하지 않은 simple timer 예제에 대해서 알아보았다.

다음에는 RTC(Real Time Counter) 예제를 살펴보도록 하겠다.

 

 

 

728x90
반응형

댓글