diff --git a/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp b/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp new file mode 100644 index 00000000000..51a85cea963 --- /dev/null +++ b/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp @@ -0,0 +1,274 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 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. + */ +#if !MBED_TICKLESS +#error [NOT_SUPPORTED] Tickless mode not supported for this target. +#endif + +#if !DEVICE_LOWPOWERTIMER +#error [NOT_SUPPORTED] Current SysTimer implementation requires lp ticker support. +#endif + +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" + +extern "C" { +#include "rtx_lib.h" +} +#include "rtos/TARGET_CORTEX/SysTimer.h" + +#define TEST_TICKS 42UL +#define DELAY_DELTA_US 2500ULL + +using namespace utest::v1; + +const us_timestamp_t DELAY_US = 1000000ULL * TEST_TICKS / OS_TICK_FREQ; + +// Override the handler() -- the SysTick interrupt must not be set as pending by the test code. +class SysTimerTest: public rtos::internal::SysTimer { +private: + Semaphore _sem; + virtual void handler() + { + increment_tick(); + _sem.release(); + } + +public: + SysTimerTest() : + SysTimer(), _sem(0, 1) + { + } + + virtual ~SysTimerTest() + { + } + + int32_t sem_wait(uint32_t millisec) + { + return _sem.wait(millisec); + } +}; + +/** Test tick count is zero upon creation + * + * Given a SysTimer + * When the timer is created + * Then tick count is zero + */ +void test_created_with_zero_tick_count(void) +{ + SysTimerTest st; + TEST_ASSERT_EQUAL_UINT32(0, st.get_tick()); +} + +/** Test tick count is updated correctly + * + * Given a SysTimer + * When @a update_tick method is called immediately after creation + * Then the tick count is not updated + * When @a update_tick is called again after a delay + * Then the tick count is updated + * and the number of ticks incremented is equal TEST_TICKS - 1 + * When @a update_tick is called again without a delay + * Then the tick count is not updated + */ +void test_update_tick(void) +{ + SysTimerTest st; + TEST_ASSERT_EQUAL_UINT32(0, st.update_tick()); + TEST_ASSERT_EQUAL_UINT32(0, st.get_tick()); + us_timestamp_t test_ticks_elapsed_ts = st.get_time() + DELAY_US; + + while (st.get_time() <= test_ticks_elapsed_ts) {} + TEST_ASSERT_EQUAL_UINT32(TEST_TICKS - 1, st.update_tick()); + TEST_ASSERT_EQUAL_UINT32(TEST_TICKS - 1, st.get_tick()); + + TEST_ASSERT_EQUAL_UINT32(0, st.update_tick()); + TEST_ASSERT_EQUAL_UINT32(TEST_TICKS - 1, st.get_tick()); +} + +/** Test get_time returns correct time + * + * Given a SysTimer + * When @a get_time method is called before and after a delay + * Then time difference is equal the delay + */ +void test_get_time(void) +{ + SysTimerTest st; + us_timestamp_t t1 = st.get_time(); + + wait_us(DELAY_US); + us_timestamp_t t2 = st.get_time(); + TEST_ASSERT_UINT64_WITHIN(DELAY_DELTA_US, DELAY_US, t2 - t1); +} + +/** Test cancel_tick + * + * Given a SysTimer with a scheduled tick + * When @a cancel_tick is called before the given number of ticks elapse + * Then the handler is never called + * and the tick count is not incremented + */ +void test_cancel_tick(void) +{ + SysTimerTest st; + st.cancel_tick(); + st.schedule_tick(TEST_TICKS); + + st.cancel_tick(); + int32_t sem_slots = st.sem_wait((DELAY_US + DELAY_DELTA_US) / 1000ULL); + TEST_ASSERT_EQUAL_INT32(0, sem_slots); + TEST_ASSERT_EQUAL_UINT32(0, st.get_tick()); +} + +/** Test schedule zero + * + * Given a SysTimer + * When a tick is scheduled with delta = 0 ticks + * Then the handler is called instantly + */ +void test_schedule_zero(void) +{ + SysTimerTest st; + + st.schedule_tick(0UL); + int32_t sem_slots = st.sem_wait(0UL); + TEST_ASSERT_EQUAL_INT32(1, sem_slots); +} + +/** Test handler called once + * + * Given a SysTimer with a tick scheduled with delta = TEST_TICKS + * When the handler is called + * Then the tick count is incremented by 1 + * and elapsed time is equal 1000000ULL * TEST_TICKS / OS_TICK_FREQ; + * When more time elapses + * Then the handler is not called again + */ +void test_handler_called_once(void) +{ + SysTimerTest st; + st.schedule_tick(TEST_TICKS); + us_timestamp_t t1 = st.get_time(); + int32_t sem_slots = st.sem_wait(0); + TEST_ASSERT_EQUAL_INT32(0, sem_slots); + + sem_slots = st.sem_wait(osWaitForever); + us_timestamp_t t2 = st.get_time(); + TEST_ASSERT_EQUAL_INT32(1, sem_slots); + TEST_ASSERT_EQUAL_UINT32(1, st.get_tick()); + TEST_ASSERT_UINT64_WITHIN(DELAY_DELTA_US, DELAY_US, t2 - t1); + + sem_slots = st.sem_wait((DELAY_US + DELAY_DELTA_US) / 1000ULL); + TEST_ASSERT_EQUAL_INT32(0, sem_slots); + TEST_ASSERT_EQUAL_UINT32(1, st.get_tick()); +} + +/** Test wake up from sleep + * + * Given a SysTimer with a tick scheduled in the future + * and a core in sleep mode + * When given time elapses + * Then the uC is woken up from sleep + * and the tick handler is called + * and measured time matches requested delay + */ +void test_sleep(void) +{ + Timer timer; + SysTimerTest st; + + sleep_manager_lock_deep_sleep(); + timer.start(); + st.schedule_tick(TEST_TICKS); + + TEST_ASSERT_FALSE_MESSAGE(sleep_manager_can_deep_sleep(), "Deep sleep should be disallowed"); + while (st.sem_wait(0) != 1) { + sleep(); + } + timer.stop(); + sleep_manager_unlock_deep_sleep(); + + TEST_ASSERT_UINT64_WITHIN(DELAY_DELTA_US, DELAY_US, timer.read_high_resolution_us()); +} + +#if DEVICE_LOWPOWERTIMER +/** Test wake up from deepsleep + * + * Given a SysTimer with a tick scheduled in the future + * and a core in deepsleep mode + * When given time elapses + * Then the uC is woken up from deepsleep + * and the tick handler is called + * and measured time matches requested delay + */ +void test_deepsleep(void) +{ + /* + * Since deepsleep() may shut down the UART peripheral, we wait for 10ms + * to allow for hardware serial buffers to completely flush. + + * This should be replaced with a better function that checks if the + * hardware buffers are empty. However, such an API does not exist now, + * so we'll use the wait_ms() function for now. + */ + wait_ms(10); + + // Regular Timer might be disabled during deepsleep. + LowPowerTimer lptimer; + SysTimerTest st; + + lptimer.start(); + st.schedule_tick(TEST_TICKS); + TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "Deep sleep should be allowed"); + while (st.sem_wait(0) != 1) { + sleep(); + } + lptimer.stop(); + + TEST_ASSERT_UINT64_WITHIN(DELAY_DELTA_US, DELAY_US, lptimer.read_high_resolution_us()); +} +#endif + +utest::v1::status_t test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(5, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Tick count is zero upon creation", test_created_with_zero_tick_count), + Case("Tick count is updated correctly", test_update_tick), + Case("Time is updated correctly", test_get_time), + Case("Tick can be cancelled", test_cancel_tick), + Case("Schedule zero ticks", test_schedule_zero), + Case("Handler called once", test_handler_called_once), + Case("Wake up from sleep", test_sleep), +#if DEVICE_LOWPOWERTIMER + Case("Wake up from deep sleep", test_deepsleep), +#endif + +}; + +Specification specification(test_setup, cases); + +int main() +{ + return !Harness::run(specification); +} diff --git a/rtos/TARGET_CORTEX/SysTimer.cpp b/rtos/TARGET_CORTEX/SysTimer.cpp new file mode 100644 index 00000000000..fc2e06bc363 --- /dev/null +++ b/rtos/TARGET_CORTEX/SysTimer.cpp @@ -0,0 +1,126 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2012 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "rtos/TARGET_CORTEX/SysTimer.h" + +#if DEVICE_LOWPOWERTIMER + +#include "hal/lp_ticker_api.h" +#include "rtx_core_cm.h" +extern "C" { +#include "rtx_lib.h" +} + +#if (defined(NO_SYSTICK)) +/** + * Return an IRQ number that can be used in the absence of SysTick + * + * @return Free IRQ number that can be used + */ +extern "C" IRQn_Type mbed_get_m0_tick_irqn(void); +#endif + +namespace rtos { +namespace internal { + +SysTimer::SysTimer() : + TimerEvent(get_lp_ticker_data()), _start_time(0), _tick(0) +{ + _start_time = ticker_read_us(_ticker_data); +} + +void SysTimer::setup_irq() +{ +#if (defined(NO_SYSTICK)) + NVIC_SetVector(mbed_get_m0_tick_irqn(), (uint32_t)SysTick_Handler); + NVIC_SetPriority(mbed_get_m0_tick_irqn(), 0xFF); /* RTOS requires lowest priority */ + NVIC_EnableIRQ(mbed_get_m0_tick_irqn()); +#else + // Ensure SysTick has the correct priority as it is still used + // to trigger software interrupts on each tick. The period does + // not matter since it will never start counting. + OS_Tick_Setup(osRtxConfig.tick_freq, OS_TICK_HANDLER); +#endif +} + +void SysTimer::schedule_tick(uint32_t delta) +{ + insert_absolute(_start_time + (_tick + delta) * 1000000ULL / OS_TICK_FREQ); +} + +void SysTimer::cancel_tick() +{ + remove(); +} + +uint32_t SysTimer::get_tick() +{ + return _tick & 0xFFFFFFFF; +} + +uint32_t SysTimer::update_tick() +{ + uint64_t new_tick = (ticker_read_us(_ticker_data) - _start_time) * OS_TICK_FREQ / 1000000; + if (new_tick > _tick) { + // Don't update to the current tick. Instead, update to the + // previous tick and let the SysTick handler increment it + // to the current value. This allows scheduling restart + // successfully after the OS is resumed. + new_tick--; + } + uint32_t elapsed_ticks = new_tick - _tick; + _tick = new_tick; + return elapsed_ticks; +} + +us_timestamp_t SysTimer::get_time() +{ + return ticker_read_us(_ticker_data); +} + +SysTimer::~SysTimer() +{ +} + +void SysTimer::set_irq_pending() +{ +#if (defined(NO_SYSTICK)) + NVIC_SetPendingIRQ(mbed_get_m0_tick_irqn()); +#else + SCB->ICSR = SCB_ICSR_PENDSTSET_Msk; +#endif +} + +void SysTimer::increment_tick() +{ + _tick++; +} + +void SysTimer::handler() +{ + set_irq_pending(); + increment_tick(); +} + +} +} + +#endif diff --git a/rtos/TARGET_CORTEX/SysTimer.h b/rtos/TARGET_CORTEX/SysTimer.h new file mode 100644 index 00000000000..2af7c8ec33f --- /dev/null +++ b/rtos/TARGET_CORTEX/SysTimer.h @@ -0,0 +1,117 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2012 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_SYS_TIMER_H +#define MBED_SYS_TIMER_H + +#if defined(DEVICE_LOWPOWERTIMER) || defined(DOXYGEN_ONLY) + +#include "platform/NonCopyable.h" +#include "drivers/TimerEvent.h" + +namespace rtos { +namespace internal { + +/** + * @cond RTOS_INTERNAL + * + * @addtogroup rtos + * @{ + * + * @defgroup rtos_SysTimer SysTimer class + * @{ + */ + +/** + * The SysTimer class is used exclusively by RTX idle loop in TICKLESS mode. + * + * @note SysTimer is not the part of Mbed RTOS API. + */ +class SysTimer: private mbed::TimerEvent, private mbed::NonCopyable { +public: + + SysTimer(); + virtual ~SysTimer(); + + /** + * Enable an IRQ/SysTick with the correct priority. + */ + static void setup_irq(); + + /** + * Schedule an os tick to fire + * + * @param delta Tick to fire at relative to current tick + * + * @warning If a tick is already scheduled it needs to be cancelled first! + */ + void schedule_tick(uint32_t delta = 1); + + /** + * Prevent any scheduled ticks from triggering + */ + void cancel_tick(); + + /** Get the current tick count + * + * @return The number of ticks since timer creation. For the os_timer this + * should match RTX's tick count (the number of ticks since boot). + */ + uint32_t get_tick(); + + /** + * Update the internal tick count + * + * @return The number of ticks incremented + * + * @note Due to a scheduling issue, the number of ticks returned is decremented + * by 1 so that a handler can be called and update to the current value. + * This allows scheduling restart successfully after the OS is resumed. + */ + uint32_t update_tick(); + + /** + * Get the time + * + * @return Current time in microseconds + */ + us_timestamp_t get_time(); + +protected: + virtual void handler(); + void increment_tick(); + static void set_irq_pending(); + us_timestamp_t _start_time; + uint64_t _tick; +}; + +/** + * @} + * @} + * @endcond + */ + +} +} + +#endif + +#endif diff --git a/rtos/TARGET_CORTEX/mbed_rtx_idle.cpp b/rtos/TARGET_CORTEX/mbed_rtx_idle.cpp index 23830a8f5dc..ac0ad4c53b3 100644 --- a/rtos/TARGET_CORTEX/mbed_rtx_idle.cpp +++ b/rtos/TARGET_CORTEX/mbed_rtx_idle.cpp @@ -36,113 +36,18 @@ using namespace mbed; #ifdef MBED_TICKLESS -#if (defined(NO_SYSTICK)) -/** - * Return an IRQ number that can be used in the absence of SysTick - * - * @return Free IRQ number that can be used - */ -extern "C" IRQn_Type mbed_get_m0_tick_irqn(void); -#endif - -class RtosTimer : private TimerEvent { -public: - RtosTimer(): TimerEvent(get_lp_ticker_data()), _start_time(0), _tick(0) { - _start_time = ticker_read_us(_ticker_data); -#if (defined(NO_SYSTICK)) - NVIC_SetVector(mbed_get_m0_tick_irqn(), (uint32_t)SysTick_Handler); - NVIC_SetPriority(mbed_get_m0_tick_irqn(), 0xFF); /* RTOS requires lowest priority */ - NVIC_EnableIRQ(mbed_get_m0_tick_irqn()); -#else - // Ensure SysTick has the correct priority as it is still used - // to trigger software interrupts on each tick. The period does - // not matter since it will never start counting. - OS_Tick_Setup(osRtxConfig.tick_freq, OS_TICK_HANDLER); -#endif - }; - - /** - * Schedule an os tick to fire - * - * @param delta Tick to fire at relative to current tick - */ - void schedule_tick(uint32_t delta=1) { - insert_absolute(_start_time + (_tick + delta) * 1000000 / OS_TICK_FREQ); - } - - - /** - * Prevent any scheduled ticks from triggering - */ - void cancel_tick() { - remove(); - } - - /** - * Get the current tick count - * - * @return The number of ticks since boot. This should match RTX's tick count - */ - uint32_t get_tick() { - return _tick & 0xFFFFFFFF; - } - - /** - * Update the internal tick count - * - * @return The number of ticks incremented - */ - uint32_t update_tick() { - uint64_t new_tick = ticker_read_us(_ticker_data) * OS_TICK_FREQ / 1000000; - if (new_tick > _tick) { - // Don't update to the current tick. Instead, update to the - // previous tick and let the SysTick handler increment it - // to the current value. This allows scheduling restart - // successfully after the OS is resumed. - new_tick--; - } - uint32_t elapsed_ticks = new_tick - _tick; - _tick = new_tick; - return elapsed_ticks; - } - - /** - * Get the time - * - * @return Current time in microseconds - */ - us_timestamp_t get_time() { - return ticker_read_us(_ticker_data); - } - - ~RtosTimer() { - - }; - -protected: - - void handler() { -#if (defined(NO_SYSTICK)) - NVIC_SetPendingIRQ(mbed_get_m0_tick_irqn()); -#else - SCB->ICSR = SCB_ICSR_PENDSTSET_Msk; -#endif - _tick++; - } - - us_timestamp_t _start_time; - uint64_t _tick; -}; +#include "rtos/TARGET_CORTEX/SysTimer.h" -static RtosTimer *os_timer; -static uint64_t os_timer_data[sizeof(RtosTimer) / 8]; +static rtos::internal::SysTimer *os_timer; +static uint64_t os_timer_data[sizeof(rtos::internal::SysTimer) / 8]; /// Enable System Timer. int32_t OS_Tick_Enable (void) { // Do not use SingletonPtr since this relies on the RTOS if (NULL == os_timer) { - os_timer = new (os_timer_data) RtosTimer(); + os_timer = new (os_timer_data) rtos::internal::SysTimer(); + os_timer->setup_irq(); } // set to fire interrupt on next tick