Index: ctrl/firmware/Main/CubeMX/Core/Src/main.c
===================================================================
--- ctrl/firmware/Main/CubeMX/Core/Src/main.c	(revision 74)
+++ ctrl/firmware/Main/CubeMX/Core/Src/main.c	(revision 75)
@@ -106,4 +106,6 @@
   /* USER CODE BEGIN Init */
 
+  SCB_DisableDCache();
+
   /* USER CODE END Init */
 
@@ -123,6 +125,6 @@
   MX_USART3_UART_Init();
   MX_TIM3_Init();
+  MX_FATFS_Init();
   MX_TIM8_Init();
-  MX_FATFS_Init();
   /* USER CODE BEGIN 2 */
 
Index: ctrl/firmware/Main/CubeMX/Core/Src/rtc.c
===================================================================
--- ctrl/firmware/Main/CubeMX/Core/Src/rtc.c	(revision 74)
+++ ctrl/firmware/Main/CubeMX/Core/Src/rtc.c	(revision 75)
@@ -35,4 +35,7 @@
   /* USER CODE END RTC_Init 0 */
 
+  RTC_TimeTypeDef sTime = {0};
+  RTC_DateTypeDef sDate = {0};
+
   /* USER CODE BEGIN RTC_Init 1 */
 
@@ -50,4 +53,29 @@
   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
   if (HAL_RTC_Init(&hrtc) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /* USER CODE BEGIN Check_RTC_BKUP */
+
+  /* USER CODE END Check_RTC_BKUP */
+
+  /** Initialize RTC and set the Time and Date
+  */
+  sTime.Hours = 0x0;
+  sTime.Minutes = 0x0;
+  sTime.Seconds = 0x0;
+  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
+  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
+  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
+  {
+    Error_Handler();
+  }
+  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
+  sDate.Month = RTC_MONTH_JANUARY;
+  sDate.Date = 0x1;
+  sDate.Year = 0x0;
+
+  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
   {
     Error_Handler();
Index: ctrl/firmware/Main/CubeMX/Core/Src/sdmmc.c
===================================================================
--- ctrl/firmware/Main/CubeMX/Core/Src/sdmmc.c	(revision 74)
+++ ctrl/firmware/Main/CubeMX/Core/Src/sdmmc.c	(revision 75)
@@ -46,5 +46,5 @@
   hsd1.Init.BusWide = SDMMC_BUS_WIDE_4B;
   hsd1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE;
-  hsd1.Init.ClockDiv = 0;
+  hsd1.Init.ClockDiv = 1;
   /* USER CODE BEGIN SDMMC1_Init 2 */
 
@@ -102,5 +102,5 @@
 
     /* SDMMC1 interrupt Init */
-    HAL_NVIC_SetPriority(SDMMC1_IRQn, 15, 0);
+    HAL_NVIC_SetPriority(SDMMC1_IRQn, 5, 0);
     HAL_NVIC_EnableIRQ(SDMMC1_IRQn);
   /* USER CODE BEGIN SDMMC1_MspInit 1 */
Index: ctrl/firmware/Main/CubeMX/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_mdma.c
===================================================================
--- ctrl/firmware/Main/CubeMX/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_mdma.c	(revision 75)
+++ ctrl/firmware/Main/CubeMX/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_mdma.c	(revision 75)
@@ -0,0 +1,1899 @@
+/**
+  ******************************************************************************
+  * @file    stm32h7xx_hal_mdma.c
+  * @author  MCD Application Team
+  * @brief  This file provides firmware functions to manage the following
+  *         functionalities of the Master Direct Memory Access (MDMA) peripheral:
+  *           + Initialization/de-initialization functions
+  *           + I/O operation functions
+  *           + Peripheral State and errors functions
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2017 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  @verbatim
+  ==============================================================================
+                        ##### How to use this driver #####
+  ==============================================================================
+  [..]
+   (#) Enable and configure the peripheral to be connected to the MDMA Channel
+       (except for internal SRAM/FLASH memories: no initialization is
+       necessary) please refer to Reference manual for connection between peripherals
+       and MDMA requests.
+
+   (#)
+       For a given Channel use HAL_MDMA_Init function to program the required configuration through the following parameters:
+       transfer request , channel priority, data endianness, Source increment, destination increment ,
+       source data size, destination data size, data alignment, source Burst, destination Burst ,
+       buffer Transfer Length, Transfer Trigger Mode (buffer transfer, block transfer, repeated block transfer
+       or full transfer) source and destination block address offset, mask address and data.
+
+       If using the MDMA in linked list mode then use function HAL_MDMA_LinkedList_CreateNode to fill a transfer node.
+       Note that parameters given to the function HAL_MDMA_Init corresponds always to the node zero.
+       Use function HAL_MDMA_LinkedList_AddNode to connect the created node to the linked list at a given position.
+       User can make a linked list circular using function HAL_MDMA_LinkedList_EnableCircularMode , this function will automatically connect the
+       last node of the list to the first one in order to make the list circular.
+       In this case the linked list will loop on node 1 : first node connected after the initial transfer defined by the HAL_MDMA_Init
+
+      -@-   The initial transfer itself (node 0 corresponding to the Init).
+            User can disable the circular mode using function HAL_MDMA_LinkedList_DisableCircularMode, this function will then remove
+            the connection between last node and first one.
+
+       Function HAL_MDMA_LinkedList_RemoveNode can be used to remove (disconnect) a node from the transfer linked list.
+       When a linked list is circular (last node connected to first one), if removing node1  (node where the linked list loops),
+       the linked list remains circular and node 2 becomes the first one.
+       Note that if the linked list is made circular the transfer will loop infinitely (or until aborted by the user).
+
+    [..]
+       (+) User can select the transfer trigger mode (parameter TransferTriggerMode) to define the amount of data to be
+           transfer upon a request :
+             (++) MDMA_BUFFER_TRANSFER : each request triggers a transfer of BufferTransferLength data
+               with BufferTransferLength defined within the HAL_MDMA_Init.
+             (++) MDMA_BLOCK_TRANSFER : each request triggers a transfer of a block
+               with block size defined within the function HAL_MDMA_Start/HAL_MDMA_Start_IT
+               or within the current linked list node parameters.
+             (++) MDMA_REPEAT_BLOCK_TRANSFER : each request triggers a transfer of a number of blocks
+               with block size and number of blocks defined within the function HAL_MDMA_Start/HAL_MDMA_Start_IT
+               or within the current linked list node parameters.
+             (++) MDMA_FULL_TRANSFER : each request triggers a full transfer
+              all blocks and all nodes(if a linked list has been created using HAL_MDMA_LinkedList_CreateNode \ HAL_MDMA_LinkedList_AddNode).
+
+     *** Polling mode IO operation ***
+     =================================
+    [..]
+          (+) Use HAL_MDMA_Start() to start MDMA transfer after the configuration of Source
+              address and destination address and the Length of data to be transferred.
+          (+) Use HAL_MDMA_PollForTransfer() to poll for the end of current transfer or a transfer level
+             In this case a fixed Timeout can be configured by User depending from his application.
+          (+) Use HAL_MDMA_Abort() function to abort the current transfer : blocking method this API returns
+              when the abort ends or timeout (should not be called from an interrupt service routine).
+
+     *** Interrupt mode IO operation ***
+     ===================================
+    [..]
+          (+) Configure the MDMA interrupt priority using HAL_NVIC_SetPriority()
+          (+) Enable the MDMA IRQ handler using HAL_NVIC_EnableIRQ()
+          (+) Use HAL_MDMA_Start_IT() to start MDMA transfer after the configuration of
+              Source address and destination address and the Length of data to be transferred. In this
+              case the MDMA interrupt is configured.
+          (+) Use HAL_MDMA_IRQHandler() called under MDMA_IRQHandler() Interrupt subroutine
+          (+) At the end of data transfer HAL_MDMA_IRQHandler() function is executed and user can
+              add his own function by customization of function pointer XferCpltCallback and
+              XferErrorCallback (i.e a member of MDMA handle structure).
+
+          (+) Use HAL_MDMA_Abort_IT() function to abort the current transfer : non-blocking method. This API will finish the execution immediately
+              then the callback XferAbortCallback (if specified  by the user) is asserted once the MDMA channel has effectively aborted.
+              (could be called from an interrupt service routine).
+
+          (+) Use functions HAL_MDMA_RegisterCallback and HAL_MDMA_UnRegisterCallback respectevely to register unregister user callbacks
+              from the following list :
+              (++) XferCpltCallback            : transfer complete callback.
+              (++) XferBufferCpltCallback      : buffer transfer complete callback.
+              (++) XferBlockCpltCallback       : block transfer complete callback.
+              (++) XferRepeatBlockCpltCallback : repeated block transfer complete callback.
+              (++) XferErrorCallback           : transfer error callback.
+              (++) XferAbortCallback           : transfer abort complete callback.
+
+    [..]
+         (+)  If the transfer Request corresponds to SW request (MDMA_REQUEST_SW) User can use function HAL_MDMA_GenerateSWRequest to
+              trigger requests manually. Function HAL_MDMA_GenerateSWRequest must be used with the following precautions:
+              (++) This function returns an error if used while the Transfer has ended or not started.
+              (++) If used while the current request has not been served yet (current request transfer on going)
+                this function returns an error and the new request is ignored.
+
+              Generally this function should be used in conjunctions with the MDMA callbacks:
+              (++) example 1:
+                 (+++) Configure a transfer with request set to MDMA_REQUEST_SW and trigger mode set to MDMA_BUFFER_TRANSFER
+                 (+++) Register a callback for buffer transfer complete (using callback ID set to HAL_MDMA_XFER_BUFFERCPLT_CB_ID)
+                 (+++) After calling HAL_MDMA_Start_IT the MDMA will issue the transfer of a first BufferTransferLength data.
+                 (+++) When the buffer transfer complete callback is asserted first buffer has been transferred and user can ask for a new buffer transfer
+                   request using HAL_MDMA_GenerateSWRequest.
+
+              (++) example 2:
+                 (+++) Configure a transfer with request set to MDMA_REQUEST_SW and trigger mode set to MDMA_BLOCK_TRANSFER
+                 (+++) Register a callback for block transfer complete (using callback ID HAL_MDMA_XFER_BLOCKCPLT_CB_ID)
+                 (+++) After calling HAL_MDMA_Start_IT the MDMA will issue the transfer of a first block of data.
+                 (+++) When the block transfer complete callback is asserted the first block has been transferred and user can ask
+                   for a new block transfer request using HAL_MDMA_GenerateSWRequest.
+
+    [..]  Use HAL_MDMA_GetState() function to return the MDMA state and HAL_MDMA_GetError() in case of error detection.
+
+     *** MDMA HAL driver macros list ***
+     =============================================
+     [..]
+       Below the list of most used macros in MDMA HAL driver.
+
+      (+) __HAL_MDMA_ENABLE: Enable the specified MDMA Channel.
+      (+) __HAL_MDMA_DISABLE: Disable the specified MDMA Channel.
+      (+) __HAL_MDMA_GET_FLAG: Get the MDMA Channel pending flags.
+      (+) __HAL_MDMA_CLEAR_FLAG: Clear the MDMA Channel pending flags.
+      (+) __HAL_MDMA_ENABLE_IT: Enable the specified MDMA Channel interrupts.
+      (+) __HAL_MDMA_DISABLE_IT: Disable the specified MDMA Channel interrupts.
+      (+) __HAL_MDMA_GET_IT_SOURCE: Check whether the specified MDMA Channel interrupt has occurred or not.
+
+     [..]
+      (@) You can refer to the header file of the MDMA HAL driver for more useful macros.
+
+    [..]
+
+  @endverbatim
+  */
+
+/* Includes ------------------------------------------------------------------*/
+#include "stm32h7xx_hal.h"
+
+/** @addtogroup STM32H7xx_HAL_Driver
+  * @{
+  */
+
+/** @defgroup MDMA  MDMA
+  * @brief MDMA HAL module driver
+  * @{
+  */
+
+#ifdef HAL_MDMA_MODULE_ENABLED
+
+/* Private typedef -----------------------------------------------------------*/
+/* Private constants ---------------------------------------------------------*/
+/** @addtogroup MDMA_Private_Constants
+ * @{
+ */
+#define HAL_TIMEOUT_MDMA_ABORT    5U    /* 5 ms */
+#define HAL_MDMA_CHANNEL_SIZE     0x40U /* an MDMA instance channel size is 64 byte  */
+/**
+  * @}
+  */
+/* Private macro -------------------------------------------------------------*/
+/* Private variables ---------------------------------------------------------*/
+/* Private function prototypes -----------------------------------------------*/
+/** @addtogroup MDMA_Private_Functions_Prototypes
+  * @{
+  */
+static void MDMA_SetConfig(MDMA_HandleTypeDef *hmdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t BlockDataLength, uint32_t BlockCount);
+static void MDMA_Init(MDMA_HandleTypeDef *hmdma);
+
+/**
+  * @}
+  */
+
+/** @addtogroup MDMA_Exported_Functions MDMA Exported Functions
+  * @{
+  */
+
+/** @addtogroup MDMA_Exported_Functions_Group1
+  *
+@verbatim
+ ===============================================================================
+             ##### Initialization and de-initialization functions  #####
+ ===============================================================================
+    [..]
+    This section provides functions allowing to :
+      Initialize and de-initialize the MDMA channel.
+      Register and Unregister MDMA callbacks
+    [..]
+    The HAL_MDMA_Init() function follows the MDMA channel configuration procedures as described in
+    reference manual.
+    The HAL_MDMA_DeInit function allows to deinitialize the MDMA channel.
+    HAL_MDMA_RegisterCallback and  HAL_MDMA_UnRegisterCallback functions allows
+    respectevely to register/unregister an MDMA callback function.
+
+@endverbatim
+  * @{
+  */
+
+/**
+  * @brief  Initializes the MDMA according to the specified
+  *         parameters in the MDMA_InitTypeDef and create the associated handle.
+  * @param  hmdma: Pointer to a MDMA_HandleTypeDef structure that contains
+  *               the configuration information for the specified MDMA Channel.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_Init(MDMA_HandleTypeDef *hmdma)
+{
+  uint32_t tickstart = HAL_GetTick();
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Check the parameters */
+  assert_param(IS_MDMA_STREAM_ALL_INSTANCE(hmdma->Instance));
+  assert_param(IS_MDMA_PRIORITY(hmdma->Init.Priority));
+  assert_param(IS_MDMA_ENDIANNESS_MODE(hmdma->Init.Endianness));
+  assert_param(IS_MDMA_REQUEST(hmdma->Init.Request));
+  assert_param(IS_MDMA_SOURCE_INC(hmdma->Init.SourceInc));
+  assert_param(IS_MDMA_DESTINATION_INC(hmdma->Init.DestinationInc));
+  assert_param(IS_MDMA_SOURCE_DATASIZE(hmdma->Init.SourceDataSize));
+  assert_param(IS_MDMA_DESTINATION_DATASIZE(hmdma->Init.DestDataSize));
+  assert_param(IS_MDMA_DATA_ALIGNMENT(hmdma->Init.DataAlignment));
+  assert_param(IS_MDMA_SOURCE_BURST(hmdma->Init.SourceBurst));
+  assert_param(IS_MDMA_DESTINATION_BURST(hmdma->Init.DestBurst));
+  assert_param(IS_MDMA_BUFFER_TRANSFER_LENGTH(hmdma->Init.BufferTransferLength));
+  assert_param(IS_MDMA_TRANSFER_TRIGGER_MODE(hmdma->Init.TransferTriggerMode));
+  assert_param(IS_MDMA_BLOCK_ADDR_OFFSET(hmdma->Init.SourceBlockAddressOffset));
+  assert_param(IS_MDMA_BLOCK_ADDR_OFFSET(hmdma->Init.DestBlockAddressOffset));
+
+
+  /* Allocate lock resource */
+  __HAL_UNLOCK(hmdma);
+
+  /* Change MDMA peripheral state */
+  hmdma->State = HAL_MDMA_STATE_BUSY;
+
+  /* Disable the MDMA channel */
+  __HAL_MDMA_DISABLE(hmdma);
+
+  /* Check if the MDMA channel is effectively disabled */
+  while((hmdma->Instance->CCR & MDMA_CCR_EN) != 0U)
+  {
+    /* Check for the Timeout */
+    if((HAL_GetTick() - tickstart ) > HAL_TIMEOUT_MDMA_ABORT)
+    {
+      /* Update error code */
+      hmdma->ErrorCode = HAL_MDMA_ERROR_TIMEOUT;
+
+      /* Change the MDMA state */
+      hmdma->State = HAL_MDMA_STATE_ERROR;
+
+      return HAL_ERROR;
+    }
+  }
+
+  /* Initialize the MDMA channel registers */
+  MDMA_Init(hmdma);
+
+  /* Reset the MDMA first/last linkedlist node addresses and node counter */
+  hmdma->FirstLinkedListNodeAddress  = 0;
+  hmdma->LastLinkedListNodeAddress   = 0;
+  hmdma->LinkedListNodeCounter  = 0;
+
+  /* Initialize the error code */
+  hmdma->ErrorCode = HAL_MDMA_ERROR_NONE;
+
+  /* Initialize the MDMA state */
+  hmdma->State = HAL_MDMA_STATE_READY;
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  DeInitializes the MDMA peripheral
+  * @param  hmdma: pointer to a MDMA_HandleTypeDef structure that contains
+  *               the configuration information for the specified MDMA Channel.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_DeInit(MDMA_HandleTypeDef *hmdma)
+{
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Disable the selected MDMA Channelx */
+  __HAL_MDMA_DISABLE(hmdma);
+
+  /* Reset MDMA Channel control register */
+  hmdma->Instance->CCR  = 0;
+  hmdma->Instance->CTCR = 0;
+  hmdma->Instance->CBNDTR = 0;
+  hmdma->Instance->CSAR = 0;
+  hmdma->Instance->CDAR = 0;
+  hmdma->Instance->CBRUR = 0;
+  hmdma->Instance->CLAR = 0;
+  hmdma->Instance->CTBR = 0;
+  hmdma->Instance->CMAR = 0;
+  hmdma->Instance->CMDR = 0;
+
+  /* Clear all flags */
+  __HAL_MDMA_CLEAR_FLAG(hmdma,(MDMA_FLAG_TE | MDMA_FLAG_CTC | MDMA_FLAG_BRT | MDMA_FLAG_BT | MDMA_FLAG_BFTC));
+
+  /* Reset the  MDMA first/last linkedlist node addresses and node counter */
+  hmdma->FirstLinkedListNodeAddress  = 0;
+  hmdma->LastLinkedListNodeAddress   = 0;
+  hmdma->LinkedListNodeCounter  = 0;
+
+  /* Initialize the error code */
+  hmdma->ErrorCode = HAL_MDMA_ERROR_NONE;
+
+  /* Initialize the MDMA state */
+  hmdma->State = HAL_MDMA_STATE_RESET;
+
+  /* Release Lock */
+  __HAL_UNLOCK(hmdma);
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Config the Post request Mask address and Mask data
+  * @param  hmdma      : pointer to a MDMA_HandleTypeDef structure that contains
+  *                               the configuration information for the specified MDMA Channel.
+  * @param  MaskAddress: specifies the address to be updated (written) with MaskData after a request is served.
+  * @param  MaskData:    specifies the value to be written to MaskAddress after a request is served.
+  *                      MaskAddress and MaskData could be used to automatically clear a peripheral flag when the request is served.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_ConfigPostRequestMask(MDMA_HandleTypeDef *hmdma, uint32_t MaskAddress, uint32_t MaskData)
+{
+  HAL_StatusTypeDef  status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* if HW request set Post Request MaskAddress and MaskData,  */
+    if((hmdma->Instance->CTCR & MDMA_CTCR_SWRM) == 0U)
+    {
+      /* Set the HW request clear Mask and Data */
+      hmdma->Instance->CMAR = MaskAddress;
+      hmdma->Instance->CMDR = MaskData;
+
+      /*
+      -If the request is done by SW : BWM could be set to 1 or 0.
+      -If the request is done by a peripheral :
+         If mask address not set (0) => BWM must be set to 0
+         If mask address set (different than 0) => BWM could be set to 1 or 0
+      */
+      if(MaskAddress == 0U)
+      {
+        hmdma->Instance->CTCR &=  ~MDMA_CTCR_BWM;
+      }
+      else
+      {
+        hmdma->Instance->CTCR |=  MDMA_CTCR_BWM;
+      }
+    }
+    else
+    {
+      /* Return error status */
+      status =  HAL_ERROR;
+    }
+  }
+  else
+  {
+    /* Return error status */
+    status =  HAL_ERROR;
+  }
+  /* Release Lock */
+  __HAL_UNLOCK(hmdma);
+
+  return status;
+}
+
+/**
+  * @brief  Register callbacks
+  * @param  hmdma:                pointer to a MDMA_HandleTypeDef structure that contains
+  *                               the configuration information for the specified MDMA Channel.
+  * @param  CallbackID:           User Callback identifier
+  * @param  pCallback:            pointer to callbacsk function.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_RegisterCallback(MDMA_HandleTypeDef *hmdma, HAL_MDMA_CallbackIDTypeDef CallbackID, void (* pCallback)(MDMA_HandleTypeDef *_hmdma))
+{
+  HAL_StatusTypeDef status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    switch (CallbackID)
+    {
+    case  HAL_MDMA_XFER_CPLT_CB_ID:
+      hmdma->XferCpltCallback = pCallback;
+      break;
+
+    case  HAL_MDMA_XFER_BUFFERCPLT_CB_ID:
+      hmdma->XferBufferCpltCallback = pCallback;
+      break;
+
+    case  HAL_MDMA_XFER_BLOCKCPLT_CB_ID:
+      hmdma->XferBlockCpltCallback = pCallback;
+      break;
+
+    case  HAL_MDMA_XFER_REPBLOCKCPLT_CB_ID:
+      hmdma->XferRepeatBlockCpltCallback = pCallback;
+      break;
+
+    case  HAL_MDMA_XFER_ERROR_CB_ID:
+      hmdma->XferErrorCallback = pCallback;
+      break;
+
+    case  HAL_MDMA_XFER_ABORT_CB_ID:
+      hmdma->XferAbortCallback = pCallback;
+      break;
+
+    default:
+      break;
+    }
+  }
+  else
+  {
+    /* Return error status */
+    status =  HAL_ERROR;
+  }
+
+  /* Release Lock */
+  __HAL_UNLOCK(hmdma);
+
+  return status;
+}
+
+/**
+  * @brief  UnRegister callbacks
+  * @param  hmdma:                 pointer to a MDMA_HandleTypeDef structure that contains
+  *                               the configuration information for the specified MDMA Channel.
+  * @param  CallbackID:           User Callback identifier
+  *                               a HAL_MDMA_CallbackIDTypeDef ENUM as parameter.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_UnRegisterCallback(MDMA_HandleTypeDef *hmdma, HAL_MDMA_CallbackIDTypeDef CallbackID)
+{
+  HAL_StatusTypeDef status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    switch (CallbackID)
+    {
+    case  HAL_MDMA_XFER_CPLT_CB_ID:
+      hmdma->XferCpltCallback = NULL;
+      break;
+
+    case  HAL_MDMA_XFER_BUFFERCPLT_CB_ID:
+      hmdma->XferBufferCpltCallback = NULL;
+      break;
+
+    case  HAL_MDMA_XFER_BLOCKCPLT_CB_ID:
+      hmdma->XferBlockCpltCallback = NULL;
+      break;
+
+    case  HAL_MDMA_XFER_REPBLOCKCPLT_CB_ID:
+      hmdma->XferRepeatBlockCpltCallback = NULL;
+      break;
+
+    case  HAL_MDMA_XFER_ERROR_CB_ID:
+      hmdma->XferErrorCallback = NULL;
+      break;
+
+    case  HAL_MDMA_XFER_ABORT_CB_ID:
+      hmdma->XferAbortCallback = NULL;
+      break;
+
+    case   HAL_MDMA_XFER_ALL_CB_ID:
+      hmdma->XferCpltCallback = NULL;
+      hmdma->XferBufferCpltCallback = NULL;
+      hmdma->XferBlockCpltCallback = NULL;
+      hmdma->XferRepeatBlockCpltCallback = NULL;
+      hmdma->XferErrorCallback = NULL;
+      hmdma->XferAbortCallback = NULL;
+      break;
+
+    default:
+      status = HAL_ERROR;
+      break;
+    }
+  }
+  else
+  {
+    status = HAL_ERROR;
+  }
+
+  /* Release Lock */
+  __HAL_UNLOCK(hmdma);
+
+  return status;
+}
+
+/**
+  * @}
+  */
+
+/** @addtogroup MDMA_Exported_Functions_Group2
+ *
+@verbatim
+ ===============================================================================
+                      #####  Linked list operation functions  #####
+ ===============================================================================
+    [..]  This section provides functions allowing to:
+      (+) Create a linked list node
+      (+) Add a node to the MDMA linked list
+      (+) Remove a node from the MDMA linked list
+      (+) Enable/Disable linked list circular mode
+@endverbatim
+  * @{
+  */
+
+/**
+  * @brief  Initializes an MDMA Link Node according to the specified
+  *         parameters in the pMDMA_LinkedListNodeConfig .
+  * @param  pNode: Pointer to a MDMA_LinkNodeTypeDef structure that contains Linked list node
+  *         registers configurations.
+  * @param  pNodeConfig: Pointer to a MDMA_LinkNodeConfTypeDef structure that contains
+  *               the configuration information for the specified MDMA Linked List Node.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_LinkedList_CreateNode(MDMA_LinkNodeTypeDef *pNode, MDMA_LinkNodeConfTypeDef *pNodeConfig)
+{
+  uint32_t addressMask;
+  uint32_t blockoffset;
+
+  /* Check the MDMA peripheral state */
+  if((pNode == NULL) || (pNodeConfig == NULL))
+  {
+    return HAL_ERROR;
+  }
+
+  /* Check the parameters */
+  assert_param(IS_MDMA_PRIORITY(pNodeConfig->Init.Priority));
+  assert_param(IS_MDMA_ENDIANNESS_MODE(pNodeConfig->Init.Endianness));
+  assert_param(IS_MDMA_REQUEST(pNodeConfig->Init.Request));
+  assert_param(IS_MDMA_SOURCE_INC(pNodeConfig->Init.SourceInc));
+  assert_param(IS_MDMA_DESTINATION_INC(pNodeConfig->Init.DestinationInc));
+  assert_param(IS_MDMA_SOURCE_DATASIZE(pNodeConfig->Init.SourceDataSize));
+  assert_param(IS_MDMA_DESTINATION_DATASIZE(pNodeConfig->Init.DestDataSize));
+  assert_param(IS_MDMA_DATA_ALIGNMENT(pNodeConfig->Init.DataAlignment));
+  assert_param(IS_MDMA_SOURCE_BURST(pNodeConfig->Init.SourceBurst));
+  assert_param(IS_MDMA_DESTINATION_BURST(pNodeConfig->Init.DestBurst));
+  assert_param(IS_MDMA_BUFFER_TRANSFER_LENGTH(pNodeConfig->Init.BufferTransferLength));
+  assert_param(IS_MDMA_TRANSFER_TRIGGER_MODE(pNodeConfig->Init.TransferTriggerMode));
+  assert_param(IS_MDMA_BLOCK_ADDR_OFFSET(pNodeConfig->Init.SourceBlockAddressOffset));
+  assert_param(IS_MDMA_BLOCK_ADDR_OFFSET(pNodeConfig->Init.DestBlockAddressOffset));
+
+  assert_param(IS_MDMA_TRANSFER_LENGTH(pNodeConfig->BlockDataLength));
+  assert_param(IS_MDMA_BLOCK_COUNT(pNodeConfig->BlockCount));
+
+
+  /* Configure next Link node Address Register to zero */
+  pNode->CLAR =  0;
+
+  /* Configure the Link Node registers*/
+  pNode->CTBR   = 0;
+  pNode->CMAR   = 0;
+  pNode->CMDR   = 0;
+  pNode->Reserved = 0;
+
+  /* Write new CTCR Register value */
+  pNode->CTCR =  pNodeConfig->Init.SourceInc | pNodeConfig->Init.DestinationInc | \
+    pNodeConfig->Init.SourceDataSize | pNodeConfig->Init.DestDataSize           | \
+      pNodeConfig->Init.DataAlignment| pNodeConfig->Init.SourceBurst            | \
+        pNodeConfig->Init.DestBurst                                             | \
+          ((pNodeConfig->Init.BufferTransferLength - 1U) << MDMA_CTCR_TLEN_Pos) | \
+            pNodeConfig->Init.TransferTriggerMode;
+
+  /* If SW request set the CTCR register to SW Request Mode*/
+  if(pNodeConfig->Init.Request == MDMA_REQUEST_SW)
+  {
+    pNode->CTCR |= MDMA_CTCR_SWRM;
+  }
+
+  /*
+  -If the request is done by SW : BWM could be set to 1 or 0.
+  -If the request is done by a peripheral :
+     If mask address not set (0) => BWM must be set to 0
+     If mask address set (different than 0) => BWM could be set to 1 or 0
+  */
+  if((pNodeConfig->Init.Request == MDMA_REQUEST_SW) || (pNodeConfig->PostRequestMaskAddress != 0U))
+  {
+    pNode->CTCR |=  MDMA_CTCR_BWM;
+  }
+
+  /* Set the new CBNDTR Register value */
+  pNode->CBNDTR = ((pNodeConfig->BlockCount - 1U) << MDMA_CBNDTR_BRC_Pos) & MDMA_CBNDTR_BRC;
+
+  /* if block source address offset is negative set the Block Repeat Source address Update Mode to decrement */
+  if(pNodeConfig->Init.SourceBlockAddressOffset < 0)
+  {
+    pNode->CBNDTR |= MDMA_CBNDTR_BRSUM;
+    /*write new CBRUR Register value : source repeat block offset */
+    blockoffset = (uint32_t)(- pNodeConfig->Init.SourceBlockAddressOffset);
+    pNode->CBRUR = blockoffset & 0x0000FFFFU;
+  }
+  else
+  {
+    /*write new CBRUR Register value : source repeat block offset */
+    pNode->CBRUR = (((uint32_t) pNodeConfig->Init.SourceBlockAddressOffset) & 0x0000FFFFU);
+  }
+
+  /* if block destination address offset is negative set the Block Repeat destination address Update Mode to decrement */
+  if(pNodeConfig->Init.DestBlockAddressOffset < 0)
+  {
+    pNode->CBNDTR |= MDMA_CBNDTR_BRDUM;
+    /*write new CBRUR Register value : destination repeat block offset */
+    blockoffset = (uint32_t)(- pNodeConfig->Init.DestBlockAddressOffset);
+    pNode->CBRUR |= ((blockoffset & 0x0000FFFFU) << MDMA_CBRUR_DUV_Pos);
+  }
+  else
+  {
+    /*write new CBRUR Register value : destination repeat block offset */
+    pNode->CBRUR |= ((((uint32_t)pNodeConfig->Init.DestBlockAddressOffset) & 0x0000FFFFU) << MDMA_CBRUR_DUV_Pos);
+  }
+
+  /* Configure MDMA Link Node data length */
+  pNode->CBNDTR |=  pNodeConfig->BlockDataLength;
+
+  /* Configure MDMA Link Node destination address */
+  pNode->CDAR = pNodeConfig->DstAddress;
+
+  /* Configure MDMA Link Node Source address */
+  pNode->CSAR = pNodeConfig->SrcAddress;
+
+  /* if HW request set the HW request and the requet CleraMask and ClearData MaskData,  */
+  if(pNodeConfig->Init.Request != MDMA_REQUEST_SW)
+  {
+    /* Set the HW request in CTBR register  */
+    pNode->CTBR = pNodeConfig->Init.Request & MDMA_CTBR_TSEL;
+    /* Set the HW request clear Mask and Data */
+    pNode->CMAR = pNodeConfig->PostRequestMaskAddress;
+    pNode->CMDR = pNodeConfig->PostRequestMaskData;
+  }
+
+  addressMask = pNodeConfig->SrcAddress & 0xFF000000U;
+  if((addressMask == 0x20000000U) || (addressMask == 0x00000000U))
+  {
+    /*The AHBSbus is used as source (read operation) on channel x */
+    pNode->CTBR |= MDMA_CTBR_SBUS;
+  }
+
+  addressMask = pNodeConfig->DstAddress & 0xFF000000U;
+  if((addressMask == 0x20000000U) || (addressMask == 0x00000000U))
+  {
+    /*The AHB bus is used as destination (write operation) on channel x */
+    pNode->CTBR |= MDMA_CTBR_DBUS;
+  }
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Connect a node to the linked list.
+  * @param  hmdma    : Pointer to a MDMA_HandleTypeDef structure that contains
+  *                    the configuration information for the specified MDMA Channel.
+  * @param  pNewNode : Pointer to a MDMA_LinkNodeTypeDef structure that contains Linked list node
+  *                    to be add to the list.
+  * @param pPrevNode : Pointer to the new node position in the linked list or zero to insert the new node
+  *                    at the end of the list
+  *
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_LinkedList_AddNode(MDMA_HandleTypeDef *hmdma, MDMA_LinkNodeTypeDef *pNewNode, const MDMA_LinkNodeTypeDef *pPrevNode)
+{
+  MDMA_LinkNodeTypeDef *pNode;
+  uint32_t counter = 0, nodeInserted = 0;
+  HAL_StatusTypeDef hal_status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if((hmdma == NULL) || (pNewNode == NULL))
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* Change MDMA peripheral state */
+    hmdma->State = HAL_MDMA_STATE_BUSY;
+
+    /* Check if this is the first node (after the Inititlization node) */
+    if((uint32_t)hmdma->FirstLinkedListNodeAddress == 0U)
+    {
+      if(pPrevNode == NULL)
+      {
+        /* if this is the first node after the initialization
+        connect this node to the node 0 by updating
+        the MDMA channel CLAR register to this node address */
+        hmdma->Instance->CLAR = (uint32_t)pNewNode;
+        /* Set the MDMA handle First linked List node*/
+        hmdma->FirstLinkedListNodeAddress = pNewNode;
+
+        /*reset New node link */
+        pNewNode->CLAR = 0;
+
+        /* Update the Handle last node address */
+        hmdma->LastLinkedListNodeAddress = pNewNode;
+
+        hmdma->LinkedListNodeCounter = 1;
+      }
+      else
+      {
+        hal_status = HAL_ERROR;
+      }
+    }
+    else if(hmdma->FirstLinkedListNodeAddress != pNewNode)
+    {
+      /* Check if the node to insert already exists*/
+      pNode = hmdma->FirstLinkedListNodeAddress;
+      while((counter < hmdma->LinkedListNodeCounter) && (hal_status == HAL_OK))
+      {
+        if(pNode->CLAR == (uint32_t)pNewNode)
+        {
+          hal_status = HAL_ERROR; /* error this node already exist in the linked list and it is not first node */
+        }
+        pNode = (MDMA_LinkNodeTypeDef *)pNode->CLAR;
+        counter++;
+      }
+
+      if(hal_status == HAL_OK)
+      {
+        /* Check if the previous node is the last one in the current list or zero */
+        if((pPrevNode == hmdma->LastLinkedListNodeAddress) || (pPrevNode == NULL))
+        {
+          /* insert the new node at the end of the list */
+          pNewNode->CLAR = hmdma->LastLinkedListNodeAddress->CLAR;
+          hmdma->LastLinkedListNodeAddress->CLAR = (uint32_t)pNewNode;
+          /* Update the Handle last node address */
+          hmdma->LastLinkedListNodeAddress = pNewNode;
+          /* Increment the linked list node counter */
+          hmdma->LinkedListNodeCounter++;
+        }
+        else
+        {
+          /*insert the new node after the pPreviousNode node */
+          pNode = hmdma->FirstLinkedListNodeAddress;
+          counter = 0;
+          while((counter < hmdma->LinkedListNodeCounter) && (nodeInserted == 0U))
+          {
+            counter++;
+            if(pNode == pPrevNode)
+            {
+              /*Insert the new node after the previous one */
+              pNewNode->CLAR = pNode->CLAR;
+              pNode->CLAR = (uint32_t)pNewNode;
+              /* Increment the linked list node counter */
+              hmdma->LinkedListNodeCounter++;
+              nodeInserted = 1;
+            }
+            else
+            {
+              pNode = (MDMA_LinkNodeTypeDef *)pNode->CLAR;
+            }
+          }
+
+          if(nodeInserted == 0U)
+          {
+            hal_status = HAL_ERROR;
+          }
+        }
+      }
+    }
+    else
+    {
+      hal_status = HAL_ERROR;
+    }
+
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    hmdma->State = HAL_MDMA_STATE_READY;
+
+    return hal_status;
+  }
+  else
+  {
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    /* Return error status */
+    return HAL_BUSY;
+  }
+}
+
+/**
+  * @brief  Disconnect/Remove a node from the transfer linked list.
+  * @param  hmdma : Pointer to a MDMA_HandleTypeDef structure that contains
+  *                 the configuration information for the specified MDMA Channel.
+  * @param  pNode : Pointer to a MDMA_LinkNodeTypeDef structure that contains Linked list node
+  *                 to be removed from the list.
+  *
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_LinkedList_RemoveNode(MDMA_HandleTypeDef *hmdma, MDMA_LinkNodeTypeDef *pNode)
+{
+  MDMA_LinkNodeTypeDef *ptmpNode;
+  uint32_t counter = 0, nodeDeleted = 0;
+  HAL_StatusTypeDef hal_status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if((hmdma == NULL) || (pNode == NULL))
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* Change MDMA peripheral state */
+    hmdma->State = HAL_MDMA_STATE_BUSY;
+
+    /* If first and last node are null (no nodes in the list) : return error*/
+    if(((uint32_t)hmdma->FirstLinkedListNodeAddress == 0U) || ((uint32_t)hmdma->LastLinkedListNodeAddress == 0U) || (hmdma->LinkedListNodeCounter == 0U))
+    {
+      hal_status = HAL_ERROR;
+    }
+    else if(hmdma->FirstLinkedListNodeAddress == pNode) /* Deleting first node */
+    {
+      /* Delete 1st node */
+      if(hmdma->LastLinkedListNodeAddress == pNode)
+      {
+        /*if the last node is at the same time the first one (1 single node after the init node 0)
+        then update the last node too */
+
+        hmdma->FirstLinkedListNodeAddress = 0;
+        hmdma->LastLinkedListNodeAddress  = 0;
+        hmdma->LinkedListNodeCounter = 0;
+
+        hmdma->Instance->CLAR = 0;
+      }
+      else
+      {
+        if((uint32_t)hmdma->FirstLinkedListNodeAddress == hmdma->LastLinkedListNodeAddress->CLAR)
+        {
+          /* if last node is looping to first (circular list) one update the last node connection */
+          hmdma->LastLinkedListNodeAddress->CLAR = pNode->CLAR;
+        }
+
+        /* if deleting the first node after the initialization
+        connect the next node to the node 0 by updating
+        the MDMA channel CLAR register to this node address */
+        hmdma->Instance->CLAR = pNode->CLAR;
+        hmdma->FirstLinkedListNodeAddress = (MDMA_LinkNodeTypeDef *)hmdma->Instance->CLAR;
+        /* Update the Handle node counter */
+        hmdma->LinkedListNodeCounter--;
+      }
+    }
+    else /* Deleting any other node */
+    {
+      /*Deleted node is not the first one : find it  */
+      ptmpNode = hmdma->FirstLinkedListNodeAddress;
+      while((counter < hmdma->LinkedListNodeCounter) && (nodeDeleted == 0U))
+      {
+        counter++;
+        if(ptmpNode->CLAR == ((uint32_t)pNode))
+        {
+          /* if deleting the last node */
+          if(pNode == hmdma->LastLinkedListNodeAddress)
+          {
+            /*Update the linked list last node address in the handle*/
+            hmdma->LastLinkedListNodeAddress = ptmpNode;
+          }
+          /* update the next node link after deleting pMDMA_LinkedListNode */
+          ptmpNode->CLAR = pNode->CLAR;
+          nodeDeleted = 1;
+          /* Update the Handle node counter */
+          hmdma->LinkedListNodeCounter--;
+        }
+        else
+        {
+          ptmpNode = (MDMA_LinkNodeTypeDef *)ptmpNode->CLAR;
+        }
+      }
+
+      if(nodeDeleted == 0U)
+      {
+        /* last node reashed without finding the node to delete : return error */
+        hal_status = HAL_ERROR;
+      }
+    }
+
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    hmdma->State = HAL_MDMA_STATE_READY;
+
+    return hal_status;
+  }
+  else
+  {
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    /* Return error status */
+    return HAL_BUSY;
+  }
+}
+
+/**
+  * @brief  Make the linked list circular by connecting the last node to the first.
+  * @param  hmdma : Pointer to a MDMA_HandleTypeDef structure that contains
+  *                 the configuration information for the specified MDMA Channel.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_LinkedList_EnableCircularMode(MDMA_HandleTypeDef *hmdma)
+{
+  HAL_StatusTypeDef hal_status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* Change MDMA peripheral state */
+    hmdma->State = HAL_MDMA_STATE_BUSY;
+
+    /* If first and last node are null (no nodes in the list) : return error*/
+    if(((uint32_t)hmdma->FirstLinkedListNodeAddress == 0U) || ((uint32_t)hmdma->LastLinkedListNodeAddress == 0U) || (hmdma->LinkedListNodeCounter == 0U))
+    {
+      hal_status = HAL_ERROR;
+    }
+    else
+    {
+      /* to enable circular mode Last Node should be connected to first node */
+      hmdma->LastLinkedListNodeAddress->CLAR = (uint32_t)hmdma->FirstLinkedListNodeAddress;
+    }
+
+  }
+  /* Process unlocked */
+  __HAL_UNLOCK(hmdma);
+
+  hmdma->State = HAL_MDMA_STATE_READY;
+
+  return hal_status;
+}
+
+/**
+  * @brief  Disable the linked list circular mode by setting the last node connection to null
+  * @param  hmdma : Pointer to a MDMA_HandleTypeDef structure that contains
+  *                 the configuration information for the specified MDMA Channel.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_LinkedList_DisableCircularMode(MDMA_HandleTypeDef *hmdma)
+{
+  HAL_StatusTypeDef hal_status = HAL_OK;
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* Change MDMA peripheral state */
+    hmdma->State = HAL_MDMA_STATE_BUSY;
+
+    /* If first and last node are null (no nodes in the list) : return error*/
+    if(((uint32_t)hmdma->FirstLinkedListNodeAddress == 0U) || ((uint32_t)hmdma->LastLinkedListNodeAddress == 0U) || (hmdma->LinkedListNodeCounter == 0U))
+    {
+      hal_status = HAL_ERROR;
+    }
+    else
+    {
+      /* to disable circular mode Last Node should be connected to NULL */
+      hmdma->LastLinkedListNodeAddress->CLAR = 0;
+    }
+
+  }
+  /* Process unlocked */
+  __HAL_UNLOCK(hmdma);
+
+  hmdma->State = HAL_MDMA_STATE_READY;
+
+  return hal_status;
+}
+
+/**
+  * @}
+  */
+
+/** @addtogroup MDMA_Exported_Functions_Group3
+ *
+@verbatim
+ ===============================================================================
+                      #####  IO operation functions  #####
+ ===============================================================================
+    [..]  This section provides functions allowing to:
+      (+) Configure the source, destination address and data length and Start MDMA transfer
+      (+) Configure the source, destination address and data length and
+          Start MDMA transfer with interrupt
+      (+) Abort MDMA transfer
+      (+) Poll for transfer complete
+      (+) Generate a SW request (when Request is set to MDMA_REQUEST_SW)
+      (+) Handle MDMA interrupt request
+
+@endverbatim
+  * @{
+  */
+
+/**
+  * @brief  Starts the MDMA Transfer.
+  * @param  hmdma           : pointer to a MDMA_HandleTypeDef structure that contains
+  *                           the configuration information for the specified MDMA Channel.
+  * @param  SrcAddress      : The source memory Buffer address
+  * @param  DstAddress      : The destination memory Buffer address
+  * @param  BlockDataLength : The length of a block transfer in bytes
+  * @param  BlockCount      : The number of a blocks to be transfer
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_Start(MDMA_HandleTypeDef *hmdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t BlockDataLength, uint32_t BlockCount)
+{
+  /* Check the parameters */
+  assert_param(IS_MDMA_TRANSFER_LENGTH(BlockDataLength));
+  assert_param(IS_MDMA_BLOCK_COUNT(BlockCount));
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* Change MDMA peripheral state */
+    hmdma->State = HAL_MDMA_STATE_BUSY;
+
+    /* Initialize the error code */
+    hmdma->ErrorCode = HAL_MDMA_ERROR_NONE;
+
+    /* Disable the peripheral */
+    __HAL_MDMA_DISABLE(hmdma);
+
+    /* Configure the source, destination address and the data length */
+    MDMA_SetConfig(hmdma, SrcAddress, DstAddress, BlockDataLength, BlockCount);
+
+    /* Enable the Peripheral */
+    __HAL_MDMA_ENABLE(hmdma);
+
+    if(hmdma->Init.Request == MDMA_REQUEST_SW)
+    {
+      /* activate If SW request mode*/
+      hmdma->Instance->CCR |=  MDMA_CCR_SWRQ;
+    }
+  }
+  else
+  {
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    /* Return error status */
+    return HAL_BUSY;
+  }
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Starts the MDMA Transfer with interrupts enabled.
+  * @param  hmdma           : pointer to a MDMA_HandleTypeDef structure that contains
+  *                           the configuration information for the specified MDMA Channel.
+  * @param  SrcAddress      : The source memory Buffer address
+  * @param  DstAddress      : The destination memory Buffer address
+  * @param  BlockDataLength : The length of a block transfer in bytes
+  * @param  BlockCount      : The number of a blocks to be transfer
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_Start_IT(MDMA_HandleTypeDef *hmdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t BlockDataLength, uint32_t BlockCount)
+{
+  /* Check the parameters */
+  assert_param(IS_MDMA_TRANSFER_LENGTH(BlockDataLength));
+  assert_param(IS_MDMA_BLOCK_COUNT(BlockCount));
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Process locked */
+  __HAL_LOCK(hmdma);
+
+  if(HAL_MDMA_STATE_READY == hmdma->State)
+  {
+    /* Change MDMA peripheral state */
+    hmdma->State = HAL_MDMA_STATE_BUSY;
+
+    /* Initialize the error code */
+    hmdma->ErrorCode = HAL_MDMA_ERROR_NONE;
+
+    /* Disable the peripheral */
+    __HAL_MDMA_DISABLE(hmdma);
+
+    /* Configure the source, destination address and the data length */
+    MDMA_SetConfig(hmdma, SrcAddress, DstAddress, BlockDataLength, BlockCount);
+
+    /* Enable Common interrupts i.e Transfer Error IT and Channel Transfer Complete IT*/
+    __HAL_MDMA_ENABLE_IT(hmdma, (MDMA_IT_TE | MDMA_IT_CTC));
+
+    if(hmdma->XferBlockCpltCallback != NULL)
+    {
+      /* if Block transfer complete Callback is set enable the corresponding IT*/
+      __HAL_MDMA_ENABLE_IT(hmdma, MDMA_IT_BT);
+    }
+
+    if(hmdma->XferRepeatBlockCpltCallback != NULL)
+    {
+      /* if Repeated Block transfer complete Callback is set enable the corresponding IT*/
+      __HAL_MDMA_ENABLE_IT(hmdma, MDMA_IT_BRT);
+    }
+
+    if(hmdma->XferBufferCpltCallback != NULL)
+    {
+      /* if buffer transfer complete Callback is set enable the corresponding IT*/
+      __HAL_MDMA_ENABLE_IT(hmdma, MDMA_IT_BFTC);
+    }
+
+    /* Enable the Peripheral */
+    __HAL_MDMA_ENABLE(hmdma);
+
+    if(hmdma->Init.Request == MDMA_REQUEST_SW)
+    {
+      /* activate If SW request mode*/
+      hmdma->Instance->CCR |=  MDMA_CCR_SWRQ;
+    }
+  }
+  else
+  {
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    /* Return error status */
+    return HAL_BUSY;
+  }
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Aborts the MDMA Transfer.
+  * @param  hmdma  : pointer to a MDMA_HandleTypeDef structure that contains
+  *                 the configuration information for the specified MDMA Channel.
+  *
+  * @note  After disabling a MDMA Channel, a check for wait until the MDMA Channel is
+  *        effectively disabled is added. If a Channel is disabled
+  *        while a data transfer is ongoing, the current data will be transferred
+  *        and the Channel will be effectively disabled only after the transfer of
+  *        this single data is finished.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_Abort(MDMA_HandleTypeDef *hmdma)
+{
+  uint32_t tickstart =  HAL_GetTick();
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  if(HAL_MDMA_STATE_BUSY != hmdma->State)
+  {
+    hmdma->ErrorCode = HAL_MDMA_ERROR_NO_XFER;
+
+    /* Process Unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    return HAL_ERROR;
+  }
+  else
+  {
+    /* Disable all the transfer interrupts */
+    __HAL_MDMA_DISABLE_IT(hmdma, (MDMA_IT_TE | MDMA_IT_CTC | MDMA_IT_BT | MDMA_IT_BRT | MDMA_IT_BFTC));
+
+    /* Disable the channel */
+    __HAL_MDMA_DISABLE(hmdma);
+
+    /* Check if the MDMA Channel is effectively disabled */
+    while((hmdma->Instance->CCR & MDMA_CCR_EN) != 0U)
+    {
+      /* Check for the Timeout */
+      if( (HAL_GetTick()  - tickstart ) > HAL_TIMEOUT_MDMA_ABORT)
+      {
+        /* Update error code */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_TIMEOUT;
+
+        /* Process Unlocked */
+        __HAL_UNLOCK(hmdma);
+
+        /* Change the MDMA state */
+        hmdma->State = HAL_MDMA_STATE_ERROR;
+
+        return HAL_ERROR;
+      }
+    }
+
+    /* Clear all interrupt flags */
+    __HAL_MDMA_CLEAR_FLAG(hmdma, (MDMA_FLAG_TE | MDMA_FLAG_CTC | MDMA_FLAG_BT | MDMA_FLAG_BRT | MDMA_FLAG_BFTC));
+
+    /* Process Unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    /* Change the MDMA state*/
+    hmdma->State = HAL_MDMA_STATE_READY;
+  }
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Aborts the MDMA Transfer in Interrupt mode.
+  * @param  hmdma  : pointer to a MDMA_HandleTypeDef structure that contains
+  *                 the configuration information for the specified MDMA Channel.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_Abort_IT(MDMA_HandleTypeDef *hmdma)
+{
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  if(HAL_MDMA_STATE_BUSY != hmdma->State)
+  {
+    /* No transfer ongoing */
+    hmdma->ErrorCode = HAL_MDMA_ERROR_NO_XFER;
+
+    return HAL_ERROR;
+  }
+  else
+  {
+    /* Set Abort State  */
+    hmdma->State = HAL_MDMA_STATE_ABORT;
+
+    /* Disable the stream */
+    __HAL_MDMA_DISABLE(hmdma);
+  }
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Polling for transfer complete.
+  * @param  hmdma:          pointer to a MDMA_HandleTypeDef structure that contains
+  *                        the configuration information for the specified MDMA Channel.
+  * @param  CompleteLevel: Specifies the MDMA level complete.
+  * @param  Timeout:       Timeout duration.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_PollForTransfer(MDMA_HandleTypeDef *hmdma, HAL_MDMA_LevelCompleteTypeDef CompleteLevel, uint32_t Timeout)
+{
+  uint32_t levelFlag, errorFlag;
+  uint32_t tickstart;
+
+  /* Check the parameters */
+  assert_param(IS_MDMA_LEVEL_COMPLETE(CompleteLevel));
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  if(HAL_MDMA_STATE_BUSY != hmdma->State)
+  {
+    /* No transfer ongoing */
+    hmdma->ErrorCode = HAL_MDMA_ERROR_NO_XFER;
+
+    return HAL_ERROR;
+  }
+
+  /* Get the level transfer complete flag */
+  levelFlag = ((CompleteLevel == HAL_MDMA_FULL_TRANSFER)  ? MDMA_FLAG_CTC  : \
+               (CompleteLevel == HAL_MDMA_BUFFER_TRANSFER)? MDMA_FLAG_BFTC : \
+               (CompleteLevel == HAL_MDMA_BLOCK_TRANSFER) ? MDMA_FLAG_BT   : \
+               MDMA_FLAG_BRT);
+
+
+  /* Get timeout */
+  tickstart = HAL_GetTick();
+
+  while(__HAL_MDMA_GET_FLAG(hmdma, levelFlag) == 0U)
+  {
+    if((__HAL_MDMA_GET_FLAG(hmdma, MDMA_FLAG_TE) != 0U))
+    {
+      /* Get the transfer error source flag */
+      errorFlag = hmdma->Instance->CESR;
+
+      if((errorFlag & MDMA_CESR_TED) == 0U)
+      {
+        /* Update error code : Read Transfer error  */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_READ_XFER;
+      }
+      else
+      {
+        /* Update error code : Write Transfer error */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_WRITE_XFER;
+      }
+
+      if((errorFlag & MDMA_CESR_TEMD) != 0U)
+      {
+        /* Update error code : Error Mask Data */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_MASK_DATA;
+      }
+
+      if((errorFlag & MDMA_CESR_TELD) != 0U)
+      {
+        /* Update error code : Error Linked list */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_LINKED_LIST;
+      }
+
+      if((errorFlag & MDMA_CESR_ASE) != 0U)
+      {
+        /* Update error code : Address/Size alignment error */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_ALIGNMENT;
+      }
+
+      if((errorFlag & MDMA_CESR_BSE) != 0U)
+      {
+        /* Update error code : Block Size error */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_BLOCK_SIZE;
+      }
+
+      (void) HAL_MDMA_Abort(hmdma); /* if error then abort the current transfer */
+
+      /*
+        Note that the Abort function will
+          - Clear all transfer flags
+          - Unlock
+          - Set the State
+      */
+
+      return HAL_ERROR;
+
+    }
+
+    /* Check for the Timeout */
+    if(Timeout != HAL_MAX_DELAY)
+    {
+      if(((HAL_GetTick() - tickstart ) > Timeout) || (Timeout == 0U))
+      {
+        /* Update error code */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_TIMEOUT;
+
+        (void) HAL_MDMA_Abort(hmdma); /* if timeout then abort the current transfer */
+
+        /*
+          Note that the Abort function will
+            - Clear all transfer flags
+            - Unlock
+            - Set the State
+        */
+
+        return HAL_ERROR;
+      }
+    }
+  }
+
+  /* Clear the transfer level flag */
+  if(CompleteLevel == HAL_MDMA_BUFFER_TRANSFER)
+  {
+    __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_BFTC);
+
+  }
+  else if(CompleteLevel == HAL_MDMA_BLOCK_TRANSFER)
+  {
+    __HAL_MDMA_CLEAR_FLAG(hmdma, (MDMA_FLAG_BFTC | MDMA_FLAG_BT));
+
+  }
+  else if(CompleteLevel == HAL_MDMA_REPEAT_BLOCK_TRANSFER)
+  {
+    __HAL_MDMA_CLEAR_FLAG(hmdma, (MDMA_FLAG_BFTC | MDMA_FLAG_BT | MDMA_FLAG_BRT));
+  }
+  else if(CompleteLevel == HAL_MDMA_FULL_TRANSFER)
+  {
+    __HAL_MDMA_CLEAR_FLAG(hmdma, (MDMA_FLAG_BRT | MDMA_FLAG_BT | MDMA_FLAG_BFTC | MDMA_FLAG_CTC));
+
+    /* Process unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    hmdma->State = HAL_MDMA_STATE_READY;
+  }
+  else
+  {
+    return HAL_ERROR;
+  }
+
+  return HAL_OK;
+}
+
+/**
+  * @brief  Generate an MDMA SW request trigger to activate the request on the given Channel.
+  * @param  hmdma:       pointer to a MDMA_HandleTypeDef structure that contains
+  *                     the configuration information for the specified MDMA Stream.
+  * @retval HAL status
+  */
+HAL_StatusTypeDef HAL_MDMA_GenerateSWRequest(MDMA_HandleTypeDef *hmdma)
+{
+  uint32_t request_mode;
+
+  /* Check the MDMA peripheral handle */
+  if(hmdma == NULL)
+  {
+    return HAL_ERROR;
+  }
+
+  /* Get the softawre request mode */
+  request_mode = hmdma->Instance->CTCR & MDMA_CTCR_SWRM;
+
+  if((hmdma->Instance->CCR &  MDMA_CCR_EN) == 0U)
+  {
+    /* if no Transfer on going (MDMA enable bit not set) return error */
+    hmdma->ErrorCode = HAL_MDMA_ERROR_NO_XFER;
+
+    return HAL_ERROR;
+  }
+  else if(((hmdma->Instance->CISR &  MDMA_CISR_CRQA) != 0U) || (request_mode == 0U))
+  {
+    /* if an MDMA ongoing request has not yet end or if request mode is not SW request return error */
+    hmdma->ErrorCode = HAL_MDMA_ERROR_BUSY;
+
+    return HAL_ERROR;
+  }
+  else
+  {
+    /* Set the SW request bit to activate the request on the Channel */
+    hmdma->Instance->CCR |= MDMA_CCR_SWRQ;
+
+    return HAL_OK;
+  }
+}
+
+/**
+  * @brief  Handles MDMA interrupt request.
+  * @param  hmdma: pointer to a MDMA_HandleTypeDef structure that contains
+  *               the configuration information for the specified MDMA Channel.
+  * @retval None
+  */
+void HAL_MDMA_IRQHandler(MDMA_HandleTypeDef *hmdma)
+{
+  __IO uint32_t count = 0;
+  uint32_t timeout = SystemCoreClock / 9600U;
+
+  uint32_t generalIntFlag, errorFlag;
+
+  /* General Interrupt Flag management ****************************************/
+  generalIntFlag =  1UL << ((((uint32_t)hmdma->Instance - (uint32_t)(MDMA_Channel0))/HAL_MDMA_CHANNEL_SIZE) & 0x1FU);
+  if((MDMA->GISR0 & generalIntFlag) == 0U)
+  {
+    return; /* the  General interrupt flag for the current channel is down , nothing to do */
+  }
+
+  /* Transfer Error Interrupt management ***************************************/
+  if((__HAL_MDMA_GET_FLAG(hmdma, MDMA_FLAG_TE) != 0U))
+  {
+    if(__HAL_MDMA_GET_IT_SOURCE(hmdma, MDMA_IT_TE) != 0U)
+    {
+      /* Disable the transfer error interrupt */
+      __HAL_MDMA_DISABLE_IT(hmdma, MDMA_IT_TE);
+
+      /* Get the transfer error source flag */
+      errorFlag = hmdma->Instance->CESR;
+
+      if((errorFlag & MDMA_CESR_TED) == 0U)
+      {
+        /* Update error code : Read Transfer error  */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_READ_XFER;
+      }
+      else
+      {
+        /* Update error code : Write Transfer error */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_WRITE_XFER;
+      }
+
+      if((errorFlag & MDMA_CESR_TEMD) != 0U)
+      {
+        /* Update error code : Error Mask Data */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_MASK_DATA;
+      }
+
+      if((errorFlag & MDMA_CESR_TELD) != 0U)
+      {
+        /* Update error code : Error Linked list */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_LINKED_LIST;
+      }
+
+      if((errorFlag & MDMA_CESR_ASE) != 0U)
+      {
+        /* Update error code : Address/Size alignment error */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_ALIGNMENT;
+      }
+
+      if((errorFlag & MDMA_CESR_BSE) != 0U)
+      {
+        /* Update error code : Block Size error error */
+        hmdma->ErrorCode |= HAL_MDMA_ERROR_BLOCK_SIZE;
+      }
+
+      /* Clear the transfer error flags */
+      __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_TE);
+    }
+  }
+
+  /* Buffer Transfer Complete Interrupt management ******************************/
+  if((__HAL_MDMA_GET_FLAG(hmdma, MDMA_FLAG_BFTC) != 0U))
+  {
+    if(__HAL_MDMA_GET_IT_SOURCE(hmdma, MDMA_IT_BFTC) != 0U)
+    {
+      /* Clear the buffer transfer complete flag */
+      __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_BFTC);
+
+      if(hmdma->XferBufferCpltCallback != NULL)
+      {
+        /* Buffer transfer callback */
+        hmdma->XferBufferCpltCallback(hmdma);
+      }
+    }
+  }
+
+  /* Block Transfer Complete Interrupt management ******************************/
+  if((__HAL_MDMA_GET_FLAG(hmdma, MDMA_FLAG_BT) != 0U))
+  {
+    if(__HAL_MDMA_GET_IT_SOURCE(hmdma, MDMA_IT_BT) != 0U)
+    {
+      /* Clear the block transfer complete flag */
+      __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_BT);
+
+      if(hmdma->XferBlockCpltCallback != NULL)
+      {
+        /* Block transfer callback */
+        hmdma->XferBlockCpltCallback(hmdma);
+      }
+    }
+  }
+
+  /* Repeated Block Transfer Complete Interrupt management ******************************/
+  if((__HAL_MDMA_GET_FLAG(hmdma, MDMA_FLAG_BRT) != 0U))
+  {
+    if(__HAL_MDMA_GET_IT_SOURCE(hmdma, MDMA_IT_BRT) != 0U)
+    {
+      /* Clear the repeat block transfer complete flag */
+      __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_BRT);
+
+      if(hmdma->XferRepeatBlockCpltCallback != NULL)
+      {
+        /* Repeated Block transfer callback */
+        hmdma->XferRepeatBlockCpltCallback(hmdma);
+      }
+    }
+  }
+
+  /* Channel Transfer Complete Interrupt management ***********************************/
+  if((__HAL_MDMA_GET_FLAG(hmdma, MDMA_FLAG_CTC) != 0U))
+  {
+    if(__HAL_MDMA_GET_IT_SOURCE(hmdma, MDMA_IT_CTC) != 0U)
+    {
+      /* Disable all the transfer interrupts */
+      __HAL_MDMA_DISABLE_IT(hmdma, (MDMA_IT_TE | MDMA_IT_CTC | MDMA_IT_BT | MDMA_IT_BRT | MDMA_IT_BFTC));
+
+      if(HAL_MDMA_STATE_ABORT == hmdma->State)
+      {
+        /* Process Unlocked */
+        __HAL_UNLOCK(hmdma);
+
+        /* Change the DMA state */
+        hmdma->State = HAL_MDMA_STATE_READY;
+
+        if(hmdma->XferAbortCallback != NULL)
+        {
+          hmdma->XferAbortCallback(hmdma);
+        }
+        return;
+      }
+
+      /* Clear the Channel Transfer Complete flag */
+      __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_CTC);
+
+      /* Process Unlocked */
+      __HAL_UNLOCK(hmdma);
+
+      /* Change MDMA peripheral state */
+      hmdma->State = HAL_MDMA_STATE_READY;
+
+      if(hmdma->XferCpltCallback != NULL)
+      {
+        /* Channel Transfer Complete callback */
+        hmdma->XferCpltCallback(hmdma);
+      }
+    }
+  }
+
+  /* manage error case */
+  if(hmdma->ErrorCode != HAL_MDMA_ERROR_NONE)
+  {
+    hmdma->State = HAL_MDMA_STATE_ABORT;
+
+    /* Disable the channel */
+    __HAL_MDMA_DISABLE(hmdma);
+
+    do
+    {
+      if (++count > timeout)
+      {
+        break;
+      }
+    }
+    while((hmdma->Instance->CCR & MDMA_CCR_EN) != 0U);
+
+    /* Process Unlocked */
+    __HAL_UNLOCK(hmdma);
+
+    if((hmdma->Instance->CCR & MDMA_CCR_EN) != 0U)
+    {
+      /* Change the MDMA state to error if MDMA disable fails */
+      hmdma->State = HAL_MDMA_STATE_ERROR;
+    }
+    else
+    {
+      /* Change the MDMA state to Ready if MDMA disable success */
+      hmdma->State = HAL_MDMA_STATE_READY;
+    }
+
+
+    if (hmdma->XferErrorCallback != NULL)
+    {
+      /* Transfer error callback */
+      hmdma->XferErrorCallback(hmdma);
+    }
+  }
+}
+
+/**
+  * @}
+  */
+
+/** @addtogroup MDMA_Exported_Functions_Group4
+ *
+@verbatim
+ ===============================================================================
+                    ##### State and Errors functions #####
+ ===============================================================================
+    [..]
+    This subsection provides functions allowing to
+      (+) Check the MDMA state
+      (+) Get error code
+
+@endverbatim
+  * @{
+  */
+
+/**
+  * @brief  Returns the MDMA state.
+  * @param  hmdma: pointer to a MDMA_HandleTypeDef structure that contains
+  *               the configuration information for the specified MDMA Channel.
+  * @retval HAL state
+  */
+HAL_MDMA_StateTypeDef HAL_MDMA_GetState(const MDMA_HandleTypeDef *hmdma)
+{
+  return hmdma->State;
+}
+
+/**
+  * @brief  Return the MDMA error code
+  * @param  hmdma : pointer to a MDMA_HandleTypeDef structure that contains
+  *              the configuration information for the specified MDMA Channel.
+  * @retval MDMA Error Code
+  */
+uint32_t HAL_MDMA_GetError(const MDMA_HandleTypeDef *hmdma)
+{
+  return hmdma->ErrorCode;
+}
+
+/**
+  * @}
+  */
+
+/**
+  * @}
+  */
+
+/** @addtogroup MDMA_Private_Functions
+  * @{
+  */
+
+/**
+  * @brief  Sets the MDMA Transfer parameter.
+  * @param  hmdma:       pointer to a MDMA_HandleTypeDef structure that contains
+  *                     the configuration information for the specified MDMA Channel.
+  * @param  SrcAddress: The source memory Buffer address
+  * @param  DstAddress: The destination memory Buffer address
+  * @param  BlockDataLength : The length of a block transfer in bytes
+  * @param  BlockCount: The number of blocks to be transferred
+  * @retval HAL status
+  */
+static void MDMA_SetConfig(MDMA_HandleTypeDef *hmdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t BlockDataLength, uint32_t BlockCount)
+{
+  uint32_t addressMask;
+
+  /* Configure the MDMA Channel data length */
+  MODIFY_REG(hmdma->Instance->CBNDTR ,MDMA_CBNDTR_BNDT, (BlockDataLength & MDMA_CBNDTR_BNDT));
+
+  /* Configure the MDMA block repeat count */
+  MODIFY_REG(hmdma->Instance->CBNDTR , MDMA_CBNDTR_BRC , ((BlockCount - 1U) << MDMA_CBNDTR_BRC_Pos) & MDMA_CBNDTR_BRC);
+
+  /* Clear all interrupt flags */
+  __HAL_MDMA_CLEAR_FLAG(hmdma, MDMA_FLAG_TE | MDMA_FLAG_CTC | MDMA_CISR_BRTIF | MDMA_CISR_BTIF | MDMA_CISR_TCIF);
+
+  /* Configure MDMA Channel destination address */
+  hmdma->Instance->CDAR = DstAddress;
+
+  /* Configure MDMA Channel Source address */
+  hmdma->Instance->CSAR = SrcAddress;
+
+  addressMask = SrcAddress & 0xFF000000U;
+  if((addressMask == 0x20000000U) || (addressMask == 0x00000000U))
+  {
+    /*The AHBSbus is used as source (read operation) on channel x */
+    hmdma->Instance->CTBR |= MDMA_CTBR_SBUS;
+  }
+  else
+  {
+    /*The AXI bus is used as source (read operation) on channel x */
+    hmdma->Instance->CTBR &= (~MDMA_CTBR_SBUS);
+  }
+
+  addressMask = DstAddress & 0xFF000000U;
+  if((addressMask == 0x20000000U) || (addressMask == 0x00000000U))
+  {
+    /*The AHB bus is used as destination (write operation) on channel x */
+    hmdma->Instance->CTBR |= MDMA_CTBR_DBUS;
+  }
+  else
+  {
+    /*The AXI bus is used as destination (write operation) on channel x */
+    hmdma->Instance->CTBR &= (~MDMA_CTBR_DBUS);
+  }
+
+  /* Set the linked list register to the first node of the list */
+  hmdma->Instance->CLAR = (uint32_t)hmdma->FirstLinkedListNodeAddress;
+}
+
+/**
+  * @brief  Initializes the MDMA handle according to the specified
+  *         parameters in the MDMA_InitTypeDef
+  * @param  hmdma:       pointer to a MDMA_HandleTypeDef structure that contains
+  *                     the configuration information for the specified MDMA Channel.
+  * @retval None
+  */
+static void MDMA_Init(MDMA_HandleTypeDef *hmdma)
+{
+  uint32_t blockoffset;
+
+  /* Prepare the MDMA Channel configuration */
+  hmdma->Instance->CCR = hmdma->Init.Priority  | hmdma->Init.Endianness;
+
+  /* Write new CTCR Register value */
+  hmdma->Instance->CTCR =  hmdma->Init.SourceInc      | hmdma->Init.DestinationInc | \
+                           hmdma->Init.SourceDataSize | hmdma->Init.DestDataSize   | \
+                           hmdma->Init.DataAlignment  | hmdma->Init.SourceBurst    | \
+                           hmdma->Init.DestBurst                                   | \
+                           ((hmdma->Init.BufferTransferLength - 1U) << MDMA_CTCR_TLEN_Pos) | \
+                           hmdma->Init.TransferTriggerMode;
+
+  /* If SW request set the CTCR register to SW Request Mode */
+  if(hmdma->Init.Request == MDMA_REQUEST_SW)
+  {
+    /*
+    -If the request is done by SW : BWM could be set to 1 or 0.
+    -If the request is done by a peripheral :
+    If mask address not set (0) => BWM must be set to 0
+    If mask address set (different than 0) => BWM could be set to 1 or 0
+    */
+    hmdma->Instance->CTCR |= (MDMA_CTCR_SWRM | MDMA_CTCR_BWM);
+  }
+
+  /* Reset CBNDTR Register */
+  hmdma->Instance->CBNDTR = 0;
+
+  /* if block source address offset is negative set the Block Repeat Source address Update Mode to decrement */
+  if(hmdma->Init.SourceBlockAddressOffset < 0)
+  {
+    hmdma->Instance->CBNDTR |= MDMA_CBNDTR_BRSUM;
+    /* Write new CBRUR Register value : source repeat block offset */
+    blockoffset = (uint32_t)(- hmdma->Init.SourceBlockAddressOffset);
+    hmdma->Instance->CBRUR = (blockoffset & 0x0000FFFFU);
+  }
+  else
+  {
+    /* Write new CBRUR Register value : source repeat block offset */
+    hmdma->Instance->CBRUR = (((uint32_t)hmdma->Init.SourceBlockAddressOffset) & 0x0000FFFFU);
+  }
+
+  /* If block destination address offset is negative set the Block Repeat destination address Update Mode to decrement */
+  if(hmdma->Init.DestBlockAddressOffset < 0)
+  {
+    hmdma->Instance->CBNDTR |= MDMA_CBNDTR_BRDUM;
+    /* Write new CBRUR Register value : destination repeat block offset */
+    blockoffset = (uint32_t)(- hmdma->Init.DestBlockAddressOffset);
+    hmdma->Instance->CBRUR |= ((blockoffset & 0x0000FFFFU) << MDMA_CBRUR_DUV_Pos);
+  }
+  else
+  {
+    /*write new CBRUR Register value : destination repeat block offset */
+    hmdma->Instance->CBRUR |= ((((uint32_t)hmdma->Init.DestBlockAddressOffset) & 0x0000FFFFU) << MDMA_CBRUR_DUV_Pos);
+  }
+
+  /* if HW request set the HW request and the requet CleraMask and ClearData MaskData, */
+  if(hmdma->Init.Request != MDMA_REQUEST_SW)
+  {
+    /* Set the HW request in CTRB register  */
+    hmdma->Instance->CTBR = hmdma->Init.Request & MDMA_CTBR_TSEL;
+  }
+  else /* SW request : reset the CTBR register */
+  {
+    hmdma->Instance->CTBR = 0;
+  }
+
+  /* Write Link Address Register */
+  hmdma->Instance->CLAR =  0;
+}
+
+/**
+  * @}
+  */
+
+#endif /* HAL_MDMA_MODULE_ENABLED */
+/**
+  * @}
+  */
+
+/**
+  * @}
+  */
+
Index: ctrl/firmware/Main/CubeMX/FATFS/App/fatfs.c
===================================================================
--- ctrl/firmware/Main/CubeMX/FATFS/App/fatfs.c	(revision 74)
+++ ctrl/firmware/Main/CubeMX/FATFS/App/fatfs.c	(revision 75)
@@ -38,4 +38,16 @@
 }
 
