硬汉嵌入式论坛

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

[SPI/QSPI] STM32H743 QSPI 使用MDMA

[复制链接]

5

主题

16

回帖

31

积分

新手上路

积分
31
发表于 2025-11-23 19:55:02 | 显示全部楼层 |阅读模式
大佬,我使用MDMA读取写入W25Q256的时候,数据完全不对,不用的话没问题,而且使用了MDMA后 直接读取和写入也不正常了,不知道为什么
这是我的配置

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "quadspi.h"





QSPI_HandleTypeDef hqspi;
MDMA_HandleTypeDef hmdma_quadspi_fifo_th;


/* QUADSPI init function */
void MX_QUADSPI_Init(void)
{

  hqspi.Instance = QUADSPI;
  hqspi.Init.ClockPrescaler = 1;
  hqspi.Init.FifoThreshold = 32;
  hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  hqspi.Init.FlashSize = 24;
  hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE;
  hqspi.Init.ClockMode = QSPI_CLOCK_MODE_3;
  hqspi.Init.FlashID = QSPI_FLASH_ID_1;
  hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
  if (HAL_QSPI_Init(&hqspi) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN QUADSPI_Init 2 */
// __HAL_MDMA_ENABLE_IT(&hmdma_quadspi_fifo_th,MDMA_IT_CTC);
  /* USER CODE END QUADSPI_Init 2 */


}


void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)
{


  GPIO_InitTypeDef GPIO_InitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
  if(qspiHandle->Instance==QUADSPI)
  {
  /* USER CODE BEGIN QUADSPI_MspInit 0 */


  /* USER CODE END QUADSPI_MspInit 0 */


  /** Initializes the peripherals clock
  */
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_QSPI;
    PeriphClkInitStruct.QspiClockSelection = RCC_QSPICLKSOURCE_D1HCLK;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
      Error_Handler();
    }


    /* QUADSPI clock enable */
    __HAL_RCC_QSPI_CLK_ENABLE();


    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**QUADSPI GPIO Configuration
    PF6     ------> QUADSPI_BK1_IO3
    PF7     ------> QUADSPI_BK1_IO2
    PF8     ------> QUADSPI_BK1_IO0
    PF9     ------> QUADSPI_BK1_IO1
    PB2     ------> QUADSPI_CLK
    PB6     ------> QUADSPI_BK1_NCS
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);


    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);


    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


    /* QUADSPI MDMA Init */
    /* QUADSPI_FIFO_TH Init */
    hmdma_quadspi_fifo_th.Instance = MDMA_Channel0;
    hmdma_quadspi_fifo_th.Init.Request = MDMA_REQUEST_QUADSPI_FIFO_TH;
    hmdma_quadspi_fifo_th.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
    hmdma_quadspi_fifo_th.Init.Priority = MDMA_PRIORITY_LOW;
    hmdma_quadspi_fifo_th.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
    hmdma_quadspi_fifo_th.Init.SourceInc = MDMA_SRC_INC_BYTE;
    hmdma_quadspi_fifo_th.Init.DestinationInc = MDMA_DEST_INC_DISABLE;
    hmdma_quadspi_fifo_th.Init.SourceDataSize = MDMA_SRC_DATASIZE_BYTE;
    hmdma_quadspi_fifo_th.Init.DestDataSize = MDMA_DEST_DATASIZE_BYTE;
    hmdma_quadspi_fifo_th.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
    hmdma_quadspi_fifo_th.Init.BufferTransferLength = 128;
    hmdma_quadspi_fifo_th.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;
    hmdma_quadspi_fifo_th.Init.DestBurst = MDMA_DEST_BURST_SINGLE;
    hmdma_quadspi_fifo_th.Init.SourceBlockAddressOffset = 0;
    hmdma_quadspi_fifo_th.Init.DestBlockAddressOffset = 0;
    if (HAL_MDMA_Init(&hmdma_quadspi_fifo_th) != HAL_OK)
    {
      Error_Handler();
    }


    if (HAL_MDMA_ConfigPostRequestMask(&hmdma_quadspi_fifo_th, 0, 0) != HAL_OK)
    {
      Error_Handler();
    }


    __HAL_LINKDMA(qspiHandle,hmdma,hmdma_quadspi_fifo_th);

  }
}

这是我的读写函数:

