Advertisement

hal库模拟量_STM32使用HAL库操作FLASH的注意事项

阅读量:

该微控制器系列普遍具有充足内部存储器中的Flash区域

通常而言,在我们当前的代码逻辑中加入常量数据所占用的空间远未达到片上Flash所需的空间规模。因此我们可以将一些区域划分为掉电存储区域以承担EEPROM的功能。此外当产品设计需求希望进行简单的AOT固件升级时则必须具备片上FLASH操作的能力

在使用STM32的HAL库进行FLASH操作的时候,有以下几点值得注意:

1. 注意顺序

先解锁Flash,再擦除片区,再写入,写完了别忘了加锁。

HAL_FLASH_Unlock();

HAL_FLASHEx_Erase();

HAL_FLASH_Program();

HAL_FLASH_Lock();

2. 擦除相关

HAL_FLASHEx_Erase()函数会接收一个用于清除扇区配置的数据结构作为输入参数。这个参数所对应的结构体内容因不同芯片而异,在STM32的不同系列中表现得也不尽相同——例如F1、F4或L4控制器各自都有特定的区别。此外,在同一系列但存储容量不同的Flash控制器中指向的目标地址也会有所变化。这些数据结构还涉及到了扇区、页以及银行等基本概念——这些概念决定了每次清除操作能够处理的空间范围。建议查阅相关参考手册(如RM0351 STM32L475 RM0090 STM32F407 RM0008 STM32F103)以获取关于嵌入式Flash存储器结构的具体说明,并通过ST官网下载相关文档进行详细学习

在STM32F407VET6上,参考以下代码擦除:

rt_err_t FlashEraseSector7(){

rt_uint32_t flashEraseRet;

HAL_StatusTypeDef halRet;

flashEraseInitType.TypeErase = FLASH_TYPEERASE_SECTORS;

flashEraseInitType.Sector = FLASH_SECTOR_7;

flashEraseInitType.NbSectors = 1;

flashEraseInitType.Banks = FLASH_BANK_1;

flashEraseInitType.VoltageRange = FLASH_VOLTAGE_RANGE_3;

halRet = HAL_FLASHEx_Erase(&flashEraseInitType, &flashEraseRet);

if(halRet != HAL_OK || flashEraseRet != 0xFFFFFFFFU){

return RT_EINTR;

}

return RT_EOK;

}

该代码成功清除ST... sector区域的数据;这部分数据总量为128KB;为了避免与常规程序占用的Flash空间发生冲突;一般情况下我们会选择片内Flash的最后一区;值得注意的是这块数据量看似较大;但对于该芯片而言,并没有其他选择余地;其他芯片则需参考相应的技术文档进行操作;如果可以按Page擦除,则可仅需处理约两KB的空间区域

示例代码中使用了RT-Thread的相关状态类型,可自行忽略。

3. 编程(写入)相关

HAL_FLASH_Program()函数在处理不同芯片的HAL库时需要特别注意其输入参数的设置方式各有不同:有些芯片允许按1字节、2字节、4字节或8字节进行写入操作(例如STM32F407系列芯片),而另一些芯片则仅支持8字节写入(如STM32L4系列)。在执行Flash存储操作时必须遵循严格的字节对齐规则:例如,在向特定Flash地址写入双字节数据时必须确保目标地址也是双字节倍数;若需存储4字节数据,则对应地址需为4字节数值倍数;同样适用于8字节数据的情况;若违背上述规定将会导致函数返回错误结果,并可能引发Hard fault异常情况的发生。值得注意的是,在单个字节数据的情况下无需考虑上述对齐要求。此外需要注意的是:将一个N字节数缓冲块拆分成N次单字节写入操作会导致效率显著下降;因此在保证数据对齐的同时也需要综合考虑读取速度的问题

参考代码如下:

rt_err_t flashWrite(rt_uint32_t address, rt_uint8_t* buffer, rt_uint32_t size){

HAL_StatusTypeDef halRet = HAL_OK;

rt_uint32_t pos = 0;

while((size > pos) && (halRet == HAL_OK)){

halRet = HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, address + pos, buffer[pos]);

pos++;

}

