首页 > 编程语言 >STM32WB55 BLE双核flash擦写程序深度解析

STM32WB55 BLE双核flash擦写程序深度解析

时间:2024-05-30 17:14:11浏览次数:15  
标签:status FLASH 擦写 flash CPU2 STM32WB55 扇区 擦除

简介

STM32WB55的flash擦除有两种机制,一种是只有单核运行下的flash擦除,这种模式下,flash擦除的步骤同其他STM32的flash擦除一样,直接调用HAL库中flash擦除的库函数即可;另一种是双核运行下的flash擦除,这种模式下,因为两颗CPU内核都会访问地址总线,可能会有访问冲突,为了解决这个问题,ST引入了硬件信号量机制,因此,在双核运行下,即当单片机执行BLE应用时,要想擦除flash,就要结合硬件信号量来综合处理,执行步骤比单核下要复杂的多,今天我们就来解析一下双核flash擦除驱动是怎样运行的。

准备变量

在APP_BLE_Init函数中,我们在BLE服务初始化之后,广播启动之前,添加如下代码

/******************** START FLASH TEST SPECIFIC INITIALIZATION *************************/

  NbrOfSectorToBeErased = CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS;
  NbrOfDataToBeWritten = CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS * 512;
  FlashProcessStatus = FLASH_PROCESS_FINISHED;
  FlashOperationReq = FLASH_ERASE_REQ;
  UTIL_SEQ_RegTask(1 << CFG_TASK_FLASH_OPERATION_REQ_ID, UTIL_SEQ_RFU, FlashOperationProc);
  /* Select which mechanism is used by CPU2 to protect its timing versus flash operation */
  SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7);

 /**
   * The error flag shall be cleared before moving forward
   */
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
/******************** END FLASH TEST SPECIFIC INITIALIZATION ***************************/

变量定义如下

uint32_t NbrOfSectorToBeErased;
uint32_t NbrOfDataToBeWritten;
typedef enum
{
  FLASH_PROCESS_FINISHED,
  FLASH_PROCESS_STARTED,
}FlashProcessStatus_t;

typedef enum
{
  FLASH_ERASE_REQ,
  FLASH_WRITE_REQ,
}FlashOperationReq_t;

#define CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS      (1)
  • NbrOfSectorToBeErased直接赋值为一个宏,表示本次要处理的flash扇区个数,STM32WB55的flash每4K字节构成一个扇区,整个扇区的分布在参考手册中

image

由于flash的擦除只能按扇区擦,即当我们要向flash写入新数据时,首先要擦除一个4K字节扇区,然后才能向这个已经擦除成功的扇区内写入数据。

  • NbrOfDataToBeWritten表示本次要写入的数据的个数,注意STM32WB55写入数据时,必须以双字格式写入,即数据的最小写入单位是64bit,用字节表示的话,就是一次性要写入4个字节,因此这个变量表示的含义,是64bit的数据的个数,而非字节个数,这一点非常重要,因此如果要写满一个扇区,则需要写满 4096 / 8 = 512 个字节。我们一般是定义一个uint64_t的数组,然后将要写入的数据拼接成每4个字节一组,填充进该数组,然后将该数组的元素一个一个写进flash。

  • FlashProcessStatus 表示flash擦写任务的执行结果,在双核系统运用中,我们专门启动一个后台任务来处理flash事务,这个任务执行一次,并不能保证flash擦写完全成功,因为在任务执行时,需要获取硬件信号量,如果暂时获取不到,任务就会先结束(不阻塞等待),并且返回FLASH_PROCESS_STARTED,表示这个任务的擦写操作还未完成,之后任务会被调度器重新启动,重新启动后的任务根据这个标志判断是要继续擦写flash。

  • FlashOperationReq 表示任务执行的阶段,因为我们让擦除和写入操作都由同一个任务完成,那这个任务某一阶段到底是要运行擦除函数还是运行写入函数,就是靠这个变量做区分的。

    FlashProcessStatus和FlashOperationReq的作用,可以用如下这个图来表示:

image

  • 系统中注册一个任务FlashOperationProc,用来专门负责flash区域数据的更新

  • SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7);

    这个函数在shci.h文件中有解释

      /**
      * SHCI_C2_SetFlashActivityControl
      * @brief Set the mechanism to be used on CPU2 to prevent the CPU1 to either write or erase in flash
      *
      * @param Source: It can be one of the following list
      *                -  FLASH_ACTIVITY_CONTROL_PES : The CPU2 set the PES bit to prevent the CPU1 to either read or write in flash
      *                -  FLASH_ACTIVITY_CONTROL_SEM7 : The CPU2 gets the semaphore 7 to prevent the CPU1 to either read or write in flash.
      *                                                 This requires the CPU1 to first get semaphore 7 before erasing or writing the flash.
      *
      * @retval Status
      */
    

    意思就是说通过该函数,让CPU2使用bit位还是使用信号量7来阻止CPU1对flash的读写。

  • __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);

    这句代码清空了FLASH由于上电可能导致的错误状态位,保证后面关于flash的HAL库函数能够正常运行,建议每次在处理有关flash的应用之前都调用这句代码对错误状态位清理一下

flash擦写任务

这几句代码理解完成后,我们接下来看执行flash擦写的专用任务函数FlashOperationProc

void FlashOperationProc(void)

这个FlashOperationProc任务,是官方给我们提供的现成可用的flash擦写任务,我们直接将这个任务函数添加到应用中即可,有关于该任务执行的步骤,我已经在代码中添加了注释,供大家参考,这里我带大家看一些关键点

