Merge pull request #7524 from c1728p9/tickless_fix

Fixes for tickless and LPTICKER_DELAY_TICKS
pull/7843/head
Cruz Monrreal 2018-08-21 10:03:41 -05:00 committed by GitHub
commit e02466a77a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1082 additions and 210 deletions

View File

@ -29,90 +29,90 @@ using namespace utest::v1;
void deep_sleep_lock_lock_test() void deep_sleep_lock_lock_test()
{ {
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Check basic usage works // Check basic usage works
DeepSleepLock lock; DeepSleepLock lock;
TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Check that unlock and lock change can deep sleep as expected // Check that unlock and lock change can deep sleep as expected
DeepSleepLock lock; DeepSleepLock lock;
TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check());
lock.unlock(); lock.unlock();
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
lock.lock(); lock.lock();
TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Check that unlock releases sleep based on count // Check that unlock releases sleep based on count
DeepSleepLock lock; DeepSleepLock lock;
lock.lock(); lock.lock();
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Check that unbalanced locks do not leave deep sleep locked // Check that unbalanced locks do not leave deep sleep locked
DeepSleepLock lock; DeepSleepLock lock;
lock.lock(); lock.lock();
TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
} }
void timer_lock_test() void timer_lock_test()
{ {
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Just creating a timer object does not lock sleep // Just creating a timer object does not lock sleep
Timer timer; Timer timer;
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Starting a timer does lock sleep // Starting a timer does lock sleep
Timer timer; Timer timer;
timer.start(); timer.start();
TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Stopping a timer after starting it allows sleep // Stopping a timer after starting it allows sleep
Timer timer; Timer timer;
timer.start(); timer.start();
timer.stop(); timer.stop();
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Starting a timer multiple times still lets you sleep // Starting a timer multiple times still lets you sleep
Timer timer; Timer timer;
timer.start(); timer.start();
timer.start(); timer.start();
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
{ {
// Stopping a timer multiple times still lets you sleep // Stopping a timer multiple times still lets you sleep
Timer timer; Timer timer;
timer.start(); timer.start();
timer.stop(); timer.stop();
timer.stop(); timer.stop();
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
} }
TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check());
} }
Case cases[] = { Case cases[] = {

View File

@ -263,7 +263,7 @@ void test_sleep(void)
timer.start(); timer.start();
timeout.attach_callback(mbed::callback(sem_callback, &sem), delay_us); timeout.attach_callback(mbed::callback(sem_callback, &sem), delay_us);
bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_FALSE_MESSAGE(deep_sleep_allowed, "Deep sleep should be disallowed"); TEST_ASSERT_FALSE_MESSAGE(deep_sleep_allowed, "Deep sleep should be disallowed");
while (sem.wait(0) != 1) { while (sem.wait(0) != 1) {
sleep(); sleep();
@ -322,7 +322,7 @@ void test_deepsleep(void)
timer.start(); timer.start();
timeout.attach_callback(mbed::callback(sem_callback, &sem), delay_us); timeout.attach_callback(mbed::callback(sem_callback, &sem), delay_us);
bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed"); TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed");
while (sem.wait(0) != 1) { while (sem.wait(0) != 1) {
sleep(); sleep();

View File

@ -20,6 +20,7 @@
#include "ticker_api_tests.h" #include "ticker_api_tests.h"
#include "hal/us_ticker_api.h" #include "hal/us_ticker_api.h"
#include "hal/lp_ticker_api.h" #include "hal/lp_ticker_api.h"
#include "hal/mbed_lp_ticker_wrapper.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -496,7 +497,15 @@ utest::v1::status_t us_ticker_setup(const Case *const source, const size_t index
{ {
intf = get_us_ticker_data()->interface; intf = get_us_ticker_data()->interface;
OS_Tick_Disable(); /* OS, common ticker and low power ticker wrapper
* may make use of us ticker so suspend them for this test */
osKernelSuspend();
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
/* Suspend the lp ticker wrapper since it makes use of the us ticker */
ticker_suspend(get_lp_ticker_data());
lp_ticker_wrapper_suspend();
#endif
ticker_suspend(get_us_ticker_data());
intf->init(); intf->init();
@ -515,7 +524,12 @@ utest::v1::status_t us_ticker_teardown(const Case *const source, const size_t pa
prev_irq_handler = NULL; prev_irq_handler = NULL;
OS_Tick_Enable(); ticker_resume(get_us_ticker_data());
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
lp_ticker_wrapper_resume();
ticker_resume(get_lp_ticker_data());
#endif
osKernelResume(0);
return greentea_case_teardown_handler(source, passed, failed, reason); return greentea_case_teardown_handler(source, passed, failed, reason);
} }
@ -525,7 +539,9 @@ utest::v1::status_t lp_ticker_setup(const Case *const source, const size_t index
{ {
intf = get_lp_ticker_data()->interface; intf = get_lp_ticker_data()->interface;
OS_Tick_Disable(); /* OS and common ticker may make use of lp ticker so suspend them for this test */
osKernelSuspend();
ticker_suspend(get_lp_ticker_data());
intf->init(); intf->init();
@ -544,7 +560,8 @@ utest::v1::status_t lp_ticker_teardown(const Case *const source, const size_t pa
prev_irq_handler = NULL; prev_irq_handler = NULL;
OS_Tick_Enable(); ticker_resume(get_lp_ticker_data());
osKernelResume(0);
return greentea_case_teardown_handler(source, passed, failed, reason); return greentea_case_teardown_handler(source, passed, failed, reason);
} }

View File

@ -28,6 +28,7 @@
#include "ticker_api_test_freq.h" #include "ticker_api_test_freq.h"
#include "hal/us_ticker_api.h" #include "hal/us_ticker_api.h"
#include "hal/lp_ticker_api.h" #include "hal/lp_ticker_api.h"
#include "hal/mbed_lp_ticker_wrapper.h"
#if !DEVICE_USTICKER #if !DEVICE_USTICKER
#error [NOT_SUPPORTED] test not supported #error [NOT_SUPPORTED] test not supported
@ -38,25 +39,51 @@
using namespace utest::v1; using namespace utest::v1;
const ticker_interface_t *intf; const ticker_interface_t *intf;
uint32_t intf_mask;
static volatile unsigned int overflowCounter; uint32_t intf_last_tick;
uint32_t intf_elapsed_ticks;
ticker_irq_handler_type prev_handler;
uint32_t ticks_to_us(uint32_t ticks, uint32_t freq) uint32_t ticks_to_us(uint32_t ticks, uint32_t freq)
{ {
return (uint32_t)((uint64_t)ticks * US_PER_S / freq); return (uint32_t)((uint64_t)ticks * US_PER_S / freq);
} }
void elapsed_ticks_reset()
{
core_util_critical_section_enter();
const uint32_t ticker_bits = intf->get_info()->bits;
intf_mask = (1 << ticker_bits) - 1;
intf_last_tick = intf->read();
intf_elapsed_ticks = 0;
core_util_critical_section_exit();
}
uint32_t elapsed_ticks_update()
{
core_util_critical_section_enter();
const uint32_t current_tick = intf->read();
intf_elapsed_ticks += (current_tick - intf_last_tick) & intf_mask;
intf_last_tick = current_tick;
/* Schedule next interrupt half way to overflow */
uint32_t next = (current_tick + intf_mask / 2) & intf_mask;
intf->set_interrupt(next);
uint32_t elapsed = intf_elapsed_ticks;
core_util_critical_section_exit();
return elapsed;
}
void ticker_event_handler_stub(const ticker_data_t *const ticker) void ticker_event_handler_stub(const ticker_data_t *const ticker)
{ {
if (ticker == get_us_ticker_data()) { intf->clear_interrupt();
us_ticker_clear_interrupt(); elapsed_ticks_update();
} else {
#if DEVICE_LPTICKER
lp_ticker_clear_interrupt();
#endif
}
overflowCounter++;
} }
/* Test that the ticker is operating at the frequency it specifies. */ /* Test that the ticker is operating at the frequency it specifies. */
@ -66,13 +93,13 @@ void ticker_frequency_test()
char _value[128] = { }; char _value[128] = { };
int expected_key = 1; int expected_key = 1;
const uint32_t ticker_freq = intf->get_info()->frequency; const uint32_t ticker_freq = intf->get_info()->frequency;
const uint32_t ticker_bits = intf->get_info()->bits;
const uint32_t ticker_max = (1 << ticker_bits) - 1;
intf->init(); intf->init();
elapsed_ticks_reset();
/* Detect overflow for tickers with lower counters width. */ /* Detect overflow for tickers with lower counters width. */
intf->set_interrupt(0); elapsed_ticks_update();
greentea_send_kv("timing_drift_check_start", 0); greentea_send_kv("timing_drift_check_start", 0);
@ -82,9 +109,7 @@ void ticker_frequency_test()
expected_key = strcmp(_key, "base_time"); expected_key = strcmp(_key, "base_time");
} while (expected_key); } while (expected_key);
overflowCounter = 0; const uint32_t begin_ticks = elapsed_ticks_update();
const uint32_t begin_ticks = intf->read();
/* Assume that there was no overflow at this point - we are just after init. */ /* Assume that there was no overflow at this point - we are just after init. */
greentea_send_kv(_key, ticks_to_us(begin_ticks, ticker_freq)); greentea_send_kv(_key, ticks_to_us(begin_ticks, ticker_freq));
@ -92,9 +117,9 @@ void ticker_frequency_test()
/* Wait for 2nd signal from host. */ /* Wait for 2nd signal from host. */
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
const uint32_t end_ticks = intf->read(); const uint32_t end_ticks = elapsed_ticks_update();
greentea_send_kv(_key, ticks_to_us(end_ticks + overflowCounter * ticker_max, ticker_freq)); greentea_send_kv(_key, ticks_to_us(end_ticks, ticker_freq));
/* Get the results from host. */ /* Get the results from host. */
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
@ -106,43 +131,74 @@ void ticker_frequency_test()
utest::v1::status_t us_ticker_case_setup_handler_t(const Case *const source, const size_t index_of_case) utest::v1::status_t us_ticker_case_setup_handler_t(const Case *const source, const size_t index_of_case)
{ {
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
/* Suspend the lp ticker wrapper since it makes use of the us ticker */
ticker_suspend(get_lp_ticker_data());
lp_ticker_wrapper_suspend();
#endif
ticker_suspend(get_us_ticker_data());
intf = get_us_ticker_data()->interface; intf = get_us_ticker_data()->interface;
set_us_ticker_irq_handler(ticker_event_handler_stub); prev_handler = set_us_ticker_irq_handler(ticker_event_handler_stub);
return greentea_case_setup_handler(source, index_of_case); return greentea_case_setup_handler(source, index_of_case);
} }
utest::v1::status_t us_ticker_case_teardown_handler_t(const Case *const source, const size_t passed, const size_t failed,
const failure_t reason)
{
set_us_ticker_irq_handler(prev_handler);
ticker_resume(get_us_ticker_data());
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
lp_ticker_wrapper_resume();
ticker_resume(get_lp_ticker_data());
#endif
return greentea_case_teardown_handler(source, passed, failed, reason);
}
#if DEVICE_LPTICKER #if DEVICE_LPTICKER
utest::v1::status_t lp_ticker_case_setup_handler_t(const Case *const source, const size_t index_of_case) utest::v1::status_t lp_ticker_case_setup_handler_t(const Case *const source, const size_t index_of_case)
{ {
ticker_suspend(get_lp_ticker_data());
intf = get_lp_ticker_data()->interface; intf = get_lp_ticker_data()->interface;
set_lp_ticker_irq_handler(ticker_event_handler_stub); prev_handler = set_lp_ticker_irq_handler(ticker_event_handler_stub);
return greentea_case_setup_handler(source, index_of_case); return greentea_case_setup_handler(source, index_of_case);
} }
#endif
utest::v1::status_t ticker_case_teardown_handler_t(const Case *const source, const size_t passed, const size_t failed, utest::v1::status_t lp_ticker_case_teardown_handler_t(const Case *const source, const size_t passed, const size_t failed,
const failure_t reason) const failure_t reason)
{ {
set_lp_ticker_irq_handler(prev_handler);
ticker_resume(get_lp_ticker_data());
return greentea_case_teardown_handler(source, passed, failed, reason); return greentea_case_teardown_handler(source, passed, failed, reason);
} }
#endif
// Test cases // Test cases
Case cases[] = { Case cases[] = {
Case("Microsecond ticker frequency test", us_ticker_case_setup_handler_t, ticker_frequency_test, Case("Microsecond ticker frequency test", us_ticker_case_setup_handler_t, ticker_frequency_test,
ticker_case_teardown_handler_t), us_ticker_case_teardown_handler_t),
#if DEVICE_LPTICKER #if DEVICE_LPTICKER
Case("Low power ticker frequency test", lp_ticker_case_setup_handler_t, ticker_frequency_test, Case("Low power ticker frequency test", lp_ticker_case_setup_handler_t, ticker_frequency_test,
ticker_case_teardown_handler_t), lp_ticker_case_teardown_handler_t),
#endif #endif
}; };
utest::v1::status_t greentea_test_setup(const size_t number_of_cases) utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{ {
/* Suspend RTOS Kernel so the timers are not in use. */
osKernelSuspend();
GREENTEA_SETUP(120, "timing_drift_auto"); GREENTEA_SETUP(120, "timing_drift_auto");
return greentea_test_setup_handler(number_of_cases); return greentea_test_setup_handler(number_of_cases);
} }
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); void greentea_test_teardown(const size_t passed, const size_t failed, const failure_t failure)
{
osKernelResume(0);
greentea_test_teardown_handler(passed, failed, failure);
}
Specification specification(greentea_test_setup, cases, greentea_test_teardown);
int main() int main()
{ {

View File

@ -20,6 +20,7 @@
#include "rtos.h" #include "rtos.h"
#include "lp_ticker_api_tests.h" #include "lp_ticker_api_tests.h"
#include "hal/lp_ticker_api.h" #include "hal/lp_ticker_api.h"
#include "hal/mbed_lp_ticker_wrapper.h"
#if !DEVICE_LPTICKER #if !DEVICE_LPTICKER
#error [NOT_SUPPORTED] Low power timer not supported for this target #error [NOT_SUPPORTED] Low power timer not supported for this target
@ -29,6 +30,8 @@ using namespace utest::v1;
volatile int intFlag = 0; volatile int intFlag = 0;
ticker_irq_handler_type prev_handler;
#define US_PER_MS 1000 #define US_PER_MS 1000
#define TICKER_GLITCH_TEST_TICKS 1000 #define TICKER_GLITCH_TEST_TICKS 1000
@ -113,8 +116,6 @@ void lp_ticker_deepsleep_test()
{ {
intFlag = 0; intFlag = 0;
set_lp_ticker_irq_handler(ticker_event_handler_stub);
lp_ticker_init(); lp_ticker_init();
/* Give some time Green Tea to finish UART transmission before entering /* Give some time Green Tea to finish UART transmission before entering
@ -130,7 +131,7 @@ void lp_ticker_deepsleep_test()
* tick_count + TICKER_INT_VAL. */ * tick_count + TICKER_INT_VAL. */
lp_ticker_set_interrupt(tick_count + TICKER_INT_VAL); lp_ticker_set_interrupt(tick_count + TICKER_INT_VAL);
TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep_test_check());
while (!intFlag) { while (!intFlag) {
sleep(); sleep();
@ -157,6 +158,32 @@ void lp_ticker_glitch_test()
} }
} }
#if DEVICE_LPTICKER
utest::v1::status_t lp_ticker_deepsleep_test_setup_handler(const Case *const source, const size_t index_of_case)
{
/* disable everything using the lp ticker for this test */
osKernelSuspend();
ticker_suspend(get_lp_ticker_data());
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
lp_ticker_wrapper_suspend();
#endif
prev_handler = set_lp_ticker_irq_handler(ticker_event_handler_stub);
return greentea_case_setup_handler(source, index_of_case);
}
utest::v1::status_t lp_ticker_deepsleep_test_teardown_handler(const Case *const source, const size_t passed, const size_t failed,
const failure_t reason)
{
set_lp_ticker_irq_handler(prev_handler);
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
lp_ticker_wrapper_resume();
#endif
ticker_resume(get_lp_ticker_data());
osKernelResume(0);
return greentea_case_teardown_handler(source, passed, failed, reason);
}
#endif
utest::v1::status_t test_setup(const size_t number_of_cases) utest::v1::status_t test_setup(const size_t number_of_cases)
{ {
GREENTEA_SETUP(20, "default_auto"); GREENTEA_SETUP(20, "default_auto");
@ -166,7 +193,7 @@ utest::v1::status_t test_setup(const size_t number_of_cases)
Case cases[] = { Case cases[] = {
Case("lp ticker info test", lp_ticker_info_test), Case("lp ticker info test", lp_ticker_info_test),
#if DEVICE_SLEEP #if DEVICE_SLEEP
Case("lp ticker sleep test", lp_ticker_deepsleep_test), Case("lp ticker sleep test", lp_ticker_deepsleep_test_setup_handler, lp_ticker_deepsleep_test, lp_ticker_deepsleep_test_teardown_handler),
#endif #endif
Case("lp ticker glitch test", lp_ticker_glitch_test) Case("lp ticker glitch test", lp_ticker_glitch_test)
}; };

View File

@ -74,7 +74,7 @@ void rtc_sleep_test_support(bool deepsleep_mode)
timeout.attach(callback, DELAY_4S); timeout.attach(callback, DELAY_4S);
TEST_ASSERT(sleep_manager_can_deep_sleep() == deepsleep_mode); TEST_ASSERT(sleep_manager_can_deep_sleep_test_check() == deepsleep_mode);
while (!expired) { while (!expired) {
sleep(); sleep();

View File

@ -23,6 +23,7 @@
#include "utest/utest.h" #include "utest/utest.h"
#include "unity/unity.h" #include "unity/unity.h"
#include "greentea-client/test_env.h" #include "greentea-client/test_env.h"
#include "mbed_lp_ticker_wrapper.h"
#include "sleep_api_tests.h" #include "sleep_api_tests.h"
@ -40,7 +41,7 @@
* *
* This should be replaced with a better function that checks if the * This should be replaced with a better function that checks if the
* hardware buffers are empty. However, such an API does not exist now, * hardware buffers are empty. However, such an API does not exist now,
* so we'll use the wait_ms() function for now. * so we'll use the busy_wait_ms() function for now.
*/ */
#define SERIAL_FLUSH_TIME_MS 20 #define SERIAL_FLUSH_TIME_MS 20
@ -103,6 +104,20 @@ bool compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, uns
} }
} }
void busy_wait_ms(int ms)
{
const ticker_info_t *info = us_ticker_get_info();
uint32_t mask = (1 << info->bits) - 1;
int delay = (int)((uint64_t)ms * info->frequency / 1000);
uint32_t prev = us_ticker_read();
while (delay > 0) {
uint32_t next = us_ticker_read();
delay -= (next - prev) & mask;
prev = next;
}
}
void us_ticker_isr(const ticker_data_t *const ticker_data) void us_ticker_isr(const ticker_data_t *const ticker_data)
{ {
us_ticker_clear_interrupt(); us_ticker_clear_interrupt();
@ -125,17 +140,6 @@ void sleep_usticker_test()
const ticker_irq_handler_type us_ticker_irq_handler_org = set_us_ticker_irq_handler(us_ticker_isr); const ticker_irq_handler_type us_ticker_irq_handler_org = set_us_ticker_irq_handler(us_ticker_isr);
// call ticker_read_us to initialize ticker upper layer
// prevents subsequent scheduling of max_delta interrupt during ticker initialization while test execution
// (e.g when ticker_read_us is called)
ticker_read_us(ticker);
#ifdef DEVICE_LPTICKER
// call ticker_read_us to initialize lp_ticker
// prevents scheduling interrupt during ticker initialization (in lp_ticker_init) while test execution
// (e.g when ticker_read_us is called for lp_ticker, see MBED_CPU_STATS_ENABLED)
ticker_read_us(get_lp_ticker_data());
#endif
/* Test only sleep functionality. */ /* Test only sleep functionality. */
sleep_manager_lock_deep_sleep(); sleep_manager_lock_deep_sleep();
TEST_ASSERT_FALSE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should be locked"); TEST_ASSERT_FALSE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should be locked");
@ -173,17 +177,12 @@ void deepsleep_lpticker_test()
const unsigned int ticker_freq = ticker->interface->get_info()->frequency; const unsigned int ticker_freq = ticker->interface->get_info()->frequency;
const unsigned int ticker_width = ticker->interface->get_info()->bits; const unsigned int ticker_width = ticker->interface->get_info()->bits;
// call ticker_read_us to initialize ticker upper layer
// prevents subsequent scheduling of max_delta interrupt during ticker initialization while test execution
// (e.g when ticker_read_us is called)
ticker_read_us(ticker);
const ticker_irq_handler_type lp_ticker_irq_handler_org = set_lp_ticker_irq_handler(lp_ticker_isr); const ticker_irq_handler_type lp_ticker_irq_handler_org = set_lp_ticker_irq_handler(lp_ticker_isr);
/* Give some time Green Tea to finish UART transmission before entering /* Give some time Green Tea to finish UART transmission before entering
* deep-sleep mode. * deep-sleep mode.
*/ */
wait_ms(SERIAL_FLUSH_TIME_MS); busy_wait_ms(SERIAL_FLUSH_TIME_MS);
TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked"); TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked");
@ -218,7 +217,7 @@ void deepsleep_high_speed_clocks_turned_off_test()
/* Give some time Green Tea to finish UART transmission before entering /* Give some time Green Tea to finish UART transmission before entering
* deep-sleep mode. * deep-sleep mode.
*/ */
wait_ms(SERIAL_FLUSH_TIME_MS); busy_wait_ms(SERIAL_FLUSH_TIME_MS);
TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked"); TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked");
@ -256,15 +255,38 @@ utest::v1::status_t greentea_failure_handler(const Case *const source, const fai
utest::v1::status_t greentea_test_setup(const size_t number_of_cases) utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{ {
GREENTEA_SETUP(60, "default_auto"); GREENTEA_SETUP(60, "default_auto");
/* Suspend RTOS Kernel to enable sleep modes. */
osKernelSuspend();
#if DEVICE_LPTICKER
ticker_suspend(get_lp_ticker_data());
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
lp_ticker_wrapper_suspend();
#endif
#endif
ticker_suspend(get_us_ticker_data());
us_ticker_init(); us_ticker_init();
#if DEVICE_LPTICKER #if DEVICE_LPTICKER
lp_ticker_init(); lp_ticker_init();
#endif #endif
/* Suspend RTOS Kernel to enable sleep modes. */
osKernelSuspend();
return greentea_test_setup_handler(number_of_cases); return greentea_test_setup_handler(number_of_cases);
} }
void greentea_test_teardown(const size_t passed, const size_t failed, const failure_t failure)
{
ticker_resume(get_us_ticker_data());
#if DEVICE_LPTICKER
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
lp_ticker_wrapper_resume();
#endif
ticker_resume(get_lp_ticker_data());
#endif
osKernelResume(0);
greentea_test_teardown_handler(passed, failed, failure);
}
Case cases[] = { Case cases[] = {
Case("sleep - source of wake-up - us ticker", sleep_usticker_test, greentea_failure_handler), Case("sleep - source of wake-up - us ticker", sleep_usticker_test, greentea_failure_handler),
#if DEVICE_LPTICKER #if DEVICE_LPTICKER
@ -273,7 +295,7 @@ Case cases[] = {
#endif #endif
}; };
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); Specification specification(greentea_test_setup, cases, greentea_test_teardown);
int main() int main()
{ {

View File

@ -25,15 +25,15 @@ using namespace utest::v1;
void sleep_manager_deepsleep_counter_test() void sleep_manager_deepsleep_counter_test()
{ {
bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_TRUE(deep_sleep_allowed); TEST_ASSERT_TRUE(deep_sleep_allowed);
sleep_manager_lock_deep_sleep(); sleep_manager_lock_deep_sleep();
deep_sleep_allowed = sleep_manager_can_deep_sleep(); deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_FALSE(deep_sleep_allowed); TEST_ASSERT_FALSE(deep_sleep_allowed);
sleep_manager_unlock_deep_sleep(); sleep_manager_unlock_deep_sleep();
deep_sleep_allowed = sleep_manager_can_deep_sleep(); deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_TRUE(deep_sleep_allowed); TEST_ASSERT_TRUE(deep_sleep_allowed);
} }

View File

@ -55,7 +55,7 @@ void sleep_manager_multithread_test()
t2.join(); t2.join();
} }
bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed"); TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed");
} }
@ -83,7 +83,7 @@ void sleep_manager_irq_test()
timer.stop(); timer.stop();
} }
bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check();
TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed"); TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed");
} }