+/**
+  * @brief  Gets Time from RTC
+  * @param  None
+  * @retval Time in DWORD
+  */
+DWORD get_fattime(void)
+{
+  /* USER CODE BEGIN get_fattime */
+  return 0;
+  /* USER CODE END get_fattime */
+}
+
 /* USER CODE BEGIN Application */
 
Index: ctrl/firmware/Main/CubeMX/FATFS/Target/ffconf.h
===================================================================
--- ctrl/firmware/Main/CubeMX/FATFS/Target/ffconf.h	(revision 74)
+++ ctrl/firmware/Main/CubeMX/FATFS/Target/ffconf.h	(revision 75)
@@ -26,4 +26,5 @@
 #include "stm32h7xx_hal.h"
 #include "bsp_driver_sd.h"
+#include "cmsis_os.h" /* _FS_REENTRANT set to 1 and CMSIS API chosen */
 
 /*-----------------------------------------------------------------------------/
@@ -46,5 +47,5 @@
 /   3: f_lseek() function is removed in addition to 2. */
 
-#define _USE_STRFUNC         2      /* 0:Disable or 1-2:Enable */
+#define _USE_STRFUNC         0      /* 0:Disable or 1-2:Enable */
 /* This option switches string functions, f_gets(), f_putc(), f_puts() and
 /  f_printf().
@@ -127,10 +128,10 @@
 /  ff_memfree(), must be added to the project. */
 