首先,整个任务大的框架就是一个if,一个else,通过判断FlashOperationReq变量是FLASH_ERASE_REQ还是FLASH_WRITE_REQ来确定执行擦除还是写入,这个我们在分析FlashOperationReq变量的作用时已经说过了。

代码

first_secure_sector_idx = (READ_BIT(FLASH->SFR, FLASH_SFR_SFSA) >> FLASH_SFR_SFSA_Pos);

这里涉及一个flash寄存器,内容如下
image

image
STM32WB的主存储区(见上图flash划分)可以简单的分为两类,一类安全flash,专门存放BLE协议栈,一般处于主存储区的尾部,用户无法访问,另一类非安全flash,存放应用程序,放到主存储区的前面,用户可以访问,因此如果要向flash中写入数据,我们不仅要避开应用程序占用的flash区域,也要避开安全flash区域,这样安全flash的存储起始边界就很重要。官方的参考例程,是将要擦写的flash扇区放到安全flash前面,这样就能保证这块flash是空闲可用的,当然擦写的时候,不能超过扇区的大小,否则会碰到安全flash区域。我们可以通过下图直观的看到flash划分。

image

STM32WB不同系列flash大小不一样,安全flash的边界也不一样,我们可以通过读取FLASH->SFSA寄存器来获取安全flash的起始地址,以此来确定与应用程序的边界,获取到安全flash的起始地址后,我们往前让出几个扇区,然后把数据写入到这个扇区就行了。注意我们从这个寄存器中读到的数值,并不是直接可用的地址,而是该地址所在的扇区页的编号,例如我们读取flash为1MB的芯片,读到的值为CE,表示安全flash是从第CE(206)个扇区开始的。这样,变量first_secure_sector_idx就存放了安全flash扇区的起始编号。

接下来,将FlashProcessStatus变量值转成FLASH_PROCESS_STARTED,表示flash任务正在运行。

代码

NbrOfSectorToBeErased = FD_EraseSectors(first_secure_sector_idx - CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS, NbrOfSectorToBeErased);

通过调用驱动函数FD_EraseSectors擦除指定的扇区,函数的第一个入口参数为要擦除的起始扇区的编号,这里我们把first_secure_sector_idx减去我们想要往前让出的扇区的个数,就是我们要擦除的扇区的编号,我们设置为4,从安全flash边界往前让出4个扇区进行擦除,第二个入口参数为要擦除的扇区的个数,我们设置为1,让其擦除一个扇区即可。

#define CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS   (4)

我们先不进FD_EraseSectors函数内部,先知道这个函数有个返回值,返回的是还没有被擦除的扇区的个数,只要返回值不是0,就说明还有扇区没有擦除完,如果是这样,则进代码

      /**
       * There are still sectors to be erased
       * Request the background to run one more time the task
       */
      UTIL_SEQ_SetTask( 1<<CFG_TASK_FLASH_OPERATION_REQ_ID, CFG_SCH_PRIO_0);
      return;

退出当前任务 ,重新激活当前任务,交由调度器重新调度,下次任务执行时继续擦除。

如果返回值为0,则进代码if(NbrOfSectorToBeErased == 0)中,变量值修改

      FlashOperationReq = FLASH_WRITE_REQ;
      FlashProcessStatus = FLASH_PROCESS_FINISHED;
      NbrOfSectorToBeErased = CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS;

其中FlashOperationReq修改,表示当前擦操作已经完成,接下来任务执行时,可以执行写操作。FlashProcessStatus修改,表示当前的flash擦除操作已经完成了,NbrOfSectorToBeErased值恢复为初始值,为后面任务再次被调用执行擦除时做准备。

接下来,进入for循环,执行代码

p_data_flash = (uint64_t*)(FLASH_BASE + ((loop1 + first_secure_sector_idx - CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS)*FLASH_SECTOR_SIZE*1024));

表示从我们刚才擦除的地址开始读取数据,看是不是都擦写成了0xFF(flash被擦除后的数据就是0xFF),通过(loop1 + first_secure_sector_idx - CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS)来计算扇区下标,然后乘上FLASH_SECTOR_SIZE*1024即扇区下标对应的实际地址。

#define FLASH_SECTOR_SIZE                       (4)     /* a sector on stm32wb55xx is 4K bytes */

p_data_flash将存放要检查的扇区的起始地址,循环 for(loop2 = 0; loop2 < (FLASH_SECTOR_SIZE128); loop2++) 表示从当前这个p_data_flash地址开始,以双字(8个字节)为单位检查数据,扇区大小为4 * 1K,1K下有128个双字,那么4K下就有4128个双字,即一个扇区下要检查的双字个数,这样就确定好了循环次数,然后以64bit地址递增读取双字并判断即可。

然后我们看FlashOperationProc任务中,有关写入数据的操作

    NbrOfDataToBeWritten = FD_WriteData(FLASH_BASE
                                        + ((first_secure_sector_idx - CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS)*FLASH_SECTOR_SIZE*1024)
                                        + (((CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS*512) - NbrOfDataToBeWritten)*8),
                                        FlashDataToWriteTab + (CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS*512) - NbrOfDataToBeWritten,
                                        NbrOfDataToBeWritten);

任务调用驱动函数FD_WriteData来实现数据的写入(写入数据前必须保证FLASH扇区已经被擦除),同样,我们先不进FD_WriteData函数里面查看细节,只要知道它用来写入数据就行,它的返回值是剩余的未写入的数据个数,这里的数据个数是以双字为单位的。函数的第一个入口参数是要写入的数据的目标地址,第二个入口参数是数据的源地址,第三个是要写入的数据个数,同样以双字为单位,我们来分析这个公式

