现场: 烧录快照固件后 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 回归全过
390 lines
12 KiB
C
390 lines
12 KiB
C
/**
|
||
* 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;
|
||
}
|