[求助] SPI_FLASH W25Q128写数据所需时间超长该如何解决

Tobey   2017-7-21 12:44 楼主

硬件:
     MCU:STM32F103C8T6(采用SPI2,频率设置为9M)
     FLASH: W25Q128 --》4096×4K(扇区大小) --》 4096×16×256(每个扇区由16个256字节的页组成)
开发平台:
     keil5.20

这几天拿了块焊了片W25Q128的板子做SPI_FLASH读写测试,每次写数据前读取对应扇区数据并擦除扇区,接着将新的数据重新写入扇区,即不管数据量大小,每次都得对一整个扇区进行操作,这样每次写数据所需的时间非常长(接近200ms),各部分对应时间数据手册中也给了,实际应用中该如何缩短FLASH写数据所需时间呢?

datasheet_shotp.jpg





回复评论 (13)

写数据的时间很短,擦除的时间很长,但是写数据之前都要擦除,这个没办法,这个spi flash主要还是作为大量的数据保存,例如字库或者图片,但是不适合高速的数据存取。
亚里士缺德
点赞  2017-7-21 14:13
引用: 汤权 发表于 2017-7-21 14:13
写数据的时间很短,擦除的时间很长,但是写数据之前都要擦除,这个没办法,这个spi flash主要还是作为大量 ...

多谢汤权哥的说明,我是打算用于存储GPS数据,每秒采集一次并存储至FLASH中,需要分析数据时取出~~第一次使用FLASH芯片,不理解的是--》FLASH是按页编程的,但是在写入数据时至少要擦除一个扇区,而一个扇区是由16个256字节的页构成的,这样不是每次不管写入的数据量多小,为了保留原有的数据,每次都得读取4K数据->擦除FLASH中的数据->修改缓存数据->将缓存数据写入FLASH,这一写就是一整个扇区的数据了,这么浪费?是不是我理解错了?~~~
点赞  2017-7-21 16:16
引用: Tobey 发表于 2017-7-21 16:16
多谢汤权哥的说明,我是打算用于存储GPS数据,每秒采集一次并存储至FLASH中,需要分析数据时取出~~第一 ...

SD卡或者说Nand Flash都是这样子的,只是这个spi flash的速度比他们慢很多,这点其实你能在电脑的存储机制上找到,你创建一个txt文件,但是只向文件中写若干字符,然后查看文件属性发现实际空间是字符数,占用空间是4KB, QQ截图20170721163511.jpg
这个是电脑的文件系统的规定,究其原因是底层这样规定的,磁盘或者SD卡都有扇区和块的概念,具体可以百度,例如SD卡的读写都是按照sector来的,而不是单个字节的读写,可以参考FATFS文件系统的底层驱动程序部分。
亚里士缺德
点赞  2017-7-21 16:40
引用: 汤权 发表于 2017-7-21 16:40
SD卡或者说Nand Flash都是这样子的,只是这个spi flash的速度比他们慢很多,这点其实你能在电脑的存储机 ...

好的,多谢
点赞  2017-7-21 17:08
你可以尝试使用两个扇区或者多个扇区进行轮流使用,在空闲的时候把下一次要用的扇区预先擦除掉,这样只需要花费写数据的时间,不用多等待扇区擦除完成
点赞  2017-7-21 17:27
引用: bobde163 发表于 2017-7-21 17:27
你可以尝试使用两个扇区或者多个扇区进行轮流使用,在空闲的时候把下一次要用的扇区预先擦除掉,这样只需要 ...

好的,谢谢!等新板子整合好了试试,当前每次写入的数据的数据不足一个扇区4K的大小,一般一次存储128字节,这样只要当前正在使用的扇区未存满,每次存储数据时都得先把数据读出存储至缓存中,修改缓存数据,接着再将修改后的缓存数据写入扇区,同一个扇区要被重复写入32次才写满~
点赞  2017-7-22 08:43
引用: Tobey 发表于 2017-7-22 08:43
好的,谢谢!等新板子整合好了试试,当前每次写入的数据的数据不足一个扇区4K的大小,一般一次存储128字 ...

那这样的话,你可以每次使用一页来写入数据就可以了,当一个扇区里的所有页用完了再擦除扇区,这样也不用先读取到内存中了,可以直接写入
点赞  2017-7-22 09:05
引用: bobde163 发表于 2017-7-22 09:05
那这样的话,你可以每次使用一页来写入数据就可以了,当一个扇区里的所有页用完了再擦除扇区,这样也不用 ...