FLASH_BASE + ((first_secure_sector_idx - CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS)*FLASH_SECTOR_SIZE*1024)
                                        + (((CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS*512) - NbrOfDataToBeWritten)*8)

((first_secure_sector_idx - CFG_OFFSET_OF_FLASH_SECTOR_TO_PROCESS) * FLASH_SECTOR_SIZE * 1024)得到的是要写入的扇区首地址,(CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS*512)表示要处理的扇区里面双字单元的个数,这个数减去现在准备要写入的数据个数,再乘上8就是当前要写的数据的目标地址,这里的NbrOfDataToBeWritten有两层含义,一层表示本次准备要写入的数据个数,一层代表上次还有多少未写入,其实意思是一样的,归根结底还是因为我们的任务不能一次性将所有数据写入完成,任务需要执行很多次,这样上次未写完的数据个数,就自然而然成为本次准备要写入的数据个数了。我们通过下面这个图就能很好的理解地址为什么这么算了。
image
数据的源地址计算也是同样的道理,只不过这里我们每写完一个双字,指针往后递增一下就可以了。

代码

      for(loop1 = 0; loop1 < (CFG_NBR_OF_FLASH_SECTOR_TO_PROCESS*512); loop1++)

循环读取刚才写入的数据是否与源数据相等,验证写入过程,如果FD_WriteData的返回值不为0,则退出当前任务,并且激活任务,让调度器重新调度,继续写入过程,这跟擦除是一样的。

至此,我们的flash擦写任务代码分析完毕,我们做个总结:

  • 这个任务被调度后,执行完毕并不一定完全擦除或者完全写入数据,它会根据驱动函数的返回值,重新启动自身,让调度器重新调度自己,重新尝试擦写
  • 这个任务有两个关键变量,一个变量负责该任务本次做擦除还是写入,一个变量负责该任务继续之前的擦除或者写入,还是可以进入到下一个阶段。

驱动函数

好,接下来我们分析刚才漏掉的两个驱动函数,这两个函数在官方的驱动文件flash_driver.c文件中,先看擦除

  /**
   * @brief  Implements the Dual core algorithm to erase multiple sectors in flash with CPU1
   *         It calls for each sector to be erased the API FD_EraseSingleSector()
   *
   * @param  FirstSector:   The first sector to be erased
   *                        This parameter must be a value between 0 and (SFSA - 1)
   * @param  NbrOfSectors:  The number of sectors to erase
   *                        This parameter must be a value between 1 and (SFSA - FirstSector)
   * @retval Number of sectors not erased:
   *                        Depending on the implementation of FD_WaitForSemAvailable(),
   *                        it may still have some sectors not erased when the timing protection has been
   *                        enabled by either CPU1 or CPU2. When the value returned is not 0, the application
   *                        should wait until both timing protection before retrying to erase the last missing sectors.
   *
   *                        In addition, When the returned value is not 0:
   *                        - The Sem2 is NOT released
   *                        - The FLASH is NOT locked
   *                        - SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF) is NOT called
   *                        It is expected that the user will call one more time this function to finish the process
   */
uint32_t FD_EraseSectors(uint32_t FirstSector, uint32_t NbrOfSectors);

在flash_driver.h文件中,有该函数的详细描述,这个函数专门用来在双核系统中执行多个扇区的擦除,第一个入口参数是第一个要被擦除的扇区的编号,第二个入口参数是要擦除的扇区的个数,返回值为还未擦除的扇区的个数,由于时序保护机制,所有的扇区并非可以在一个连续的时间段内完全擦除,因此当返回值非0时,应用程序需要等待定时保护结束再重新尝试擦除。函数内部通过变量single_flash_operation_status来确定扇区是否擦除成功,如果不成功,则修改对应的返回值,返回该函数,下次重新尝试。关键代码

  /**
   *  Take the semaphore to take ownership of the Flash IP
   */
  while(LL_HSEM_1StepLock(HSEM, CFG_HW_FLASH_SEMID));

  HAL_FLASH_Unlock();

  /**
   *  Notify the CPU2 that some flash erase activity may be executed
   *  On reception of this command, the CPU2 enables the BLE timing protection versus flash erase processing
   *  The Erase flash activity will be executed only when the BLE RF is idle for at least 25ms
   *  The CPU2 will prevent all flash activity (write or erase) in all cases when the BL RF Idle is shorter than 25ms.
   */
  SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON);

通过获取信号量来获取对flash的操作权,并且解锁flash,并通过shci指令向CPU2发送一个指令,通知CPU2 flash擦除操作将要执行,当CPU2接收到这个指令,它使能基于flash擦除的BLE时序保护处理机制,这种机制使得只有当 BLE RF 闲置至少 25ms 时,才会执行擦除闪存活动,当 BL RF 空闲时间短于 25 ms时,CPU2 在任何情况下都会阻止所有闪存活动(写入或擦除)。

接下来,调用循环体,循环擦除每个扇区

  for(loop_flash = 0; (loop_flash < NbrOfSectors) && (single_flash_operation_status ==  SINGLE_FLASH_OPERATION_DONE) ; loop_flash++)
  {
    single_flash_operation_status = FD_EraseSingleSector(FirstSector+loop_flash);
  }

循环体的截止条件除了扇区个数外,还有单次扇区擦除的结果状态,如果某个扇区擦除的状态为无效,则结束这个循环。之后通过代码

  if(single_flash_operation_status != SINGLE_FLASH_OPERATION_DONE)
  {
    return_value = NbrOfSectors - loop_flash + 1;
  }
  else
  {
    /**
     *  Notify the CPU2 there will be no request anymore to erase the flash
     *  On reception of this command, the CPU2 will disables the BLE timing protection versus flash erase processing
     *  The protection is active until next end of radio event.
     */
    SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF);

    HAL_FLASH_Lock();

    /**
     *  Release the ownership of the Flash IP
     */
    LL_HSEM_ReleaseLock(HSEM, CFG_HW_FLASH_SEMID, 0);

    return_value = 0;
  }

