硬汉嵌入式论坛

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

关于bsp_spi_flash.c文件中sf_AutoWritePage函数

[复制链接]

36

主题

1446

回帖

1554

积分

至尊会员

积分
1554
发表于 2019-4-9 16:45:04 | 显示全部楼层 |阅读模式
本帖最后由 sanit 于 2019-4-12 18:53 编辑

1.好久之前在论坛好像看到有坛友指出sf_AutoWritePage函数中重复读取flash,导致效率不高的问题。暂时没有在论坛中搜索出来帖子了。记不太清原帖子的具体内容了,谁帮我找出来原帖子,多谢!已经找到:http://www.armbbs.cn/forum.php?m ... 1095&fromuid=58
2.关于这个问题,sf_AutoWritePage函数本身是没有多余读取的,不过还有可以优化的地方。下面分别给出原版以及优化后的代码,如有不对,欢迎大家指出。
3.原版:
/*
*********************************************************************************************************
*  函 数 名: sf_AutoWritePage
*  功能说明: 写1个PAGE并校验,如果不正确则再重写三次。本函数自动完成擦除操作。
*  形    参: _pBuf        :数据源缓冲区
*            _uiWriteAddr :目标区域首地址
*            _usSize      :数据个数,不能超过页面大小(4KB)
*  返 回 值: 0 : 错误, 1 : 成功  测试通过
*********************************************************************************************************
*/
static uint8_t sf_AutoWritePage1(const uint8_t *_ucpSrc, const uint32_t _uiWrAddr, const uint16_t _usWrLen)
{
  uint16_t i;          /* 计算数据偏移以及for循环使用 */
  uint16_t j;          /* 用于延时 */
  uint32_t uiFirstAddr;/* 扇区首址 */
  uint8_t  ucNeedErase;/* 1表示需要擦除 */
  uint8_t  cRet;       /* 函数执行成功标志 */
  
  /* 长度为0时不继续操作,直接认为成功 */
  if (_usWrLen == 0)
  {
    return 1;
  }
  
  /* 如果偏移地址超过芯片容量则认为失败退出 */
  if (_uiWrAddr >= g_tSF.TotalSize)
  {
    return 0;
  }
  
  /* 如果数据长度大于扇区容量,则认为失败退出 */
  if (_usWrLen > g_tSF.PageSize)
  {
    return 0;
  }
  
  /* 如果FLASH中的数据没有变化,则不写FLASH */
  sf_ReadBuffer(s_spiBuf, _uiWrAddr, _usWrLen);
  if (memcmp(s_spiBuf, _ucpSrc, _usWrLen) == 0)
  {
    return 1;
  }
  
  /* 判断是否需要先擦除扇区 */
  /* 如果旧数据修改为新数据,所有位均是 1->0 或者 0->0, 则无需擦除,提高Flash寿命 */
  ucNeedErase = 0;/* 先清除擦除标志 */
  if (sf_NeedErase(s_spiBuf, _ucpSrc, _usWrLen))
  {
    ucNeedErase = 1;
  }
  
  /* 找到当前地址所在的页首地址,比如0x1010,属于第二页,第二页首地址是0x1000 */
  uiFirstAddr = _uiWrAddr & (~(g_tSF.PageSize - 1));
  
  if (_usWrLen == g_tSF.PageSize)/* 整个扇区都改写 */
  {
    for  (i = 0; i < g_tSF.PageSize; i++)
    {
      s_spiBuf = _ucpSrc;
    }
  }
  else/* 改写部分数据 */
  {
    /* 先将整个扇区的数据读出 */
    sf_ReadBuffer(s_spiBuf, uiFirstAddr, g_tSF.PageSize);
   
    /* 再用新数据覆盖 */
    i = _uiWrAddr & (g_tSF.PageSize - 1);
    memcpy(&s_spiBuf, _ucpSrc, _usWrLen);
  }
  
  /* 写完之后进行校验,如果不正确则重写,最多3次 */
  cRet = 0;/* 清除写入成功标志 */
  for (i = 0; i < 3; i++)
  {
    /* 如果旧数据修改为新数据,所有位均是 1->0 或者 0->0, 则无需擦除,提高Flash寿命 */
    /* 读者标注:但是失败一次后,下次重新写必须执行擦除操作 */
    if (ucNeedErase == 1)
    {
      sf_EraseSector_4K(uiFirstAddr);/* 擦除1个扇区 */
    }
   
    /* 编程一个PAGE */
    sf_Page_Program(s_spiBuf, uiFirstAddr, g_tSF.PageSize);
   
    /* 校验数据,判断写入是否成功 */
    if (sf_CmpData(_uiWrAddr, _ucpSrc, _usWrLen) == 0)
    {
      cRet = 1;
      break;
    }
    else /* 连续校验两次,判断写入是否成功 */
    {
      if (sf_CmpData(_uiWrAddr, _ucpSrc, _usWrLen) == 0)
      {
        cRet = 1;
        break;
      }
      ucNeedErase = 1;/* 写入失败的话,必须执行擦除操作 */ //读者增加,这是一个BUG
      
      /* 失败后延迟一段时间再重试 */
      for (j = 0; j < 65500; j++);
    }
  }
  
  return cRet;
}