是的,感觉仅仅写入少量数据而要以4K的单位写入数据太浪费了~听了你的建议正在考虑修改为按页写数据,擦除倒是可以通过把指定地址中的数据读出与将要写入的数据进行比较,从而判断要不要擦除,不过因为数据写入时循环的,写数据时计算地址有点麻烦 ,而且因为地址是32位的,移植到大点的项目就会有数据堆栈溢出问题,在考虑要不要换成引用,,,
点赞  2017-7-22 23:19
感觉哪里有延时,驱动中程序代码中存在延时,请具体看下是否存在,不应该这么长时间
点赞  2017-7-29 15:35
引用: sunset2007 发表于 2017-7-29 15:35
感觉哪里有延时,驱动中程序代码中存在延时,请具体看下是否存在,不应该这么长时间

延迟倒是没有,就是每次写之前都得读一个扇区,然后擦除一个扇区,这部分比较耗时,现在做了修改,只能按顺序写入数据,每次写到扇区首地址时则擦除整个扇区,每次写入数据少于256字节时则读出一页数据并更新后重新写入,读写单位由扇区缩小为以页为单位,这样可以缩短些时间
点赞  2017-8-4 10:27
总结下结贴吧,又找不到编辑帖子的地方了,,,
目前flash写的最小单位修改为页(256字节),flash的写顺序只能按顺序从低位地址向高位地址写数据,擦除扇区操作仅在写入地址在扇区首地址时才执行,
常规条件下,若写入的数据正好是页首地址,且写入数据大小为256字节,则直接写入;少于256字节时则先读出页内原始数据,更新数据后重新写入该页,完成数据更新