View File

@ -2268,6 +2268,82 @@ static void test_match_interval_passed_table()
} }
} }
/**
* Check that suspend and resume work as expected
*
* Check the following
* -time does not change while suspended
* -restricted interface functions are not called
* -scheduling resumes correctly
*/
static void test_suspend_resume()
{
ticker_set_handler(&ticker_stub, NULL);
interface_stub.timestamp = 1000;
us_timestamp_t start = ticker_read_us(&ticker_stub);
TEST_ASSERT_EQUAL(1000, start);
/* Reset call count */
interface_stub.init_call = 0;
interface_stub.read_call = 0;
interface_stub.set_interrupt_call = 0;
/* Suspend the ticker */
ticker_suspend(&ticker_stub);
const timestamp_t suspend_time = queue_stub.present_time;
/* Simulate time passing */
interface_stub.timestamp = 1500;
us_timestamp_t next = ticker_read_us(&ticker_stub);
/* Time should not have passed and no calls to interface should have been made */
TEST_ASSERT_EQUAL(start, next);
TEST_ASSERT_EQUAL(0, interface_stub.init_call);
TEST_ASSERT_EQUAL(0, interface_stub.read_call);
TEST_ASSERT_EQUAL(0, interface_stub.disable_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.clear_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.set_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.fire_interrupt_call);
/* Simulate a reinit (time reset to 0) */
interface_stub.timestamp = 0;
next = ticker_read_us(&ticker_stub);
/* Time should not have passed and no calls to interface should have been made */
TEST_ASSERT_EQUAL(start, next);
TEST_ASSERT_EQUAL(0, interface_stub.init_call);
TEST_ASSERT_EQUAL(0, interface_stub.read_call);
TEST_ASSERT_EQUAL(0, interface_stub.disable_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.clear_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.set_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.fire_interrupt_call);
/* Insert an event in the past and future */
ticker_event_t event_past = { 0 };
const timestamp_t event_past_timestamp = suspend_time - 10;
ticker_insert_event_us(&ticker_stub, &event_past, event_past_timestamp, 0);
ticker_event_t event_future = { 0 };
const timestamp_t event_future_timestamp = suspend_time + 10;
ticker_insert_event_us(&ticker_stub, &event_future, event_future_timestamp, 0);
TEST_ASSERT_EQUAL(0, interface_stub.init_call);
TEST_ASSERT_EQUAL(0, interface_stub.read_call);
TEST_ASSERT_EQUAL(0, interface_stub.disable_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.clear_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.set_interrupt_call);
TEST_ASSERT_EQUAL(0, interface_stub.fire_interrupt_call);
/* Resume and verify everything starts again */
ticker_resume(&ticker_stub);
TEST_ASSERT_EQUAL(suspend_time, queue_stub.present_time);
TEST_ASSERT_EQUAL(1, interface_stub.fire_interrupt_call);
}
static const case_t cases[] = { static const case_t cases[] = {
MAKE_TEST_CASE("ticker initialization", test_ticker_initialization), MAKE_TEST_CASE("ticker initialization", test_ticker_initialization),
MAKE_TEST_CASE( MAKE_TEST_CASE(
@ -2376,6 +2452,10 @@ static const case_t cases[] = {
MAKE_TEST_CASE( MAKE_TEST_CASE(
"test_match_interval_passed_table", "test_match_interval_passed_table",
test_match_interval_passed_table test_match_interval_passed_table
),
MAKE_TEST_CASE(
"test_suspend_resume",
test_suspend_resume
) )
}; };

View File

@ -313,7 +313,7 @@ void test_deepsleep(void)
lptimer.start(); lptimer.start();
st.schedule_tick(TEST_TICKS); st.schedule_tick(TEST_TICKS);
TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "Deep sleep should be allowed"); TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep_test_check(), "Deep sleep should be allowed");
while (st.sem_wait(0) != 1) { while (st.sem_wait(0) != 1) {
sleep(); sleep();
} }

View File

@ -0,0 +1,295 @@
/* mbed Microcontroller Library
* Copyright (c) 2018 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "hal/LowPowerTickerWrapper.h"
#include "platform/Callback.h"
LowPowerTickerWrapper::LowPowerTickerWrapper(const ticker_data_t *data, const ticker_interface_t *interface, uint32_t min_cycles_between_writes, uint32_t min_cycles_until_match)
: _intf(data->interface), _min_count_between_writes(min_cycles_between_writes + 1), _min_count_until_match(min_cycles_until_match + 1), _suspended(false)
{
core_util_critical_section_enter();
this->data.interface = interface;
this->data.queue = data->queue;
_reset();
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::irq_handler(ticker_irq_handler_type handler)
{
core_util_critical_section_enter();
if (_suspended) {
if (handler) {
handler(&data);
}
core_util_critical_section_exit();
return;
}
if (_pending_fire_now || _match_check(_intf->read())) {
_timeout.detach();
_pending_timeout = false;
_pending_match = false;
_pending_fire_now = false;
if (handler) {
handler(&data);
}
} else {
// Spurious interrupt
_intf->clear_interrupt();
}
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::suspend()
{
core_util_critical_section_enter();
// Wait until rescheduling is allowed
while (!_set_interrupt_allowed) {
timestamp_t current = _intf->read();
if (((current - _last_actual_set_interrupt) & _mask) >= _min_count_between_writes) {
_set_interrupt_allowed = true;
}
}
_reset();
_suspended = true;
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::resume()
{
core_util_critical_section_enter();
_suspended = false;
core_util_critical_section_exit();
}
bool LowPowerTickerWrapper::timeout_pending()
{
core_util_critical_section_enter();
bool pending = _pending_timeout;
core_util_critical_section_exit();
return pending;
}
void LowPowerTickerWrapper::init()
{
core_util_critical_section_enter();
_reset();
_intf->init();
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::free()
{
core_util_critical_section_enter();
_reset();
_intf->free();
core_util_critical_section_exit();
}
uint32_t LowPowerTickerWrapper::read()
{
core_util_critical_section_enter();
timestamp_t current = _intf->read();
if (_match_check(current)) {
_intf->fire_interrupt();
}
core_util_critical_section_exit();
return current;
}
void LowPowerTickerWrapper::set_interrupt(timestamp_t timestamp)
{
core_util_critical_section_enter();
_last_set_interrupt = _intf->read();
_cur_match_time = timestamp;
_pending_match = true;
_schedule_match(_last_set_interrupt);
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::disable_interrupt()
{
core_util_critical_section_enter();
_intf->disable_interrupt();
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::clear_interrupt()
{
core_util_critical_section_enter();
_intf->clear_interrupt();
core_util_critical_section_exit();
}
void LowPowerTickerWrapper::fire_interrupt()
{
core_util_critical_section_enter();
_pending_fire_now = 1;
_intf->fire_interrupt();
core_util_critical_section_exit();
}
const ticker_info_t *LowPowerTickerWrapper::get_info()
{
core_util_critical_section_enter();
const ticker_info_t *info = _intf->get_info();
core_util_critical_section_exit();
return info;
}
void LowPowerTickerWrapper::_reset()
{
MBED_ASSERT(core_util_in_critical_section());
_timeout.detach();
_pending_timeout = false;
_pending_match = false;
_pending_fire_now = false;
_set_interrupt_allowed = true;
_cur_match_time = 0;
_last_set_interrupt = 0;
_last_actual_set_interrupt = 0;
const ticker_info_t *info = _intf->get_info();
if (info->bits >= 32) {
_mask = 0xffffffff;
} else {
_mask = ((uint64_t)1 << info->bits) - 1;
}
// Round us_per_tick up
_us_per_tick = (1000000 + info->frequency - 1) / info->frequency;
}
void LowPowerTickerWrapper::_timeout_handler()
{
core_util_critical_section_enter();
_pending_timeout = false;
timestamp_t current = _intf->read();
if (_ticker_match_interval_passed(_last_set_interrupt, current, _cur_match_time)) {
_intf->fire_interrupt();
} else {
_schedule_match(current);
}
core_util_critical_section_exit();
}
bool LowPowerTickerWrapper::_match_check(timestamp_t current)
{
MBED_ASSERT(core_util_in_critical_section());
if (!_pending_match) {
return false;
}
return _ticker_match_interval_passed(_last_set_interrupt, current, _cur_match_time);
}
uint32_t LowPowerTickerWrapper::_lp_ticks_to_us(uint32_t ticks)
{
MBED_ASSERT(core_util_in_critical_section());
// Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period)
return _us_per_tick * ticks + 4;
}
void LowPowerTickerWrapper::_schedule_match(timestamp_t current)
{
MBED_ASSERT(core_util_in_critical_section());
// Check if _intf->set_interrupt is allowed
if (!_set_interrupt_allowed) {
if (((current - _last_actual_set_interrupt) & _mask) >= _min_count_between_writes) {
_set_interrupt_allowed = true;
}
}
uint32_t cycles_until_match = (_cur_match_time - _last_set_interrupt) & _mask;
bool too_close = cycles_until_match < _min_count_until_match;
if (!_set_interrupt_allowed) {
// Can't use _intf->set_interrupt so use microsecond Timeout instead
// Speed optimization - if a timer has already been scheduled
// then don't schedule it again.
if (!_pending_timeout) {
uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match;
_timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks));
_pending_timeout = true;
}
return;
}
if (!too_close) {
// Schedule LP ticker
_intf->set_interrupt(_cur_match_time);
current = _intf->read();
_last_actual_set_interrupt = current;
_set_interrupt_allowed = false;
// Check for overflow
uint32_t new_cycles_until_match = (_cur_match_time - current) & _mask;
if (new_cycles_until_match > cycles_until_match) {
// Overflow so fire now
_intf->fire_interrupt();
return;
}
// Update variables with new time
cycles_until_match = new_cycles_until_match;
too_close = cycles_until_match < _min_count_until_match;
}
if (too_close) {
// Low power ticker incremented to less than _min_count_until_match
// so low power ticker may not fire. Use Timeout to ensure it does fire.
uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match;
_timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks));
_pending_timeout = true;
return;
}
}

239
hal/LowPowerTickerWrapper.h Normal file
View File

@ -0,0 +1,239 @@
/** \addtogroup hal */
/** @{*/
/* mbed Microcontroller Library
* Copyright (c) 2018 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MBED_LOW_POWER_TICKER_WRAPPER_H
#define MBED_LOW_POWER_TICKER_WRAPPER_H
#include "device.h"
#include "hal/ticker_api.h"
#include "hal/us_ticker_api.h"
#include "drivers/Timeout.h"
class LowPowerTickerWrapper {
public:
/**
* Create a new wrapped low power ticker object
*
* @param data Low power ticker data to wrap
* @param interface new ticker interface functions
* @param min_cycles_between_writes The number of whole low power clock periods
* which must complete before subsequent calls to set_interrupt
* @param min_cycles_until_match The minimum number of whole low power clock periods
* from the current time for which the match timestamp passed to set_interrupt is
* guaranteed to fire.
*
* N = min_cycles_between_writes
*
* 0 1 N - 1 N N + 1 N + 2 N + 3
* |-------|------...------|-------|-------|-------|-------|
* ^ ^
* | |
* set_interrupt Next set_interrupt allowed
*
* N = min_cycles_until_match
*
* 0 1 N - 1 N N + 1 N + 2 N + 3
* |-------|------...------|-------|-------|-------|-------|
* ^ ^
* | |
* set_interrupt Earliest match timestamp allowed
*
*
*/
LowPowerTickerWrapper(const ticker_data_t *data, const ticker_interface_t *interface, uint32_t min_cycles_between_writes, uint32_t min_cycles_until_match);
/**
* Interrupt handler called by the underlying driver/hardware
*
* @param handler The callback which would normally be called by the underlying driver/hardware
*/
void irq_handler(ticker_irq_handler_type handler);
/**
* Suspend wrapper operation and pass through interrupts.
*
* This stops to wrapper layer from using the microsecond ticker.
* This should be called before using the low power ticker APIs directly.
*/
void suspend();
/**
* Resume wrapper operation and filter interrupts normally
*/
void resume();
/**
* Check if a Timeout object is being used
*
* @return true if Timeout is used for scheduling false otherwise
*/
bool timeout_pending();
/*
* Implementation of ticker_init
*/
void init();
/*
* Implementation of free
*/
void free();
/*
* Implementation of read
*/
uint32_t read();
/*
* Implementation of set_interrupt
*/
void set_interrupt(timestamp_t timestamp);
/*
* Implementation of disable_interrupt
*/
void disable_interrupt();
/*
* Implementation of clear_interrupt
*/
void clear_interrupt();
/*
* Implementation of fire_interrupt
*/
void fire_interrupt();
/*
* Implementation of get_info
*/
const ticker_info_t *get_info();
ticker_data_t data;
private:
mbed::Timeout _timeout;
const ticker_interface_t *const _intf;
/*
* The number of low power clock cycles which must pass between subsequent
* calls to intf->set_interrupt
*/
const uint32_t _min_count_between_writes;
/*
* The minimum number of low power clock cycles in the future that
* a match value can be set to and still fire
*/
const uint32_t _min_count_until_match;
/*
* Flag to indicate if the timer is suspended
*/
bool _suspended;
/*
* _cur_match_time is valid and Timeout is scheduled to fire
*/
bool _pending_timeout;
/*
* set_interrupt has been called and _cur_match_time is valid
*/
bool _pending_match;
/*
* The function LowPowerTickerWrapper::fire_interrupt has been called
* and an interrupt is expected.
*/
bool _pending_fire_now;
/*
* It is safe to call intf->set_interrupt
*/
bool _set_interrupt_allowed;
/*
* Last value written by LowPowerTickerWrapper::set_interrupt
*/
timestamp_t _cur_match_time;
/*
* Time of last call to LowPowerTickerWrapper::set_interrupt
*/
uint32_t _last_set_interrupt;
/*
* Time of last call to intf->set_interrupt
*/
uint32_t _last_actual_set_interrupt;
/*
* Mask of valid bits from intf->read()
*/
uint32_t _mask;
/*
* Microsecond per low power tick (rounded up)
*/
uint32_t _us_per_tick;
void _reset();
/**
* Set the low power ticker match time when hardware is ready
*
* This event is scheduled to set the lp timer after the previous write
* has taken effect and it is safe to write a new value without blocking.
* If the time has already passed then this function fires and interrupt
* immediately.
*/
void _timeout_handler();
/*
* Check match time has passed
*/
bool _match_check(timestamp_t current);
/*
* Convert low power ticks to approximate microseconds
*
* This value is always larger or equal to exact value.
*/
uint32_t _lp_ticks_to_us(uint32_t);
/*
* Schedule a match interrupt to fire at the correct time
*
* @param current The current low power ticker time
*/
void _schedule_match(timestamp_t current);
};
#endif
/** @}*/

