Files
vd_960/vd960DBN/tests/test_snapshot.c
T
wangfq 3d4814bffe fix(vd960DBN): init/clear 懒擦修复不断重启 — 34s 全量擦除超 IWDG 4s
现场: 烧录快照固件后 CH32V208 不断重启 + 串口乱码
根因: snap_init 首擦 751 扇区 ~34s (头在擦完才写→复位后仍全新→死循环);
      snap_clear 34s / offlog_clear 2.8s 同类; 均超 IWDG 4s
修复(懒擦): init/clear 只擦写指针起点扇区 ~45ms, 其余由环形写切扇区
      逻辑自动擦; clear 为逻辑清除 (count=0 旧数据不可读)
附带: usart_biz.c 非 0x7F 帧 %s 打印改 hex (Loop 数据当字符串=乱码源)
      snapshot.h 线程模型注释修正 (lup_process_frame 实际在主循环 uart_srv)
测试: test_snapshot 9例(新增 lazy_erase) + offlog/ble_offlog 回归全过
2026-08-12 18:31:56 +08:00

390 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* test_snapshot.c — 传感快照日志 gcc 隔离单测
*
* 编译: gcc -I../BLE/OnlyUpdateApp_Peripheral/APP/include \
* -o test_snapshot test_snapshot.c
* 运行: ./test_snapshot
*
* Mock: W25Q32 NOR 行为 (写=AND, 擦=0xFF), mstick 固定值,
* g_offlog_part (事件区 512KB), offlog_evt (审计记录 mock)
* 注意: 不 include offlog.c — 两个 .c 的 static 变量 (如 _wr_off) 在
* 同一翻译单元会符号冲突 (踩过: snap_clear 审计写乱了快照写指针)
* 覆盖: 全新初始化 / 中断安全(enqueue 不落盘) / flush 落盘 / 打包格式
* 环形回绕 / 掉电恢复 / 扇区切换 / 暂存满丢新 / 清空审计 / 分区表
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "../BLE/OnlyUpdateApp_Peripheral/APP/include/offlog.h"
/* ============ mock W25Q32 (4MB, NOR 语义) ============ */
static uint8_t flash[4 * 1024 * 1024];
void SPI_Flash_Erase_Sector(uint32_t sec)
{
memset(flash + sec * 4096, 0xFF, 4096);
}
void SPI_Flash_Read(uint8_t *buf, uint32_t addr, uint16_t size)
{
memcpy(buf, flash + addr, size);
}
void SPI_Flash_Write_NoCheck(uint8_t *buf, uint32_t addr, uint16_t size)
{
uint16_t i;
for (i = 0; i < size; i++) flash[addr + i] &= buf[i]; /* NOR: 只能 1→0 */
}
uint8_t SPI_Flash_ReadJEDEC_ID(void) { return 0x16; } /* mock W25Q32 */
void SPI_Flash_Write(uint8_t *buf, uint32_t addr, uint16_t size)
{
/* 模拟 storage.c 行为: 目标区域非全 FF 则擦整扇区再写 */
uint16_t i;
int need_erase = 0;
for (i = 0; i < size; i++) {
if (flash[addr + i] != 0xFF) { need_erase = 1; break; }
}
if (need_erase) SPI_Flash_Erase_Sector(addr / 4096);
SPI_Flash_Write_NoCheck(buf, addr, size);
}
/* ============ mock 时间 ============ */
static uint32_t mock_ms = 0;
uint32_t mstick(void) { return mock_ms; }
/* ============ mock offlog 依赖 (不 include offlog.c, 避免 static 冲突) ============ */
OfflogPart g_offlog_part = {
OFFLOG_CHIP_W25Q32, /* W25Q32 */
0x80000UL, /* 事件区 512KB */
127,
16256,
};
static uint8_t g_audit_type;
static uint8_t g_audit_payload[4];
static uint8_t g_audit_len;
void offlog_evt(uint8_t type, const uint8_t *payload, uint8_t len)
{
g_audit_type = type;
g_audit_len = len;
if (payload && len > 0 && len <= 4) memcpy(g_audit_payload, payload, len);
}
/* ch32v20x 库类型 (单测环境无 SDK) */
typedef unsigned char u8;
/* ============ 被测模块 (直接包含实现) ============ */
#include "../BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c"
/* ============ 断言 ============ */
static int failures = 0;
#define CHECK(cond) do { \
if (!(cond)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); failures++; } \
} while (0)
static void reset_flash(void)
{
memset(flash, 0xFF, sizeof(flash));
mock_ms = 0;
}
/* 构造一帧传感报告: 4 线圈, 各线圈填可辨识值 */
static void make_report(LUP_SensorReport *sr, uint32_t base_freq, int32_t base_var)
{
uint8_t i;
memset(sr, 0, sizeof(*sr));
sr->sens_type = 0x0C;
sr->coil_count = 4;
for (i = 0; i < 4; i++) {
sr->coils[i].freq_level = 2; /* 中低 66nF */
sr->coils[i].direction = 1;
sr->coils[i].freq_type = 0;
sr->coils[i].sensitivity = 9;
sr->coils[i].condition = 5;
sr->coils[i].loop_state = 1;
sr->coils[i].car_state = (i & 1); /* 0/1 交替 */
sr->coils[i].misc_type = 0;
sr->coils[i].freq = base_freq + i * 1000;
sr->coils[i].variation = base_var + i * 100;
sr->coils[i].misc.passtime_ms = 0x10000000 + i;
}
}
/* 测试1: 全新初始化 → enqueue 不落盘(中断安全), flush 落盘可读回 */
static void test_fresh_init_and_flush(void)
{
LUP_SensorReport sr;
SnapRec r;
uint32_t i;
reset_flash();
snap_init();
CHECK(snap_enabled() == 1);
CHECK(snap_count() == 0);
make_report(&sr, 0x123456, 1000);
mock_ms = 12345;
/* 中断路径: 只 enqueue, 不 flush → flash 不得有任何快照数据 */
snap_enqueue(&sr);
CHECK(snap_count() == 0); /* 未落盘 */
for (i = 0; i < 64; i++) {
CHECK(flash[SNAP_DATA_BASE + i] == 0xFF); /* 数据区仍全 FF */
}
/* 主循环: flush → 落盘 1 条 */
snap_flush();
CHECK(snap_count() == 1);
CHECK(snap_read_idx(0, &r) == 0);
CHECK(r.magic == SNAP_MAGIC);
CHECK(r.len == 48); /* 4 线圈 × 12B */
CHECK(r.seq == 1);
CHECK(r.boot_seq == 1);
CHECK(r.ts_ms == 12345);
CHECK(snap_seq_last() == 1);
printf("PASS test_fresh_init_and_flush\n");
}
/* 测试2: 打包格式与 0xC0 线上线圈单元 12B 一致 */
static void test_pack_format(void)
{
LUP_SensorReport sr;
SnapRec r;
reset_flash();
snap_init();
make_report(&sr, 0x123456, -12345); /* 负变化量 */
/* 覆盖值: freq_level=2 direction=1 freq_type=0 sens=9 → o[0]=0xA9
condition=5 loop=1 car=0 misc=3 → o[1]=0x5B
freq=0x123456 → 56 34 12
variation=-12345 → 0xFFFFCFC7 截 24bit → C7 CF FF
misc=0x10000000 → 00 00 00 10 */
sr.coils[0].freq = 0x123456;
sr.coils[0].variation = -12345;
sr.coils[0].misc_type = 3;
sr.coils[0].misc.relay_count = 0x10000000;
snap_enqueue(&sr);
snap_flush();
snap_read_idx(0, &r);
CHECK(r.coils[0] == 0xA9);
CHECK(r.coils[1] == 0x5B);
CHECK(r.coils[2] == 0x56 && r.coils[3] == 0x34 && r.coils[4] == 0x12);
CHECK(r.coils[5] == 0xC7 && r.coils[6] == 0xCF && r.coils[7] == 0xFF);
CHECK(r.coils[8] == 0x00 && r.coils[9] == 0x00 && r.coils[10] == 0x00 && r.coils[11] == 0x10);
/* 线圈 2: car_state=1 → o[1] bit2=1 → 0x5F (其他字段相同) */
CHECK((r.coils[12 + 1] & 0x04) == 0x04);
CHECK((r.coils[0 + 1] & 0x04) == 0x00);
printf("PASS test_pack_format\n");
}
/* 测试3: 环形回绕 — 写满 max+5 条, count 封顶, 序号单调 */
static void test_ring_wrap(void)
{
LUP_SensorReport sr;
SnapRec r;
uint32_t i, n = SNAP_MAX_RECORDS + 5;
reset_flash();
snap_init();
make_report(&sr, 0x100, 100);
for (i = 0; i < n; i++) {
snap_enqueue(&sr);
snap_flush();
}
CHECK(snap_count() == SNAP_MAX_RECORDS - SNAP_REC_PER_SECTOR + 5);
CHECK(snap_read_idx(snap_count() - 1, &r) == 0);
CHECK(r.seq == n);
CHECK(snap_seq_last() == n);
CHECK(snap_read_idx(snap_count(), &r) == -1); /* 越界 */
printf("PASS test_ring_wrap (count %lu, max %lu)\n",
(unsigned long)snap_count(), (unsigned long)SNAP_MAX_RECORDS);
}
/* 测试4: 掉电恢复 — 写 10 条后重新 init, boot_seq 递增, seq 连续 */
static void test_power_loss_recovery(void)
{
LUP_SensorReport sr;
SnapRec r;
uint32_t i;
reset_flash();
snap_init();
make_report(&sr, 0x200, 200);
for (i = 0; i < 10; i++) {
snap_enqueue(&sr);
snap_flush();
}
CHECK(snap_count() == 10);
snap_init(); /* 模拟掉电重启 */
CHECK(snap_boot_seq() == 2);
CHECK(snap_count() == 10);
snap_enqueue(&sr);
snap_flush();
CHECK(snap_count() == 11);
CHECK(snap_read_idx(10, &r) == 0);
CHECK(r.seq == 11);
CHECK(r.boot_seq == 2);
printf("PASS test_power_loss_recovery\n");
}
/* 测试5: 扇区切换 — 写满 64 条后头扇区更新, 数据完整 */
static void test_sector_switch(void)
{
LUP_SensorReport sr;
SnapRec r;
SnapHead h;
uint32_t i;
reset_flash();
snap_init();
make_report(&sr, 0x300, 300);
for (i = 0; i < 64; i++) {
snap_enqueue(&sr);
snap_flush();
}
snap_enqueue(&sr);
snap_flush(); /* 第 65 条触发扇区切换 */
CHECK(snap_count() == 65);
SPI_Flash_Read((uint8_t *)&h, g_snap_part.area_base, sizeof(h));
CHECK(h.magic[0] == SNAP_HEAD_MAGIC0);
CHECK(h.wr_sector == 2);
CHECK(h.wr_off == SNAP_DATA_BASE + 4096);
CHECK(snap_read_idx(63, &r) == 0);
CHECK(r.seq == 64);
CHECK(snap_read_idx(64, &r) == 0);
CHECK(r.seq == 65);
printf("PASS test_sector_switch\n");
}
/* 测试6: RAM 暂存满丢新 — 8 深, 第 9 条丢, flush 只落 8 条 */
static void test_ram_overflow_drop(void)
{
LUP_SensorReport sr;
SnapRec r;
uint32_t i;
reset_flash();
snap_init();
make_report(&sr, 0x400, 400);
for (i = 0; i < SNAP_RAM_DEPTH + 3; i++) { /* 11 条, 深 8 */
snap_enqueue(&sr); /* 不 flush, 模拟中断风暴 */
}
CHECK(snap_count() == 0); /* 全部还在 RAM */
snap_flush();
CHECK(snap_count() == 8); /* 只落 8 条, 丢 3 条 */
CHECK(snap_read_idx(0, &r) == 0);
CHECK(r.seq == 1); /* 丢的是最新(尾部) */
CHECK(snap_read_idx(7, &r) == 0);
CHECK(r.seq == 8);
printf("PASS test_ram_overflow_drop\n");
}
/* 测试7: 清空 — 擦数据区 + 事件流审计 (payload[0]=2=快照流) */
static void test_clear_audit(void)
{
LUP_SensorReport sr;
SnapRec r;
uint32_t i;
reset_flash();
g_audit_type = 0; g_audit_len = 0; memset(g_audit_payload, 0, sizeof(g_audit_payload));
snap_init();
make_report(&sr, 0x500, 500);
for (i = 0; i < 5; i++) {
snap_enqueue(&sr);
snap_flush();
}
CHECK(snap_count() == 5);
snap_clear();
CHECK(snap_count() == 0);
CHECK(snap_seq_last() == 5); /* seq 不重置 */
CHECK(snap_read_idx(0, &r) == -1); /* 数据区已空 */
/* 审计: offlog_evt 被调用, type=LOG_CLEAR, payload[0]=2 */
CHECK(g_audit_type == OFFLOG_EVT_LOG_CLEAR);
CHECK(g_audit_len == 1);
CHECK(g_audit_payload[0] == 2);
printf("PASS test_clear_audit\n");
}
/* 测试8: 运行时分区 — W25Q32 快照区 0x110000, 3008KB, 48064 条 */
static void test_part_table(void)
{
reset_flash();
snap_init();
CHECK(g_snap_part.area_base == 0x110000UL);
CHECK(g_snap_part.area_size == 0x2F0000UL); /* 3008KB */
CHECK(g_snap_part.data_sectors == 751);
CHECK(g_snap_part.max_records == 751 * 64);
CHECK(SNAP_DATA_BASE == 0x111000UL); /* base + 4KB 头扇区 */
printf("PASS test_part_table\n");
}
/* 测试9: 懒擦 — init 只擦当前写扇区, 其余扇区由环形写切扇区时覆盖
(修复: 原 init 擦全部 751 扇区 ~34s → 主循环阻塞超 IWDG 4s → 不断复位) */
static void test_lazy_erase(void)
{
LUP_SensorReport sr;
SnapRec r;
uint32_t i;
reset_flash();
/* 在数据扇区 2 预埋旧数据 (模拟 Flash 残留) */
flash[SNAP_DATA_BASE + 4096] = 0xAA;
flash[SNAP_DATA_BASE + 4096 + 1] = 0x55;
snap_init();
/* 懒擦: 只擦扇区 1, 扇区 2 保持旧数据 */
CHECK(flash[SNAP_DATA_BASE] == 0xFF); /* 扇区1 已擦 */
CHECK(flash[SNAP_DATA_BASE + 4096] == 0xAA); /* 扇区2 未动 */
CHECK(flash[SNAP_DATA_BASE + 4096 + 1] == 0x55);
make_report(&sr, 0x600, 600);
for (i = 0; i < 64; i++) { /* 写满扇区 1 */
snap_enqueue(&sr);
snap_flush();
}
CHECK(flash[SNAP_DATA_BASE + 4096] == 0xAA); /* 仍在扇区 1 内, 扇区2 未动 */
snap_enqueue(&sr); /* 第 65 条切扇区 */
snap_flush();
CHECK(flash[SNAP_DATA_BASE + 4096] != 0xAA); /* 扇区2 已擦并写入新数据 */
CHECK(snap_read_idx(64, &r) == 0);
CHECK(r.seq == 65);
CHECK(snap_count() == 65);
printf("PASS test_lazy_erase\n");
}
int main(void)
{
test_fresh_init_and_flush();
test_pack_format();
test_ring_wrap();
test_power_loss_recovery();
test_sector_switch();
test_ram_overflow_drop();
test_clear_audit();
test_part_table();
test_lazy_erase();
if (failures) {
printf("\n%d FAILURE(S)\n", failures);
return 1;
}
printf("\nALL PASS\n");
return 0;
}