Hello.
I have encountered a case where app_timer gets stuck indefinitely while processing its timer queue. I have attached a simplified version of the code that runs on the nRF52840 DK. (I actually have the nRF52840 PDK, but I doubt that it makes a difference here).
The attached main.c should replace the main.c of the PCA10056 BSP example in SDK 15.2. The code toggles leds 1-3 every time buttons 1-3 are pressed. In addition there is another app_timer that toggles led 4 every second.
Essentially my app_timer code adds long press detection to the basic button handling done by app_button. I looked at the BSP library for an example of long press detection, and implemented the code similarly as the BSP does. So a button press starts an app_timer and a release stops it. If the timer expires before a release, a long press is signaled. This works otherwise, but rapidly clicking two buttons makes the code hang. It usually doesn't take more than a dozen clicks to make it hang on the DK.
Here is the button handling part of the code: (The full, compilable code is the attached main.c)
// Timer callback when long press timeout occurs. // Forward long press events to the button handler. static void button_long_press_handler(void * p_context) { uint8_t * p_button = (uint8_t *)p_context; // Check if we have a button combo uint8_t is_pushed = 0; bool button_1 = false; bool button_2 = false; bool button_3 = false; if(app_button_is_pushed(0)){is_pushed++; button_1 = true;} // app_button_is_pushed needs the index into the buttons[] array, hence (0,1,2). if(app_button_is_pushed(1)){is_pushed++; button_2 = true;} if(app_button_is_pushed(2)){is_pushed++; button_3 = true;} uint8_t event_id = BUTTON_LONG_PUSH; if(is_pushed > 1) { if(button_1 && button_2) event_id = BUTTON_COMBO_1_2; else if(button_1 && button_3) event_id = BUTTON_COMBO_1_3; else if(button_2 && button_3) event_id = BUTTON_COMBO_2_3; } button_common_handler(*p_button, event_id); } // Button definitions // The config values are taken from custom_board.h static const app_button_cfg_t buttons[BUTTONS_NUMBER] = { { .pin_no = BUTTON_1, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler }, { .pin_no = BUTTON_2, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler }, { .pin_no = BUTTON_3, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler } #if (BUTTONS_NUMBER > 3) , { .pin_no = BUTTON_4, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler } #endif }; void buttons_init(void) { ret_code_t err_code; err_code = app_button_init(buttons, BUTTONS_NUMBER, APP_TIMER_TICKS(20)); APP_ERROR_CHECK(err_code); err_code = app_button_enable(); APP_ERROR_CHECK(err_code); err_code = app_timer_create(&button_long_press_timer, APP_TIMER_MODE_SINGLE_SHOT, button_long_press_handler); APP_ERROR_CHECK(err_code); } // Common handler for button events. Detects short and long presses. static void button_common_handler(uint8_t pin_no, uint8_t button_action) { static bool suppress_next_release_event = false; if(button_action == APP_BUTTON_PUSH) { // Start long press timer on PUSH event ret_code_t err_code = app_timer_start(button_long_press_timer, APP_TIMER_TICKS(LONG_PRESS_TIMEOUT_MS), (void*)¤t_long_push_pin_no); if (err_code == NRF_SUCCESS) { current_long_push_pin_no = pin_no; } suppress_next_release_event = false; } if(button_action == APP_BUTTON_RELEASE) { // Stop long press timer (void)app_timer_stop(button_long_press_timer); // If this was the release event after a long press, ignore it. if(suppress_next_release_event){return;} if(pin_no == BUTTON_1){nrf_gpio_pin_toggle(LED_1);} if(pin_no == BUTTON_2){nrf_gpio_pin_toggle(LED_2);} if(pin_no == BUTTON_3){nrf_gpio_pin_toggle(LED_3);} } if(button_action == BUTTON_LONG_PUSH) { // Suppress next release event suppress_next_release_event = true; } if(button_action == BUTTON_COMBO_1_2) { // Suppress next release event suppress_next_release_event = true; } if(button_action == BUTTON_COMBO_1_3) { // Suppress next release event suppress_next_release_event = true; } if(button_action == BUTTON_COMBO_2_3) { // Suppress next release event suppress_next_release_event = true; } }
One interesting aspect is that the code hangs in two different while() loops inside app_timer, depending on whether another app_timer (that implements a one second tick) is running. If the tick timer is running, execution hangs in timer_list_remove(), similarly as in this old thread. If I commend out the tick timer (one_sec_timer_start() in the attached code), the code hangs in timer_timeouts_check() in the following loop:
// Expire all timers within ticks_elapsed and collect ticks_expired. while (p_timer != NULL) { // Do nothing if timer did not expire. if (ticks_elapsed < p_timer->ticks_to_expire) { break; } // Decrement ticks_elapsed and collect expired ticks. ticks_elapsed -= p_timer->ticks_to_expire; ticks_expired += p_timer->ticks_to_expire; // Move to next timer. p_previous_timer = p_timer; p_timer = p_timer->next; // Execute Task. if (p_previous_timer->is_running) { p_previous_timer->is_running = false; timeout_handler_exec(p_previous_timer); } }
When it hangs, both p_timer and p_previous_timer point to the same timer. And their next pointers also point to the same, so the loop never exits.
I suspect that the root cause is a race condition, since I am triggering starts and stops of the same timer rapidly. Yet this code uses the same principle as the BSP, and I can't get the unmodified BSP example to hang. So I must be doing something wrong. I would appreciate any advice on how to make this work.
/** * Copyright (c) 2014 - 2018, Nordic Semiconductor ASA * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /** @file * * @defgroup bsp_example_main main.c * @{ * @ingroup bsp_example * @brief BSP Example Application main file. * */ #include <stdbool.h> #include <stdint.h> #include "boards.h" #include "app_timer.h" #include "app_button.h" #include "nrf_gpio.h" #include "nordic_common.h" #include "nrf_error.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #define LONG_PRESS_TIMEOUT_MS 3000 #define BUTTON_LONG_PUSH 2 #define BUTTON_COMBO_1_2 3 #define BUTTON_COMBO_1_3 4 #define BUTTON_COMBO_2_3 5 #define ONE_SEC_TIMER_TIMEOUT (APP_TIMER_TICKS(1000)) static volatile uint32_t one_sec_ticks; static void button_common_handler(uint8_t pin_no, uint8_t button_action); uint8_t current_long_push_pin_no; // The button timer is used to detect long presses APP_TIMER_DEF(button_long_press_timer); APP_TIMER_DEF(one_sec_timer_id); /**@brief Function for initializing low frequency clock. */ void clock_initialization() { NRF_CLOCK->LFCLKSRC = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos); NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; NRF_CLOCK->TASKS_LFCLKSTART = 1; while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) { // Do nothing. } } // This handler is called every second void handle_one_sec_timer_tick(void *p_context) { one_sec_ticks++; nrf_gpio_pin_toggle(LED_4); } /**@brief Function for the Timer initialization. * * @details Initializes the timer module. This creates and starts application timers. */ void one_sec_timer_init(void) { // Initialize app_timer module. ret_code_t err_code = app_timer_init(); // Create timers. err_code = app_timer_create(&one_sec_timer_id, APP_TIMER_MODE_REPEATED, &handle_one_sec_timer_tick); APP_ERROR_CHECK(err_code); } /**@brief Function for starting timers. */ void one_sec_timer_start(void) { ret_code_t err_code; err_code = app_timer_start(one_sec_timer_id, ONE_SEC_TIMER_TIMEOUT, NULL); APP_ERROR_CHECK(err_code); } // Timer callback when long press timeout occurs. // Forward long press events to the button handler. static void button_long_press_handler(void * p_context) { uint8_t * p_button = (uint8_t *)p_context; // Check if we have a button combo uint8_t is_pushed = 0; bool button_1 = false; bool button_2 = false; bool button_3 = false; if(app_button_is_pushed(0)){is_pushed++; button_1 = true;} // app_button_is_pushed needs the index into the buttons[] array, hence (0,1,2). if(app_button_is_pushed(1)){is_pushed++; button_2 = true;} if(app_button_is_pushed(2)){is_pushed++; button_3 = true;} uint8_t event_id = BUTTON_LONG_PUSH; if(is_pushed > 1) { if(button_1 && button_2) event_id = BUTTON_COMBO_1_2; else if(button_1 && button_3) event_id = BUTTON_COMBO_1_3; else if(button_2 && button_3) event_id = BUTTON_COMBO_2_3; } button_common_handler(*p_button, event_id); } // Button definitions // The config values are taken from custom_board.h static const app_button_cfg_t buttons[BUTTONS_NUMBER] = { { .pin_no = BUTTON_1, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler }, { .pin_no = BUTTON_2, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler }, { .pin_no = BUTTON_3, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler } #if (BUTTONS_NUMBER > 3) , { .pin_no = BUTTON_4, .active_state = BUTTONS_ACTIVE_STATE, .pull_cfg = BUTTON_PULL, .button_handler = button_common_handler } #endif }; void buttons_init(void) { ret_code_t err_code; err_code = app_button_init(buttons, BUTTONS_NUMBER, APP_TIMER_TICKS(20)); APP_ERROR_CHECK(err_code); err_code = app_button_enable(); APP_ERROR_CHECK(err_code); err_code = app_timer_create(&button_long_press_timer, APP_TIMER_MODE_SINGLE_SHOT, button_long_press_handler); APP_ERROR_CHECK(err_code); } // Common handler for button events. Detects short and long presses. static void button_common_handler(uint8_t pin_no, uint8_t button_action) { static bool suppress_next_release_event = false; if(button_action == APP_BUTTON_PUSH) { // Start long press timer on PUSH event ret_code_t err_code = app_timer_start(button_long_press_timer, APP_TIMER_TICKS(LONG_PRESS_TIMEOUT_MS), (void*)¤t_long_push_pin_no); if (err_code == NRF_SUCCESS) { current_long_push_pin_no = pin_no; } suppress_next_release_event = false; } if(button_action == APP_BUTTON_RELEASE) { // Stop long press timer (void)app_timer_stop(button_long_press_timer); // If this was the release event after a long press, ignore it. if(suppress_next_release_event){return;} if(pin_no == BUTTON_1){nrf_gpio_pin_toggle(LED_1);} if(pin_no == BUTTON_2){nrf_gpio_pin_toggle(LED_2);} if(pin_no == BUTTON_3){nrf_gpio_pin_toggle(LED_3);} } if(button_action == BUTTON_LONG_PUSH) { // Suppress next release event suppress_next_release_event = true; } if(button_action == BUTTON_COMBO_1_2) { // Suppress next release event suppress_next_release_event = true; } if(button_action == BUTTON_COMBO_1_3) { // Suppress next release event suppress_next_release_event = true; } if(button_action == BUTTON_COMBO_2_3) { // Suppress next release event suppress_next_release_event = true; } } /** * @brief Function for application main entry. */ int main(void) { clock_initialization(); nrf_gpio_cfg_output(LED_1); nrf_gpio_cfg_output(LED_2); nrf_gpio_cfg_output(LED_3); nrf_gpio_cfg_output(LED_4); one_sec_timer_init(); //one_sec_timer_start(); APP_ERROR_CHECK(NRF_LOG_INIT(NULL)); NRF_LOG_DEFAULT_BACKENDS_INIT(); NRF_LOG_INFO("BSP example started."); buttons_init(); while (true) { NRF_LOG_FLUSH(); __SEV(); __WFE(); __WFE(); // no implementation needed } } /** @} */