View File

@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
#include "hal/lp_ticker_api.h" #include "hal/lp_ticker_api.h"
#include "hal/mbed_lp_ticker_wrapper.h"
#if DEVICE_LPTICKER #if DEVICE_LPTICKER
void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp);
static ticker_event_queue_t events = { 0 }; static ticker_event_queue_t events = { 0 };
static ticker_irq_handler_type irq_handler = ticker_irq_handler; static ticker_irq_handler_type irq_handler = ticker_irq_handler;
@ -28,11 +27,7 @@ static const ticker_interface_t lp_interface = {
.read = lp_ticker_read, .read = lp_ticker_read,
.disable_interrupt = lp_ticker_disable_interrupt, .disable_interrupt = lp_ticker_disable_interrupt,
.clear_interrupt = lp_ticker_clear_interrupt, .clear_interrupt = lp_ticker_clear_interrupt,
#if LPTICKER_DELAY_TICKS > 0
.set_interrupt = lp_ticker_set_interrupt_wrapper,
#else
.set_interrupt = lp_ticker_set_interrupt, .set_interrupt = lp_ticker_set_interrupt,
#endif
.fire_interrupt = lp_ticker_fire_interrupt, .fire_interrupt = lp_ticker_fire_interrupt,
.get_info = lp_ticker_get_info, .get_info = lp_ticker_get_info,
.free = lp_ticker_free, .free = lp_ticker_free,
@ -45,7 +40,11 @@ static const ticker_data_t lp_data = {
const ticker_data_t *get_lp_ticker_data(void) const ticker_data_t *get_lp_ticker_data(void)
{ {
#if LPTICKER_DELAY_TICKS > 0
return get_lp_ticker_wrapper_data(&lp_data);
#else
return &lp_data; return &lp_data;
#endif
} }
ticker_irq_handler_type set_lp_ticker_irq_handler(ticker_irq_handler_type ticker_irq_handler) ticker_irq_handler_type set_lp_ticker_irq_handler(ticker_irq_handler_type ticker_irq_handler)
@ -59,9 +58,13 @@ ticker_irq_handler_type set_lp_ticker_irq_handler(ticker_irq_handler_type ticker
void lp_ticker_irq_handler(void) void lp_ticker_irq_handler(void)
{ {
#if LPTICKER_DELAY_TICKS > 0
lp_ticker_wrapper_irq_handler(irq_handler);
#else
if (irq_handler) { if (irq_handler) {
irq_handler(&lp_data); irq_handler(&lp_data);
} }
#endif
} }
#endif #endif

View File

@ -13,144 +13,115 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include "hal/lp_ticker_api.h" #include "hal/mbed_lp_ticker_wrapper.h"
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) #if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
#include "Timeout.h" #include "hal/LowPowerTickerWrapper.h"
#include "mbed_critical.h" #include "platform/mbed_critical.h"
static const timestamp_t min_delta = LPTICKER_DELAY_TICKS;
static bool init = false;
static bool pending = false;
static bool timeout_pending = false;
static timestamp_t last_set_interrupt = 0;
static timestamp_t last_request = 0;
static timestamp_t next = 0;
static timestamp_t mask;
static timestamp_t reschedule_us;
// Do not use SingletonPtr since this must be initialized in a critical section // Do not use SingletonPtr since this must be initialized in a critical section
static mbed::Timeout *timeout; static LowPowerTickerWrapper *ticker_wrapper;
static uint64_t timeout_data[sizeof(mbed::Timeout) / 8]; static uint64_t ticker_wrapper_data[(sizeof(LowPowerTickerWrapper) + 7) / 8];
static bool init = false;
/** static void lp_ticker_wrapper_init()
* Initialize variables
*/
static void init_local()
{ {
MBED_ASSERT(core_util_in_critical_section()); ticker_wrapper->init();
const ticker_info_t *info = lp_ticker_get_info();
if (info->bits >= 32) {
mask = 0xffffffff;
} else {
mask = ((uint64_t)1 << info->bits) - 1;
}
// Round us_per_tick up
timestamp_t us_per_tick = (1000000 + info->frequency - 1) / info->frequency;
// Add 1 tick to the min delta for the case where the clock transitions after you read it
// Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period)
reschedule_us = (min_delta + 1) * us_per_tick + 4;
timeout = new (timeout_data) mbed::Timeout();
} }
/** static uint32_t lp_ticker_wrapper_read()
* Call lp_ticker_set_interrupt with a value that is guaranteed to fire
*
* Assumptions
* -Only one low power clock tick can pass from the last read (last_read)
* -The closest an interrupt can fire is max_delta + 1
*
* @param last_read The last value read from lp_ticker_read
* @param timestamp The timestamp to trigger the interrupt at
*/
static void set_interrupt_safe(timestamp_t last_read, timestamp_t timestamp)
{ {
MBED_ASSERT(core_util_in_critical_section()); return ticker_wrapper->read();
uint32_t delta = (timestamp - last_read) & mask;
if (delta < min_delta + 2) {
timestamp = (last_read + min_delta + 2) & mask;
}
lp_ticker_set_interrupt(timestamp);
} }
/** static void lp_ticker_wrapper_set_interrupt(timestamp_t timestamp)
* Set the low power ticker match time when hardware is ready
*
* This event is scheduled to set the lp timer after the previous write
* has taken effect and it is safe to write a new value without blocking.
* If the time has already passed then this function fires and interrupt
* immediately.
*/
static void set_interrupt_later()
{ {
core_util_critical_section_enter(); ticker_wrapper->set_interrupt(timestamp);
timestamp_t current = lp_ticker_read();
if (_ticker_match_interval_passed(last_request, current, next)) {
lp_ticker_fire_interrupt();
} else {
set_interrupt_safe(current, next);
last_set_interrupt = lp_ticker_read();
}
timeout_pending = false;
core_util_critical_section_exit();
} }
/** static void lp_ticker_wrapper_disable_interrupt()
* Wrapper around lp_ticker_set_interrupt to prevent blocking {
* ticker_wrapper->disable_interrupt();
* Problems this function is solving: }
* 1. Interrupt may not fire if set earlier than LPTICKER_DELAY_TICKS low power clock cycles
* 2. Setting the interrupt back-to-back will block static void lp_ticker_wrapper_clear_interrupt()
* {
* This wrapper function prevents lp_ticker_set_interrupt from being called ticker_wrapper->clear_interrupt();
* back-to-back and blocking while the first write is in progress. This function }
* avoids that problem by scheduling a timeout event if the lp ticker is in the
* middle of a write operation. static void lp_ticker_wrapper_fire_interrupt()
* {
* @param timestamp Time to call ticker irq ticker_wrapper->fire_interrupt();
* @note this is a utility function and it's not required part of HAL implementation }
*/
extern "C" void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp) static const ticker_info_t *lp_ticker_wrapper_get_info()
{
return ticker_wrapper->get_info();
}
static void lp_ticker_wrapper_free()
{
ticker_wrapper->free();
}
static const ticker_interface_t lp_interface = {
lp_ticker_wrapper_init,
lp_ticker_wrapper_read,
lp_ticker_wrapper_disable_interrupt,
lp_ticker_wrapper_clear_interrupt,
lp_ticker_wrapper_set_interrupt,
lp_ticker_wrapper_fire_interrupt,
lp_ticker_wrapper_free,
lp_ticker_wrapper_get_info
};
void lp_ticker_wrapper_irq_handler(ticker_irq_handler_type handler)
{ {
core_util_critical_section_enter(); core_util_critical_section_enter();
if (!init) { if (!init) {
init_local(); // Force ticker to initialize
init = true; get_lp_ticker_data();
} }
timestamp_t current = lp_ticker_read(); ticker_wrapper->irq_handler(handler);
if (pending) {
// Check if pending should be cleared
if (((current - last_set_interrupt) & mask) >= min_delta) {
pending = false;
}
}
if (pending || timeout_pending) {
next = timestamp;
last_request = current;
if (!timeout_pending) {
timeout->attach_us(set_interrupt_later, reschedule_us);
timeout_pending = true;
}
} else {
// Schedule immediately if nothing is pending
set_interrupt_safe(current, timestamp);
last_set_interrupt = lp_ticker_read();
pending = true;
}
core_util_critical_section_exit(); core_util_critical_section_exit();
} }
const ticker_data_t *get_lp_ticker_wrapper_data(const ticker_data_t *data)
{
core_util_critical_section_enter();
if (!init) {
ticker_wrapper = new (ticker_wrapper_data) LowPowerTickerWrapper(data, &lp_interface, LPTICKER_DELAY_TICKS, LPTICKER_DELAY_TICKS);
init = true;
}
core_util_critical_section_exit();
return &ticker_wrapper->data;
}
void lp_ticker_wrapper_suspend()
{
if (!init) {
// Force ticker to initialize
get_lp_ticker_data();
}
ticker_wrapper->suspend();
}
void lp_ticker_wrapper_resume()
{
if (!init) {
// Force ticker to initialize
get_lp_ticker_data();
}
ticker_wrapper->resume();
}
#endif #endif

View File

@ -0,0 +1,78 @@
/** \addtogroup hal */
/** @{*/
/* mbed Microcontroller Library
* Copyright (c) 2018 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MBED_LP_TICKER_WRAPPER_H
#define MBED_LP_TICKER_WRAPPER_H
#include "device.h"
#if DEVICE_LPTICKER
#include "hal/ticker_api.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*ticker_irq_handler_type)(const ticker_data_t *const);
/**
* Interrupt handler for the wrapped lp ticker
*
* @param handler the function which would normally be called by the
* lp ticker handler when it is not wrapped
*/
void lp_ticker_wrapper_irq_handler(ticker_irq_handler_type handler);
/**
* Get wrapped lp ticker data
*
* @param data hardware low power ticker object
* @return wrapped low power ticker object
*/
const ticker_data_t *get_lp_ticker_wrapper_data(const ticker_data_t *data);
/**
* Suspend the wrapper layer code
*
* Pass through all interrupts to the low power ticker and stop using
* the microsecond ticker.
*/
void lp_ticker_wrapper_suspend(void);
/**
* Resume the wrapper layer code
*
* Resume operation of the wrapper layer. Interrupts will be filtered
* as normal and the microsecond timer will be used for interrupts scheduled
* too quickly back-to-back.
*/
void lp_ticker_wrapper_resume(void);
/**@}*/
#ifdef __cplusplus
}
#endif
#endif
#endif
/** @}*/

