cancel
Showing results for 
Search instead for 
Did you mean: 

problem with STM32F411CEUx and HAL on SDIO and ADC.

LLope.1
Associate II

Hello everyone, first of all I apologize for my English as I am not fluent in it.

I am using the STM32F411 and SE and when using two DMAs for Tx and Tx it always shows BUSY in HalState. This I have solved (although not totally yet) by choosing only one DMA, the same for both, from a question I saw in this forum. Also, when I use ADC with a regular channel, triggered by TIM3, and two injected, triggered by software, the values ​​of the injected channels are placed in positions 3 and 4 when from CUBEmx it sets offset 0 and 1.

1 REPLY 1
LLope.1
Associate II

Hello again, I have found a workaround to the problem. The ADC is working properly. The problem is with the built-in HAL procedures for SDIO. For convenience and after much trial and error, I am using both DMA channels, although it is not essential, I do not need them for other jobs yet. When you write a block to SD with the HAL_SD_WriteBlocks_DMA function, the result is quite strange (even after fixing it). At the end of the function we find:

    /* Enable SDIO DMA transfer */
    __HAL_SD_DMA_ENABLE(hsd);
 
    /* Enable the DMA Channel */
    if(HAL_DMA_Start_IT(hsd->hdmatx, (uint32_t)pData, (uint32_t)&hsd->Instance->FIFO, (uint32_t)(BLOCKSIZE * NumberOfBlocks)/4U) != HAL_OK)
    {
#if defined(SDIO_STA_STBITERR)
      __HAL_SD_DISABLE_IT(hsd, (SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_TXUNDERR | SDIO_IT_STBITERR));
#else /* SDIO_STA_STBITERR not defined */
      __HAL_SD_DISABLE_IT(hsd, (SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_TXUNDERR));   
#endif /* SDIO_STA_STBITERR */
      __HAL_SD_CLEAR_FLAG(hsd, SDIO_STATIC_FLAGS);
      hsd->ErrorCode |= HAL_SD_ERROR_DMA;
      hsd->State = HAL_SD_STATE_READY;
      hsd->Context = SD_CONTEXT_NONE;
      return HAL_ERROR;
    }
    else
    {
      /* Configure the SD DPSM (Data Path State Machine) */
      config.DataTimeOut   = SDMMC_DATATIMEOUT;
      config.DataLength    = BLOCKSIZE * NumberOfBlocks;
      config.DataBlockSize = SDIO_DATABLOCK_SIZE_512B;
      config.TransferDir   = SDIO_TRANSFER_DIR_TO_CARD;
      config.TransferMode  = SDIO_TRANSFER_MODE_BLOCK;
      config.DPSM          = SDIO_DPSM_ENABLE;
      (void)SDIO_ConfigData(hsd->Instance, &config);
 
      return HAL_OK;
    }
  }
  else
  {
    return HAL_BUSY;
  }
}
 

When it calls HAL_DMA_Start_IT, the DMA that was prepared to send 0x80 words, happens to have in the counter 0xFFFF, so it has sent all the prepared information to SDIO and (which is even more strange) one more word.

0693W000008G5UKQA0.jpgAfter activating the DMA, this happens:

0693W000008G5UUQA0.jpgPossibly this error is caused by some defects in the F411 hardware, the known ones are described in the Errata sheet.

The truth is that the DMA should have waited for SDIO to start requesting data from it, then the SDIO data request signal is active when it is disabled.

My workaround has been to program SDIO first and program and activate DMA later.

    /* Enable SDIO DMA transfer */
    __HAL_SD_DMA_ENABLE(hsd);
      __HAL_SD_DISABLE_IT(hsd, (SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_TXUNDERR | SDIO_IT_STBITERR));    /****************LO HE TRA�?DO DEL ELSE***************/
    config.DataTimeOut   = SDMMC_DATATIMEOUT;
    config.DataLength    = BLOCKSIZE * NumberOfBlocks;
    config.DataBlockSize = SDIO_DATABLOCK_SIZE_512B;
    config.TransferDir   = SDIO_TRANSFER_DIR_TO_CARD;
    config.TransferMode  = SDIO_TRANSFER_MODE_BLOCK;
    config.DPSM          = SDIO_DPSM_ENABLE;
    (void)SDIO_ConfigData(hsd->Instance, &config);
    /****************LO HE TRA�?DO DEL ELSE***************/
 
    __enable_irq();//Algunas veces no salta.
 
    /* Enable the DMA Channel */
    if(HAL_DMA_Start_IT(hsd->hdmatx, (uint32_t)pData, (uint32_t)&hsd->Instance->FIFO, (uint32_t)(BLOCKSIZE * NumberOfBlocks)/4U) != HAL_OK)
    {
#if defined(SDIO_STA_STBITERR)
      __HAL_SD_DISABLE_IT(hsd, (SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_TXUNDERR | SDIO_IT_STBITERR | SDIO_IT_DBCKEND));
#else /* SDIO_STA_STBITERR not defined */
      __HAL_SD_DISABLE_IT(hsd, (SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_TXUNDERR | SDIO_IT_DBCKEND));
#endif /* SDIO_STA_STBITERR */
      __HAL_SD_CLEAR_FLAG(hsd, SDIO_STATIC_FLAGS);
      hsd->ErrorCode |= HAL_SD_ERROR_DMA;
      hsd->State = HAL_SD_STATE_READY;
      hsd->Context = SD_CONTEXT_NONE;
      return HAL_ERROR;
    }
    else
    {
      /* Configure the SD DPSM (Data Path State Machine) */
 
      return HAL_OK;
    }
  }
  else
  {
    return HAL_BUSY;
  }
}

