시작하기
이번에는 타이머의 마지막 예제로 WDT(Watch Dog Timer)에 대해서 알아보도록 하겠다.
와치독 타이머란 시스템이 비정상적인 동작을 하여 멈춰버리는 것을 방지하기 위해 쓰이는 방법 중 하나이다.
상당히 유용하며 중요한 장치로 임베디드 시스템에 빠질 수 없는 타이머이다.
이제 \examples\peripheral\wdt\pca10040\blank\ses 폴더에서 프로젝트를 실행시키고 예제를 살펴보도록 하자.
The WDT Example demonstrates the usage of the watchdog peripherals.
When the application starts, all LEDs are turned on one after the other.
When all LEDs are lit, user input ("feeding the watchdog") is required to avoid system reset.
Pressing Button 1 triggers feeding the watchdog. The watchdog timeout is two seconds.
Therefore, it should be fed at least once every two seconds.
Test the WDT Example application by performing the following steps:
1. Compile and program the application.
2. Observe that the system is starting up and the LEDs are turned on.
3. Feed the watchdog by pressing Button 1.
- If the watchdog is fed regularly, all LEDs remain turned on.
- If the watchdog is not fed within two seconds, the watchdog timer will expire and the system will be reset.
wdt 예제는 시스템이 시작되면 LED를 순서대로 하나씩 켠다.
그리고 2초 안에 버튼 1을 누르지 않으면 와치독 타이머에 의해 시스템이 재시작하게 된다.
이제 main.c 소스코드를 살펴보도록 하자.
#define FEED_BUTTON_ID 0 /**< Button for feeding the dog. */
nrf_drv_wdt_channel_id m_channel_id;
/**
* @brief WDT events handler.
*/
void wdt_event_handler(void)
{
bsp_board_leds_off();
//NOTE: The max amount of time we can spend in WDT interrupt is two cycles of 32768[Hz] clock - after that, reset occurs
}
/**
* @brief Assert callback.
*
* @param[in] id Fault identifier. See @ref NRF_FAULT_IDS.
* @param[in] pc The program counter of the instruction that triggered the fault, or 0 if
* unavailable.
* @param[in] info Optional additional information regarding the fault. Refer to each fault
* identifier for details.
*/
void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{
bsp_board_leds_off();
while (1);
}
/**
* @brief BSP events callback.
*/
void bsp_event_callback(bsp_event_t event)
{
switch (event)
{
case BSP_EVENT_KEY_0:
nrf_drv_wdt_channel_feed(m_channel_id);
break;
default :
//Do nothing.
break;
}
}
/**
* @brief Function for main application entry.
*/
int main(void)
{
uint32_t err_code = NRF_SUCCESS;
//BSP configuration for button support: button pushing will feed the dog.
err_code = nrf_drv_clock_init();
APP_ERROR_CHECK(err_code);
nrf_drv_clock_lfclk_request(NULL);
err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
err_code = bsp_init(BSP_INIT_BUTTONS, bsp_event_callback);
APP_ERROR_CHECK(err_code);
//Configure all LEDs on board.
bsp_board_init(BSP_INIT_LEDS);
//Configure WDT.
nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
err_code = nrf_drv_wdt_init(&config, wdt_event_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
APP_ERROR_CHECK(err_code);
nrf_drv_wdt_enable();
//Indicate program start on LEDs.
for (uint32_t i = 0; i < LEDS_NUMBER; i++)
{ nrf_delay_ms(200);
bsp_board_led_on(i);
}
err_code = bsp_buttons_enable();
APP_ERROR_CHECK(err_code);
while (1)
{
__SEV();
__WFE();
__WFE();
}
}
nrf_drv_clock_init, nrf_drv_clock_lfclk_request, app_timer_init 함수를 호출해 clock을 설정하며 기본적으로 rtc와 비슷하다.
app timer가 16.348Khz 주기가 되도록 prescaler 값을 1로 설정하면 1 TICK에 약 61.035us가 된다.
drv_rtc_overflow_enable 함수를 호출해 rtc overflow를 활성해주고 drv_rtc_compare_set 함수의 전달 인자로 Compare channel 1의 Compare 값으로 0x007 FFFFF(8388607)를 전달해 61.035us * 8388607 = 약 511.7초의 overflow로 설정된다.
ret_code_t app_timer_init(void)
{
ret_code_t err_code;
drv_rtc_config_t config = {
.prescaler = APP_TIMER_CONFIG_RTC_FREQUENCY,
.interrupt_priority = APP_TIMER_CONFIG_IRQ_PRIORITY
};
err_code = NRF_ATFIFO_INIT(m_req_fifo);
if (err_code != NRFX_SUCCESS)
{
return err_code;
}
err_code = drv_rtc_init(&m_rtc_inst, &config, rtc_irq);
if (err_code != NRFX_SUCCESS)
{
return err_code;
}
drv_rtc_overflow_enable(&m_rtc_inst, true);
drv_rtc_compare_set(&m_rtc_inst, 1, DRV_RTC_MAX_CNT >> 1, true);
if (APP_TIMER_KEEPS_RTC_ACTIVE)
{
drv_rtc_start(&m_rtc_inst);
}
m_global_active = true;
return err_code;
}
그다음으로 bsp_init 함수를 살펴보도록 하자.
m_registered_callback에 bsp_event_callback 함수가 전달된다.
bsp_event_to_button_action_assign 함수를 통해 버튼이 눌렸을 때 해당 버튼의 PUSH 이벤트를 할당해준다.
app_button_init 함수의 전달 인자로 app_buttons, BUTTONS_NUMBER, APP_TIMER_TICKS(50)가 전달된다.
먼저 app_buttons은 버튼과 이벤트 핸들러를 정의해놓은 일종의 매크로라고 볼 수 있고, BUTTONS_NUMBER는 버튼 개수로 값은 4이다.
APP_TIMER_TICKS는 ms를 TICK으로 변환하는 계산식으로 50을 입력하면 819 TICK이며 이는 49,987us로 약 50ms가 되는 것을 알 수 있다.
uint32_t bsp_init(uint32_t type, bsp_event_callback_t callback)
{
uint32_t err_code = NRF_SUCCESS;
#if LEDS_NUMBER > 0 && !(defined BSP_SIMPLE)
m_indication_type = type;
#endif // LEDS_NUMBER > 0 && !(defined BSP_SIMPLE)
#if (BUTTONS_NUMBER > 0) && !(defined BSP_SIMPLE)
m_registered_callback = callback;
// BSP will support buttons and generate events
if (type & BSP_INIT_BUTTONS)
{
uint32_t num;
for (num = 0; ((num < BUTTONS_NUMBER) && (err_code == NRF_SUCCESS)); num++)
{
err_code = bsp_event_to_button_action_assign(num, BSP_BUTTON_ACTION_PUSH, BSP_EVENT_DEFAULT);
}
if (err_code == NRF_SUCCESS)
{
err_code = app_button_init((app_button_cfg_t *)app_buttons,
BUTTONS_NUMBER,
APP_TIMER_TICKS(50));
}
if (err_code == NRF_SUCCESS)
{
err_code = app_button_enable();
}
if (err_code == NRF_SUCCESS)
{
err_code = app_timer_create(&m_bsp_button_tmr,
APP_TIMER_MODE_SINGLE_SHOT,
button_timer_handler);
}
}
#elif (BUTTONS_NUMBER > 0) && (defined BSP_SIMPLE)
bsp_board_init(type);
#endif // (BUTTONS_NUMBER > 0) && !(defined BSP_SIMPLE)
#if LEDS_NUMBER > 0 && !(defined BSP_SIMPLE)
if (type & BSP_INIT_LEDS)
{
//handle LEDs only. Buttons are already handled.
bsp_board_init(BSP_INIT_LEDS);
// timers module must be already initialized!
if (err_code == NRF_SUCCESS)
{
err_code =
app_timer_create(&m_bsp_leds_tmr, APP_TIMER_MODE_SINGLE_SHOT, leds_timer_handler);
}
if (err_code == NRF_SUCCESS)
{
err_code =
app_timer_create(&m_bsp_alert_tmr, APP_TIMER_MODE_REPEATED, alert_timer_handler);
}
}
#endif // LEDS_NUMBER > 0 && !(defined BSP_SIMPLE)
return err_code;
}
app_button_init 함수를 자세히 알아보자.
nrf_drv_gpiote_init 함수를 호출해 GPIOTE를 사용할 수 있도록 설정해 준다.
nrf_drv_gpiote_in_init 함수로 버튼들이 토글 되면 gpiote_event_handler 함수가 호출되도록 설정해 준다.
그리고 app_timer_create 함수로 APP_TIMER_MODE_SINGLE_SHOT 모드의 타이머를 생성한다.
버튼이 눌리면 detection_delay_timeout_handler 함수가 호출되는데 evt_handle 함수로 버튼 상태를 체크하는 함수이다.
app_button_enable 함수를 호출해 GPIOTE 입력 이벤트를 활성해준다.
bsp_button_event_handler 함수는 버튼이 눌렸을 때 호출되며 해당 버튼은 m_registered_callback 함수로 event 값을 전달한다.
bsp_event_callback 함수를 보면 event 값이 BSP_EVENT_KEY_0인 경우에 nrf_drv_wdt_channel_feed 함수를 호출해 wdt를 리셋해준다.
uint32_t app_button_init(app_button_cfg_t const * p_buttons,
uint8_t button_count,
uint32_t detection_delay)
{
uint32_t err_code;
if (detection_delay < 2*APP_TIMER_MIN_TIMEOUT_TICKS)
{
return NRF_ERROR_INVALID_PARAM;
}
if (!nrf_drv_gpiote_is_init())
{
err_code = nrf_drv_gpiote_init();
VERIFY_SUCCESS(err_code);
}
/* Save configuration. */
mp_buttons = p_buttons;
m_button_count = button_count;
m_detection_delay = detection_delay;
memset(m_pin_states, 0, sizeof(m_pin_states));
m_pin_active = 0;
while (button_count--)
{
app_button_cfg_t const * p_btn = &p_buttons[button_count];
#if defined(BUTTON_HIGH_ACCURACY_ENABLED) && (BUTTON_HIGH_ACCURACY_ENABLED == 1)
nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(p_btn->hi_accuracy);
#else
nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);
#endif
config.pull = p_btn->pull_cfg;
err_code = nrf_drv_gpiote_in_init(p_btn->pin_no, &config, gpiote_event_handler);
VERIFY_SUCCESS(err_code);
}
/* Create polling timer. */
return app_timer_create(&m_detection_delay_timer_id,
APP_TIMER_MODE_SINGLE_SHOT,
detection_delay_timeout_handler);
}
/* GPIOTE event is used only to start periodic timer when first button is activated. */
static void gpiote_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
app_button_cfg_t const * p_btn = button_get(pin);
bool is_set = nrf_drv_gpiote_in_is_set(p_btn->pin_no);
bool is_active = !((p_btn->active_state == APP_BUTTON_ACTIVE_HIGH) ^ is_set);
/* If event indicates that pin is active and no other pin is active start the timer. All
* action happens in timeout event.
*/
if (is_active && (m_pin_active == 0))
{
NRF_LOG_DEBUG("First active button, starting periodic timer");
timer_start();
}
}
static void detection_delay_timeout_handler(void * p_context)
{
for (int i = 0; i < m_button_count; i++)
{
app_button_cfg_t const * p_btn = &mp_buttons[i];
bool is_set = nrf_drv_gpiote_in_is_set(p_btn->pin_no);
bool is_active = !((p_btn->active_state == APP_BUTTON_ACTIVE_HIGH) ^ is_set);
evt_handle(p_btn->pin_no, is_active);
}
if (m_pin_active)
{
timer_start();
}
else
{
NRF_LOG_DEBUG("No active buttons, stopping timer");
}
}
/**@brief Function for handling button events.
*
* @param[in] pin_no The pin number of the button pressed.
* @param[in] button_action Action button.
*/
static void bsp_button_event_handler(uint8_t pin_no, uint8_t button_action)
{
bsp_event_t event = BSP_EVENT_NOTHING;
uint32_t button = 0;
uint32_t err_code;
static uint8_t current_long_push_pin_no; /**< Pin number of a currently pushed button, that could become a long push if held long enough. */
static bsp_event_t release_event_at_push[BUTTONS_NUMBER]; /**< Array of what the release event of each button was last time it was pushed, so that no release event is sent if the event was bound after the push of the button. */
button = bsp_board_pin_to_button_idx(pin_no);
if (button < BUTTONS_NUMBER)
{
switch (button_action)
{
case APP_BUTTON_PUSH:
event = m_events_list[button].push_event;
if (m_events_list[button].long_push_event != BSP_EVENT_NOTHING)
{
err_code = app_timer_start(m_bsp_button_tmr, APP_TIMER_TICKS(BSP_LONG_PUSH_TIMEOUT_MS), (void*)¤t_long_push_pin_no);
if (err_code == NRF_SUCCESS)
{
current_long_push_pin_no = pin_no;
}
}
release_event_at_push[button] = m_events_list[button].release_event;
break;
case APP_BUTTON_RELEASE:
(void)app_timer_stop(m_bsp_button_tmr);
if (release_event_at_push[button] == m_events_list[button].release_event)
{
event = m_events_list[button].release_event;
}
break;
case BSP_BUTTON_ACTION_LONG_PUSH:
event = m_events_list[button].long_push_event;
}
}
if ((event != BSP_EVENT_NOTHING) && (m_registered_callback != NULL))
{
m_registered_callback(event);
}
}
/**
* @brief BSP events callback.
*/
void bsp_event_callback(bsp_event_t event)
{
switch (event)
{
case BSP_EVENT_KEY_0:
nrf_drv_wdt_channel_feed(m_channel_id);
break;
default :
//Do nothing.
break;
}
}
정리하면 bsp_init 함수를 통해 버튼을 눌렀을 때 다음과 같이 동작한다.
1 | gpiote_event_handler | 첫번째로 버튼이 눌렸을 때 app timer가 시작됨 |
2 | detection_delay_timeout_handler | evt_handle 함수를 통해 버튼 상태 확인 |
3 | bsp_button_event_handler | event 값에 눌린 버튼에 대한 정보 저장 |
4 | bsp_event_callback | event 값에 대한 이벤트 핸들러 처리(wdt 리셋) |
이제 마지막으로 wdt 설정하는 부분을 살펴보자.
nrf_drv_wdt_init 함수의 전달 인자로 NRF_DRV_WDT_DEAFULT_CONFIG 설정값과 wdt_event_handler 함수가 넘어간다.
nrf_wdt_reload_value_set 함수의 전달 인자로 값 65536이 CRV 레지스터에 저장된다.
그렇게 되면 계산식에 의해 wdt 타임아웃은 2초가 되며 wdt_event_handler 함수를 호출하도록 설정된다.
nrf_drv_wdt_channel_alloc 함수로 wdt 채널 0으로 할당해 주고 nrfx_wdt_enable 함수로 TASK를 시작하면 와치독 타이머 설정이 끝난다.
nrfx_err_t nrfx_wdt_init(nrfx_wdt_config_t const * p_config,
nrfx_wdt_event_handler_t wdt_event_handler)
{
NRFX_ASSERT(p_config);
nrfx_err_t err_code;
#if !NRFX_CHECK(NRFX_WDT_CONFIG_NO_IRQ)
NRFX_ASSERT(wdt_event_handler != NULL);
m_wdt_event_handler = wdt_event_handler;
#else
NRFX_ASSERT(wdt_event_handler == NULL);
(void)wdt_event_handler;
#endif
if (m_state == NRFX_DRV_STATE_UNINITIALIZED)
{
m_state = NRFX_DRV_STATE_INITIALIZED;
}
else
{
err_code = NRFX_ERROR_INVALID_STATE;
NRFX_LOG_WARNING("Function: %s, error code: %s.",
__func__,
NRFX_LOG_ERROR_STRING_GET(err_code));
return err_code;
}
nrf_wdt_behaviour_set(p_config->behaviour);
uint64_t ticks = (p_config->reload_value * 32768ULL) / 1000;
NRFX_ASSERT(ticks <= UINT32_MAX);
nrf_wdt_reload_value_set((uint32_t) ticks);
#if !NRFX_CHECK(NRFX_WDT_CONFIG_NO_IRQ)
NRFX_IRQ_PRIORITY_SET(WDT_IRQn, p_config->interrupt_priority);
NRFX_IRQ_ENABLE(WDT_IRQn);
#endif
err_code = NRFX_SUCCESS;
NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));
return err_code;
}
이유는 잘 모르겠지만 wdt 예제는 와치독 타이머를 설정하는 부분보다 버튼 입력을 처리하는 부분이 굉장히 복잡하게 되어 있다.
이 부분에 대해서는 좀 더 자세히 다뤄봐야 할 것 같다.
앞으로
이렇게 nRF52832에서 사용 가능한 타이머들에 대해서 알아보았다.
다음에는 소소코드 디버깅을 위해 필요한 기능에 대해서 알아보도록 하겠다.
'Embedded > nRF52 BLE 개발 안내서' 카테고리의 다른 글
nRF52 BLE 개발하기 - temperature (0) | 2021.01.20 |
---|---|
nRF52 BLE 개발하기 - NRF_LOG_INFO (0) | 2021.01.19 |
nRF52 BLE 개발하기 - rtc (8) | 2021.01.15 |
nRF52 BLE 개발하기 - simple_timer (0) | 2021.01.14 |
nRF52 BLE 개발하기 - timer (0) | 2021.01.12 |
댓글