芯片架构演进:SD7001固件升级从SPI直控到I2C桥接
写在前面
在上一篇文章中,我们详细介绍了BST7001电量计芯片的固件升级方案,那是一种直接访问Flash寄存器的模式——MCU通过I2C直接读写芯片内部的Flash寄存器,就像直接打开芯片的”后门”一样简单。
但技术不会止步不前。新一代SD7001芯片采用了完全不同的架构:内置SPI Flash控制器,因为其采用更大的256KB Flash,固件升级需要通过SPI控制器这个”中介”来完成。
本文将深入解析SD7001的固件升级方案,带你理解芯片架构演进的逻辑,以及如何在新的约束下实现可靠的固件烧录。
一、架构变革:从”直接访问”到”间接访问”
1.1 BST7001 vs SD7001:架构对比
先上图,直观感受两种架构的差异:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| ┌─────────────────────────────────────────────────────────────────────────────┐ │ BST7001 架构 (旧方案) │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ MCU(I2C 0x18) ──────────────────────────────────────────▶ BST7001 │ │ │ │ │ │ I2C/SMBus │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ Flash寄存器 │ ◀── 直接读写 │ │ │ 0x7000 - 0x700A │ 固件就暴露在总线上了 │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ │ SD7001 架构 (新方案) │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ MCU(I2C 0x30) ──────────────────────────────────────────▶ SD7001 │ │ │ │ │ │ I2C/SMBus (带PEC校验) │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ SPI控制器 │ ◀── 需要通过"中介" │ │ │ 0x400070xx │ 发命令操作 │ │ └─────────┬──────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ 内部SPI Flash │ ◀── 真正的数据存储 │ │ │ (64KB) │ 隐藏起来了 │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
|
1.2 为什么要”多此一举”?
这个问题值得深入思考。增加SPI控制器层看似增加了复杂度,但实际上:
- 安全性提升:Flash不再直接暴露在I2C总线上,需要通过控制器验证和转换
- 标准化接口:SPI Flash是通用存储单元,方案成熟、易于替换
- 读写优化:控制器可以做缓存、ECC校验等高级功能
- 功耗管理:可以独立控制Flash电源域
二、SD7001升级方案:关键差异点
2.1 核心差异一览
| 特性 |
BST7001 (旧) |
SD7001 (新) |
| I2C地址 |
0x18 |
0x30 |
| Flash访问方式 |
直接寄存器 |
SPI控制器 |
| 页大小 |
128字节 |
256字节 |
| 擦除单位 |
Page |
Sector (4KB) |
| 数据传输 |
128B直写 |
128B→256B聚合 |
| PEC校验 |
可选 |
必须 |
2.2 最大的挑战:256 vs 128
这是SD7001升级方案中最棘手的问题:
1 2 3 4
| 上位机发送: 128字节/包 ◀── USB传输效率最优 SPI Flash要求: 256字节/页 ◀── Flash物理特性
矛盾:如何用128字节的"水桶"装满256字节的"水池"?
|
解决方案:数据聚合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| uint8_t page_buf[256]; uint16_t page_filled = 0;
void write_chunk(uint8_t *data, uint16_t len) { memcpy(page_buf + page_filled, data, len); page_filled += len; if (page_filled >= 256) { spi_page_program(page_addr, page_buf, 256); page_filled = 0; } }
|
2.3 固件地址映射:非线性的挑战
SD7001的Flash布局与BST7001不同,固件bin文件的偏移地址需要转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| USB固件格式: ┌────────────────┬────────────────┐ │ 0x00000 │ Code Area │ (约48KB) │ 0xC3FF │ │ ├────────────────┼────────────────┤ │ 0xC400 │ Data Area │ (约8KB) │ 0xF3FF │ │ └────────────────┴────────────────┘
SPI Flash物理地址: ┌────────────────┬────────────────┐ │ 0x1000 │ Code Area │ ← 偏移0x0 映射到 0x1000 │ 0xC3FF │ │ ├────────────────┼────────────────┤ │ 0xD400 │ Data Area │ ← 偏移0xC400 映射到 0xD400 │ 0xF3FF │ │ └────────────────┴────────────────┘
地址转换公式: if (offset < 0xC400) { spi_addr = 0x1000 + offset; // 代码区 } else { spi_addr = 0xD400 + (offset - 0xC400); // 数据区 }
|
三、通信协议:I2C + PEC校验
3.1 为什么要PEC?
PEC(Packet Error Checking)是SMBus协议的一种CRC-8校验机制。在SD7001方案中,每个I2C写操作都必须带PEC,否则芯片会拒绝执行:
1 2 3 4 5 6 7 8 9 10
| ┌─────────────────────────────────────────────┐ │ I2C写操作格式 (带PEC) │ ├─────────────────────────────────────────────┤ │ │ │ [DeviceAddr][Reg] [Data_Hi] [Data_Lo] [PEC]│ │ 1 byte 1 byte 2 bytes 1 │ │ │ │ PEC = CRC8(DeviceAddr<<1, Reg, Data...) │ │ │ └─────────────────────────────────────────────┘
|
这增加了通信的可靠性,但也让驱动开发变得更加复杂。
3.2 寄存器映射
SD7001通过I2C寄存器控制SPI控制器:
| 寄存器 |
用途 |
| 0x11 |
解锁密码1 (0x6318) |
| 0x12 |
解锁密码2 (0x6303=enable) |
| 0x20 |
AHB模式控制 |
| 0x21 |
Remap控制 |
| 0x22 |
CRC控制 (0x0111=禁用) |
| 0x29 |
SPI外部访问开关 |
| 0x01 |
地址低16位 |
| 0x02 |
地址高16位 |
| 0x0F |
数据访问命令 |
3.3 32位寄存器访问
SPI控制器是32位的,需要分步操作:
1 2 3 4 5 6 7 8 9 10 11
| void block_4bytes_write(uint32_t addr, uint32_t val) { write_word(0x01, addr & 0xFFFF); write_word(0x02, (addr >> 16) & 0xFFFF); block_write(0x0F, &val, 4); }
|
四、升级流程:五步走
4.1 完整序列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| ┌─────────────────────────────────────────────────────────────┐ │ Step 1: I2C解锁 │ ├─────────────────────────────────────────────────────────────┤ │ 1.1 WriteWord(0x11, 0x6318) ← 密码1 │ │ 1.2 WriteWord(0x12, 0x6303) ← 启用AHB │ │ 1.3 WriteWord(0x20, 0x8001) ×2 ← 进入编程模式 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 2: 禁用系统CRC │ ├─────────────────────────────────────────────────────────────┤ │ 2.1 WriteWord(0x22, 0x0111) ← 禁用CRC,否则会阻止编程 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 3: SPI Remap │ ├─────────────────────────────────────────────────────────────┤ │ 3.1 WriteWord(0x21, 0x1000) ← Remap配置 │ │ 3.2 WriteWord(0x21, 0) ← 执行Remap │ │ 3.3 WriteWord(0x29, 0x02) ← 启用SPI外部访问 │ │ 3.4 Block4BytesWrite(0x40007030, 0xABCD) │ │ 3.5 WriteWord(0x29, 0) ← 禁用SPI外部访问 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 4: SPI Flash初始化 │ ├─────────────────────────────────────────────────────────────┤ │ 4.1 PowerOn: 配置时钟,等待就绪 │ │ 4.2 Enable Buffer: 启用写入缓冲(加速) │ │ 4.3 ModeSwitch: 切换到SPI模式 │ │ 4.4 Wakeup: 唤醒Flash │ │ 4.5 Reset: 复位Flash │ │ 4.6 ProgramUnlock: 解锁编程 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 5: 擦除 → 编程 → 完成 │ ├─────────────────────────────────────────────────────────────┤ │ 5.1 擦除: 按4KB扇区擦除Code Area和Data Area │ │ 5.2 编程: 128B聚合→256B页写入 │ │ 5.3 完成: 恢复寄存器 + Remap跳转 │ └─────────────────────────────────────────────────────────────┘
|
4.2 关键函数实现
SPI等待就绪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| sd7001_upg_error_t spi_wait_ready(uint32_t timeout_ms) { uint32_t start = ticker_read(); do { delay_ms(10); wwdt_feeddog(); uint32_t status; block_4bytes_read(0x40007018, &status); if ((status & 0x01) == 0) { return SD7001_UPG_OK; } if ((ticker_read() - start) > timeout_ms) { return SD7001_UPG_ERR_TIMEOUT; } } while (1); }
|
扇区擦除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| sd7001_upg_error_t spi_sector_erase(uint32_t sector_addr) { spi_wait_ready(100); block_4bytes_write(0x40007020, 0); block_4bytes_write(0x40007004, 0x06000000); block_4bytes_write(0x40007020, 2); spi_wait_ready(50); block_4bytes_write(0x40007008, sector_addr); block_4bytes_write(0x40007020, 0); block_4bytes_write(0x40007004, 0x20080000); block_4bytes_write(0x40007020, 2); return spi_wait_ready(400); }
|
五、软件架构:分层设计
5.1 模块结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| dev_cps8610.c # USB回调入口,命令路由 │ ├── CFG_ENABLE_FG_UPGRADE = 1 → BST7001 (旧) │ └── CFG_ENABLE_FG_UPGRADE = 2 → SD7001 (新) │ ▼ dev_sd7001_upgrade.c # SD7001升级实现 │ ├── I2C层 (sd7001_write_word, sd7001_block_4bytes_read/write) │ ├── SPI操作层 (sd7001_spi_wait_ready, sd7001_spi_erase, sd7001_spi_program) │ ├── 下载序列 (sd7001_download_start/write_chunk/finish) │ └── 公共API (sd7001_upgrade_process_command)
|
5.2 上下文结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| typedef struct { int32_t fd_dev_ops; uint8_t i2c_addr; sd7001_upg_state_t state; sd7001_upg_error_t last_error; sd7001_upg_config_t config; uint32_t bytes_written; uint32_t expected_offset; uint32_t page_base; uint16_t page_filled; uint8_t page_buf[256]; } sd7001_upg_ctx_t;
|
六、地址转换:理解映射逻辑
这是SD7001方案中最容易出错的地方。固件bin文件的偏移地址是连续的,但SPI Flash的物理地址有”空洞”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
static uint32_t convert_offset_to_addr(uint32_t offset) { if (offset < 0xC400) { return 0x1000 + offset; } else { return 0xD400 + (offset - 0xC400); } }
|
七、调试经验:踩坑记录
7.1 PEC计算错误
症状: 芯片不响应,写操作无效
排查: 检查CRC-8计算是否正确, PEC = CRC8(设备地址<<1, 寄存器, 数据…)
7.2 页对齐问题
症状: 写入后数据校验失败
排查: 确保256字节页写入地址是256的倍数,使用缓冲区聚合解决
7.3 地址映射错误
症状: 代码区写到了数据区,或 vice versa
排查: 添加日志打印 offset → addr 转换结果,与预期对比
7.4 看门狗超时
症状: 升级过程中芯片意外复位
排查: 在长时间操作(特别是擦除)中定期调用 wwdt_feeddog()
八、架构演进的启示
回顾BST7001到SD7001的演进,我们可以得到以下启示:
架构选择没有绝对好坏:直接访问简单但不够安全,间接访问复杂但更灵活
兼容性设计:虽然底层变了,但上层USB命令协议保持一致,降低了上位机的修改成本
数据聚合的智慧:面对”消费级”USB和”工业级”Flash的差异,用缓冲区巧妙化解
状态机的重要性:无论架构如何变,清晰的状态管理始终是可靠性的保障
九、总结
SD7001的固件升级方案展示了芯片架构演进的一种典型路径:通过引入SPI控制器层,在保持I2C接口不变的同时,实现了更高的安全性和灵活性。虽然驱动开发增加了复杂度(特别是PEC校验和256字节页对齐),但这些代价换来了更好的系统设计。
理解这些底层差异,对于固件工程师来说是宝贵的经验。当你在实际项目中遇到类似的需求时,希望本文的分析能给你提供思路。
相关阅读: