口天土立口 发表于 2025-9-6 13:16

APM32E030的片内FLASH驱动

本帖最后由 口天土立口 于 2025-9-8 19:29 编辑



APM32E030有64KB的主存储区,支持页/片擦除;


每页大小为1KB,有64页;

本次代码基于开发板:APM32E030R Micro-EVB


FLASH的驱动代码如下:
/* FLASH大小: 64KB */
#define FLASH_SIZE_MAX          ((uint32_t)0x10000)
/* 页大小: 1KB */
#define FLASH_PAGE_SIZE         ((uint32_t)0x400)/*
* @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 {   
      __disable_irq();
      /* 解锁 */
      FMC_Unlock();
      
      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)));
      
      /* 上锁 */   
      FMC_Lock();
      __enable_irq();
    }
   
    return ret_len;
}
/*
* @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 {   
      __disable_irq();
      /* 解锁 */
      FMC_Unlock();
      
      /* 先处理单字节 */
      if ((addr & 0x01) != 0) {
            write_addr = addr & (0xFFFFFFFEU);
            data = *(uint16_t *)write_addr;
            data &= 0x00FF;
            data += (((uint16_t)buf) << 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();
                __enable_irq();
                return ret_len;
            }
            ret_len += 1;
            write_addr += 2;
      } else {
            write_addr = addr;
      }
      
      if (ret_len >= len) {
            /* 上锁 */   
            FMC_Lock();
            __enable_irq();
            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) << 8) + buf;
                  write_len = 2;
                } else {
                  data = buf;
                  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;
      }
      
      /* 上锁 */   
      FMC_Lock();
      __enable_irq();
    }
   
    return ret_len;
}/*
* @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;
}/*
* @brief       直写
*            无需先手动擦除,可直接写FLASH,函数内部自动计算并执行擦除操作,
*            但函数内部每次都执行擦除,所以执行效率比较慢
* @param       addr: 直写起始地址
*            buf:直写缓存
*            len:直写长度
*
* @retval      直写长度
*
*/
static uint8_t flash_write_buf;
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 {   
      __disable_irq();
      /* 解锁 */
      FMC_Unlock();
      
      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, &buf, cur_len);
            } else {
                cur_len = FLASH_PAGE_SIZE;
                erase_addr = write_addr;
                // 更改缓存数据
                memcpy(flash_write_buf, &buf, 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) << 8) + flash_write_buf;
                  /* 写数据 */
                  *(__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)));
      
      /* 上锁 */   
      FMC_Lock();
      __enable_irq();
    }
   
    return ret_len;
}
注意:频繁的中断,可能会导致FLASH的擦写产生异常,进而死机,所以需加入禁止中断操作!

驱动测试代码如下:
// FLASH测试
int8_t flag_flash_test_e_w_r, flag_flash_test_w_direct;
uint32_t test_addr = 0;
static void flash_test(void)
{
#define TEST_SIZE_SINGLE   (11)
#define TEST_FLASH_ST_ADDR(FMC_BASE + 0x400 * 3 + 5)      /* 程序自身需 2KB+ FLASH空间 */
    uint32_t flash_max_addr = FMC_BASE + 0x10000;
    uint32_t cur_test_len = 0;
    uint8_t test_buf_w, test_buf_r;
    uint8_t test_magic_num = 0;
    uint8_t i = 0;
    static uint8_t flag_flash_test = 0;
   
    if (flag_flash_test != 0) {
      return;
    }
   
    /* FLASH 擦除/写/读测试 */
    flag_flash_test_e_w_r = 0;
    test_addr = TEST_FLASH_ST_ADDR;
    do {
      /* 计算测试的数据长度 */
      if ((test_addr + TEST_SIZE_SINGLE) <= flash_max_addr) {
            cur_test_len = TEST_SIZE_SINGLE;
      } else {
            cur_test_len = flash_max_addr - test_addr;
      }
      /* 填充数据 */
      for (i = 0; i < TEST_SIZE_SINGLE; i++) {
            test_buf_w = test_magic_num++;
      }
      /* 擦除 */
      if (bsp_flash_erase(test_addr, cur_test_len) < cur_test_len) {
            flag_flash_test_e_w_r = -1;
            break;
      }
      /* 写 */
      if (bsp_flash_write(test_addr, test_buf_w, cur_test_len) != cur_test_len) {
            flag_flash_test_e_w_r = -1;
            break;
      }
      /* 读 */
      memset(test_buf_r, 0, sizeof(test_buf_r));
      if (bsp_flash_read(test_addr, test_buf_r, cur_test_len) != cur_test_len) {
            flag_flash_test_e_w_r = -1;
            break;
      }
      /* 比较 */
      for (i = 0; i < cur_test_len; i++) {
            if (test_buf_w != test_buf_r) {
                flag_flash_test_e_w_r = -1;
                break;
            }
      }
      test_addr += cur_test_len;
    } while (test_addr < flash_max_addr);
   
    if (flag_flash_test_e_w_r == 0) {
      flag_flash_test_e_w_r = 2;
    }
   
    /* FLASH 直写测试 */
    flag_flash_test_w_direct = 0;
    test_addr = TEST_FLASH_ST_ADDR;
    do {
      /* 计算测试的数据长度 */
      if ((test_addr + TEST_SIZE_SINGLE) <= flash_max_addr) {
            cur_test_len = TEST_SIZE_SINGLE;
      } else {
            cur_test_len = flash_max_addr - test_addr;
      }
      /* 填充数据 */
      for (i = 0; i < TEST_SIZE_SINGLE; i++) {
            test_buf_w = test_magic_num++;
      }
      /* 直写 */
      if (bsp_flash_write_direct(test_addr, test_buf_w, cur_test_len) != cur_test_len) {
            flag_flash_test_w_direct = -1;
            break;
      }
      /* 读 */
      memset(test_buf_r, 0, sizeof(test_buf_r));
      if (bsp_flash_read(test_addr, test_buf_r, cur_test_len) != cur_test_len) {
            flag_flash_test_w_direct = -1;
            break;
      }
      /* 比较 */
      for (i = 0; i < cur_test_len; i++) {
            if (test_buf_w != test_buf_r) {
                flag_flash_test_w_direct = -1;
                break;
            }
      }
      test_addr += cur_test_len;
    } while (test_addr < flash_max_addr);
   
    if (flag_flash_test_w_direct == 0) {
      flag_flash_test_w_direct = 2;
    }
   
    flag_flash_test = 1;
}// 应用初始化
void app_init(void)
{
    flash_test();
}

// 应用任务
void app_task(void)
{
}

详细代码,请查看附件:




黎明热忱 发表于 2025-9-7 23:26

我也在犹豫是不是把flash的操作放在bsp里面更合适。
看了楼主的帖子,我觉得就是应该把flash的操作放到bps里面

银河漫步 发表于 2025-9-8 10:12

楼主的代码是不是写错了。怎么先unlock_flash()之后要打开irq,再操作flash的写入操作?
是不是在unlock_flash()之后,要关闭中断啊

口天土立口 发表于 2025-9-8 19:23

银河漫步 发表于 2025-9-8 10:12
楼主的代码是不是写错了。怎么先unlock_flash()之后要打开irq,再操作flash的写入操作?
是不是在unlock_fl ...

感谢,确实写反了

银河漫步 发表于 2025-9-23 21:58

嘿嘿
页: [1]
查看完整版本: APM32E030的片内FLASH驱动