返回还有多少个扇区未擦除,注意由于for循环,loop_flash至少会加1,因此这里有一个NbrOfSectors - loop_flash + 1的操作,总之return_value一定表示有多少个扇区没有处理完毕,如果当前要擦除的这个扇区没有处理完毕,也要算到没有处理的扇区里面。如果能够正常完成for循环,说明给定的扇区已经全部擦除完成,此时向CPU2 发送shci指令,告知擦除操作已经完成,CPU2于是禁用flash擦除相对应的时序保护,时序保护将持续到下一次RADIO事件结束。然后是FLASH上锁,释放flash使用信号量,这跟上面的操作是对称的。

接下来看单一扇区擦除函数,这个函数的入口参数只有一个,即需要擦除的扇区编号

  /**
   * @brief  Implements the Dual core algorithm to erase one sector in flash with CPU1
   *
   *         It expects the following point before calling this API:
   *         - The Sem2 is taken
   *         - The FLASH is unlocked
   *         - SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON) has been called
   *         It expects the following point to be done when no more sectors need to be erased
   *         - The Sem2 is released
   *         - The FLASH is locked
   *         - SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF) is called
   *
   *         The two point above are implemented in FD_EraseSectors()
   *         This API needs to be used instead of FD_EraseSectors() in case a provided library is taking
   *         care of these two points and request only a single operation.
   *
   * @param  FirstSector:   The sector to be erased
   *                        This parameter must be a value between 0 and (SFSA - 1)
   * @retval: SINGLE_FLASH_OPERATION_DONE -> The data has been written
   *          SINGLE_FLASH_OPERATION_NOT_EXECUTED -> The data has not been written due to timing protection
   *                                         from either CPU1 or CPU2. On a failure status, the user should check
   *                                         both timing protection before retrying.
   */
  SingleFlashOperationStatus_t FD_EraseSingleSector(uint32_t SectorNumber);

函数的注释中写的很清楚,在调用这个函数前,需要获取flash信号量,flash解锁,通知CPU2 flash擦除要执行,结束这个函数调用后,使用对称的操作。函数的返回值是擦除的状态,成功或失败,失败是因为时序保护机制导致的。函数内部代码如下,注释写的很清楚,它做了一个小的等待后,直接调用函数ProcessSingleFlashOperation,这个函数很重要,负责擦写,第一个入口参数表示是擦除操作还是写入操作,第二个参数代表本次操作的扇区编号,第三个入口参数为0时无意义。我们接下来就到这个函数里面一探究竟。

SingleFlashOperationStatus_t FD_EraseSingleSector(uint32_t SectorNumber)
{
  SingleFlashOperationStatus_t return_value;
  
  /* Add at least 5us (CPU1 up to 64MHz) to guarantee that CPU2 can take SEM7 to protect BLE timing */ 
  for (volatile uint32_t i = 0; i < 35; i++);
  
  /* The last parameter is unused in that case and set to 0 */
  return_value =  ProcessSingleFlashOperation(FLASH_ERASE, SectorNumber, 0);

  return return_value;
}

代码如下:

static SingleFlashOperationStatus_t ProcessSingleFlashOperation(FlashOperationType_t FlashOperationType,
                                                                uint32_t SectorNumberOrDestAddress,
                                                                uint64_t Data)

这个函数是一个局部函数,没有头文件介绍,我们直接看内部执行流程,首先是局部变量

  SemStatus_t cpu1_sem_status;
  SemStatus_t cpu2_sem_status;
  WaitedSemStatus_t waited_sem_status;
  SingleFlashOperationStatus_t return_status;

  uint32_t page_error;
  FLASH_EraseInitTypeDef p_erase_init;

  waited_sem_status = WAITED_SEM_FREE;

  p_erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
  p_erase_init.NbPages = 1;
  p_erase_init.Page = SectorNumberOrDestAddress;

两个硬件信号量状态cpu1_sem_status和cpu2_sem_status用来表示是否时序保护机制允许flash操作,等待状态waited_sem_status表示当时序保护机制阻止flash操作时应该如何处理。page_error将被HAL库函数使用,p_erase_init是HAL库函数调用时需要的入口结构体。我们还是按先全局,后局部的流程看这个函数。

接着代码