本部分驱动api接口如下所示,提供个参考,欠妥之处还望多拍砖!
  1. /*
  2. *********************************************************************************************************
  3. *
  4. *        模块名称 : SPI接口串行FLASH 读写模块
  5. *        文件名称 : bsp_spi_flash.c
  6. *        版    本 : V1.0
  7. *        说    明 : 支持 SST25VF016B、MX25L1606E 和 W25Q32DWSSIG、W25Q32BVSSIG、W25Q64BVSSIG,W25Q128BVSSIG
  8. *                           SST25VF016B 的写操作采用AAI指令,可以提高写入效率。
  9. *
  10. *                           缺省使用STM32F1的硬件SPI2接口,时钟频率最高为 9MHz
  11. *       
  12. * 本模块的使用方法:
  13. *                                1、初始化模块:sf_Init();
  14. *                                2、调用写数据函数向FLASH写入数据:sf_WriteByIndex(_pBuf, NumOfSector, NumofPage, _usWriteFixSize);
  15. *                                3、调用读数据函数从FLASH读取数据:sf_ReadByIndex(_pBuf, NumOfSector, NumofPage, _usReadFixSize);
  16. *       
  17. *                                以W25Q32BVSSIG为例,以下代码用于写满W25Q32BVSSIG:

  18.                                 uint16_t j=0,k=0;                                // 扇区与页的索引
  19.                                 uint8_t i;                                               
  20.                                 char buf[32] = {0};                        // 写入数据缓存区
  21.                                 uint8_t temp[128];                        // 读出数据缓冲区
  22.                                 sf_Init();                                                         // 初始化SPIFLASH
  23.                                 sf_EraseChip();                                 // 擦除整个FLASH

  24.                                 // 写满W25Q32BV
  25.                                 // W25Q32BV总容量为4M,总共有1024个扇区,每个扇区由16个256字节的页构成
  26.                                 // 这里自定义每页大小为32字节,则每页由128个页构成
  27.                                 for(j=0; j<1024; j++)
  28.                                 {
  29.                                         for(k=0; k<128; k++)
  30.                                         {
  31.                                                 for(i=0; i<32; i++)
  32.                                                 {
  33.                                                         buf[i] = k;
  34.                                                 }
  35.                                                
  36.                                                 if(0 == sf_WriteByIndex((uint8_t *)buf, j, k, 32)) // 如果写入失败则打印失败信息
  37.                                                 {
  38.                                                         printf("%s\n","向SPIFLASH中写入数据失败!");
  39.                                                 }
  40.                                                 sf_ReadByIndex((uint8_t *)temp, j, k, 32); // 读取数据存储于temp临时数组中
  41.                                         }
  42.                                 }
  43. *
  44. *********************************************************************************************************
  45. */
  46. /*
  47. *********************************************************************************************************
  48. *        函 数 名: sf_AutoWriteSmallPage
  49. *        功能说明: 写1个SmallPAGE并校验,如果不正确则再重写两次。本函数自动完成擦除操作。
  50. *        形    参:          _pBuf : 数据源缓冲区;
  51. *                                _uiWriteAddr :目标区域首地址
  52. *                                _usSize :数据个数,不能超过页面大小
  53. *        返 回 值: 0 : 错误, 1 : 成功
  54. *********************************************************************************************************
  55. */
  56. static uint8_t sf_AutoWriteSmallPage(uint8_t *_ucpSrc, uint32_t _uiWrAddr, uint16_t _usWrLen)
  57. {
  58.         uint16_t i;
  59.         uint16_t j;                                        /* 用于延时 */
  60.         uint32_t uiFirstAddr;                /* 页面首址 */
  61.         uint8_t ucNeedErase;                /* 1表示需要擦除 */
  62.         uint8_t cRet;

  63.         /* 长度为0时不继续操作,直接认为成功 */
  64.         if (_usWrLen == 0)
  65.         {
  66.                 return 1;
  67.         }

  68.         /* 如果偏移地址超过芯片容量则退出 */
  69.         if (_uiWrAddr >= g_tSF.TotalSize)
  70.         {
  71.                 return 0;
  72.         }

  73.         /* 如果数据长度大于页面容量,则退出 */
  74.         if (_usWrLen > 256)
  75.         {
  76.                 return 0;
  77.         }

  78.         /* 判断是否需要先擦除扇区 */
  79.         /* 如果写入地址为扇区首地址, 则需进行擦除 */
  80.         ucNeedErase = 0;

  81.         i = _uiWrAddr % g_tSF.PageSize;        // 页面偏移量
  82.         if(0 == i)
  83.         {
  84.                 ucNeedErase = 1;
  85.         }
  86.        
  87.         uiFirstAddr = _uiWrAddr & (~(256 - 1));

  88.         if (_usWrLen == 256)                /* 整个页面都改写 */
  89.         {
  90.                 for        (i = 0; i < 256; i++)
  91.                 {
  92.                         s_spiBuf[i] = _ucpSrc[i];
  93.                 }
  94.         }
  95.         else                                                /* 改写部分数据 */
  96.         {
  97.                 /* 先将整个页面的数据读出 */
  98.                 sf_ReadBuffer(s_spiBuf, uiFirstAddr, 256);

  99.                 /* 再用新数据覆盖 */
  100.                 i = _uiWrAddr & (256 - 1);
  101.                 memcpy(&s_spiBuf[i], _ucpSrc, _usWrLen);
  102.         }

  103.         /* 写完之后进行校验,如果不正确则重写,最多3次 */
  104.         cRet = 0;
  105.         for (i = 0; i < 3; i++)
  106.         {

  107.                 /* 如果旧数据修改为新数据,所有位均是 1->0 或者 0->0, 则无需擦除,提高Flash寿命 */
  108.                 if (ucNeedErase == 1)
  109.                 {
  110.                         sf_EraseSector(uiFirstAddr);                /* 擦除1个扇区 */
  111.                 }

  112.                 /* 编程一个PAGE */
  113.                 sf_PageWrite(s_spiBuf, uiFirstAddr, 256);

  114.                 if (sf_CmpData(_uiWrAddr, _ucpSrc, _usWrLen) == 0)
  115.                 {
  116.                         cRet = 1;
  117.                         break;
  118.                 }
  119.                 else
  120.                 {
  121.                         if (sf_CmpData(_uiWrAddr, _ucpSrc, _usWrLen) == 0)
  122.                         {
  123.                                 cRet = 1;
  124.                                 break;
  125.                         }

  126.                         /* 失败后延迟一段时间再重试 */
  127.                         for (j = 0; j < 10000; j++);
  128.                 }
  129.         }

  130.         return cRet;
  131. }

  132. /*
  133. *********************************************************************************************************
  134. *        函 数 名: sf_WriteSmallBuffer
  135. *        功能说明: 写1个页面并校验,如果不正确则再重写两次。本函数自动完成擦除操作。
  136. *        形    参:          _pBuf : 数据源缓冲区;
  137. *                                _uiWrAddr :目标区域首地址
  138. *                                _usSize :数据个数
  139. *        返 回 值: 1 : 成功, 0 : 失败
  140. *********************************************************************************************************
  141. */
  142. uint8_t sf_WriteSmallBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
  143. {
  144.         uint16_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
  145.         uint8_t reason = 0;
  146.        
  147.         Addr = _uiWriteAddr % 256;        // 页面偏移量
  148.         count = 256 - Addr;                                // 最后一页写完数据后能够剩余多少空间
  149.         NumOfPage =  _usWriteSize / 256;                // 计算需要写入的数据需要使用多少页的空间
  150.         NumOfSingle = _usWriteSize % 256;        // 计算最后一页还需写入多少数据

  151.         if (Addr == 0) /* 起始地址是页面首地址  */
  152.         {
  153.                 if (NumOfPage == 0) /* 数据长度小于页面大小 */
  154.                 {
  155.                         if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
  156.                         {
  157.                                 reason = 1;
  158.                                 return 0;
  159.                         }
  160.                 }
  161.                 else         /* 数据长度大于等于页面大小 */
  162.                 {
  163.                         while (NumOfPage--)
  164.                         {
  165.                                 if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, g_tSF.PageSize) == 0)
  166.                                 {
  167.                                         return 0;
  168.                                 }
  169.                                 _uiWriteAddr +=  256;
  170.                                 _pBuf += 256;
  171.                         }
  172.                         if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
  173.                         {
  174.                                 return 0;
  175.                         }
  176.                 }
  177.         }
  178.         else  /* 起始地址不是页面首地址  */
  179.         {
  180.                 if (NumOfPage == 0) /* 数据长度小于页面大小 */
  181.                 {
  182.                         if (NumOfSingle > count) /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE --> 当前页从指定的首地址至扇区末尾剩余的空间不足,需要在填满当前页空间后继续在下一页写入数据 */
  183.                         {
  184.                                 temp = NumOfSingle - count;

  185.                                 if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, count) == 0)
  186.                                 {
  187.                                         return 0;
  188.                                 }

  189.                                 _uiWriteAddr +=  count;
  190.                                 _pBuf += count;

  191.                                 if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, temp) == 0)
  192.                                 {
  193.                                         return 0;
  194.                                 }
  195.                         }
  196.                         else // 指定的首地址到页尾的空间足够写入需要写入的数据
  197.                         {
  198.                                 if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
  199.                                 {
  200.                                         return 0;
  201.                                 }
  202.                         }
  203.                 }
  204.                 else        /* 数据长度大于等于页面大小 */
  205.                 {
  206.                         _usWriteSize -= count;
  207.                         NumOfPage =  _usWriteSize / 256;
  208.                         NumOfSingle = _usWriteSize % 256;

  209.                         if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, count) == 0) // 1、写入count字节
  210.                         {
  211.                                 return 0;
  212.                         }

  213.                         _uiWriteAddr +=  count;
  214.                         _pBuf += count;

  215.                         while (NumOfPage--)
  216.                         {
  217.                                 if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, 256) == 0)
  218.                                 {
  219.                                         return 0;
  220.                                 }
  221.                                 _uiWriteAddr +=  g_tSF.PageSize;
  222.                                 _pBuf += g_tSF.PageSize;
  223.                         }

  224.                         if (NumOfSingle != 0)
  225.                         {
  226.                                 if (sf_AutoWriteSmallPage(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
  227.                                 {
  228.                                         return 0;
  229.                                 }
  230.                         }
  231.                 }
  232.         }
  233.         return 1;        /* 成功 */
  234. }

点赞  2017-8-4 10:48
扇区只需要开始写该扇区前擦除一次,写页数据也不需要把原来的数据读出来,只需要保存上次写完数据后的下一个字节地址,接着写就行了。手册里面没有说必须从页首地址开始写数据,而是可以从任意地址开始写数据。
相信自己。
点赞  2017-9-14 16:16
引用: skymaster 发表于 2017-9-14 16:16
扇区只需要开始写该扇区前擦除一次,写页数据也不需要把原来的数据读出来,只需要保存上次写完数据后的下一 ...

嗯 确实是看到了,不过写数据的最小操作单位就是页啊,所以每次肯定都得写一页的数据的,不是这样吗?
点赞  2017-9-16 20:44
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复