打印
[综合信息]

HC32 flash 读写操作

[复制链接]
5344|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
磨砂|  楼主 | 2021-8-1 15:06 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
HC32 flash 简介
HC32F4A0 的flash是两块独立 FLASH 构成 dual bank。容量高2Mbytes,由两块 1Mbytes 的 FLASH 构成,共 256 个扇区,每个扇区为8Kbytes。 块 0 中扇区 0~扇区 15 为可配置为 OTP 区域。

  • OTP(One Time Program)区域共 134KBytes, 其中 128Kbytes 配置在块 0 地址0x0000_0000~0x0001_FFFF, 6Kbytes 配置在地址 0x0300_00000x0300_17FF。地址0x0300_18000x0300_1AD7 为 OTP 数据锁存区。
  • 编程单位为 4bytes,擦除单位为 8Kbytes

+


使用特权

评论回复
评论
dunwii 2021-10-19 10:50 回复TA
大佬,Flash块 0 中扇区 0~扇区 15 共享的 OTP 区域,配置是否可逆呢? 
沙发
磨砂|  楼主 | 2021-8-1 15:07 | 只看该作者
HC32 flash 操作和时钟之间的关系
要正确读取 FLASH 数据,用户需要根据 CPU 动作频率在 FLASH 读模式寄存器
(EFM_FRMC)中正确设定等待周期数(FLWT[3:0])。具体可以参照下表进行相关设置。


对应相关处理在flash 初始化的时候,更改stc_efm_cfg_t结构体中的u32waitcycle.
eg

int Init(void)
{
    /*
     * Clock  <= 40MHZ            EFM_WAIT_CYCLE_0
     * 40MHZ  < Clock <= 80MHZ    EFM_WAIT_CYCLE_1
     * 80MHZ  < Clock <= 120MHZ   EFM_WAIT_CYCLE_2
     * 120MHZ < Clock <= 160MHZ   EFM_WAIT_CYCLE_3
     * 160MHZ < Clock <= 200MHZ   EFM_WAIT_CYCLE_4
     * 200MHZ < Clock <= 240MHZ   EFM_WAIT_CYCLE_5
     */

    stc_efm_cfg_t stcEfmCfg;
    en_int_status_t flag1;
    en_int_status_t flag2;

    //解锁对应的寄存器
    UnLock_Flash();

    EFM_StructInit(&stcEfmCfg);     //初始化结构体
    //stcEfmCfg.u32BusStatus  = EFM_BUS_RELEASE; /* Bus release while programming or erasing */
    stcEfmCfg.u32WaitCycle = EFM_WAIT_CYCLE_5; // 5-wait @ 240MHz
    EFM_Init(&stcEfmCfg);                      // 初始化

    do{
        flag1 = EFM_GetFlagStatus(EFM_FLAG_RDY0);
        flag2 = EFM_GetFlagStatus(EFM_FLAG_RDY1);
    }while((Set != flag1) || (Set != flag2));  // 等待flash0 和 flash1 准备就绪

    /* 加锁所有扇区 */
    Lock_Flash();
    return 0;
}


使用特权

评论回复
板凳
磨砂|  楼主 | 2021-8-1 15:08 | 只看该作者
Flash 的读写操作
FLASH 支持编程,扇区擦除,全擦除操作。FLASH 编程,扇区/全擦除地址末位必须以 4 对齐(末位地址为: 0x0, 0x4, 0x8, 0xC),编程单位是 4bytes, 扇区擦除单位为 8Kbytes,全擦除根据寄存器设定可以是单个 FLASH块也可是两个 FLASH 块。 FLASH 编程方式分为单次编程无回读模式,单次编程回读模式,连续编程模式三种。 FLASH 编程,擦除期间,设定 EFM_FWMC.BUSHLDCTL=0,则总线被占有,直至擦写结束; EFM_FWMC.BUSHLDCTL=1, 则总线被释放,总线可以继续访问另一块 FLASH 地址。 FLASH 编程,擦除前,请把缓存使能及预取指无效。

解锁寄存器
复位解除后, FLASH 编程,擦除模式寄存器( EFM_FWMC)处于写禁止状态,需要先解除 FLASH 访问保护寄存器( EFM_FAPRT),然后再解除 EFM_KEY1 的保护.

  • 解除 FLASH 寄存器访问写保护(EFM_FAPRT 先写 0x0123, 再写 0x3210)
  • 解除 EFM_KEY1 锁定(EFM_KEY1 先写 0x01234567, 再写 0xFEDCBA98)

