Initial commit: CCU621_M firmware project with BLE debug link support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:28:36 +08:00
commit 9ceb218f80
1597 changed files with 724159 additions and 0 deletions
@@ -0,0 +1,570 @@
/**
* @file ocpp_mv_offline_flash_impl.c
* @brief OCPP 离线 MeterValuesSPI Flash 线性池 + FRAM 流水号映射(实现仿 fault_flash_impl
*
* =============================================================================
* 本文件按功能划分为以下模块(自上而下阅读):
* -----------------------------------------------------------------------------
* [1] 编译期检查、全局变量与文件内静态资源
* [2] FRAM 管理块:CRC 计算、默认值、读/写 EEPROM、上电初始化
* [3] Flash 池寻址:枪半区基址、该枪序号 ↔ 槽、槽 ↔ 线性字节地址
* [4] Flash 单槽访问:组帧/校验、按物理槽读一条、按物理槽写一条(含扇区擦除策略)
* [5] FRAM 映射表:按流水号查找行、查找空行
* [6] 对外接口:池容量、负载合法性、保存/按序号读取 MV
* [7] 对外:映射表查询/删除;整池擦除仅本文件 static
* =============================================================================
*/
#include "ocpp_mv_offline_flash_impl.h"
#include "unsettled_order_mng.h"
#include "publicdata/publicdata.h"
#include <string.h>
/* -------------------------------------------------------------------------- */
/* [1] 编译期检查、全局变量与文件内静态资源 */
/* -------------------------------------------------------------------------- */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(OCPP_MV_OFFLINE_RECORDS_PER_SECTOR > 0u, "records per sector");
_Static_assert(OCPP_MV_OFFLINE_POOL_MAX_PER_GUN > 0u, "pool max per gun");
#else
typedef int __ocpp_mv_check_c1[(OCPP_MV_OFFLINE_RECORDS_PER_SECTOR > 0u) ? 1 : -1];
typedef int __ocpp_mv_check_c2[(OCPP_MV_OFFLINE_POOL_MAX_PER_GUN > 0u) ? 1 : -1];
#endif
/**
* @var s_ocppMvOfflineFramMng
* @brief OCPP 离线 MV 的 FRAM 管理块在 RAM 中的镜像
* @details
* - 与 FM24 地址 `EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL`0x0700)对应,长度为 128 字节。
* - 含 `u32_next_seq[2]`(每枪下一条单调序号)及最多 `OCPP_MV_OFFLINE_MAP_ROWS` 行流水号+枪映射。
* - 上电由 `v_ocpp_mv_offline_init` 通过 `u8_ocpp_mv_offline_fram_read` 装载;业务写入 MV 后
* 由 `u8_ocpp_mv_offline_fram_write` 回写 FRAM,掉电保持序号与映射连续性。
*/
static S_OCPP_MV_OFFLINE_FRAM_MNG s_ocppMvOfflineFramMng;
/**
* @var s_ocpp_mv_frame_buf
* @brief 单条 Flash 记录在 RAM 中的组帧缓冲(静态、本文件内可见)
* @details 布局与 `fault_flash_impl` 一致:`[0]` 帧头、`[1..64]` 负载、`[65]` CRC 低 8 位,
* 总长 `OCPP_MV_OFFLINE_FRAME_BYTES`34 = 1+32+1)。供底层按物理槽读/写时复用,非重入。
*/
static U8_T s_ocpp_mv_frame_buf[OCPP_MV_OFFLINE_FRAME_BYTES];
/* -------------------------------------------------------------------------- */
/* [2] FRAM 管理块:CRC、默认值、读/写 EEPROM、上电初始化 */
/* -------------------------------------------------------------------------- */
/**
* @fn u16_ocpp_mv_fram_crc
* @brief 计算 FRAM 管理块整结构的 CRC-16(算法与未结算模块 `u16_calculate_crc` 一致)
* @param[in] p 待计算的管理块指针(只读)
* @return 16 位 CRC 值
* @note 计算前将副本中 `u16_crc` 置 0,再对整块字节流做 CRC,与写入 EEPROM 时的规则一致。
*/
static U16_T u16_ocpp_mv_fram_crc(const S_OCPP_MV_OFFLINE_FRAM_MNG *p)
{
S_OCPP_MV_OFFLINE_FRAM_MNG z;
(void)memcpy(&z, p, sizeof(z));
z.u16_crc = 0;
return u16_calculate_crc((const U8_T *)&z, (U16_T)sizeof(z));
}
/**
* @fn v_ocpp_mv_offline_fram_default
* @brief 将 FRAM 管理块置为出厂/安全默认并填入合法 CRC(不写 EEPROM)
* @param[in,out] mng 目标结构体指针;为 NULL 时直接返回
* @details 清零后设置 `u16_head`、`u16_version``u32_next_seq[]` 与映射表全 0。
*/
static void v_ocpp_mv_offline_fram_default(S_OCPP_MV_OFFLINE_FRAM_MNG *mng)
{
if (mng == NULL) {
return;
}
(void)memset(mng, 0, sizeof(*mng));
mng->u16_head = OCPP_MV_OFFLINE_FRAM_HEAD;
mng->u16_version = (U16_T)OCPP_MV_OFFLINE_FRAM_MNG_VER;
mng->u16_crc = u16_ocpp_mv_fram_crc(mng);
}
/**
* @fn u8_ocpp_mv_offline_fram_read
* @brief 从 FRAM(FM24)读取管理块到调用方缓冲区,并校验头与 CRC
* @param[out] out 输出缓冲区,不可为 NULL
* @return 0 读取且校验成功;1 空指针;2 I2C 读失败(已写入默认块到 out);3 自愈写 EEPROM 失败
* @details
* - 若头标记、版本号或 CRC 不匹配,则调用 `v_ocpp_mv_offline_fram_default` 填充 out
* 并尝试写回 EEPROM 以自愈坏块。
*/
static U8_T u8_ocpp_mv_offline_fram_read(S_OCPP_MV_OFFLINE_FRAM_MNG *out)
{
S_OCPP_MV_OFFLINE_FRAM_MNG tmp;
U16_T crc_expect;
if (out == NULL) {
return 1u;
}
if (eeprom_read((S32_T)EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL, (U8_T *)&tmp, (S32_T)sizeof(tmp)) != HAL_OK) {
v_ocpp_mv_offline_fram_default(out);
return 2u;
}
crc_expect = u16_ocpp_mv_fram_crc(&tmp);
if (tmp.u16_head != OCPP_MV_OFFLINE_FRAM_HEAD || tmp.u16_version != (U16_T)OCPP_MV_OFFLINE_FRAM_MNG_VER
|| tmp.u16_crc != crc_expect) {
v_ocpp_mv_offline_fram_default(out);
if (eeprom_write((S32_T)EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL, (U8_T *)out, (S32_T)sizeof(*out)) != HAL_OK) {
return 3u;
}
return 0u;
}
(void)memcpy(out, &tmp, sizeof(tmp));
return 0u;
}
/**
* @fn u8_ocpp_mv_offline_fram_write
* @brief 将管理块写入 FRAM,并自动重算 `u16_crc` 后落盘
* @param[in] mng 待写入的管理块指针;为 NULL 时返回错误
* @return 0 成功;1 空指针;2 EEPROM 写失败
*/
static U8_T u8_ocpp_mv_offline_fram_write(const S_OCPP_MV_OFFLINE_FRAM_MNG *mng)
{
S_OCPP_MV_OFFLINE_FRAM_MNG tmp;
if (mng == NULL) {
return 1u;
}
(void)memcpy(&tmp, mng, sizeof(tmp));
tmp.u16_crc = u16_ocpp_mv_fram_crc(&tmp);
if (eeprom_write((S32_T)EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL, (U8_T *)&tmp, (S32_T)sizeof(tmp)) != HAL_OK) {
return 2u;
}
return 0u;
}
/**
* @fn v_ocpp_mv_offline_init
* @brief 模块上电初始化:从 FRAM 恢复 `s_ocppMvOfflineFramMng`
* @details 若读失败或数据非法,则写入默认管理块,保证后续 `mv_save` 有合法序号与空映射表。
*/
void v_ocpp_mv_offline_init(void)
{
if (u8_ocpp_mv_offline_fram_read(&s_ocppMvOfflineFramMng) != 0u) {
v_ocpp_mv_offline_fram_default(&s_ocppMvOfflineFramMng);
(void)u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
}
}
/* -------------------------------------------------------------------------- */
/* [3] Flash 池寻址:枪半区基址、该枪单调序号 ↔ 槽、槽 ↔ 线性地址 */
/* -------------------------------------------------------------------------- */
/**
* @fn u32_ocpp_mv_gun_half_base
* @brief 指定枪在 SPI Flash 上的半区首字节地址(枪0=前半 32KB,枪1=后半 32KB
*/
static U32_T u32_ocpp_mv_gun_half_base(U8_T gun)
{
return DATAFLASH_OCPP_MV_OFFLINE_ADDR + (U32_T)gun * DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE;
}
/**
* @fn u32_gun_seq_to_slot
* @brief 该枪单调序号映射为该枪半区内物理槽(环形覆盖)
*/
static U32_T u32_gun_seq_to_slot(U32_T seq)
{
return (U32_T)(seq % (U32_T)OCPP_MV_OFFLINE_POOL_MAX_PER_GUN);
}
/**
* @fn u32_gun_slot_to_addr
* @brief 枪号 + 该枪半区内槽索引 → 线性字节地址
*/
static U32_T u32_gun_slot_to_addr(U8_T gun, U32_T slot_in_gun)
{
U32_T rps = (U32_T)OCPP_MV_OFFLINE_RECORDS_PER_SECTOR;
U32_T sec_in_gun = slot_in_gun / rps;
U32_T pos = slot_in_gun % rps;
U32_T sec_base = u32_ocpp_mv_gun_half_base(gun) + sec_in_gun * SPI_SECTOR_SIZE;
return sec_base + pos * (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES;
}
/* -------------------------------------------------------------------------- */
/* [4] Flash 单槽访问:读一条、写一条(扇区擦除策略同 fault) */
/* -------------------------------------------------------------------------- */
/**
* @fn u8_ocpp_mv_read_at_gun_slot
* @brief 从指定枪半区内槽读取一条 MV 负载(校验帧头与 CRC)
*/
static U8_T u8_ocpp_mv_read_at_gun_slot(U8_T gun, U32_T slot_in_gun, S_OCPP_MV_OFFLINE_MV *mv)
{
U32_T addr = u32_gun_slot_to_addr(gun, slot_in_gun);
if (mv == NULL) {
return 1u;
}
if (s32_flash_dataflash_read(addr, s_ocpp_mv_frame_buf, (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES) != 0u) {
return 2u;
}
if (s_ocpp_mv_frame_buf[0] != RECORD_HEADER) {
return 3u;
}
if (s_ocpp_mv_frame_buf[1u + sizeof(S_OCPP_MV_OFFLINE_MV)] !=
(U8_T)u16_crc_checksum(s_ocpp_mv_frame_buf + 1u, (U16_T)sizeof(S_OCPP_MV_OFFLINE_MV))) {
return 3u;
}
(void)memcpy(mv, s_ocpp_mv_frame_buf + 1u, sizeof(S_OCPP_MV_OFFLINE_MV));
return 0u;
}
/**
* @fn u8_ocpp_mv_write_at_gun_slot
* @brief 向指定枪半区内槽写入一条 MV(组帧、按扇区擦除后写入)
* @details 扇区首槽擦扇区;非首槽若本扇区 0 槽无效则先擦整扇区(同 fault 策略)。
*/
static U8_T u8_ocpp_mv_write_at_gun_slot(U8_T gun, U32_T slot_in_gun, const S_OCPP_MV_OFFLINE_MV *mv)
{
U32_T rps = (U32_T)OCPP_MV_OFFLINE_RECORDS_PER_SECTOR;
U32_T sec_in_gun = slot_in_gun / rps;
U32_T pos = slot_in_gun % rps;
U32_T sec_base = u32_ocpp_mv_gun_half_base(gun) + sec_in_gun * SPI_SECTOR_SIZE;
U32_T addr = sec_base + pos * (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES;
U32_T slot0_in_sector = sec_in_gun * rps;
S_OCPP_MV_OFFLINE_MV dummy;
if (mv == NULL) {
return 1u;
}
/* 非扇区首槽:仅在本扇区尚无其它有效记录时擦除(避免 seq 20 写入后 seq 21 因 slot0 空再次擦扇区抹掉 20) */
if (pos != 0u) {
if (u8_ocpp_mv_read_at_gun_slot(gun, slot0_in_sector, &dummy) != 0u) {
U32_T s;
U8_T sector_has_data = 0u;
for (s = slot0_in_sector + 1u; s < slot_in_gun; s++) {
if (u8_ocpp_mv_read_at_gun_slot(gun, s, &dummy) == 0u) {
sector_has_data = 1u;
break;
}
}
if (sector_has_data == 0u) {
(void)s32_flash_dataflash_erase_sector(sec_base);
}
}
}
s_ocpp_mv_frame_buf[0] = RECORD_HEADER;
(void)memcpy(s_ocpp_mv_frame_buf + 1u, mv, sizeof(S_OCPP_MV_OFFLINE_MV));
s_ocpp_mv_frame_buf[1u + sizeof(S_OCPP_MV_OFFLINE_MV)] =
(U8_T)u16_crc_checksum((U8_T *)mv, (U16_T)sizeof(S_OCPP_MV_OFFLINE_MV));
if (pos == 0u) {
(void)s32_flash_dataflash_erase_sector(sec_base);
}
return s32_flash_dataflash_write(addr, s_ocpp_mv_frame_buf, (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES);
}
/* -------------------------------------------------------------------------- */
/* [5] FRAM 映射表:按会话键(当前为开始充电秒 + hi=0)查找、查找空行 */
/* -------------------------------------------------------------------------- */
/**
* @fn p_ocpp_mv_map_find_row
* @brief 在映射表中查找与给定会话键(`u32_tx_sn_lo` / `u32_tx_sn_hi`)及枪号完全匹配的一行
*/
static S_OCPP_MV_OFFLINE_MAP_ROW *p_ocpp_mv_map_find_row(U32_T tx_sn_lo, U32_T tx_sn_hi, U8_T gun)
{
U32_T i;
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u32_tx_sn_lo == tx_sn_lo && r->u32_tx_sn_hi == tx_sn_hi && r->u8_gun_no == gun) {
return r;
}
}
return NULL;
}
/**
* @fn p_ocpp_mv_map_find_empty
* @brief 在映射表中查找第一个空闲行(约定:`u32_tx_sn_lo` 与 `u32_tx_sn_hi` 均为 0 表示未使用)
* @return 空闲行指针;若无空行返回 NULL(表示已达映射行上限)
*/
static S_OCPP_MV_OFFLINE_MAP_ROW *p_ocpp_mv_map_find_empty(void)
{
U32_T i;
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u32_tx_sn_lo == 0u && r->u32_tx_sn_hi == 0u) {
return r;
}
}
return NULL;
}
/**
* @brief 映射表满时,复用指定枪最旧一行(seq_start 最小)
* @details 起充不再批量 clear_gun;满表时仅在新会话首条 MV 前淘汰一行,避免 “map full” 丢采样
*/
static S_OCPP_MV_OFFLINE_MAP_ROW *p_ocpp_mv_map_evict_oldest_gun(U8_T gun)
{
U32_T i;
S_OCPP_MV_OFFLINE_MAP_ROW *best = NULL;
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u8_gun_no != gun || (r->u32_tx_sn_lo == 0u && r->u32_tx_sn_hi == 0u)) {
continue;
}
if (best == NULL || r->u32_seq_start < best->u32_seq_start) {
best = r;
}
}
if (best != NULL) {
(void)memset(best, 0, sizeof(*best));
}
return best;
}
/* -------------------------------------------------------------------------- */
/* [6] 对外:负载校验、保存 MV、按枪序号读取 MV */
/* -------------------------------------------------------------------------- */
/**
* @fn u8_ocpp_mv_offline_mv_is_valid
* @brief 判断内存中的一条 MV 负载是否带有合法魔数与版本号(不读 Flash)
* @param[in] mv 负载指针;NULL 视为无效
* @return 1 有效;0 无效
*/
U8_T u8_ocpp_mv_offline_mv_is_valid(const S_OCPP_MV_OFFLINE_MV *mv)
{
if (mv == NULL) {
return 0u;
}
if (mv->u32_magic != OCPP_MV_OFFLINE_FLASH_MAGIC) {
return 0u;
}
if (mv->u16_version != (U16_T)OCPP_MV_OFFLINE_FLASH_VER) {
return 0u;
}
return 1u;
}
/**
* @brief 为指定会话键在该枪半区追加保存一条离线 MV:分配序号、写 Flash、更新 FRAM
* @param[in] tx_sn_lo 会话键:当前为 `xDate2Seconds(StartChargeTime)`(单路 U32
* @param[in] tx_sn_hi 当前恒 0(与 FRAM 行 `u32_tx_sn_hi` 一致)
* @param[in] u8_gun_no 枪号 0 或 1
* @param[out] out_seq 本次在该枪下的单调序号(与物理槽 `seq % POOL_MAX_PER_GUN` 对应)
* @details
* - `seq` 取当前 `u32_next_seq[u8_gun_no]`;已存在映射行时要求 `seq == seq_start + count`。
* - Flash 写失败时回滚 RAM 中对映射行的修改,不推进该枪的 `u32_next_seq`。
*/
U8_T u8_ocpp_mv_offline_mv_save(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, const S_OCPP_MV_OFFLINE_MV *mv,
U32_T *out_seq)
{
S_OCPP_MV_OFFLINE_MV wr;
S_OCPP_MV_OFFLINE_MAP_ROW *row;
S_OCPP_MV_OFFLINE_MAP_ROW *empty;
U32_T seq;
U32_T slot;
U8_T ret;
if (mv == NULL || out_seq == NULL) {
return 1u;
}
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
(void)memcpy(&wr, mv, sizeof(wr));
if (wr.u32_magic != OCPP_MV_OFFLINE_FLASH_MAGIC) {
wr.u32_magic = OCPP_MV_OFFLINE_FLASH_MAGIC;
}
if (wr.u16_version != (U16_T)OCPP_MV_OFFLINE_FLASH_VER) {
wr.u16_version = (U16_T)OCPP_MV_OFFLINE_FLASH_VER;
}
seq = s_ocppMvOfflineFramMng.u32_next_seq[u8_gun_no];
row = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (row != NULL) {
if (seq != row->u32_seq_start + (U32_T)row->u16_count) {
return 5u;
}
row->u16_count++;
} else {
empty = p_ocpp_mv_map_find_empty();
if (empty == NULL) {
empty = p_ocpp_mv_map_evict_oldest_gun(u8_gun_no);
if (empty == NULL) {
return 2u;
}
}
empty->u32_tx_sn_lo = tx_sn_lo;
empty->u32_tx_sn_hi = tx_sn_hi;
empty->u32_seq_start = seq;
empty->u16_count = 1u;
empty->u8_gun_no = u8_gun_no;
empty->u8_reserved = 0u;
}
slot = u32_gun_seq_to_slot(seq);
ret = u8_ocpp_mv_write_at_gun_slot(u8_gun_no, slot, &wr);
if (ret != 0u) {
if (row != NULL) {
row->u16_count--;
} else {
empty = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (empty != NULL) {
(void)memset(empty, 0, sizeof(*empty));
}
}
return 3u;
}
s_ocppMvOfflineFramMng.u32_next_seq[u8_gun_no] = seq + 1u;
*out_seq = seq;
ret = u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
if (ret != 0u) {
return 4u;
}
return 0u;
}
/**
* @brief 按该枪单调序号读取一条 MV`seq % POOL_MAX_PER_GUN`
*/
U8_T u8_ocpp_mv_offline_mv_read_by_seq(U8_T u8_gun_no, U32_T seq, S_OCPP_MV_OFFLINE_MV *out)
{
U32_T slot;
if (out == NULL) {
return 1u;
}
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
slot = u32_gun_seq_to_slot(seq);
return u8_ocpp_mv_read_at_gun_slot(u8_gun_no, slot, out);
}
/* -------------------------------------------------------------------------- */
/* [7] 对外:映射表查询/删除、整池擦除 */
/* -------------------------------------------------------------------------- */
/**
* @fn u8_ocpp_mv_offline_map_get
* @brief 根据会话键查询其在池中的连续序号区间(供上送循环 `seq_start .. seq_start+count-1`
* @param[in] tx_sn_lo 会话键(当前为开始充电秒)
* @param[in] tx_sn_hi 当前恒 0
* @param[out] seq_start 输出该区间的首条全局序号
* @param[out] count 输出连续条数
* @return 0 找到;1 空指针;2 未找到对应键
*/
U8_T u8_ocpp_mv_offline_map_get(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, U32_T *seq_start, U16_T *count)
{
S_OCPP_MV_OFFLINE_MAP_ROW *row;
if (seq_start == NULL || count == NULL) {
return 1u;
}
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
row = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (row == NULL) {
return 2u;
}
*seq_start = row->u32_seq_start;
*count = row->u16_count;
return 0u;
}
/**
* @fn u8_ocpp_mv_offline_map_remove
* @brief 上送完成后删除指定会话键的映射行(不自动擦除 Flash 池中对应物理数据)
* @param[in] tx_sn_lo 会话键(当前为开始充电秒)
* @param[in] tx_sn_hi 当前恒 0
* @return 0 成功删除并已写回 FRAM;1 未找到该键;2 FRAM 写失败
* @note 若需同时清空整池 Flash,可调用本文件内静态函数 `v_ocpp_mv_offline_flash_erase_pool`(维护/调试用)。
*/
U8_T u8_ocpp_mv_offline_map_remove(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi)
{
S_OCPP_MV_OFFLINE_MAP_ROW *row;
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
row = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (row == NULL) {
return 1u;
}
(void)memset(row, 0, sizeof(*row));
return u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
}
/**
* @fn u8_ocpp_mv_offline_map_clear_gun
* @brief 新一笔充电开始前,清除指定枪在 FRAM 中的全部离线 MV 映射行
*
* @details
* - **作用对象**:仅 `s_ocppMvOfflineFramMng.aMap[]`(会话键 → Flash 全局序号区间),**不擦除** SPI Flash 池内已写入的 MV 物理记录;池区仍按环形序号覆盖写入。
* - **调用时机**:维护/调试;正常起充不再批量调用(同枪多笔离线充须保留各行映射)。映射满时由 `mv_save` 按枪淘汰最旧一行。
* - **与 `u8_ocpp_mv_offline_map_remove` 区别**`map_remove` 在**单笔**离线 MV 上送完成后按会话键删一行;本函数在**起充边沿**按枪批量清空,不区分 `tx_sn_lo`/`tx_sn_hi`。
* - **上送侧**:清空后本枪需重新落盘才会建立新映射;进行中的 `session` 补发状态机在桥接层会随起充一并 `memset` 复位。
*
* @param[in] u8_gun_no 枪索引 `0 .. OCPP_MV_OFFLINE_GUN_CNT-1`
* @return 0 成功(含本枪本无映射行、无需写 FRAM);1 枪号非法;2 有行被清但 FRAM 写回失败
*/
U8_T u8_ocpp_mv_offline_map_clear_gun(U8_T u8_gun_no)
{
U32_T i;
U8_T changed = 0u;
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u8_gun_no == u8_gun_no && (r->u32_tx_sn_lo != 0u || r->u32_tx_sn_hi != 0u)) {
(void)memset(r, 0, sizeof(*r));
changed = 1u;
}
}
if (changed == 0u) {
return 0u;
}
if (u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng) != 0u) {
return 2u;
}
return 0u;
}
/**
* @fn v_ocpp_mv_offline_flash_erase_pool
* @brief 擦除离线 MV 专用分区内的全部物理扇区(整池 Flash 清空)
* @param[in] also_reset_fram 非 0 时同时将 `s_ocppMvOfflineFramMng` 置默认并写回 FRAM(序号与映射清零)
* @details 遍历 `DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT`,对每扇区基址调用 `s32_flash_dataflash_erase_sector`。
*/
static void v_ocpp_mv_offline_flash_erase_pool(U8_T also_reset_fram)
{
U32_T i;
for (i = 0u; i < DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT; i++) {
(void)s32_flash_dataflash_erase_sector(DATAFLASH_OCPP_MV_OFFLINE_ADDR + i * SPI_SECTOR_SIZE);
}
if (also_reset_fram != 0u) {
v_ocpp_mv_offline_fram_default(&s_ocppMvOfflineFramMng);
(void)u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
}
}