Line 13

__enable_irq ();

I don't think it's necessary but I had a lot of problems with interruptions, in fact I had to program all three myself, and it could be removed.

It is important that SDIO_IT_DBCKEND is active in the SDIO mask register, since it will activate the interrupt and we will know when it has finished sending the block.

With the HAL_SD_ReadBlocks_DMA function you have to do the same. It is more complicated because, surprisingly, it does not follow the same flow as that of writing function . I have put them both with the flow described above.

The proposed workaround has no error handling, but it does not load the CPU for it. Also, as there are already known bugs with SDIO and, I suppose, some unknown bugs. I have not addressed any hypothetical bugs that did not show up in my tests.

To correctly configure this solution, you must:

  1. Disable, in CubeMX, the code generation box corresponding to SDIO, DMA2 Stream 3 and 6 global interrupt, in NVIC.
  2. Write the three callback functions for the interrupts, preparing the handlers to continue:
/**
  * @brief  DMA SD communication error callback
  * @param  hdma: DMA handle
  * @retval None
  */
void DMA2_Stream3_IRQHandler(void)
{
	if(DMA2->LISR & DMA_LISR_TCIF3_Msk)
		sigue = 5;
	//Borramos todos los flag de interrupción.
	DMA2->LIFCR = 0x3F << DMA_LIFCR_CFEIF3_Pos;
	hsd.State = HAL_SD_STATE_READY;
	hsd.hdmarx->State = HAL_DMA_STATE_READY;
	//Se libera el stream.
	__HAL_UNLOCK(hsd.hdmarx);
}
 
/**
  * @brief  DMA SD communication error callback
  * @param  hdma: DMA handle
  * @retval None
  */
void DMA2_Stream6_IRQHandler(void)
{
	if(DMA2->HISR & DMA_HISR_TCIF6_Msk)
		sigue = 1;
 
	//Borramos todos los flag de interrupción.
	DMA2->HIFCR = 0x3FU << DMA_HIFCR_CFEIF6_Pos;
	hsd.State = HAL_SD_STATE_READY;
	hsd.hdmatx->State = HAL_DMA_STATE_READY;
	//Se libera el stream.
	__HAL_UNLOCK(hsd.hdmatx);
}
 
 
void SDIO_IRQHandler(void)
{
	if(SDIO->STA & SDIO_STA_DBCKEND_Msk){//Se ha activado por fin de bloque de datos
		//No se tienen en cuenta los errores, por ahora.
		__HAL_SD_CLEAR_FLAG(&hsd, ((uint32_t)(SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT |
		 SDIO_FLAG_TXUNDERR | SDIO_FLAG_RXOVERR |
		 SDIO_FLAG_CMDSENT | SDIO_FLAG_DATAEND | SDIO_FLAG_DBCKEND)));
		//SDIO->ICR = 0xFFFFFFFF;//Se apagan todos los flags para que no haya interrupciones
		sigueSD = 2;
	}
	else{
		__HAL_SD_CLEAR_FLAG(&hsd, SDIO_STATIC_FLAGS);
	}
 
}

Now with variables sigue and sigueSD you can control when each part has finished. It is necessary that they are set to 0 before starting the block read and write functions and not re-enter (with a while) until they are different from 0. The different numbers have been to be able to know the interrupt functions (I have used many variants) I was going through.

It is not necessary to record the interruptions since they are declared in CubeMX.

Finally, for utility, we write the wait function before entering (or at the beginning of each block function with DMA, as appropriate):

void espera(void){
	pasosEsperaWhile = 0;
	uEspera0 = DameMicros;
 
	while(!sigueSD && !sigue && ret == HAL_OK);
	uEspera1 = CalculaMicros(uEspera0);
	while((s = HAL_SD_GetCardState(&hsd)) != HAL_SD_CARD_TRANSFER){
		++pasosEsperaWhile;
	}
	uEspera2 = CalculaMicros(uEspera0);
}

I have activated the TIM2 to measure microseconds and know the computation time, they are lines that can be eliminated. Only while loops are necessary.

Typically, the wait at HAL_SD_GetCardState takes about 88 us, and requires multiple steps which makes writing very slow. I'll just try writing to see if it improves, since the program writes and then reads to check that it is doing well. In total, a block write with your wait takes about 880 us. There are waits that go to 140 ms, which is an unacceptable delay.

If anyone knows of any better algorithm to improve the high write wait, I would be very grateful.

It is best to put the new functions in a separate file, rename them and call them directly. Thus we avoid that, every time CubeMX regenerates the project, it has to be modified again and also if a new version of HAL arrives, these functions will not be updated.

If I find a new improvement I will try to write it here. At least in the black pills, that are becoming very popular, it will be possible to read and write to the SD without much effort (it was quite difficult for me).

By the way, I use a 1 bit data bus. As the error note already states, in hardware SDIO control it fails, so do not activate it, it leaves everything by default. The read and write tests have been performed at 24 MHz (SDIOCLK = 0) on an 8 GB uSD.