对应的库函数为EFM_Unlock(),EFM_FWMC_Unlock();在调用的时候一定是先EFM_Unloc() 后EFM_FWMC_Unlock;
单次编程无回读功能


单编程有回读

eg

int32_t main(void)
{
    stc_efm_cfg_t stcEfmCfg;
    en_int_status_t flag1;
    en_int_status_t flag2;
    uint32_t u32Data = 0xAA5555AAU;
    uint32_t u32Addr;

    /* Unlock peripherals or registers */
    Peripheral_WE();

    /* Configure system clock. HClK = 240MHZ */
    BSP_CLK_Init();
    /* EFM default config. */
    (void)EFM_StructInit(&stcEfmCfg);
    /*
     * Clock <= 40MHZ             EFM_WAIT_CYCLE_0
     * 40MHZ < Clock <= 80MHZ     EFM_WAIT_CYCLE_1
     * 80MHZ < Clock <= 120MHZ    EFM_WAIT_CYCLE_2
     * 120MHZ < Clock <= 160MHZ   EFM_WAIT_CYCLE_3
     * 160MHZ < Clock <= 200MHZ   EFM_WAIT_CYCLE_4
     * 200MHZ < Clock <= 240MHZ   EFM_WAIT_CYCLE_5
     */
    stcEfmCfg.u32WaitCycle = EFM_WAIT_CYCLE_5;
    /* EFM config */
    (void)EFM_Init(&stcEfmCfg);

    /* Wait flash0, flash1 ready. */
    do{
        flag1 = EFM_GetFlagStatus(EFM_FLAG_RDY0);
        flag2 = EFM_GetFlagStatus(EFM_FLAG_RDY1);
    }while((Set != flag1) || (Set != flag2));

    /* Sector 10 disables write protection */
    (void)EFM_SectorCmd_Single(EFM_SECTOR_10, Enable);
    /* Erase sector 10. sector 10: 0x00014000~0x00015FFF */
    (void)EFM_SectorErase(EFM_ADDR_SECTOR10);

    u32Addr = EFM_ADDR_SECTOR10;
    //单个字节写
    if(Ok != EFM_SingleProgram(u32Addr, u32Data))
    {
        dosomething();
    }

   //写后回读
    if(Ok != EFM_ProgramReadBack(u32Addr + sizeof(u32Data), u32Data))
    {
       dosomething();
    }
    EFM_SectorCmd_Single(EFM_SECTOR_10, Disable);

    Peripheral_WP();
    for (;;)
    {
        ;
    }
}


使用特权

评论回复
地板
磨砂|  楼主 | 2021-8-1 15:09 | 只看该作者
连续编程


连续编程模式中特别要注意5步骤,连续编程不可以在程序运行的flash上进行,即如果程序运行在flash0 块上,只能在flash1 上进行连续编程。不然程序会死在EFM_SequenceProgram()中。


使用特权

评论回复
5
磨砂|  楼主 | 2021-8-1 15:10 | 只看该作者
擦除功能
  • 解除 FLASH 的寄存器写保护(EFM_FAPRT 先写 0x0123, 再写 0x3210)。
  • 解除 EFM_KEY1 锁定。 (EFM_KEY1 先写 0x01234567, 再写 0xFEDCBA98)
  • 设定擦除模式(EFM_FWMC.PEMOD[2:0]=100)。
  • 解除写保护。 (EFM_ F0/1NWPRTx(x=0~3)对应位设定为 1)
  • 对需要擦除扇区内的任意地址(地址需以 4 对齐)写入 32 位任意值。
  • 等待 FLASH 处于空闲状态。 (EFM_FSR.RDY0/1=1)
  • 清除擦除结束标志位。 (EFM_FSR.OPTEND0/1)
对已锁存的 OTP 地址发行擦除操作,擦除不成功, OTP 区域数据保留,标志位
EFM_FSR.OTPWERR0 置位。
对应的库函数为EFM_SectorErase;


使用特权

评论回复
6
磨砂|  楼主 | 2021-8-1 15:11 | 只看该作者
全擦除功能


