打印
[APM32F0]

APM32F035的片内FLASH操作驱动

[复制链接]
963|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 口天土立口 于 2025-5-22 08:48 编辑

#技术资源# #申请原创#  @21小跑堂
APM32F035片内共有64KB的用户FLASH存储容量,而FLASH为每1KB为一页,4页为一个扇区,即有0~6364页,0~1516个扇区。

1.擦除FLASH
F035的片内FLASH支持页擦除功能,所以本次驱动的擦除以页为单位执行擦除操作,而操作FLASH时,需注意:频繁的中断,可能会导致FLASH的擦除出现异常,所以擦除FLASH期间,需关闭中断,擦除完成后,再打开中断,同时擦除FLASH前,需先执行解锁操作,否则擦除操作不会被执行,擦除完成后再上锁。

/*
* @brief       擦除
*
* @param       addr: 擦除起始地址
*              len:  擦除长度
*
* @retval      擦除长度
*
*/
uint32_t bsp_flash_erase(uint32_t addr, uint32_t len)
{
    uint32_t ret_len = 0U;
    uint32_t cur_len = 0;
    uint32_t erase_addr = addr;
   
    if ((addr < FMC_BASE) || (addr >= (FMC_BASE + FLASH_SIZE_MAX)) || (len == 0)) {
        ;
    } else {   
        /* 解锁 */
        FMC_Unlock();
        __enable_irq();
        
        do {
            if ((erase_addr & (FLASH_PAGE_SIZE - 1)) != 0) {
                cur_len = FLASH_PAGE_SIZE - (erase_addr & (FLASH_PAGE_SIZE - 1));
                erase_addr = erase_addr & (~(FLASH_PAGE_SIZE - 1));
            } else {
                cur_len = FLASH_PAGE_SIZE;
            }
            if (FMC_ErasePage(erase_addr) != FMC_STATE_COMPLETE) {
                FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);          /* 清除异常标志,重试 */
                if (FMC_ErasePage(erase_addr) != FMC_STATE_COMPLETE) {
                    FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);      /* 清除异常标志 */
                    break;
                }
            }
            erase_addr += FLASH_PAGE_SIZE;
            ret_len += cur_len;
        } while ((ret_len < len) && (erase_addr < (FMC_BASE + FLASH_SIZE_MAX)));
        
        __disable_irq();
        /* 上锁 */     
        FMC_Lock();
    }
   
    return ret_len;
}

2.写FLASH
FLASH支持16位的半字写入,所以对于单字节操作,本驱动执行先读取原有数据,再更新16bit数据写入。需注意:频繁的中断,可能会导致FLASH的擦除出现异常,所以写入FLASH期间,需关闭中断,写入完成后,再打开中断,同时写入FLASH前,需先执行解锁操作,否则写入操作不会被执行,写入完成后再上锁。另外FLASH的特性决定,写入对应的FLASH位置前,需确保对应的位置为FF,否则写入异常,因此每次写入前,先执行擦除操作即可。

