/** * Copyright (c) 2011-2022 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License * * 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 RingBuf_h #define RingBuf_h /** * \file * \brief Ring buffer for data loggers. */ #include "common/SysCall.h" #include "common/FmtNumber.h" #ifndef DOXYGEN_SHOULD_SKIP_THIS // Teensy 3.5/3.6 has hard fault at 0x20000000 for unaligned memcpy. #if defined(__MK64FX512__) || defined(__MK66FX1M0__) inline bool is_aligned(const void* ptr, uintptr_t alignment) { auto iptr = reinterpret_cast(ptr); return !(iptr % alignment); } inline void memcpyBuf(void* dst, const void* src, size_t len) { const uint8_t* b = reinterpret_cast(0X20000000UL); uint8_t* d = reinterpret_cast(dst); const uint8_t *s = reinterpret_cast(src); if ((is_aligned(d, 4) && is_aligned(s, 4) && (len & 3) == 0) || !((d < b && b <= (d + len)) || (s < b && b <= (s + len)))) { memcpy(dst, src, len); } else { while (len--) { *d++ = *s++; } } } #else // defined(__MK64FX512__) || defined(__MK66FX1M0__) inline void memcpyBuf(void* dst, const void* src, size_t len) { memcpy(dst, src, len); } #endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) #endif // DOXYGEN_SHOULD_SKIP_THIS /** * \class RingBuf * \brief Ring buffer for data loggers. * * This ring buffer may be used in ISRs. bytesFreeIsr(), bytesUsedIsr(), * memcopyIn(), and memcopyOut() are ISR callable. For ISR use call * memcopyIn() in the ISR and use writeOut() in non-interrupt code * to write data to a file. readIn() and memcopyOut can be use in a * similar way to provide file data to an ISR. * * Print into a RingBuf in an ISR should also work but has not been verified. */ template class RingBuf : public Print { public: /** * RingBuf Constructor. */ RingBuf() {} /** * Initialize RingBuf. * \param[in] file Underlying file. */ void begin(F* file) { m_file = file; m_count = 0; m_head = 0; m_tail = 0; clearWriteError(); } /** * * \return the RingBuf free space in bytes. Not ISR callable. */ size_t bytesFree() const { size_t count; noInterrupts(); count = m_count; interrupts(); return Size - count; } /** * \return the RingBuf free space in bytes. ISR callable. */ size_t bytesFreeIsr() const { return Size - m_count; } /** * \return the RingBuf used space in bytes. Not ISR callable. */ size_t bytesUsed() const { size_t count; noInterrupts(); count = m_count; interrupts(); return count; } /** * \return the RingBuf used space in bytes. ISR callable. */ size_t bytesUsedIsr() const { return m_count; } /** * Copy data to the RingBuf from buf. * The number of bytes copied may be less than count if * count is greater than bytesFree. * * This function may be used in an ISR with writeOut() * in non-interrupt code. * * \param[in] buf Location of data to be copied. * \param[in] count number of bytes to be copied. * \return Number of bytes actually copied. */ size_t memcpyIn(const void* buf, size_t count) { const uint8_t* src = (const uint8_t*)buf; size_t n = Size - m_count; if (count > n) { count = n; } size_t nread = 0; while (nread != count) { n = minSize(Size - m_head, count - nread); memcpyBuf(m_buf + m_head, src + nread, n); m_head = advance(m_head, n); nread += n; } m_count += nread; return nread; } /** * Copy date from the RingBuf to buf. * The number of bytes copied may be less than count if * bytesUsed is less than count. * * This function may be used in an ISR with readIn() in * non-interrupt code. * * \param[out] buf Location to receive the data. * \param[in] count number of bytes to be copied. * \return Number of bytes actually copied. */ size_t memcpyOut(void* buf, size_t count) { uint8_t* dst = reinterpret_cast(buf); size_t nwrite = 0; size_t n = m_count; if (count > n) { count = n; } while (nwrite != count) { n = minSize(Size - m_tail, count - nwrite); memcpyBuf(dst + nwrite, m_buf + m_tail, n); m_tail = advance(m_tail, n); nwrite += n; } m_count -= nwrite; return nwrite; } /** Print a number followed by a field terminator. * \param[in] value The number to be printed. * \param[in] term The field terminator. Use '\\n' for CR LF. * \param[in] prec Number of digits after decimal point. * \return The number of bytes written. */ size_t printField(double value, char term, uint8_t prec = 2) { char buf[24]; char* str = buf + sizeof(buf); if (term) { *--str = term; if (term == '\n') { *--str = '\r'; } } str = fmtDouble(str, value, prec, false); return write(str, buf + sizeof(buf) - str); } /** Print a number followed by a field terminator. * \param[in] value The number to be printed. * \param[in] term The field terminator. Use '\\n' for CR LF. * \param[in] prec Number of digits after decimal point. * \return The number of bytes written or -1 if an error occurs. */ size_t printField(float value, char term, uint8_t prec = 2) { return printField(static_cast(value), term, prec); } /** Print a number followed by a field terminator. * \param[in] value The number to be printed. * \param[in] term The field terminator. Use '\\n' for CR LF. * \return The number of bytes written or -1 if an error occurs. */ template size_t printField(Type value, char term) { char sign = 0; char buf[3*sizeof(Type) + 3]; char* str = buf + sizeof(buf); if (term) { *--str = term; if (term == '\n') { *--str = '\r'; } } if (value < 0) { value = -value; sign = '-'; } if (sizeof(Type) < 4) { str = fmtBase10(str, (uint16_t)value); } else { str = fmtBase10(str, (uint32_t)value); } if (sign) { *--str = sign; } return write((const uint8_t*)str, &buf[sizeof(buf)] - str); } /** * Read data into the RingBuf from the underlying file. * the number of bytes read may be less than count if * bytesFree is less than count. * * This function may be used in non-interrupt code with * memcopyOut() in an ISR. * * \param[in] count number of bytes to be read. * \return Number of bytes actually read. */ size_t readIn(size_t count) { size_t nread = 0; size_t n = bytesFree(); // Protected from interrupts. if (count > n) { count = n; } while (nread != count) { n = minSize(Size - m_head, count - nread); if ((size_t)m_file->read(m_buf + m_head, n) != n) { return nread; } m_head = advance(m_head, n); nread += n; } noInterrupts(); m_count += nread; interrupts(); return nread; } /** * Write all data in the RingBuf to the underlying file. * \return true for success. */ bool sync() { size_t n = bytesUsed(); return writeOut(n) == n; } /** * Copy data to the RingBuf from buf. * * The number of bytes copied may be less than count if * count is greater than bytesFree. * Use getWriteError() to check for print errors and * clearWriteError() to clear error. * * \param[in] buf Location of data to be written. * \param[in] count number of bytes to be written. * \return Number of bytes actually written. */ size_t write(const void* buf, size_t count) { if (count > bytesFree()) { setWriteError(); } return memcpyIn(buf, count); } /** * Copy str to RingBuf. * * \param[in] str Location of data to be written. * \return Number of bytes actually written. */ size_t write(const char* str) { return Print::write(str); } /** * Override virtual function in Print for efficiency. * * \param[in] buf Location of data to be written. * \param[in] count number of bytes to be written. * \return Number of bytes actually written. */ size_t write(const uint8_t* buf, size_t count) override { return write((const void*)buf, count); } /** * Required function for Print. * \param[in] data Byte to be written. * \return Number of bytes actually written. */ size_t write(uint8_t data) override { return write(&data, 1); } /** * Write data to file from RingBuf buffer. * \param[in] count number of bytes to be written. * * The number of bytes written may be less than count if * bytesUsed is less than count or if an error occurs. * * This function may be used in non-interrupt code with * memcopyIn() in an ISR. * * \return Number of bytes actually written. */ size_t writeOut(size_t count) { size_t n = bytesUsed(); // Protected from interrupts; if (count > n) { count = n; } size_t nwrite = 0; while (nwrite != count) { n = minSize(Size - m_tail, count - nwrite); if (m_file->write(m_buf + m_tail, n) != n) { break; } m_tail = advance(m_tail, n); nwrite += n; } noInterrupts(); m_count -= nwrite; interrupts(); return nwrite; } private: uint8_t __attribute__((aligned(4))) m_buf[Size]; F* m_file = nullptr; volatile size_t m_count; size_t m_head; size_t m_tail; size_t advance(size_t index, size_t n) { index += n; return index < Size ? index : index - Size; } // avoid macro MIN size_t minSize(size_t a, size_t b) {return a < b ? a : b;} }; #endif // RingBuf_h