9ceb218f80
Co-authored-by: Cursor <cursoragent@cursor.com>
571 lines
21 KiB
C
571 lines
21 KiB
C
/**
|
||
* @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 <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);
|
||
}
|
||
}
|