赞
踩
最近在项目中遇到了 通过SPI DMA方式读写W25Q256;
网上许多例程都是直接读取flash,读写会占用比较长的时间 ,如果这个SPI上还挂载了其他SPI 器件,如SPI显示屏,就需要通过开启DMA来提升速度了。
在这里记录一下我的调试过程:
一.详细CUBE 配置
1.基础设置,我用的是SPI6.
2.DMA设置,并设置中断优先级
3.SPI global interrupt不用开启
然后生成代码;
二,代码。
基础代码是正点原子的SPI flash 函数
基本读写函数没有开启DMA,因为只有单字节读写,所以不用开启DMA,后面两个就是DMA读写函数(hal库操作还是贼简单)
static inline void W25Q_SpiDmaStop() { while(__HAL_DMA_GET_FLAG(&hdma_spi2_tx,DMA_FLAG_TCIF3_7)) { asm(""); } while (hspiflash.State != HAL_SPI_STATE_READY); // W25Q_SpiLcdCsDisable(); } uint8_t W25Q_SpiReadWriteByte(uint8_t TxData) { uint8_t Rxdata; HAL_SPI_TransmitReceive(&hspiflash,&TxData,&Rxdata,1, 1000); return Rxdata; //返回收到的数据 } uint16_t W25Q_SpiWriteDMA(uint8_t *pTxData, uint16_t Size) { W25Q_SpiDmaStop(); // uint16_t retval = HAL_SPI_Transmit_DMA(&hspiflash,pTxData,Size); uint16_t retval = HAL_SPI_TransmitReceive_DMA(&hspiflash,pTxData,pTxData,Size); return retval; } /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */ uint16_t W25Q_SpiReadDMA( uint8_t *pRxData, uint32_t Size) { uint16_t rem; uint32_t g_spiLen; uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; uint8_t retval = 0; for (uint16_t i = 0; i <( Size / SPI_BUFFER_SIZE); i++) { g_spiLen = SPI_BUFFER_SIZE; HAL_SPI_TransmitReceive_DMA(&hspiflash,g_spiTxBuf,g_spiRxBuf,g_spiLen); memcpy(pRxData, g_spiRxBuf, SPI_BUFFER_SIZE); pRxData += SPI_BUFFER_SIZE; } rem = Size % SPI_BUFFER_SIZE; /* 剩余字节 */ if (rem > 0) { g_spiLen = rem; HAL_SPI_TransmitReceive_DMA(&hspiflash,g_spiTxBuf,g_spiRxBuf,g_spiLen); memcpy(pRxData, g_spiRxBuf, rem); } return retval; }
基本读写函数写好了,就只需要去改flash的块读写操作函数即可,中间有一个很容易忽略的问题在后面提出
//读取SPI FLASH //在指定地址开始读取指定长度的数据 //pBuffer:数据存储区 //ReadAddr:开始读取的地址(24bit) //NumByteToRead:要读取的字节数(最大65535) void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u32 NumByteToRead) { u16 i; W25QXX_CS=0; //使能器件 SPI6_ReadWriteByte(W25X_ReadData); //发送读取命令 if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的,要发送最高8位 { SPI6_ReadWriteByte((u8)((ReadAddr)>>24)); } SPI6_ReadWriteByte((u8)((ReadAddr)>>16)); //发送24bit地址 SPI6_ReadWriteByte((u8)((ReadAddr)>>8)); SPI6_ReadWriteByte((u8)ReadAddr); #ifdef SPI_USE_DMA W25Q_SpiReadDMA(pBuffer,NumByteToRead); W25Q_SpiCsDisable(); W25Q_SpiDmaStop(); //等待写入完成 #else for(i=0;i<NumByteToRead;i++) { pBuffer[i]=SPI6_ReadWriteByte(0XFF); //循环读数 } W25Q_SpiCsDisable(); #endif } //SPI在一页(0~65535)内写入少于256个字节的数据 //在指定地址开始写入最大256字节的数据 //pBuffer:数据存储区 //WriteAddr:开始写入的地址(24bit) //NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!! void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16 i; u8* rxBuffer; W25QXX_Write_Enable(); //SET WEL W25Q_SpiCsEnable(); //使能器件 SPI6_ReadWriteByte(W25X_PageProgram); //发送写页命令 if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的,要发送最高8位 { SPI6_ReadWriteByte((u8)((WriteAddr)>>24)); } SPI6_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址 SPI6_ReadWriteByte((u8)((WriteAddr)>>8)); SPI6_ReadWriteByte((u8)WriteAddr); #ifdef SPI_USE_DMA W25Q_SpiWriteDMA(pBuffer,NumByteToWrite); W25Q_SpiDmaStop(); W25Q_SpiCsDisable(); //取消片选 #else for(i=0;i<NumByteToWrite;i++)SPI6_ReadWriteByte(pBuffer[i]);//循环写数 W25Q_SpiCsDisable(); //取消片选 #endif W25QXX_Wait_Busy(); //等待写入结束 }
上面通过宏定义区分是否使用DMA,其中需要注意的就是W25Q64
的片选信号在DMA方式时不能马上取消片选;因为SPI6_WriteDMA函数实际只是打开了DMA,数据并没有发送完成。
当然在这里等待写入完成也是个坑,暂时没想到怎么填,如果SPI上只挂载了flash,就不用等了,可以去干其他的事。所以只能在回调函数中去取消片选。
W25QXX_Wait_Busy(); //等待写入结束
添加回调函数
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi== &hspi6) { W25QXX_CS=1; } } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi== &hspi6) { W25QXX_CS=1; } }
到这里;DMA方式读写flash才算完成配置了。
总结如下:
1. 配置SPI 及对应Tx RX DMA;接收DMA要分包读取,一次性读取大量数据DMA缓存不够
2.改写flash块读写函数;
3.添加回调函数,取消片选; ->3.直接等待DMA传输完成就取消片选
速度测试:
写100K数据用时2s-3s,因为100K数据需要擦除32个扇区,
W25QXX_Erase_Sector扇区擦除命令就占了90ms,所以spi-flash最好不要实时存取数据,在开关机阶段操作存取保存的数据比较好
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。