From d16242ecbcf10a9b902311314d441787b801e9e8 Mon Sep 17 00:00:00 2001 From: Christos Choutouridis Date: Thu, 7 Oct 2021 12:01:24 +0300 Subject: [PATCH] DEV: A print interface and a timer_delay added --- include/drv/cli_device.h | 2 +- include/tbx.h | 2 + include/utils/print.h | 314 ++++++++++++++++++++++++++++++++++ include/utils/timer_delay.h | 333 ++++++++++++++++++++++++++++++++++++ 4 files changed, 650 insertions(+), 1 deletion(-) create mode 100644 include/utils/print.h create mode 100644 include/utils/timer_delay.h diff --git a/include/drv/cli_device.h b/include/drv/cli_device.h index 57cd27d..11772f8 100644 --- a/include/drv/cli_device.h +++ b/include/drv/cli_device.h @@ -38,8 +38,8 @@ #include #include -#include +#include #include #include diff --git a/include/tbx.h b/include/tbx.h index cac2b86..51bd35f 100644 --- a/include/tbx.h +++ b/include/tbx.h @@ -38,6 +38,8 @@ #include #include +#include +#include #include #include diff --git a/include/utils/print.h b/include/utils/print.h new file mode 100644 index 0000000..6ba331c --- /dev/null +++ b/include/utils/print.h @@ -0,0 +1,314 @@ +/*! + * \file utils/print.h + * \brief + * A CRTP base class to provide print interface + * + * \copyright Copyright (C) 2021 Christos Choutouridis + * + *
License
+ * The MIT License (MIT) + * + * 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 TBX_UTILS_PRINT_H_ +#define TBX_UTILS_PRINT_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tbx { + + /*! + * \class Print + * \brief + * A CRTP print interface + * + * Requirements: + * - size_t write_impl(const Char_t* buffer, size_t size) : Return the number of \c Char_t written + * - size_t write_(const Char_t ch) : Return the number of \c Char_t written (normally one). + * + * \tparam Impl_t The derived type + * \tparam Char_t The char type to use + */ + template + class Print { + _CRTP_IMPL(Impl_t); + + public: + using value_type = Char_t; + using pointer_type = Char_t*; + using iterator_type = Char_t*; + using const_iterator_type = const Char_t*; + using difference_type = std::ptrdiff_t; + using size_type = size_t; + using str_view_t = std::basic_string_view; + + //! Enumerator for number base formating + enum class Base { + BIN =2, OCT =8, DEC =10, HEX =16 + }; + + private: + //! \name CRTP requirements + //! @{ + size_t write_(const Char_t* buffer, size_t size) { + return impl().write_impl(buffer, size); + } + size_t write_(const Char_t ch) { + return impl().write_impl(ch); + } + //! @} + + protected: + Print() noexcept = default; //!< Construct from derived only + + private: + //! Helper tool to convert strong enums to their underlying type + template + constexpr typename std::underlying_type_t value(E e) noexcept { + return static_cast>(e); + } + + size_t print_unsigned(unsigned long n, Base base); // integer conversion base tool + size_t print_double(double number, uint8_t digits); // double conversion base tool + + public: + /*! + * \brief + * Prints a string view + * \param str The string view to print + * \return The number of printed \c Char_t + */ + size_t print(const str_view_t str) { + return write_(str.data(), str.size()); + } + + /*! + * \brief + * Prints a string + * \param str Pointer to string to print + * \return The number of printed \c Char_t + */ + size_t print(const Char_t* str) { + if (str == nullptr) + return 0; + return write_(str, std::strlen(str)); + } + + /*! + * \brief + * Prints a buffer of size \c size. If there is a null termination + * before the end of the buffer, prints up to termination. + * + * \param str Pointer to string buffer to print + * \param size The size of buffer + * \return The number of printed \c Char_t + */ + size_t print(const Char_t* str, size_t size) { + if (str == nullptr) + return 0; + return write_(str, size); + } + + /*! + * \brief + * Prints a \c Char_t + * \param ch The Char_t to print + * \return The number of printed \c Char_t + */ + size_t print(Char_t ch) { + return write_ (ch); + } + + /*! + * \brief + * Convert and print a long. + * \param n The number to print + * \param base The number base to use for conversion. + * \return The number of printed \c Char_t + */ + size_t print(long n, Base base =Base::DEC) { + size_t cnt =0; + if (n < 0) { + n = -n; + cnt = write_ ('-'); + } + return cnt + print_unsigned((unsigned long)n, base); + } + + /*! + * \brief + * Convert and print an int. + * \param n The number to print + * \param base The number base to use for conversion. + * \return The number of printed \c Char_t + */ + size_t print(int n, Base base= Base::DEC) { + return print ((long)n, base); + } + + /*! + * \brief + * Convert and print an unsigned long. + * \param n The number to print + * \param base The number base to use for conversion. + * \return The number of printed \c Char_t + */ + size_t print(unsigned long n, Base base= Base::DEC) { + return print_unsigned ((unsigned long)n, base); + } + + /*! + * \brief + * Convert and print an unsigned int. + * \param n The number to print + * \param base The number base to use for conversion. + * \return The number of printed \c Char_t + */ + size_t print(unsigned int n, Base base= Base::DEC) { + return print_unsigned ((unsigned long)n, base); + } + + /*! + * \brief + * Convert and print adouble + * \param n The number to print + * \param digits The number of decimal digits to print + * \return The number of printed \c Char_t + */ + size_t print(double n, uint8_t digits = 2) { + return print_double (n, digits); + } + + /*! + * \brief + * Perfect forwarder to print functionality with a new line termination + * \tparam Ts The types of parameters + * \param args The arguments to pass + * \return The number of printed \c Char_t + */ + template + size_t println(Ts&& ...args) { + size_t r = print (std::forward(args)...); + r += write_ ('\n'); + return r; + } + }; + + /*! + * \brief + * Converts and prints an unsigned long + * + * \tparam Impl_t The derived type + * \tparam Char_t The char type to use + * + * \param n The number to print + * \param base The number base to use + * \return The number of printed \c Char_t + */ + template + size_t Print::print_unsigned(unsigned long n, Base base) { + Char_t buf[8 *sizeof(Char_t) * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. + Char_t *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + do { + Char_t c = n % value(base); + n /= value(base); + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); + + return write_(str, std::strlen(str)); + } + + /*! + * \brief + * Converts and prints a double + * + * \note + * Internally, this implementation uses a long to store the integer part of the number. + * Thus overflows for numbers bigger than std::numeric_limits::max() / min(). + * For these numbers it prints "ovf" instead. + * + * \tparam Impl_t The derived type + * \tparam Char_t The char type to use + * + * \param n The number to print + * \param digits The number of decimal digits to print + * \return The number of printed \c Char_t + */ + template + size_t Print::print_double(double number, uint8_t digits) { + size_t n = 0; + + if (std::isnan(number)) return print("nan"); + if (std::isinf(number)) return print("inf"); + if (number > (double)std::numeric_limits::max()) + return print ("ovf"); + if (number < (double)std::numeric_limits::min()) + return print ("-ovf"); + + // Handle negative numbers + if (number < 0.0) { + n += write_ ('-'); + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for (uint8_t i=0; i 0) { + n += print('.'); + } + + // Extract digits from the remainder one at a time + while (digits-- > 0) { + remainder *= 10.0; + unsigned int toPrint = (unsigned int)(remainder); + n += print(toPrint); + remainder -= toPrint; + } + + return n; + } + + +} +#endif /* TBX_UTILS_PRINT_H_ */ diff --git a/include/utils/timer_delay.h b/include/utils/timer_delay.h new file mode 100644 index 0000000..9826eca --- /dev/null +++ b/include/utils/timer_delay.h @@ -0,0 +1,333 @@ +/*! + * \file utils/timer_delay.h + * \brief + * A CRTP timer delay utility + * + * \copyright Copyright (C) 2021 Christos Choutouridis + * + *
License
+ * The MIT License (MIT) + * + * 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 TBX_UTILS_TIMER_DELAY_H_ +#define TBX_UTILS_TIMER_DELAY_H_ + +#include +#include + +#include + +namespace tbx { + + /*! + * \class timer_delay + * \brief + * A CRTP hw timer based, delay implementation. + * + * CRTP requirements: + * - int set_frequency_impl (size_t freq, Counter_t ticks) + * Initialize and start the hw timer with tick frequency \c freq and reload value \c ticks + * - volatile Counter_t* get_value_ptr_impl (Counter_t discard) + * Return a pointer to hw timer counter value register. The \c discard argument should discarded. + * + * \tparam Impl_t The derived class + * \tparam Counter_t The hw timer's type + */ + template + class timer_delay { + _CRTP_IMPL(Impl_t); + using value_t = volatile Counter_t; + using marker_t = std::make_signed_t>; + + //! \name CRTP requirements + //! @{ + private: + int set_frequency (size_t freq, Counter_t ticks) { + return impl().set_frequency_impl(freq, ticks); + } + volatile Counter_t* get_value_ptr (Counter_t discard = Counter_t{}) { + return impl().get_value_ptr_impl (discard); + } + //! @} + + //! \name Object lifetime + //! @{ + protected: + //! \brief + //! Create and initialize + //! \param freq The required hw timer's frequency + //! \param ticks The required timer's reload value + timer_delay (size_t freq, Counter_t ticks) { + init (freq, ticks); + } + timer_delay() noexcept = default; //!< Default object is valid, but non-usable + timer_delay(const timer_delay&) = delete; //!< No copies + timer_delay& operator=(const timer_delay&) = delete; //!< No copies + //! \note + //! We are not initializing the timer via default ctor, in order to be able to + //! declare a timer_delay object globally and initialize it after the call to main(). + //! @} + + private: + //! Period to frequency compile time tool + constexpr Counter_t period2freq (double period) noexcept { + return (Counter_t)(1 / period); + } + + /*! + * \brief Return the systems best approximation for ticks per msec + * \return The calculated value or zero if no calculation can apply + */ + Counter_t ticks_per_msec () { + Counter_t tck = (Counter_t)(frequency / period2freq(0.001)); + return (tck <= 1) ? 1 : tck; + } + + /*! + * \brief Return the systems best approximation for ticks per usec + * \return The calculated value or zero if no calculation can apply + */ + Counter_t ticks_per_usec () { + Counter_t tck = (Counter_t)(frequency / period2freq(0.000001)); + return (tck <= 1) ? 1 : tck; + } + + /*! + * \brief Return the systems best approximation for ticks per usec + * \return The calculated value or zero if no calculation can apply + */ + Counter_t ticks_per_100nsec () { + Counter_t tck = (Counter_t)(frequency / period2freq(0.0000001)); + return (tck <= 1) ? 1 : tck; + } + + public: + /*! + * \brief + * Initializes both object members and hw timer. + * + * \param freq The required hw timer's frequency + * \param ticks The required timer's reload value + * \return + */ + bool init (size_t freq, Counter_t ticks) { + if (set_frequency(freq, ticks)) + return false; + volatile Counter_t* v = get_value_ptr(); + + value = (v != nullptr) ? v : value; + frequency = freq; + max_ticks = ticks; + tp1ms = ticks_per_msec(); + tp1us = ticks_per_usec(); + tp100ns= ticks_per_100nsec(); + return true; + } + + /*! + * \brief + * A code based delay implementation, using hw timer for timing. + * This is NOT accurate but it ensures that the time passed is always + * more than the requested value. + * The delay values are multiplications of 1 msec. + * \param msec Time in msec for delay + */ + void delay_ms (int msec) { + marker_t m, m2, m1 = (marker_t)*value; + + msec *= tp1ms; + + // Eat the time difference from msec value. + do { + m2 = (marker_t)(*value); + m = m2 - m1; + msec -= (m>=0) ? m : max_ticks + m; + m1 = m2; + } while (msec>0); + } + + /*! + * \brief + * A code based delay implementation, using hw timer for timing. + * This is NOT accurate but it ensures that the time passed is always + * more than the requested value. + * The delay values are multiplications of 1 usec. + * \param usec Time in usec for delay + */ + void delay_us (int usec) { + marker_t m, m2, m1 = (marker_t)*value; + + usec *= tp1us; + if ((marker_t)(*value) - m1 > usec) // Very small delays may return here. + return; + + // Eat the time difference from usec value. + do { + m2 = (marker_t)(*value); + m = m2 - m1; + usec -= (m>=0) ? m : max_ticks + m; + m1 = m2; + } while (usec>0); + } + + /*! + * \brief + * A code based delay implementation, using hw timer for timing. + * This is NOT accurate but it ensures that the time passed is always + * more than the requested value. + * The delay values are multiplications of 100 nsec. + * \param _100nsec Time in 100nsec for delay + */ + void delay_100ns (int _100nsec) { + marker_t m, m2, m1 = (marker_t)*value; + + _100nsec *= tp100ns; + if ((marker_t)(*value) - m1 > _100nsec) // Very small delays may return here. + return; + + // Eat the time difference from _100nsec value. + do { + m2 = (marker_t)(*value); + m = m2 - m1; + _100nsec -= (m>=0) ? m : max_ticks + m; + m1 = m2; + } while (_100nsec>0); + } + + /*! + * \brief + * A code based polling version delay implementation, using hw timer for timing. + * This is NOT accurate but it ensures that the time passed is always + * more than the requested value. + * The delay values are multiplications of 1 msec. + * \param msec Time in msec for delay + * \return The status of ongoing delay + * \arg false: Delay time has passed + * \arg true: Delay is ongoing, keep calling + */ + bool check_msec (int msec) { + static marker_t m1=-1, cnt; + marker_t m, m2; + + if (m1 == -1) { + m1 = *value; + cnt = tp1ms * msec; + } + + // Eat the time difference from msec value. + if (cnt>0) { + m2 = (marker_t)(*value); + m = m2-m1; + cnt -= (m>=0) ? m : max_ticks + m; + m1 = m2; + return 1; // wait + } else { + m1 = -1; + return 0; // do not wait any more + } + } + + /*! + * \brief + * A code based polling version delay implementation, using hw timer for timing. + * This is NOT accurate but it ensures that the time passed is always + * more than the requested value. + * The delay values are multiplications of 1 usec. + * \param usec Time in usec for delay + * \return The status of ongoing delay + * \arg false: Delay time has passed + * \arg true: Delay is ongoing, keep calling + */ + bool check_usec (int usec) { + static marker_t m1=-1, cnt; + marker_t m, m2; + + if (m1 == -1) { + m1 = *value; + cnt = tp1us * usec; + } + + // Eat the time difference from usec value. + if (cnt>0) { + m2 = (marker_t)(*value); + m = m2-m1; + cnt -= (m>=0) ? m : max_ticks + m; + m1 = m2; + return 1; // wait + } else { + m1 = -1; + return 0; // do not wait any more + } + } + + + /*! + * \brief + * A code based polling version delay implementation, using hw timer for timing. + * This is NOT accurate but it ensures that the time passed is always + * more than the requested value. + * The delay values are multiplications of 100 nsec. + * \param + * _100nsec Time in 100nsec for delay + * \return The status of ongoing delay + * \arg false: Delay time has passed + * \arg true: Delay is ongoing, keep calling + */ + bool check_100nsec (int _100nsec) { + static marker_t m1=-1, cnt; + marker_t m, m2; + + if (m1 == -1) { + m1 = *value; + cnt = tp100ns * _100nsec; + } + + // Eat the time difference from _100nsec value. + if (cnt>0) { + m2 = (marker_t)(*value); + m = m2-m1; + cnt -= (m>=0) ? m : max_ticks + m; + m1 = m2; + return 1; // wait + } + else { + m1 = -1; + return 0; // do not wait any more + } + } + + private: + static constexpr Counter_t zero = 0; //!< A always zero place + + value_t* value {(value_t*)&zero}; //!< Pointer to hw timer's counter register + //!< We initialize it to &zero to avoid nullptr dereference + size_t frequency{}; //!< The frequency of the timer + Counter_t max_ticks{}; //!< The reload value of the timer + Counter_t tp1ms{}; //!< ticks per ms temporary variable + Counter_t tp1us{}; //!< ticks per us temporary variable + Counter_t tp100ns{}; //!< ticks per 100ns temporary variable + }; + + +} // namespace tbx + +#endif /* TBX_UTILS_TIMER_DELAY_H_ */