static bool qspi_receive(uint8_t *buf, uint32_t datalen)
{
    hqspi.Instance->DLR = datalen - 1;   /* 配置数据长度 */
    if (HAL_QSPI_Receive(&hqspi, buf, 5000) == HAL_OK)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

static bool qspi_transmit(uint8_t *buf, uint32_t datalen)
{
    hqspi.Instance->DLR = datalen - 1;  /* 配置数据长度 */
    if (HAL_QSPI_Transmit(&hqspi, buf, 5000) == HAL_OK)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}


static bool qspi_receive_dma(uint8_t *buf, uint32_t datalen)
{
    hqspi.Instance->DLR = datalen - 1;  /* 配置数据长度 */
        SCB_CleanInvalidateDCache();
    if (HAL_QSPI_Receive_DMA(&hqspi, buf) == HAL_OK)
    {
               
        uint32_t tickstart = HAL_GetTick();
        while (HAL_QSPI_GetState(&hqspi) != HAL_QSPI_STATE_READY)
        {
            if ((HAL_GetTick() - tickstart) > 5000) // 1秒超时
            {
                return false;
            }
        }
                SCB_CleanInvalidateDCache();
        return true;
    }
    else
    {
        return false;
    }
       
}




static bool qspi_transmit_dma(uint8_t *buf, uint32_t datalen)
{
    hqspi.Instance->DLR = datalen - 1;  /* 配置数据长度 */
        SCB_CleanInvalidateDCache();
    if (HAL_QSPI_Transmit_DMA(&hqspi, buf) == HAL_OK)
    {
                        // 或者使用超时等待
        uint32_t tickstart = HAL_GetTick();
        while (HAL_QSPI_GetState(&hqspi) != HAL_QSPI_STATE_READY)
        {
            if ((HAL_GetTick() - tickstart) > 5000) // 1秒超时
            {
                return false;
            }
        }
                SCB_CleanInvalidateDCache();
        return true;
    }
    else
    {
        return false;
    }
       
}

这里是 读写W25Q256函数




static void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    /* QPI,快速读数据,地址为ReadAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,8空周期,NumByteToRead个数据 */
    QSPI_Handle.Send_Cmd(FLASH_FastReadData, addr, (3 << 6) | (3 << 4) | (3 << 2) | (3 << 0), 8);
    QSPI_Handle.Read(pbuf, datalen);
}
static void norflash_read_dma(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    /* QPI,快速读数据,地址为ReadAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,8空周期,NumByteToRead个数据 */
    QSPI_Handle.Send_Cmd(FLASH_FastReadData, addr, (3 << 6) | (3 << 4) | (3 << 2) | (3 << 0), 8);
    QSPI_Handle.Read_DMA(pbuf, datalen);
}




static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    norflash_write_enable();                    /* 写使能 */
    /* QPI,页写指令,地址为WriteAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,无空周期,NumByteToWrite个数据 */
    QSPI_Handle.Send_Cmd(FLASH_PageProgram, addr, (3 << 6) | (3 << 4) | (3 << 2) | (3 << 0), 0);
    QSPI_Handle.Write(pbuf, datalen);
    norflash_wait_busy();                       /* 等待写入结束 */
}
static void norflash_write_page_dma(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    norflash_write_enable();                    /* 写使能 */
    /* QPI,页写指令,地址为WriteAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,无空周期,NumByteToWrite个数据 */
    QSPI_Handle.Send_Cmd(FLASH_PageProgram, addr, (3 << 6) | (3 << 4) | (3 << 2) | (3 << 0), 0);
    QSPI_Handle.Write_DMA(pbuf, datalen);
    norflash_wait_busy();                       /* 等待写入结束 */
}




static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;              /* 单页剩余的字节数 */


    if (datalen <= pageremain)
    {
        pageremain = datalen;                   /* 不大于256个字节 */
    }


    while (1)
    {
        norflash_write_page(pbuf, addr, pageremain);


        if (datalen == pageremain)
        {
            break;                              /* 写入结束了 */
        }
        else
        {
            pbuf += pageremain;
            addr += pageremain;


            datalen -= pageremain;              /* 减去已经写入了的字节数 */


            if (datalen > 256)
            {
                pageremain = 256;               /* 一次可以写入256个字节 */
            }
            else
            {
                pageremain = datalen;           /* 不够256个字节了 */
            }
        }
    }
}




