From 1f97f1137300e0c63fc4103924619b8c2405ecae Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Fri, 11 Aug 2017 14:27:24 -0500 Subject: [PATCH] Add documentation and test the HAL RTC API Add requirements, tests, an example implementation and additional function documentation to the HAL RTC API. --- TESTS/host_tests/rtc_reset.py | 119 ++++++++++++++++++++ TESTS/mbed_hal/rtc/main.cpp | 127 ++++++++++++++++++++++ TESTS/mbed_hal/rtc/rtc_test.h | 63 +++++++++++ TESTS/mbed_hal/rtc_reset/main.cpp | 98 +++++++++++++++++ TESTS/mbed_hal/rtc_reset/rtc_reset_test.h | 40 +++++++ hal/rtc_api.h | 124 +++++++++++++++++++-- 6 files changed, 563 insertions(+), 8 deletions(-) create mode 100644 TESTS/host_tests/rtc_reset.py create mode 100644 TESTS/mbed_hal/rtc/main.cpp create mode 100644 TESTS/mbed_hal/rtc/rtc_test.h create mode 100644 TESTS/mbed_hal/rtc_reset/main.cpp create mode 100644 TESTS/mbed_hal/rtc_reset/rtc_reset_test.h diff --git a/TESTS/host_tests/rtc_reset.py b/TESTS/host_tests/rtc_reset.py new file mode 100644 index 0000000000..56a86ec745 --- /dev/null +++ b/TESTS/host_tests/rtc_reset.py @@ -0,0 +1,119 @@ +""" +mbed SDK +Copyright (c) 2017-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. +""" +from __future__ import print_function + +from mbed_host_tests import BaseHostTest +from time import sleep + + +class RtcResetTest(BaseHostTest): + """This test checks that a device's RTC keeps count through a reset + + It does this by setting the RTC's time, triggering a reset, + delaying and then reading the RTC's time again to ensure + that the RTC is still counting. + """ + + """Start of the RTC""" + START_TIME = 50000 + START_TIME_TOLERANCE = 10 + """Time to delay after sending reset""" + DELAY_TIME = 4.0 + DELAY_TOLERANCE = 1.0 + VALUE_PLACEHOLDER = "0" + + def setup(self): + """Register callbacks required for the test""" + self._error = False + generator = self.rtc_reset_test() + generator.next() + + def run_gen(key, value, time): + """Run the generator, and fail testing if the iterator stops""" + if self._error: + return + try: + print("Calling generator") + generator.send((key, value, time)) + except StopIteration: + self._error = True + + for resp in ("start", "read"): + self.register_callback(resp, run_gen) + + def teardown(self): + """No work to do here""" + pass + + def rtc_reset_test(self): + """Generator for running the reset test + + This function calls yield to wait for the next event from + the device. If the device gives the wrong response, then the + generator terminates by returing which raises a StopIteration + exception and fails the test. + """ + + # Wait for start token + key, value, time = yield + if key != "start": + return + + # Initialize, and set the time + self.send_kv("init", self.VALUE_PLACEHOLDER) + self.send_kv("write", str(self.START_TIME)) + self.send_kv("read", self.VALUE_PLACEHOLDER) + key, value, time = yield + if key != "read": + return + dev_time_start = int(value) + + # Unitialize, and reset + self.send_kv("free", self.VALUE_PLACEHOLDER) + self.send_kv("reset", self.VALUE_PLACEHOLDER) + sleep(self.DELAY_TIME) + + # Restart the test, and send the sync token + self.send_kv("__sync", "00000000-0000-000000000-000000000000") + key, value, time = yield + if key != "start": + return + + # Initialize, and read the time + self.send_kv("init", self.VALUE_PLACEHOLDER) + self.send_kv("read", self.VALUE_PLACEHOLDER) + key, value, time = yield + if key != "read": + return + dev_time_end = int(value) + + # Check result + elapsed = dev_time_end - dev_time_start + start_time_valid = (self.START_TIME <= dev_time_start < + self.START_TIME + self.START_TIME_TOLERANCE) + elapsed_time_valid = elapsed >= self.DELAY_TIME - self.DELAY_TOLERANCE + passed = start_time_valid and elapsed_time_valid + if not start_time_valid: + self.log("FAIL: Expected start time of %i got %i" % + (self.START_TIME, dev_time_start)) + elif not passed: + self.log("FAIL: Delayed for %fs but device " + "reported elapsed time of %fs" % + (self.DELAY_TIME, elapsed)) + self.send_kv("exit", "pass" if passed else "fail") + yield # No more events expected + diff --git a/TESTS/mbed_hal/rtc/main.cpp b/TESTS/mbed_hal/rtc/main.cpp new file mode 100644 index 0000000000..084822517f --- /dev/null +++ b/TESTS/mbed_hal/rtc/main.cpp @@ -0,0 +1,127 @@ +/* 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 !DEVICE_RTC + #error [NOT_SUPPORTED] RTC API not supported for this target +#endif + +#include "utest/utest.h" +#include "unity/unity.h" +#include "greentea-client/test_env.h" +#include "rtc_test.h" + +#include "mbed.h" +#include "rtc_api.h" + + +using namespace utest::v1; + +static const uint32_t WAIT_TIME = 4; +static const uint32_t WAIT_TOLERANCE = 1; + + +void rtc_init_test() +{ + for (int i = 0; i < 10; i++) { + rtc_init(); + } +} + +void rtc_sleep_test() +{ + const uint32_t start = 100; + rtc_init(); + + rtc_write(start); + wait(WAIT_TIME); + const uint32_t stop = rtc_read(); + + rtc_free(); + + TEST_ASSERT_UINT32_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); +} + +void rtc_persist_test() +{ + const uint32_t start = 100; + rtc_init(); + rtc_write(start); + rtc_free(); + + wait(WAIT_TIME); + + rtc_init(); + const uint32_t stop = rtc_read(); + const int enabled = rtc_isenabled(); + rtc_free(); + + TEST_ASSERT_TRUE(enabled); + TEST_ASSERT_UINT32_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); +} + +void rtc_glitch_test() +{ + const uint32_t start = 0xffffe; + rtc_init(); + + rtc_write(start); + uint32_t last = start; + while (last < start + 4) { + const uint32_t cur = rtc_read(); + TEST_ASSERT(cur >= last); + last = cur; + } + + rtc_free(); +} + +void rtc_range_test() +{ + static const uint32_t starts[] = { + 0x00000000, + 0xEFFFFFFF, + 0x00001000, + }; + + rtc_init(); + for (uint32_t i = 0; i < sizeof(starts) / sizeof(starts[0]); i++) { + const uint32_t start = starts[i]; + rtc_write(start); + wait(WAIT_TIME); + const uint32_t stop = rtc_read(); + TEST_ASSERT_UINT32_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); + } + rtc_free(); +} + +Case cases[] = { + Case("RTC - init", rtc_init_test), + Case("RTC - sleep", rtc_sleep_test), + Case("RTC - persist", rtc_persist_test), + Case("RTC - glitch", rtc_glitch_test), + Case("RTC - range", rtc_range_test), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(30, "default_auto"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() { + Harness::run(specification); +} diff --git a/TESTS/mbed_hal/rtc/rtc_test.h b/TESTS/mbed_hal/rtc/rtc_test.h new file mode 100644 index 0000000000..74d14ab709 --- /dev/null +++ b/TESTS/mbed_hal/rtc/rtc_test.h @@ -0,0 +1,63 @@ +/** \addtogroup hal_rtc_tests + * @{ + */ +/* mbed Microcontroller Library + * Copyright (c) 2017-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. + */ +#ifndef MBED_RTC_TEST_H +#define MBED_RTC_TEST_H + +#if DEVICE_RTC + +#ifdef __cplusplus +extern "C" { +#endif + +/** Test that ::rtc_init can be called multiple times + * + */ +void rtc_init_test(void); + +/** Test that the RTC keeps counting in the various sleep modes + * + */ +void rtc_sleep_test(void); + +/** Test that the RTC keeps counting even after ::rtc_free has been called + * + */ +void rtc_persist_test(void); + +/** Test time does not glitch backwards due to an incorrectly implemented ripple counter driver + * + */ +void rtc_glitch_test(void); + +/** Test that the RTC correctly handles large time values + * + */ +void rtc_range_test(void); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +/** @}*/ diff --git a/TESTS/mbed_hal/rtc_reset/main.cpp b/TESTS/mbed_hal/rtc_reset/main.cpp new file mode 100644 index 0000000000..658711c040 --- /dev/null +++ b/TESTS/mbed_hal/rtc_reset/main.cpp @@ -0,0 +1,98 @@ +/* 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 !DEVICE_RTC + #error [NOT_SUPPORTED] RTC API not supported for this target +#endif + +#include "utest/utest.h" +#include "unity/unity.h" +#include "greentea-client/test_env.h" +#include "rtc_reset_test.h" + +#include "mbed.h" +#include "rtc_api.h" + + +typedef enum { + CMD_STATUS_PASS, + CMD_STATUS_FAIL, + CMD_STATUS_CONTINUE, + CMD_STATUS_ERROR +} cmd_status_t; + +static cmd_status_t handle_command(const char *key, const char *value) +{ + if (strcmp(key, "init") == 0) { + rtc_init(); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "free") == 0) { + rtc_free(); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "read") == 0) { + static char time_buf[64]; + memset(time_buf, 0, sizeof(time_buf)); + sprintf(time_buf, "%lu", (uint32_t)rtc_read()); + greentea_send_kv("read", time_buf); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "write") == 0) { + uint32_t time; + sscanf(value, "%lu", &time); + rtc_write(time); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "reset") == 0) { + NVIC_SystemReset(); + // Code shouldn't read this due to the reset + return CMD_STATUS_ERROR; + + } else if (strcmp(key, "exit") == 0) { + return strcmp(value, "pass") == 0 ? CMD_STATUS_PASS : CMD_STATUS_FAIL; + + } else { + return CMD_STATUS_ERROR; + + } +} + +void rtc_reset_test() +{ + GREENTEA_SETUP(60, "rtc_reset"); + + static char _key[10 + 1] = {}; + static char _value[128 + 1] = {}; + + greentea_send_kv("start", 1); + + // Handshake with host + cmd_status_t cmd_status = CMD_STATUS_CONTINUE; + while (CMD_STATUS_CONTINUE == cmd_status) { + memset(_key, 0, sizeof(_key)); + memset(_value, 0, sizeof(_value)); + greentea_parse_kv(_key, _value, sizeof(_key) - 1, sizeof(_value) - 1); + cmd_status = handle_command(_key, _value); + } + + GREENTEA_TESTSUITE_RESULT(CMD_STATUS_PASS == cmd_status); +} + +int main() +{ + rtc_reset_test(); +} diff --git a/TESTS/mbed_hal/rtc_reset/rtc_reset_test.h b/TESTS/mbed_hal/rtc_reset/rtc_reset_test.h new file mode 100644 index 0000000000..d145ec21a8 --- /dev/null +++ b/TESTS/mbed_hal/rtc_reset/rtc_reset_test.h @@ -0,0 +1,40 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-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. + */ +#ifndef MBED_RTC_TEST_H +#define MBED_RTC_TEST_H + +#if DEVICE_RTC + +#ifdef __cplusplus +extern "C" { +#endif + +/** Test that the RTC does not stop counting after a software reset + * \ingroup hal_rtc_tests + */ +void rtc_reset_test(); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +/** @}*/ diff --git a/hal/rtc_api.h b/hal/rtc_api.h index db0c55aada..be3182e585 100644 --- a/hal/rtc_api.h +++ b/hal/rtc_api.h @@ -28,37 +28,145 @@ extern "C" { #endif /** - * \defgroup hal_rtc RTC hal functions + * \defgroup hal_rtc RTC hal + * + * The RTC hal provides a low level interface to the Real Time Counter (RTC) of a + * target. + * + * # Defined behavior + * * The function ::rtc_init is safe to call repeatedly - Verified by test ::rtc_init_test + * * RTC accuracy is at least 10% - Not verified + * * Init/free doesn't stop RTC from counting - Verified by test ::rtc_persist_test + * * Software reset doesn't stop RTC from counting - Verified by ::rtc_reset_test + * * Sleep modes don't stop RTC from counting - Verified by ::rtc_sleep_test + * * Shutdown mode doesn't stop RTC from counting - Not verified + * + * # Undefined behavior + * * Calling any function other than ::rtc_init before the initialization of the RTC + * + * # Potential bugs + * * Incorrect overflow handling - Verified by ::rtc_range_test + * * Glitches due to ripple counter - Verified by ::rtc_glitch_test + * + * @see hal_rtc_tests + * * @{ */ +/** + * \defgroup hal_rtc_tests RTC hal tests + * The RTC test validate proper implementation of the RTC hal. + * + * To run the RTC hal tests use the command: + * + * mbed test -t -m -n tests-mbed_hal-rtc* + */ + + /** Initialize the RTC peripheral * + * Powerup the RTC in perpetration for access. This function must be called + * before any other RTC functions ares called. This does not change the state + * of the RTC. It just enables access to it. + * + * @note This function is safe to call repeatedly - Tested by ::rtc_init_test + * + * Pseudo Code: + * @code + * void rtc_init() + * { + * // Enable clock gate so processor can read RTC registers + * POWER_CTRL |= POWER_CTRL_RTC_Msk; + * + * // See if the RTC is already setup + * if (!(RTC_STATUS & RTC_STATUS_COUNTING_Msk)) { + * + * // Setup the RTC clock source + * RTC_CTRL |= RTC_CTRL_CLK32_Msk; + * } + * } + * @endcode */ void rtc_init(void); /** Deinitialize RTC * - * TODO: The function is not used by rtc api in mbed-drivers. + * Powerdown the RTC in preparation for sleep, powerdown or reset. + * After this function is called no other RTC functions should be called + * except for ::rtc_init. + * + * @note This function does not stop the RTC from counting - Tested by ::rtc_persist_test + * + * Pseudo Code: + * @code + * void rtc_free() + * { + * // Disable clock gate since processor no longer needs to read RTC registers + * POWER_CTRL &= ~POWER_CTRL_RTC_Msk; + * } + * @endcode */ void rtc_free(void); -/** Get the RTC enable status +/** Check if the RTC has the time set and is counting * - * @retval 0 disabled - * @retval 1 enabled + * @retval 0 The time reported by the RTC is not valid + * @retval 1 The time has been set the RTC is counting + * + * Pseudo Code: + * @code + * int rtc_isenabled() + * { + * if (RTC_STATUS & RTC_STATUS_COUNTING_Msk) { + * return 1; + * } else { + * return 0; + * } + * } + * @endcode */ int rtc_isenabled(void); /** Get the current time from the RTC peripheral * - * @return The current time + * @return The current time in seconds + * + * @note Some RTCs are not synchronized with the main clock. If + * this is the case with your RTC then you must read the RTC time + * in a loop to prevent reading the wrong time due to a glitch. + * The test ::rtc_glitch_test is intended to catch this bug. + * + * Example implementation for an unsynchronized ripple counter: + * @code + * time_t rtc_read() + * { + * uint32_t val; + * uint32_t last_val; + * + * // Loop until the same value is read twice + * val = RTC_SECONDS; + * do { + * last_val = val; + * val = RTC_SECONDS; + * } while (last_val != val); + * + * return (time_t)val; + * } + * @endcode */ time_t rtc_read(void); -/** Set the current time to the RTC peripheral +/** Write the current time in seconds to the RTC peripheral * - * @param t The current time to be set + * @param t The current time to be set in seconds. + * + * Pseudo Code: + * @code + * void rtc_write(time_t t) + * { + * RTC_SECONDS = t; + * } + * @endcode */ void rtc_write(time_t t);