硬汉嵌入式论坛

 找回密码
 立即注册
查看: 5118|回复: 14
收起左侧

[有问必答] SPI DMA读取W25QXX问题,卡死在while (wTransferState == TRANSFER_WAIT)

[复制链接]

14

主题

37

回帖

79

积分

初级会员

积分
79
发表于 2020-12-3 17:32:35 | 显示全部楼层 |阅读模式
参考硬汉的V5程序,移植到F103上面
读取数据一致卡死在  while (wTransferState == TRANSFER_WAIT);

  wTransferState = TRANSFER_WAIT;

if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)
{
  Error_Handler(__FILE__, __LINE__);
}

  while (wTransferState == TRANSFER_WAIT)
  {
    ;
  }



回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106907
QQ
发表于 2020-12-4 09:33:15 | 显示全部楼层
降低SPI时钟速度试试。
回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-4 11:34:45 | 显示全部楼层
目前更改SPI速度,分频,没有得到解决。
PS:附件显示太大,不能上传,工程打包,放在百度网盘:

链接:https://pan.baidu.com/s/1Vp7qAjpl3BbZbTJwAebaNg
提取码:0gqk
复制这段内容后打开百度网盘手机App,操作更方便哦



使用HAL库官方自带的SPI DMA 全双工工程,MDK工程。更改读取W25Q128:
W25QXX底层驱动拉取:https://github.com/nimaltd/w25qxx

我只更改了W25Q的read 函数,测试读取效果,可以独立出来,并不影响。其他函数并没有修改(readID,write等)

向W25Q中写数据测试,可以用另外工程写入,或者在这个工程修改。由于我的W25Q之前有写入数据,所以,直接测试read函数就可以。
//=========================================================//
目前问题
说明:       
1、不加DMA,read函数读取数据正常,小规模数据/大批量数据皆正常
       
2、添加DMA,如果添加 while (wTransferState == TRANSFER_WAIT)判断DMA执行完成,
         在回调函数 HAL_SPI_TxRxCpltCallback中清状态wTransferState = TRANSFER_COMPLETE;
         则会卡死在        while (wTransferState == TRANSFER_WAIT),说明就没进回调函数
         已测试过。
       
3、添加DMA,不加 while (wTransferState == TRANSFER_WAIT)状态判断,
         可以读取数据,但是读取的数据量不大,如果整块整块批量读取,读取数据丢失或者无
         已测试过。
       
4、
        CS引脚拉高放在read函数内:                        读取数据失败
        读取到的数据为:
        0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,       
        读取完数据,将CS引脚拉高。如果放在read函数内,则数据读取失败,
        //-------------------------------------------------------------------
        CS引脚拉高放在DMA回调函数内:读取数据正常
        读取到的数据为:
        0x00,0xD0,0xD0,0xBB,0xC4,0xFA,0xD1,0xA1,
        0xD3,0xC3,0xD2,0xB0,0xBB,0xF0,0x73,0x74,
        0x6D,0x33,0x32,0xBF,0xAA,0xB7,0xA2,0xB0,       
        读取完数据,由于不知道DMA什么时候读取完成,所以将CS引脚拉高放在DMA完成的回调函数内执行       
        已测试过。



数据测试:
读取W25Q内的数据:
非DMA模式。读取到如下:
感谢使用用安富莱stm32开发板 http://www.armbbs.cn
pname 2 22 = LSB6060wen.bin
pname 2 22 = LSBniao60.bin
pname 2 22 = MSBniao60.bin
pname 2 22 = LSBback4040.bin
pname 2 22 = LSBdown4060.bin
pname 2 22 = LSBup4060.bin
pname 2 22 = LSBfolder6060.bin

pname = LSBfile6060.bin   size = 20000



DMA模式读取到的数据为:
感谢使用用安富莱stm32开发板 http://www.armbbs.cn
name = LSBfile6060.bin     size = 0

信息能读到(感谢使用用安富莱stm32。。。。。)
而其他数据,则没有读取到(name = LSBfile6060.bin     size = 0)


回复

使用道具 举报

334

主题

2036

回帖

3043

积分

版主

Rank: 7Rank: 7Rank: 7

积分
3043
发表于 2020-12-4 16:10:39 | 显示全部楼层
spi读取比较特别,它的原理造成它必须写数据,才能读数据。 就是说,写数据的同时,读取线上才会有数据出来。先写一个无意义的字节,才可以读到需要的字节。
回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-4 16:15:07 | 显示全部楼层
又移植一遍bsp_spi_bus  bsp_spi_flash


相同的一张图片分别使用查询、中断、DMA方式读取测得时间
/*安富莱 */
查询方式:
W25Q128_ID
pname = 24_SYSTEM0.bin size = 20000
读耗时: 81ms, 读速度: 246913 Bytes/s