static void norflash_write_nocheck_dma(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;              /* 单页剩余的字节数 */


    if (datalen <= pageremain)
    {
        pageremain = datalen;                   /* 不大于256个字节 */
    }


    while (1)
    {
        norflash_write_page_dma(pbuf, addr, pageremain);


        if (datalen == pageremain)
        {
            break;                              /* 写入结束了 */
        }
        else
        {
            pbuf += pageremain;
            addr += pageremain;


            datalen -= pageremain;              /* 减去已经写入了的字节数 */


            if (datalen > 256)
            {
                pageremain = 256;               /* 一次可以写入256个字节 */
            }
            else
            {
                pageremain = datalen;           /* 不够256个字节了 */
            }
        }
    }
}




/**
* @brief       写SPI FLASH
* @note        在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
*              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
*              擦除的最小单位为Sector.
*
* @param       pbuf    : 数据存储区
* @param       addr    : 开始写入的地址(最大32bit)
* @param       datalen : 要写入的字节数(最大65535)
* @retval      无
*/
uint8_t g_norflash_buf[4096];


static void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *norflash_buf;
    norflash_buf = g_norflash_buf;
    secpos = addr / 4096;                                       /* 扇区地址 */
    secoff = addr % 4096;                                       /* 在扇区内的偏移 */
    secremain = 4096 - secoff;                                  /* 扇区剩余空间大小 */


    //printf("ad:%X,nb:%X\r\n",addr, datalen);                  /* 测试用 */
    if (datalen <= secremain)secremain = datalen;               /* 不大于4096个字节 */


    while (1)
    {
        norflash_read(norflash_buf, secpos * 4096, 4096);       /* 读出整个扇区的内容 */


        for (i = 0; i < secremain; i++)                         /* 校验数据 */
        {
           if (norflash_buf[secoff + i] != 0XFF)
           {
               break;          /* 需要擦除 */
           }
           
        }


        if (i < secremain)                                      /* 需要擦除 */
        {
            norflash_erase_sector(secpos);                      /* 擦除这个扇区 */


            for (i = 0; i < secremain; i++)                     /* 复制 */
            {
                norflash_buf[i + secoff] = pbuf;
            }


            norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */


        }
        else
        {
            norflash_write_nocheck(pbuf, addr, secremain);      /* 写已经擦除了的,直接写入扇区剩余区间. */
        }


        if (datalen == secremain)
        {
            break;                      /* 写入结束了 */
        }
        else                            /* 写入未结束 */
        {
            secpos++;                   /* 扇区地址增1 */
            secoff = 0;                 /* 偏移位置为0 */


            pbuf += secremain;          /* 指针偏移 */
            addr += secremain;          /* 写地址偏移 */
            datalen -= secremain;       /* 字节数递减 */


            if (datalen > 4096)
            {
                secremain = 4096;       /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = datalen;    /* 下一个扇区可以写完了 */
            }
        }
    }
}




