diff --git a/include/lvgl/core/lv_timer.h b/include/lvgl/core/lv_timer.h index 372234c80e..34838f94ef 100644 --- a/include/lvgl/core/lv_timer.h +++ b/include/lvgl/core/lv_timer.h @@ -57,6 +57,14 @@ LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void); */ LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler_run_in_period(uint32_t period); + +/** + * Calculate the time to the soonest LVGL timer. + * @return time till it needs to be run next timer (in ms) + */ +LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_get_time_to_next(void); + + /** * Call it in the super-loop of main() or threads. It will automatically call lv_timer_handler() at the right time. * This function is used to simplify the porting. diff --git a/src/misc/lv_timer.c b/src/misc/lv_timer.c index c1545ac9c8..0c39592085 100644 --- a/src/misc/lv_timer.c +++ b/src/misc/lv_timer.c @@ -111,17 +111,7 @@ LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void) } } while(timer_active); - uint32_t time_until_next = LV_NO_TIMER_READY; - next = lv_ll_get_head(timer_head); - while(next) { - if(!next->paused) { - uint32_t delay = lv_timer_time_remaining(next); - if(delay < time_until_next) - time_until_next = delay; - } - - next = lv_ll_get_next(timer_head, next); /*Find the next timer*/ - } + uint32_t time_until_next = lv_timer_get_time_to_next(); state_p->busy_time += lv_tick_elaps(handler_start); uint32_t idle_period_time = lv_tick_elaps(state_p->idle_period_start); @@ -142,6 +132,24 @@ LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void) return time_until_next; } +LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_get_time_to_next(void) +{ + uint32_t time_until_next = LV_NO_TIMER_READY; + lv_ll_t * timer_head = timer_ll_p; + lv_timer_t * next = lv_ll_get_head(timer_head); + + while(next && time_until_next) { + if(!next->paused) { + uint32_t timer_remaining = lv_timer_time_remaining(next); + time_until_next = LV_MIN(time_until_next, timer_remaining); + } + + next = lv_ll_get_next(timer_head, next); /*Find the next timer*/ + } + + return time_until_next; +} + LV_ATTRIBUTE_TIMER_HANDLER void lv_timer_periodic_handler(void) { lv_timer_state_t * state_p = &state; diff --git a/tests/src/test_cases/test_tickless.c b/tests/src/test_cases/test_tickless.c new file mode 100644 index 0000000000..3b486357d3 --- /dev/null +++ b/tests/src/test_cases/test_tickless.c @@ -0,0 +1,102 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" +#include "unity/unity.h" + +/* STATIC VARIABLES */ +static uint32_t test_tick_value = 0; +static uint32_t test_async_cb_count = 0; +static lv_timer_t * test_timer = NULL; + +/* STATIC FUNCTIONS */ +static uint32_t test_tick_cb(void) +{ + return test_tick_value; +} + +/* TEST FUNCTIONS */ +void setUp(void) +{ + /* Function run before every test */ + /* Reset test variables */ + test_tick_value = 0; + test_async_cb_count = 0; + + /* Reset tick module */ + lv_tick_set_cb(NULL); +} + +void tearDown(void) +{ + /* Function run after every test */ + if(test_timer) { + lv_timer_delete(test_timer); + test_timer = NULL; + } + + /* Reset tick module */ + lv_tick_set_cb(NULL); +} + + +static void timer_async_demo_cb(lv_timer_t * t) +{ + TEST_ASSERT_EQUAL(test_timer, t); + lv_timer_delete(test_timer); + test_timer = NULL; + test_async_cb_count++; +} + +void test_tickless(void) +{ + /* Install our own tick callback so we can control ticks*/ + lv_tick_set_cb(test_tick_cb); + + /* LVGL typically has a few timers by this point */ + /* Test with a relatively short period that should fit between them */ + const uint32_t MAX_PERIOD = 3; + + /* First process any pending work to do */ + for(unsigned i = 0; i < 100; ++i) { + if(lv_timer_handler() > 2 * MAX_PERIOD) { + break; + } + test_tick_value++; + } + + /* If this fires, the above loop failed to find an idle interval */ + TEST_ASSERT_GREATER_THAN(MAX_PERIOD, lv_timer_get_time_to_next()); + + /* Install a timer. Advance ticks until almost ready to fire */ + test_timer = lv_timer_create(timer_async_demo_cb, MAX_PERIOD, NULL); + uint32_t period = MAX_PERIOD; + while(period >= 1) { + TEST_ASSERT_EQUAL(period, lv_timer_get_time_to_next()); + TEST_ASSERT_EQUAL(period, lv_timer_handler()); + TEST_ASSERT_EQUAL(period, lv_timer_get_time_to_next()); + TEST_ASSERT_EQUAL(0, test_async_cb_count); + period--; + test_tick_value++; + } + + /* It is time to run the callback. Verify has not run yet */ + TEST_ASSERT_EQUAL(0, lv_timer_get_time_to_next()); + TEST_ASSERT_EQUAL(0, test_async_cb_count); + + /* Run the callback. Verify it ran */ + TEST_ASSERT_GREATER_THAN(MAX_PERIOD, lv_timer_handler()); + TEST_ASSERT_GREATER_THAN(MAX_PERIOD, lv_timer_get_time_to_next()); + TEST_ASSERT_EQUAL(1, test_async_cb_count); + test_tick_value++; + + /* Run a few more times */ + for(unsigned i = 0; i < 5; ++i) { + uint32_t lhs = lv_timer_handler(); + uint32_t rhs = lv_timer_get_time_to_next(); + TEST_ASSERT_EQUAL(lhs, rhs); + test_tick_value++; + } + + TEST_ASSERT_EQUAL(1, test_async_cb_count); +} + +#endif