-#define _LFN_UNICODE    1 /* 0:ANSI/OEM or 1:Unicode */
+#define _LFN_UNICODE    0 /* 0:ANSI/OEM or 1:Unicode */
 /* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16)
 /  To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
 /  This option also affects behavior of string I/O functions. */
 
-#define _STRF_ENCODE    3
+#define _STRF_ENCODE    0
 /* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
 /  be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
@@ -210,10 +211,10 @@
 /  buffer in the file system object (FATFS) is used for the file data transfer. */
 
-#define _FS_EXFAT	1
+#define _FS_EXFAT	0
 /* This option switches support of exFAT file system. (0:Disable or 1:Enable)
 /  When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
 /  Note that enabling exFAT discards C89 compatibility. */
 
-#define _FS_NORTC	1
+#define _FS_NORTC	0
 #define _NORTC_MON	1
 #define _NORTC_MDAY	1
@@ -239,7 +240,9 @@
 /      lock control is independent of re-entrancy. */
 
-#define _FS_REENTRANT    0  /* 0:Disable or 1:Enable */
+#define _FS_REENTRANT    1  /* 0:Disable or 1:Enable */
+
+#define _USE_MUTEX       1 /* 0:Disable or 1:Enable */
 #define _FS_TIMEOUT      1000 /* Timeout period in unit of time ticks */
-#define _SYNC_t          NULL
+#define _SYNC_t          osMutexId_t
 /* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
 /  module itself. Note that regardless of this option, file access to different
@@ -259,9 +262,8 @@
 /  included somewhere in the scope of ff.h. */
 
-/* define the ff_malloc ff_free macros as standard malloc free */
+/* define the ff_malloc ff_free macros as FreeRTOS pvPortMalloc and vPortFree macros */
 #if !defined(ff_malloc) && !defined(ff_free)
-#include <stdlib.h>
-#define ff_malloc  malloc
-#define ff_free  free
+#define ff_malloc  pvPortMalloc
+#define ff_free  vPortFree
 #endif
 
Index: ctrl/firmware/Main/CubeMX/FATFS/Target/sd_diskio.c
===================================================================
--- ctrl/firmware/Main/CubeMX/FATFS/Target/sd_diskio.c	(revision 74)
+++ ctrl/firmware/Main/CubeMX/FATFS/Target/sd_diskio.c	(revision 75)
@@ -18,6 +18,6 @@
 /* USER CODE END Header */
 
-/* Note: code generation based on sd_diskio_dma_template_bspv1.c v2.1.4
-   as "Use dma template" is enabled. */
+/* Note: code generation based on sd_diskio_dma_rtos_template_bspv1.c v2.1.4
+   as FreeRTOS is enabled. */
 
 /* USER CODE BEGIN firstSection */
@@ -30,9 +30,25 @@
 
 #include <string.h>
+#include <stdio.h>
 
 /* Private typedef -----------------------------------------------------------*/
 /* Private define ------------------------------------------------------------*/
 
- /*
+#define QUEUE_SIZE         (uint32_t) 10
+#define READ_CPLT_MSG      (uint32_t) 1
+#define WRITE_CPLT_MSG     (uint32_t) 2
+/*
+==================================================================
+enable the defines below to send custom rtos messages
+when an error or an abort occurs.
+Notice: depending on the HAL/SD driver the HAL_SD_ErrorCallback()
+may not be available.
+See BSP_SD_ErrorCallback() and BSP_SD_AbortCallback() below
+==================================================================
+
+#define RW_ERROR_MSG       (uint32_t) 3
+#define RW_ABORT_MSG       (uint32_t) 4
+*/
+/*
  * the following Timeout is useful to give the control back to the applications
  * in case of errors in either BSP_SD_ReadCpltCallback() or BSP_SD_WriteCpltCallback()
@@ -60,5 +76,5 @@
  */
 /* USER CODE BEGIN enableSDDmaCacheMaintenance */
-#define ENABLE_SD_DMA_CACHE_MAINTENANCE  1
+//#define ENABLE_SD_DMA_CACHE_MAINTENANCE  1
 /* USER CODE END enableSDDmaCacheMaintenance */
 
@@ -69,5 +85,5 @@
 */
 /* USER CODE BEGIN enableScratchBuffer */
-#define ENABLE_SCRATCH_BUFFER
+//#define ENABLE_SCRATCH_BUFFER
 /* USER CODE END enableScratchBuffer */
 
@@ -83,5 +99,9 @@
 static volatile DSTATUS Stat = STA_NOINIT;
 
-static volatile  UINT  WriteStatus = 0, ReadStatus = 0;
+#if (osCMSIS <= 0x20000U)
+static osMessageQId SDQueueID = NULL;
+#else
+static osMessageQueueId_t SDQueueID = NULL;
+#endif
 /* Private function prototypes -----------------------------------------------*/
 static DSTATUS SD_CheckStatus(BYTE lun);
@@ -118,7 +138,13 @@
 static int SD_CheckStatusWithTimeout(uint32_t timeout)
 {
-  uint32_t timer = HAL_GetTick();
-  /* block until SDIO IP is ready again or a timeout occur */
-  while(HAL_GetTick() - timer < timeout)
+  uint32_t timer;
+  /* block until SDIO peripheral is ready again or a timeout occur */
+#if (osCMSIS <= 0x20000U)
+  timer = osKernelSysTick();
+  while( osKernelSysTick() - timer < timeout)
+#else
+  timer = osKernelGetTickCount();
+  while( osKernelGetTickCount() - timer < timeout)
+#endif
   {
     if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
@@ -135,5 +161,5 @@
   Stat = STA_NOINIT;
 
-  if(BSP_SD_GetCardState() == MSD_OK)
+  if(BSP_SD_GetCardState() == SD_TRANSFER_OK)
   {
     Stat &= ~STA_NOINIT;
@@ -150,15 +176,50 @@
 DSTATUS SD_initialize(BYTE lun)
 {
-
+Stat = STA_NOINIT;
+
+  /*
+   * check that the kernel has been started before continuing
+   * as the osMessage API will fail otherwise
+   */
+#if (osCMSIS <= 0x20000U)
+  if(osKernelRunning())
+#else
+  if(osKernelGetState() == osKernelRunning)
+#endif
+  {
 #if !defined(DISABLE_SD_INIT)
 
-  if(BSP_SD_Init() == MSD_OK)
-  {
+    if(BSP_SD_Init() == MSD_OK)
+    {
+      Stat = SD_CheckStatus(lun);
+    }
+
+#else
     Stat = SD_CheckStatus(lun);
-  }
-
-#else
-  Stat = SD_CheckStatus(lun);
-#endif
+#endif
+
+    /*
+    * if the SD is correctly initialized, create the operation queue
+    * if not already created
+    */
+
+    if (Stat != STA_NOINIT)
+    {
+      if (SDQueueID == NULL)
+      {
+ #if (osCMSIS <= 0x20000U)
+      osMessageQDef(SD_Queue, QUEUE_SIZE, uint16_t);
+      SDQueueID = osMessageCreate (osMessageQ(SD_Queue), NULL);
+#else
+      SDQueueID = osMessageQueueNew(QUEUE_SIZE, 2, NULL);
+#endif
+      }
+
+      if (SDQueueID == NULL)
+      {
+        Stat |= STA_NOINIT;
+      }
+    }
+  }
 
   return Stat;
@@ -189,13 +250,16 @@
 DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
 {
+  uint8_t ret;
   DRESULT res = RES_ERROR;
-  uint32_t timeout;
-#if defined(ENABLE_SCRATCH_BUFFER)
-  uint8_t ret;
+  uint32_t timer;
+#if (osCMSIS < 0x20000U)
+  osEvent event;
+#else
+  uint16_t event;
+  osStatus_t status;
 #endif
 #if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
   uint32_t alignedAddr;
 #endif
-
   /*
   * ensure the SDCard is ready for a new operation
@@ -211,44 +275,52 @@
   {
 #endif
-    if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff,
-                             (uint32_t) (sector),
-                             count) == MSD_OK)
+    /* Fast path cause destination buffer is correctly aligned */
+    ret = BSP_SD_ReadBlocks_DMA((uint32_t*)buff, (uint32_t)(sector), count);
+
+    if (ret == MSD_OK) {
+#if (osCMSIS < 0x20000U)
+    /* wait for a message from the queue or a timeout */
+    event = osMessageGet(SDQueueID, SD_TIMEOUT);
+
+    if (event.status == osEventMessage)
     {
-      ReadStatus = 0;
-      /* Wait that the reading process is completed or a timeout occurs */
-      timeout = HAL_GetTick();
-      while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
+      if (event.value.v == READ_CPLT_MSG)
       {
-      }
-      /* in case of a timeout return error */
-      if (ReadStatus == 0)
-      {
-        res = RES_ERROR;
-      }
-      else
-      {
-        ReadStatus = 0;
-        timeout = HAL_GetTick();
-
-        while((HAL_GetTick() - timeout) < SD_TIMEOUT)
-        {
-          if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
+        timer = osKernelSysTick();
+        /* block until SDIO IP is ready or a timeout occur */
+        while(osKernelSysTick() - timer <SD_TIMEOUT)
+#else
+          status = osMessageQueueGet(SDQueueID, (void *)&event, NULL, SD_TIMEOUT);
+          if ((status == osOK) && (event == READ_CPLT_MSG))
           {
-            res = RES_OK;
+            timer = osKernelGetTickCount();
+            /* block until SDIO IP is ready or a timeout occur */
+            while(osKernelGetTickCount() - timer <SD_TIMEOUT)
+#endif
+            {
+              if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
+              {
+                res = RES_OK;
 #if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
-            /*
-            the SCB_InvalidateDCache_by_Addr() requires a 32-Byte aligned address,
-            adjust the address and the D-Cache size to invalidate accordingly.
-            */
-            alignedAddr = (uint32_t)buff & ~0x1F;
-            SCB_InvalidateDCache_by_Addr((uint32_t*)alignedAddr, count*BLOCKSIZE + ((uint32_t)buff - alignedAddr));
-#endif
-            break;
+                /*
+                the SCB_InvalidateDCache_by_Addr() requires a 32-Byte aligned address,
+                adjust the address and the D-Cache size to invalidate accordingly.
+                */
+                alignedAddr = (uint32_t)buff & ~0x1F;
+                SCB_InvalidateDCache_by_Addr((uint32_t*)alignedAddr, count*BLOCKSIZE + ((uint32_t)buff - alignedAddr));
+#endif
+                break;
+              }
+            }
+#if (osCMSIS < 0x20000U)
           }
         }
+#else
       }
-    }
+#endif
+    }
+
 #if defined(ENABLE_SCRATCH_BUFFER)
-  }
+    }
     else
     {
@@ -256,20 +328,49 @@
       int i;
 
-      for (i = 0; i < count; i++) {
+      for (i = 0; i < count; i++)
+      {
         ret = BSP_SD_ReadBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);
-        if (ret == MSD_OK) {
+        if (ret == MSD_OK )
+        {
           /* wait until the read is successful or a timeout occurs */
-
-          timeout = HAL_GetTick();
-          while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
+#if (osCMSIS < 0x20000U)
+          /* wait for a message from the queue or a timeout */
+          event = osMessageGet(SDQueueID, SD_TIMEOUT);
+
+          if (event.status == osEventMessage)
           {
+            if (event.value.v == READ_CPLT_MSG)
+            {
+              timer = osKernelSysTick();
+              /* block until SDIO IP is ready or a timeout occur */
+              while(osKernelSysTick() - timer <SD_TIMEOUT)
+#else
+                status = osMessageQueueGet(SDQueueID, (void *)&event, NULL, SD_TIMEOUT);
+              if ((status == osOK) && (event == READ_CPLT_MSG))
+              {
+                timer = osKernelGetTickCount();
+                /* block until SDIO IP is ready or a timeout occur */
+                ret = MSD_ERROR;
+                while(osKernelGetTickCount() - timer < SD_TIMEOUT)
+#endif
+                {
+                  ret = BSP_SD_GetCardState();
+
+                  if (ret == MSD_OK)
+                  {
+                    break;
+                  }
+                }
+
+                if (ret != MSD_OK)
+                {
+                  break;
+                }
+#if (osCMSIS < 0x20000U)
+              }
+            }
+#else
           }
-          if (ReadStatus == 0)
-          {
-            res = RES_ERROR;
-            break;
-          }
-          ReadStatus = 0;
-
+#endif
 #if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
           /*
@@ -288,9 +389,8 @@
       }
 
-      if ((i == count) && (ret == MSD_OK))
+      if ((i == count) && (ret == MSD_OK ))
         res = RES_OK;
     }
 #endif
-
   return res;
 }
@@ -312,55 +412,66 @@
 {
   DRESULT res = RES_ERROR;
-  uint32_t timeout;
+  uint32_t timer;
+
+#if (osCMSIS < 0x20000U)
+  osEvent event;
+#else
+  uint16_t event;
+  osStatus_t status;
+#endif
+
 #if defined(ENABLE_SCRATCH_BUFFER)
-  uint8_t ret;
-  int i;
-#endif
-
-   WriteStatus = 0;
+  int32_t ret;
+#endif
+
+  /*
+  * ensure the SDCard is ready for a new operation
+  */
+
+  if (SD_CheckStatusWithTimeout(SD_TIMEOUT) < 0)
+  {
+    return res;
+  }
+
+#if defined(ENABLE_SCRATCH_BUFFER)
+  if (!((uint32_t)buff & 0x3))
+  {
+#endif
 #if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
   uint32_t alignedAddr;
-#endif
-
-  if (SD_CheckStatusWithTimeout(SD_TIMEOUT) < 0)
-  {
-    return res;
-  }
-
-#if defined(ENABLE_SCRATCH_BUFFER)
-  if (!((uint32_t)buff & 0x3))
-  {
-#endif
-#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
-
-    /*
+  /*
     the SCB_CleanDCache_by_Addr() requires a 32-Byte aligned address
     adjust the address and the D-Cache size to clean accordingly.
-    */
-    alignedAddr = (uint32_t)buff &  ~0x1F;
-    SCB_CleanDCache_by_Addr((uint32_t*)alignedAddr, count*BLOCKSIZE + ((uint32_t)buff - alignedAddr));
-#endif
-
-    if(BSP_SD_WriteBlocks_DMA((uint32_t*)buff,
-                              (uint32_t)(sector),
-                              count) == MSD_OK)
+  */
+  alignedAddr = (uint32_t)buff & ~0x1F;
+  SCB_CleanDCache_by_Addr((uint32_t*)alignedAddr, count*BLOCKSIZE + ((uint32_t)buff - alignedAddr));
+#endif
+
+  if(BSP_SD_WriteBlocks_DMA((uint32_t*)buff,
+                           (uint32_t) (sector),
+                           count) == MSD_OK)
+  {
+#if (osCMSIS < 0x20000U)
+    /* Get the message from the queue */
+    event = osMessageGet(SDQueueID, SD_TIMEOUT);
+
+    if (event.status == osEventMessage)
     {
-      /* Wait that writing process is completed or a timeout occurs */
-
-      timeout = HAL_GetTick();
-      while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
+      if (event.value.v == WRITE_CPLT_MSG)
       {
-      }
-      /* in case of a timeout return error */
-      if (WriteStatus == 0)
-      {
-        res = RES_ERROR;
-      }
-      else
-      {
-        WriteStatus = 0;
-        timeout = HAL_GetTick();
-
-        while((HAL_GetTick() - timeout) < SD_TIMEOUT)
+#else
+    status = osMessageQueueGet(SDQueueID, (void *)&event, NULL, SD_TIMEOUT);
+    if ((status == osOK) && (event == WRITE_CPLT_MSG))
+    {
+#endif
+ #if (osCMSIS < 0x20000U)
+        timer = osKernelSysTick();
+        /* block until SDIO IP is ready or a timeout occur */
+        while(osKernelSysTick() - timer  < SD_TIMEOUT)
+#else
+        timer = osKernelGetTickCount();
+        /* block until SDIO IP is ready or a timeout occur */
+        while(osKernelGetTickCount() - timer  < SD_TIMEOUT)
+#endif
         {
           if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
@@ -370,37 +481,70 @@
           }
         }
+#if (osCMSIS < 0x20000U)
       }
     }
+#else
+    }
+#endif
+  }
 #if defined(ENABLE_SCRATCH_BUFFER)
-  }
-    else
-    {
-      /* Slow path, fetch each sector a part and memcpy to destination buffer */
+  else {
+    /* Slow path, fetch each sector a part and memcpy to destination buffer */
+    int i;
+
 #if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
-      /*
-      * invalidate the scratch buffer before the next write to get the actual data instead of the cached one
-      */
-      SCB_InvalidateDCache_by_Addr((uint32_t*)scratch, BLOCKSIZE);
-#endif
-
+    /*
+     * invalidate the scratch buffer before the next write to get the actual data instead of the cached one
+     */
+     SCB_InvalidateDCache_by_Addr((uint32_t*)scratch, BLOCKSIZE);
+#endif
       for (i = 0; i < count; i++)
       {
-        WriteStatus = 0;
-
-        memcpy((void *)scratch, (void *)buff, BLOCKSIZE);
+        memcpy((void *)scratch, buff, BLOCKSIZE);
         buff += BLOCKSIZE;
 
         ret = BSP_SD_WriteBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);
-        if (ret == MSD_OK) {
+        if (ret == MSD_OK )
+        {
+          /* wait until the read is successful or a timeout occurs */
+#if (osCMSIS < 0x20000U)
           /* wait for a message from the queue or a timeout */
-          timeout = HAL_GetTick();
-          while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
+          event = osMessageGet(SDQueueID, SD_TIMEOUT);
+
+          if (event.status == osEventMessage)
           {
+            if (event.value.v == READ_CPLT_MSG)
+            {
+              timer = osKernelSysTick();
+              /* block until SDIO IP is ready or a timeout occur */
+              while(osKernelSysTick() - timer <SD_TIMEOUT)
+#else
+                status = osMessageQueueGet(SDQueueID, (void *)&event, NULL, SD_TIMEOUT);
+              if ((status == osOK) && (event == READ_CPLT_MSG))
+              {
+                timer = osKernelGetTickCount();
+                /* block until SDIO IP is ready or a timeout occur */
+                ret = MSD_ERROR;
+                while(osKernelGetTickCount() - timer < SD_TIMEOUT)
+#endif
+                {
+                  ret = BSP_SD_GetCardState();
+
+                  if (ret == MSD_OK)
+                  {
+                    break;
+                  }
+                }
+
+                if (ret != MSD_OK)
+                {
+                  break;
+                }
+#if (osCMSIS < 0x20000U)
+              }
+            }
+#else
           }
-          if (WriteStatus == 0)
-          {
-            break;
-          }
-
+#endif
         }
         else
@@ -409,11 +553,15 @@
         }
       }
-      if ((i == count) && (ret == MSD_OK))
+
+      if ((i == count) && (ret == MSD_OK ))
         res = RES_OK;
     }
-#endif
+
+  }
+#endif
+
   return res;
 }
-#endif /* _USE_WRITE == 1 */
+ #endif /* _USE_WRITE == 1 */
 
 /* USER CODE BEGIN beforeIoctlSection */
@@ -486,5 +634,14 @@
 {
 
-  WriteStatus = 1;
+  /*
+   * No need to add an "osKernelRunning()" check here, as the SD_initialize()
+   * is always called before any SD_Read()/SD_Write() call
+   */
+#if (osCMSIS < 0x20000U)
+   osMessagePut(SDQueueID, WRITE_CPLT_MSG, 0);
+#else
+   const uint16_t msg = WRITE_CPLT_MSG;
+   osMessageQueuePut(SDQueueID, (const void *)&msg, 0, 0);
+#endif
 }
 
@@ -496,5 +653,14 @@
 void BSP_SD_ReadCpltCallback(void)
 {
-  ReadStatus = 1;
+  /*
+   * No need to add an "osKernelRunning()" check here, as the SD_initialize()
+   * is always called before any SD_Read()/SD_Write() call
+   */
+#if (osCMSIS < 0x20000U)
+   osMessagePut(SDQueueID, READ_CPLT_MSG, 0);
+#else
+   const uint16_t msg = READ_CPLT_MSG;
+   osMessageQueuePut(SDQueueID, (const void *)&msg, 0, 0);
+#endif
 }
 
Index: ctrl/firmware/Main/CubeMX/FATFS/Target/sd_diskio.h
===================================================================
--- ctrl/firmware/Main/CubeMX/FATFS/Target/sd_diskio.h	(revision 74)
+++ ctrl/firmware/Main/CubeMX/FATFS/Target/sd_diskio.h	(revision 75)
@@ -18,5 +18,5 @@
 /* USER CODE END Header */
 
-/* Note: code generation based on sd_diskio_dma_template.h */
+/* Note: code generation based on sd_diskio_dma_rtos_template.h */
 
 /* Define to prevent recursive inclusion -------------------------------------*/
Index: ctrl/firmware/Main/CubeMX/Middlewares/Third_Party/FatFs/src/option/ccsbcs.c
===================================================================
--- ctrl/firmware/Main/CubeMX/Middlewares/Third_Party/FatFs/src/option/ccsbcs.c	(revision 75)
+++ ctrl/firmware/Main/CubeMX/Middlewares/Third_Party/FatFs/src/option/ccsbcs.c	(revision 75)
@@ -0,0 +1,388 @@
+/*------------------------------------------------------------------------*/
+/* Unicode - Local code bidirectional converter  (C)ChaN, 2015            */
+/* (SBCS code pages)                                                      */
+/*------------------------------------------------------------------------*/
+/*  437   U.S.
+/   720   Arabic
+/   737   Greek
+/   771   KBL
+/   775   Baltic
+/   850   Latin 1
+/   852   Latin 2
+/   855   Cyrillic
+/   857   Turkish
+/   860   Portuguese
+/   861   Icelandic
+/   862   Hebrew
+/   863   Canadian French
+/   864   Arabic
+/   865   Nordic
+/   866   Russian
+/   869   Greek 2
+*/
+
+#include "../ff.h"
+
+
+#if _CODE_PAGE == 437
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP437(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+	0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 720
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP720(0x80-0xFF) to Unicode conversion table */
+	0x0000, 0x0000, 0x00E9, 0x00E2, 0x0000, 0x00E0, 0x0000, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0651, 0x0652, 0x00F4, 0x00A4, 0x0640, 0x00FB, 0x00F9, 0x0621, 0x0622, 0x0623, 0x0624, 0x00A3, 0x0625, 0x0626, 0x0627,
+	0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 0x0641, 0x00B5, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A,
+	0x2261, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 737
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP737(0x80-0xFF) to Unicode conversion table */
+	0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0,
+	0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8,
+	0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, 0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E,
+	0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 771
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP771(0x80-0xFF) to Unicode conversion table */
+	0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+	0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+	0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x0104, 0x0105, 0x010C, 0x010D,
+	0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+	0x0118, 0x0119, 0x0116, 0x0117, 0x012E, 0x012F, 0x0160, 0x0161, 0x0172, 0x0173, 0x016A, 0x016B, 0x017D, 0x017E, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 775
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP775(0x80-0xFF) to Unicode conversion table */
+	0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, 0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5,
+	0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4,
+	0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, 0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, 0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D,
+	0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, 0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, 0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019,
+	0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, 0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 850
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP850(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+	0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+	0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580,
+	0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4,
+	0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 852
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP852(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106,
+	0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+	0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580,
+	0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4,
+	0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 855
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP855(0x80-0xFF) to Unicode conversion table */
+	0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408,
+	0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A,
+	0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+	0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580,
+	0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116,
+	0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 857
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP857(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0131, 0x00C4, 0x00C5,
+	0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x0130, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x015E, 0x015F,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x011E, 0x011F, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+	0x00BA, 0x00AA, 0x00CA, 0x00CB, 0x00C8, 0x0000, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580,
+	0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x0000, 0x00D7, 0x00DA, 0x00DB, 0x00D9, 0x00EC, 0x00FF, 0x00AF, 0x00B4,
+	0x00AD, 0x00B1, 0x0000, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 860
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP860(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E3, 0x00E0, 0x00C1, 0x00E7, 0x00EA, 0x00CA, 0x00E8, 0x00CD, 0x00D4, 0x00EC, 0x00C3, 0x00C2,
+	0x00C9, 0x00C0, 0x00C8, 0x00F4, 0x00F5, 0x00F2, 0x00DA, 0x00F9, 0x00CC, 0x00D5, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x20A7, 0x00D3,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00D2, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 861
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP861(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00D0, 0x00F0, 0x00DE, 0x00C4, 0x00C5,
+	0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00FE, 0x00FB, 0x00DD, 0x00FD, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00C1, 0x00CD, 0x00D3, 0x00DA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 862
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP862(0x80-0xFF) to Unicode conversion table */
+	0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
+	0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 863
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP863(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00C2, 0x00E0, 0x00B6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x2017, 0x00C0,
+	0x00C9, 0x00C8, 0x00CA, 0x00F4, 0x00CB, 0x00CF, 0x00FB, 0x00F9, 0x00A4, 0x00D4, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x00DB, 0x0192,
+	0x00A6, 0x00B4, 0x00F3, 0x00FA, 0x00A8, 0x00BB, 0x00B3, 0x00AF, 0x00CE, 0x3210, 0x00AC, 0x00BD, 0x00BC, 0x00BE, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2219,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 864
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP864(0x80-0xFF) to Unicode conversion table */
+	0x00B0, 0x00B7, 0x2219, 0x221A, 0x2592, 0x2500, 0x2502, 0x253C, 0x2524, 0x252C, 0x251C, 0x2534, 0x2510, 0x250C, 0x2514, 0x2518,
+	0x03B2, 0x221E, 0x03C6, 0x00B1, 0x00BD, 0x00BC, 0x2248, 0x00AB, 0x00BB, 0xFEF7, 0xFEF8, 0x0000, 0x0000, 0xFEFB, 0xFEFC, 0x0000,
+	0x00A0, 0x00AD, 0xFE82, 0x00A3, 0x00A4, 0xFE84, 0x0000, 0x20AC, 0xFE8E, 0xFE8F, 0xFE95, 0xFE99, 0x060C, 0xFE9D, 0xFEA1, 0xFEA5,
+	0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0xFED1, 0x061B, 0xFEB1, 0xFEB5, 0xFEB9, 0x061F,
+	0x00A2, 0xFE80, 0xFE81, 0xFE83, 0xFE85, 0xFECA, 0xFE8B, 0xFE8D, 0xFE91, 0xFE93, 0xFE97, 0xFE9B, 0xFE9F, 0xFEA3, 0xFEA7, 0xFEA9,
+	0xFEAB, 0xFEAD, 0xFEAF, 0xFEB3, 0xFEB7, 0xFEBB, 0xFEBF, 0xFEC1, 0xFEC5, 0xFECB, 0xFECF, 0x00A6, 0x00AC, 0x00F7, 0x00D7, 0xFEC9,
+	0x0640, 0xFED3, 0xFED7, 0xFEDB, 0xFEDF, 0xFEE3, 0xFEE7, 0xFEEB, 0xFEED, 0xFEEF, 0xFEF3, 0xFEBD, 0xFECC, 0xFECE, 0xFECD, 0xFEE1,
+	0xFE7D, 0x0651, 0xFEE5, 0xFEE9, 0xFEEC, 0xFEF0, 0xFEF2, 0xFED0, 0xFED5, 0xFEF5, 0xFEF6, 0xFEDD, 0xFED9, 0xFEF1, 0x25A0, 0x0000
+};
+
+#elif _CODE_PAGE == 865
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP865(0x80-0xFF) to Unicode conversion table */
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+	0x00C5, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00A4,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 866
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP866(0x80-0xFF) to Unicode conversion table */
+	0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+	0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+	0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+	0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0
+};
+
+#elif _CODE_PAGE == 869
+#define _TBLDEF 1
+static
+const WCHAR Tbl[] = {	/*  CP869(0x80-0xFF) to Unicode conversion table */
+	0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x0386, 0x00B7, 0x00B7, 0x00AC, 0x00A6, 0x2018, 0x2019, 0x0388, 0x2015, 0x0389,
+	0x038A, 0x03AA, 0x038C, 0x00B7, 0x00B7, 0x038E, 0x03AB, 0x00A9, 0x038F, 0x00B2, 0x00B3, 0x03AC, 0x00A3, 0x03AD, 0x03AE, 0x03AF,
+	0x03CA, 0x0390, 0x03CC, 0x03CD, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x00BD, 0x0398, 0x0399, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x039A, 0x039B, 0x039C, 0x039D, 0x2563, 0x2551, 0x2557, 0x255D, 0x039E, 0x039F, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0A30, 0x03A1, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x03A3,
+	0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x2518, 0x250C, 0x2588, 0x2584, 0x03B4, 0x03B5, 0x2580,
+	0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x0384,
+	0x00AD, 0x00B1, 0x03C5, 0x03C6, 0x03C7, 0x00A7, 0x03C8, 0x0385, 0x00B0, 0x00A8, 0x03C9, 0x03CB, 0x03B0, 0x03CE, 0x25A0, 0x00A0
+};
+
+#endif
+
+
+#if !_TBLDEF || !_USE_LFN
+#error This file is not needed at current configuration. Remove from the project.
+#endif
+
+
+
+
+WCHAR ff_convert (	/* Converted character, Returns zero on error */
+	WCHAR	chr,	/* Character code to be converted */
+	UINT	dir		/* 0: Unicode to OEM code, 1: OEM code to Unicode */
+)
+{
+	WCHAR c;
+
+
+	if (chr < 0x80) {	/* ASCII */
+		c = chr;
+
+	} else {
+		if (dir) {		/* OEM code to Unicode */
+			c = (chr >= 0x100) ? 0 : Tbl[chr - 0x80];
+
+		} else {		/* Unicode to OEM code */
+			for (c = 0; c < 0x80; c++) {
+				if (chr == Tbl[c]) break;
+			}
+			c = (c + 0x80) & 0xFF;
+		}
+	}
+
+	return c;
+}
+
+
+
+WCHAR ff_wtoupper (	/* Returns upper converted character */
+	WCHAR chr		/* Unicode character to be upper converted (BMP only) */
+)
+{
+	/* Compressed upper conversion table */
+	static const WCHAR cvt1[] = {	/* U+0000 - U+0FFF */
+		/* Basic Latin */
+		0x0061,0x031A,
+		/* Latin-1 Supplement */
+		0x00E0,0x0317,  0x00F8,0x0307,  0x00FF,0x0001,0x0178,
+		/* Latin Extended-A */
+		0x0100,0x0130,  0x0132,0x0106,  0x0139,0x0110,  0x014A,0x012E,  0x0179,0x0106,
+		/* Latin Extended-B */
+		0x0180,0x004D,0x0243,0x0181,0x0182,0x0182,0x0184,0x0184,0x0186,0x0187,0x0187,0x0189,0x018A,0x018B,0x018B,0x018D,0x018E,0x018F,0x0190,0x0191,0x0191,0x0193,0x0194,0x01F6,0x0196,0x0197,0x0198,0x0198,0x023D,0x019B,0x019C,0x019D,0x0220,0x019F,0x01A0,0x01A0,0x01A2,0x01A2,0x01A4,0x01A4,0x01A6,0x01A7,0x01A7,0x01A9,0x01AA,0x01AB,0x01AC,0x01AC,0x01AE,0x01AF,0x01AF,0x01B1,0x01B2,0x01B3,0x01B3,0x01B5,0x01B5,0x01B7,0x01B8,0x01B8,0x01BA,0x01BB,0x01BC,0x01BC,0x01BE,0x01F7,0x01C0,0x01C1,0x01C2,0x01C3,0x01C4,0x01C5,0x01C4,0x01C7,0x01C8,0x01C7,0x01CA,0x01CB,0x01CA,
+		0x01CD,0x0110,  0x01DD,0x0001,0x018E,  0x01DE,0x0112,  0x01F3,0x0003,0x01F1,0x01F4,0x01F4,  0x01F8,0x0128,
+		0x0222,0x0112,  0x023A,0x0009,0x2C65,0x023B,0x023B,0x023D,0x2C66,0x023F,0x0240,0x0241,0x0241,  0x0246,0x010A,
+		/* IPA Extensions */
+		0x0253,0x0040,0x0181,0x0186,0x0255,0x0189,0x018A,0x0258,0x018F,0x025A,0x0190,0x025C,0x025D,0x025E,0x025F,0x0193,0x0261,0x0262,0x0194,0x0264,0x0265,0x0266,0x0267,0x0197,0x0196,0x026A,0x2C62,0x026C,0x026D,0x026E,0x019C,0x0270,0x0271,0x019D,0x0273,0x0274,0x019F,0x0276,0x0277,0x0278,0x0279,0x027A,0x027B,0x027C,0x2C64,0x027E,0x027F,0x01A6,0x0281,0x0282,0x01A9,0x0284,0x0285,0x0286,0x0287,0x01AE,0x0244,0x01B1,0x01B2,0x0245,0x028D,0x028E,0x028F,0x0290,0x0291,0x01B7,
+		/* Greek, Coptic */
+		0x037B,0x0003,0x03FD,0x03FE,0x03FF,  0x03AC,0x0004,0x0386,0x0388,0x0389,0x038A,  0x03B1,0x0311,
+		0x03C2,0x0002,0x03A3,0x03A3,  0x03C4,0x0308,  0x03CC,0x0003,0x038C,0x038E,0x038F,  0x03D8,0x0118,
+		0x03F2,0x000A,0x03F9,0x03F3,0x03F4,0x03F5,0x03F6,0x03F7,0x03F7,0x03F9,0x03FA,0x03FA,
+		/* Cyrillic */
+		0x0430,0x0320,  0x0450,0x0710,  0x0460,0x0122,  0x048A,0x0136,  0x04C1,0x010E,  0x04CF,0x0001,0x04C0,  0x04D0,0x0144,
+		/* Armenian */
+		0x0561,0x0426,
+
+		0x0000
+	};
+	static const WCHAR cvt2[] = {	/* U+1000 - U+FFFF */
+		/* Phonetic Extensions */
+		0x1D7D,0x0001,0x2C63,
+		/* Latin Extended Additional */
+		0x1E00,0x0196,  0x1EA0,0x015A,
+		/* Greek Extended */
+		0x1F00,0x0608,  0x1F10,0x0606,  0x1F20,0x0608,  0x1F30,0x0608,  0x1F40,0x0606,
+		0x1F51,0x0007,0x1F59,0x1F52,0x1F5B,0x1F54,0x1F5D,0x1F56,0x1F5F,  0x1F60,0x0608,
+		0x1F70,0x000E,0x1FBA,0x1FBB,0x1FC8,0x1FC9,0x1FCA,0x1FCB,0x1FDA,0x1FDB,0x1FF8,0x1FF9,0x1FEA,0x1FEB,0x1FFA,0x1FFB,
+		0x1F80,0x0608,  0x1F90,0x0608,  0x1FA0,0x0608,  0x1FB0,0x0004,0x1FB8,0x1FB9,0x1FB2,0x1FBC,
+		0x1FCC,0x0001,0x1FC3,  0x1FD0,0x0602,  0x1FE0,0x0602,  0x1FE5,0x0001,0x1FEC,  0x1FF2,0x0001,0x1FFC,
+		/* Letterlike Symbols */
+		0x214E,0x0001,0x2132,
+		/* Number forms */
+		0x2170,0x0210,  0x2184,0x0001,0x2183,
+		/* Enclosed Alphanumerics */
+		0x24D0,0x051A,  0x2C30,0x042F,
+		/* Latin Extended-C */
+		0x2C60,0x0102,  0x2C67,0x0106, 0x2C75,0x0102,
+		/* Coptic */
+		0x2C80,0x0164,
+		/* Georgian Supplement */
+		0x2D00,0x0826,
+		/* Full-width */
+		0xFF41,0x031A,
+
+		0x0000
+	};
+	const WCHAR *p;
+	WCHAR bc, nc, cmd;
+
+
+	p = chr < 0x1000 ? cvt1 : cvt2;
+	for (;;) {
+		bc = *p++;								/* Get block base */
+		if (!bc || chr < bc) break;
+		nc = *p++; cmd = nc >> 8; nc &= 0xFF;	/* Get processing command and block size */
+		if (chr < bc + nc) {	/* In the block? */
+			switch (cmd) {
+			case 0:	chr = p[chr - bc]; break;		/* Table conversion */
+			case 1:	chr -= (chr - bc) & 1; break;	/* Case pairs */
+			case 2: chr -= 16; break;				/* Shift -16 */
+			case 3:	chr -= 32; break;				/* Shift -32 */
+			case 4:	chr -= 48; break;				/* Shift -48 */
+			case 5:	chr -= 26; break;				/* Shift -26 */
+			case 6:	chr += 8; break;				/* Shift +8 */
+			case 7: chr -= 80; break;				/* Shift -80 */
+			case 8:	chr -= 0x1C60; break;			/* Shift -0x1C60 */
+			}
+			break;
+		}
+		if (!cmd) p += nc;
+	}
+
+	return chr;
+}
+
Index: ctrl/firmware/Main/CubeMX/Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c
===================================================================
--- ctrl/firmware/Main/CubeMX/Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c	(revision 74)
+++ ctrl/firmware/Main/CubeMX/Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c	(revision 75)
@@ -62,5 +62,5 @@
 	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
 #else
-	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]								__attribute__((section(".DTCM_RAM")));
+	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
 #endif /* configAPPLICATION_ALLOCATED_HEAP */
 
Index: ctrl/firmware/Main/CubeMX/charger.ioc
===================================================================
--- ctrl/firmware/Main/CubeMX/charger.ioc	(revision 74)
+++ ctrl/firmware/Main/CubeMX/charger.ioc	(revision 75)
@@ -67,13 +67,14 @@
 Dma.USART3_TX.2.SyncSignalID=NONE
 FATFS.BSP.number=1
-FATFS.IPParameters=_USE_FIND,_USE_EXPAND,_USE_LABEL,_USE_LFN,_LFN_UNICODE,_MULTI_PARTITION,_FS_EXFAT,_USE_MUTEX,_FS_REENTRANT,_FS_NORTC,_NORTC_YEAR,_NORTC_MDAY,_NORTC_MON
-FATFS._FS_EXFAT=1
-FATFS._FS_NORTC=1
-FATFS._FS_REENTRANT=0
-FATFS._LFN_UNICODE=1
+FATFS.IPParameters=_USE_FIND,_USE_EXPAND,_USE_LABEL,_USE_LFN,_LFN_UNICODE,_MULTI_PARTITION,_FS_EXFAT,_USE_MUTEX,_FS_REENTRANT,_FS_NORTC,_NORTC_YEAR,_NORTC_MDAY,_NORTC_MON,_STRF_ENCODE,_USE_STRFUNC
+FATFS._FS_EXFAT=0
+FATFS._FS_NORTC=0
+FATFS._FS_REENTRANT=1
+FATFS._LFN_UNICODE=0
 FATFS._MULTI_PARTITION=0
 FATFS._NORTC_MDAY=1
 FATFS._NORTC_MON=1
 FATFS._NORTC_YEAR=2025
+FATFS._STRF_ENCODE=0
 FATFS._USE_EXPAND=1
 FATFS._USE_FIND=1
@@ -81,4 +82,5 @@
 FATFS._USE_LFN=2
 FATFS._USE_MUTEX=1
+FATFS._USE_STRFUNC=0
 FATFS0.BSP.STBoard=false
 FATFS0.BSP.api=Unknown
@@ -95,5 +97,5 @@
 FREERTOS.INCLUDE_xTaskGetHandle=1
 FREERTOS.IPParameters=Tasks01,configENABLE_FPU,configENABLE_BACKWARD_COMPATIBILITY,configUSE_TICKLESS_IDLE,configRECORD_STACK_HIGH_ADDRESS,configCHECK_FOR_STACK_OVERFLOW,INCLUDE_xTaskGetHandle,INCLUDE_uxTaskGetStackHighWaterMark2,FootprintOK
-FREERTOS.Tasks01=mainTask,24,128,mainTaskStart,As weak,NULL,Static,mainTaskBuffer,mainTaskControlBlock
+FREERTOS.Tasks01=mainTask,24,512,mainTaskStart,As weak,NULL,Static,mainTaskBuffer,mainTaskControlBlock
 FREERTOS.configCHECK_FOR_STACK_OVERFLOW=2
 FREERTOS.configENABLE_BACKWARD_COMPATIBILITY=0
@@ -419,5 +421,5 @@
 ProjectManager.UAScriptBeforePath=
 ProjectManager.UnderRoot=true
-ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_RTC_Init-RTC-false-HAL-true,5-MX_SPI4_Init-SPI4-false-HAL-true,6-MX_SDMMC1_SD_Init-SDMMC1-false-HAL-true,7-MX_USART3_UART_Init-USART3-false-HAL-true,8-MX_TIM3_Init-TIM3-false-HAL-true,9-MX_TIM8_Init-TIM8-false-HAL-true,10-MX_FATFS_Init-FATFS-false-HAL-false,0-MX_CORTEX_M7_Init-CORTEX_M7-false-HAL-true
+ProjectManager.functionlistsort=1-MX_GPIO_Init-GPIO-false-HAL-true,2-MX_DMA_Init-DMA-false-HAL-true,3-MX_MDMA_Init-MDMA-false-HAL-true,3-MX_RTC_Init-RTC-false-HAL-true,4-MX_SPI4_Init-SPI4-false-HAL-true,5-MX_SDMMC1_SD_Init-SDMMC1-false-HAL-true,6-MX_USART3_UART_Init-USART3-false-HAL-true,7-MX_TIM3_Init-TIM3-false-HAL-true,8-SystemClock_Config-RCC-false-HAL-false,9-MX_FATFS_Init-FATFS-false-HAL-false,10-MX_TIM8_Init-TIM8-false-HAL-true,0-MX_CORTEX_M7_Init-CORTEX_M7-false-HAL-true
 RCC.ADCFreq_Value=166666666.66666666
 RCC.AHB12Freq_Value=100000000
@@ -501,7 +503,8 @@
 RCC.VCOInput2Freq_Value=8333333.333333333
 RCC.VCOInput3Freq_Value=1000000
+SDMMC1.ClockDiv=1
 SDMMC1.ClockPowerSave=SDMMC_CLOCK_POWER_SAVE_ENABLE
 SDMMC1.HardwareFlowControl=SDMMC_HARDWARE_FLOW_CONTROL_ENABLE
-SDMMC1.IPParameters=ClockPowerSave,HardwareFlowControl
+SDMMC1.IPParameters=ClockPowerSave,HardwareFlowControl,ClockDiv
 SH.S_TIM3_CH2.0=TIM3_CH2,PWM Generation2 CH2
 SH.S_TIM3_CH2.ConfNb=1