if(halRet == HAL_OK)

return RT_EOK;

else

return RT_ERROR;

}

该代码每次以1个字节的方式读取数据块,并将其存储在一个缓冲区中。未进行针对字节数量的对齐处理。若希望提升运行效率,则可以在现有编码机制的基础上增加针对不同长度的数据块的支持。

4. 写入模式

特别提供在STM32L475上共有三种写入方式

#define FLASH_TYPEPROGRAM_DOUBLEWORD ((uint32_t)0x00)/*!

我对第一个模式的理解较为透彻;至于后面的两个模式,则缺乏深入理解;所有的英文术语我也都能准确识别;将它们串联成一个完整的句子时就感到力不从心;如果有朋友愿意赐教于我,在评论区提出宝贵意见的话,默认情况下我会持之以疑

经过实际操作测试发现仅有第一个操作方案表现出色;另外两个操作方案均会触发Hard fault错误。花了不少时间在网络上搜索相关信息后最终在一个国外论坛上了解到有用户讨论了这两个操作方案;其中一种解决方案是在执行HAL_FLASHEx_Erase()时应采用 FLASH_TYPEERASE_MASSERASE 模式而非传统的 FLASH_TYPEERASE_PAGES 模式

目前的操作步骤是先选择一个页或扇区进行擦除操作。然后将8字节的数据按照指定地址写入内存。其他的模式我也未深入研究。针对STM32L475系列芯片而言,在当前配置下仅支持Flash-TypeProgram Doubleword模式。需要注意的是,输入的内存地址必须满足8字节对齐的要求。由于每次操作仅能一次性处理8字节的数据,并且当前系统中没有其他适用的擦除模式。

5. 读Flash

我现在使用的读Flash的宏定义如下:

#define FlashGetChar(addr) ((char)(addr))

#define FlashGetU8(addr) ((uint8_t)(addr))

#define FlashGetU16(addr) ((uint16_t)(addr))

#define FlashGetU32(addr) ((uint32_t)(addr))

#define FlashGetU64(addr) ((uint64_t)(addr))

此处存在一些常见的错误点,在过去的时间里我曾参考过类似简洁明了的说明

#define FlashGetU8(addr) (uint8_t)addr

上面这种方法,在addr两边少一个括号,这样会导致一个潜在的问题,比如:

uint8_t i = FlashGetU8(FLASH_USER_ADDRESS + 1);

我们旨在获取FLASH_USER_ADDRESS字段向后偏移一位的位置对应的值。然而,在未包裹括号的情况下使用该种宏将导致语法错误,并使所得数据存在偏差。在调试过程中深感困惑后发现问题源于所引用的宏定义存在缺陷,在此情况下建议采用上述改进后的版本作为替代方案。

6. 耗时、锁和寿命

擦除和写入片上Flash都需要耗时较多的操作,在这种情况下与主频的关系并不密切;相反地,则主要与其自身特性相关联。HAL库会根据主频大小自动调节擦除与写入的等待时间;具体数值仍需参考官方文档

需要注意,在进行擦除或写入Flash操作时,在操作前与完成后会依次调用__HAL_LOCK(&pFlash)并完成__HAL_UNLOCK(&pFlash),以确保总线处于加锁状态。由于总线连接的是代码指令的读取端口,在此状态下程序基本无法正常运行。因此在一些高实时性的场合下可能会对通信造成干扰(例如作为高速SPI总线从站或者高实时性以太网通信从站),建议在此时段尽量避免使用片上Flash功能,并推荐采用外部EEPROM配合标准IIC外设及DMA方式进行数据存储与传输

最后需要注意的是存储器的寿命问题,在集成于芯片上的Flash存储器通常仅支持至少10,000次可编程 erase操作(Erase operation),而EPROM则能够支持至少1,00万次这样的操作(at least 1, )相比而言,在外部环境中使用的EPROM则能够支持至少1, )。具体到产品的设计周期,则可能对应于从一至十年的时间跨度(time span)。当然,在设计时如果采用均匀擦写算法等优化策略,则有可能进一步延长其使用寿命(use life)。

全部评论 (0)

还没有任何评论哟~