/*
* @brief       写
*              需先手动调用擦除函数,确保待更新区域的字节全部为FF,才能正确执行写操作
* @param       addr: 写起始地址
*              buf:  写缓存
*              len:  写长度
*
* @retval      写长度
*
*/
uint32_t bsp_flash_write(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint32_t ret_len = 0U;
    uint32_t write_len = 0U;
    uint32_t write_addr = 0U;
    uint16_t data = 0;
    FMC_STATE_T state = FMC_STATE_COMPLETE;
   
    if ((addr < FMC_BASE) || (addr >= (FMC_BASE + FLASH_SIZE_MAX)) || (buf == NULL) || (len == 0)) {
        ;
    } else {   
        /* 解锁 */
        FMC_Unlock();
        __enable_irq();
        
        /* 先处理单字节 */
        if ((addr & 0x01) != 0) {
            write_addr = addr & (0xFFFFFFFEU);
            data = *(uint16_t *)write_addr;
            data &= 0x00FF;
            data += (((uint16_t)buf[0]) << 8);
            if (FMC_ProgramHalfWord(write_addr, data) != FMC_STATE_COMPLETE) {
                FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);      /* 清除异常标志 */
                /* 上锁 */   
                FMC_Lock();
                __disable_irq();
                return ret_len;
            }
            ret_len += 1;
            write_addr += 2;
        } else {
            write_addr = addr;
        }
        
        if (ret_len >= len) {
            /* 上锁 */   
            __disable_irq();
            FMC_Lock();
            return ret_len;
        }
        
        state = FMC_WaitForReady(FMC_DELAY_PROGRAM);
        if (state == FMC_STATE_COMPLETE) {
            /* 开启编程 */
            FMC->CTRL2_B.PG = BIT_SET;  
            do {
                /* 填充数据 */
                data = 0;
                if ((ret_len + 2) <= len) {
                    data = (((uint16_t)buf[ret_len + 1]) << 8) + buf[ret_len];
                    write_len = 2;
                } else {
                    data = buf[ret_len];
                    write_len = 1;
                }
                /* 写数据 */
                *(__IO uint16_t *)write_addr = data;
                /* 等待编程结束 */
                state = FMC_WaitForReady(FMC_DELAY_PROGRAM);
                if (state != FMC_STATE_COMPLETE) {
                    FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);      /* 清除异常标志 */
                    break;
                }
                write_addr += write_len;
                ret_len += write_len;
            } while ((ret_len < len) && (write_addr < (FMC_BASE + FLASH_SIZE_MAX)));
            /* 结束编程 */
            FMC->CTRL2_B.PG = BIT_RESET;
        }
        
        __disable_irq();
        /* 上锁 */   
        FMC_Lock();
    }
   
    return ret_len;

}

3.读FLASH
读取FLASH为最简单的操作,直接拷贝对应地址的数据到缓存即可。


/*
* @brief       读
*
* @param       addr: 读起始地址
*              buf:  读缓存
*              len:  读长度
*
* @retval      读长度
*
*/
uint32_t bsp_flash_read(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint32_t ret_len = 0U;

    if ((addr < FMC_BASE) || (addr >= (FMC_BASE + FLASH_SIZE_MAX)) || (buf == NULL) || (len == 0)) {
        ;
    } else {
        /* 计算可读取的长度 */
        if ((addr + len) > (FMC_BASE + FLASH_SIZE_MAX)) {
            ret_len = (FMC_BASE + FLASH_SIZE_MAX) - addr;
        } else {
            ret_len = len;
        }
        memcpy(buf, (uint32_t *)addr, ret_len);
    }

    return ret_len;
}

4.直写FLASH
直接操作,无需手动先擦除FLASH再执行写操作,而是由程序内部自动判断执行,但也有一些弊端:需消耗1个页的SRAM(F035为1KB),同时多次写小于1个页的FLASH数据,效率比较低。

