feat(vd960DBN): 事件日志区动态分区 — JEDEC ID 选档 (ROADMAP 落地)

- offlog.h: OfflogPart 运行时分区表 + g_offlog_part; AREA_BASE 改 0x090000
  (参数64KB+OTA512KB); 容量宏展开为运行时值(主体零改动)
- offlog.c: offlog_part_detect() 读 JEDEC ID → 事件区容量
  W25Q32=512KB/16256条, Q64=1MB/32640, Q128=2MB/65408, Q256=4MB/130944
- storage.c/h: SPI_Flash_ReadJEDEC_ID() (0x9F, 校验 EF 40)
- 旧固件事件区 0x010000 自动废弃(新地址无 magic → 重建), 预期行为
- 测试: mock JEDEC=0x16 + g_offlog_part, 15 例全过
- 文档: BLE/MQTT/TCP 协议 capacity 8064→动态, 交互示例, devlog
This commit is contained in:
wangfq
2026-08-12 16:46:19 +08:00
parent 6c6ddf8b0f
commit a8120cc852
11 changed files with 158 additions and 31 deletions
@@ -4,18 +4,19 @@
* @author wfq
* @version V1.0
* @date 2026-08-04
* @brief DBN 脱机事件日志 (W25Q32 外部 SPI NOR, 环形)
* @brief DBN 脱机事件日志 (W25Qxx 外部 SPI NOR, 环形)
*
* 目的: 解决"平台重复上线/异常重启但现场无日志"的取证问题。
* 区分「设备真复位」vs「MQTT 断连重连」(两者都会导致平台重收 initialize):
* - 真复位: 会有 OFFLOG_EVT_BOOT 事件, boot_seq 递增
* - 断连重连: boot_seq 不变, 只有 IOT_* 网络事件
*
* 分区 (W25Q32, 4MB):
* | 参数区 0x000000 64KB (现有配置, 不动)
* | 事件区 0x010000 256KB = 扇区 16..79 (头扇区 + 63 数据扇区)
* | OTA 区 0x050000 512KB (预留)
* | 快照区 0x0D0000 ~3.25MB (预留, 后续)
* 分区 (2026-08-12 ROADMAP 新规划):
* | 参数区 0x000000 64KB (固定)
* | OTA 区 0x010000 512KB (固定)
* | 事件区 0x090000 动态: W25Q32=512KB / Q64=1MB / Q128=2MB / Q256=4MB
* | 快照区 事件区后 剩余容量 (预留, 后续)
* 上电读 JEDEC ID (0x9F) 决定事件区容量 (g_offlog_part 运行时分区表)
*
* 环形实现:
* - 扇区 0 (区内) = 头扇区, 存元数据 (boot_seq/写位置/条数), 扇区切换时更新
@@ -37,15 +38,35 @@
/*===========================================================================
* 分区与容量
*===========================================================================*/
#define OFFLOG_AREA_BASE 0x010000UL /* 事件日志区起始 (绝对地址) */
#define OFFLOG_AREA_SIZE 0x040000UL /* 256KB */
/* 固定分区 (ROADMAP 2026-08-12): 参数区 64KB + OTA 512KB, 事件区紧随其后 */
#define OFFLOG_PARAM_SIZE 0x10000UL /* 参数区 64KB (固定) */
#define OFFLOG_OTA_SIZE 0x80000UL /* OTA 镜像暂存 512KB (固定) */
#define OFFLOG_AREA_BASE (OFFLOG_PARAM_SIZE + OFFLOG_OTA_SIZE) /* 0x090000 */
#define OFFLOG_HEAD_SECTOR 0 /* 区内扇区号: 头扇区 */
#define OFFLOG_DATA_SECTOR_CNT 63 /* 数据扇区数 (1..63) */
#define OFFLOG_EVT_PER_SECTOR 128 /* 4096 / 32 */
#define OFFLOG_DATA_BASE (OFFLOG_AREA_BASE + 4096)
#define OFFLOG_DATA_SIZE (OFFLOG_DATA_SECTOR_CNT * 4096)
#define OFFLOG_MAX_RECORDS (OFFLOG_DATA_SECTOR_CNT * OFFLOG_EVT_PER_SECTOR) /* 8064 */
/* 芯片 JEDEC ID (0x9F 命令第 3 字节, EF 40 xx) */
#define OFFLOG_CHIP_W25Q32 0x16
#define OFFLOG_CHIP_W25Q64 0x17
#define OFFLOG_CHIP_W25Q128 0x18
#define OFFLOG_CHIP_W25Q256 0x19
/* 运行时分区表 (offlog_init 由 JEDEC ID 填充; 默认 W25Q32) */
typedef struct {
uint8_t chip; /* OFFLOG_CHIP_xxx */
uint32_t area_size; /* 事件日志区大小 */
uint32_t data_sectors; /* 数据扇区数 (不含头扇区) */
uint32_t max_records; /* 最大记录数 = data_sectors * 128 */
} OfflogPart;
extern OfflogPart g_offlog_part;
/* 兼容宏: 展开为运行时值 (offlog.c 主体引用不变) */
#define OFFLOG_AREA_SIZE (g_offlog_part.area_size)
#define OFFLOG_DATA_SECTOR_CNT (g_offlog_part.data_sectors)
#define OFFLOG_DATA_SIZE ((g_offlog_part.data_sectors) * 4096UL)
#define OFFLOG_MAX_RECORDS (g_offlog_part.max_records)
#define OFFLOG_DATA_BASE (OFFLOG_AREA_BASE + 4096UL)
/* 头扇区记录 (32B, 扇区切换时更新) */
typedef struct {
@@ -54,7 +75,7 @@ typedef struct {
uint32_t wr_sector; /* 上次写扇区号 (区内 1..63) */
uint32_t wr_off; /* 上次写偏移 (相对 OFFLOG_AREA_BASE) */
uint32_t wr_seq; /* 下一条记录全局 seq (掉电恢复锚点) */
uint32_t count; /* 有效记录数 (0..8064) */
uint32_t count; /* 有效记录数 (0..max_records) */
uint32_t rsvd[2];
} OfflogHead; /* 28B, 按 32B 对齐使用 */
@@ -36,5 +36,6 @@ void SPI_Flash_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t size
void SPI_Flash_Erase_Sector(uint32_t Dst_Addr);
void storage_init(void);
uint8_t SPI_Flash_ReadJEDEC_ID(void); /* 0x9F JEDEC device id (W25Q32=0x16...) */
#endif
@@ -28,9 +28,39 @@ static uint32_t _wr_off; /* 下一条记录写入的绝对地址
static uint16_t _wr_sector; /* 当前写扇区号 (区内 1..63) */
static uint32_t _wr_seq; /* 下一条记录的全局 seq */
static uint16_t _boot_seq; /* 当前 boot 序号 */
static uint32_t _count; /* 有效记录数 (0..8064) */
static uint32_t _count; /* 有效记录数 (0..max_records) */
static uint8_t _ready; /* init 完成标志 */
/*===========================================================================
* 运行时分区表 (ROADMAP 2026-08-12: 上电读 JEDEC ID 决定事件区容量)
*===========================================================================*/
OfflogPart g_offlog_part = {
OFFLOG_CHIP_W25Q32, /* 默认 */
0x80000UL, /* 512KB */
127, /* (512KB/4096) - 1 头扇区 */
16256, /* 127 * 128 */
};
/* 读 JEDEC ID → 填分区表。未知芯片按 W25Q32 兜底 (与 storage_init 打印对照) */
static void offlog_part_detect(void)
{
uint8_t dev = SPI_Flash_ReadJEDEC_ID();
uint32_t size = 0x80000UL;
uint8_t chip = OFFLOG_CHIP_W25Q32;
switch (dev) {
case OFFLOG_CHIP_W25Q32: size = 0x80000UL; break; /* 512KB */
case OFFLOG_CHIP_W25Q64: size = 0x100000UL; break; /* 1MB */
case OFFLOG_CHIP_W25Q128: size = 0x200000UL; break; /* 2MB */
case OFFLOG_CHIP_W25Q256: size = 0x400000UL; break; /* 4MB */
default: chip = OFFLOG_CHIP_W25Q32; break; /* 未知→默认 */
}
g_offlog_part.chip = chip;
g_offlog_part.area_size = size;
g_offlog_part.data_sectors = (size / 4096UL) - 1;
g_offlog_part.max_records = g_offlog_part.data_sectors * OFFLOG_EVT_PER_SECTOR;
}
/*===========================================================================
* 基础读写
*===========================================================================*/
@@ -106,6 +136,7 @@ void offlog_init(void)
{
OfflogHead h;
offlog_part_detect(); /* JEDEC ID → 事件区容量 (W25Q32/Q64/Q128/Q256) */
memset(&h, 0xFF, sizeof(h));
offlog_head_read(&h);
@@ -225,6 +225,30 @@ u16 SPI_Flash_ReadID(void)
}
/*********************************************************************
* @fn SPI_Flash_ReadJEDEC_ID
*
* @brief Read JEDEC ID (0x9F). Return 3rd byte (device id).
* W25Q32=0x16 Q64=0x17 Q128=0x18 Q256=0x19; 0 if not Winbond.
*
* @return device id byte
*/
uint8_t SPI_Flash_ReadJEDEC_ID(void)
{
uint8_t id[3] = {0, 0, 0};
GPIO_WriteBit(GPIOA, GPIO_Pin_4, 0);
SPI1_ReadWriteByte(W25X_JedecDeviceID);
id[0] = SPI1_ReadWriteByte(0xFF);
id[1] = SPI1_ReadWriteByte(0xFF);
id[2] = SPI1_ReadWriteByte(0xFF);
GPIO_WriteBit(GPIOA, GPIO_Pin_4, 1);
if (id[0] != 0xEF || id[1] != 0x40) return 0;
return id[2];
}
/*********************************************************************
* @fn SPI_Flash_Erase_Sector
*