static void norflash_write_dma(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *norflash_buf;
    norflash_buf = g_norflash_buf;
    secpos = addr / 4096;                                       /* 扇区地址 */
    secoff = addr % 4096;                                       /* 在扇区内的偏移 */
    secremain = 4096 - secoff;                                  /* 扇区剩余空间大小 */


   
    if (datalen <= secremain)secremain = datalen;               /* 不大于4096个字节 */


    while (1)
    {
        norflash_read_dma(norflash_buf, secpos * 4096, 4096);       /* 读出整个扇区的内容 */


        for (i = 0; i < secremain; i++)                         /* 校验数据 */
        {
           if (norflash_buf[secoff + i] != 0XFF)
           {
               break;          /* 需要擦除 */
           }
           
        }


        if (i < secremain)                                      /* 需要擦除 */
        {
            norflash_erase_sector(secpos);                      /* 擦除这个扇区 */


            for (i = 0; i < secremain; i++)                     /* 复制 */
            {
                norflash_buf[i + secoff] = pbuf;
            }


            norflash_write_nocheck_dma(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */


        }
        else
        {
            norflash_write_nocheck_dma(pbuf, addr, secremain);      /* 写已经擦除了的,直接写入扇区剩余区间. */
        }


        if (datalen == secremain)
        {
            break;                      /* 写入结束了 */
        }
        else                            /* 写入未结束 */
        {
            secpos++;                   /* 扇区地址增1 */
            secoff = 0;                 /* 偏移位置为0 */


            pbuf += secremain;          /* 指针偏移 */
            addr += secremain;          /* 写地址偏移 */
            datalen -= secremain;       /* 字节数递减 */


            if (datalen > 4096)
            {
                secremain = 4096;       /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = datalen;    /* 下一个扇区可以写完了 */
            }
        }
    }
}






static void norflash_erase_chip(void)
{
    norflash_write_enable();                    /* SET WEL */
    norflash_wait_busy();
    /* QPI,写全片擦除指令,地址为0,无数据_8位地址_无地址_4线传输指令,无空周期,0个字节数据 */
    QSPI_Handle.Send_Cmd(FLASH_ChipErase, 0, (0 << 6) | (0 << 4) | (0 << 2) | (3 << 0), 0);
    norflash_wait_busy();                       /* 等待芯片擦除结束 */
}




static void norflash_erase_sector(uint32_t saddr)
{


    //printf("fe:%x\r\n",Dst_Addr);           /* 监视falsh擦除情况,测试用 */
    saddr *= 4096;
    norflash_write_enable();                  /* SET WEL */
    norflash_wait_busy();
    /* QPI,写扇区擦除指令,地址为0,无数据_32位地址_4线传输地址_4线传输指令,无空周期,0个字节数据 */
    QSPI_Handle.Send_Cmd(FLASH_SectorErase, saddr, (0 << 6) | (3 << 4) | (3 << 2) | (3 << 0), 0);
    norflash_wait_busy();                     /* 等待擦除完成 */
}




static void norflash_wait_busy(void)
{
    while ((norflash_read_sr(1) & 0x01) == 0x01);   /* 等待BUSY位清空 */
}

可能是什么情况呀?

回复

使用道具 举报

1万

主题

7万

回帖

12万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
120448
QQ
发表于 2025-11-24 09:13:52 | 显示全部楼层
直接测试我们分享的原始例子是否正常。

V7-029
https://forum.anfulai.cn/forum.p ... &extra=page%3D1
回复

使用道具 举报

5

主题

16

回帖

31

积分

新手上路

积分
31
 楼主| 发表于 2025-11-24 18:07:01 | 显示全部楼层
eric2013 发表于 2025-11-24 09:13
直接测试我们分享的原始例子是否正常。

V7-029

好了,又调试了好久找出来了,
就是调用之后没有开启QSPI的中断,或者说 没有手动 把hqspi->State == HAL_QSPI_STATE_READY 这个QSPI的状态更改,所以后续的QSP就都出问题了,
在HAL_QSPI_Receive 这个函数里他的hqspi->State 在函数里面查询标志位后就进行hqspi->State  = HAL_QSPI_STATE_READY,复原了这个,但是HAL_QSPI_Receive_DMA 需QSPI中断处理函数哪里来更新这个HAL_QSPI_STATE_READY状态,所以只需要更新状态就行,在MDMA完成中断里手动更新,或者开启QSPI中断用HAL的共用处理函数自动处理也行


但是我这个速度才:  写长度:8192 速度:  85.21701 KB/s  读长度:8192   读速度:  40943.621 KB/s   Err:0.00
   读速度40M还可以, 写速度85K,跟您的程序差了巨大多,就优化mpu配置就行吗

回复

使用道具 举报

1万

主题

7万

回帖

12万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
120448
QQ
发表于 2025-11-25 09:37:11 | 显示全部楼层
AnFuLai#stm32 发表于 2025-11-24 18:07
好了,又调试了好久找出来了,,
就是调用之后没有开启QSPI的中断,或者说 没有手动 把hqspi->Stat ...

写速度还行,你这个包含了擦除时间了吧
回复

使用道具 举报

5

主题

29

回帖

44

积分

新手上路

积分
44
发表于 2025-11-28 10:56:08 | 显示全部楼层
请问用MDMA 可以兼容 文件系统吗,读写擦,都能够通过mdma 实现吗,同时加上文件系统,我不喜欢阻塞,感觉如果qdma 也应当如此
回复

使用道具 举报

1万

主题

7万

回帖

12万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
120448
QQ
发表于 2025-11-29 09:51:08 | 显示全部楼层
VDVA 发表于 2025-11-28 10:56
请问用MDMA 可以兼容 文件系统吗,读写擦,都能够通过mdma 实现吗,同时加上文件系统,我不喜欢阻塞,感觉如果qd ...

基于STM32H7驱动QSPI Flash的FatFS文件系统 + QSPI Flash虚拟U盘实现,读速度24.6MB/S(2019-03-28)
https://forum.anfulai.cn/forum.p ... 1634&fromuid=58
(出处: 硬汉嵌入式论坛)
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-10 17:23 , Processed in 0.051386 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

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