注意:不论擦除是以扇区进行的,即8K,写入的是后必须为4bytes.


使用特权

评论回复
7
磨砂|  楼主 | 2021-8-1 15:11 | 只看该作者
综合应用demo
因为在同一个flash块上无法实现连续编程,因此要进行flash的连续的读写只能通过单次编程实现。

#define HC32FLASH_END           (EFM_ADDR_SECTOR255 + 0x2000)
int FLASH_ReadBytes(uint32_t readAddr,
                                              void  *pBuffer,
                                              int NumToRead)
{
    uint32_t nread = NumToRead;
    uint8_t* d = (uint8_t *)pBuffer;
    const uint8_t* s = (const uint8_t *)readAddr;
   //判断参数
    if (!pBuffer /*|| Address < HC32FLASH_BASE*/ || ((readAddr + NumToRead) >= HC32FLASH_END))
        return 0;

    while (nread >= sizeof(uint32_t) && (((uint32_t)s) <= (HC32FLASH_END - 4)))
    {
        *(uint32_t *)d = *(uint32_t *)s;
        d += sizeof(uint32_t);
        s += sizeof(uint32_t);
        nread -= sizeof(uint32_t);
    }

    while ((nread != 0) && (((uint32_t)s) < HC32FLASH_END))
    {
        *d++ = *s++;
        nread--;
    }

    return (NumToRead - nread);
}

//使用单次法写入多个数据
int  FlashWriteBuff(uint32_t writeAddr,uint32_t u32Len, const uint32_t *pu32Buf)
{
    uint16_t i = 0;
    uint32_t mm;
    mm = *pu32Buf;

    if(NULL == pu32Buf)
    {
       return 1;
    }

    for(i = 0; i < u32Len; i++)
    {
        if(Ok != EFM_SingleProgram(writeAddr, *pu32Buf))
        {
            return 1;
        }

        writeAddr = writeAddr + sizeof(uint32_t);
        pu32Buf++;
    }
    return 0;

}
/**
* @brief:  写FLASH操作
* @param:  Address -- 写入起始地址,要求必须4字节对齐!!
            Buffer  -- 待写入的数据,(uint32_t *)
            NumToWrite -- 要写入的数据量,单位:字节!
* @note:   该函数是用的是单次写入,非连续
* @return: none
**/
int FLASH_WriteBytes(uint32_t writeAddr, uint32_t *pBuffer,  int NumToWrite)
{
    uint32_t i = 0;
    uint32_t sectorPos = 0;         // 扇区位置
    uint32_t sectorOff = 0;         // 扇区内偏移地址
    uint32_t sectorFre = 0;         // 扇区内空余空间
    uint32_t offset = 0;            // Address在FLASH中的偏移
    uint32_t  worldNumber =(( NumToWrite %4 ==0)? (NumToWrite /4) :(NumToWrite /4 + 1));

    uint32_t nwrite = worldNumber;
    en_result_t res;

    //参数判断
    if ((((writeAddr) | 0xFFFFFFFCUL) != 0xFFFFFFFCUL) || writeAddr > (HC32FLASH_END - 4) || NumToWrite == 0 || pBuffer == NULL)
        return 0;

    /* 计算偏移地址 */
    offset = writeAddr - HC32FLASH_BASE;

    /* 计算当前扇区位置 */
    sectorPos = offset / HC32FLASH_SECTOR_SIZE;

    /* 计算要写数据的起始地址在当前页内的偏移地址 */
    sectorOff = ((offset % HC32FLASH_SECTOR_SIZE) >> 2);

    /* 计算当前扇区内空余空间 */
    sectorFre = ((HC32FLASH_SECTOR_SIZE >> 2) - sectorOff);

    /* 要写入的数据量低于当前扇区空余量 */
    if (nwrite <= sectorFre)
        sectorFre = nwrite;

    /* 解锁 */
    UnLock_Flash();

    while(nwrite != 0)
    {
         /* 检查是否超扇区了 */
        if (sectorPos >= HC32FLASH_SECTOR_NUM)
            break;

        /* 读取整页 */
        BSP_STMFLASH_ReadBytes(HC32FLASH_BASE + sectorPos * HC32FLASH_SECTOR_SIZE, STMFLASHBuf,HC32FLASH_SECTOR_SIZE);

        /* 检查是否需要擦除 */
        for (i = 0; i < sectorFre; i++)
        {
            if (*(STMFLASHBuf + sectorOff + i) != 0xFFFFFFFF) /* FLASH擦出后默认内容全为0xFF */
                break;
        }


        //设置flash单字节操作模式
        EFM_SectorCmd_Single( sectorPos, Enable);
        //擦除
        if (i < sectorFre)
        {
            uint32_t index = 0;

            /* 擦除当前扇区 */
            res = EFM_SectorErase(HC32FLASH_BASE + sectorPos * HC32FLASH_SECTOR_SIZE);
            if (Ok != res)
            {
                break;
            }

            /* 复制到缓存 */
            for (index = 0; index < sectorFre; index++)
            {
                *(STMFLASHBuf + sectorOff + index) = *(pBuffer + index);
            }

            /* 写回FLASH */
            res = FlashWriteBuff(HC32FLASH_BASE + sectorPos * HC32FLASH_SECTOR_SIZE, HC32FLASH_SECTOR_SIZE, STMFLASHBuf);
            if (Ok != res)
            {
                break;
            }

        }
        else
        {
            /* 直接写不需要擦除 */
            res = FlashWriteBuff(writeAddr, sectorFre, pBuffer);

        }
        EFM_SectorCmd_Single( sectorPos, Disable);

        pBuffer += sectorFre;         /* 读取地址递增         */
        writeAddr += (sectorFre << 2); /* 写入地址递增         */
        nwrite -= sectorFre;         /* 更新剩余未写入数据量 */

        sectorPos++;                 /* 下一扇区     */
        sectorOff = 0;               /* 页内偏移地址置零         */

        /* 根据剩余量计算下次写入数据量 */
        sectorFre = nwrite >= (HC32FLASH_SECTOR_SIZE >> 2) ? (HC32FLASH_SECTOR_SIZE >> 2) : nwrite;
    }
    /* 加锁所有扇区 */
    Lock_Flash();


    return ((NumToWrite - nwrite) << 2);
}


