Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d54378
Refactor protections around a static rule engine
jorgesg82 Apr 5, 2026
e3df156
Decouple diagnostics and fault broadcasting
jorgesg82 Apr 5, 2026
0bd6e18
Harden simulator and time service support
jorgesg82 Apr 5, 2026
6ff0998
Hooked to Board init
jorgesg82 Apr 5, 2026
e82a9bc
Harden fault, diagnostic, and reporter subsystems
jorgesg82 Apr 8, 2026
f95052a
Update peripheral callsites to PANIC/FAULT/WARNING/INFO macros
jorgesg82 Apr 8, 2026
d1dff46
Update build, includes, and infrastructure for PM-no-ETH refactoring
jorgesg82 Apr 8, 2026
d9a2d24
Add diagnostics and fault controller test coverage
jorgesg82 Apr 8, 2026
7bc6135
Add protections-and-diagnostics architecture guide
jorgesg82 Apr 8, 2026
2ce3d84
Constrain global fault entry API
jorgesg82 Apr 17, 2026
bf81353
Make Board take an explicit fault policy type
jorgesg82 Apr 17, 2026
e780fba
Make time_accumulation use scheduler time
jorgesg82 Apr 17, 2026
5ff8970
applied formatter
jorgesg82 Apr 22, 2026
9fb61fe
added changeset
jorgesg82 Apr 22, 2026
85a4ad2
Remove legacy bootstrap entry points and compatibility shims
jorgesg82 Apr 22, 2026
15a1940
Preserve early faults across bootstrap and replay retained diagnostics
jorgesg82 Apr 22, 2026
f8d53c3
Document the updated protections and fault contract
jorgesg82 Apr 22, 2026
5dd879e
Fix CI regressions and address Copilot review feedback
jorgesg82 Apr 22, 2026
3be10c3
Make simulator MPU support portable on macOS
jorgesg82 Apr 22, 2026
44f2d9b
fix RTC
jorgesg82 Apr 23, 2026
65644b1
feat(Protections)!:Make protections declared in compile time
FoniksFox Apr 25, 2026
47ff73b
fix(SPI Tests): Reset SPI entirely on each init so that tests can be …
FoniksFox Apr 25, 2026
7daa019
feat(Protections)!: integrate compile-time protections with Board
jorgesg82 Apr 27, 2026
a05f36f
applied formatter
jorgesg82 May 4, 2026
8743bf9
Applied some fixes
jorgesg82 May 4, 2026
d87305d
fixed diagnosis ID
jorgesg82 May 6, 2026
ce3107c
fixed size
jorgesg82 May 6, 2026
9e0c1e5
applied formatter
jorgesg82 May 6, 2026
7eeba35
applied GH comments
jorgesg82 May 6, 2026
ebb24a8
fixes after rebase
jorgesg82 May 6, 2026
e7602c1
minor fix
jorgesg82 May 6, 2026
3999f1f
applied formatter
jorgesg82 May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changesets/pm-no-eth-major.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
release: major
summary: Redesign fault handling, protections, and Board bootstrap around explicit fault policies

This PR changes the public integration contract for applications built on ST-LIB.

Breaking changes:

- `Board` now takes the fault policy type as its first template parameter.
- The global `FAULT` runtime is owned exclusively by `FaultController`.
- User state machines are now nested under the global `OPERATIONAL` state through `FaultPolicy` or `FaultPolicyNoMachine`.
- Protections are now compile-time `Board` request objects evaluated through `Board::ProtectionEngine`; the previous `ProtectionManager` and boundary split is no longer the active model.
- Runtime reporting is unified under `PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)`.
- The real bootstrap path is `Board::init()`. Legacy `STLIB::start()`, `STLIB::update()`, `STLIB_LOW::start()`, and `STLIB_HIGH::start()` must not be used as the integration path.

Migration notes:

- Declare the board as `Board<YourFaultPolicy, ...>`.
- Use `FaultPolicy<app_machine, on_fault_enter>` when you want an operational state machine nested under the global runtime.
- Use `FaultPolicyNoMachine<on_fault_enter>` when you only need a fault-entry callback.
- Use `DefaultFaultPolicy` when you want neither an operational machine nor a fault-entry callback.
- Declare protections with `Protections::protection<"name", source>(...)` and pass the resulting request objects to `Board`.
- In the main loop, drive the runtime through `FaultController::check_transitions()`, `Board::evaluate_protections()`, and `Diagnostics::Hub::flush()`.
28 changes: 18 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ set(HALAL_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MDMA/MDMA.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/Packet.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/SPIOrder.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/SPI/SPI2.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/DMA/DMA2.cpp
Expand All @@ -289,6 +290,10 @@ set(HALAL_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/UART/UART.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/EXTI/EXTI.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/FMAC/FMAC.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Flash/Flash.cpp
Expand All @@ -306,7 +311,6 @@ set(HALAL_C_ETH_CORE)
set(HALAL_CPP_ETH_CORE
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/IPV4/IPV4.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MAC/MAC.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/Packet.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/SNTP/SNTP.cpp
)

Expand Down Expand Up @@ -368,7 +372,6 @@ set(STLIB_LOW_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/DigitalOutput/DigitalOutput.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Math/Math.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ST-LIB_LOW.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sd/Sd.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/DigitalSensor/DigitalSensor.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/SensorInterrupt/SensorInterrupt.cpp
Expand All @@ -380,17 +383,14 @@ set(STLIB_LOW_CPP_NO_ETH

set(STLIB_HIGH_C_ETH)

set(STLIB_HIGH_CPP_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/Boundary.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp
)
set(STLIB_HIGH_CPP_ETH)

set(STLIB_HIGH_C_NO_ETH)

set(STLIB_HIGH_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashStorer.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashVariable.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp
)

# ============================
Expand Down Expand Up @@ -426,9 +426,6 @@ add_library(${STLIB_LIBRARY} OBJECT
$<$<BOOL:${CMAKE_CROSSCOMPILING}>:${STLIB_HIGH_CPP_NO_ETH}>
$<$<AND:$<BOOL:${CMAKE_CROSSCOMPILING}>,$<BOOL:${USE_ETHERNET}>>:${STLIB_HIGH_C_ETH}>
$<$<AND:$<BOOL:${CMAKE_CROSSCOMPILING}>,$<BOOL:${USE_ETHERNET}>>:${STLIB_HIGH_CPP_ETH}>

$<$<BOOL:${CMAKE_CROSSCOMPILING}>:${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB.cpp>

$<$<NOT:$<BOOL:${CMAKE_CROSSCOMPILING}>>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Time/Scheduler.cpp>
$<$<NOT:$<BOOL:${CMAKE_CROSSCOMPILING}>>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/TimerDomain/TimerDomain.cpp>
$<$<NOT:$<BOOL:${CMAKE_CROSSCOMPILING}>>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp>
Expand Down Expand Up @@ -567,6 +564,17 @@ else()
endif()
endif()

if(CMAKE_CROSSCOMPILING)
add_library(stlib_compile_check_board_protections OBJECT
${CMAKE_CURRENT_LIST_DIR}/Tests/compile_checks/board_protection_contract.cpp
)
target_link_libraries(stlib_compile_check_board_protections PRIVATE ${STLIB_LIBRARY})
set_target_properties(stlib_compile_check_board_protections PROPERTIES
CXX_STANDARD 23
CXX_STANDARD_REQUIRED YES
)
endif()

if(PROJECT_IS_TOP_LEVEL)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink
Expand Down
7 changes: 7 additions & 0 deletions Inc/C++Utilities/CppImports.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@
#include <ranges>
#include <cstdarg>
#include <stdarg.h>
#include <bit>
#include <concepts>
#include <expected>
#include <new>
#include <optional>
#include <type_traits>
#include <variant>
8 changes: 8 additions & 0 deletions Inc/C++Utilities/CppUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ namespace chrono = std::chrono;
namespace placeholders = std::placeholders;

using std::array;
using std::byte;
using std::construct_at;
using std::destroy_at;
using std::expected;
using std::function;
using std::hash;
using std::integral_constant;
Expand All @@ -17,6 +21,7 @@ using std::make_unique;
using std::map;
using std::move;
using std::nullopt;
using std::optional;
using std::pair;
using std::queue;
using std::reference_wrapper;
Expand All @@ -29,6 +34,9 @@ using std::stack;
using std::string;
using std::stringstream;
using std::to_string;
using std::unexpected;
using std::unique_ptr;
using std::unordered_map;
using std::variant;
using std::vector;
using std::visit;
2 changes: 1 addition & 1 deletion Inc/C++Utilities/StaticVector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ template <typename T, size_t Capacity> class StaticVector {

constexpr void push_back(const T& value) {
if (size_ >= Capacity) {
ErrorHandler("StaticVector capacity exceeded");
PANIC("StaticVector capacity exceeded");
return;
}
data[size_] = value;
Expand Down
10 changes: 0 additions & 10 deletions Inc/HALAL/HALAL.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,3 @@
#include "HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp"
#include "HALAL/Services/Communication/SNTP/SNTP.hpp"
#endif

namespace HALAL {

#ifdef STLIB_ETH
void start(MAC mac, IPV4 ip, IPV4 subnet_mask, IPV4 gateway, UART::Peripheral& printf_peripheral);
#else
void start(UART::Peripheral& printf_peripheral);
#endif

} // namespace HALAL
4 changes: 2 additions & 2 deletions Inc/HALAL/Models/DMA/DMA2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ struct DMADomain {

instances[i].dma = {};
if (stream == Stream::none) {
ErrorHandler("DMA stream must be selected before init");
PANIC("DMA stream must be selected before init");
continue;
}

Expand All @@ -512,7 +512,7 @@ struct DMADomain {

if (HAL_DMA_Init(&instances[i].dma) != HAL_OK) {
instances[i].dma = {};
ErrorHandler("DMA Init failed");
PANIC("DMA Init failed");
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions Inc/HALAL/Models/MDMA/MDMA.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class MDMA {

void init_node(void* src, void* dst, size_t size) {
if (size == 0) {
ErrorHandler("MDMA: zero-length transfer is invalid");
PANIC("MDMA: zero-length transfer is invalid");
return;
}

Expand Down Expand Up @@ -221,7 +221,7 @@ class MDMA {
nodeConfig.Init.BufferTransferLength = buf_len;

if (HAL_MDMA_LinkedList_CreateNode(&node, &nodeConfig) != HAL_OK) {
ErrorHandler("Error creating linked list in MDMA");
PANIC("Error creating linked list in MDMA");
}

// HAL_MDMA_LinkedList_CreateNode only sets the request field in CTBR;
Expand Down
70 changes: 47 additions & 23 deletions Inc/HALAL/Models/MPU.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,15 @@
* `__attribute__` so the header is safe to include from both C and C++ translation
* units (some build units may be plain C and would reject the [[...]] syntax).
*/
#define D1_NC __attribute__((section(".mpu_ram_d1_nc.user")))
#define D2_NC __attribute__((section(".mpu_ram_d2_nc.user")))
#define D3_NC __attribute__((section(".mpu_ram_d3_nc.user")))
#define D1_C __attribute__((section(".ram_d1.user")))
#define D2_C __attribute__((section(".ram_d2.user")))
#define D3_C __attribute__((section(".ram_d3.user")))

// Define for RAM code
#define RAM_CODE __attribute__((section(".ram_code")))

#define D1_NC __attribute__((section(".mpu_ram_d1_nc.user"), used)) volatile
#define D2_NC __attribute__((section(".mpu_ram_d2_nc.user"), used)) volatile
#define D3_NC __attribute__((section(".mpu_ram_d3_nc.user"), used)) volatile
#define D1_C __attribute__((section(".ram_d1.user"), used))
#define D2_C __attribute__((section(".ram_d2.user"), used))
#define D3_C __attribute__((section(".ram_d3.user"), used))
#endif

// Memory Bank Symbols from Linker
Expand All @@ -92,6 +93,17 @@ extern "C" const char __mpu_d2_nc_end;
extern "C" const char __mpu_d3_nc_start;
extern "C" const char __mpu_d3_nc_end;

inline constexpr std::array<std::size_t, 6> mpu_supported_alignments = {32, 16, 8, 4, 2, 1};

consteval bool is_supported_mpu_alignment(std::size_t alignment) {
for (std::size_t candidate : mpu_supported_alignments) {
if (candidate == alignment) {
return true;
}
}
return false;
}

template <typename T>
concept mpu_buffer_request = requires(typename T::domain d) {
typename T::buffer_type;
Expand Down Expand Up @@ -147,7 +159,7 @@ struct MPUDomain {
"Requested type has alignment greater than cache line size (32 bytes)."
);
static_assert(
std::ranges::find(alignments, alignof(T)) != std::ranges::end(alignments),
is_supported_mpu_alignment(alignof(T)),
"Requested type has alignment not supported by MPU buffer system."
);
}
Expand All @@ -157,14 +169,12 @@ struct MPUDomain {
* @param entry The Entry with all buffer requirements specified.
*/
consteval Buffer(Entry entry) : e(entry) {
static_assert(
entry.alignment <= 32,
"Requested alignment greater than cache line size (32 bytes)."
);
static_assert(
std::ranges::find(alignments, entry.alignment) != std::ranges::end(alignments),
"Requested alignment not supported by MPU buffer system."
);
if (entry.alignment > 32) {
compile_error("Requested alignment greater than cache line size (32 bytes).");
}
if (!is_supported_mpu_alignment(entry.alignment)) {
compile_error("Requested alignment not supported by MPU buffer system.");
}
// Verify size matches sizeof(T)
if (entry.size_in_bytes != sizeof(T)) {
compile_error("Entry size_in_bytes must match sizeof(T)");
Expand Down Expand Up @@ -241,7 +251,7 @@ struct MPUDomain {
uint32_t offsets_c[3] = {}; // D1, D2, D3
uint32_t assigned_offsets[N];

for (size_t align : alignments) {
for (size_t align : mpu_supported_alignments) {
for (size_t i = 0; i < N; i++) {
if (entries[i].alignment == align) {
size_t d_idx = static_cast<size_t>(entries[i].memory_domain) - 1;
Expand Down Expand Up @@ -299,14 +309,18 @@ struct MPUDomain {
}
};

template <typename Board, mpu_buffer_request auto& Target, typename... Args>
template <typename Board, auto& Target, typename... Args>
static auto& construct(Args&&... args) {
using Request = std::remove_cvref_t<decltype(Target)>;
static_assert(mpu_buffer_request<Request>, "Target must be a valid MPUDomain buffer");
return Board::template instance_of<Target>().template construct<Target>(
std::forward<Args>(args)...
);
}

template <typename Board, mpu_buffer_request auto& Target> static auto* as() {
template <typename Board, auto& Target> static auto* as() {
using Request = std::remove_cvref_t<decltype(Target)>;
static_assert(mpu_buffer_request<Request>, "Target must be a valid MPUDomain buffer");
return Board::template instance_of<Target>().template as<Target>();
}

Expand All @@ -316,8 +330,19 @@ struct MPUDomain {
static constexpr auto Sizes = calculate_total_sizes(cfgs);

// Sections defined in Linker Script (aligned to 32 bytes just in case)
__attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32) static inline volatile uint8_t
d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1];
#ifdef SIM_ON
alignas(32
) static inline uint8_t d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1];
alignas(32) static inline uint8_t d1_c_buffer[Sizes.d1_c_total > 0 ? Sizes.d1_c_total : 1];
alignas(32
) static inline uint8_t d2_nc_buffer[Sizes.d2_nc_total > 0 ? Sizes.d2_nc_total : 1];
alignas(32) static inline uint8_t d2_c_buffer[Sizes.d2_c_total > 0 ? Sizes.d2_c_total : 1];
alignas(32
) static inline uint8_t d3_nc_buffer[Sizes.d3_nc_total > 0 ? Sizes.d3_nc_total : 1];
alignas(32) static inline uint8_t d3_c_buffer[Sizes.d3_c_total > 0 ? Sizes.d3_c_total : 1];
#else
__attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32
) static inline uint8_t d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1];
__attribute__((section(".ram_d1.buffer"))) alignas(32
) static inline uint8_t d1_c_buffer[Sizes.d1_c_total > 0 ? Sizes.d1_c_total : 1];

Expand All @@ -330,6 +355,7 @@ struct MPUDomain {
d3_nc_buffer[Sizes.d3_nc_total > 0 ? Sizes.d3_nc_total : 1];
__attribute__((section(".ram_d3.buffer"))) alignas(32
) static inline uint8_t d3_c_buffer[Sizes.d3_c_total > 0 ? Sizes.d3_c_total : 1];
#endif

static void init() {
HAL_MPU_Disable();
Expand Down Expand Up @@ -380,8 +406,6 @@ struct MPUDomain {
};

private:
static constexpr std::size_t alignments[6] = {32, 16, 8, 4, 2, 1};

static void configure_dynamic_region(uintptr_t start, uintptr_t end, uint8_t region_num) {
if (end <= start)
return;
Expand Down
2 changes: 1 addition & 1 deletion Inc/HALAL/Models/MPUManager/MPUManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class MPUManager {
no_cached_ram_occupied_bytes = no_cached_ram_occupied_bytes + size;
if (no_cached_ram_occupied_bytes > NO_CACHED_RAM_MAXIMUM_SPACE) {
uint32_t excess_bytes = no_cached_ram_occupied_bytes - NO_CACHED_RAM_MAXIMUM_SPACE;
ErrorHandler("Maximum capacity on non cached ram heap exceeded by %d", excess_bytes);
PANIC("Maximum capacity on non cached ram heap exceeded by %d", excess_bytes);
return nullptr;
}
return buffer;
Expand Down
9 changes: 2 additions & 7 deletions Inc/HALAL/Models/Packets/SPIOrder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,15 @@ class SPIBaseOrder {
SPIBaseOrder(uint16_t id, uint16_t master_data_size, uint16_t slave_data_size)
: id(id), master_data_size(master_data_size), slave_data_size(slave_data_size) {
if (id == 0) {
ErrorHandler(
"Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal"
);
PANIC("Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal");
}
if (master_data_size > slave_data_size) {
payload_size = master_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL;
} else {
payload_size = slave_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL;
}
if (payload_size > SPI_MAXIMUM_PAYLOAD_SIZE_BYTES) {
ErrorHandler(
"Cannot declare SPIOrder %d as its size surpasses the maximum data size",
id
);
PANIC("Cannot declare SPIOrder %d as its size surpasses the maximum data size", id);
}
MISO_payload = new uint8_t[payload_size]{0};
MOSI_payload = new uint8_t[payload_size]{0};
Expand Down
Loading
Loading