中断方式:
W25Q128_ID
pname = 24_SYSTEM0.bin size = 20000
读耗时: 93ms, 读速度: 215053 Bytes/s

DMA方式:
W25Q128_ID
pname = 24_SYSTEM0.bin size = 20000
读耗时: 50ms, 读速度: 400000 Bytes/s


但是使用正常底层读取程序(不使用DMA或者中断):
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
{
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_ReadData);
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
  while (NumByteToRead--)
  {
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    pBuffer++;
  }
  SPI_FLASH_CS_HIGH();
}

W25Q128_ID
pname = 24_SYSTEM0.bin size = 20000
读耗时: 55ms, 读速度: 363636 Bytes/s

所耗费时间与上面使用DMA的方式基本持平,比查询模式,中断模式少了大部分。

相同图片使用标准库读取:
非DMA:
STD:
pname = 24_SYSTEM0.bin size = 20000
读耗时:57ms, 读速度: 350877 Bytes/s


DMA:
pname = 24_SYSTEM0.bin size = 20000
读耗时:23ms, 读速度: 869565 Bytes/s

使用DMA方式时间少了一倍多。

所以汉子哥的程序时间是不是耗费在下面标红的这个部分:
这样的话使用DMA跟不使用DMA优势没有体现出来

void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
        uint16_t rem;
        uint16_t i;
       
        /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
        if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
        {
                return;
        }

        /* 擦除扇区操作 */
        sf_SetCS(0);                                                                        /* 使能片选 */
        g_spiLen = 0;
        g_spiTxBuf[g_spiLen++] = (CMD_READ);                                                        /* 发送读命令 */
        g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);        /* 发送扇区地址的高8bit */
        g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);                /* 发送扇区地址中间8bit */
        g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                                /* 发送扇区地址低8bit */
        bsp_spiTransfer();
       
        /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
        for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
        {
                g_spiLen = SPI_BUFFER_SIZE;
                bsp_spiTransfer();
               
                memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
                _pBuf += SPI_BUFFER_SIZE;
        }
       
        rem = _uiSize % SPI_BUFFER_SIZE;        /* 剩余字节 */
        if (rem > 0)
        {
                g_spiLen = rem;
                bsp_spiTransfer();
               
                memcpy(_pBuf, g_spiRxBuf, rem);
        }
       
        sf_SetCS(1);                                                                        /* 禁能片选 */
}



我试过改成:
void SPI_FLASH_BufferRead(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
        uint16_t i;
        uint8_t cmd[5];
        uint8_t* ptr = _pBuf-4;
        cmd[0] = W25X_ReadData;
        cmd[1] = (uint8_t)((_uiReadAddr)>>24);
        cmd[2] = (uint8_t)((_uiReadAddr)>>16);
        cmd[3] = (uint8_t)((_uiReadAddr)>>8);
        cmd[4] = (uint8_t)(_uiReadAddr);
       
        SF_CS_0();                            //使能器件   
        HAL_SPI_Transmit(&hspi1, cmd, 1, 10);  //发送读取命令   

        HAL_SPI_TransmitReceive_DMA(&hspi1, &cmd[2], ptr+1, 3+_uiSize);  //发送24bit地址
       
//        SF_CS_1();                
}

直接HardFault_Handler
回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-4 16:23:02 | 显示全部楼层
caicaptain2 发表于 2020-12-4 16:10
spi读取比较特别,它的原理造成它必须写数据,才能读数据。 就是说,写数据的同时,读取线上才会有数据出来 ...

对的,我连接中的工程  调用了HAL的DMA收发函数
1、HAL_SPI_Receive_DMA(&SpiHandle,pBuffer,NumByteToRead); 它的函数内有
  if ((hspi->Init.Direction == SPI_DIRECTION_2LINES) && (hspi->Init.Mode == SPI_MODE_MASTER))
  {
    hspi->State = HAL_SPI_STATE_BUSY_RX;

    /* Check tx dma handle */
    assert_param(IS_SPI_DMA_HANDLE(hspi->hdmatx));

    /* Call transmit-receive function to send Dummy data on Tx line and generate clock on CLK line */
    return HAL_SPI_TransmitReceive_DMA(hspi, pData, pData, Size);
  }