/*
* @brief       直写
*              无需先手动擦除,可直接写FLASH,函数内部自动计算并执行擦除操作,
*              但函数内部每次都执行擦除,所以执行效率比较慢
* @param       addr: 直写起始地址
*              buf:  直写缓存
*              len:  直写长度
*
* @retval      直写长度
*
*/
static uint8_t flash_write_buf[FLASH_PAGE_SIZE];
uint32_t bsp_flash_write_direct(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint32_t ret_len = 0U;
    uint32_t cur_len = 0U;
    uint32_t single_len = 0U;
    uint32_t write_addr = addr;
    uint32_t erase_addr = 0U;
    uint16_t data = 0;
    FMC_STATE_T state = FMC_STATE_COMPLETE;

    if ((addr < FMC_BASE) || (addr >= (FMC_BASE + FLASH_SIZE_MAX)) || (buf == NULL) || (len == 0)) {
        ;
    } else {   
        /* 解锁 */
        FMC_Unlock();
        __enable_irq();

        do {
            // 起始地址非扇区对齐或写长度不足一个扇区
            if (((write_addr % FLASH_PAGE_SIZE) != 0U) || ((ret_len + FLASH_PAGE_SIZE) > len)) {
                // 获取FLASH现有数据
                erase_addr = write_addr & (~(FLASH_PAGE_SIZE - 1));
                bsp_flash_read(erase_addr, flash_write_buf, FLASH_PAGE_SIZE);
                // 计算需更新的数据长度
                cur_len = FLASH_PAGE_SIZE - (write_addr % FLASH_PAGE_SIZE);
                cur_len = ((ret_len + cur_len) > len) ? (len - ret_len) : cur_len;
                // 更改缓存数据
                memcpy(&flash_write_buf[write_addr % FLASH_PAGE_SIZE], &buf[ret_len], cur_len);
            } else {
                cur_len = FLASH_PAGE_SIZE;
                erase_addr = write_addr;
                // 更改缓存数据
                memcpy(flash_write_buf, &buf[ret_len], cur_len);
            }

            // 擦除扇区
            if (FMC_ErasePage(erase_addr) != FMC_STATE_COMPLETE) {
                FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);          /* 清除异常标志,重试 */
                if (FMC_ErasePage(erase_addr) != FMC_STATE_COMPLETE) {
                    FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);      /* 清除异常标志 */
                    break;
                }
            }

            state = FMC_WaitForReady(FMC_DELAY_PROGRAM);
            if (state == FMC_STATE_COMPLETE) {
                single_len = 0;
                /* 开启编程 */
                FMC->CTRL2_B.PG = BIT_SET;
                /* 需回写已被擦除的完整扇区 */
                do {
                    /* 填充数据 */
                    data = (((uint16_t)flash_write_buf[single_len + 1]) << 8) + flash_write_buf[single_len];
                    /* 写数据 */
                    *(__IO uint16_t *)erase_addr = data;
                    /* 等待编程结束 */
                    state = FMC_WaitForReady(FMC_DELAY_PROGRAM);
                    if (state != FMC_STATE_COMPLETE) {
                        FMC_ClearStatusFlag(FMC_FLAG_BUSY | FMC_FLAG_PE | FMC_FLAG_WPE | FMC_FLAG_OC);      /* 清除异常标志 */
                        break;
                    }
                    erase_addr += 2;
                    single_len += 2;
                    /* 真正更新数据到FLASH */
                    if (erase_addr > write_addr) {
                        /* 可能当前2个字节的高位字节才是需更新的数据起点
                         * 或 后续回写的字节不是需更新的数据
                         */
                        if ((ret_len + (erase_addr - write_addr)) >= len) {
                            ret_len = len;
                        } else {
                            ret_len += (erase_addr - write_addr);  
                        }                           
                        write_addr = erase_addr;
                    }
                } while (single_len < FLASH_PAGE_SIZE);
                /* 结束编程 */
                FMC->CTRL2_B.PG = BIT_RESET;

                if (state != FMC_STATE_COMPLETE) {
                    break;
                }
            }
        } while ((ret_len < len) && (write_addr < (FMC_BASE + FLASH_SIZE_MAX)));

        __disable_irq();
        /* 上锁 */   
        FMC_Lock();
    }

    return ret_len;
}




  
  

Read_Write_Erase.zip

234.36 KB

APM32F035片内FLASH驱动

使用特权

评论回复
沙发
OceanGaze| | 2025-5-7 11:08 | 只看该作者
Flash操作还是蛮重要的。
如果操作不当,直接变砖了

使用特权

评论回复
板凳
记忆花园| | 2025-5-12 18:19 | 只看该作者
这款小MCU上面对Flash定义了sector的概念,有对sector的操作吗?

使用特权

评论回复
地板
口天土立口|  楼主 | 2025-5-13 14:12 | 只看该作者
记忆花园 发表于 2025-5-12 18:19
这款小MCU上面对Flash定义了sector的概念,有对sector的操作吗?

支持页擦除,所以以页为单位操作,更小的擦除容量,可以有更精细的容量分配方式,不浪费每块FLASH容量

使用特权

评论回复
5
发光的梦| | 2025-5-13 18:19 | 只看该作者
按照官方提供的例程来实现,是不是就挺安全的呀

使用特权

评论回复
6
口天土立口|  楼主 | 2025-5-14 08:57 | 只看该作者
发光的梦 发表于 2025-5-13 18:19
按照官方提供的例程来实现,是不是就挺安全的呀

只有经常长时间现场使用的代码才算得上安全,理想环境下写出来的代码,只能算是提供一个正确的大方向,小细节需要长时间的验证才能完善

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

3

主题

12

帖子

0

粉丝