I'm having this issue with oldish SDK 12 but since I reproduce the issue with SDK 15: I re-used the code from https://devzone.nordicsemi.com/nordic/short-range-guides/b/software-development-kit/posts/application-timer-tutorial with SDK 15.2.
I modified the example to add a long timer (10 minutes) started at the beginning. If there is inactivity more than 255 seconds, then the first time the single-shot timer is started, it's directly fired.
I added some traces to :
- see events
- see show how the timer is inserted.
Logs:
13:07:35.880325 <info> app: Application timer tutorial example started. 13:07:35.880399 <info> app: 648: 0x200060D4: at_start: 0, to_expire: 19660800 last:0 13:07:41.842139 <info> app: Button1 13:07:41.842201 <info> app: 648: 0x200060B4: at_start: 226187, to_expire: 232741 last:0 13:07:42.044955 <info> app: LED1::toggle() 13:07:42.045018 <info> app: 648: 0x200060B4: at_start: 232741, to_expire: 6554 last:232741 13:07:42.245050 <info> app: LED1::toggle() 13:07:42.245118 <info> app: 648: 0x200060B4: at_start: 239295, to_expire: 6554 last:239295 13:07:42.444565 <info> app: LED1::toggle() 13:07:42.444627 <info> app: 648: 0x200060B4: at_start: 245849, to_expire: 6554 last:245849 13:07:42.646043 <info> app: LED1::toggle() 13:07:42.646118 <info> app: 648: 0x200060B4: at_start: 252403, to_expire: 6554 last:252403 13:07:42.846832 <info> app: LED1::toggle() 13:07:42.846900 <info> app: 648: 0x200060B4: at_start: 258957, to_expire: 6554 last:258957 13:07:43.049107 <info> app: LED1::toggle() 13:07:43.049177 <info> app: 648: 0x200060B4: at_start: 265511, to_expire: 6554 last:265511 13:07:43.138703 <info> app: Button2 13:07:44.178113 <info> app: Button3 (timeout=1000) 13:07:44.178185 <info> app: 648: 0x200060F4: at_start: 302709, to_expire: 69966 last:265511 13:07:45.182614 <info> app: LED2::on() 13:07:46.644688 <info> app: Button3 (timeout=2000) 13:07:46.644752 <info> app: 648: 0x200060F4: at_start: 383632, to_expire: 113691 last:335477 13:07:48.643914 <info> app: LED2::on() 13:12:11.049169 <info> app: Button3 (timeout=3000) 13:12:11.049227 <info> app: 663: 0x200060F4: at_start: 9047471, to_expire: 0 last:449168, delta:8178913 13:12:11.060131 <info> app: LED2::on()
From the logs:
- I waited more that 4 minutes and 15 seconds (i.e. 255 seconds) between last action (that refreshed the m_ticks_latest) and pressing button2 (between 13:07:48 and 13:12:11)
- we can see that single_timer has been fired immediatly at 13:12:11 not waiting for expected timeout of 3 seconds
- It looks that the computation is wrong (to_expire is computed to 0)
/** * Copyright (c) 2014 - 2019, 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. * */ #include <stdbool.h> #include "boards.h" #include "bsp.h" #include "nrf_drv_gpiote.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #include "app_timer.h" #include "nrf_drv_clock.h" static uint32_t timeout = 0; APP_TIMER_DEF(m_repeated_timer_id); /**< Handler for repeated timer used to blink LED 1. */ APP_TIMER_DEF(m_single_shot_timer_id); /**< Handler for single shot timer used to light LED 2. */ APP_TIMER_DEF(m_single_shot_long_timer_id); /**@brief Timeout handler for the repeated timer. */ static void repeated_timer_handler(void * p_context) { NRF_LOG_INFO("LED1::toggle()"); nrf_drv_gpiote_out_toggle(LED_1); } /**@brief Timeout handler for the single shot timer. */ static void single_shot_timer_handler(void * p_context) { NRF_LOG_INFO("LED2::on()"); nrf_drv_gpiote_out_clear(LED_2); } /**@brief Timeout handler for the single shot timer. */ static void single_shot_long_timer_handler(void * p_context) { NRF_LOG_INFO("long-timer over"); } /**@brief Create timers. */ static void create_timers() { ret_code_t err_code; // Create timers err_code = app_timer_create(&m_repeated_timer_id, APP_TIMER_MODE_REPEATED, repeated_timer_handler); APP_ERROR_CHECK(err_code); err_code = app_timer_create(&m_single_shot_timer_id, APP_TIMER_MODE_SINGLE_SHOT, single_shot_timer_handler); APP_ERROR_CHECK(err_code); err_code = app_timer_create(&m_single_shot_long_timer_id, APP_TIMER_MODE_SINGLE_SHOT, single_shot_long_timer_handler); APP_ERROR_CHECK(err_code); } /**@brief Function starting the internal LFCLK oscillator. * * @details This is needed by RTC1 which is used by the Application Timer * (When SoftDevice is enabled the LFCLK is always running and this is not needed). */ static void lfclk_request(void) { ret_code_t err_code = nrf_drv_clock_init(); APP_ERROR_CHECK(err_code); nrf_drv_clock_lfclk_request(NULL); } /**@brief Button event handler function. * * @details Responsible for controlling LEDs based on button presses. */ void button_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { ret_code_t err_code; switch (pin) { case BUTTON_1: // Start repeated timer (start blinking LED). err_code = app_timer_start(m_repeated_timer_id, APP_TIMER_TICKS(200), NULL); NRF_LOG_INFO("Button1"); APP_ERROR_CHECK(err_code); break; case BUTTON_2: // Stop the repeated timer (stop blinking LED). NRF_LOG_INFO("Button2"); err_code = app_timer_stop(m_repeated_timer_id); APP_ERROR_CHECK(err_code); break; case BUTTON_3: // Start single shot timer which turns on LED2 when it expires. // Increase the timeout with 1 second every time. timeout += 1000; NRF_LOG_INFO("Button3 (timeout=%u)", timeout); err_code = app_timer_start(m_single_shot_timer_id, APP_TIMER_TICKS(timeout), NULL); APP_ERROR_CHECK(err_code); break; case BUTTON_4: // Turn off LED 2. NRF_LOG_INFO("Button4"); nrf_drv_gpiote_out_set(LED_2); break; default: break; } } /**@brief Function for initializing GPIO pins. */ static void gpio_config() { ret_code_t err_code; // Initialize GPIOTE driver. err_code = nrf_drv_gpiote_init(); APP_ERROR_CHECK(err_code); // Configure output pins for LEDs. nrf_gpio_range_cfg_output(LED_1, LED_2); // Set output pins (this will turn off the LEDs). nrf_drv_gpiote_out_set(LED_1); nrf_drv_gpiote_out_set(LED_2); // Make a configuration for input pints. This is suitable for both pins in this example. nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true); in_config.pull = NRF_GPIO_PIN_PULLUP; // Configure input pins for 4 buttons, all using the same event handler. err_code = nrf_drv_gpiote_in_init(BUTTON_1, &in_config, button_event_handler); APP_ERROR_CHECK(err_code); err_code = nrf_drv_gpiote_in_init(BUTTON_2, &in_config, button_event_handler); APP_ERROR_CHECK(err_code); err_code = nrf_drv_gpiote_in_init(BUTTON_3, &in_config, button_event_handler); APP_ERROR_CHECK(err_code); err_code = nrf_drv_gpiote_in_init(BUTTON_4, &in_config, button_event_handler); APP_ERROR_CHECK(err_code); // Enable input pins for buttons. nrf_drv_gpiote_in_event_enable(BUTTON_1, true); nrf_drv_gpiote_in_event_enable(BUTTON_2, true); nrf_drv_gpiote_in_event_enable(BUTTON_3, true); nrf_drv_gpiote_in_event_enable(BUTTON_4, true); } /**@brief Function for initializing logging. */ static void log_init(void) { ret_code_t err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); } /**@brief Main function. */ int main(void) { ret_code_t err_code; log_init(); gpio_config(); lfclk_request(); app_timer_init(); create_timers(); NRF_LOG_INFO("Application timer tutorial example started."); err_code = app_timer_start(m_single_shot_long_timer_id, APP_TIMER_TICKS(10 * 60* 1000), NULL); // 10 minutes APP_ERROR_CHECK(err_code); // Enter main loop. while (true) { __WFI(); } }
diff --git a/components/libraries/timer/app_timer.c b/components/libraries/timer/app_timer.c index 639a905..1fc49ce 100644 --- a/components/libraries/timer/app_timer.c +++ b/components/libraries/timer/app_timer.c @@ -51,6 +51,11 @@ #include "app_scheduler.h" #endif +#include "nrf_log.h" +#define DBG(fmt, ...) NRF_LOG_INFO("%d: " fmt, __LINE__, ##__VA_ARGS__) + + + #define RTC1_IRQ_PRI APP_TIMER_CONFIG_IRQ_PRIORITY /**< Priority of the RTC1 interrupt (used for checking for timeouts and executing timeout handlers). */ #define SWI_IRQ_PRI APP_TIMER_CONFIG_IRQ_PRIORITY /**< Priority of the SWI interrupt (used for updating the timer list). */ @@ -640,6 +645,7 @@ static bool list_insertions_handler(timer_node_t * p_restart_list_head) { p_timer->ticks_to_expire = ticks_diff_get(p_timer->ticks_at_start, m_ticks_latest) + p_timer->ticks_first_interval; + DBG("%p: at_start: %d, to_expire: %d last:%d", (uint32_t)p_timer, p_timer->ticks_at_start, p_timer->ticks_to_expire, m_ticks_latest); } else { @@ -654,6 +660,7 @@ static bool list_insertions_handler(timer_node_t * p_restart_list_head) { p_timer->ticks_to_expire = 0; } + DBG("%p: at_start: %d, to_expire: %d last:%d, delta:%d", (uint32_t)p_timer, p_timer->ticks_at_start, p_timer->ticks_to_expire, m_ticks_latest, delta_current_start); } p_timer->ticks_at_start = 0;
I feel that a workaround could be to split my long-running timer (10 minutes) into smaller timers of < 255 (or 127 ??) but I'd rather have a real patch (backportable to SDK12).
Thanks,
Marc.