2、HAL_SPI_TransmitReceive_DMA 函数就有 pTxData  pRxData,它们应该就完成了写,然后获取读的动作
这些应该是HAL内部函数已经做了处理,理论上调用收发函数就可以了
回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-4 16:41:04 | 显示全部楼层
测试了上面说的 可能耗时的地方
if(Time1_flag)
{
        Time1_flag = 0;
        GetTime1 = GUI_X_GetTime();
}               

        /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
        for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
        {
                g_spiLen = SPI_BUFFER_SIZE;
                bsp_spiTransfer();
               
                memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
                _pBuf += SPI_BUFFER_SIZE;
        }
       
        rem = _uiSize % SPI_BUFFER_SIZE;        /* 剩余字节 */
        if (rem > 0)
        {
                g_spiLen = rem;
                bsp_spiTransfer();
               
                memcpy(_pBuf, g_spiRxBuf, rem);
        }
       
if(Time2_flag)
{
        Time2_flag = 0;
        GetTime2 = GUI_X_GetTime();
}       



W25Q128_ID
pname = 24_SYSTEM0.bin size = 20000
读耗时: 51ms, 读速度: 392156 Bytes/s
GetTime1= 116, GetTime2 = 116
读耗时: 0ms, 读速度: -1 Bytes/s


这地方并未耗费太多时间,那是哪个地方导致 使用DMA,与常规spi flash底层 读取耗费时间基本持平?



回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

积分
212
发表于 2020-12-4 20:29:48 | 显示全部楼层
ZMLZML1009 发表于 2020-12-4 16:15
又移植一遍bsp_spi_bus  bsp_spi_flash

大哥 W25Q128怎么能用4字节地址线来读取呢?

而且读取你为啥不写成
HAL_SPI_Transmit(&hspi1, cmd, 4, 10);  //发送读取命令   
HAL_SPI_Receive(&hspi1, _pBuf, _uiSize);  //发送24bit地址

你的DMA读取有问题你RX和TX都配置了DMA吗?
回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