View File

@ -21,6 +21,7 @@
#include "mbed_error.h" #include "mbed_error.h"
#include "mbed_debug.h" #include "mbed_debug.h"
#include "mbed_stats.h" #include "mbed_stats.h"
#include "us_ticker_api.h"
#include "lp_ticker_api.h" #include "lp_ticker_api.h"
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
@ -183,6 +184,19 @@ bool sleep_manager_can_deep_sleep(void)
return deep_sleep_lock == 0 ? true : false; return deep_sleep_lock == 0 ? true : false;
} }
bool sleep_manager_can_deep_sleep_test_check()
{
const uint32_t check_time_us = 2000;
const ticker_data_t *const ticker = get_us_ticker_data();
uint32_t start = ticker_read(ticker);
while ((ticker_read(ticker) - start) < check_time_us) {
if (sleep_manager_can_deep_sleep()) {
return true;
}
}
return false;
}
void sleep_manager_sleep_auto(void) void sleep_manager_sleep_auto(void)
{ {
#ifdef MBED_SLEEP_TRACING_ENABLED #ifdef MBED_SLEEP_TRACING_ENABLED

View File

@ -32,6 +32,9 @@ static void initialize(const ticker_data_t *ticker)
if (ticker->queue->initialized) { if (ticker->queue->initialized) {
return; return;
} }
if (ticker->queue->suspended) {
return;
}
ticker->interface->init(); ticker->interface->init();
@ -70,6 +73,7 @@ static void initialize(const ticker_data_t *ticker)
ticker->queue->max_delta_us = max_delta_us; ticker->queue->max_delta_us = max_delta_us;
ticker->queue->present_time = 0; ticker->queue->present_time = 0;
ticker->queue->dispatching = false; ticker->queue->dispatching = false;
ticker->queue->suspended = false;
ticker->queue->initialized = true; ticker->queue->initialized = true;
update_present_time(ticker); update_present_time(ticker);
@ -121,6 +125,9 @@ static us_timestamp_t convert_timestamp(us_timestamp_t ref, timestamp_t timestam
static void update_present_time(const ticker_data_t *const ticker) static void update_present_time(const ticker_data_t *const ticker)
{ {
ticker_event_queue_t *queue = ticker->queue; ticker_event_queue_t *queue = ticker->queue;
if (queue->suspended) {
return;
}
uint32_t ticker_time = ticker->interface->read(); uint32_t ticker_time = ticker->interface->read();
if (ticker_time == ticker->queue->tick_last_read) { if (ticker_time == ticker->queue->tick_last_read) {
// No work to do // No work to do
@ -230,7 +237,7 @@ int _ticker_match_interval_passed(timestamp_t prev_tick, timestamp_t cur_tick, t
static void schedule_interrupt(const ticker_data_t *const ticker) static void schedule_interrupt(const ticker_data_t *const ticker)
{ {
ticker_event_queue_t *queue = ticker->queue; ticker_event_queue_t *queue = ticker->queue;
if (ticker->queue->dispatching) { if (queue->suspended || ticker->queue->dispatching) {
// Don't schedule the next interrupt until dispatching is // Don't schedule the next interrupt until dispatching is
// finished. This prevents repeated calls to interface->set_interrupt // finished. This prevents repeated calls to interface->set_interrupt
return; return;
@ -282,6 +289,10 @@ void ticker_irq_handler(const ticker_data_t *const ticker)
core_util_critical_section_enter(); core_util_critical_section_enter();
ticker->interface->clear_interrupt(); ticker->interface->clear_interrupt();
if (ticker->queue->suspended) {
core_util_critical_section_exit();
return;
}
/* Go through all the pending TimerEvents */ /* Go through all the pending TimerEvents */
ticker->queue->dispatching = true; ticker->queue->dispatching = true;
@ -427,3 +438,29 @@ int ticker_get_next_timestamp(const ticker_data_t *const data, timestamp_t *time
return ret; return ret;
} }
void ticker_suspend(const ticker_data_t *const ticker)
{
core_util_critical_section_enter();
ticker->queue->suspended = true;
core_util_critical_section_exit();
}
void ticker_resume(const ticker_data_t *const ticker)
{
core_util_critical_section_enter();
ticker->queue->suspended = false;
if (ticker->queue->initialized) {
ticker->queue->tick_last_read = ticker->interface->read();
update_present_time(ticker);
schedule_interrupt(ticker);
} else {
initialize(ticker);
}
core_util_critical_section_exit();
}

View File

@ -82,6 +82,7 @@ typedef struct {
us_timestamp_t present_time; /**< Store the timestamp used for present time */ us_timestamp_t present_time; /**< Store the timestamp used for present time */
bool initialized; /**< Indicate if the instance is initialized */ bool initialized; /**< Indicate if the instance is initialized */
bool dispatching; /**< The function ticker_irq_handler is dispatching */ bool dispatching; /**< The function ticker_irq_handler is dispatching */
bool suspended; /**< Indicate if the instance is suspended */
uint8_t frequency_shifts; /**< If frequency is a value of 2^n, this is n, otherwise 0 */ uint8_t frequency_shifts; /**< If frequency is a value of 2^n, this is n, otherwise 0 */
} ticker_event_queue_t; } ticker_event_queue_t;
@ -184,6 +185,27 @@ us_timestamp_t ticker_read_us(const ticker_data_t *const ticker);
*/ */
int ticker_get_next_timestamp(const ticker_data_t *const ticker, timestamp_t *timestamp); int ticker_get_next_timestamp(const ticker_data_t *const ticker, timestamp_t *timestamp);
/** Suspend this ticker
*
* When suspended reads will always return the same time and no
* events will be dispatched. When suspended the common layer
* will only ever call the interface function clear_interrupt()
* and that is only if ticker_irq_handler is called.
*
*
* @param ticker The ticker object.
*/
void ticker_suspend(const ticker_data_t *const ticker);
/** Resume this ticker
*
* When resumed the ticker will ignore any time that has passed
* and continue counting up where it left off.
*
* @param ticker The ticker object.
*/
void ticker_resume(const ticker_data_t *const ticker);
/* Private functions /* Private functions
* *
* @cond PRIVATE * @cond PRIVATE

View File

@ -122,6 +122,17 @@ void sleep_manager_unlock_deep_sleep_internal(void);
*/ */
bool sleep_manager_can_deep_sleep(void); bool sleep_manager_can_deep_sleep(void);
/** Check if the target can deep sleep within a period of time
*
* This function in intended for use in testing. The amount
* of time this functions waits for deeps sleep to be available
* is currently 2ms. This may change in the future depending
* on testing requirements.
*
* @return true if a target can go to deepsleep, false otherwise
*/
bool sleep_manager_can_deep_sleep_test_check(void);
/** Enter auto selected sleep mode. It chooses the sleep or deeepsleep modes based /** Enter auto selected sleep mode. It chooses the sleep or deeepsleep modes based
* on the deepsleep locking counter * on the deepsleep locking counter
* *