使用特权

评论回复
8
caigang13| | 2021-8-2 08:13 | 只看该作者
2MB的flash,确实够大了。

使用特权

评论回复
9
chenjun89| | 2021-8-3 08:14 | 只看该作者
还有单独的OTP区域?

使用特权

评论回复
10
martinhu| | 2021-8-3 09:07 | 只看该作者
chenjun89 发表于 2021-8-3 08:14
还有单独的OTP区域?

F460和F4A0有OTP的,但是其他型号没有,
这个Flash的描述也是针对这几个型号,不是所有型号都适用

使用特权

评论回复
11
dunwii| | 2021-10-18 16:00 | 只看该作者
martinhu 发表于 2021-8-3 09:07
F460和F4A0有OTP的,但是其他型号没有,
这个Flash的描述也是针对这几个型号,不是所有型号都适用 ...

请教下大神,F4A0的OTP设置是否可逆呢?使能OTP功能,并且对对应块空间的锁存地址全部写0后,再次写OTP使能寄存器和锁存地址进行编程,还能否恢复到全1的模式,取消OTP配置?
手上只有一块板子,不敢做实验,大神如果知道麻烦指教下哈

使用特权

评论回复
评论
dunwii 2021-10-19 14:38 回复TA
@martinhu :嗯,好的,感谢大神 
martinhu 2021-10-19 13:28 回复TA
@dunwii :4A0不了解 
dunwii 2021-10-19 09:56 回复TA
@martinhu :但是F4A0的有134KB的OTP区域,其中128KB是Flash的0~0x1FFFF共享出来的物理空间 
martinhu 2021-10-18 19:17 回复TA
@dunwii :F460的OTP总共才960 Bytes,没有128K复用的说法。 
dunwii 2021-10-18 17:14 回复TA
@martinhu :幸好没做实验^_^ 大胆猜测下,是不是OTP配置区域的那几K空间是真正的OTP,一旦改了配置从1-->0,就没办法恢复0-->1,从而Flash复用的那128K OTP区域也无法恢复为普通Flash区? 
martinhu 2021-10-18 16:27 回复TA
这个OTP是真的OTP,一旦锁了之后,就不能恢复了…… 
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

94

主题

4126

帖子

2

粉丝