cancel
Showing results for 
Search instead for 
Did you mean: 

SD with DMA works, except in callback

con3
Senior

Hey everyone,

I'm seeing something weird.

I've got a working SDIO in 4 bit mode, with dma (FIFO enabled with peripheral flow controller) and using a stm32f722ze.

It works perfectly. I can write a large bunch of data to the SDcard relatively quickly even if it isn't aligned with the SDcards block size.

This all happens in the main function though.

I have a timer running in input capture, it reads in data also using the same DMA (i.e. DMA2) although a different channel, once it has filled the buffer it starts filling a secondary buffer (multi-buffer mode).

If I call f_write in the callback for the DMA from the input capture, the sdcard hangs. It gets stuck in either one of these portions :

DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
  DRESULT res = RES_ERROR;
  ReadStatus = 0;
  uint32_t timeout;
#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
  uint32_t alignedAddr;
#endif
 
  if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff,
                           (uint32_t) (sector),
                           count) == MSD_OK)
  {
    /* Wait that the reading process is completed or a timeout occurs */
    timeout = HAL_GetTick();
    while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
    {
    }
    /* incase 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)
        {
          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;
        }
      }
    }
  }
 
  return res;
}

or

#if _USE_WRITE == 1
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
  DRESULT res = RES_ERROR;
  WriteStatus = 0;
  uint32_t timeout;
#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
  uint32_t alignedAddr;
  /*
   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)
  {
    /* Wait that writing process is completed or a timeout occurs */
 
    timeout = HAL_GetTick();
    while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
    {
    }
    /* incase of a timeout return error */
    if (WriteStatus == 0)
    {
      res = RES_ERROR;
    }
    else
    {
      WriteStatus = 0;
      timeout = HAL_GetTick();
 
      while((HAL_GetTick() - timeout) < SD_TIMEOUT)
      {
        if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
        {
          res = RES_OK;
          break;
        }
      }
    }
  }
 
  return res;
}
#endif /* _USE_WRITE == 1 */

but specifically at these two checks :

while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
  {
}
 while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
    {
}

It just stays there. Alternatively if I don't have the f_write in the callback it never gets stuck, even if the timer is actively filling the same buffer that the sdcard is writing.

I've gone through the registers after the write eventually fails and no error flags are triggered(over,under run etc).

This is the DMA config:

static void MX_DMA_Init(void) {
	/* DMA controller clock enable */
	__HAL_RCC_DMA2_CLK_ENABLE()
	;
 
	/* DMA interrupt init */
	/* DMA2_Stream0_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 1, 0);
	HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
	/* DMA2_Stream1_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 1, 0);
	HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
	/* DMA2_Stream2_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 1, 0);
	HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
	/* DMA2_Stream3_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 1, 0);
	HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
	/* DMA2_Stream5_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA2_Stream5_IRQn, 1, 0);
	HAL_NVIC_EnableIRQ(DMA2_Stream5_IRQn);
	/* DMA2_Stream6_IRQn interrupt configuration */
	HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 1, 0);
	HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);
 
}

I've got it setup as to have a lower priority than the systick timer.

Futhermore here is the callback for the buffer being filled:

static void TransferComplete(DMA_HandleTypeDef *DmaHandle) {
	res4 = f_write(&SDFile, &aDST_Buffer1[0], sizeof(aDST_Buffer1), &wbytes);
	if (i == 100) {
		__HAL_TIM_DISABLE_DMA(&htim8, TIM_DMA_CC2|TIM_DMA_CC3);
		__HAL_TIM_DISABLE_IT(&htim8, TIM_IT_CC2|TIM_IT_CC3);
	res = f_close(&SDFile);
	}
	i++;
}
 
static void TransferComplete1(DMA_HandleTypeDef *DmaHandle) {
 
	res4 = f_write(&SDFile, &aDST_Buffer2[0], sizeof(aDST_Buffer2), &wbytes);
	if (i == 100) {
		__HAL_TIM_DISABLE_DMA(&htim8, TIM_DMA_CC2|TIM_DMA_CC3);
		__HAL_TIM_DISABLE_IT(&htim8, TIM_IT_CC2|TIM_IT_CC3);
		res = f_close(&SDFile);
	}
  i++;
}

the code in the if statement is just used for debugging at this point. RES 4 returns FR_DISK_ERR.

I'm honestly not sure why it fails in the callback but works even when the same functions are running except it's executing from the main loop. Does anyone have any idea?

Any help will really be appreciated!

 EDIT: Data cache isn't enabled but instruction cache is

1 ACCEPTED SOLUTION

Accepted Solutions

My preference is to keep all the FATFS/SDIO stuff in a single thread, or managed via a state-machine/sequencer.

A reasonably simple approach being to have the IO occur in the foreground task that checks on a buffer-ready type signal, and then writes a 16KB block (or something that works for you that some sector size multiple) to the output file.

Other stuff gets managed in interrupts can be left to exit quickly. You' never want interrupts to block, or wait ms for things to complete.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

View solution in original post

4 REPLIES 4

Really shouldn't be using FATFS in interrupt/callback context unless you have some means to serialize access.

For HAL_GetTick to work the SysTick interrupt needs to be able to preempt all other interrupt sources, and should be of specific concern in callback context.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Hi @Community member​ ,

Thank you for reply.

I thought that using the double buffer DMA would help serialize access? Is there any other way to help with this? I thought that if conflicts occurred I would receive an overflow flag for the FIFO on the DMA that's receiving the data/

Thank you again for the help

My preference is to keep all the FATFS/SDIO stuff in a single thread, or managed via a state-machine/sequencer.

A reasonably simple approach being to have the IO occur in the foreground task that checks on a buffer-ready type signal, and then writes a 16KB block (or something that works for you that some sector size multiple) to the output file.

Other stuff gets managed in interrupts can be left to exit quickly. You' never want interrupts to block, or wait ms for things to complete.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
con3
Senior

Thank you @Community member​ !,

I'm going to look into this