Files
CCU621M/BSP/flash_file_mgr/ocpp_mv_offline_flash_impl.c

571 lines
21 KiB
C
Raw Permalink 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.
/**
* @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);
}
}