From 48edfe1a9f98062e5d5ef0332bf87ae2c1a31f47 Mon Sep 17 00:00:00 2001 From: Jerome Hue Date: Sun, 19 May 2024 07:19:27 +0200 Subject: [PATCH 01/15] Add a Fram ringbuffer and associated test The logic of the Fram ringbuffer is based on the ETL circular buffer. It offers the following methods : Front(), Back(), Push(), Capacity(), Size(), and the bracket operator for element access. Its usage is demonstrated in the FramRinfBuffer.test.cpp unit test. --- Sts1CobcSw/Edu/ProgramStatusHistory.cpp | 32 +++ Sts1CobcSw/Edu/ProgramStatusHistory.hpp | 8 + Sts1CobcSw/Periphery/FramRingBuffer.hpp | 47 +++++ Sts1CobcSw/Periphery/FramRingBuffer.ipp | 89 ++++++++ Tests/GoldenTests/CMakeLists.txt | 2 - Tests/GoldenTests/UpdateRingBuffer.test.cpp | 107 ---------- Tests/UnitTests/CMakeLists.txt | 6 + Tests/UnitTests/FramRingBuffer.test.cpp | 219 ++++++++++++++++++++ iwyu.imp | 3 +- 9 files changed, 403 insertions(+), 110 deletions(-) create mode 100644 Sts1CobcSw/Periphery/FramRingBuffer.hpp create mode 100644 Sts1CobcSw/Periphery/FramRingBuffer.ipp delete mode 100644 Tests/GoldenTests/UpdateRingBuffer.test.cpp create mode 100644 Tests/UnitTests/FramRingBuffer.test.cpp diff --git a/Sts1CobcSw/Edu/ProgramStatusHistory.cpp b/Sts1CobcSw/Edu/ProgramStatusHistory.cpp index 0a6f75c2..5c7cfca5 100644 --- a/Sts1CobcSw/Edu/ProgramStatusHistory.cpp +++ b/Sts1CobcSw/Edu/ProgramStatusHistory.cpp @@ -22,4 +22,36 @@ auto UpdateProgramStatusHistory(ProgramId programId, RealTime startTime, Program } } } + + +using sts1cobcsw::DeserializeFrom; +using sts1cobcsw::SerializeTo; + + +template +auto DeserializeFrom(void const * source, ProgramStatusHistoryEntry * data) -> void const * +{ + source = DeserializeFrom(source, &(data->programId)); + source = DeserializeFrom(source, &(data->startTime)); + source = DeserializeFrom(source, &(data->status)); + return source; +} + + +template +auto SerializeTo(void * destination, ProgramStatusHistoryEntry const & data) -> void * +{ + destination = SerializeTo(destination, data.programId); + destination = SerializeTo(destination, data.startTime); + destination = SerializeTo(destination, data.status); + return destination; +} + + +template auto DeserializeFrom(void const *, ProgramStatusHistoryEntry *) + -> void const *; +template auto DeserializeFrom(void const *, ProgramStatusHistoryEntry *) + -> void const *; +template auto SerializeTo(void *, ProgramStatusHistoryEntry const &) -> void *; +template auto SerializeTo(void *, ProgramStatusHistoryEntry const &) -> void *; } diff --git a/Sts1CobcSw/Edu/ProgramStatusHistory.hpp b/Sts1CobcSw/Edu/ProgramStatusHistory.hpp index a378caee..4098dd43 100644 --- a/Sts1CobcSw/Edu/ProgramStatusHistory.hpp +++ b/Sts1CobcSw/Edu/ProgramStatusHistory.hpp @@ -16,6 +16,7 @@ #include // clang-format on +#include #include @@ -64,5 +65,12 @@ extern RODOS::RingBuffer void; + +template +[[nodiscard]] auto DeserializeFrom(void const * source, ProgramStatusHistoryEntry * data) + -> void const *; +template +[[nodiscard]] auto SerializeTo(void * destination, ProgramStatusHistoryEntry const & data) + -> void *; } } diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.hpp b/Sts1CobcSw/Periphery/FramRingBuffer.hpp new file mode 100644 index 00000000..b1cfe902 --- /dev/null +++ b/Sts1CobcSw/Periphery/FramRingBuffer.hpp @@ -0,0 +1,47 @@ +#pragma once + + +#include +#include +#include + +#include + +#include +#include +#include + + +namespace sts1cobcsw::fram +{ +template +class RingBuffer +{ +public: + RingBuffer() + : bufferSize_(size + 1U){ + + }; + + auto Push(T const & newData) -> void; + + auto operator[](std::size_t index) -> T; + + auto Front() -> T; + auto Back() -> T; + + //! @brief Returns the current size of the ringbuffer + auto Size() -> std::size_t; + + //! @brief Returns the capacity of the ringbuffer + auto Capacity() -> std::size_t; + +private: + std::uint32_t nextWriteIndex_ = 0; + std::uint32_t nextReadIndex_ = 0; + std::size_t bufferSize_; +}; +} + + +#include // IWYU pragma: keep diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.ipp b/Sts1CobcSw/Periphery/FramRingBuffer.ipp new file mode 100644 index 00000000..b53cca21 --- /dev/null +++ b/Sts1CobcSw/Periphery/FramRingBuffer.ipp @@ -0,0 +1,89 @@ +#pragma once + + +#include + + +namespace sts1cobcsw::fram +{ +template +void RingBuffer::Push(T const & newData) +{ + auto const address = startAddress + nextWriteIndex_ * serialSize; + fram::WriteTo(address, Span(Serialize(newData)), 0); + + + ++nextWriteIndex_; + if(nextWriteIndex_ == bufferSize_) + { + nextWriteIndex_ = 0; + } + + if(nextWriteIndex_ == nextReadIndex_) + { + ++nextReadIndex_; + if(nextReadIndex_ == bufferSize_) + { + nextReadIndex_ = 0; + } + } +} + + +template +auto RingBuffer::Front() -> T +{ + auto const address = startAddress + nextReadIndex_ * serialSize; + auto readData = fram::ReadFrom>(address, 0); + auto fromRing = Deserialize(std::span(readData)); + + return fromRing; +} + + +template +auto RingBuffer::Back() -> T +{ + std::uint32_t readIndex = 0; + if(nextWriteIndex_ == 0) + { + readIndex = bufferSize_ - 1; + } + else + { + readIndex = nextWriteIndex_ - 1; + } + + auto const address = startAddress + readIndex * serialSize; + auto readData = fram::ReadFrom>(address, 0); + auto fromRing = Deserialize(std::span(readData)); + + return fromRing; +} + + +template +auto RingBuffer::operator[](std::size_t index) -> T +{ + auto const address = startAddress + ((nextReadIndex_ + index) % bufferSize_) * serialSize; + auto readData = fram::ReadFrom>(address, 0); + return Deserialize(std::span(readData)); +} + + +template +auto RingBuffer::Size() -> std::size_t +{ + if(nextWriteIndex_ >= nextReadIndex_) + { + return (nextWriteIndex_ - nextReadIndex_); + } + return (bufferSize_ - (nextReadIndex_ - nextWriteIndex_)); +} + +template +auto RingBuffer::Capacity() -> std::size_t +{ + return (bufferSize_ - 1U); +} +} diff --git a/Tests/GoldenTests/CMakeLists.txt b/Tests/GoldenTests/CMakeLists.txt index ac92f597..1c4c2b61 100644 --- a/Tests/GoldenTests/CMakeLists.txt +++ b/Tests/GoldenTests/CMakeLists.txt @@ -9,6 +9,4 @@ add_golden_test(TESTFILE "HelloWorld.test.cpp" LIB rodos::rodos) add_golden_test(TESTFILE "HelloDummy.test.cpp" LIB rodos::rodos etl::etl Sts1CobcSw_Dummy) -add_golden_test(TESTFILE "UpdateRingBuffer.test.cpp" LIB rodos::rodos Sts1CobcSw_Edu) - add_custom_target(AllGoldenTests DEPENDS ${output_files}) diff --git a/Tests/GoldenTests/UpdateRingBuffer.test.cpp b/Tests/GoldenTests/UpdateRingBuffer.test.cpp deleted file mode 100644 index 7accc36c..00000000 --- a/Tests/GoldenTests/UpdateRingBuffer.test.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include -#include - -#include - -// clang-format off -#include -// ringbuffer.h does not include even though it requires it -#include -// clang-format on -#include - -#include - - -std::uint32_t printfMask = 0; - - -namespace sts1cobcsw -{ -auto ToString(edu::ProgramStatus status) -> std::string_view -{ - switch(status) - { - case edu::ProgramStatus::programRunning: - return "programRunning"; - case edu::ProgramStatus::programExecutionFailed: - return "programExecutionFailed"; - case edu::ProgramStatus::programExecutionSucceeded: - return "programExecutionSucceeded"; - default: - return ""; - } -} - - -// Helper function for edu::programStatusHistory -void PrintBuffer() -{ - for(std::uint32_t i = 0; i < edu::programStatusHistory.occupiedCnt; ++i) - { - RODOS::PRINTF("Vals[%d] = .id(%d), .status(%s)\n", - i, - value_of(edu::programStatusHistory.vals[i].programId), - ToString(edu::programStatusHistory.vals[i].status).data()); - } -} - - -class UpdateRingBufferTest : public RODOS::StaticThread<> -{ - void run() override - { - printfMask = 1; - - edu::programStatusHistory.put( - edu::ProgramStatusHistoryEntry{.programId = ProgramId(1), - .startTime = RealTime(1), - .status = edu::ProgramStatus::programExecutionFailed}); - edu::programStatusHistory.put( - edu::ProgramStatusHistoryEntry{.programId = ProgramId(2), - .startTime = RealTime(1), - .status = edu::ProgramStatus::programRunning}); - edu::programStatusHistory.put( - edu::ProgramStatusHistoryEntry{.programId = ProgramId(3), - .startTime = RealTime(1), - .status = edu::ProgramStatus::programRunning}); - edu::programStatusHistory.put( - edu::ProgramStatusHistoryEntry{.programId = ProgramId(4), - .startTime = RealTime(1), - .status = edu::ProgramStatus::programRunning}); - - - auto readCnt = edu::programStatusHistory.readCnt; - auto writeCnt = edu::programStatusHistory.writeCnt; - auto occupiedCnt = edu::programStatusHistory.occupiedCnt; - - PrintBuffer(); - - edu::UpdateProgramStatusHistory( - ProgramId(2), RealTime(1), edu::ProgramStatus::programExecutionSucceeded); - edu::UpdateProgramStatusHistory( - ProgramId(4), RealTime(1), edu::ProgramStatus::programExecutionFailed); - edu::programStatusHistory.put( - edu::ProgramStatusHistoryEntry{.programId = ProgramId(5), - .startTime = RealTime(1), - .status = edu::ProgramStatus::programRunning}); - edu::UpdateProgramStatusHistory( - ProgramId(5), RealTime(1), edu::ProgramStatus::programExecutionSucceeded); - - // 1, because we did not read anything - RODOS::PRINTF("readCnt unchanged : %d\n", - static_cast(edu::programStatusHistory.readCnt == readCnt)); - // 0, because we did write - RODOS::PRINTF("writeCnt unchanged : %d\n", - static_cast(edu::programStatusHistory.readCnt == writeCnt)); - // 0 - RODOS::PRINTF("OccupiedCnt unchanged : %d\n", - static_cast(edu::programStatusHistory.occupiedCnt == occupiedCnt)); - - PrintBuffer(); - - RODOS::hwResetAndReboot(); - } -} updateRingBufferTest; -} diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index f1666690..e87d875c 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -14,6 +14,12 @@ target_link_libraries( Sts1CobcSw_Utility ) +add_program(FramRingBuffer FramRingBuffer.test.cpp) +target_link_libraries( + Sts1CobcSwTests_FramRingBuffer PRIVATE Catch2::Catch2WithMain Sts1CobcSw_Periphery + Sts1CobcSw_Serial Sts1CobcSw_Edu +) + add_program(LfsRam LfsRam.test.cpp) target_link_libraries(Sts1CobcSwTests_LfsRam PRIVATE Catch2::Catch2WithMain Sts1CobcSw_FileSystem) diff --git a/Tests/UnitTests/FramRingBuffer.test.cpp b/Tests/UnitTests/FramRingBuffer.test.cpp new file mode 100644 index 00000000..0f8e407d --- /dev/null +++ b/Tests/UnitTests/FramRingBuffer.test.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +namespace fram = sts1cobcsw::fram; + +using sts1cobcsw::operator""_b; // NOLINT(misc-unused-using-decls) + +TEST_CASE("Initial State ringbuffer") +{ + fram::RingBuffer buffer; + + REQUIRE(buffer.Size() == 0); + REQUIRE(buffer.Capacity() == 10); // Fixed from 0 to 10 to reflect actual buffer capacity +} + +TEST_CASE("FramRingBuffer Push function") +{ + fram::RingBuffer buffer; + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + + REQUIRE(buffer.Size() == 3); +} + +TEMPLATE_TEST_CASE_SIG("FramRingBuffer Front Address", + "", + ((typename T, size_t S, fram::Address A), T, S, A), + (int, 10, fram::Address{0}), + (int, 10, fram::Address{31415})) +{ + fram::ram::SetAllDoFunctions(); + fram::ram::storage.fill(0x00_b); + fram::Initialize(); + + fram::RingBuffer buffer; + + buffer.Push(10); + buffer.Push(20); + buffer.Push(30); + + REQUIRE(buffer[0] == 10); + REQUIRE(buffer[1] == 20); + REQUIRE(buffer[2] == 30); +} + +TEST_CASE("FramRingBuffer Back() and Front() methods") +{ + fram::ram::SetAllDoFunctions(); + fram::ram::storage.fill(0x00_b); + fram::Initialize(); + + fram::RingBuffer buffer; + etl::circular_buffer etlBuffer; + + // NOLINTNEXTLINE (readability-container-size-empty) + CHECK(etlBuffer.size() == 0); + CHECK(etlBuffer.max_size() == 5); + CHECK(etlBuffer.capacity() == 5); + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + + etlBuffer.push(1); + etlBuffer.push(2); + etlBuffer.push(3); + + REQUIRE(etlBuffer.front() == 1); + REQUIRE(etlBuffer.back() == 3); + REQUIRE(etlBuffer.size() == 3); + REQUIRE(etlBuffer.capacity() == 5); + + REQUIRE(buffer.Front() == 1); + REQUIRE(buffer.Back() == 3); + REQUIRE(buffer.Size() == 3); + REQUIRE(buffer.Capacity() == 5); + + etlBuffer.push(4); + etlBuffer.push(5); + // Overwrite + etlBuffer.push(6); + + buffer.Push(4); + buffer.Push(5); + buffer.Push(6); + + REQUIRE(etlBuffer.front() == 2); + REQUIRE(buffer.Front() == 2); + + REQUIRE(etlBuffer.back() == 6); + REQUIRE(buffer.Back() == 6); + + REQUIRE(etlBuffer.size() == 5); + REQUIRE(etlBuffer.capacity() == 5); + REQUIRE(etlBuffer[0] == 2); + REQUIRE(etlBuffer[1] == 3); + REQUIRE(etlBuffer[2] == 4); + REQUIRE(buffer[0] == 2); + REQUIRE(buffer[1] == 3); + REQUIRE(buffer[2] == 4); +} + +TEST_CASE("FramRingBuffer Full and Empty conditions") +{ + fram::ram::SetAllDoFunctions(); + fram::ram::storage.fill(0x00_b); + fram::Initialize(); + + fram::RingBuffer buffer; + + REQUIRE(buffer.Size() == 0); + REQUIRE(buffer.Capacity() == 3); + + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + + REQUIRE(buffer.Size() == 3); + REQUIRE(buffer.Front() == 1); + REQUIRE(buffer.Back() == 3); + + buffer.Push(4); // Overwrite the oldest element + + REQUIRE(buffer.Size() == 3); + REQUIRE(buffer.Front() == 2); + REQUIRE(buffer.Back() == 4); + + buffer.Push(10); + + REQUIRE(buffer.Size() == 3); + REQUIRE(buffer.Front() == 3); + REQUIRE(buffer.Back() == 10); +} + +TEST_CASE("FramRingBuffer and ETL Circular Buffer") +{ + fram::ram::SetAllDoFunctions(); + fram::ram::storage.fill(0x00_b); + fram::Initialize(); + + fram::RingBuffer framBuffer; + etl::circular_buffer etlBuffer; + + for (int i = 0; i < 5; ++i) + { + framBuffer.Push(i); + etlBuffer.push(i); + } + + REQUIRE(framBuffer.Size() == etlBuffer.size()); + for (size_t i = 0; i < framBuffer.Size(); ++i) + { + REQUIRE(framBuffer[i] == etlBuffer[i]); + } + + framBuffer.Push(5); + etlBuffer.push(5); + + REQUIRE(framBuffer.Front() == etlBuffer.front()); + REQUIRE(framBuffer.Back() == etlBuffer.back()); +} + +TEST_CASE("FramRingBuffer Stress Test") +{ + fram::ram::SetAllDoFunctions(); + fram::ram::storage.fill(0x00_b); + fram::Initialize(); + + fram::RingBuffer buffer; + + for (int i = 0; i < 10000; ++i) + { + buffer.Push(i); + } + + REQUIRE(buffer.Size() == 10000); + + for (size_t i = 0; i < 10000; ++i) + { + REQUIRE(buffer[i] == static_cast(i)); + } +} + +TEST_CASE("Custom Type") +{ + fram::ram::SetAllDoFunctions(); + fram::ram::storage.fill(0x00_b); + fram::Initialize(); + + + fram::RingBuffer buffer; + + auto pshEntry = sts1cobcsw::edu::ProgramStatusHistoryEntry{ + .programId = sts1cobcsw::ProgramId(0), + .startTime = 0, + .status = sts1cobcsw::edu::ProgramStatus::programRunning}; + + // some things are wrong with the Serialize Functions here + buffer.Push(pshEntry); + + REQUIRE(buffer.Size() == 1); + REQUIRE(buffer.Front().status == sts1cobcsw::edu::ProgramStatus::programRunning); +} diff --git a/iwyu.imp b/iwyu.imp index 032b215b..b4486367 100644 --- a/iwyu.imp +++ b/iwyu.imp @@ -47,12 +47,13 @@ # Instead of all our .ipp files include the .hpp ones { include: ["\"Sts1CobcSw/Edu/Types.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/FileSystem/FileSystem.ipp\"", "private", "", "public"] }, - { include: ["\"Sts1CobcSw/FramSections/Subsections.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/FramSections/PersistentVariables.ipp\"", "private", "", "public"] }, + { include: ["\"Sts1CobcSw/FramSections/Subsections.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Hal/GpioPin.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Hal/Spi.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Hal/Uart.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Periphery/Fram.ipp\"", "private", "", "public"] }, + { include: ["\"Sts1CobcSw/Periphery/FramRingBuffer.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/ProgramId/ProgramId.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Serial/Byte.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Serial/Serial.ipp\"", "private", "", "public"] }, From 4ac37805fec4d5e0d475f863df22fa2e449259b7 Mon Sep 17 00:00:00 2001 From: Jerome Hue Date: Wed, 31 Jul 2024 20:08:59 +0200 Subject: [PATCH 02/15] Fix spelling and formatting errors --- .codespell-ignore | 1 + Tests/UnitTests/FramRingBuffer.test.cpp | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.codespell-ignore b/.codespell-ignore index 703c336f..682c3983 100644 --- a/.codespell-ignore +++ b/.codespell-ignore @@ -1,3 +1,4 @@ tust fram framlayout +framBuffer diff --git a/Tests/UnitTests/FramRingBuffer.test.cpp b/Tests/UnitTests/FramRingBuffer.test.cpp index 0f8e407d..aa632278 100644 --- a/Tests/UnitTests/FramRingBuffer.test.cpp +++ b/Tests/UnitTests/FramRingBuffer.test.cpp @@ -23,7 +23,7 @@ TEST_CASE("Initial State ringbuffer") fram::RingBuffer buffer; REQUIRE(buffer.Size() == 0); - REQUIRE(buffer.Capacity() == 10); // Fixed from 0 to 10 to reflect actual buffer capacity + REQUIRE(buffer.Capacity() == 10); // Fixed from 0 to 10 to reflect actual buffer capacity } TEST_CASE("FramRingBuffer Push function") @@ -156,14 +156,14 @@ TEST_CASE("FramRingBuffer and ETL Circular Buffer") fram::RingBuffer framBuffer; etl::circular_buffer etlBuffer; - for (int i = 0; i < 5; ++i) + for(int i = 0; i < 5; ++i) { framBuffer.Push(i); etlBuffer.push(i); } REQUIRE(framBuffer.Size() == etlBuffer.size()); - for (size_t i = 0; i < framBuffer.Size(); ++i) + for(size_t i = 0; i < framBuffer.Size(); ++i) { REQUIRE(framBuffer[i] == etlBuffer[i]); } @@ -183,14 +183,14 @@ TEST_CASE("FramRingBuffer Stress Test") fram::RingBuffer buffer; - for (int i = 0; i < 10000; ++i) + for(int i = 0; i < 10000; ++i) { buffer.Push(i); } REQUIRE(buffer.Size() == 10000); - for (size_t i = 0; i < 10000; ++i) + for(size_t i = 0; i < 10000; ++i) { REQUIRE(buffer[i] == static_cast(i)); } @@ -203,8 +203,7 @@ TEST_CASE("Custom Type") fram::Initialize(); - fram::RingBuffer buffer; + fram::RingBuffer buffer; auto pshEntry = sts1cobcsw::edu::ProgramStatusHistoryEntry{ .programId = sts1cobcsw::ProgramId(0), From cbd1ebbea8efcd6a13da81d302008bc3aa2944cd Mon Sep 17 00:00:00 2001 From: Jerome Hue Date: Wed, 31 Jul 2024 22:09:59 +0200 Subject: [PATCH 03/15] Take rebase into account --- Sts1CobcSw/Periphery/FramRingBuffer.hpp | 1 + Sts1CobcSw/Periphery/FramRingBuffer.ipp | 18 +++++------ Tests/UnitTests/FramRingBuffer.test.cpp | 42 ++++++++++++++----------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.hpp b/Sts1CobcSw/Periphery/FramRingBuffer.hpp index b1cfe902..735b3d96 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.hpp +++ b/Sts1CobcSw/Periphery/FramRingBuffer.hpp @@ -36,6 +36,7 @@ class RingBuffer //! @brief Returns the capacity of the ringbuffer auto Capacity() -> std::size_t; + private: std::uint32_t nextWriteIndex_ = 0; std::uint32_t nextReadIndex_ = 0; diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.ipp b/Sts1CobcSw/Periphery/FramRingBuffer.ipp index b53cca21..55de4e0c 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.ipp +++ b/Sts1CobcSw/Periphery/FramRingBuffer.ipp @@ -9,9 +9,8 @@ namespace sts1cobcsw::fram template void RingBuffer::Push(T const & newData) { - auto const address = startAddress + nextWriteIndex_ * serialSize; - fram::WriteTo(address, Span(Serialize(newData)), 0); - + auto const rawaddress = value_of(startAddress) + (nextWriteIndex_ * serialSize); + fram::WriteTo(fram::Address(rawaddress), Span(Serialize(newData)), 0); ++nextWriteIndex_; if(nextWriteIndex_ == bufferSize_) @@ -33,8 +32,8 @@ void RingBuffer::Push(T const & newData) template auto RingBuffer::Front() -> T { - auto const address = startAddress + nextReadIndex_ * serialSize; - auto readData = fram::ReadFrom>(address, 0); + auto const rawAddress = value_of(startAddress) + (nextReadIndex_ * serialSize); + auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); auto fromRing = Deserialize(std::span(readData)); return fromRing; @@ -54,8 +53,8 @@ auto RingBuffer::Back() -> T readIndex = nextWriteIndex_ - 1; } - auto const address = startAddress + readIndex * serialSize; - auto readData = fram::ReadFrom>(address, 0); + auto const rawaddress = value_of(startAddress) + readIndex * serialSize; + auto readData = fram::ReadFrom>(fram::Address(rawaddress), 0); auto fromRing = Deserialize(std::span(readData)); return fromRing; @@ -65,8 +64,9 @@ auto RingBuffer::Back() -> T template auto RingBuffer::operator[](std::size_t index) -> T { - auto const address = startAddress + ((nextReadIndex_ + index) % bufferSize_) * serialSize; - auto readData = fram::ReadFrom>(address, 0); + auto const rawaddress = + value_of(startAddress) + ((nextReadIndex_ + index) % bufferSize_) * serialSize; + auto readData = fram::ReadFrom>(fram::Address(rawaddress), 0); return Deserialize(std::span(readData)); } diff --git a/Tests/UnitTests/FramRingBuffer.test.cpp b/Tests/UnitTests/FramRingBuffer.test.cpp index aa632278..fa452f67 100644 --- a/Tests/UnitTests/FramRingBuffer.test.cpp +++ b/Tests/UnitTests/FramRingBuffer.test.cpp @@ -40,19 +40,24 @@ TEST_CASE("FramRingBuffer Push function") TEMPLATE_TEST_CASE_SIG("FramRingBuffer Front Address", "", ((typename T, size_t S, fram::Address A), T, S, A), - (int, 10, fram::Address{0}), - (int, 10, fram::Address{31415})) + (int, 10U, fram::Address{0}), + (int, 10U, fram::Address{31415})) { fram::ram::SetAllDoFunctions(); - fram::ram::storage.fill(0x00_b); + fram::ram::memory.fill(0x00_b); fram::Initialize(); fram::RingBuffer buffer; + // FIXME: [] not working buffer.Push(10); + REQUIRE(buffer[0] == 10); + buffer.Push(20); - buffer.Push(30); + REQUIRE(buffer[0] == 10); + REQUIRE(buffer[1] == 20); + buffer.Push(30); REQUIRE(buffer[0] == 10); REQUIRE(buffer[1] == 20); REQUIRE(buffer[2] == 30); @@ -61,10 +66,10 @@ TEMPLATE_TEST_CASE_SIG("FramRingBuffer Front Address", TEST_CASE("FramRingBuffer Back() and Front() methods") { fram::ram::SetAllDoFunctions(); - fram::ram::storage.fill(0x00_b); + fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer buffer; + auto buffer = fram::RingBuffer(); etl::circular_buffer etlBuffer; // NOLINTNEXTLINE (readability-container-size-empty) @@ -74,21 +79,22 @@ TEST_CASE("FramRingBuffer Back() and Front() methods") buffer.Push(1); buffer.Push(2); + REQUIRE(buffer.Front() == 1); buffer.Push(3); etlBuffer.push(1); etlBuffer.push(2); etlBuffer.push(3); - REQUIRE(etlBuffer.front() == 1); - REQUIRE(etlBuffer.back() == 3); REQUIRE(etlBuffer.size() == 3); REQUIRE(etlBuffer.capacity() == 5); + REQUIRE(etlBuffer.back() == 3); + REQUIRE(etlBuffer.front() == 1); - REQUIRE(buffer.Front() == 1); - REQUIRE(buffer.Back() == 3); REQUIRE(buffer.Size() == 3); REQUIRE(buffer.Capacity() == 5); + REQUIRE(buffer.Back() == 3); + REQUIRE(buffer.Front() == 1); etlBuffer.push(4); etlBuffer.push(5); @@ -118,10 +124,10 @@ TEST_CASE("FramRingBuffer Back() and Front() methods") TEST_CASE("FramRingBuffer Full and Empty conditions") { fram::ram::SetAllDoFunctions(); - fram::ram::storage.fill(0x00_b); + fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer buffer; + auto buffer = fram::RingBuffer{}; REQUIRE(buffer.Size() == 0); REQUIRE(buffer.Capacity() == 3); @@ -150,10 +156,10 @@ TEST_CASE("FramRingBuffer Full and Empty conditions") TEST_CASE("FramRingBuffer and ETL Circular Buffer") { fram::ram::SetAllDoFunctions(); - fram::ram::storage.fill(0x00_b); + fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer framBuffer; + fram::RingBuffer framBuffer{}; etl::circular_buffer etlBuffer; for(int i = 0; i < 5; ++i) @@ -178,10 +184,10 @@ TEST_CASE("FramRingBuffer and ETL Circular Buffer") TEST_CASE("FramRingBuffer Stress Test") { fram::ram::SetAllDoFunctions(); - fram::ram::storage.fill(0x00_b); + fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer buffer; + auto buffer = fram::RingBuffer(); for(int i = 0; i < 10000; ++i) { @@ -199,11 +205,11 @@ TEST_CASE("FramRingBuffer Stress Test") TEST_CASE("Custom Type") { fram::ram::SetAllDoFunctions(); - fram::ram::storage.fill(0x00_b); + fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer buffer; + fram::RingBuffer buffer; auto pshEntry = sts1cobcsw::edu::ProgramStatusHistoryEntry{ .programId = sts1cobcsw::ProgramId(0), From 35e424cc9c7ba67013ce9a34d8d245b8a4ad4c52 Mon Sep 17 00:00:00 2001 From: Jerome Hue Date: Sat, 10 Aug 2024 22:15:28 +0200 Subject: [PATCH 04/15] Use cyclic_value for framRingBuffer indexes --- Sts1CobcSw/Periphery/FramRingBuffer.hpp | 15 +++++++++------ Sts1CobcSw/Periphery/FramRingBuffer.ipp | 22 +++++++--------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.hpp b/Sts1CobcSw/Periphery/FramRingBuffer.hpp index 735b3d96..27a60fab 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.hpp +++ b/Sts1CobcSw/Periphery/FramRingBuffer.hpp @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -18,10 +20,11 @@ template class RingBuffer { public: - RingBuffer() - : bufferSize_(size + 1U){ - - }; + RingBuffer() : bufferSize_(size + 1U) + { + nextWriteIndex_.set(0); + nextReadIndex_.set(0); + }; auto Push(T const & newData) -> void; @@ -38,9 +41,9 @@ class RingBuffer private: - std::uint32_t nextWriteIndex_ = 0; - std::uint32_t nextReadIndex_ = 0; std::size_t bufferSize_; + etl::cyclic_value nextWriteIndex_; + etl::cyclic_value nextReadIndex_; }; } diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.ipp b/Sts1CobcSw/Periphery/FramRingBuffer.ipp index 55de4e0c..2c5c367b 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.ipp +++ b/Sts1CobcSw/Periphery/FramRingBuffer.ipp @@ -9,22 +9,14 @@ namespace sts1cobcsw::fram template void RingBuffer::Push(T const & newData) { + // auto const rawaddress = value_of(startAddress) + (nextWriteIndex_ * serialSize); auto const rawaddress = value_of(startAddress) + (nextWriteIndex_ * serialSize); fram::WriteTo(fram::Address(rawaddress), Span(Serialize(newData)), 0); ++nextWriteIndex_; - if(nextWriteIndex_ == bufferSize_) - { - nextWriteIndex_ = 0; - } - if(nextWriteIndex_ == nextReadIndex_) { - ++nextReadIndex_; - if(nextReadIndex_ == bufferSize_) - { - nextReadIndex_ = 0; - } + nextReadIndex_++; } } @@ -50,11 +42,11 @@ auto RingBuffer::Back() -> T } else { - readIndex = nextWriteIndex_ - 1; + readIndex = nextWriteIndex_.get() - 1; } - auto const rawaddress = value_of(startAddress) + readIndex * serialSize; - auto readData = fram::ReadFrom>(fram::Address(rawaddress), 0); + auto const rawAddress = value_of(startAddress) + readIndex * serialSize; + auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); auto fromRing = Deserialize(std::span(readData)); return fromRing; @@ -64,9 +56,9 @@ auto RingBuffer::Back() -> T template auto RingBuffer::operator[](std::size_t index) -> T { - auto const rawaddress = + auto const rawAddress = value_of(startAddress) + ((nextReadIndex_ + index) % bufferSize_) * serialSize; - auto readData = fram::ReadFrom>(fram::Address(rawaddress), 0); + auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); return Deserialize(std::span(readData)); } From 28c46753fb3863158648a95cb576c5e1fd9bd0c3 Mon Sep 17 00:00:00 2001 From: Jerome Hue Date: Sat, 24 Aug 2024 22:57:30 +0200 Subject: [PATCH 05/15] Add a reset mechanism for fram ringbuffer --- Sts1CobcSw/Periphery/FramRingBuffer.hpp | 15 ++++-- Sts1CobcSw/Periphery/FramRingBuffer.ipp | 62 +++++++++++++++++++------ Tests/UnitTests/FramRingBuffer.test.cpp | 21 +++++++++ 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.hpp b/Sts1CobcSw/Periphery/FramRingBuffer.hpp index 27a60fab..7903ca86 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.hpp +++ b/Sts1CobcSw/Periphery/FramRingBuffer.hpp @@ -20,10 +20,9 @@ template class RingBuffer { public: - RingBuffer() : bufferSize_(size + 1U) + RingBuffer() : bufferSize_(size + 1U), offset_(sizeof(std::size_t) * 2) { - nextWriteIndex_.set(0); - nextReadIndex_.set(0); + Initialize(); }; auto Push(T const & newData) -> void; @@ -39,11 +38,17 @@ class RingBuffer //! @brief Returns the capacity of the ringbuffer auto Capacity() -> std::size_t; + // @brief Initializes the ringbuffer by reading indices from FRAM + auto Initialize() -> void; private: std::size_t bufferSize_; - etl::cyclic_value nextWriteIndex_; - etl::cyclic_value nextReadIndex_; + std::size_t offset_; + etl::cyclic_value iEnd_; + etl::cyclic_value iBegin_; + + auto WriteIndices() -> void; + auto ReadIndices() -> void; }; } diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.ipp b/Sts1CobcSw/Periphery/FramRingBuffer.ipp index 2c5c367b..41bff372 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.ipp +++ b/Sts1CobcSw/Periphery/FramRingBuffer.ipp @@ -9,22 +9,23 @@ namespace sts1cobcsw::fram template void RingBuffer::Push(T const & newData) { - // auto const rawaddress = value_of(startAddress) + (nextWriteIndex_ * serialSize); - auto const rawaddress = value_of(startAddress) + (nextWriteIndex_ * serialSize); - fram::WriteTo(fram::Address(rawaddress), Span(Serialize(newData)), 0); + auto const rawAddress = offset_ + value_of(startAddress) + (iEnd_ * serialSize); + fram::WriteTo(fram::Address(rawAddress), Span(Serialize(newData)), 0); - ++nextWriteIndex_; - if(nextWriteIndex_ == nextReadIndex_) + ++iEnd_; + if(iEnd_ == iBegin_) { - nextReadIndex_++; + iBegin_++; } + + WriteIndices(); } template auto RingBuffer::Front() -> T { - auto const rawAddress = value_of(startAddress) + (nextReadIndex_ * serialSize); + auto const rawAddress = offset_ + value_of(startAddress) + (iBegin_ * serialSize); auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); auto fromRing = Deserialize(std::span(readData)); @@ -36,16 +37,16 @@ template auto RingBuffer::Back() -> T { std::uint32_t readIndex = 0; - if(nextWriteIndex_ == 0) + if(iEnd_ == 0) { readIndex = bufferSize_ - 1; } else { - readIndex = nextWriteIndex_.get() - 1; + readIndex = iEnd_.get() - 1; } - auto const rawAddress = value_of(startAddress) + readIndex * serialSize; + auto const rawAddress = offset_ + value_of(startAddress) + readIndex * serialSize; auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); auto fromRing = Deserialize(std::span(readData)); @@ -57,20 +58,22 @@ template auto RingBuffer::operator[](std::size_t index) -> T { auto const rawAddress = - value_of(startAddress) + ((nextReadIndex_ + index) % bufferSize_) * serialSize; + offset_ + value_of(startAddress) + ((iBegin_ + index) % bufferSize_) * serialSize; auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); - return Deserialize(std::span(readData)); + auto fromRing = Deserialize(std::span(readData)); + + return fromRing; } template auto RingBuffer::Size() -> std::size_t { - if(nextWriteIndex_ >= nextReadIndex_) + if(iEnd_ >= iBegin_) { - return (nextWriteIndex_ - nextReadIndex_); + return (iEnd_ - iBegin_); } - return (bufferSize_ - (nextReadIndex_ - nextWriteIndex_)); + return (bufferSize_ - (iBegin_ - iEnd_)); } template @@ -78,4 +81,33 @@ auto RingBuffer::Capacity() -> std::size_t { return (bufferSize_ - 1U); } + +template +auto RingBuffer::Initialize() -> void +{ + ReadIndices(); +} + +template +auto RingBuffer::WriteIndices() -> void +{ + auto beginAddress = value_of(startAddress); + auto endAddress = beginAddress + sizeof(std::size_t); + + fram::WriteTo(fram::Address(beginAddress), Span(Serialize(iBegin_.get())), 0); + fram::WriteTo(fram::Address(endAddress), Span(Serialize(iEnd_.get())), 0); +} + +template +auto RingBuffer::ReadIndices() -> void +{ + auto beginAddress = value_of(startAddress); + auto endAddress = beginAddress + sizeof(std::size_t); + + auto beginData = fram::ReadFrom(fram::Address(beginAddress), 0); + auto endData = fram::ReadFrom(fram::Address(endAddress), 0); + + iBegin_.set(Deserialize(std::span(beginData))); + iEnd_.set(Deserialize(std::span(endData))); +} } diff --git a/Tests/UnitTests/FramRingBuffer.test.cpp b/Tests/UnitTests/FramRingBuffer.test.cpp index fa452f67..7724afc7 100644 --- a/Tests/UnitTests/FramRingBuffer.test.cpp +++ b/Tests/UnitTests/FramRingBuffer.test.cpp @@ -222,3 +222,24 @@ TEST_CASE("Custom Type") REQUIRE(buffer.Size() == 1); REQUIRE(buffer.Front().status == sts1cobcsw::edu::ProgramStatus::programRunning); } + +TEST_CASE("Reset mechanism") +{ + fram::ram::SetAllDoFunctions(); + fram::ram::memory.fill(0x00_b); + fram::Initialize(); + + { + sts1cobcsw::fram::RingBuffer buffer; + buffer.Push(1); + buffer.Push(2); + buffer.Push(3); + } + + // Simulate a reset by creating a new buffer instance + fram::RingBuffer buffer; + + REQUIRE(buffer.Size() == 3); + REQUIRE(buffer.Front() == 1); + REQUIRE(buffer.Back() == 3); +} From 8d6e6adbe0274902131b89b14939fcf5e33b341f Mon Sep 17 00:00:00 2001 From: Jerome Hue Date: Sat, 24 Aug 2024 23:27:19 +0200 Subject: [PATCH 06/15] Rename ringbuffer to ringarray --- .../{FramRingBuffer.hpp => FramRingArray.hpp} | 12 +++---- .../{FramRingBuffer.ipp => FramRingArray.ipp} | 20 +++++------ Tests/UnitTests/CMakeLists.txt | 6 ++-- ...Buffer.test.cpp => FramRingArray.test.cpp} | 34 +++++++++---------- 4 files changed, 36 insertions(+), 36 deletions(-) rename Sts1CobcSw/Periphery/{FramRingBuffer.hpp => FramRingArray.hpp} (71%) rename Sts1CobcSw/Periphery/{FramRingBuffer.ipp => FramRingArray.ipp} (81%) rename Tests/UnitTests/{FramRingBuffer.test.cpp => FramRingArray.test.cpp} (83%) diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp similarity index 71% rename from Sts1CobcSw/Periphery/FramRingBuffer.hpp rename to Sts1CobcSw/Periphery/FramRingArray.hpp index 7903ca86..c4adc549 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -17,10 +17,10 @@ namespace sts1cobcsw::fram { template -class RingBuffer +class RingArray { public: - RingBuffer() : bufferSize_(size + 1U), offset_(sizeof(std::size_t) * 2) + RingArray() : bufferSize_(size + 1U), offset_(sizeof(std::size_t) * 2) { Initialize(); }; @@ -32,13 +32,13 @@ class RingBuffer auto Front() -> T; auto Back() -> T; - //! @brief Returns the current size of the ringbuffer + //! @brief Returns the current size of the ring array auto Size() -> std::size_t; - //! @brief Returns the capacity of the ringbuffer + //! @brief Returns the capacity of the ring array auto Capacity() -> std::size_t; - // @brief Initializes the ringbuffer by reading indices from FRAM + // @brief Initializes the ring array by reading indices from FRAM auto Initialize() -> void; private: @@ -53,4 +53,4 @@ class RingBuffer } -#include // IWYU pragma: keep +#include // IWYU pragma: keep diff --git a/Sts1CobcSw/Periphery/FramRingBuffer.ipp b/Sts1CobcSw/Periphery/FramRingArray.ipp similarity index 81% rename from Sts1CobcSw/Periphery/FramRingBuffer.ipp rename to Sts1CobcSw/Periphery/FramRingArray.ipp index 41bff372..a76c4386 100644 --- a/Sts1CobcSw/Periphery/FramRingBuffer.ipp +++ b/Sts1CobcSw/Periphery/FramRingArray.ipp @@ -1,13 +1,13 @@ #pragma once -#include +#include namespace sts1cobcsw::fram { template -void RingBuffer::Push(T const & newData) +void RingArray::Push(T const & newData) { auto const rawAddress = offset_ + value_of(startAddress) + (iEnd_ * serialSize); fram::WriteTo(fram::Address(rawAddress), Span(Serialize(newData)), 0); @@ -23,7 +23,7 @@ void RingBuffer::Push(T const & newData) template -auto RingBuffer::Front() -> T +auto RingArray::Front() -> T { auto const rawAddress = offset_ + value_of(startAddress) + (iBegin_ * serialSize); auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); @@ -34,7 +34,7 @@ auto RingBuffer::Front() -> T template -auto RingBuffer::Back() -> T +auto RingArray::Back() -> T { std::uint32_t readIndex = 0; if(iEnd_ == 0) @@ -55,7 +55,7 @@ auto RingBuffer::Back() -> T template -auto RingBuffer::operator[](std::size_t index) -> T +auto RingArray::operator[](std::size_t index) -> T { auto const rawAddress = offset_ + value_of(startAddress) + ((iBegin_ + index) % bufferSize_) * serialSize; @@ -67,7 +67,7 @@ auto RingBuffer::operator[](std::size_t index) -> T template -auto RingBuffer::Size() -> std::size_t +auto RingArray::Size() -> std::size_t { if(iEnd_ >= iBegin_) { @@ -77,19 +77,19 @@ auto RingBuffer::Size() -> std::size_t } template -auto RingBuffer::Capacity() -> std::size_t +auto RingArray::Capacity() -> std::size_t { return (bufferSize_ - 1U); } template -auto RingBuffer::Initialize() -> void +auto RingArray::Initialize() -> void { ReadIndices(); } template -auto RingBuffer::WriteIndices() -> void +auto RingArray::WriteIndices() -> void { auto beginAddress = value_of(startAddress); auto endAddress = beginAddress + sizeof(std::size_t); @@ -99,7 +99,7 @@ auto RingBuffer::WriteIndices() -> void } template -auto RingBuffer::ReadIndices() -> void +auto RingArray::ReadIndices() -> void { auto beginAddress = value_of(startAddress); auto endAddress = beginAddress + sizeof(std::size_t); diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index e87d875c..efd075d9 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -14,10 +14,10 @@ target_link_libraries( Sts1CobcSw_Utility ) -add_program(FramRingBuffer FramRingBuffer.test.cpp) +add_program(FramRingArray FramRingArray.test.cpp) target_link_libraries( - Sts1CobcSwTests_FramRingBuffer PRIVATE Catch2::Catch2WithMain Sts1CobcSw_Periphery - Sts1CobcSw_Serial Sts1CobcSw_Edu + Sts1CobcSwTests_FramRingArray PRIVATE Catch2::Catch2WithMain Sts1CobcSw_Periphery + Sts1CobcSw_Serial Sts1CobcSw_Edu ) add_program(LfsRam LfsRam.test.cpp) diff --git a/Tests/UnitTests/FramRingBuffer.test.cpp b/Tests/UnitTests/FramRingArray.test.cpp similarity index 83% rename from Tests/UnitTests/FramRingBuffer.test.cpp rename to Tests/UnitTests/FramRingArray.test.cpp index 7724afc7..601645bd 100644 --- a/Tests/UnitTests/FramRingBuffer.test.cpp +++ b/Tests/UnitTests/FramRingArray.test.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -20,15 +20,15 @@ using sts1cobcsw::operator""_b; // NOLINT(misc-unused-using-decls) TEST_CASE("Initial State ringbuffer") { - fram::RingBuffer buffer; + fram::RingArray buffer; REQUIRE(buffer.Size() == 0); REQUIRE(buffer.Capacity() == 10); // Fixed from 0 to 10 to reflect actual buffer capacity } -TEST_CASE("FramRingBuffer Push function") +TEST_CASE("FramRingArray Push function") { - fram::RingBuffer buffer; + fram::RingArray buffer; buffer.Push(1); buffer.Push(2); @@ -37,7 +37,7 @@ TEST_CASE("FramRingBuffer Push function") REQUIRE(buffer.Size() == 3); } -TEMPLATE_TEST_CASE_SIG("FramRingBuffer Front Address", +TEMPLATE_TEST_CASE_SIG("FramRingArray Front Address", "", ((typename T, size_t S, fram::Address A), T, S, A), (int, 10U, fram::Address{0}), @@ -47,7 +47,7 @@ TEMPLATE_TEST_CASE_SIG("FramRingBuffer Front Address", fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer buffer; + fram::RingArray buffer; // FIXME: [] not working buffer.Push(10); @@ -63,13 +63,13 @@ TEMPLATE_TEST_CASE_SIG("FramRingBuffer Front Address", REQUIRE(buffer[2] == 30); } -TEST_CASE("FramRingBuffer Back() and Front() methods") +TEST_CASE("FramRingArray Back() and Front() methods") { fram::ram::SetAllDoFunctions(); fram::ram::memory.fill(0x00_b); fram::Initialize(); - auto buffer = fram::RingBuffer(); + auto buffer = fram::RingArray(); etl::circular_buffer etlBuffer; // NOLINTNEXTLINE (readability-container-size-empty) @@ -121,13 +121,13 @@ TEST_CASE("FramRingBuffer Back() and Front() methods") REQUIRE(buffer[2] == 4); } -TEST_CASE("FramRingBuffer Full and Empty conditions") +TEST_CASE("FramRingArray Full and Empty conditions") { fram::ram::SetAllDoFunctions(); fram::ram::memory.fill(0x00_b); fram::Initialize(); - auto buffer = fram::RingBuffer{}; + auto buffer = fram::RingArray{}; REQUIRE(buffer.Size() == 0); REQUIRE(buffer.Capacity() == 3); @@ -153,13 +153,13 @@ TEST_CASE("FramRingBuffer Full and Empty conditions") REQUIRE(buffer.Back() == 10); } -TEST_CASE("FramRingBuffer and ETL Circular Buffer") +TEST_CASE("FramRingArray and ETL Circular Buffer") { fram::ram::SetAllDoFunctions(); fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingBuffer framBuffer{}; + fram::RingArray framBuffer{}; etl::circular_buffer etlBuffer; for(int i = 0; i < 5; ++i) @@ -181,13 +181,13 @@ TEST_CASE("FramRingBuffer and ETL Circular Buffer") REQUIRE(framBuffer.Back() == etlBuffer.back()); } -TEST_CASE("FramRingBuffer Stress Test") +TEST_CASE("FramRingArray Stress Test") { fram::ram::SetAllDoFunctions(); fram::ram::memory.fill(0x00_b); fram::Initialize(); - auto buffer = fram::RingBuffer(); + auto buffer = fram::RingArray(); for(int i = 0; i < 10000; ++i) { @@ -209,7 +209,7 @@ TEST_CASE("Custom Type") fram::Initialize(); - fram::RingBuffer buffer; + fram::RingArray buffer; auto pshEntry = sts1cobcsw::edu::ProgramStatusHistoryEntry{ .programId = sts1cobcsw::ProgramId(0), @@ -230,14 +230,14 @@ TEST_CASE("Reset mechanism") fram::Initialize(); { - sts1cobcsw::fram::RingBuffer buffer; + sts1cobcsw::fram::RingArray buffer; buffer.Push(1); buffer.Push(2); buffer.Push(3); } // Simulate a reset by creating a new buffer instance - fram::RingBuffer buffer; + fram::RingArray buffer; REQUIRE(buffer.Size() == 3); REQUIRE(buffer.Front() == 1); From a3a544cf7df0decc220531e0ff03a7b8612ec651 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Sat, 7 Sep 2024 16:30:15 +0000 Subject: [PATCH 07/15] Completely rework `fram::RingArray` It has basically the same public interface but using my new cool stuff like `Section` and `PersistentVariables`. Also, the class has only `static` members now, because it doesn't make sense to have multiple `RingArray` instances over the same FRAM section. --- Sts1CobcSw/Periphery/Fram.hpp | 8 +- Sts1CobcSw/Periphery/FramRingArray.hpp | 77 +++++++----- Sts1CobcSw/Periphery/FramRingArray.ipp | 159 +++++++++++++++---------- iwyu.imp | 3 +- 4 files changed, 148 insertions(+), 99 deletions(-) diff --git a/Sts1CobcSw/Periphery/Fram.hpp b/Sts1CobcSw/Periphery/Fram.hpp index 15154ffe..06a8279d 100644 --- a/Sts1CobcSw/Periphery/Fram.hpp +++ b/Sts1CobcSw/Periphery/Fram.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -20,11 +21,8 @@ namespace sts1cobcsw::fram { // NOLINTNEXTLINE(*magic-numbers) using DeviceId = std::array; -using Size = strong::type>; +using Size = strong:: + type>; using Address = strong::type, diff --git a/Sts1CobcSw/Periphery/FramRingArray.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp index c4adc549..31f168e5 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -1,9 +1,16 @@ #pragma once +#include +#include +#include +#include +#include #include #include #include +#include +#include #include @@ -14,41 +21,57 @@ #include -namespace sts1cobcsw::fram +// TODO: Move to FramSections +namespace sts1cobcsw { -template +// TODO: Make RingArray thread-safe with semaphores and scope protectors +template + requires(serialSize > 0) class RingArray { public: - RingArray() : bufferSize_(size + 1U), offset_(sizeof(std::size_t) * 2) - { - Initialize(); - }; + using ValueType = T; + static constexpr auto section = ringArraySection; - auto Push(T const & newData) -> void; + [[nodiscard]] static constexpr auto Capacity() -> std::size_t; + [[nodiscard]] static auto Size() -> std::size_t; + [[nodiscard]] static auto Get(std::size_t index) -> T; + // TODO: We might also need Set(index, t) + [[nodiscard]] static auto Front() -> T; + [[nodiscard]] static auto Back() -> T; + static auto PushBack(T const & t) -> void; - auto operator[](std::size_t index) -> T; - - auto Front() -> T; - auto Back() -> T; - - //! @brief Returns the current size of the ring array - auto Size() -> std::size_t; - - //! @brief Returns the capacity of the ring array - auto Capacity() -> std::size_t; - - // @brief Initializes the ring array by reading indices from FRAM - auto Initialize() -> void; private: - std::size_t bufferSize_; - std::size_t offset_; - etl::cyclic_value iEnd_; - etl::cyclic_value iBegin_; - - auto WriteIndices() -> void; - auto ReadIndices() -> void; + static constexpr auto elementSize = fram::Size(serialSize); + static constexpr auto indexesSize = fram::Size(2 * totalSerialSize); + static constexpr auto subsections = + Subsections, + SubsectionInfo<"indexes1", indexesSize>, + SubsectionInfo<"indexes2", indexesSize>, + SubsectionInfo<"array", section.size - 3 * indexesSize>>{}; + static constexpr auto persistentIndexes = + PersistentVariables(), + subsections.template Get<"indexes1">(), + subsections.template Get<"indexes2">(), + PersistentVariableInfo<"iBegin", std::size_t>, + PersistentVariableInfo<"iEnd", std::size_t>>{}; + // We reduce the capacity by one to distinguish between an empty and a full ring. See PushBack() + // for details. + static constexpr auto capacity = subsections.template Get<"array">().size / elementSize - 1; + static constexpr auto spiTimeout = elementSize < 300U ? 1 * ms : value_of(elementSize) * 3 * us; + + using RingIndex = etl::cyclic_value; + + static RingIndex iEnd; + static RingIndex iBegin; + + static auto LoadIndexes() -> void; + static auto StoreIndexes() -> void; + [[nodiscard]] static auto ComputeSize() -> std::size_t; + [[nodiscard]] static auto ReadElement(RingIndex index) -> T; + static auto WriteElement(RingIndex index, T const & t) -> void; }; } diff --git a/Sts1CobcSw/Periphery/FramRingArray.ipp b/Sts1CobcSw/Periphery/FramRingArray.ipp index a76c4386..7a5561ea 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.ipp +++ b/Sts1CobcSw/Periphery/FramRingArray.ipp @@ -2,112 +2,139 @@ #include +#include +#include -namespace sts1cobcsw::fram +namespace sts1cobcsw { -template -void RingArray::Push(T const & newData) -{ - auto const rawAddress = offset_ + value_of(startAddress) + (iEnd_ * serialSize); - fram::WriteTo(fram::Address(rawAddress), Span(Serialize(newData)), 0); +template + requires(serialSize > 0) +typename RingArray::RingIndex RingArray::iEnd = + RingIndex{}; - ++iEnd_; - if(iEnd_ == iBegin_) - { - iBegin_++; - } - WriteIndices(); -} +template + requires(serialSize > 0) +typename RingArray::RingIndex RingArray::iBegin = + RingIndex{}; -template -auto RingArray::Front() -> T +template + requires(serialSize > 0) +inline constexpr auto RingArray::Capacity() -> std::size_t { - auto const rawAddress = offset_ + value_of(startAddress) + (iBegin_ * serialSize); - auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); - auto fromRing = Deserialize(std::span(readData)); + return capacity; +} + - return fromRing; +template + requires(serialSize > 0) +auto RingArray::Size() -> std::size_t +{ + LoadIndexes(); + return ComputeSize(); } -template -auto RingArray::Back() -> T +template + requires(serialSize > 0) +auto RingArray::Get(std::size_t index) -> T { - std::uint32_t readIndex = 0; - if(iEnd_ == 0) - { - readIndex = bufferSize_ - 1; - } - else + LoadIndexes(); + auto size = ComputeSize(); + if(index >= size) { - readIndex = iEnd_.get() - 1; + DEBUG_PRINT("Index out of bounds in RingArray::Get: %u >= %u\n", index, size); + index = size - 1; } + auto i = iBegin; + i.advance(static_cast(index)); + return ReadElement(i); +} - auto const rawAddress = offset_ + value_of(startAddress) + readIndex * serialSize; - auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); - auto fromRing = Deserialize(std::span(readData)); - return fromRing; +template + requires(serialSize > 0) +auto RingArray::Front() -> T +{ + LoadIndexes(); + return ReadElement(iBegin); } -template -auto RingArray::operator[](std::size_t index) -> T +template + requires(serialSize > 0) +auto RingArray::Back() -> T { - auto const rawAddress = - offset_ + value_of(startAddress) + ((iBegin_ + index) % bufferSize_) * serialSize; - auto readData = fram::ReadFrom>(fram::Address(rawAddress), 0); - auto fromRing = Deserialize(std::span(readData)); - - return fromRing; + LoadIndexes(); + auto i = iEnd; + i--; + return ReadElement(i); } -template -auto RingArray::Size() -> std::size_t +template + requires(serialSize > 0) +auto RingArray::PushBack(T const & t) -> void { - if(iEnd_ >= iBegin_) + LoadIndexes(); + WriteElement(iEnd, t); + // We reduce the capacity by one to distinguish between an empty and a full ring: iEnd == iBegin + // means empty, iEnd == iBegin - 1 means full + iEnd++; + if(iEnd == iBegin) { - return (iEnd_ - iBegin_); + iBegin++; } - return (bufferSize_ - (iBegin_ - iEnd_)); + StoreIndexes(); } -template -auto RingArray::Capacity() -> std::size_t + +template + requires(serialSize > 0) +auto RingArray::LoadIndexes() -> void { - return (bufferSize_ - 1U); + iBegin.set(persistentIndexes.template Load<"iBegin">()); + iEnd.set(persistentIndexes.template Load<"iEnd">()); } -template -auto RingArray::Initialize() -> void + +template + requires(serialSize > 0) +auto RingArray::StoreIndexes() -> void { - ReadIndices(); + persistentIndexes.template Store<"iBegin">(iBegin.get()); + persistentIndexes.template Store<"iEnd">(iEnd.get()); } -template -auto RingArray::WriteIndices() -> void -{ - auto beginAddress = value_of(startAddress); - auto endAddress = beginAddress + sizeof(std::size_t); - fram::WriteTo(fram::Address(beginAddress), Span(Serialize(iBegin_.get())), 0); - fram::WriteTo(fram::Address(endAddress), Span(Serialize(iEnd_.get())), 0); +template + requires(serialSize > 0) +auto RingArray::ComputeSize() -> std::size_t +{ + if(iEnd.get() >= iBegin.get()) + { + return iEnd.get() - iBegin.get(); + } + return capacity + 1 + iEnd.get() - iBegin.get(); } -template -auto RingArray::ReadIndices() -> void + +template + requires(serialSize > 0) +auto RingArray::ReadElement(RingIndex index) -> T { - auto beginAddress = value_of(startAddress); - auto endAddress = beginAddress + sizeof(std::size_t); + auto address = subsections.template Get<"array">().begin + index.get() * elementSize; + return Deserialize(fram::ReadFrom>(address, value_of(spiTimeout))); +} - auto beginData = fram::ReadFrom(fram::Address(beginAddress), 0); - auto endData = fram::ReadFrom(fram::Address(endAddress), 0); - iBegin_.set(Deserialize(std::span(beginData))); - iEnd_.set(Deserialize(std::span(endData))); +template + requires(serialSize > 0) +auto RingArray::WriteElement(RingIndex index, T const & t) -> void +{ + auto address = subsections.template Get<"array">().begin + index.get() * elementSize; + fram::WriteTo(address, Span(Serialize(t)), value_of(spiTimeout)); } } diff --git a/iwyu.imp b/iwyu.imp index b4486367..2b3ad285 100644 --- a/iwyu.imp +++ b/iwyu.imp @@ -53,7 +53,7 @@ { include: ["\"Sts1CobcSw/Hal/Spi.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Hal/Uart.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Periphery/Fram.ipp\"", "private", "", "public"] }, - { include: ["\"Sts1CobcSw/Periphery/FramRingBuffer.ipp\"", "private", "", "public"] }, + { include: ["\"Sts1CobcSw/Periphery/FramRingArray.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/ProgramId/ProgramId.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Serial/Byte.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/Serial/Serial.ipp\"", "private", "", "public"] }, @@ -89,6 +89,7 @@ { include: ["\"Sts1CobcSw/Periphery/Fram.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/Periphery/FramEpsSpi.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/Periphery/FramMock.hpp\"", "public", "", "public"] }, + { include: ["\"Sts1CobcSw/Periphery/FramRingArray.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/Periphery/Rf.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/ProgramId/ProgramId.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/Serial/Byte.hpp\"", "public", "", "public"] }, From ef33436f36fd9adcf379cee84d06a1ee22043d84 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Tue, 10 Sep 2024 20:19:29 +0000 Subject: [PATCH 08/15] Rework test for `fram::RingArray` --- Tests/UnitTests/CMakeLists.txt | 13 +- Tests/UnitTests/FramRingArray.test.cpp | 287 ++++++------------------- 2 files changed, 71 insertions(+), 229 deletions(-) diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index efd075d9..850c71af 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -14,12 +14,6 @@ target_link_libraries( Sts1CobcSw_Utility ) -add_program(FramRingArray FramRingArray.test.cpp) -target_link_libraries( - Sts1CobcSwTests_FramRingArray PRIVATE Catch2::Catch2WithMain Sts1CobcSw_Periphery - Sts1CobcSw_Serial Sts1CobcSw_Edu -) - add_program(LfsRam LfsRam.test.cpp) target_link_libraries(Sts1CobcSwTests_LfsRam PRIVATE Catch2::Catch2WithMain Sts1CobcSw_FileSystem) @@ -91,6 +85,13 @@ target_link_libraries( ) add_test(NAME PersistentVariables COMMAND Sts1CobcSwTests_PersistentVariables) +add_program(FramRingArray FramRingArray.test.cpp UnitTestThread.cpp) +target_link_libraries( + Sts1CobcSwTests_FramRingArray PRIVATE rodos::rodos Sts1CobcSw_Periphery Sts1CobcSw_Serial + Sts1CobcSw_Edu +) +add_test(NAME FramRingArray COMMAND Sts1CobcSwTests_FramRingArray) + # --- All unit tests --- get_property( diff --git a/Tests/UnitTests/FramRingArray.test.cpp b/Tests/UnitTests/FramRingArray.test.cpp index 601645bd..a1aeb09a 100644 --- a/Tests/UnitTests/FramRingArray.test.cpp +++ b/Tests/UnitTests/FramRingArray.test.cpp @@ -1,245 +1,86 @@ -#include +#include + +#include #include #include #include -#include #include -#include -#include - -#include +#include +#include -#include #include #include +#include -namespace fram = sts1cobcsw::fram; +namespace fram = sts1cobcsw::fram; using sts1cobcsw::operator""_b; // NOLINT(misc-unused-using-decls) -TEST_CASE("Initial State ringbuffer") -{ - fram::RingArray buffer; - - REQUIRE(buffer.Size() == 0); - REQUIRE(buffer.Capacity() == 10); // Fixed from 0 to 10 to reflect actual buffer capacity -} - -TEST_CASE("FramRingArray Push function") -{ - fram::RingArray buffer; - - buffer.Push(1); - buffer.Push(2); - buffer.Push(3); - - REQUIRE(buffer.Size() == 3); -} - -TEMPLATE_TEST_CASE_SIG("FramRingArray Front Address", - "", - ((typename T, size_t S, fram::Address A), T, S, A), - (int, 10U, fram::Address{0}), - (int, 10U, fram::Address{31415})) -{ - fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); - fram::Initialize(); - - fram::RingArray buffer; - - // FIXME: [] not working - buffer.Push(10); - REQUIRE(buffer[0] == 10); - - buffer.Push(20); - REQUIRE(buffer[0] == 10); - REQUIRE(buffer[1] == 20); - - buffer.Push(30); - REQUIRE(buffer[0] == 10); - REQUIRE(buffer[1] == 20); - REQUIRE(buffer[2] == 30); -} - -TEST_CASE("FramRingArray Back() and Front() methods") -{ - fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); - fram::Initialize(); - - auto buffer = fram::RingArray(); - etl::circular_buffer etlBuffer; - - // NOLINTNEXTLINE (readability-container-size-empty) - CHECK(etlBuffer.size() == 0); - CHECK(etlBuffer.max_size() == 5); - CHECK(etlBuffer.capacity() == 5); - - buffer.Push(1); - buffer.Push(2); - REQUIRE(buffer.Front() == 1); - buffer.Push(3); - - etlBuffer.push(1); - etlBuffer.push(2); - etlBuffer.push(3); - - REQUIRE(etlBuffer.size() == 3); - REQUIRE(etlBuffer.capacity() == 5); - REQUIRE(etlBuffer.back() == 3); - REQUIRE(etlBuffer.front() == 1); - - REQUIRE(buffer.Size() == 3); - REQUIRE(buffer.Capacity() == 5); - REQUIRE(buffer.Back() == 3); - REQUIRE(buffer.Front() == 1); - - etlBuffer.push(4); - etlBuffer.push(5); - // Overwrite - etlBuffer.push(6); - - buffer.Push(4); - buffer.Push(5); - buffer.Push(6); - - REQUIRE(etlBuffer.front() == 2); - REQUIRE(buffer.Front() == 2); - - REQUIRE(etlBuffer.back() == 6); - REQUIRE(buffer.Back() == 6); - - REQUIRE(etlBuffer.size() == 5); - REQUIRE(etlBuffer.capacity() == 5); - REQUIRE(etlBuffer[0] == 2); - REQUIRE(etlBuffer[1] == 3); - REQUIRE(etlBuffer[2] == 4); - REQUIRE(buffer[0] == 2); - REQUIRE(buffer[1] == 3); - REQUIRE(buffer[2] == 4); -} - -TEST_CASE("FramRingArray Full and Empty conditions") -{ - fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); - fram::Initialize(); - - auto buffer = fram::RingArray{}; - - REQUIRE(buffer.Size() == 0); - REQUIRE(buffer.Capacity() == 3); - - buffer.Push(1); - buffer.Push(2); - buffer.Push(3); - REQUIRE(buffer.Size() == 3); - REQUIRE(buffer.Front() == 1); - REQUIRE(buffer.Back() == 3); +inline constexpr auto section = + sts1cobcsw::Section{}; +inline constexpr auto charRingArray = sts1cobcsw::RingArray{}; - buffer.Push(4); // Overwrite the oldest element +static_assert(std::is_same_v); +static_assert(charRingArray.Capacity() == 3); +static_assert(charRingArray.section.begin == fram::Address(0)); +static_assert(charRingArray.section.end == fram::Address(28)); +static_assert(charRingArray.section.size == fram::Size(28)); - REQUIRE(buffer.Size() == 3); - REQUIRE(buffer.Front() == 2); - REQUIRE(buffer.Back() == 4); - buffer.Push(10); - - REQUIRE(buffer.Size() == 3); - REQUIRE(buffer.Front() == 3); - REQUIRE(buffer.Back() == 10); -} - -TEST_CASE("FramRingArray and ETL Circular Buffer") +auto RunUnitTest() -> void { fram::ram::SetAllDoFunctions(); fram::ram::memory.fill(0x00_b); fram::Initialize(); - fram::RingArray framBuffer{}; - etl::circular_buffer etlBuffer; - - for(int i = 0; i < 5; ++i) - { - framBuffer.Push(i); - etlBuffer.push(i); - } - - REQUIRE(framBuffer.Size() == etlBuffer.size()); - for(size_t i = 0; i < framBuffer.Size(); ++i) - { - REQUIRE(framBuffer[i] == etlBuffer[i]); - } - - framBuffer.Push(5); - etlBuffer.push(5); - - REQUIRE(framBuffer.Front() == etlBuffer.front()); - REQUIRE(framBuffer.Back() == etlBuffer.back()); -} - -TEST_CASE("FramRingArray Stress Test") -{ - fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); - fram::Initialize(); - - auto buffer = fram::RingArray(); - - for(int i = 0; i < 10000; ++i) - { - buffer.Push(i); - } - - REQUIRE(buffer.Size() == 10000); - - for(size_t i = 0; i < 10000; ++i) - { - REQUIRE(buffer[i] == static_cast(i)); - } -} - -TEST_CASE("Custom Type") -{ - fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); - fram::Initialize(); - - - fram::RingArray buffer; - - auto pshEntry = sts1cobcsw::edu::ProgramStatusHistoryEntry{ - .programId = sts1cobcsw::ProgramId(0), - .startTime = 0, - .status = sts1cobcsw::edu::ProgramStatus::programRunning}; - - // some things are wrong with the Serialize Functions here - buffer.Push(pshEntry); - - REQUIRE(buffer.Size() == 1); - REQUIRE(buffer.Front().status == sts1cobcsw::edu::ProgramStatus::programRunning); -} - -TEST_CASE("Reset mechanism") -{ - fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); - fram::Initialize(); - - { - sts1cobcsw::fram::RingArray buffer; - buffer.Push(1); - buffer.Push(2); - buffer.Push(3); - } - - // Simulate a reset by creating a new buffer instance - fram::RingArray buffer; - - REQUIRE(buffer.Size() == 3); - REQUIRE(buffer.Front() == 1); - REQUIRE(buffer.Back() == 3); + Require(charRingArray.Size() == 0); + + charRingArray.PushBack(11); + Require(charRingArray.Size() == 1); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 11); + + charRingArray.PushBack(12); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 12); + + charRingArray.PushBack(13); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 13); + + Require(charRingArray.Get(0) == 11); + Require(charRingArray.Get(1) == 12); + Require(charRingArray.Get(2) == 13); + + // Out-of-bounds access returns the last element and prints a debug message + Require(charRingArray.Get(17) == 13); + + // PushBack writes to memory + constexpr auto ringArrayStartAddress = 3 * 2 * sizeof(std::size_t); + Require(fram::ram::memory[ringArrayStartAddress] == 11_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); + + // When pushing to a full ring, the size stays the same and the oldest element is lost + charRingArray.PushBack(14); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 12); + Require(charRingArray.Back() == 14); + + // Only the (size + 2)th element overwrites the first one in memory because we keep a gap of one + // between begin and end indexes + charRingArray.PushBack(15); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 13); + Require(charRingArray.Back() == 15); + Require(fram::ram::memory[ringArrayStartAddress] == 15_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); + Require(fram::ram::memory[ringArrayStartAddress + 3] == 14_b); + + // TODO: Add tests with custom types } From 502e9db1e6ce4aba16c22268bf346b08992490ff Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Tue, 10 Sep 2024 20:20:53 +0000 Subject: [PATCH 09/15] Remove default constructor of `PersistentVariables` It was explicitly defaulted but the implicitly defined one does the same so no reason to waste vertical space. --- Sts1CobcSw/FramSections/PersistentVariables.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sts1CobcSw/FramSections/PersistentVariables.hpp b/Sts1CobcSw/FramSections/PersistentVariables.hpp index 821005ea..8a26214c 100644 --- a/Sts1CobcSw/FramSections/PersistentVariables.hpp +++ b/Sts1CobcSw/FramSections/PersistentVariables.hpp @@ -28,8 +28,6 @@ class PersistentVariables Subsections::template Index(), std::tuple>; - constexpr PersistentVariables() = default; - template [[nodiscard]] static auto Load() -> ValueType; template From f44b939bd5ce6caeb8a49ef889a7f35c57e6f050 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Tue, 10 Sep 2024 20:32:14 +0000 Subject: [PATCH 10/15] Make `fram::RingArray` thread-safe with semaphore --- Sts1CobcSw/Periphery/FramRingArray.hpp | 6 +++--- Sts1CobcSw/Periphery/FramRingArray.ipp | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Sts1CobcSw/Periphery/FramRingArray.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp index 31f168e5..0d457ad4 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include @@ -24,7 +24,6 @@ // TODO: Move to FramSections namespace sts1cobcsw { -// TODO: Make RingArray thread-safe with semaphores and scope protectors template requires(serialSize > 0) class RingArray @@ -36,7 +35,7 @@ class RingArray [[nodiscard]] static constexpr auto Capacity() -> std::size_t; [[nodiscard]] static auto Size() -> std::size_t; [[nodiscard]] static auto Get(std::size_t index) -> T; - // TODO: We might also need Set(index, t) + // TODO: We also need Set(index, t) [[nodiscard]] static auto Front() -> T; [[nodiscard]] static auto Back() -> T; static auto PushBack(T const & t) -> void; @@ -66,6 +65,7 @@ class RingArray static RingIndex iEnd; static RingIndex iBegin; + static RODOS::Semaphore semaphore; static auto LoadIndexes() -> void; static auto StoreIndexes() -> void; diff --git a/Sts1CobcSw/Periphery/FramRingArray.ipp b/Sts1CobcSw/Periphery/FramRingArray.ipp index 7a5561ea..0f92ac61 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.ipp +++ b/Sts1CobcSw/Periphery/FramRingArray.ipp @@ -20,6 +20,11 @@ typename RingArray::RingIndex RingArray + requires(serialSize > 0) +RODOS::Semaphore RingArray::semaphore = RODOS::Semaphore{}; + + template requires(serialSize > 0) inline constexpr auto RingArray::Capacity() -> std::size_t @@ -32,6 +37,7 @@ template requires(serialSize > 0) auto RingArray::Size() -> std::size_t { + auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) LoadIndexes(); return ComputeSize(); } @@ -41,6 +47,7 @@ template requires(serialSize > 0) auto RingArray::Get(std::size_t index) -> T { + auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) LoadIndexes(); auto size = ComputeSize(); if(index >= size) @@ -58,6 +65,7 @@ template requires(serialSize > 0) auto RingArray::Front() -> T { + auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) LoadIndexes(); return ReadElement(iBegin); } @@ -67,6 +75,7 @@ template requires(serialSize > 0) auto RingArray::Back() -> T { + auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) LoadIndexes(); auto i = iEnd; i--; @@ -78,6 +87,7 @@ template requires(serialSize > 0) auto RingArray::PushBack(T const & t) -> void { + auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) LoadIndexes(); WriteElement(iEnd, t); // We reduce the capacity by one to distinguish between an empty and a full ring: iEnd == iBegin From a2beba79e380035abf7da9d332f02e2fb14642a3 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Thu, 12 Sep 2024 19:06:46 +0000 Subject: [PATCH 11/15] Add `fram::RingArray::Set()` --- Sts1CobcSw/Edu/EduMock.cpp | 2 ++ Sts1CobcSw/Periphery/FramRingArray.hpp | 3 ++- Sts1CobcSw/Periphery/FramRingArray.ipp | 18 +++++++++++++ Tests/UnitTests/FramRingArray.test.cpp | 36 +++++++++++++++++++++----- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Sts1CobcSw/Edu/EduMock.cpp b/Sts1CobcSw/Edu/EduMock.cpp index d4e7a787..6bc4ee66 100644 --- a/Sts1CobcSw/Edu/EduMock.cpp +++ b/Sts1CobcSw/Edu/EduMock.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include // IWYU pragma: keep diff --git a/Sts1CobcSw/Periphery/FramRingArray.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp index 0d457ad4..f07f6156 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -21,6 +21,7 @@ #include +// TODO: Add cache in case the FRAM is not working // TODO: Move to FramSections namespace sts1cobcsw { @@ -35,9 +36,9 @@ class RingArray [[nodiscard]] static constexpr auto Capacity() -> std::size_t; [[nodiscard]] static auto Size() -> std::size_t; [[nodiscard]] static auto Get(std::size_t index) -> T; - // TODO: We also need Set(index, t) [[nodiscard]] static auto Front() -> T; [[nodiscard]] static auto Back() -> T; + static auto Set(std::size_t index, T const & t) -> void; static auto PushBack(T const & t) -> void; diff --git a/Sts1CobcSw/Periphery/FramRingArray.ipp b/Sts1CobcSw/Periphery/FramRingArray.ipp index 0f92ac61..34059c60 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.ipp +++ b/Sts1CobcSw/Periphery/FramRingArray.ipp @@ -83,6 +83,24 @@ auto RingArray::Back() -> T } +template + requires(serialSize > 0) +auto RingArray::Set(std::size_t index, T const & t) -> void +{ + auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + LoadIndexes(); + auto size = ComputeSize(); + if(index >= size) + { + DEBUG_PRINT("Index out of bounds in RingArray::Set: %u >= %u\n", index, size); + return; + } + auto i = iBegin; + i.advance(static_cast(index)); + WriteElement(i, t); +} + + template requires(serialSize > 0) auto RingArray::PushBack(T const & t) -> void diff --git a/Tests/UnitTests/FramRingArray.test.cpp b/Tests/UnitTests/FramRingArray.test.cpp index a1aeb09a..da4f4c9a 100644 --- a/Tests/UnitTests/FramRingArray.test.cpp +++ b/Tests/UnitTests/FramRingArray.test.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -31,12 +32,18 @@ static_assert(charRingArray.section.size == fram::Size(28)); auto RunUnitTest() -> void { + using fram::ram::memory; + fram::ram::SetAllDoFunctions(); - fram::ram::memory.fill(0x00_b); fram::Initialize(); + memory.fill(0x00_b); Require(charRingArray.Size() == 0); + // Trying to set an element in an empty ring prints a debug message and does not set anything + charRingArray.Set(0, 11); + Require(std::all_of(memory.begin(), memory.end(), [](auto x) { return x == 0_b; })); + charRingArray.PushBack(11); Require(charRingArray.Size() == 1); Require(charRingArray.Front() == 11); @@ -56,12 +63,9 @@ auto RunUnitTest() -> void Require(charRingArray.Get(1) == 12); Require(charRingArray.Get(2) == 13); - // Out-of-bounds access returns the last element and prints a debug message - Require(charRingArray.Get(17) == 13); - // PushBack writes to memory constexpr auto ringArrayStartAddress = 3 * 2 * sizeof(std::size_t); - Require(fram::ram::memory[ringArrayStartAddress] == 11_b); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 11_b); Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); @@ -77,10 +81,30 @@ auto RunUnitTest() -> void Require(charRingArray.Size() == 3); Require(charRingArray.Front() == 13); Require(charRingArray.Back() == 15); - Require(fram::ram::memory[ringArrayStartAddress] == 15_b); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 15_b); Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); Require(fram::ram::memory[ringArrayStartAddress + 3] == 14_b); + // Set() writes to memory + charRingArray.Set(0, 21); + charRingArray.Set(1, 22); + charRingArray.Set(2, 23); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(charRingArray.Get(2) == 23); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 23_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 21_b); + Require(fram::ram::memory[ringArrayStartAddress + 3] == 22_b); + + // Get() with out-of-bounds index prints a debug message and returns the last element + Require(charRingArray.Get(17) == 23); + // Set() with out-of-bounds index prints a debug message and does not set anything + charRingArray.Set(17, 0); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(charRingArray.Get(2) == 23); + // TODO: Add tests with custom types } From fadb7a563329c68f8e08143dfb8f3178de1b4982 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Sun, 15 Sep 2024 12:06:28 +0000 Subject: [PATCH 12/15] Make `PersistentVariables` require only a single `Section` Instead of prividing three separate `Section`s, the constructor of `PersistentVariables` now only requires a single one. The three sections for the three copies of the variables are generated inside the class. --- Sts1CobcSw/FramSections/FramLayout.hpp | 7 +- .../FramSections/PersistentVariables.hpp | 25 +-- .../FramSections/PersistentVariables.ipp | 169 +++++------------- Sts1CobcSw/Periphery/FramRingArray.hpp | 12 +- .../UnitTests/PersistentVariableInfo.test.cpp | 3 - Tests/UnitTests/PersistentVariables.test.cpp | 15 +- 6 files changed, 74 insertions(+), 157 deletions(-) diff --git a/Sts1CobcSw/FramSections/FramLayout.hpp b/Sts1CobcSw/FramSections/FramLayout.hpp index 25612e0b..a0129718 100644 --- a/Sts1CobcSw/FramSections/FramLayout.hpp +++ b/Sts1CobcSw/FramSections/FramLayout.hpp @@ -13,20 +13,15 @@ namespace sts1cobcsw { inline constexpr auto framMemory = Section{}; -inline constexpr auto persistentVariablesSize = fram::Size(100); inline constexpr auto framSections = Subsections, - SubsectionInfo<"persistentVariables1", persistentVariablesSize>, - SubsectionInfo<"persistentVariables2", persistentVariablesSize>, + SubsectionInfo<"persistentVariables0", fram::Size(300)>, SubsectionInfo<"eduProgramQueue", fram::Size(20 * 8)>, SubsectionInfo<"eduProgramStatusHistory", fram::Size(50 * 7)>, SubsectionInfo<"testMemory", fram::Size(1000)>, SubsectionInfo<"telemetry", fram::Size(26'168 * 40)>>{}; inline constexpr auto persistentVariables = PersistentVariables(), - framSections.Get<"persistentVariables1">(), - framSections.Get<"persistentVariables2">(), PersistentVariableInfo<"nResetsSinceRf", std::uint16_t>, PersistentVariableInfo<"activeSecondaryFwPartition", std::int8_t>, PersistentVariableInfo<"backupSecondaryFwPartition", std::int8_t>, diff --git a/Sts1CobcSw/FramSections/PersistentVariables.hpp b/Sts1CobcSw/FramSections/PersistentVariables.hpp index 8a26214c..dbce9a98 100644 --- a/Sts1CobcSw/FramSections/PersistentVariables.hpp +++ b/Sts1CobcSw/FramSections/PersistentVariables.hpp @@ -14,18 +14,16 @@ namespace sts1cobcsw { -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) class PersistentVariables { public: + static constexpr auto section = persistentVariablesSection; + template using ValueType = std::tuple_element_t< - Subsections::template Index(), + Subsections::template Index(), std::tuple>; template @@ -50,9 +48,16 @@ class PersistentVariables static std::tuple cache1; static std::tuple cache2; - static constexpr auto subsections0 = Subsections(); - static constexpr auto subsections1 = Subsections(); - static constexpr auto subsections2 = Subsections(); + static constexpr auto subsections = Subsections, + SubsectionInfo<"1", section.size / 3>, + SubsectionInfo<"2", section.size / 3>>{}; + static constexpr auto variables0 = + Subsections(), PersistentVariableInfos...>(); + static constexpr auto variables1 = + Subsections(), PersistentVariableInfos...>(); + static constexpr auto variables2 = + Subsections(), PersistentVariableInfos...>(); // With a baud rate of 48 MHz we can read 6000 bytes in 1 ms, which should be more than enough static constexpr auto spiTimeout = 1 * RODOS::MILLISECONDS; diff --git a/Sts1CobcSw/FramSections/PersistentVariables.ipp b/Sts1CobcSw/FramSections/PersistentVariables.ipp index be059a1c..a544f07a 100644 --- a/Sts1CobcSw/FramSections/PersistentVariables.ipp +++ b/Sts1CobcSw/FramSections/PersistentVariables.ipp @@ -8,29 +8,10 @@ namespace sts1cobcsw { -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) -RODOS::Semaphore PersistentVariables::semaphore = RODOS::Semaphore(); - - -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::Load() -> ValueType +auto PersistentVariables::Load() -> ValueType { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) auto [value0, value1, value2] = @@ -47,17 +28,10 @@ auto PersistentVariables - requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::Store(ValueType const & value) +auto PersistentVariables::Store(ValueType const & value) { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) if(fram::framIsWorking.Load()) @@ -68,17 +42,10 @@ auto PersistentVariables - requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::Increment() +auto PersistentVariables::Increment() { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) auto [value0, value1, value2] = @@ -94,25 +61,18 @@ auto PersistentVariables - requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::ReadFromFram() +auto PersistentVariables::ReadFromFram() -> std::array, 3> { - constexpr auto address0 = subsections0.template Get().begin; - constexpr auto address1 = subsections1.template Get().begin; - constexpr auto address2 = subsections2.template Get().begin; - constexpr auto size0 = value_of(subsections0.template Get().size); - constexpr auto size1 = value_of(subsections1.template Get().size); - constexpr auto size2 = value_of(subsections2.template Get().size); + constexpr auto address0 = variables0.template Get().begin; + constexpr auto address1 = variables1.template Get().begin; + constexpr auto address2 = variables2.template Get().begin; + constexpr auto size0 = value_of(variables0.template Get().size); + constexpr auto size1 = value_of(variables1.template Get().size); + constexpr auto size2 = value_of(variables2.template Get().size); auto value0 = Deserialize>(fram::ReadFrom(address0, spiTimeout)); auto value1 = Deserialize>(fram::ReadFrom(address1, spiTimeout)); auto value2 = Deserialize>(fram::ReadFrom(address2, spiTimeout)); @@ -120,102 +80,71 @@ auto PersistentVariables - requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::WriteToFram(ValueType const & value) +auto PersistentVariables::WriteToFram( + ValueType const & value) { - constexpr auto address0 = subsections0.template Get().begin; - constexpr auto address1 = subsections1.template Get().begin; - constexpr auto address2 = subsections2.template Get().begin; + constexpr auto address0 = variables0.template Get().begin; + constexpr auto address1 = variables1.template Get().begin; + constexpr auto address2 = variables2.template Get().begin; fram::WriteTo(address0, Span(Serialize(value)), spiTimeout); fram::WriteTo(address1, Span(Serialize(value)), spiTimeout); fram::WriteTo(address2, Span(Serialize(value)), spiTimeout); } -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::ReadFromCache() +auto PersistentVariables::ReadFromCache() -> std::array, 3> { - constexpr auto index = subsections0.template Index(); + constexpr auto index = variables0.template Index(); return {get(cache0), get(cache1), get(cache2)}; } -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) template -auto PersistentVariables::WriteToCache(ValueType const & value) +auto PersistentVariables::WriteToCache( + ValueType const & value) { - constexpr auto index = subsections0.template Index(); + constexpr auto index = variables0.template Index(); get(cache0) = value; get(cache1) = value; get(cache2) = value; } -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) std::tuple PersistentVariables< - parentSection0, - parentSection1, - parentSection2, + section, PersistentVariableInfos...>::cache0 = std::tuple(typename PersistentVariableInfos::ValueType{}...); -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) std::tuple PersistentVariables< - parentSection0, - parentSection1, - parentSection2, + section, PersistentVariableInfos...>::cache1 = std::tuple(typename PersistentVariableInfos::ValueType{}...); -template
- requires(sizeof...(PersistentVariableInfos) > 0 && parentSection0.end <= parentSection1.begin - && parentSection1.end <= parentSection2.begin) +template
+ requires(sizeof...(PersistentVariableInfos) > 0) std::tuple PersistentVariables< - parentSection0, - parentSection1, - parentSection2, + section, PersistentVariableInfos...>::cache2 = std::tuple(typename PersistentVariableInfos::ValueType{}...); + + +template
+ requires(sizeof...(PersistentVariableInfos) > 0) +RODOS::Semaphore PersistentVariables::semaphore = + RODOS::Semaphore(); } diff --git a/Sts1CobcSw/Periphery/FramRingArray.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp index f07f6156..85085318 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -44,17 +44,13 @@ class RingArray private: static constexpr auto elementSize = fram::Size(serialSize); - static constexpr auto indexesSize = fram::Size(2 * totalSerialSize); + static constexpr auto indexesSize = fram::Size(3 * 2 * totalSerialSize); static constexpr auto subsections = Subsections, - SubsectionInfo<"indexes1", indexesSize>, - SubsectionInfo<"indexes2", indexesSize>, - SubsectionInfo<"array", section.size - 3 * indexesSize>>{}; + SubsectionInfo<"indexes", indexesSize>, + SubsectionInfo<"array", section.size - indexesSize>>{}; static constexpr auto persistentIndexes = - PersistentVariables(), - subsections.template Get<"indexes1">(), - subsections.template Get<"indexes2">(), + PersistentVariables(), PersistentVariableInfo<"iBegin", std::size_t>, PersistentVariableInfo<"iEnd", std::size_t>>{}; // We reduce the capacity by one to distinguish between an empty and a full ring. See PushBack() diff --git a/Tests/UnitTests/PersistentVariableInfo.test.cpp b/Tests/UnitTests/PersistentVariableInfo.test.cpp index 794cb5ed..26183217 100644 --- a/Tests/UnitTests/PersistentVariableInfo.test.cpp +++ b/Tests/UnitTests/PersistentVariableInfo.test.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -11,8 +10,6 @@ using sts1cobcsw::APersistentVariableInfo; using sts1cobcsw::isAPersistentVariableInfo; using sts1cobcsw::PersistentVariableInfo; using sts1cobcsw::SubsectionInfoLike; -using sts1cobcsw::fram::Address; -using sts1cobcsw::fram::Size; TEST_CASE("All static_asserts passed") diff --git a/Tests/UnitTests/PersistentVariables.test.cpp b/Tests/UnitTests/PersistentVariables.test.cpp index 0fe9f183..11d35597 100644 --- a/Tests/UnitTests/PersistentVariables.test.cpp +++ b/Tests/UnitTests/PersistentVariables.test.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -23,16 +24,10 @@ using sts1cobcsw::fram::Address; using sts1cobcsw::fram::Size; -constexpr auto section0 = Section(); -constexpr auto section1 = Section(); -constexpr auto section2 = Section(); - -constexpr auto pvs = PersistentVariables{}, PersistentVariableInfo<"nResets", std::uint32_t>, PersistentVariableInfo<"activeFwImage", std::uint8_t>, - PersistentVariableInfo<"somethingElse", std::int16_t>>(); + PersistentVariableInfo<"somethingElse", std::int16_t>>{}; static_assert(std::is_same_v, std::uint32_t>); static_assert(std::is_same_v, std::uint8_t>); @@ -54,8 +49,8 @@ auto RunUnitTest() -> void constexpr auto nResetsAddress0 = 0; constexpr auto activeFwImageAddress0 = 4; constexpr auto somethingElseAddress0 = 5; - constexpr auto activeFwImageAddress1 = activeFwImageAddress0 + 100; - constexpr auto activeFwImageAddress2 = activeFwImageAddress1 + 100; + constexpr auto activeFwImageAddress1 = activeFwImageAddress0 + value_of(pvs.section.size / 3); + constexpr auto activeFwImageAddress2 = activeFwImageAddress1 + value_of(pvs.section.size / 3); sts1cobcsw::fram::ram::SetAllDoFunctions(); From 3d66ead4c6e600a0961704dd8c91e1997332d8dc Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Sun, 15 Sep 2024 12:59:27 +0000 Subject: [PATCH 13/15] Fix includes --- Sts1CobcSw/CobcSoftware/CommandParser.cpp | 3 ++- Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.cpp | 1 + Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.hpp | 3 --- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Sts1CobcSw/CobcSoftware/CommandParser.cpp b/Sts1CobcSw/CobcSoftware/CommandParser.cpp index 390a2ec4..1ffd8bf2 100644 --- a/Sts1CobcSw/CobcSoftware/CommandParser.cpp +++ b/Sts1CobcSw/CobcSoftware/CommandParser.cpp @@ -5,8 +5,9 @@ #include #include -#include // IWYU pragma: keep +#include +#include // IWYU pragma: keep namespace sts1cobcsw { diff --git a/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.cpp b/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.cpp index dce2e23e..0c1512ca 100644 --- a/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.cpp +++ b/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.hpp b/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.hpp index ed3771e7..e99a0678 100644 --- a/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.hpp +++ b/Sts1CobcSw/CobcSoftware/EduCommunicationErrorThread.hpp @@ -1,9 +1,6 @@ #pragma once -#include - - namespace sts1cobcsw { auto ResumeEduCommunicationErrorThread() -> void; From 999d3b804b9dd216b16daee9b38a12749a7aff61 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Sun, 15 Sep 2024 14:09:54 +0000 Subject: [PATCH 14/15] Rename `Buffer[View]` to `SerialBuffer[View]` --- Sts1CobcSw/Edu/Edu.cpp | 2 +- Sts1CobcSw/Periphery/Eps.cpp | 2 +- Sts1CobcSw/Serial/Serial.hpp | 13 ++++++------- Sts1CobcSw/Serial/Serial.ipp | 10 +++++----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Sts1CobcSw/Edu/Edu.cpp b/Sts1CobcSw/Edu/Edu.cpp index aa0d9081..9806ddd4 100644 --- a/Sts1CobcSw/Edu/Edu.cpp +++ b/Sts1CobcSw/Edu/Edu.cpp @@ -432,7 +432,7 @@ auto ReceiveDataPacket() -> Result template auto Receive() -> Result { - auto buffer = Buffer{}; + auto buffer = SerialBuffer{}; OUTCOME_TRY(Receive(buffer)); return Deserialize(buffer); } diff --git a/Sts1CobcSw/Periphery/Eps.cpp b/Sts1CobcSw/Periphery/Eps.cpp index 016614c1..055005ff 100644 --- a/Sts1CobcSw/Periphery/Eps.cpp +++ b/Sts1CobcSw/Periphery/Eps.cpp @@ -230,7 +230,7 @@ auto ReadAdc(hal::GpioPin * adcCsPin) -> AdcValues RODOS::AT(RODOS::NOW() + conversionTime); // Resolution is 12 bit, sent like this: [0 0 0 0 MSB x x x], [x x x x x x x LSB] - auto adcData = Buffer{}; + auto adcData = SerialBuffer{}; adcCsPin->Reset(); hal::ReadFrom(&framEpsSpi, Span(&adcData), spiTimeout); adcCsPin->Set(); diff --git a/Sts1CobcSw/Serial/Serial.hpp b/Sts1CobcSw/Serial/Serial.hpp index c8a1fcaf..f7e12394 100644 --- a/Sts1CobcSw/Serial/Serial.hpp +++ b/Sts1CobcSw/Serial/Serial.hpp @@ -59,25 +59,24 @@ inline constexpr auto defaultEndianness = std::endian::little; template requires(serialSize != 0) -using Buffer = std::array>; +using SerialBuffer = std::array>; -// TODO: Maybe remove this type alias or prepend "Serial" to both again template requires(serialSize != 0) -using BufferView = std::span>; +using SerialBufferView = std::span>; template -[[nodiscard]] auto Serialize(T const & t) -> Buffer; +[[nodiscard]] auto Serialize(T const & t) -> SerialBuffer; template -[[nodiscard]] auto Serialize(T const & t) -> Buffer; +[[nodiscard]] auto Serialize(T const & t) -> SerialBuffer; template -[[nodiscard]] auto Deserialize(BufferView bufferView) -> T; +[[nodiscard]] auto Deserialize(SerialBufferView bufferView) -> T; template -[[nodiscard]] auto Deserialize(BufferView bufferView) -> T; +[[nodiscard]] auto Deserialize(SerialBufferView bufferView) -> T; // Must be overloaded for user-defined types to be serializable template diff --git a/Sts1CobcSw/Serial/Serial.ipp b/Sts1CobcSw/Serial/Serial.ipp index 111d611e..dcec45b4 100644 --- a/Sts1CobcSw/Serial/Serial.ipp +++ b/Sts1CobcSw/Serial/Serial.ipp @@ -7,30 +7,30 @@ namespace sts1cobcsw { template -inline auto Serialize(T const & t) -> Buffer +inline auto Serialize(T const & t) -> SerialBuffer { return Serialize(t); } template -inline auto Serialize(T const & t) -> Buffer +inline auto Serialize(T const & t) -> SerialBuffer { - auto buffer = Buffer{}; + auto buffer = SerialBuffer{}; (void)SerializeTo(buffer.data(), t); return buffer; } template -inline auto Deserialize(BufferView bufferView) -> T +inline auto Deserialize(SerialBufferView bufferView) -> T { return Deserialize(bufferView); } template -inline auto Deserialize(BufferView bufferView) -> T +inline auto Deserialize(SerialBufferView bufferView) -> T { auto t = T{}; (void)DeserializeFrom(bufferView.data(), &t); From 740db96b2c7f4387a605fe65107ef192e2bf1e03 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Sun, 15 Sep 2024 14:10:47 +0000 Subject: [PATCH 15/15] Add cache to `fram::RingArray` --- Sts1CobcSw/Periphery/FramRingArray.hpp | 16 +- Sts1CobcSw/Periphery/FramRingArray.ipp | 142 ++++++++++++------ Tests/UnitTests/FramRingArray.test.cpp | 199 ++++++++++++++++--------- 3 files changed, 238 insertions(+), 119 deletions(-) diff --git a/Sts1CobcSw/Periphery/FramRingArray.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp index 85085318..a27ba8fd 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -14,6 +14,7 @@ #include +#include #include #include @@ -25,7 +26,7 @@ // TODO: Move to FramSections namespace sts1cobcsw { -template +template requires(serialSize > 0) class RingArray { @@ -33,7 +34,8 @@ class RingArray using ValueType = T; static constexpr auto section = ringArraySection; - [[nodiscard]] static constexpr auto Capacity() -> std::size_t; + [[nodiscard]] static constexpr auto FramCapacity() -> std::size_t; + [[nodiscard]] static constexpr auto CacheCapacity() -> std::size_t; [[nodiscard]] static auto Size() -> std::size_t; [[nodiscard]] static auto Get(std::size_t index) -> T; [[nodiscard]] static auto Front() -> T; @@ -53,15 +55,17 @@ class RingArray PersistentVariables(), PersistentVariableInfo<"iBegin", std::size_t>, PersistentVariableInfo<"iEnd", std::size_t>>{}; - // We reduce the capacity by one to distinguish between an empty and a full ring. See PushBack() - // for details. - static constexpr auto capacity = subsections.template Get<"array">().size / elementSize - 1; + // We reduce the FRAM capacity by one to distinguish between an empty and a full ring. See + // PushBack() for details. + static constexpr auto framCapacity = subsections.template Get<"array">().size / elementSize - 1; static constexpr auto spiTimeout = elementSize < 300U ? 1 * ms : value_of(elementSize) * 3 * us; - using RingIndex = etl::cyclic_value; + + using RingIndex = etl::cyclic_value; static RingIndex iEnd; static RingIndex iBegin; + static etl::circular_buffer, nCachedElements> cache; static RODOS::Semaphore semaphore; static auto LoadIndexes() -> void; diff --git a/Sts1CobcSw/Periphery/FramRingArray.ipp b/Sts1CobcSw/Periphery/FramRingArray.ipp index 34059c60..06a52db1 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.ipp +++ b/Sts1CobcSw/Periphery/FramRingArray.ipp @@ -8,74 +8,92 @@ namespace sts1cobcsw { -template - requires(serialSize > 0) -typename RingArray::RingIndex RingArray::iEnd = - RingIndex{}; +using fram::framIsWorking; -template +template requires(serialSize > 0) -typename RingArray::RingIndex RingArray::iBegin = - RingIndex{}; - - -template - requires(serialSize > 0) -RODOS::Semaphore RingArray::semaphore = RODOS::Semaphore{}; +inline constexpr auto RingArray::FramCapacity() -> std::size_t +{ + return framCapacity; +} -template +template requires(serialSize > 0) -inline constexpr auto RingArray::Capacity() -> std::size_t +inline constexpr auto RingArray::CacheCapacity() + -> std::size_t { - return capacity; + return nCachedElements; } -template +template requires(serialSize > 0) -auto RingArray::Size() -> std::size_t +auto RingArray::Size() -> std::size_t { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + return cache.size(); + } LoadIndexes(); return ComputeSize(); } -template +template requires(serialSize > 0) -auto RingArray::Get(std::size_t index) -> T +auto RingArray::Get(std::size_t index) -> T { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) - LoadIndexes(); - auto size = ComputeSize(); + auto size = []() + { + if(framIsWorking.Load()) + { + LoadIndexes(); + return ComputeSize(); + } + return cache.size(); + }(); if(index >= size) { DEBUG_PRINT("Index out of bounds in RingArray::Get: %u >= %u\n", index, size); index = size - 1; } + if(not framIsWorking.Load()) + { + return Deserialize(cache[index]); + } auto i = iBegin; i.advance(static_cast(index)); return ReadElement(i); } -template +template requires(serialSize > 0) -auto RingArray::Front() -> T +auto RingArray::Front() -> T { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + return Deserialize(cache.front()); + } LoadIndexes(); return ReadElement(iBegin); } -template +template requires(serialSize > 0) -auto RingArray::Back() -> T +auto RingArray::Back() -> T { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + return Deserialize(cache.back()); + } LoadIndexes(); auto i = iEnd; i--; @@ -83,29 +101,45 @@ auto RingArray::Back() -> T } -template +template requires(serialSize > 0) -auto RingArray::Set(std::size_t index, T const & t) -> void +auto RingArray::Set(std::size_t index, T const & t) -> void { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) - LoadIndexes(); - auto size = ComputeSize(); + auto size = []() + { + if(framIsWorking.Load()) + { + LoadIndexes(); + return ComputeSize(); + } + return cache.size(); + }(); if(index >= size) { DEBUG_PRINT("Index out of bounds in RingArray::Set: %u >= %u\n", index, size); return; } + if(not framIsWorking.Load()) + { + cache[index] = Serialize(t); + } auto i = iBegin; i.advance(static_cast(index)); WriteElement(i, t); } -template +template requires(serialSize > 0) -auto RingArray::PushBack(T const & t) -> void +auto RingArray::PushBack(T const & t) -> void { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + cache.push(Serialize(t)); + return; + } LoadIndexes(); WriteElement(iEnd, t); // We reduce the capacity by one to distinguish between an empty and a full ring: iEnd == iBegin @@ -119,48 +153,72 @@ auto RingArray::PushBack(T const & t) -> void } -template +template + requires(serialSize > 0) +typename RingArray::RingIndex + RingArray::iEnd = {}; + + +template + requires(serialSize > 0) +typename RingArray::RingIndex + RingArray::iBegin = {}; + + +template + requires(serialSize > 0) +etl::circular_buffer, + nCachedElements> RingArray::cache = {}; + + +template + requires(serialSize > 0) +RODOS::Semaphore RingArray::semaphore = {}; + + +template requires(serialSize > 0) -auto RingArray::LoadIndexes() -> void +auto RingArray::LoadIndexes() -> void { iBegin.set(persistentIndexes.template Load<"iBegin">()); iEnd.set(persistentIndexes.template Load<"iEnd">()); } -template +template requires(serialSize > 0) -auto RingArray::StoreIndexes() -> void +auto RingArray::StoreIndexes() -> void { persistentIndexes.template Store<"iBegin">(iBegin.get()); persistentIndexes.template Store<"iEnd">(iEnd.get()); } -template +template requires(serialSize > 0) -auto RingArray::ComputeSize() -> std::size_t +auto RingArray::ComputeSize() -> std::size_t { if(iEnd.get() >= iBegin.get()) { return iEnd.get() - iBegin.get(); } - return capacity + 1 + iEnd.get() - iBegin.get(); + return framCapacity + 1 + iEnd.get() - iBegin.get(); } -template +template requires(serialSize > 0) -auto RingArray::ReadElement(RingIndex index) -> T +auto RingArray::ReadElement(RingIndex index) -> T { auto address = subsections.template Get<"array">().begin + index.get() * elementSize; return Deserialize(fram::ReadFrom>(address, value_of(spiTimeout))); } -template +template requires(serialSize > 0) -auto RingArray::WriteElement(RingIndex index, T const & t) -> void +auto RingArray::WriteElement(RingIndex index, T const & t) + -> void { auto address = subsections.template Get<"array">().begin + index.get() * elementSize; fram::WriteTo(address, Span(Serialize(t)), value_of(spiTimeout)); diff --git a/Tests/UnitTests/FramRingArray.test.cpp b/Tests/UnitTests/FramRingArray.test.cpp index da4f4c9a..babe1dba 100644 --- a/Tests/UnitTests/FramRingArray.test.cpp +++ b/Tests/UnitTests/FramRingArray.test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -21,10 +22,11 @@ using sts1cobcsw::operator""_b; // NOLINT(misc-unused-using-decls) inline constexpr auto section = sts1cobcsw::Section{}; -inline constexpr auto charRingArray = sts1cobcsw::RingArray{}; +inline constexpr auto charRingArray = sts1cobcsw::RingArray{}; static_assert(std::is_same_v); -static_assert(charRingArray.Capacity() == 3); +static_assert(charRingArray.FramCapacity() == 3); +static_assert(charRingArray.CacheCapacity() == 2); static_assert(charRingArray.section.begin == fram::Address(0)); static_assert(charRingArray.section.end == fram::Address(28)); static_assert(charRingArray.section.size == fram::Size(28)); @@ -36,75 +38,130 @@ auto RunUnitTest() -> void fram::ram::SetAllDoFunctions(); fram::Initialize(); - memory.fill(0x00_b); - - Require(charRingArray.Size() == 0); - - // Trying to set an element in an empty ring prints a debug message and does not set anything - charRingArray.Set(0, 11); - Require(std::all_of(memory.begin(), memory.end(), [](auto x) { return x == 0_b; })); - - charRingArray.PushBack(11); - Require(charRingArray.Size() == 1); - Require(charRingArray.Front() == 11); - Require(charRingArray.Back() == 11); - - charRingArray.PushBack(12); - Require(charRingArray.Size() == 2); - Require(charRingArray.Front() == 11); - Require(charRingArray.Back() == 12); - - charRingArray.PushBack(13); - Require(charRingArray.Size() == 3); - Require(charRingArray.Front() == 11); - Require(charRingArray.Back() == 13); - - Require(charRingArray.Get(0) == 11); - Require(charRingArray.Get(1) == 12); - Require(charRingArray.Get(2) == 13); - - // PushBack writes to memory - constexpr auto ringArrayStartAddress = 3 * 2 * sizeof(std::size_t); - Require(fram::ram::memory[ringArrayStartAddress + 0] == 11_b); - Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); - Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); - - // When pushing to a full ring, the size stays the same and the oldest element is lost - charRingArray.PushBack(14); - Require(charRingArray.Size() == 3); - Require(charRingArray.Front() == 12); - Require(charRingArray.Back() == 14); - - // Only the (size + 2)th element overwrites the first one in memory because we keep a gap of one - // between begin and end indexes - charRingArray.PushBack(15); - Require(charRingArray.Size() == 3); - Require(charRingArray.Front() == 13); - Require(charRingArray.Back() == 15); - Require(fram::ram::memory[ringArrayStartAddress + 0] == 15_b); - Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); - Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); - Require(fram::ram::memory[ringArrayStartAddress + 3] == 14_b); - - // Set() writes to memory - charRingArray.Set(0, 21); - charRingArray.Set(1, 22); - charRingArray.Set(2, 23); - Require(charRingArray.Get(0) == 21); - Require(charRingArray.Get(1) == 22); - Require(charRingArray.Get(2) == 23); - Require(fram::ram::memory[ringArrayStartAddress + 0] == 23_b); - Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); - Require(fram::ram::memory[ringArrayStartAddress + 2] == 21_b); - Require(fram::ram::memory[ringArrayStartAddress + 3] == 22_b); - - // Get() with out-of-bounds index prints a debug message and returns the last element - Require(charRingArray.Get(17) == 23); - // Set() with out-of-bounds index prints a debug message and does not set anything - charRingArray.Set(17, 0); - Require(charRingArray.Get(0) == 21); - Require(charRingArray.Get(1) == 22); - Require(charRingArray.Get(2) == 23); + static constexpr auto ringArrayStartAddress = 3 * 2 * sizeof(std::size_t); + + + // SECTION("FRAM is working") + { + memory.fill(0x00_b); + fram::framIsWorking.Store(true); + + Require(charRingArray.Size() == 0); + + // Trying to set an element in an empty ring prints a debug message and does not set + // anything + charRingArray.Set(0, 11); + Require(std::all_of(memory.begin(), memory.end(), [](auto x) { return x == 0_b; })); + + charRingArray.PushBack(11); + Require(charRingArray.Size() == 1); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 11); + + charRingArray.PushBack(12); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 12); + + charRingArray.PushBack(13); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 13); + + Require(charRingArray.Get(0) == 11); + Require(charRingArray.Get(1) == 12); + Require(charRingArray.Get(2) == 13); + + // PushBack writes to memory + Require(fram::ram::memory[ringArrayStartAddress + 0] == 11_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); + + // When pushing to a full ring, the size stays the same and the oldest element is lost + charRingArray.PushBack(14); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 12); + Require(charRingArray.Back() == 14); + + // Only the (size + 2)th element overwrites the first one in memory because we keep a gap of + // one between begin and end indexes + charRingArray.PushBack(15); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 13); + Require(charRingArray.Back() == 15); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 15_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); + Require(fram::ram::memory[ringArrayStartAddress + 3] == 14_b); + + // Set() writes to memory + charRingArray.Set(0, 21); + charRingArray.Set(1, 22); + charRingArray.Set(2, 23); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(charRingArray.Get(2) == 23); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 23_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 21_b); + Require(fram::ram::memory[ringArrayStartAddress + 3] == 22_b); + + // Get() with out-of-bounds index prints a debug message and returns the last element + Require(charRingArray.Get(17) == 23); + // Set() with out-of-bounds index prints a debug message and does not set anything + charRingArray.Set(17, 0); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(charRingArray.Get(2) == 23); + } + + // SECTION("FRAM is not working") + { + memory.fill(0x00_b); + fram::framIsWorking.Store(false); + + Require(charRingArray.Size() == 0); + // Trying to set an element in an empty ring prints a debug message + charRingArray.Set(0, 11); + + charRingArray.PushBack(11); + Require(charRingArray.Size() == 1); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 11); + + charRingArray.PushBack(12); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 12); + + Require(charRingArray.Get(0) == 11); + Require(charRingArray.Get(1) == 12); + + // PushBack does not write to memory + Require(fram::ram::memory[ringArrayStartAddress + 0] == 0_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 0_b); + + // When pushing to a full ring, the size stays the same and the oldest element is lost + charRingArray.PushBack(13); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 12); + Require(charRingArray.Back() == 13); + + // Set() does not write to memory + charRingArray.Set(0, 21); + charRingArray.Set(1, 22); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 0_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 0_b); + + // Get() with out-of-bounds index prints a debug message and returns the last element + Require(charRingArray.Get(17) == 22); + // Set() with out-of-bounds index prints a debug message and does not set anything + charRingArray.Set(17, 0); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + } // TODO: Add tests with custom types }