diff --git a/.vscode/settings.json b/.vscode/settings.json index 46afc1dca..81ab120a1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,10 +7,101 @@ "C_Cpp.clang_format_sortIncludes": true, "C_Cpp.intelliSenseCacheSize": 0, "files.associations": { +<<<<<<< HEAD + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "barrier": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "cfenv": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "csetjmp": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cuchar": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "expected": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "latch": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "scoped_allocator": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "spanstream": "cpp", + "sstream": "cpp", + "stacktrace": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "syncstream": "cpp", + "thread": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" +======= "*.embeddedhtml": "html", "mem.h": "c", "tinydir.h": "c", "dirent.h": "c", "initializer_list": "cpp" +>>>>>>> origin/development } } diff --git a/CMakeLists.txt b/CMakeLists.txt index a730486b6..23a2c3a93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ if(CMAKE_CROSSCOMPILING) ${HAL_DRIVER_SRC_DIR}/stm32h7xx_hal_gpio.c ${HAL_DRIVER_SRC_DIR}/stm32h7xx_hal_exti.c + # MDMA + ${HAL_DRIVER_SRC_DIR}/stm32h7xx_hal_mdma.c + # ADC ${HAL_DRIVER_SRC_DIR}/stm32h7xx_hal_adc.c ${HAL_DRIVER_SRC_DIR}/stm32h7xx_hal_adc_ex.c diff --git a/Inc/C++Utilities/CppUtils.hpp b/Inc/C++Utilities/CppUtils.hpp index 809e5f26f..23c843600 100644 --- a/Inc/C++Utilities/CppUtils.hpp +++ b/Inc/C++Utilities/CppUtils.hpp @@ -2,6 +2,7 @@ #include "CppImports.hpp" #include "RingBuffer.hpp" +#include "Stack.hpp" namespace chrono = std::chrono; diff --git a/Inc/C++Utilities/Stack.hpp b/Inc/C++Utilities/Stack.hpp new file mode 100644 index 000000000..e6716a7e2 --- /dev/null +++ b/Inc/C++Utilities/Stack.hpp @@ -0,0 +1,56 @@ +/* + * Stack.hpp + * + * Created on: 15 nov. 2025 + * Author: Boris + */ + +#ifndef STACK_HPP +#define STACK_HPP + +#include "CppImports.hpp" + +/** + * @brief A simple fixed-size stack. + * @tparam T The type of elements stored in the stack. + * @tparam S The maximum number of elements. + */ +template +class Stack { +public: + Stack() : top_idx(0) {} + + bool push(const T& value) { + if (top_idx < S) { + data[top_idx++] = value; + return true; + } + return false; + } + + bool pop() { + if (top_idx == 0) { + return false; + } + top_idx--; + return true; + } + + // Returns the top element without removing it. Returns default T{} if stack is empty. + T top() const { + if (top_idx == 0) { + return T{}; + } + return data[top_idx - 1]; + } + + size_t size() const { return top_idx; } + size_t capacity() const { return S; } + bool empty() const { return top_idx == 0; } + +private: + T data[S]; + size_t top_idx; +}; + +#endif // STACK_HPP \ No newline at end of file diff --git a/Inc/HALAL/HALAL.hpp b/Inc/HALAL/HALAL.hpp index fe4684068..396f99eb4 100644 --- a/Inc/HALAL/HALAL.hpp +++ b/Inc/HALAL/HALAL.hpp @@ -43,6 +43,7 @@ #include "HALAL/Models/BoardID/BoardID.hpp" #include "HALAL/Models/Concepts/Concepts.hpp" +#include "HALAL/Models/MDMA/MDMA.hpp" #ifdef STLIB_ETH #include "HALAL/Models/Packets/Packet.hpp" diff --git a/Inc/HALAL/Models/MDMA/MDMA.hpp b/Inc/HALAL/Models/MDMA/MDMA.hpp new file mode 100644 index 000000000..0f5224c59 --- /dev/null +++ b/Inc/HALAL/Models/MDMA/MDMA.hpp @@ -0,0 +1,225 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "stm32h7xx_hal.h" +#include "ErrorHandler/ErrorHandler.hpp" +#include "HALAL/Models/MPUManager/MPUManager.hpp" +#include "HALAL/Models/MPU.hpp" +#include +#include +#include +#include +#include +#include + + + +#ifdef MDMA +#undef MDMA +#endif + +#ifndef TRANSFER_QUEUE_MAX_SIZE +#define TRANSFER_QUEUE_MAX_SIZE 50 +#endif +class MDMA{ + public: + /** + * @brief A helper struct to create and manage MDMA linked list nodes. + */ + struct LinkedListNode { + template + LinkedListNode(T* source_ptr, void* dest_ptr) + { + init_node(source_ptr, dest_ptr, sizeof(T)); + } + + template + LinkedListNode(T* source_ptr, void* dest_ptr, size_t size) + { + init_node(source_ptr, dest_ptr, size); + } + + void set_next(MDMA_LinkNodeTypeDef* next_node) { node.CLAR = reinterpret_cast(next_node); } + void set_destination(void* destination) + { + uint32_t destination_address = reinterpret_cast(destination); + node.CDAR = destination_address; + + if ((destination_address < 0x00010000) || (destination_address >= 0x20000000 && destination_address < 0x20020000)) { + ErrorHandler("Error: MDMA destination address is inside ITCM or DTCM, which are not accessible."); + return; + } + + if (destination_address >= 0x24000000 && destination_address < 0x24050000) { + node.CTBR &= ~MDMA_CTBR_DBUS; + } else { + node.CTBR |= MDMA_CTBR_DBUS; + } + } + void set_source(void* source) { + uint32_t source_address = reinterpret_cast(source); + node.CSAR = source_address; + + if ((source_address < 0x00010000) || (source_address >= 0x20000000 && source_address < 0x20020000)) { + ErrorHandler("Error: MDMA source address is inside ITCM or DTCM, which are not accessible."); + return; + } + + if (source_address >= 0x24000000 && source_address < 0x24050000) { + node.CTBR &= ~MDMA_CTBR_SBUS; + } else { + node.CTBR |= MDMA_CTBR_SBUS; + } + } + auto get_node() -> MDMA_LinkNodeTypeDef* { return &node; } + auto get_size() -> uint32_t { return node.CBNDTR; } + auto get_destination() -> uint32_t { return node.CDAR; } + auto get_source() -> uint32_t { return node.CSAR; } + auto get_next() -> MDMA_LinkNodeTypeDef* { return reinterpret_cast(node.CLAR); } + +private: + alignas(8) MDMA_LinkNodeTypeDef node; + + void init_node(void* src, void* dst, size_t size) + { + MDMA_LinkNodeConfTypeDef nodeConfig{}; + nodeConfig.Init.DataAlignment = MDMA_DATAALIGN_RIGHT; + nodeConfig.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE; + nodeConfig.Init.DestBurst = MDMA_DEST_BURST_SINGLE; + nodeConfig.Init.BufferTransferLength = 128; + nodeConfig.Init.TransferTriggerMode = MDMA_FULL_TRANSFER; + nodeConfig.Init.SourceBlockAddressOffset = 0; + nodeConfig.Init.DestBlockAddressOffset = 0; + nodeConfig.BlockCount = 1; + nodeConfig.Init.Priority = MDMA_PRIORITY_VERY_HIGH; + nodeConfig.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; + nodeConfig.Init.Request = MDMA_REQUEST_SW; + + this->node = {}; + nodeConfig.SrcAddress = reinterpret_cast(src); + nodeConfig.DstAddress = reinterpret_cast(dst); + nodeConfig.BlockDataLength = static_cast(size); + + uint32_t source_data_size; + uint32_t dest_data_size; + uint32_t source_inc; + uint32_t dest_inc; + + switch(static_cast(size)) { + case 2: + source_data_size = MDMA_SRC_DATASIZE_HALFWORD; + dest_data_size = MDMA_DEST_DATASIZE_HALFWORD; + source_inc = MDMA_SRC_INC_HALFWORD; + dest_inc = MDMA_DEST_INC_HALFWORD; + break; + case 4: + source_data_size = MDMA_SRC_DATASIZE_WORD; + dest_data_size = MDMA_DEST_DATASIZE_WORD; + source_inc = MDMA_SRC_INC_WORD; + dest_inc = MDMA_DEST_INC_WORD; + break; + case 8: + source_data_size = MDMA_SRC_DATASIZE_DOUBLEWORD; + dest_data_size = MDMA_DEST_DATASIZE_DOUBLEWORD; + source_inc = MDMA_SRC_INC_DOUBLEWORD; + dest_inc = MDMA_DEST_INC_DOUBLEWORD; + break; + default: + source_data_size = MDMA_SRC_DATASIZE_BYTE; + dest_data_size = MDMA_DEST_DATASIZE_BYTE; + source_inc = MDMA_SRC_INC_BYTE; + dest_inc = MDMA_DEST_INC_BYTE; + break; + } + + nodeConfig.Init.SourceDataSize = source_data_size; + nodeConfig.Init.DestDataSize = dest_data_size; + nodeConfig.Init.SourceInc = source_inc; + nodeConfig.Init.DestinationInc = dest_inc; + + if (HAL_MDMA_LinkedList_CreateNode(&node, &nodeConfig) != HAL_OK) { + ErrorHandler("Error creating linked list in MDMA"); + } + } +}; + + private: + static D1_NC MDMA_LinkNodeTypeDef internal_nodes[8]; + + struct Instance{ + public: + MDMA_HandleTypeDef handle; + uint8_t id; + volatile bool* done; + MDMA_LinkNodeTypeDef* transfer_node; + + Instance() + : handle{} + , id(0U) + , done(nullptr) + , transfer_node(nullptr) + {} + + Instance(MDMA_HandleTypeDef handle_, + uint8_t id_, + volatile bool* done_, + MDMA_LinkNodeTypeDef* transfer_node_) + : handle(handle_) + , id(id_) + , done(done_) + , transfer_node(transfer_node_) + {} + + + }; + static void prepare_transfer(Instance& instance, MDMA::LinkedListNode* first_node); + static void prepare_transfer(Instance& instance, MDMA_LinkNodeTypeDef* first_node); + static Instance& get_instance(uint8_t id); + static MDMA_Channel_TypeDef* get_channel(uint8_t id); + static uint8_t get_instance_id(MDMA_Channel_TypeDef* channel); + + inline static std::array instances{}; + static std::bitset<8> instance_free_map; + inline static Stack,50> transfer_queue{}; + + static void TransferCompleteCallback(MDMA_HandleTypeDef *hmdma); + static void TransferErrorCallback(MDMA_HandleTypeDef *hmdma); + + /** + * @brief A method to add MDMA channels in linked list mode. + + * This method will be called internally for each channel during the MDMA::start() process. + * + * @param instance The reference to the MDMA instance to be initialised + */ + + static void inscribe(Instance& instance,uint8_t id); + + public: + + static void start(); + static void irq_handler(); + static void update(); + + + + + /** + * @brief A method to start a transfer from source to destination using MDMA linked list + * + * @param source_address The source address for the transfer. + * @param destination_address The destination address for the transfer. + * @param data_length The length of data to be transferred. + * @param check A reference boolean that will be set to true if the transfer was successfully done, false otherwise. + */ + static void transfer_data(uint8_t* source_address, uint8_t* destination_address, const uint32_t data_length,volatile bool* done=nullptr); + + /** + * @brief A method to transfer using MDMA linked + * + * @param first_node The linked list node representing the first node in the linked list. + * @param check A reference boolean that will be set to true if the transfer was successfully done, false otherwise. + */ + static void transfer_list(MDMA::LinkedListNode* first_node,volatile bool* check=nullptr); + +}; diff --git a/Src/HALAL/HALAL.cpp b/Src/HALAL/HALAL.cpp index b419bf5f5..f35056527 100644 --- a/Src/HALAL/HALAL.cpp +++ b/Src/HALAL/HALAL.cpp @@ -36,6 +36,10 @@ static void common_start(UART::Peripheral &printf_peripheral) { DMA::start(); #endif +#ifdef HAL_MDMA_MODULE_ENABLED + MDMA::start(); +#endif + #ifdef HAL_FMAC_MODULE_ENABLED MultiplierAccelerator::start(); #endif diff --git a/Src/HALAL/Models/MDMA/MDMA.cpp b/Src/HALAL/Models/MDMA/MDMA.cpp new file mode 100644 index 000000000..328a28ca2 --- /dev/null +++ b/Src/HALAL/Models/MDMA/MDMA.cpp @@ -0,0 +1,283 @@ +#include "HALAL/Models/MDMA/MDMA.hpp" + +D1_NC MDMA_LinkNodeTypeDef MDMA::internal_nodes[8]; + +#include + +std::bitset<8> MDMA::instance_free_map{}; + +MDMA_Channel_TypeDef* MDMA::get_channel(uint8_t id) { + if (id > 15) { + return nullptr; + } + return reinterpret_cast(MDMA_BASE + 0x40UL + (id * 0x40UL)); +} + +uint8_t MDMA::get_instance_id(MDMA_Channel_TypeDef* channel) { + uint32_t address = reinterpret_cast(channel); + return static_cast((address - (MDMA_BASE + 0x40UL)) / 0x40UL); +} + + +void MDMA::prepare_transfer(Instance& instance, MDMA_LinkNodeTypeDef* first_node) +{ + if (instance.handle.State == HAL_MDMA_STATE_BUSY ) + { + ErrorHandler("MDMA transfer already in progress"); + return; + } + instance_free_map[instance.id] = false; + + instance.handle.State = HAL_MDMA_STATE_BUSY; + instance.handle.ErrorCode = HAL_MDMA_ERROR_NONE; + instance.handle.FirstLinkedListNodeAddress = first_node; + + MDMA_Channel_TypeDef* channel = instance.handle.Instance; + + channel->CTCR = first_node->CTCR; + channel->CBNDTR = first_node->CBNDTR; + channel->CSAR = first_node->CSAR; + channel->CDAR = first_node->CDAR; + channel->CBRUR = first_node->CBRUR; + channel->CTBR = first_node->CTBR; + channel->CMAR = first_node->CMAR; + channel->CMDR = first_node->CMDR; + channel->CLAR = first_node->CLAR; + + const uint32_t clear_flags = MDMA_FLAG_TE | MDMA_FLAG_CTC | MDMA_FLAG_BT | MDMA_FLAG_BFTC | MDMA_FLAG_BRT; + __HAL_MDMA_CLEAR_FLAG(&instance.handle, clear_flags); + + __HAL_MDMA_ENABLE_IT(&instance.handle, MDMA_IT_TE | MDMA_IT_CTC); + + __HAL_MDMA_ENABLE(&instance.handle); + + if (HAL_MDMA_GenerateSWRequest(&instance.handle) != HAL_OK) + { + instance.handle.State = HAL_MDMA_STATE_BUSY; + ErrorHandler("Error generating MDMA SW request"); + return; + } + +} + +void MDMA::prepare_transfer(Instance& instance, LinkedListNode* first_node) { MDMA::prepare_transfer(instance, first_node->get_node()); } + + + +MDMA::Instance& MDMA::get_instance(uint8_t id) +{ + + Instance& instance = instances[id]; + + return instance; +} + + +void MDMA::inscribe(Instance& instance,uint8_t id) +{ + MDMA_HandleTypeDef mdma_handle{}; + MDMA_Channel_TypeDef* channel = get_channel(id); + if (channel == nullptr) + { + ErrorHandler("MDMA channel mapping not found"); + return; + } + mdma_handle.Instance = channel; + mdma_handle.Init.Request = MDMA_REQUEST_SW; + mdma_handle.Init.TransferTriggerMode = MDMA_FULL_TRANSFER; + mdma_handle.Init.Priority = MDMA_PRIORITY_VERY_HIGH; + mdma_handle.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; + mdma_handle.Init.SourceInc = MDMA_SRC_INC_BYTE; + mdma_handle.Init.DestinationInc = MDMA_DEST_INC_BYTE; + mdma_handle.Init.SourceDataSize = MDMA_SRC_DATASIZE_BYTE; + mdma_handle.Init.DestDataSize = MDMA_DEST_DATASIZE_BYTE; + mdma_handle.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE; + mdma_handle.Init.BufferTransferLength = 1; + mdma_handle.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE; + mdma_handle.Init.DestBurst = MDMA_DEST_BURST_SINGLE; + mdma_handle.Init.SourceBlockAddressOffset = 0; + mdma_handle.Init.DestBlockAddressOffset = 0; + + MDMA_LinkNodeConfTypeDef nodeConfig{}; + MDMA_LinkNodeTypeDef* transfer_node = &internal_nodes[id]; + + nodeConfig.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE; + nodeConfig.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE; + nodeConfig.Init.DestBurst = MDMA_DEST_BURST_SINGLE; + nodeConfig.Init.BufferTransferLength = 1; + nodeConfig.Init.TransferTriggerMode = MDMA_FULL_TRANSFER; + nodeConfig.Init.SourceBlockAddressOffset = 0; + nodeConfig.Init.DestBlockAddressOffset = 0; + nodeConfig.BlockCount = 1; + nodeConfig.Init.Priority = MDMA_PRIORITY_HIGH; + nodeConfig.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; + nodeConfig.Init.Request = MDMA_REQUEST_SW; + nodeConfig.Init.SourceInc = MDMA_SRC_INC_BYTE; + nodeConfig.Init.DestinationInc = MDMA_DEST_INC_BYTE; + nodeConfig.Init.SourceDataSize = MDMA_SRC_DATASIZE_BYTE; + nodeConfig.Init.DestDataSize = MDMA_DEST_DATASIZE_BYTE; + nodeConfig.BlockDataLength = 1; + nodeConfig.SrcAddress = reinterpret_cast(nullptr); + nodeConfig.DstAddress = reinterpret_cast(nullptr); + + const HAL_StatusTypeDef status = HAL_MDMA_LinkedList_CreateNode(transfer_node, &nodeConfig); + if (status != HAL_OK) + { + ErrorHandler("Error creating linked list in MDMA"); + } + instance_free_map[id] = true; + + instance = Instance(mdma_handle, id, nullptr, transfer_node); + +} + + +void MDMA::start() +{ + __HAL_RCC_MDMA_CLK_ENABLE(); + + uint8_t id = 0; + for (auto& instance : instances) + { + inscribe(instance,id); + id++; + + if (instance.handle.Instance == nullptr) + { + ErrorHandler("MDMA instance not initialised"); + } + const HAL_StatusTypeDef status = HAL_MDMA_Init(&instance.handle); + if (status != HAL_OK) + { + ErrorHandler("Error initialising MDMA instance"); + } + + HAL_MDMA_RegisterCallback(&instance.handle, HAL_MDMA_XFER_CPLT_CB_ID, MDMA::TransferCompleteCallback); + HAL_MDMA_RegisterCallback(&instance.handle, HAL_MDMA_XFER_ERROR_CB_ID, MDMA::TransferErrorCallback); + __HAL_MDMA_ENABLE_IT(&instance.handle, MDMA_IT_TE | MDMA_IT_CTC); + } + + HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(MDMA_IRQn); +} + +void MDMA::update() +{ + if(transfer_queue.empty()) + { + return; + } + + for(size_t i = 0; i < instances.size(); i++) + { + if(transfer_queue.empty()) + { + return; + } + if(instance_free_map[i]) + { + Instance& instance = get_instance(i); + auto transfer = transfer_queue.top(); + instance.done = transfer.second; + prepare_transfer(instance, transfer.first); + transfer_queue.pop(); + } + } +} + + +void MDMA::irq_handler() +{ + for (auto& instance : instances) + { + if (instance.handle.Instance == nullptr) + { + continue; + } + HAL_MDMA_IRQHandler(&instance.handle); + } +} + + +void MDMA::transfer_list(MDMA::LinkedListNode* first_node, volatile bool* done) +{ + if(transfer_queue.size() >= TRANSFER_QUEUE_MAX_SIZE) + { + ErrorHandler("MDMA transfer queue full"); + return; + } + transfer_queue.push({first_node, done}); +} + + + +void MDMA::transfer_data(uint8_t* source_address, uint8_t* destination_address, const uint32_t data_length, volatile bool* done) +{ + for(size_t i = 0; i < instances.size(); i++) + { + if(instance_free_map[i]) + { + Instance& instance = get_instance(i); + instance.done = done; + + instance.transfer_node->CSAR = reinterpret_cast(source_address); + instance.transfer_node->CBNDTR = data_length; + instance.transfer_node->CDAR = reinterpret_cast(destination_address); + + prepare_transfer(instance, instance.transfer_node); + return; + } + if(done) + { + *done = false; + } + } + return; +} + + +void MDMA::TransferCompleteCallback(MDMA_HandleTypeDef *hmdma) +{ + uint8_t id = get_instance_id(hmdma->Instance); + if (id >= instances.size()) + { + ErrorHandler("MDMA channel not registered"); + return; + } + + Instance& instance = get_instance(id); + instance.handle.State = HAL_MDMA_STATE_READY; + instance_free_map[instance.id] = true; + if(instance.done == nullptr) + { + return; + } + *(instance.done) = true; + +} + + +void MDMA::TransferErrorCallback(MDMA_HandleTypeDef *hmdma) +{ + uint8_t id = get_instance_id(hmdma->Instance); + if (id >= instances.size()) + { + ErrorHandler("MDMA channel not registered"); + return; + } + + Instance& instance = get_instance(id); + if(instance.done != nullptr) + { + *(instance.done) = false; + } + + const unsigned long error_code = static_cast(hmdma->ErrorCode); + ErrorHandler("MDMA Transfer Error, code: " + std::to_string(error_code)); +} + + +extern "C" void MDMA_IRQHandler(void) +{ + MDMA::irq_handler(); +} diff --git a/Src/ST-LIB.cpp b/Src/ST-LIB.cpp index 1e560e1d5..293a925af 100644 --- a/Src/ST-LIB.cpp +++ b/Src/ST-LIB.cpp @@ -39,6 +39,7 @@ void STLIB::update() { Ethernet::update(); Server::update_servers(); #endif - - ErrorHandlerModel::ErrorHandlerUpdate(); -} \ No newline at end of file + ErrorHandlerModel::ErrorHandlerUpdate(); + MDMA::update(); + +}