do
  {
    /**
     * When the PESD bit mechanism is used by CPU2 to protect its timing, the PESD bit should be polled here.
     * If the PESD is set, the CPU1 will be stalled when reading literals from an ISR that may occur after
     * the flash processing has been requested but suspended due to the PESD bit.
     *
     * Note: This code is required only when the PESD mechanism is used to protect the CPU2 timing.
     * However, keeping that code make it compatible with the two mechanisms.
     */
    while(LL_FLASH_IsActiveFlag_OperationSuspended());

    UTILS_ENTER_CRITICAL_SECTION();

    /**
     *  Depending on the application implementation, in case a multitasking is possible with an OS,
     *  it should be checked here if another task in the application disallowed flash processing to protect
     *  some latency in critical code execution
     *  When flash processing is ongoing, the CPU cannot access the flash anymore.
     *  Trying to access the flash during that time stalls the CPU.
     *  The only way for CPU1 to disallow flash processing is to take CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID.
     */
    cpu1_sem_status = (SemStatus_t)LL_HSEM_GetStatus(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID);
    if(cpu1_sem_status == SEM_LOCK_SUCCESSFUL)
    {
      /**
       *  Check now if the CPU2 disallows flash processing to protect its timing.
       *  If the semaphore is locked, the CPU2 does not allow flash processing
       *
       *  Note: By default, the CPU2 uses the PESD mechanism to protect its timing,
       *  therefore, it is useless to get/release the semaphore.
       *
       *  However, keeping that code make it compatible with the two mechanisms.
       *  The protection by semaphore is enabled on CPU2 side with the command SHCI_C2_SetFlashActivityControl()
       *
       */
      cpu2_sem_status = (SemStatus_t)LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID);
      if(cpu2_sem_status == SEM_LOCK_SUCCESSFUL)
      {
        /**
         * When CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is taken, it is allowed to only erase one sector or
         * write one single 64bits data
         * When either several sectors need to be erased or several 64bits data need to be written,
         * the application shall first exit from the critical section and try again.
         */
        if(FlashOperationType == FLASH_ERASE)
        {
          HAL_FLASHEx_Erase(&p_erase_init, &page_error);
        }
        else
        {
          HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, SectorNumberOrDestAddress, Data);
        }
        /**
         *  Release the semaphore to give the opportunity to CPU2 to protect its timing versus the next flash operation
         *  by taking this semaphore.
         *  Note that the CPU2 is polling on this semaphore so CPU1 shall release it as fast as possible.
         *  This is why this code is protected by a critical section.
         */
        LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0);
      }
    }

    UTILS_EXIT_CRITICAL_SECTION();

    if(cpu1_sem_status != SEM_LOCK_SUCCESSFUL)
    {
      /**
       * To avoid looping in ProcessSingleFlashOperation(), FD_WaitForSemAvailable() should implement a mechanism to
       * continue only when CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID is free
       */
      waited_sem_status = FD_WaitForSemAvailable(WAIT_FOR_SEM_BLOCK_FLASH_REQ_BY_CPU1);
    }
    else if(cpu2_sem_status != SEM_LOCK_SUCCESSFUL)
    {
      /**
       * To avoid looping in ProcessSingleFlashOperation(), FD_WaitForSemAvailable() should implement a mechanism to
       * continue only when CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is free
       */
      waited_sem_status = FD_WaitForSemAvailable(WAIT_FOR_SEM_BLOCK_FLASH_REQ_BY_CPU2);
    }
  }
  while( ((cpu2_sem_status != SEM_LOCK_SUCCESSFUL) || (cpu1_sem_status != SEM_LOCK_SUCCESSFUL))
      && (waited_sem_status != WAITED_SEM_BUSY) );

这是一个相当大的循环,先执行,轮询PESD位,我们前面有提到过,时序保护有两种方式,一种是使用硬件信号量保护,另一种是通过这个PESD位,这个函数是为了兼容这两种方式,所以这里添加了对PESD位的轮询,这样,如果应用程序选择PESD位来做时序保护,也能直接调用这个函数。在使用PESD位来做时序保护时,如果这个位置置1,则CPU1会停到这里,直到等到PESD位清零再执行下面的flash操作,然后调用UTILS_ENTER_CRITICAL_SECTION代码进入临界段,在多任务操作系统中,要在此处检查是否有其他任务阻止flash操作,当flash处理正在进行时,CPU 不能再访问闪存,在此期间尝试访问flash会导致 CPU 停止运行,
CPU1 禁止闪存处理的唯一方法是采取 CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID信号量。因此这里调用代码

cpu1_sem_status = (SemStatus_t)LL_HSEM_GetStatus(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID);

来获取硬件信号量,查看是否有其他任务在执行flash操作,如果这个信号量能拿到,则继续获取CPU2信号量

cpu2_sem_status = (SemStatus_t)LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID);

如果这个信号量也能拿到,说明CPU2目前没有做时序保护,可以进行flash操作,要注意,CPU2默认使用的是PESD位来做时序保护,因此最前面的通过shci指令通知CPU2使用硬件信号量作为时序保护方法的代码很重要。

当两个硬件信号量全部获取到,此时可以执行的操作是,擦除一个扇区或者写一个双字数据到flash,如果有更多扇区需要擦除或者更多数据写入,则需要退出当前临界段代码重新进入该函数继续执行。接下来根据传进来的第一个入口参数,决定是擦除还是写数据。

        if(FlashOperationType == FLASH_ERASE)
        {
          HAL_FLASHEx_Erase(&p_erase_init, &page_error);
        }
        else
        {
          HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, SectorNumberOrDestAddress, Data);
        }

这里就直接调用HAL库函数去处理了,我们后面再分析这两个库函数。

接下来

LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0);

释放CPU2硬件信号量,由于CPU2会轮询这个信号量,因此要尽快释放,使得CPU2有机会执行下一次flash操作时对应的时序保护操作,这也是为什么这段代码处于临界段的原因。

然后退出临界段。

接下来执行判断

    if(cpu1_sem_status != SEM_LOCK_SUCCESSFUL)
    {
      /**
       * To avoid looping in ProcessSingleFlashOperation(), FD_WaitForSemAvailable() should implement a mechanism to
       * continue only when CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID is free
       */
      waited_sem_status = FD_WaitForSemAvailable(WAIT_FOR_SEM_BLOCK_FLASH_REQ_BY_CPU1);
    }
    else if(cpu2_sem_status != SEM_LOCK_SUCCESSFUL)
    {
      /**
       * To avoid looping in ProcessSingleFlashOperation(), FD_WaitForSemAvailable() should implement a mechanism to
       * continue only when CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is free
       */
      waited_sem_status = FD_WaitForSemAvailable(WAIT_FOR_SEM_BLOCK_FLASH_REQ_BY_CPU2);
    }