优化后:

/*
*********************************************************************************************************
*  函 数 名: sf_AutoWritePage
*  功能说明: 写1个PAGE并校验,如果不正确则再重写三次。本函数自动完成擦除操作。
*  形    参: _pBuf        :数据源缓冲区
*            _uiWriteAddr :目标区域首地址
*            _usSize      :数据个数,不能超过页面大小(4KB)
*  返 回 值: 0 : 错误, 1 : 成功  测试通过
*********************************************************************************************************
*/
static uint8_t sf_AutoWritePage(const uint8_t *_ucpSrc, const uint32_t _uiWrAddr, const uint16_t _usWrLen)
{
  uint16_t i;          /* 计算数据偏移以及for循环使用 */
  uint16_t j;          /* 用于延时 */
  uint32_t uiFirstAddr;/* 扇区首址 */
  uint8_t  ucNeedErase;/* 1表示需要擦除 */
  uint8_t  cRet;       /* 函数执行成功标志 */
  uint16_t offset;     /* 偏移量 */
  
  /* 长度为0时不继续操作,直接认为成功 */
  if (_usWrLen == 0)
  {
    return 1;
  }
  
  /* 如果偏移地址超过芯片容量则认为失败退出 */
  if (_uiWrAddr >= g_tSF.TotalSize)
  {
    return 0;
  }
  
  /* 如果数据长度大于扇区容量,则认为失败退出 */
  if (_usWrLen > g_tSF.PageSize)
  {
    return 0;
  }
  
  /* 找到当前地址所在的页首地址,比如0x1010,属于第二页,第二页首地址是0x1000 */
  uiFirstAddr = _uiWrAddr & (~(g_tSF.PageSize - 1));
  
  /* 先将整个扇区的数据读出 */
  sf_ReadBuffer(s_spiBuf, uiFirstAddr, g_tSF.PageSize);
  
  offset = _uiWrAddr-uiFirstAddr;
  /* 如果FLASH中的数据没有变化,则不写FLASH */
  if (memcmp(&s_spiBuf[offset], _ucpSrc, _usWrLen) == 0)
  {
    return 1;
  }
  
  /* 判断是否需要先擦除扇区 */
  /* 如果旧数据修改为新数据,所有位均是 1->0 或者 0->0, 则无需擦除,提高Flash寿命 */
  ucNeedErase = 0;/* 先清除擦除标志 */
  if (sf_NeedErase(&s_spiBuf[offset], _ucpSrc, _usWrLen))
  {
    ucNeedErase = 1;
  }
  
  if (_usWrLen == g_tSF.PageSize)/* 整个扇区都改写 */
  {
    for (i = 0; i < g_tSF.PageSize; i++)
    {
      s_spiBuf = _ucpSrc;
    }
  }
  else/* 改写部分数据 */
  {
    /* 再用新数据覆盖 */
    memcpy(&s_spiBuf[offset], _ucpSrc, _usWrLen);
  }
  
  /* 写完之后进行校验,如果不正确则重写,最多3次 */
  cRet = 0;/* 清除写入成功标志 */
  for (i = 0; i < 3; i++)
  {
    /* 如果旧数据修改为新数据,所有位均是 1->0 或者 0->0, 则无需擦除,提高Flash寿命 */
    /* 读者标注:但是失败一次后,下次重新写必须执行擦除操作 */
    if (ucNeedErase == 1)
    {
      sf_EraseSector_4K(uiFirstAddr);/* 擦除1个扇区 */
    }
   
    /* 编程一个PAGE */
    sf_Page_Program(s_spiBuf, uiFirstAddr, g_tSF.PageSize);
   
    /* 校验数据,判断写入是否成功 */
    if (sf_CmpData(_uiWrAddr, _ucpSrc, _usWrLen) == 0)
    {
      cRet = 1;
      break;
    }
    else /* 连续校验两次,判断写入是否成功 */
    {
      if (sf_CmpData(_uiWrAddr, _ucpSrc, _usWrLen) == 0)
      {
        cRet = 1;
        break;
      }
      ucNeedErase = 1;/* 写入失败的话,必须执行擦除操作 */ //读者增加,这是一个BUG
      
      /* 失败后延迟一段时间再重试 */
      for (j = 0; j < 65500; j++);
    }
  }
  
  return cRet;
}