积分
212
发表于 2020-12-4 21:44:32 | 显示全部楼层
ZMLZML1009 发表于 2020-12-4 16:41
测试了上面说的 可能耗时的地方
if(Time1_flag)
{

如果你的SPI的RX TX都使用DMA的话

HAL_SPI_TransmitReceive_DMA 函数的 CB 函数是:

HAL_SPI_TxRxCpltCallback

/* Check if we are in Rx only or in Rx/Tx Mode and configure the DMA transfer complete callback */
  if (hspi->State == HAL_SPI_STATE_BUSY_RX)
  {
    /* Set the SPI Rx DMA Half transfer complete callback */
    hspi->hdmarx->XferHalfCpltCallback = SPI_DMAHalfReceiveCplt;
    hspi->hdmarx->XferCpltCallback     = SPI_DMAReceiveCplt;
  }
  else
  {
    /* Set the SPI Tx/Rx DMA Half transfer complete callback */
    hspi->hdmarx->XferHalfCpltCallback = SPI_DMAHalfTransmitReceiveCplt;
    hspi->hdmarx->XferCpltCallback     = SPI_DMATransmitReceiveCplt;
  }

所以你的回调函数是不是搞错了
回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

积分
212
发表于 2020-12-4 22:01:23 | 显示全部楼层
ZMLZML1009 发表于 2020-12-4 16:41
测试了上面说的 可能耗时的地方
if(Time1_flag)
{

使用SPI RX TX都是DMA但是接收DMA函数是

HAL_SPI_Receive_DMA

回调函数使用

HAL_SPI_RxCpltCallback
回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-5 10:04:43 | 显示全部楼层
旮旯旭 发表于 2020-12-4 22:01
使用SPI RX TX都是DMA但是接收DMA函数是

HAL_SPI_Receive_DMA

那百度链接中的是HAL库官方例程SPI全双工DMA。HAL_SPI_RxCpltCallback是接收回调。用的函数是收发DMA函数,所以回调用的收发回调。   我几种方式都试过,这个HAL_SPI_RxCpltCallback以及void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi),都试过。
回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-5 10:48:59 | 显示全部楼层
旮旯旭 发表于 2020-12-4 21:44
如果你的SPI的RX TX都使用DMA的话

HAL_SPI_TransmitReceive_DMA 函数的 CB 函数是:

感谢你的回复。
1、RX,TX dma都使能了,百度连接中的DMA使能在 halmsp.c中。
2、HAL_SPI_TransmitReceive_DMA 的回调我用的 HAL_SPI_TxRxCpltCallback

3、HAL_SPI_Receive_DMA的回调HAL_SPI_RxCpltCallback,我也用过。

能试过的,我都试过了,有多个不同版本工程,只是百度云连接中的那个HAL工程只是其中一个,有的没体现。
使用官方HAL库SPI全双工DMA工程的目的就是为了防止我其他地方没有配置好,所以底层SPI、DMA直接用官方的。
连W25QXX都直接拉取的github上的,排除一切干扰因素。

由于我的W25Q128中已经存储了数据(之前标准库版本已经写入),所以理论上我只想测试读取的函数,只写一个Read_dma  函数就可以。
事实上我也干过,W25QXX只有read_ID(保证spi配置没问题),以及read函数(加DMA以及不加DMA)。方便对比实验。
结果就是不加DMA读取数据正常,加DMA版本不正常,要么卡在等待DMA完成标志,要么大批量数据读取不正常。如我上面几楼有测试数据

4、我后面测试速度的工程就是另外又新建了,重新移植了汉子哥的V5例程中W25Qxx的三种方式,查询、中断以及DMA。
这三种方式读取速度我也测试过,读取相同数据
pname = 24_SYSTEM0.bin size = 20000
查询:读耗时: 81ms, 读速度: 246913 Bytes/s
中断:读耗时: 93ms, 读速度: 215053 Bytes/s
DMA:读耗时: 50ms, 读速度: 400000 Bytes/s

用隔壁原子、野火的裸SPI不加DMA方式读取相同数据,
耗时:读耗时: 55ms, 读速度: 363636 Bytes/s
所以就有了发现用了汉子哥的DMA方式比用普通底层读取速度基本持平的实验结果。

5、我最后的解决方案是:寄存器,寄存器,寄存器! 这样有一点好处,可以移植到标准库/HAL库都可以。不用受到限制。
最后用寄存器方式的SPI DMA读取相同数据速度测试:
pname = 24_SYSTEM0.bin size = 20000
读耗时:  17ms, 读速度: 1176470 Bytes/s

快了三倍。






回复

使用道具 举报

14

主题

37

回帖

79

积分

初级会员

积分
79
 楼主| 发表于 2020-12-5 11:03:38 | 显示全部楼层
旮旯旭 发表于 2020-12-4 20:29
大哥 W25Q128怎么能用4字节地址线来读取呢?

而且读取你为啥不写成

为啥用4字节地址线,我并没有用,下面有判断        cmd[0] = W25X_ReadData;
        cmd[1] = (uint8_t)((ReadAddr)>>24);
        cmd[2] = (uint8_t)((ReadAddr)>>16);
        cmd[3] = (uint8_t)((ReadAddr)>>8);
        cmd[4] = (uint8_t)(ReadAddr);
       
        W25Q128_CS_0;                            //使能器件   
        HAL_SPI_Transmit(&hspi1, cmd, 1, 10);  //发送读取命令   
        if(W25QXX_TYPE==W25Q256)    //如果是W25Q128/256的话地址为4字节的,要发送最高8位
        {                               
                HAL_SPI_TransmitReceive_DMA(&hspi1, &cmd[1], ptr, 4+NumByteToRead);  //发送32bit地址
        }
        else
        {
                HAL_SPI_TransmitReceive_DMA(&hspi1, &cmd[2], ptr+1, 3+NumByteToRead);  //发送24bit地址
        }
//        W25Q128_CS_1;  


关于:HAL_SPI_Transmit(&hspi1, cmd, 4, 10);  //发送读取命令  我上面cmd[0] = W25X_ReadData;发送读命令,
所以是用 HAL_SPI_Transmit(&hspi1, cmd, 1, 10);  //发送读取命令没毛病,
后面还有:HAL_SPI_TransmitReceive_DMA(&hspi1, &cmd[2], ptr+1, 3+NumByteToRead);  //发送24bit地址这一句,不是再发送读取地址吗


回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106907
QQ
发表于 2020-12-6 11:36:56 | 显示全部楼层
ZMLZML1009 发表于 2020-12-5 10:48
感谢你的回复。
1、RX,TX dma都使能了,百度连接中的DMA使能在 halmsp.c中。
2、HAL_SPI_TransmitRecei ...

1、你的测试数据太小了,你要测试至少2MB的数据读取来比较。
2、我们那个SPI Flash的驱动性能一般,优势就是用户调用函数WriteBuffer可以随意写操作,而无需调用擦除操作,函数会自动执行。
3、寄存器方式操作SPI Flash的要领在此贴,与SPI DMA基本无差别。

加快SPI连续读写速度的配置方式
http://www.armbbs.cn/forum.php?m ... 1095&fromuid=58
(出处: 硬汉嵌入式论坛)
回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

积分
212
发表于 2020-12-8 14:10:02 | 显示全部楼层
ZMLZML1009 发表于 2020-12-5 11:03
为啥用4字节地址线,我并没有用,下面有判断        cmd[0] = W25X_ReadData;
        cmd[1] = (uint8_t)((ReadAddr)> ...

我的F4板子的SPI DMA没问题,W25Q128的03H读取最大33MHz,你的SPI时钟是多大哦?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-9 14:25 , Processed in 0.199438 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表