BF7006内部存储体实战:解锁、擦除与编程全流程解析
1. BF7006内部存储体架构解析
第一次接触BF7006这颗芯片时,最让我头疼的就是它的存储结构。折腾了两天才搞明白,原来它内部藏着两套完全不同的存储系统:96KB主Flash+4KB只读Flash,以及2KB主EEPROM+256B只读EEPROM。这就像你家既有大容量冰箱(Flash),又有个小零食柜(EEPROM),各自还带了个上锁的保险箱(NVR区域)。
具体来看地址映射:
// Flash区域定义 #define IFLASH_ADDR_BASE 0x00000000 // 主Flash起始地址 #define IFLASH_SIZE 0x00018000 // 96KB容量 #define IFLASH_NVR_ADDR_BASE 0x00018000 // 4KB只读区 #define IFLASH_PAGE_SIZE 0x00000800 // 2KB页大小 // EEPROM区域定义 #define IEEPROM_ADDR_BASE 0x40000000 // 注意这个特殊的地址空间 #define IEEPROM_SIZE 0x00000800 // 2KB容量 #define IEEPROM_NVR_ADDR_BASE 0x40000800 // 256B只读区 #define IEEPROM_PAGE_SIZE 0x00000040 // 64B页大小实际项目中我遇到过这样的坑:有次想批量写入EEPROM,直接套用Flash的2KB页大小操作,结果数据全乱套了。后来用逻辑分析仪抓时序才发现,EEPROM的物理页只有64字节,超过这个尺寸就会自动回卷覆盖。这就好比用大卡车(Flash操作)去送快递(EEPROM数据),不翻车才怪。
2. 解锁与上锁机制实战
芯片的存储保护机制就像保险箱的密码锁,但官方文档里这个"锁"的描述实在让人困惑。经过实际测试验证,我发现正确的解锁/上锁姿势应该是这样的:
void storageUnlock(void) { // 第一步:输入密钥 EFLASH_UNLOCK = 0x5A5AA5A5; // 这个魔法数字是芯片规定的 // 第二步:设置保护范围 FLASH_LOCK_SIZE = 0x00; // 0表示全片解锁 EEPROM_LOCK_SIZE = 0x00; } void storageLock(uint8_t flashProtectKB, uint8_t eepromProtectB) { EFLASH_UNLOCK = 0x5A5AA5A5; // 先解锁才能上锁 // Flash每2KB对应一个保护单元 FLASH_LOCK_SIZE = (flashProtectKB / 2) & 0x3F; // EEPROM每64B对应一个保护单元 EEPROM_LOCK_SIZE = (eepromProtectB / 64) & 0x07; }有个特别容易踩的坑:上锁寄存器必须在解锁状态下才能修改!我有次在锁定时直接写FLASH_LOCK_SIZE,结果配置根本没生效。后来看寄存器手册才发现这个隐藏规则——就像你要改保险箱密码,得先输入旧密码才行。
3. 等待IDLE状态的正确姿势
存储操作最关键的就是等待就绪状态,但BF7006的设计有点反直觉:
uint8_t waitStorageReady(uint32_t timeout_ms) { uint32_t timeout = timeout_ms * 1000; // 转换为us // Flash状态检测 while(!(FLASH_STATE & 0x01) && timeout--) { delayus(1); } // EEPROM状态检测 while(!(EEPROM_STATE & 0x01) && timeout--) { delayus(1); } return timeout ? STORAGE_OK : STORAGE_TIMEOUT; }实测发现三个注意点:
- 状态位复位值是0(忙状态),这和多数芯片相反
- EEPROM操作耗时通常是Flash的3-5倍
- 超时时间建议Flash设100ms,EEPROM设500ms
有次我偷懒没检查状态直接操作,结果导致后续数据校验全失败。后来用示波器抓信号才发现,芯片其实还在忙着处理前一个命令。
4. 页擦除的魔鬼细节
擦除操作就像把黑板擦干净,但BF7006这块"黑板"有点特别:
4.1 Flash擦除实战
void flashErase(uint32_t addr, uint16_t pages) { storageUnlock(); while(pages--) { uint32_t *pAddr = (uint32_t*)addr; // 关键的三步配置 EFLASH_SEL = 0xAA55; // 选择Flash EFLASH_MODE = 0xA5; // 擦除模式 EFLASH_EBCFG = 0x55; // 页擦除 *pAddr = 0; // 触发擦除 addr += IFLASH_PAGE_SIZE; if(waitStorageReady(100) != STORAGE_OK) { break; } } storageLock(0, 0); }4.2 EEPROM擦除陷阱
void eepromErase(uint32_t addr, uint16_t pages) { storageUnlock(); while(pages--) { uint32_t *pAddr = (uint32_t*)addr; // 注意这三个魔法数字完全不同 EFLASH_SEL = 0xCD78; EFLASH_MODE = 0x3C; EFLASH_EBCFG = 0x55; *pAddr = 0; addr += IEEPROM_PAGE_SIZE; if(waitStorageReady(500) != STORAGE_OK) { break; } } storageLock(0, 0); }踩过的坑:曾经以为向任意地址写0都能触发擦除,后来发现必须写目标页的首地址。这就像用钥匙开锁,必须对准锁孔才能转动。
5. 编程(写入)操作精要
BF7006的写入操作有个硬性规定:必须按4字节对齐写入。这就好比快递只接受整箱发货,不接受散件。
5.1 Flash写入技巧
uint8_t flashWrite(uint32_t addr, uint8_t *data, uint16_t len) { if(len % 4 != 0) { // 长度检查 return STORAGE_ERR; } storageUnlock(); uint32_t *pData = (uint32_t*)data; len /= 4; while(len--) { EFLASH_SEL = 0xAA55; EFLASH_MODE = 0xA5; EFLASH_EBCFG = 0x33; // 编程模式 *(uint32_t*)addr = *pData++; addr += 4; if(waitStorageReady(10) != STORAGE_OK) { storageLock(0, 0); return STORAGE_TIMEOUT; } } storageLock(0, 0); return STORAGE_OK; }5.2 EEPROM写入优化
EEPROM写入有个隐藏特性:连续写入同页数据时,中间不需要重复配置寄存器:
uint8_t eepromWrite(uint32_t addr, uint8_t *data, uint16_t len) { // 先检查是否跨页 uint32_t startPage = addr / IEEPROM_PAGE_SIZE; uint32_t endPage = (addr + len - 1) / IEEPROM_PAGE_SIZE; if(startPage != endPage) { return STORAGE_ERR; // 简化示例,实际应该分多次写入 } storageUnlock(); // 只需配置一次寄存器 EFLASH_SEL = 0xCD78; EFLASH_MODE = 0x3C; EFLASH_EBCFG = 0x33; while(len >= 4) { *(uint32_t*)addr = *(uint32_t*)data; addr += 4; data += 4; len -= 4; if(waitStorageReady(50) != STORAGE_OK) { break; } } storageLock(0, 0); return len ? STORAGE_ERR : STORAGE_OK; }6. 数据验证与调试技巧
验证存储操作是否成功,我总结了三板斧:
- 直接读取验证:最简单粗暴的方式
void printFlashData(uint32_t addr, uint16_t len) { uint8_t *p = (uint8_t*)addr; while(len--) { printf("%02X ", *p++); } }- CRC校验:适合大块数据
uint32_t calcCrc32(uint32_t addr, uint16_t len) { // 实现CRC32计算(略) } int verifyData(uint32_t addr, uint8_t *data, uint16_t len) { return calcCrc32(addr, len) == calcCrc32((uint32_t)data, len); }- 逻辑分析仪抓时序:终极调试手段
有次遇到数据偶尔写入失败的情况,用示波器发现是电源不稳导致的。后来在存储操作前后加了10ms延时,并添加了重试机制:
#define MAX_RETRY 3 uint8_t safeEepromWrite(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t retry = MAX_RETRY; while(retry--) { if(eepromWrite(addr, data, len) == STORAGE_OK) { if(verifyData(addr, data, len)) { return STORAGE_OK; } } delayms(10); } return STORAGE_ERR; }