内部FLASH模拟EPPROM

发布时间:2023年12月23日

本例程基于STM32F103ZET6
FLASH大小为512K。

介绍FLASH

不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了
1024K 字节。我们的精英 STM32 开发板选择的是 STM32F103ZET6 的 FLASH 容量为 512K 字节,属于大容量产品,大容量产品的闪存模块组织如表 43.1.1 所示:
在这里插入图片描述

主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。
平时,我们烧写的代码就存放在主存储器部分,

闪存的写入步骤(写入用户数据数据)

写操作有四步:
解锁——>擦除——>写数据—---->上锁

解锁:将两个特定的解锁序列号(KEY1:0x45670123 KEY2:0xCDEF89AB)依次写入FLASH_KEYR寄存器

擦除:FLASH物理特性(只能写0,不能写1),所以写FLASH之前需要擦除,将要写入的区域变为0xFFFF.擦除操作分为:页擦除和批量擦除

写数据:擦除完成,可以向FLASH写数据,每次只能以16位方式写入。

上锁:写入数据完成,需要设置FLASH_CR[LOCK]位为1,重新上锁,以防数据不小心被修改。

代码分析

写入数据

可以看到当数据写入时,会存在两种情况。
情况1:要写入的地址范围都在一个扇区(页),不跨扇区
情况2:要写入的地址范围不在一个扇区(页),跨扇区

在这里插入图片描述
在这里插入图片描述

编写代码的核心重点:FLASH物理特性(只能写0,不能写1),如果待写入地址的数据不是0xFFFF(16字节写入),那么就要把它擦除为0xFFFF,且擦除的时候要按扇区为单位来擦。
这里擦除数据,按一个一个扇区的擦除。那么我们要先获取要被写入数据的当前扇区的全部数据(不写入的部分也要保存,以免数据被误改),然后其中要写入的范围里的数据进行判断,是否都为0xFFFF,如果不是,那么进行擦除整个扇区,再重新写入整个扇区数据。如果都为0xFFFF,那么直接写入待写入的数据即可。

对应代码如下

stmflash_write_nocheck函数在stmflash_write函数里调用

**
 * @brief       不检查的写入
                这个函数的假设已经把原来的扇区擦除过再写入
 * @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 半字(16)* @retval      无
 */
void stmflash_write_nocheck(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint16_t i;
    for (i = 0; i < length; i++)
    {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, waddr, pbuf[i]);
        waddr += 2; /* 指向下一个半字 */
    }
}
/**
 * @brief       在FLASH 指定位置, 写入指定长度的数据(自动擦除)
 *   @note      该函数往 STM32 内部 FLASH 指定位置写入指定长度的数据
 *              该函数会先检测要写入的扇区是否是空(全0XFFFF)的?, 如果
 *              不是, 则先擦除, 如果是, 则直接往扇区里面写入数据.
 *              数据长度不足扇区时,自动被回擦除前的数据
 * @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 半字(16位)数
 * @retval      无
 */
uint16_t g_flashbuf[STM32_SECTOR_SIZE / 2]; /* 最多是2K字节 */
void stmflash_write(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint32_t secpos;        /* 扇区地址 */
    uint16_t secoff;        /* 扇区内偏移地址(16位字计算) */
    uint16_t secremain;     /* 扇区内剩余地址(16位字计算) */
    uint16_t i;
    uint32_t offaddr;       /* 去掉0X08000000后的地址 */
    FLASH_EraseInitTypeDef flash_eraseop;
    uint32_t erase_addr;    /* 擦除错误,这个值为发生错误的扇区地址 */

    if (waddr < STM32_FLASH_BASE || (waddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
    {
        return;     /* 非法地址 */
    }

    HAL_FLASH_Unlock();                         /* FLASH解锁 */

    offaddr = waddr - STM32_FLASH_BASE;         /* 实际偏移地址. */
    secpos = offaddr / STM32_SECTOR_SIZE;       /* 扇区地址  0~255 for STM32F103ZET6 */
    secoff = (offaddr % STM32_SECTOR_SIZE) / 2; /* 在扇区内的偏移(2个字节为基本单位.) */
    secremain = STM32_SECTOR_SIZE / 2 - secoff; /* 扇区剩余空间大小 */
    if (length <= secremain)
    {
        secremain = length; /* 不大于该扇区范围 */
    }

    while (1)
    {
        stmflash_read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 读出整个扇区的内容 */
        for (i = 0; i < secremain; i++)     /* 校验数据 */
        {
            if (g_flashbuf[secoff + i] != 0XFFFF)
            {
                break;      /* 需要擦除 */
            }
        }
        if (i < secremain)  /* 需要擦除 */
        { 
            flash_eraseop.TypeErase = FLASH_TYPEERASE_PAGES;        /* 选择页擦除 */
            flash_eraseop.Banks = FLASH_BANK_1;
            flash_eraseop.NbPages = 1;
            flash_eraseop.PageAddress = secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE;  /* 要擦除的扇区 */
            HAL_FLASHEx_Erase( &flash_eraseop, &erase_addr);

            for (i = 0; i < secremain; i++)                         /* 复制 */
            {
                g_flashbuf[i + secoff] = pbuf[i];
            }
            stmflash_write_nocheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 写入整个扇区 */
        }
        else
        {
            stmflash_write_nocheck(waddr, pbuf, secremain);         /* 写已经擦除了的,直接写入扇区剩余区间. */
        }
        if (length == secremain)
        {
            break; /* 写入结束了 */
        }
        else       /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */
            pbuf += secremain;      /* 指针偏移 */
            waddr += secremain * 2; /* 写地址偏移(16位数据地址,需要*2) */
            length -= secremain;    /* 字节(16位)数递减 */
            if (length > (STM32_SECTOR_SIZE / 2))
            {
                secremain = STM32_SECTOR_SIZE / 2; /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = length; /* 下一个扇区可以写完了 */
            }
        }
    }

    HAL_FLASH_Lock();   /* 上锁 */
}

注意事项

!!! 注意,如果要在这部分存储用户数据的话,不要把用户数据地址和存放代码和数据常数的地址重合
占用flash大小 = Code段+RO-data+RW-data.
占用SRAM大小 = RW-data + ZI-data

在这里插入图片描述
这里计算出占用FLASH大小为40588,16进制为9E8C,实际上可以.map文件,实际生成的占用FLASH大小会小于计算出来大小,这里为40332,16进制为9D8C,这是因为,未使用变量被优化掉了。

在这里插入图片描述

因为起始地址为0x8000 0000,那么用户存储数据的地址只能在0x8000 9D8C之后。

这里就定义为0X0807 0000。
在这里插入图片描述

文章来源:https://blog.csdn.net/m0_57678852/article/details/135168027
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。