函数 FD_WaitForSemAvailable 的内容如下:

__WEAK WaitedSemStatus_t FD_WaitForSemAvailable(WaitedSemId_t WaitedSemId)
{
  /**
   * The timing protection is enabled by either CPU1 or CPU2. It should be decided here if the driver shall
   * keep trying to erase/write the flash until successful or if it shall exit and report to the user that the action
   * has not been executed.
   * WAITED_SEM_BUSY returns to the user
   * WAITED_SEM_FREE keep looping in the driver until the action is executed. This will result in the current stack looping
   * until this is done. In a bare metal implementation, only the code within interrupt handler can be executed. With an OS,
   * only task with higher priority can be processed
   *
   */
  return WAITED_SEM_BUSY;
}

这两个判断其实很精妙,其实这个函数FD_WaitForSemAvailable中的内容是可以根据入口参数进行修改的,当我们前面获取信号量失败后,可以通过这个函数,确定既然失败了,是继续往下走,还是循环的检查直至获取到信号量,而且两个信号量到底哪个获取不到,需要循环检查,这些是可以通过FD_WaitForSemAvailable来定制的,比方我们可以将FD_WaitForSemAvailable的内容设置为,获取不到CPU1硬件信号量时,返回WAITED_SEM_FREE,这样可以在CPU1信号量未获取到时继续执行循环,当获取不到CPU2硬件信号量时,返回WAITED_SEM_BUSY,使其退出当前循环。

我们现在看的例程里面FD_WaitForSemAvailable并没有对入口参数进行区分,都是返回WAITED_SEM_BUSY,那就只要两个其中一个获取不到,就退出当前循环。

最后是循环的判断条件

  while( ((cpu2_sem_status != SEM_LOCK_SUCCESSFUL) || (cpu1_sem_status != SEM_LOCK_SUCCESSFUL))
      && (waited_sem_status != WAITED_SEM_BUSY) );

只要其中一个信号量没有获取成功,并且FD_WaitForSemAvailable的返回值为WAITED_SEM_FREE,则继续这个循环,我们目前返回值都是BUSY,那自然而然只要有一个信号量获取失败,循环就结束了。

然后是等待FLASH忙标记

while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_CFGBSY));

接着

  if(waited_sem_status != WAITED_SEM_BUSY)
  {
    /**
     * The flash processing has been done. It has not been checked whether it has been successful or not.
     * The only commitment is that it is possible to request a new flash processing
     */
    return_status = SINGLE_FLASH_OPERATION_DONE;
  }
  else
  {
    /**
     * The flash processing has not been executed due to timing protection from either the CPU1 or the CPU2.
     * This status is reported up to the user that should retry after checking that each CPU do not
     * protect its timing anymore.
     */
    return_status = SINGLE_FLASH_OPERATION_NOT_EXECUTED;
  }

由于waited_sem_status初始值为free,如果是busy则一定获取信号量失败,并且循环退出了,因为如果是free,则循环一定会执行,此时busy说明操作没有完成,返回未完成状态,如果是free,则操作完毕,循环结束,返回完成状态。

这是擦除驱动函数,接下来看写入数据驱动函数

  /**
   * @brief  Implements the Dual core algorithm to write multiple 64bits data in flash with CPU1
   *         The user shall first make sure the location to be written has been first erase.
   *         Otherwise, the API will loop for ever as it will be not able to write in flash
   *         The only value that can be written even though the destination is not erased is 0.
   *         It calls for each 64bits to be written the API FD_WriteSingleData()
   *
   * @param  DestAddress: Address of the flash to write the first data. It shall be 64bits aligned
   * @param  pSrcBuffer:  Address of the buffer holding the 64bits data to be written in flash
   * @param  NbrOfData:   Number of 64bits data to be written
   * @retval Number of 64bits data not written:
   *                      Depending on the implementation of FD_WaitForSemAvailable(),
   *                      it may still have 64bits data not written when the timing protection has been
   *                      enabled by either CPU1 or CPU2. When the value returned is not 0, the application
   *                      should wait until both timing protection before retrying to write the last missing 64bits data.
   *
   *                      In addition, When the returned value is not 0:
   *                        - The Sem2 is NOT released
   *                        - The FLASH is NOT locked
   *                        It is expected that the user will call one more time this function to finish the process
   */
  uint32_t FD_WriteData(uint32_t DestAddress, uint64_t * pSrcBuffer, uint32_t NbrOfData);

注释中提到,要调用这个函数前必须保证扇区已经被擦除,否则这个API将一直循环,未擦除时只能写入数据0,第一个入口参数时要写入的地址,第二个是源数据的地址,第三个是要写入的双字的个数。

进入函数内部,single_flash_operation_status变量作用同擦除驱动函数一样,记录单次flash操作状态,然后是获取信号量,解锁flash,接着调用循环体

  for(loop_flash = 0; (loop_flash < NbrOfData) && (single_flash_operation_status ==  SINGLE_FLASH_OPERATION_DONE) ; loop_flash++)
  {
    single_flash_operation_status = FD_WriteSingleData(DestAddress+(8*loop_flash), *(pSrcBuffer+loop_flash));
  }

这一步也跟擦除一样,循环结束,如果返回值非0,表示的是未写入的双字的个数。