这个函数优化是针对每次写数据不满一页,否则不能提高写入效率。

评分

参与人数 1金币 +100 收起 理由
eric2013 + 100 赞一个!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
107137
QQ
发表于 2019-4-9 16:47:02 | 显示全部楼层
看看是不是这个帖子。

SPI接口串行FLASH BUG问题
http://www.armbbs.cn/forum.ph ... id=89447&fromuid=58
(出处: 安富莱电子论坛)
回复

使用道具 举报

36

主题

1446

回帖

1554

积分

至尊会员

积分
1554
 楼主| 发表于 2019-4-9 19:38:48 | 显示全部楼层
eric2013 发表于 2019-4-9 16:47
看看是不是这个帖子。

SPI接口串行FLASH BUG问题

不是这个,有一个帖子说这个函数内部读取了两次,多读了一次。找不到就算了。
回复

使用道具 举报

29

主题

514

回帖

606

积分

金牌会员

积分
606
QQ
发表于 2019-4-10 08:51:39 | 显示全部楼层
看文章,想起了第一份工作部门老大的训导:“多读、多比较、少写”的原则。

写之前是否有 读 和 比较的动作?
  ucNeedErase = 0;/* 先清除擦除标志 */
  if (sf_NeedErase(&s_spiBuf[offset], _ucpSrc, _usWrLen))
  {
    ucNeedErase = 1;
  }

文中这个擦除标志位感觉有点草率。


关于读,也可以分两部分读,一个是简单判断新数据的合法性(特别是外部设备加载的数据源),第二部分是先读原始数据跟新数据对比。
Releasing your creativity
回复

使用道具 举报

36

主题

1446

回帖

1554

积分

至尊会员

积分
1554
 楼主| 发表于 2019-4-10 08:56:27 | 显示全部楼层
廷润 发表于 2019-4-10 08:51
看文章,想起了第一份工作部门老大的训导:“多读、多比较、少写”的原则。

写之前是否有 读 和 比较的 ...

写之前,有比较新数据跟旧数据是否一致。如果一致,则不写,否则判断是否需要擦除,然后写入数据。
回复

使用道具 举报

36

主题

1446

回帖

1554

积分

至尊会员

积分
1554
 楼主| 发表于 2019-4-10 08:57:29 | 显示全部楼层
廷润 发表于 2019-4-10 08:51
看文章,想起了第一份工作部门老大的训导:“多读、多比较、少写”的原则。

写之前是否有 读 和 比较的 ...

标志位就是标志后面是否执行擦除操作,请问你还有好的实现方式吗?
回复

使用道具 举报

29

主题

514

回帖

606

积分

金牌会员

积分
606
QQ
发表于 2019-4-10 09:24:48 | 显示全部楼层
没有更细的实现方式。

写0不写1 的说法好像有点理解差异。用户写数据是 1 改成 0。 擦除是 整个片段都 回归 1. 希望一些蛛丝马迹可以给到你idea
Releasing your creativity
回复

使用道具 举报

36

主题

1446

回帖

1554

积分

至尊会员

积分
1554
 楼主| 发表于 2019-4-10 11:31:27 | 显示全部楼层
廷润 发表于 2019-4-10 09:24
没有更细的实现方式。

写0不写1 的说法好像有点理解差异。用户写数据是 1 改成 0。 擦除是 整个片段都  ...

你看一下这个帖子:

SPI接口串行FLASH BUG问题
http://www.armbbs.cn/forum.ph ... id=89447&fromuid=58
(出处: 安富莱电子论坛)
回复

使用道具 举报

29

主题

514

回帖

606

积分

金牌会员

积分
606
QQ
发表于 2019-4-12 09:12:54 | 显示全部楼层
sanit 发表于 2019-4-10 11:31
你看一下这个帖子:

SPI接口串行FLASH BUG问题

这帖子的评论 是精彩的经验切磋。细琢磨理解新思想了,这也验证我之前stm32 内部flash的一个实测结果。

在一个指定的地址,原本数据是 0x1F, 直接写0x1F 正确,直接写0x13也正确。新技术自带这种优化效果,减少不必要的擦除,提升使用寿命。 这或许也有可能是 内部flash 写速度慢的原因之一吧
Releasing your creativity
回复

使用道具 举报

29

主题

514

回帖

606

积分

金牌会员

积分
606
QQ
发表于 2019-4-12 09:19:25 | 显示全部楼层
最近看的一片文章, 在火星使用设计寿命可能是60天,但是实际使用大大超预期,这就是正统研发人员的手笔。 我也要跟你们修炼不一样的内功。
Releasing your creativity
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-5-20 08:43 , Processed in 0.184899 second(s), 29 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

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