/** * @file ocpp_mv_offline_flash_impl.c * @brief OCPP 离线 MeterValues:SPI 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 /* -------------------------------------------------------------------------- */ /* [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); } }