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.