然后调用

  /**
   * @brief  Implements the Dual core algorithm to write one 64bits data in flash with CPU1
   *         The user shall first make sure the location to be written has been first erase.
   *         Otherwise, the API will loop for ever as it will be not able to write in flash
   *         The only value that can be written even though the destination is not erased is 0.
   *
   *         It expects the following point before calling this API:
   *         - The Sem2 is taken
   *         - The FLASH is unlocked
   *         It expects the following point to be done when no more sectors need to be erased
   *         - The Sem2 is released
   *         - The FLASH is locked
   *
   *         The two point above are implemented in FD_WriteData()
   *         This API needs to be used instead of FD_WriteData() in case a provided library is taking
   *         care of these two points and request only a single operation.
   *
   * @param  DestAddress: Address of the flash to write the data. It shall be 64bits aligned
   * @param  Data:  64bits Data to be written
   * @retval: SINGLE_FLASH_OPERATION_DONE -> The data has been written
   *          SINGLE_FLASH_OPERATION_NOT_EXECUTED -> The data has not been written due to timing protection
   *                                         from either CPU1 or CPU2. On a failure status, the user should check
   *                                         both timing protection before retrying.
   */
  SingleFlashOperationStatus_t FD_WriteSingleData(uint32_t DestAddress, uint64_t Data);

注意这个函数第一个入口参数传入的是要写入数据的地址,因此在前面的循环体中,因为每次是写入双字,即8个字节,因此每次循环有DestAddress+(8*loop_flash),而pSrcBuffer本身是双字指针,因此只要自身递增就可以,我们看到FD_WriteSingleData第一个入口参数不变,还是数据要写入的地址,第二个入口参数变成了要写入的数据值,这里一定要注意。函数的返回值的含义跟FD_EraseSingleSector是一样的,内容

SingleFlashOperationStatus_t FD_WriteSingleData(uint32_t DestAddress, uint64_t Data)
{
  SingleFlashOperationStatus_t return_value;

  return_value =  ProcessSingleFlashOperation(FLASH_WRITE, DestAddress, Data);

  return return_value;
}

这里最终调用ProcessSingleFlashOperation函数,只不过这里传的第一个参数成了FLASH_WRITE,第三个参数不为0了,ProcessSingleFlashOperation前面已经分析过了,这里不再赘述。

HAL库函数

我们接下来看ProcessSingleFlashOperation中的两个库函数,一个用来擦除,擦除时,传入的参数为

HAL_FLASHEx_Erase(&p_erase_init, &page_error);

注意,要擦除的扇区编号已经在前面传给了结构体p_erase_init

  p_erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
  p_erase_init.NbPages = 1;
  p_erase_init.Page = SectorNumberOrDestAddress;

这个函数的内容如下

/**
  * @brief  Perform an erase of the specified FLASH memory pages.
  * @note   Before any operation, it is possible to check there is no operation suspended
  *         by call HAL_FLASHEx_IsOperationSuspended()
  * @param[in]  pEraseInit Pointer to an @ref FLASH_EraseInitTypeDef structure that
  *         contains the configuration information for the erasing.
  * @param[out]  PageError Pointer to variable that contains the configuration
  *         information on faulty page in case of error (0xFFFFFFFF means that all
  *         the pages have been correctly erased)
  * @retval HAL Status
  */
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
{
  HAL_StatusTypeDef status;
  uint32_t index;

  /* Check the parameters */
  assert_param(IS_FLASH_TYPEERASE(pEraseInit->TypeErase));

  /* Process Locked */
  __HAL_LOCK(&pFlash);

  /* Reset error code */
  pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;

  /* Verify that next operation can be proceed */
  status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

  if (status == HAL_OK)
  {
    if (pEraseInit->TypeErase == FLASH_TYPEERASE_PAGES)
    {
      /*Initialization of PageError variable*/
      *PageError = 0xFFFFFFFFU;

      for (index = pEraseInit->Page; index < (pEraseInit->Page + pEraseInit->NbPages); index++)
      {
        /* Start erase page */
        FLASH_PageErase(index);

        /* Wait for last operation to be completed */
        status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

        if (status != HAL_OK)
        {
          /* In case of error, stop erase procedure and return the faulty address */
          *PageError = index;
          break;
        }
      }

      /* If operation is completed or interrupted, disable the Page Erase Bit */
      FLASH_AcknowledgePageErase();
    }

    /* Flush the caches to be sure of the data consistency */
    FLASH_FlushCaches();
  }

  /* Process Unlocked */
  __HAL_UNLOCK(&pFlash);

  return status;
}

这个函数最终会调用FLASH_PageErase实现扇区的擦除,注意这里擦除时只擦除一个扇区,多个扇区擦除是要循环调用单个扇区擦除的函数的。

写入函数

HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, SectorNumberOrDestAddress, Data);

内容也比较简单

/**
  * @brief  Program double word or fast program of a row at a specified address.
  * @note   Before any operation, it is possible to check there is no operation suspended
  *         by call HAL_FLASHEx_IsOperationSuspended()
  * @param  TypeProgram Indicate the way to program at a specified address
  *                       This parameter can be a value of @ref FLASH_TYPE_PROGRAM
  * @param  Address Specifies the address to be programmed.
  * @param  Data Specifies the data to be programmed
  *                This parameter is the data for the double word program and the address where
  *                are stored the data for the row fast program.
  *
  * @retval HAL_StatusTypeDef HAL Status
  */
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
{
  HAL_StatusTypeDef status;

  /* Check the parameters */
  assert_param(IS_FLASH_TYPEPROGRAM(TypeProgram));
  assert_param(IS_ADDR_ALIGNED_64BITS(Address));
  assert_param(IS_FLASH_PROGRAM_ADDRESS(Address));

  /* Process Locked */
  __HAL_LOCK(&pFlash);

  /* Reset error code */
  pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;

  /* Verify that next operation can be proceed */
  status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

  if (status == HAL_OK)
  {
    if (TypeProgram == FLASH_TYPEPROGRAM_DOUBLEWORD)
    {
      /* Check the parameters */
      assert_param(IS_FLASH_PROGRAM_ADDRESS(Address));

      /* Program double-word (64-bit) at a specified address */
      FLASH_Program_DoubleWord(Address, Data);
    }
    else
    {
      /* Check the parameters */
      assert_param(IS_FLASH_FAST_PROGRAM_ADDRESS(Address));

      /* Fast program a 64 row double-word (64-bit) at a specified address */
      FLASH_Program_Fast(Address, (uint32_t)Data);
    }

    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);

    /* If the program operation is completed, disable the PG or FSTPG Bit */
    CLEAR_BIT(FLASH->CR, TypeProgram);
  }

  /* Process Unlocked */
  __HAL_UNLOCK(&pFlash);

  /* return status */
  return status;
}

