格式化/tp 发表于 2018-10-8 21:32:03

STM32F407VE单片机通过DMA往SDIO发送数据,出现Underrun和FIFO错误

#include <stdio.h>
#include <stm32f4xx.h>
#include <string.h>

#define USE_DMA

int fputc(int ch, FILE *fp)
{
if (fp == stdout)
{
    if (ch == '\n')
    {
      while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
      USART_SendData(USART1, '\r');
    }
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
}
return ch;
}

int main(void)
{
char *src = "01234567890123456789012345678901"; // 32个字符

#ifdef USE_DMA
DMA_InitTypeDef dma;
#else
uint8_t i;
#endif
GPIO_InitTypeDef gpio;
SDIO_InitTypeDef sdio;
SDIO_DataInitTypeDef sdio_data;
USART_InitTypeDef usart;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO | RCC_APB2Periph_USART1, ENABLE);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO);

gpio.GPIO_Mode = GPIO_Mode_AF;
gpio.GPIO_OType = GPIO_OType_PP;
gpio.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio.GPIO_Speed = GPIO_High_Speed;
GPIO_Init(GPIOA, &gpio);

gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
GPIO_Init(GPIOC, &gpio);
gpio.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &gpio);

USART_StructInit(&usart);
usart.USART_BaudRate = 115200;
USART_Init(USART1, &usart);
USART_Cmd(USART1, ENABLE);
printf("STM32F407VE DMA\n");
printf("strlen=%d\n", strlen(src));

SDIO_SetPowerState(SDIO_PowerState_ON);
SDIO_StructInit(&sdio);
sdio.SDIO_BusWide = SDIO_BusWide_4b;
sdio.SDIO_ClockDiv = 255;
//sdio.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Enable;
SDIO_Init(&sdio);
SDIO_ClockCmd(ENABLE);
//SDIO_SetSDIOOperation(ENABLE);

#ifdef USE_DMA
dma.DMA_BufferSize = 8;
dma.DMA_Channel = DMA_Channel_4;
dma.DMA_DIR = DMA_DIR_MemoryToPeripheral;
dma.DMA_FIFOMode = DMA_FIFOMode_Enable;
dma.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
dma.DMA_Memory0BaseAddr = (uint32_t)src;
dma.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma.DMA_Mode = DMA_Mode_Normal;
dma.DMA_PeripheralBaseAddr = (uint32_t)&SDIO->FIFO;
dma.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;
dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dma.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA2_Stream3, &dma);
//DMA_FlowControllerConfig(DMA2_Stream3, DMA_FlowCtrl_Peripheral);
DMA_Cmd(DMA2_Stream3, ENABLE);
#endif

sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_32b;
sdio_data.SDIO_DataLength = 32;
sdio_data.SDIO_DataTimeOut = 1000000;
sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;
sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToCard;
sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataConfig(&sdio_data);

#ifdef USE_DMA
SDIO_DMACmd(ENABLE);
#else
for (i = 0; i < 8; i++)
{
    while (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOHE) == RESET);
    SDIO_WriteData(*(uint32_t *)(src + 4 * i));
}
#endif

while (1)
{
    if (DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_HTIF3) == SET)
    {
      DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_HTIF3);
      printf("Half transfer! remaining=%d\n", DMA_GetCurrDataCounter(DMA2_Stream3));
    }
   
    if (DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_TCIF3) == SET)
    {
      DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_TCIF3);
      printf("Transfer complete! remaining=%d\n", DMA_GetCurrDataCounter(DMA2_Stream3));
    }
   
    if (DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_FEIF3) == SET)
    {
      DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_FEIF3);
      printf("FIFO error! remaining=%d\n", DMA_GetCurrDataCounter(DMA2_Stream3));
    }
   
    if (DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_DMEIF3) == SET)
    {
      DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_DMEIF3);
      printf("Direct mode error!\n");
    }
   
    if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) == SET)
    {
      SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
      printf("SDIO data CRC failed!\n");
    }
   
    if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) == SET)
    {
      SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
      printf("SDIO data timeout!\n");
    }
   
    if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == SET)
    {
      SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);
      printf("SDIO data underrun! DCOUNT=%d, NDT=%d\n", SDIO_GetDataCounter(), DMA_GetCurrDataCounter(DMA2_Stream3));
    }
}
}


格式化/tp 发表于 2018-10-8 21:35:40

注释掉USE_DMA宏定义(不使用DMA模式),数据能够发送成功,并输出:
STM32F407VE DMA
strlen=32
SDIO data timeout!
(或SDIO data CRC failed!)

但如果使用DMA方式(定义了USE_DMA),则会输出:
STM32F407VE DMA
strlen=32
Half transfer! remaining=4
SDIO data underrun! DCOUNT=16, NDT=4
也就是说,DMA在还有16字节的时候就停止工作了。这是怎么回事?

格式化/tp 发表于 2018-10-8 21:36:30

有没有谁在STM32F4单片机上成功用DMA驱动了SD卡的?

hugo0668 发表于 2018-10-9 09:39:42

看看学习下,谢谢      

格式化/tp 发表于 2018-10-10 08:36:20

找到解决办法了。修改如下两行:
dma.DMA_BufferSize = 0;

DMA_FlowControllerConfig(DMA2_Stream3, DMA_FlowCtrl_Peripheral);

也就是说,必须由SDIO来控制DMA的数据传输量。

程序运行结果为:
STM32F407VE DMA
strlen=32
Transfer complete! remaining=65527
FIFO error! remaining=65527
SDIO data CRC failed!
成功触发了TCIF中断,且(65535-65527)×4=32字节。
同时还触发了FEIF中断,提示FIFO error。

STM32F4xx_DSP_StdPeriph_Lib_V1.8.0标准外设库里面有一个SD卡的例程程序stm324x7i_eval_sdio_sd.c,其中处理DMA中断的SD_ProcessDMAIRQ函数实现如下:
/**
* @briefThis function waits until the SDIO DMA data transfer is finished.
* @paramNone.
* @retval None.
*/
void SD_ProcessDMAIRQ(void)
{
if(DMA2->LISR & SD_SDIO_DMA_FLAG_TCIF)
{
    DMAEndOfTransfer = 0x01;
    DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_TCIF|SD_SDIO_DMA_FLAG_FEIF);
}
}程序在处理TCIF中断的时候,将FEIF标志位也直接清除掉了。所以从这里可以得出结论:FIFO error错误信息可以直接忽略。


看了一下STM32F4的参考手册,里面有这两段话:
If the DMEIFx or the FEIFx flag is set due to an overrun or underrun condition, the faulty
stream is not automatically disabled and it is up to the software to disable or not the stream
by resetting the EN bit in the DMA_SxCR register. This is because there is no data loss
when this kind of errors occur.
When a FIFO overrun or underrun condition occurs, the data are not lost because the
peripheral request is not acknowledged by the stream until the overrun or underrun
condition is cleared. If this acknowledge takes too much time, the peripheral itself may
detect an overrun or underrun condition of its internal buffer and data might be lost.
所以,FIFO error错误的产生原因是DMA本身的FIFO暂时出现了underrun/overrun的错误,此时DMA不会对SDIO的DMA请求发出确认,这是一个可自恢复的、不会影响数据传输的错误。

格式化/tp 发表于 2018-10-10 08:46:35

接收数据时,除了dma.DMA_DIR = DMA_DIR_PeripheralToMemory外,其他的配置都是一样的,也是必须由SDIO来控制DMA传输量。接收数据不会导致FIFO error错误,所以不需要清除FEIF标志位。

cdt2000 发表于 2018-10-19 13:48:29

mark mark
页: [1]
查看完整版本: STM32F407VE单片机通过DMA往SDIO发送数据,出现Underrun和FIFO错误