结构也同擦除一样,会执行写入一个双字的操作,最终操作的还是寄存器。

至此,我们完成了STM32WB55 双核系统应用下flash擦写代码的解析!

标签:status,FLASH,擦写,flash,CPU2,STM32WB55,扇区,擦除
From: https://www.cnblogs.com/dz1206/p/18222725

相关文章

  • GD32 使用ST-Link进行调试出现Error:Flash Download Failed-“Cortex-M3“ 解决方案
    项目场景:原来一直使用STM32,最近有个项目发项工程师打板采用的是GD32,外部引脚是一一对应的,STM32的各种下载工具和方法也是基本相同的。问题描述`前期就是库函数和芯片包要改成GD32的。前期工作都完成了,剩下最后一公里,就是下载程序。在MDK5中,采用STLINK,选好芯片型号,准备下载,但出现了Error:FlashDow......
  • [Paper Reading] FlashOcc: Fast and Memory-Efficient Occupancy Prediction via Cha
    FlashOcc:FastandMemory-EfficientOccupancyPredictionviaChannel-to-HeightPluginlink时间:23.11机构:houmo.ai后摩智能TL;DR当时比较流行的OCC方案内存与计算复杂度较高,本文提出一种称为FlashOcc的方法,仅使用2D卷积将特征由二维空间lift到3D空间。MethodImageEn......
  • flash-attn安装失败
    安装大模型相关环境时,碰到了flash-attn无法安装的问题,看来看去,原来是系统的gcc、g++版本过低。通过以下链接升级centos默认的gcc版本到9:CentOS升级gcc到高版本(全部版本详细过程)。yum-yinstallcentos-release-sclyum-yinstalldevtoolset-9-gccdevtoolset-9-gcc-c++......
  • STM32_HAL_FLASH 模拟 EEPROM
    1.STM32FLASH简介STM32F407ZGT6的FLASH容量为1024K字节,STM32F40xx/41xx的闪存模块组织如图STM32F4的闪存模块由主存储器、系统存储器、OPT区域和选项字节等4部分组成。        主存储器,该部分用来存放代码和数据常数(如const类型的数据)。分为12个......
  • stm32f103c8t6对flash进行操作,Hal库,擦除1页数据大小,写入128字节大小,读取指定地址128字
    参考这篇:STM32IAP应用开发——自制BootLoader-CSDN博客把工程转到HAL库使用的函数,用HAL自带的HAL_FLASHEx_EraseHAL_FLASH_Program 串口显示结果 验证没问题flash在hal库使用的驱动程序#include"flash.h"externvoidFLASH_PageErase(uint32_tPageAddress);//......
  • html5新标签 画布 canvas 替代了 flash
    绘制矩形边框,和填充不同的是绘制使用的是strokeRect,和strokeStyle实现的 绘制路径绘制路径的作用是为了设置一个不规则的多边形状态路径都是闭合的,使用路径进行绘制的时候需要既定的步骤:需要设置路径的起点使用绘制命令画出路径封闭路径填充或者绘制已经封闭路......
  • 双核、DSPIC33CH128MP203-I/M5 DSPIC33CH128MP203-H/M5 DSPIC33CH128MP203-E/M5数字信
    产品简介dsPIC33CH双核数字信号控制器在单个芯片中集成了两个dsPICDSC内核,一个设计用作主器件,而另一个则设计用作从器件。从内核用于执行专用、时间关键型控制代码,而主内核则用于运行用户界面、系统监测和通信功能以及最终应用的定制。dsPIC33CH器件优化用于高性能数字电源、电......
  • TheAlgorithms/C - 各种基础算法、数据结构的 C 语言实现+armink/SFUD - 一款基于 JED
    1、OpenMV-RT-基于恩智浦i.MXRT系列的开源机器视觉AI模块OpenMV-RT是一款基于恩智浦最近主打的i.MXRT超高性能系列MCU的视觉模块,模块设计者是恩智浦大牛工程师宋岩(对,就是ARMCortex-M3权威指南中文版作者)。模块源代码: https://github.com/RockySong/micropython......
  • EasyFlash - 一款轻巧的嵌入式 Flash 存储器库
    1、BabyOS-一套管理功能模块和外设驱动的MCU项目开发框架BabyOS是由网友notrynohigh开发维护的适用于MCU项目的一套管理功能模块和外设驱动的框架。项目主页: https://github.com/notrynohigh/BabyOS对项目而言,使用BabyOS能缩短开发周期。项目开发时选择适用的功......
  • Keil 和Eclipse 软件编译环境下bin和ram以及flash大小及关系
    一.keil软件编译环境ProgramSize:Code=65228 RO-data=5302 RW-data=48 ZI-data=1681keil软件编译后会出现上面的提示,其意义如下:Code:指程序中代码的字节数RO-data:指程序中定义的常量字节数RW-data:程序中已初始化的变量字节数ZI-Data:程序中未初始化的变量字节数......