Initial commit: CCU621_M firmware project with BLE debug link support.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,732 @@
|
||||
/**
|
||||
* @file card_flash_impl.c
|
||||
* @brief 卡号白名单:EEPROM `S_CARD_MNG` + SPI Flash 定长槽(48B/槽,与 fault 相同的帧头+CRC 低字节)
|
||||
*
|
||||
* 单槽布局:RECORD_HEADER + CARD_FLASH_BODY_T(46) + crc8
|
||||
* 写任意槽需对该槽所在扇区做读-合并-擦除-整扇区写回(NOR Flash 不可原地改已写位)。
|
||||
*/
|
||||
|
||||
#include "publicdata/public_define.h"
|
||||
#include "publicdata/publicdata.h"
|
||||
#include "app_fatfs/fatfs_card.h"
|
||||
#include "app_fatfs/fatfs_init.h"
|
||||
#include "card_flash_impl.h"
|
||||
#include "eeprom/fm24cl16.h"
|
||||
#include "externalflash/flash_external_data.h"
|
||||
#include <string.h>
|
||||
|
||||
/* 动态内存抽象:默认使用 FreeRTOS 堆接口,可按需覆盖 */
|
||||
#ifndef FLASH_MGR_MALLOC
|
||||
#define FLASH_MGR_MALLOC(sz) pvPortMalloc((sz))
|
||||
#endif
|
||||
#ifndef FLASH_MGR_FREE
|
||||
#define FLASH_MGR_FREE(ptr) vPortFree((ptr))
|
||||
#endif
|
||||
|
||||
#if FATFS_EN
|
||||
#if 1
|
||||
|
||||
#define CARD_FLASH_MAX_CNT CARD_MAX_COUNT
|
||||
#define CARD_BODY_SIZE 46u
|
||||
#define CARD_SLOT_SIZE 48u
|
||||
#define CARD_SLOTS_PER_SECTOR ((U32_T)SPI_SECTOR_SIZE / (U32_T)CARD_SLOT_SIZE)
|
||||
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
_Static_assert(sizeof(CARD_FLASH_BODY_T) == 46u, "CARD_FLASH_BODY_T must be 46 bytes");
|
||||
#endif
|
||||
|
||||
static bool g_card_init;
|
||||
static S_CARD_MNG s_cardMng;
|
||||
|
||||
/**
|
||||
* @brief z_strnlen。
|
||||
*/
|
||||
static size_t z_strnlen(const char *s, size_t max)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < max && s[i] != '\0'; i++) {
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_pack_card_num。
|
||||
*/
|
||||
static void v_pack_card_num(const char *src, U8_T dst[32])
|
||||
{
|
||||
memset(dst, 0, 32u);
|
||||
if (src == NULL) {
|
||||
return;
|
||||
}
|
||||
size_t n = z_strnlen(src, (size_t)CARD_NUM_MAX_LEN);
|
||||
memcpy(dst, src, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief b_card_num_eq。
|
||||
*/
|
||||
static bool b_card_num_eq(const char *src, const U8_T dst[32])
|
||||
{
|
||||
U8_T a[32];
|
||||
v_pack_card_num(src, a);
|
||||
return memcmp(a, dst, 32u) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_read_eeprom_card_mng。
|
||||
*/
|
||||
static void v_read_eeprom_card_mng(S_CARD_MNG *m)
|
||||
{
|
||||
int i;
|
||||
S_CARD_MNG tmp;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (eeprom_read(EEPROM_ADDR_CARD_MNG, (U8_T *)&tmp, (S32_T)sizeof(S_CARD_MNG)) == HAL_OK) {
|
||||
m->u16_head = tmp.u16_head;
|
||||
m->u16_cardCnt = tmp.u16_cardCnt;
|
||||
m->u16_reserved = tmp.u16_reserved;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m->u16_head = 0;
|
||||
m->u16_cardCnt = 0;
|
||||
m->u16_reserved = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_write_eeprom_card_mng。
|
||||
*/
|
||||
static void v_write_eeprom_card_mng(const S_CARD_MNG *m)
|
||||
{
|
||||
S_CARD_MNG out;
|
||||
out.u16_head = CARD_MNG_HEAD;
|
||||
out.u16_cardCnt = m->u16_cardCnt;
|
||||
out.u16_reserved = m->u16_reserved;
|
||||
eeprom_write(EEPROM_ADDR_CARD_MNG, (U8_T *)&out, (S32_T)sizeof(S_CARD_MNG));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief u32_slot_addr。
|
||||
*/
|
||||
static U32_T u32_slot_addr(U16_T slot)
|
||||
{
|
||||
U32_T sec = (U32_T)slot / CARD_SLOTS_PER_SECTOR;
|
||||
U32_T j = (U32_T)slot % CARD_SLOTS_PER_SECTOR;
|
||||
return (U32_T)DATAFLASH_CARD_ADDR + sec * (U32_T)SPI_SECTOR_SIZE + j * (U32_T)CARD_SLOT_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_read_slot_raw。
|
||||
*/
|
||||
static void v_read_slot_raw(U16_T slot, U8_T *out)
|
||||
{
|
||||
(void)s32_flash_dataflash_read(u32_slot_addr(slot), out, CARD_SLOT_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief b_slot_buf_valid。
|
||||
*/
|
||||
static bool b_slot_buf_valid(const U8_T *buf)
|
||||
{
|
||||
U8_T crc;
|
||||
if (buf[0] != RECORD_HEADER) {
|
||||
return false;
|
||||
}
|
||||
crc = (U8_T)u16_crc_checksum((U8_T *)(buf + 1), (U16_T)CARD_BODY_SIZE);
|
||||
return buf[CARD_SLOT_SIZE - 1u] == crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_body_to_card_info。
|
||||
*/
|
||||
static void v_body_to_card_info(const CARD_FLASH_BODY_T *b, CardInfo *ci)
|
||||
{
|
||||
uint32_t ct;
|
||||
uint32_t lt;
|
||||
|
||||
memset(ci, 0, sizeof(*ci));
|
||||
memcpy(ci->card_num, b->card_num, sizeof(b->card_num));
|
||||
ci->card_num[CARD_NUM_MAX_LEN - 1u] = '\0';
|
||||
|
||||
ci->balance = b->amount_milli / 10u;
|
||||
memcpy(&ct, &b->rsv[0], 4u);
|
||||
memcpy(<, &b->rsv[4], 4u);
|
||||
ci->create_time = ct;
|
||||
ci->last_use_time = lt;
|
||||
ci->status = b->status;
|
||||
memcpy(ci->reserved, b->rsv, sizeof(b->rsv));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_card_info_to_body。
|
||||
*/
|
||||
static void v_card_info_to_body(const CardInfo *ci, CARD_FLASH_BODY_T *b)
|
||||
{
|
||||
memset(b, 0, sizeof(*b));
|
||||
v_pack_card_num(ci->card_num, b->card_num);
|
||||
b->amount_milli = ci->balance * 10u;
|
||||
b->status = ci->status;
|
||||
memcpy(&b->rsv[0], &ci->create_time, 4u);
|
||||
memcpy(&b->rsv[4], &ci->last_use_time, 4u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief u16_count_valid_slots。
|
||||
*/
|
||||
static U16_T u16_count_valid_slots(void)
|
||||
{
|
||||
U16_T n = 0;
|
||||
U16_T i;
|
||||
U8_T buf[CARD_SLOT_SIZE];
|
||||
|
||||
for (i = 0; i < CARD_FLASH_MAX_CNT; i++) {
|
||||
v_read_slot_raw(i, buf);
|
||||
if (b_slot_buf_valid(buf)) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief s_find_slot_by_num。
|
||||
*/
|
||||
static S16_T s_find_slot_by_num(const char *num)
|
||||
{
|
||||
U16_T i;
|
||||
U8_T buf[CARD_SLOT_SIZE];
|
||||
|
||||
if (num == NULL) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < CARD_FLASH_MAX_CNT; i++) {
|
||||
v_read_slot_raw(i, buf);
|
||||
if (!b_slot_buf_valid(buf)) {
|
||||
continue;
|
||||
}
|
||||
if (b_card_num_eq(num, buf + 1)) {
|
||||
return (S16_T)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief s_find_free_slot。
|
||||
*/
|
||||
static S16_T s_find_free_slot(void)
|
||||
{
|
||||
U16_T i;
|
||||
U8_T buf[CARD_SLOT_SIZE];
|
||||
|
||||
for (i = 0; i < CARD_FLASH_MAX_CNT; i++) {
|
||||
v_read_slot_raw(i, buf);
|
||||
if (!b_slot_buf_valid(buf)) {
|
||||
return (S16_T)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief v_erase_all_card_sectors。
|
||||
*/
|
||||
static void v_erase_all_card_sectors(void)
|
||||
{
|
||||
U8_T s;
|
||||
for (s = 0; s < (U8_T)DATAFLASH_CARD_SECTOR_CNT; s++) {
|
||||
(void)s32_flash_dataflash_erase_sector((U32_T)DATAFLASH_CARD_ADDR + (U32_T)s * (U32_T)SPI_SECTOR_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写单槽:读扇区内其它有效槽,合并后擦除扇区再写回整扇区
|
||||
* @param body_or_null 非 NULL 写入/更新;NULL 表示删除该槽(置为擦除态)
|
||||
*/
|
||||
static CardStatus e_flush_slot(U16_T slot, const CARD_FLASH_BODY_T *body_or_null)
|
||||
{
|
||||
U32_T sec = (U32_T)slot / CARD_SLOTS_PER_SECTOR;
|
||||
U32_T j = (U32_T)slot % CARD_SLOTS_PER_SECTOR;
|
||||
U32_T sector_base = (U32_T)DATAFLASH_CARD_ADDR + sec * (U32_T)SPI_SECTOR_SIZE;
|
||||
U32_T k;
|
||||
U8_T tmp[CARD_SLOT_SIZE];
|
||||
U8_T *sector_rw = (U8_T *)FLASH_MGR_MALLOC((size_t)SPI_SECTOR_SIZE);
|
||||
|
||||
if (sector_rw == NULL) {
|
||||
return CARD_STATUS_WRITE_FAILED;
|
||||
}
|
||||
|
||||
memset(sector_rw, 0xFF, SPI_SECTOR_SIZE);
|
||||
for (k = 0; k < CARD_SLOTS_PER_SECTOR; k++) {
|
||||
U16_T g = (U16_T)(sec * CARD_SLOTS_PER_SECTOR + k);
|
||||
if (g >= CARD_FLASH_MAX_CNT) {
|
||||
break;
|
||||
}
|
||||
if (g == slot) {
|
||||
continue;
|
||||
}
|
||||
v_read_slot_raw(g, tmp);
|
||||
if (b_slot_buf_valid(tmp)) {
|
||||
memcpy(§or_rw[k * CARD_SLOT_SIZE], tmp, CARD_SLOT_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (body_or_null != NULL) {
|
||||
U8_T *dst = §or_rw[j * CARD_SLOT_SIZE];
|
||||
dst[0] = RECORD_HEADER;
|
||||
memcpy(dst + 1, body_or_null, sizeof(CARD_FLASH_BODY_T));
|
||||
dst[CARD_SLOT_SIZE - 1u] =
|
||||
(U8_T)u16_crc_checksum(dst + 1, (U16_T)CARD_BODY_SIZE);
|
||||
}
|
||||
|
||||
(void)s32_flash_dataflash_erase_sector(sector_base);
|
||||
if (s32_flash_dataflash_write(sector_base, sector_rw, (U32_T)SPI_SECTOR_SIZE) != 0u) {
|
||||
FLASH_MGR_FREE(sector_rw);
|
||||
return CARD_STATUS_WRITE_FAILED;
|
||||
}
|
||||
FLASH_MGR_FREE(sector_rw);
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_init。
|
||||
*/
|
||||
CardStatus card_init(void)
|
||||
{
|
||||
U16_T actual;
|
||||
|
||||
if (g_card_init) {
|
||||
return CARD_STATUS_ALREADY_INIT;
|
||||
}
|
||||
|
||||
v_read_eeprom_card_mng(&s_cardMng);
|
||||
actual = u16_count_valid_slots();
|
||||
|
||||
if (s_cardMng.u16_head != CARD_MNG_HEAD) {
|
||||
s_cardMng.u16_head = CARD_MNG_HEAD;
|
||||
s_cardMng.u16_cardCnt = actual;
|
||||
s_cardMng.u16_reserved = 0;
|
||||
v_write_eeprom_card_mng(&s_cardMng);
|
||||
} else if (s_cardMng.u16_cardCnt != actual) {
|
||||
s_cardMng.u16_cardCnt = actual;
|
||||
v_write_eeprom_card_mng(&s_cardMng);
|
||||
}
|
||||
|
||||
g_card_init = true;
|
||||
FATFS_PRINT("card flash: init ok, cnt=%u\r\n", (unsigned)s_cardMng.u16_cardCnt);
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_deinit。
|
||||
*/
|
||||
void card_deinit(void)
|
||||
{
|
||||
g_card_init = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_add。
|
||||
*/
|
||||
CardStatus card_add(const CardInfo *card_info)
|
||||
{
|
||||
CARD_FLASH_BODY_T body;
|
||||
S16_T free_slot;
|
||||
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
if (card_info == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
if (s_find_slot_by_num(card_info->card_num) >= 0) {
|
||||
return CARD_STATUS_CARD_EXIST;
|
||||
}
|
||||
free_slot = s_find_free_slot();
|
||||
if (free_slot < 0) {
|
||||
return CARD_STATUS_NO_SPACE;
|
||||
}
|
||||
|
||||
v_card_info_to_body(card_info, &body);
|
||||
if (e_flush_slot((U16_T)free_slot, &body) != CARD_STATUS_OK) {
|
||||
return CARD_STATUS_WRITE_FAILED;
|
||||
}
|
||||
|
||||
s_cardMng.u16_cardCnt++;
|
||||
v_write_eeprom_card_mng(&s_cardMng);
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_update。
|
||||
*/
|
||||
CardStatus card_update(const CardInfo *card_info)
|
||||
{
|
||||
CARD_FLASH_BODY_T body;
|
||||
S16_T idx;
|
||||
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
if (card_info == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
idx = s_find_slot_by_num(card_info->card_num);
|
||||
if (idx < 0) {
|
||||
return CARD_STATUS_CARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
v_card_info_to_body(card_info, &body);
|
||||
if (e_flush_slot((U16_T)idx, &body) != CARD_STATUS_OK) {
|
||||
return CARD_STATUS_WRITE_FAILED;
|
||||
}
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_delete。
|
||||
*/
|
||||
CardStatus card_delete(const char *card_num)
|
||||
{
|
||||
S16_T idx;
|
||||
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
if (card_num == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
idx = s_find_slot_by_num(card_num);
|
||||
if (idx < 0) {
|
||||
return CARD_STATUS_CARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (e_flush_slot((U16_T)idx, NULL) != CARD_STATUS_OK) {
|
||||
return CARD_STATUS_WRITE_FAILED;
|
||||
}
|
||||
if (s_cardMng.u16_cardCnt > 0) {
|
||||
s_cardMng.u16_cardCnt--;
|
||||
}
|
||||
v_write_eeprom_card_mng(&s_cardMng);
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_find。
|
||||
*/
|
||||
CardStatus card_find(const char *card_num, CardInfo *card_info)
|
||||
{
|
||||
S16_T idx;
|
||||
U8_T buf[CARD_SLOT_SIZE];
|
||||
CARD_FLASH_BODY_T body;
|
||||
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
if (card_num == NULL || card_info == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
idx = s_find_slot_by_num(card_num);
|
||||
if (idx < 0) {
|
||||
return CARD_STATUS_CARD_NOT_FOUND;
|
||||
}
|
||||
|
||||
v_read_slot_raw((U16_T)idx, buf);
|
||||
if (!b_slot_buf_valid(buf)) {
|
||||
return CARD_STATUS_READ_FAILED;
|
||||
}
|
||||
memcpy(&body, buf + 1, sizeof(CARD_FLASH_BODY_T));
|
||||
v_body_to_card_info(&body, card_info);
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_get_count。
|
||||
*/
|
||||
CardStatus card_get_count(uint32_t *count)
|
||||
{
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
if (count == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
*count = (uint32_t)s_cardMng.u16_cardCnt;
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_get_all。
|
||||
*/
|
||||
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count)
|
||||
{
|
||||
U16_T i;
|
||||
U32_T n;
|
||||
U8_T buf[CARD_SLOT_SIZE];
|
||||
CARD_FLASH_BODY_T body;
|
||||
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
if (count == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
n = 0;
|
||||
for (i = 0; i < CARD_FLASH_MAX_CNT && n < max_count; i++) {
|
||||
v_read_slot_raw(i, buf);
|
||||
if (!b_slot_buf_valid(buf)) {
|
||||
continue;
|
||||
}
|
||||
if (card_list != NULL) {
|
||||
memcpy(&body, buf + 1, sizeof(CARD_FLASH_BODY_T));
|
||||
v_body_to_card_info(&body, &card_list[n]);
|
||||
}
|
||||
n++;
|
||||
}
|
||||
*count = n;
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_check_exist。
|
||||
*/
|
||||
CardStatus card_check_exist(const char *card_num, bool *exist)
|
||||
{
|
||||
if (card_num == NULL || exist == NULL) {
|
||||
return CARD_STATUS_PARAM_ERROR;
|
||||
}
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
*exist = (s_find_slot_by_num(card_num) >= 0);
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_backup。
|
||||
*/
|
||||
CardStatus card_backup(void)
|
||||
{
|
||||
/* 无独立备份区;保留接口兼容旧 CSV 备份语义 */
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_restore。
|
||||
*/
|
||||
CardStatus card_restore(void)
|
||||
{
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief card_clear_all。
|
||||
*/
|
||||
CardStatus card_clear_all(void)
|
||||
{
|
||||
if (!g_card_init) {
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
|
||||
v_erase_all_card_sectors();
|
||||
s_cardMng.u16_head = CARD_MNG_HEAD;
|
||||
s_cardMng.u16_cardCnt = 0;
|
||||
s_cardMng.u16_reserved = 0;
|
||||
v_write_eeprom_card_mng(&s_cardMng);
|
||||
FATFS_PRINT("card flash: cleared\r\n");
|
||||
return CARD_STATUS_OK;
|
||||
}
|
||||
|
||||
#else /* !FATFS_ENABLE_CARD */
|
||||
|
||||
CardStatus card_init(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
void card_deinit(void) {}
|
||||
/**
|
||||
* @brief card_add。
|
||||
*/
|
||||
CardStatus card_add(const CardInfo *card_info)
|
||||
{
|
||||
(void)card_info;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_update。
|
||||
*/
|
||||
CardStatus card_update(const CardInfo *card_info)
|
||||
{
|
||||
(void)card_info;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_delete。
|
||||
*/
|
||||
CardStatus card_delete(const char *card_num)
|
||||
{
|
||||
(void)card_num;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_find。
|
||||
*/
|
||||
CardStatus card_find(const char *card_num, CardInfo *card_info)
|
||||
{
|
||||
(void)card_num;
|
||||
(void)card_info;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_get_count。
|
||||
*/
|
||||
CardStatus card_get_count(uint32_t *count)
|
||||
{
|
||||
if (count) {
|
||||
*count = 0;
|
||||
}
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_get_all。
|
||||
*/
|
||||
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count)
|
||||
{
|
||||
(void)card_list;
|
||||
(void)max_count;
|
||||
if (count) {
|
||||
*count = 0;
|
||||
}
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_check_exist。
|
||||
*/
|
||||
CardStatus card_check_exist(const char *card_num, bool *exist)
|
||||
{
|
||||
(void)card_num;
|
||||
if (exist) {
|
||||
*exist = false;
|
||||
}
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_backup。
|
||||
*/
|
||||
CardStatus card_backup(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_restore。
|
||||
*/
|
||||
CardStatus card_restore(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_clear_all。
|
||||
*/
|
||||
CardStatus card_clear_all(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
|
||||
#endif /* FATFS_ENABLE_CARD */
|
||||
|
||||
#else /* !FATFS_EN */
|
||||
|
||||
CardStatus card_init(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
void card_deinit(void) {}
|
||||
/**
|
||||
* @brief card_add。
|
||||
*/
|
||||
CardStatus card_add(const CardInfo *card_info)
|
||||
{
|
||||
(void)card_info;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_update。
|
||||
*/
|
||||
CardStatus card_update(const CardInfo *card_info)
|
||||
{
|
||||
(void)card_info;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_delete。
|
||||
*/
|
||||
CardStatus card_delete(const char *card_num)
|
||||
{
|
||||
(void)card_num;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_find。
|
||||
*/
|
||||
CardStatus card_find(const char *card_num, CardInfo *card_info)
|
||||
{
|
||||
(void)card_num;
|
||||
(void)card_info;
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_get_count。
|
||||
*/
|
||||
CardStatus card_get_count(uint32_t *count)
|
||||
{
|
||||
if (count) {
|
||||
*count = 0;
|
||||
}
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_get_all。
|
||||
*/
|
||||
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count)
|
||||
{
|
||||
(void)card_list;
|
||||
(void)max_count;
|
||||
if (count) {
|
||||
*count = 0;
|
||||
}
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_check_exist。
|
||||
*/
|
||||
CardStatus card_check_exist(const char *card_num, bool *exist)
|
||||
{
|
||||
(void)card_num;
|
||||
if (exist) {
|
||||
*exist = false;
|
||||
}
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_backup。
|
||||
*/
|
||||
CardStatus card_backup(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_restore。
|
||||
*/
|
||||
CardStatus card_restore(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
/**
|
||||
* @brief card_clear_all。
|
||||
*/
|
||||
CardStatus card_clear_all(void)
|
||||
{
|
||||
return CARD_STATUS_INIT_FAILED;
|
||||
}
|
||||
|
||||
#endif /* FATFS_EN */
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file card_flash_impl.h
|
||||
* @brief 卡号白名单:EEPROM 管理信息 + 外部 SPI Flash 槽位存储(与 fault_flash_impl 同类策略)
|
||||
*
|
||||
* 对外业务接口见 `app_fatfs/fatfs_card.h`(card_init / card_add / card_find 等)。
|
||||
* 本头文件提供介质层结构说明,便于调试或扩展。
|
||||
*/
|
||||
|
||||
#ifndef CARD_FLASH_IMPL_H
|
||||
#define CARD_FLASH_IMPL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include "publicdata/type.h"
|
||||
|
||||
#define CARD_MNG_HEAD 0x5AA8u
|
||||
|
||||
/** EEPROM 中的卡管理块(地址 EEPROM_ADDR_CARD_MNG) */
|
||||
typedef struct
|
||||
{
|
||||
U16_T u16_head;
|
||||
U16_T u16_cardCnt;
|
||||
U16_T u16_reserved;
|
||||
} S_CARD_MNG;
|
||||
|
||||
/**
|
||||
* Flash 单槽有效载荷 46 字节(槽位总占用 48:1 字节 RECORD_HEADER + 46 + 1 字节 CRC 低 8 位)
|
||||
* - amount_milli:金额,单位 0.001 元(与 API 中 balance「分」换算:毫元 = 分 * 10)
|
||||
* - rsv[8]:前 4 字节 create_time,后 4 字节 last_use_time(Unix 秒,小端),与 CardInfo 时间字段对应
|
||||
*/
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
U8_T card_num[32];
|
||||
U32_T amount_milli;
|
||||
U16_T status;
|
||||
U8_T rsv[8];
|
||||
} CARD_FLASH_BODY_T;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CARD_FLASH_IMPL_H */
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* @file fault_flash_impl.c
|
||||
* @brief 历史故障记录:EEPROM 索引 + 外部 Flash 数据区实现。
|
||||
*
|
||||
* 迁移说明(参考 CCU601E_D):
|
||||
* - EEPROM(fm24cl16)仅存储 S_HIS_FAULT_MNG 索引信息,减少擦写与上电恢复成本。
|
||||
* - 外部 Flash(externalflash)按扇区保存 HIS_FAULT_DATA_T 记录内容。
|
||||
* - 每条记录格式:1字节 RECORD_HEADER + 数据 + 1字节CRC(低8位)。
|
||||
*/
|
||||
|
||||
#include "flash_file_mgr/fault_flash_impl.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "publicdata/publicdata.h"
|
||||
#include "publicdata/public_define.h"
|
||||
#include "eeprom/fm24cl16.h"
|
||||
#include "externalflash/flash_external_data.h"
|
||||
|
||||
/* 兼容抽象:默认使用 FreeRTOS 堆 */
|
||||
#ifndef FLASH_MGR_MALLOC
|
||||
#define FLASH_MGR_MALLOC(sz) pvPortMalloc((sz))
|
||||
#endif
|
||||
#ifndef FLASH_MGR_FREE
|
||||
#define FLASH_MGR_FREE(ptr) vPortFree((ptr))
|
||||
#endif
|
||||
|
||||
#define HIS_FAULT_MNG_HEADER (0x5AA5u)
|
||||
#define FAULT_MNG_EEPROM_ADDR ((U16_T)EEPROM_ADDR_HISALARM_MNG)
|
||||
#define FAULT_RECORD_RETRY (3u)
|
||||
|
||||
/* 来自故障任务:全局管理索引 */
|
||||
extern S_HIS_FAULT_MNG s_hisFaultMng;
|
||||
|
||||
void v_fault_flash_init(void)
|
||||
{
|
||||
/* 上电恢复 EEPROM 中的历史故障管理索引。 */
|
||||
v_read_eeprom_hisFaultMng(&s_hisFaultMng);
|
||||
|
||||
/* 管理信息异常时回退到安全默认值。 */
|
||||
if ((s_hisFaultMng.u16_head != HIS_FAULT_MNG_HEADER) ||
|
||||
(s_hisFaultMng.u16_hisFaultCnt > HIS_FAULT_MAX_CNT) ||
|
||||
(s_hisFaultMng.u16_currIndex > HIS_FAULT_MAX_CNT)) {
|
||||
v_fault_clear_his_fault();
|
||||
}
|
||||
}
|
||||
|
||||
static U16_T fault_sector_record_count(void)
|
||||
{
|
||||
return (U16_T)(SPI_SECTOR_SIZE / (sizeof(HIS_FAULT_DATA_T) + 2u));
|
||||
}
|
||||
|
||||
void v_write_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng)
|
||||
{
|
||||
S_HIS_FAULT_MNG tmp;
|
||||
|
||||
if (hisFaultMng == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
tmp.u16_head = HIS_FAULT_MNG_HEADER;
|
||||
tmp.u16_hisFaultCnt = hisFaultMng->u16_hisFaultCnt;
|
||||
tmp.u16_currIndex = hisFaultMng->u16_currIndex;
|
||||
|
||||
eeprom_buffer_write((U8_T *)&tmp, FAULT_MNG_EEPROM_ADDR, (U16_T)sizeof(S_HIS_FAULT_MNG));
|
||||
}
|
||||
|
||||
void v_read_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng)
|
||||
{
|
||||
S_HIS_FAULT_MNG tmp;
|
||||
U8_T i;
|
||||
|
||||
if (hisFaultMng == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
for (i = 0u; i < FAULT_RECORD_RETRY; i++) {
|
||||
eeprom_buffer_read((U8_T *)&tmp, FAULT_MNG_EEPROM_ADDR, (U16_T)sizeof(S_HIS_FAULT_MNG));
|
||||
if ((eeprom_get_last_error() == 0u) && (tmp.u16_head == HIS_FAULT_MNG_HEADER)) {
|
||||
hisFaultMng->u16_head = HIS_FAULT_MNG_HEADER;
|
||||
hisFaultMng->u16_hisFaultCnt = tmp.u16_hisFaultCnt;
|
||||
hisFaultMng->u16_currIndex = tmp.u16_currIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 失败时给出安全默认值,避免野数据参与索引计算。 */
|
||||
hisFaultMng->u16_head = 0u;
|
||||
hisFaultMng->u16_hisFaultCnt = 0u;
|
||||
hisFaultMng->u16_currIndex = RECORD_ZERO_SAVE;
|
||||
}
|
||||
|
||||
void v_fault_clear_his_fault(void)
|
||||
{
|
||||
s_hisFaultMng.u16_head = HIS_FAULT_MNG_HEADER;
|
||||
s_hisFaultMng.u16_hisFaultCnt = 0u;
|
||||
s_hisFaultMng.u16_currIndex = RECORD_ZERO_SAVE;
|
||||
v_write_eeprom_hisFaultMng(&s_hisFaultMng);
|
||||
}
|
||||
|
||||
U16_T u16_fault_get_his_count(void)
|
||||
{
|
||||
S_HIS_FAULT_MNG mng;
|
||||
|
||||
memset(&mng, 0, sizeof(mng));
|
||||
v_read_eeprom_hisFaultMng(&mng);
|
||||
if (mng.u16_head != HIS_FAULT_MNG_HEADER) {
|
||||
return 0u;
|
||||
}
|
||||
if (mng.u16_hisFaultCnt > HIS_FAULT_MAX_CNT) {
|
||||
return HIS_FAULT_MAX_CNT;
|
||||
}
|
||||
return mng.u16_hisFaultCnt;
|
||||
}
|
||||
|
||||
U8_T u8_fault_read_his_by_newest(U16_T newest_pos, HIS_FAULT_DATA_T *pt_hisFault)
|
||||
{
|
||||
S_HIS_FAULT_MNG mng;
|
||||
U16_T total;
|
||||
U16_T slot;
|
||||
|
||||
if (pt_hisFault == NULL) {
|
||||
return 0u;
|
||||
}
|
||||
memset(pt_hisFault, 0, sizeof(*pt_hisFault));
|
||||
|
||||
total = u16_fault_get_his_count();
|
||||
if ((total == 0u) || (newest_pos >= total)) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
memset(&mng, 0, sizeof(mng));
|
||||
v_read_eeprom_hisFaultMng(&mng);
|
||||
if ((mng.u16_head != HIS_FAULT_MNG_HEADER) || (mng.u16_currIndex == RECORD_ZERO_SAVE)) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
slot = (U16_T)((mng.u16_currIndex + HIS_FAULT_MAX_CNT - newest_pos) % HIS_FAULT_MAX_CNT);
|
||||
v_read_hisFault_record(pt_hisFault, slot);
|
||||
return (pt_hisFault->e_fault_info == E_FAULT_NULL) ? 0u : 1u;
|
||||
}
|
||||
|
||||
void v_read_hisFault_record(HIS_FAULT_DATA_T *pt_hisFault, U16_T u16_index)
|
||||
{
|
||||
U16_T rec_size = (U16_T)sizeof(HIS_FAULT_DATA_T);
|
||||
U16_T one_sector_cnt = fault_sector_record_count();
|
||||
U32_T addr;
|
||||
U16_T sector_idx;
|
||||
U16_T sector_rec_idx;
|
||||
U8_T retry;
|
||||
U8_T *buf;
|
||||
|
||||
if ((pt_hisFault == NULL) || (one_sector_cnt == 0u)) {
|
||||
return;
|
||||
}
|
||||
|
||||
buf = (U8_T *)FLASH_MGR_MALLOC((size_t)rec_size + 2u);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(pt_hisFault, 0, sizeof(*pt_hisFault));
|
||||
for (retry = 0u; retry < FAULT_RECORD_RETRY; retry++) {
|
||||
sector_idx = (U16_T)(u16_index / one_sector_cnt);
|
||||
sector_rec_idx = (U16_T)(u16_index % one_sector_cnt);
|
||||
addr = (U32_T)(DATAFLASH_FAULT_ADDR +
|
||||
(U32_T)sector_idx * SPI_SECTOR_SIZE +
|
||||
(U32_T)sector_rec_idx * (rec_size + 2u));
|
||||
|
||||
if (s32_flash_dataflash_read(addr, buf, (U32_T)(rec_size + 2u)) != 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((buf[0] == RECORD_HEADER) &&
|
||||
(buf[rec_size + 1u] == (U8_T)u16_crc_checksum(buf + 1u, rec_size))) {
|
||||
memcpy(pt_hisFault, buf + 1u, rec_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FLASH_MGR_FREE(buf);
|
||||
}
|
||||
|
||||
static void v_write_hisFault_record(HIS_FAULT_DATA_T *pt_hisFault, U16_T u16_index)
|
||||
{
|
||||
U16_T rec_size = (U16_T)sizeof(HIS_FAULT_DATA_T);
|
||||
U16_T one_sector_cnt = fault_sector_record_count();
|
||||
U16_T sector_idx;
|
||||
U16_T sector_rec_idx;
|
||||
U32_T sector_addr;
|
||||
U32_T addr;
|
||||
U8_T *buf;
|
||||
|
||||
if ((pt_hisFault == NULL) || (one_sector_cnt == 0u)) {
|
||||
return;
|
||||
}
|
||||
|
||||
buf = (U8_T *)FLASH_MGR_MALLOC((size_t)rec_size + 2u);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
buf[0] = RECORD_HEADER;
|
||||
memcpy(buf + 1u, pt_hisFault, rec_size);
|
||||
buf[rec_size + 1u] = (U8_T)u16_crc_checksum((U8_T *)pt_hisFault, rec_size);
|
||||
|
||||
sector_idx = (U16_T)(u16_index / one_sector_cnt);
|
||||
sector_rec_idx = (U16_T)(u16_index % one_sector_cnt);
|
||||
sector_addr = (U32_T)(DATAFLASH_FAULT_ADDR + (U32_T)sector_idx * SPI_SECTOR_SIZE);
|
||||
addr = (U32_T)(sector_addr + (U32_T)sector_rec_idx * (rec_size + 2u));
|
||||
|
||||
/* 扇区首条记录写入前先擦除,避免旧数据残留影响 CRC。 */
|
||||
if (sector_rec_idx == 0u) {
|
||||
(void)s32_flash_dataflash_erase_sector(sector_addr);
|
||||
}
|
||||
|
||||
(void)s32_flash_dataflash_write(addr, buf, (U32_T)(rec_size + 2u));
|
||||
FLASH_MGR_FREE(buf);
|
||||
}
|
||||
|
||||
void v_hisFault_save_record(HIS_FAULT_DATA_T *pt_hisFault_data)
|
||||
{
|
||||
if (pt_hisFault_data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_hisFaultMng.u16_currIndex == RECORD_ZERO_SAVE) {
|
||||
s_hisFaultMng.u16_currIndex = 0u;
|
||||
} else {
|
||||
s_hisFaultMng.u16_currIndex =
|
||||
(U16_T)((s_hisFaultMng.u16_currIndex + 1u) % HIS_FAULT_MAX_CNT);
|
||||
}
|
||||
|
||||
if (s_hisFaultMng.u16_hisFaultCnt < HIS_FAULT_MAX_CNT) {
|
||||
s_hisFaultMng.u16_hisFaultCnt++;
|
||||
}
|
||||
|
||||
v_write_hisFault_record(pt_hisFault_data, s_hisFaultMng.u16_currIndex);
|
||||
v_write_eeprom_hisFaultMng(&s_hisFaultMng);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @file fault_flash_impl.h
|
||||
* @brief 历史故障记录持久化接口(EEPROM索引 + 外部Flash数据)。
|
||||
*/
|
||||
#ifndef FAULT_FLASH_IMPL_H
|
||||
#define FAULT_FLASH_IMPL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include "fault_cheak/faultcheck_task.h"
|
||||
#include "fault_cheak/fault_interface.h"
|
||||
|
||||
#define FAULT_INFO_TEXT_MAX_LEN (64u)
|
||||
|
||||
/* 故障历史存储功能开关:
|
||||
* - 0:先屏蔽(避免影响 EEPROM 可靠性排查)
|
||||
* - 1:使能(恢复 EEPROM 索引 + Flash 记录读写)
|
||||
*/
|
||||
#ifndef FAULT_FLASH_MGR_ENABLE
|
||||
#define FAULT_FLASH_MGR_ENABLE (1u)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 历史故障管理信息(存放在 EEPROM)。
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
U16_T u16_head; /* 固定头 0x5AA5 */
|
||||
U16_T u16_hisFaultCnt; /* 历史记录总数(上限 HIS_FAULT_MAX_CNT) */
|
||||
U16_T u16_currIndex; /* 当前最新记录逻辑索引,初值 RECORD_ZERO_SAVE */
|
||||
} S_HIS_FAULT_MNG;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
E_FAULT_INFO e_mag;
|
||||
char info[FAULT_INFO_TEXT_MAX_LEN];
|
||||
} S_FAULT_INFO;
|
||||
|
||||
void v_fault_flash_init(void);
|
||||
void v_write_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng);
|
||||
void v_read_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng);
|
||||
void v_fault_clear_his_fault(void);
|
||||
|
||||
U16_T u16_fault_get_his_count(void);
|
||||
U8_T u8_fault_read_his_by_newest(U16_T newest_pos, HIS_FAULT_DATA_T *pt_hisFault);
|
||||
|
||||
void v_hisFault_save_record(HIS_FAULT_DATA_T *pt_hisFault_data);
|
||||
void v_read_hisFault_record(HIS_FAULT_DATA_T *pt_hisFault, U16_T u16_index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FAULT_FLASH_IMPL_H */
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# flash_file_mgr 模块说明
|
||||
|
||||
本文档用于说明 `BSP/flash_file_mgr` 目录下各文件职责、数据落盘位置与调用关系,便于定位问题和后续扩展。
|
||||
|
||||
## 目录内文件与功能
|
||||
|
||||
### 1) `fault_flash_impl.h` / `fault_flash_impl.c`
|
||||
- **功能**:历史故障记录持久化(EEPROM 管理信息 + SPI Flash 记录体)。
|
||||
- **管理信息**:`S_HIS_FAULT_MNG`,存放在 EEPROM `EEPROM_ADDR_HISALARM_MNG`。
|
||||
- **记录数据**:从 `DATAFLASH_FAULT_ADDR` 开始,单条记录格式:
|
||||
- `RECORD_HEADER(1B)` + `HIS_FAULT_DATA_T` + `CRC低8位(1B)`。
|
||||
- **核心接口**:
|
||||
- `v_hisFault_save_record()`:保存一条故障记录。
|
||||
- `v_read_hisFault_record()`:按逻辑索引读取记录。
|
||||
- `v_fault_clear_his_fault()`:清空故障管理信息。
|
||||
|
||||
### 2) `card_flash_impl.h` / `card_flash_impl.c`
|
||||
- **功能**:卡号白名单持久化(替代 CSV/FAT 文件实现),由 `fatfs_card.h` 对外暴露业务接口。
|
||||
- **管理信息**:`S_CARD_MNG`,存放在 EEPROM `EEPROM_ADDR_CARD_MNG`。
|
||||
- **记录数据**:从 `DATAFLASH_CARD_ADDR` 开始,单槽格式:
|
||||
- `RECORD_HEADER(1B)` + `CARD_FLASH_BODY_T` + `CRC低8位(1B)`。
|
||||
- **关键特性**:
|
||||
- 按卡号查重/查找。
|
||||
- 写入采用“读扇区 -> 合并 -> 擦除 -> 整扇区回写”策略,避免 NOR Flash 原地改写问题。
|
||||
- **对外接口(由 `fatfs_card.h` 声明)**:
|
||||
- `card_init/card_add/card_update/card_delete/card_find/...`。
|
||||
|
||||
### 3) `meter_calculate_flash_impl.h` / `meter_calculate_flash_impl.c`
|
||||
- **功能**:充电订单历史记录 + 临时订单(断电续传)持久化。
|
||||
- **管理信息**:`S_CHG_ORDER_MNG`,存放在 EEPROM `EEPROM_ADDR_CHGRCD_MNG`。
|
||||
- **历史订单数据**:从 `DATAFLASH_HIS_RECORD_ADDR` 开始,记录体为 `S_LOG_DATA`,单条格式:
|
||||
- `RECORD_HEADER(1B)` + `S_LOG_DATA` + `CRC低8位(1B)`。
|
||||
- **临时订单数据**:A/B 枪分别存放在 EEPROM `EEPROM_ADDR_A_LOG_DATA` / `EEPROM_ADDR_B_LOG_DATA`。
|
||||
- **核心接口**:
|
||||
- `u32_chg_order_save_record()`:保存并分配唯一索引。
|
||||
- `v_read_chg_order_record()`:按索引读取。
|
||||
- `v_temp_chg_data_save/v_temp_chg_data_read/...`:临时订单保存/恢复。
|
||||
|
||||
### 4) `unsettled_order_mng.h` / `unsettled_order_mng.c`
|
||||
- **功能**:未结算订单索引列表管理(仅存索引,不存订单正文)。
|
||||
- **存储位置**:EEPROM `EEPROM_ADDR_UNSETTLED_MNG`。
|
||||
- **数据结构**:`S_UNSETTLED_ORDER_MNG`,包含头标志、数量、CRC 和 3 字节压缩索引数组。
|
||||
- **核心接口**:
|
||||
- `u8_add_unsettled_order()`:新增未结订单索引。
|
||||
- `u8_remove_unsettled_order()`:删除已结索引。
|
||||
- `u8_check_order_settled()`:检查是否仍未结。
|
||||
|
||||
### 5) `ocpp_mv_offline_flash_impl.h` / `ocpp_mv_offline_flash_impl.c`
|
||||
- **功能**:平台离线时周期缓存 MeterValues;Flash 为 **整池定长帧数组**(仿 `fault_flash_impl`:帧头+32B 负载+CRC),**全局序号**与物理槽 `seq % POOL_MAX` 一一对应;FRAM 存 `u32_next_seq` 与 **流水号→(seq_start,count)** 映射表(最多 6 笔并发),上送时按序号直接读 Flash。
|
||||
- **容量**:`OCPP_MV_OFFLINE_POOL_MAX` = 每扇区 `floor(4096/帧长)` × `DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT`(扇区尾截断不用)。
|
||||
- **滚动**:`u32_next_seq` 递增写满池后自然覆盖最旧物理槽;映射行上送后应 `u8_ocpp_mv_offline_map_remove`。
|
||||
- **核心接口**:`v_ocpp_mv_offline_init`、`u32_ocpp_mv_offline_pool_max`、`u8_ocpp_mv_offline_mv_save`、`u8_ocpp_mv_offline_mv_read_by_seq`、`u8_ocpp_mv_offline_map_get`、`u8_ocpp_mv_offline_map_remove`、`v_ocpp_mv_offline_flash_erase_pool`、`u8_ocpp_mv_offline_fram_read` / `u8_ocpp_mv_offline_fram_write`。
|
||||
- **RAM 镜像**:`s_ocppMvOfflineFramMng`;上电 `v_ocpp_mv_offline_init()`(兼容宏 `v_ocpp_mv_offline_flash_init()`)。
|
||||
|
||||
## 地址与容量宏来源
|
||||
|
||||
`flash_file_mgr` 模块统一依赖 `BSP/spi_Flash/flash_external_data.h` 中定义的地址与容量宏(如 `DATAFLASH_FAULT_ADDR`、`DATAFLASH_CARD_ADDR`、`DATAFLASH_HIS_RECORD_ADDR`、`DATAFLASH_OCPP_MV_OFFLINE_ADDR`、`CHG_ORDER_*` 等)。
|
||||
|
||||
建议后续若调整分区地址,仅修改 `flash_external_data.h`,并回归验证:
|
||||
- 故障记录读写;
|
||||
- 卡号增删查;
|
||||
- 订单保存与按索引回读;
|
||||
- 未结算订单索引增删。
|
||||
|
||||
## 模块关系(简版)
|
||||
|
||||
- `faultcheck_task` -> `fault_flash_impl`:故障历史。
|
||||
- `fatfs_init / bs_public_impl` -> `fatfs_card.h` -> `card_flash_impl`:本地卡鉴权与白名单。
|
||||
- `meter_calculate_impl` -> `meter_calculate_flash_impl`:订单持久化。
|
||||
- `meter_calculate_flash_impl` + `bs_public_impl` -> `unsettled_order_mng`:未结算订单索引维护。
|
||||
- (规划)`BS_ocpp_ctrl` / OCPP 离线路径 -> `ocpp_mv_offline_flash_impl`:离线 MeterValues 采样落盘与联网后补发。
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# flash_file_mgr 模块说明
|
||||
|
||||
> 路径:`BSP/flash_file_mgr/`
|
||||
> 作用:提供历史故障记录的掉电持久化能力,采用“EEPROM 管理索引 + 外部 Flash 存储内容”的双存储架构。
|
||||
|
||||
## 1. 模块文件
|
||||
|
||||
- `fault_flash_impl.h`
|
||||
- 历史故障持久化对外接口声明
|
||||
- `S_HIS_FAULT_MNG` 管理结构定义
|
||||
- `fault_flash_impl.c`
|
||||
- EEPROM 索引读写实现
|
||||
- 外部 Flash 记录读写实现
|
||||
- 历史记录计数、按“最新N条”读取实现
|
||||
- `meter_calculate_flash_impl.c`
|
||||
- 充电订单记录持久化实现(EEPROM 管理块 + 外部 Flash 订单正文)
|
||||
- 提供订单保存/按索引读取/按位置读取、临时订单 EEPROM 缓存接口
|
||||
- 支持双枪临时订单标志管理(用于掉电恢复)
|
||||
- `unsettled_order_mng.c`
|
||||
- 未结算订单列表管理(EEPROM 持久化)
|
||||
- 支持未结订单添加、移除、查询、清空与 CRC 校验
|
||||
|
||||
## 2. 设计目标
|
||||
|
||||
- 将高频变化的小数据(计数、索引)放 EEPROM,降低 Flash 管理复杂度;
|
||||
- 将体积较大的历史故障记录放 externalflash,便于顺序存储与扩展;
|
||||
- 提供统一接口给 `fault_cheak`、`meter_calculate`,业务层只关心“保存/读取故障或订单记录”。
|
||||
|
||||
## 3. 存储布局
|
||||
|
||||
### 3.1 EEPROM(索引区)
|
||||
|
||||
- 地址:`EEPROM_ADDR_HISALARM_MNG`
|
||||
- 数据:`S_HIS_FAULT_MNG`
|
||||
- `u16_head`:固定头 `0x5AA5`,用于有效性校验
|
||||
- `u16_hisFaultCnt`:历史记录数
|
||||
- `u16_currIndex`:当前最新逻辑索引(环形)
|
||||
|
||||
### 3.2 externalflash(数据区)
|
||||
|
||||
- 基址:`DATAFLASH_FAULT_ADDR`
|
||||
- 扇区大小:`SPI_SECTOR_SIZE`
|
||||
- 单条记录格式:
|
||||
- `1 byte RECORD_HEADER`
|
||||
- `sizeof(HIS_FAULT_DATA_T) bytes 数据体`
|
||||
- `1 byte CRC`(`u16_crc_checksum` 低 8 位)
|
||||
|
||||
## 4. 核心接口说明
|
||||
|
||||
- `v_hisFault_save_record(HIS_FAULT_DATA_T *pt_hisFault_data)`
|
||||
- 保存一条历史故障
|
||||
- 更新环形索引与计数
|
||||
- 写 externalflash 记录后更新 EEPROM 管理信息
|
||||
- `u8_fault_read_his_by_newest(U16_T newest_pos, HIS_FAULT_DATA_T *pt_hisFault)`
|
||||
- 按“最新优先”读取历史记录
|
||||
- `newest_pos=0` 表示最新一条
|
||||
- `u16_fault_get_his_count(void)`
|
||||
- 返回历史记录数量(带上限保护)
|
||||
- `v_fault_clear_his_fault(void)`
|
||||
- 清空管理信息(不逐条清除 Flash 区内容)
|
||||
|
||||
### 4.1 计量订单相关接口(meter_calculate)
|
||||
|
||||
- `v_chg_order_flash_init(void)`
|
||||
- 初始化充电订单管理信息(读取 EEPROM 管理块,必要时恢复默认)
|
||||
- `u32_chg_order_save_record(S_LOG_DATA *order_data)`
|
||||
- 保存订单到外部 Flash,并返回单调递增订单索引
|
||||
- `v_read_chg_order_record(S_LOG_DATA *order_data, U32_T index)`
|
||||
- 按订单索引读取订单记录
|
||||
- `v_save_temp_chg_record_to_eeprom(...)` / `v_read_temp_chg_record_from_eeprom(...)`
|
||||
- 充电过程临时订单缓存/恢复(EEPROM)
|
||||
- `u8_add_unsettled_order(U32_T orderIndex)` / `u8_remove_unsettled_order(U32_T orderIndex)`
|
||||
- 未结算订单添加/移除
|
||||
- `u8_check_order_settled(U32_T orderIndex)`
|
||||
- 查询订单是否仍处于未结算列表
|
||||
|
||||
## 5. 写入策略
|
||||
|
||||
- 采用环形索引写入,达到上限后覆盖最老记录;
|
||||
- 每个扇区的第 1 条记录写入前先擦除该扇区,避免旧数据干扰;
|
||||
- 记录读取时校验:
|
||||
- 头字节必须是 `RECORD_HEADER`
|
||||
- CRC 必须匹配
|
||||
- 否则判定为无效记录。
|
||||
|
||||
## 6. 依赖关系
|
||||
|
||||
- EEPROM 驱动:`BSP/eeprom/fm24cl16.c`
|
||||
- 外部 Flash 驱动:`BSP/externalflash/flash_external_data.c`
|
||||
- CRC 工具:`app/publicdata/public_func.c` 的 `u16_crc_checksum`
|
||||
- 故障数据结构:`app/fault_cheak/faultcheck_task.h`
|
||||
- 计量订单结构:`app/meter_calculate/meter_calculate_impl.h`(`S_LOG_DATA`)
|
||||
|
||||
## 7. 注意事项
|
||||
|
||||
- 请保证 `DATAFLASH_FAULT_ADDR` 区域不与其他业务区重叠;
|
||||
- `HIS_FAULT_MAX_CNT` 与扇区分配需协同评估,避免有效容量不足;
|
||||
- 计量订单区(`DATAFLASH_HIS_RECORD_ADDR`)与故障区地址需避免重叠;
|
||||
- 未结算订单列表与订单管理块均依赖 EEPROM,建议控制写频率并保留 CRC 校验;
|
||||
- 该模块默认在任务上下文调用,若多任务并发写入建议增加互斥保护。
|
||||
|
||||
## 8. 相关文档
|
||||
|
||||
- [主说明 README](../../README.md)
|
||||
- [fault_cheak 模块说明](../../app/fault_cheak/fault_cheak模块说明.md)
|
||||
- [eeprom 模块说明](../eeprom/eeprom模块说明.md)
|
||||
- [externalflash 模块说明](../externalflash/externalflash模块说明.md)
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* @file meter_calculate_flash_impl.c
|
||||
* @brief 充电订单与临时订单的 EEPROM + 外部 SPI Flash 持久化实现
|
||||
*
|
||||
* 功能说明:
|
||||
* - 订单管理块 `S_CHG_ORDER_MNG` 存于 EEPROM(`EEPROM_ADDR_CHGRCD_MNG`):头标志、已存条数、
|
||||
* 环形槽当前位置、单调递增的 `u32_lastIndex`(对外唯一订单号)。
|
||||
* - 订单正文 `S_LOG_DATA` 存于 Flash 区 `DATAFLASH_HIS_RECORD_ADDR` 起;单条布局为
|
||||
* 1 字节 `RECORD_HEADER` + 数据 + 1 字节 CRC(`u16_crc_checksum` 低 8 位),按扇区擦除后顺序写入。
|
||||
* - `u32_chg_order_save_record`:分配新 `u32_lastIndex`,按 `(lastIndex-1)%CHG_ORDER_MAX_CNT` 映射槽位并写 Flash。
|
||||
* - `v_read_chg_order_record(index)`:用 `(index-1)%CHG_ORDER_MAX_CNT` 定位槽,并校验记录内 `u32_index` 与请求一致,防止覆盖残留。
|
||||
* - `v_read_chg_order_by_position`:在未满/已满环形缓冲下,按“第几条历史”语义换算槽位。
|
||||
* - 双枪临时订单:标志字节 + `S_LOG_DATA` 存于 EEPROM(`EEPROM_ADDR_A_LOG_DATA` / `B`),用于断电续传;清除时仅写无效标志以省擦写。
|
||||
*
|
||||
* 注意:`v_chg_order_clear_all` 仅复位管理元数据,不整片擦除 Flash(由后续写入覆盖)。
|
||||
*/
|
||||
|
||||
#include "meter_calculate_flash_impl.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "mylog/mylog.h"
|
||||
|
||||
/* 动态内存抽象:默认使用 FreeRTOS 堆接口,可按需覆盖 */
|
||||
#ifndef FLASH_MGR_MALLOC
|
||||
#define FLASH_MGR_MALLOC(sz) pvPortMalloc((sz))
|
||||
#endif
|
||||
#ifndef FLASH_MGR_FREE
|
||||
#define FLASH_MGR_FREE(ptr) vPortFree((ptr))
|
||||
#endif
|
||||
|
||||
/* 模块内部全局变量 */
|
||||
S_CHG_ORDER_MNG s_chgOrderMng;
|
||||
|
||||
/* 内部函数声明 */
|
||||
static void v_write_chg_order_record(S_LOG_DATA *order_data, U16_T position);
|
||||
static U8_T v_read_chg_order_record_internal(S_LOG_DATA *order_data, U16_T position);
|
||||
|
||||
/***************************************************************
|
||||
* EEPROM管理函数
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* @brief 写入充电订单管理信息到EEPROM
|
||||
*/
|
||||
void v_write_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng)
|
||||
{
|
||||
S_CHG_ORDER_MNG tempMng;
|
||||
|
||||
// 设置头标志
|
||||
tempMng.u16_head = 0x5AA5;
|
||||
tempMng.u16_chgOrderCnt = chgOrderMng->u16_chgOrderCnt;
|
||||
tempMng.u16_currIndex = chgOrderMng->u16_currIndex;
|
||||
tempMng.u32_lastIndex = chgOrderMng->u32_lastIndex;
|
||||
|
||||
// 写入EEPROM
|
||||
eeprom_write(EEPROM_ADDR_CHGRCD_MNG, (U8_T *)&tempMng, sizeof(S_CHG_ORDER_MNG));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从EEPROM读取充电订单管理信息
|
||||
*/
|
||||
void v_read_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng)
|
||||
{
|
||||
S_CHG_ORDER_MNG tempMng;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
// eeprom_read返回HAL_OK(0)表示成功,非0表示失败
|
||||
if (eeprom_read(EEPROM_ADDR_CHGRCD_MNG, (U8_T *)&tempMng, sizeof(S_CHG_ORDER_MNG)) == HAL_OK) {
|
||||
// 验证头标志
|
||||
if (tempMng.u16_head == 0x5AA5) {
|
||||
chgOrderMng->u16_head = 0x5AA5;
|
||||
chgOrderMng->u16_chgOrderCnt = tempMng.u16_chgOrderCnt;
|
||||
chgOrderMng->u16_currIndex = tempMng.u16_currIndex;
|
||||
chgOrderMng->u32_lastIndex = tempMng.u32_lastIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读取失败或数据无效,初始化默认值
|
||||
chgOrderMng->u16_head = 0x5AA5;
|
||||
chgOrderMng->u16_chgOrderCnt = 0;
|
||||
chgOrderMng->u16_currIndex = RECORD_ZERO_SAVE;
|
||||
chgOrderMng->u32_lastIndex = 0;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Flash记录读写函数(内部)
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* @brief 向Flash写充电订单记录(内部函数)
|
||||
*
|
||||
* @param order_data 充电订单数据指针
|
||||
* @param position 循环缓冲区中的位置(1-N范围)
|
||||
*/
|
||||
static void v_write_chg_order_record(S_LOG_DATA *order_data, U16_T position)
|
||||
{
|
||||
U16_T u16_oneRcdSize = CHG_ORDER_RECORD_SIZE; // 一条记录大小(可能大于255)
|
||||
U32_T u32_addr; // 存储地址
|
||||
U16_T SectorIndex = 0; // 扇区偏移索引
|
||||
U16_T SectorRcdIndex = 0; // 扇区内记录索引号
|
||||
U16_T OneSectorRcdCnt = CHG_ORDER_PER_SECTOR_CNT; // 一个扇区内最多存储记录数
|
||||
U32_T SectorAddr = 0; // 扇区地址
|
||||
U16_T CurSectorRcdCnt = 0; // 当前扇区内应存放最大数目
|
||||
|
||||
// position参数范围是1-N,转换为0-N范围用于内部计算
|
||||
U16_T internal_position = position - 1;
|
||||
|
||||
U8_T *tmpbuf = (U8_T *)FLASH_MGR_MALLOC(CHG_ORDER_FULL_RECORD_SIZE);
|
||||
if (tmpbuf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 填充待存储数据缓存
|
||||
tmpbuf[0] = RECORD_HEADER;
|
||||
memcpy(tmpbuf + 1, order_data, u16_oneRcdSize); // 充电订单记录
|
||||
|
||||
// 计算并存储CRC
|
||||
U16_T calculated_crc = u16_crc_checksum((U8_T *)order_data, u16_oneRcdSize);
|
||||
tmpbuf[u16_oneRcdSize + 1] = (U8_T)calculated_crc; // 存储CRC低字节
|
||||
|
||||
#ifdef DEBUG_FLASH_WRITE
|
||||
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] Write to addr 0x%08X (position %u, internal %u):", u32_addr, position, internal_position);
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " Header: 0x%02X", tmpbuf[0]);
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " Calculated CRC: 0x%04X -> stored low byte: 0x%02X", calculated_crc, tmpbuf[u16_oneRcdSize + 1]);
|
||||
#endif
|
||||
|
||||
// 确定存储位置(使用internal_position,范围0-N)
|
||||
SectorIndex = internal_position / OneSectorRcdCnt;
|
||||
SectorRcdIndex = internal_position % OneSectorRcdCnt;
|
||||
SectorAddr = DATAFLASH_HIS_RECORD_ADDR + SectorIndex * SPI_SECTOR_SIZE;
|
||||
|
||||
// 如果是扇区内第一条记录,需要擦除整个扇区
|
||||
if (SectorRcdIndex == 0) {
|
||||
s32_flash_dataflash_erase_sector(SectorAddr);
|
||||
|
||||
// 如果记录数超过最大限制,需要调整计数
|
||||
if (s_chgOrderMng.u16_chgOrderCnt > CHG_ORDER_MAX_CNT) {
|
||||
// 如果待存储扇区为最后一个扇区
|
||||
U16_T totalSectors = (CHG_ORDER_MAX_CNT + OneSectorRcdCnt - 1) / OneSectorRcdCnt;
|
||||
if ((SectorIndex + 1) == totalSectors) {
|
||||
CurSectorRcdCnt = CHG_ORDER_MAX_CNT % OneSectorRcdCnt;
|
||||
if (CurSectorRcdCnt == 0) {
|
||||
CurSectorRcdCnt = OneSectorRcdCnt;
|
||||
}
|
||||
} else {
|
||||
CurSectorRcdCnt = OneSectorRcdCnt;
|
||||
}
|
||||
|
||||
s_chgOrderMng.u16_chgOrderCnt = CHG_ORDER_MAX_CNT - CurSectorRcdCnt + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算具体地址并写入
|
||||
u32_addr = SectorAddr + SectorRcdIndex * CHG_ORDER_FULL_RECORD_SIZE;
|
||||
s32_flash_dataflash_write(u32_addr, tmpbuf, CHG_ORDER_FULL_RECORD_SIZE);
|
||||
|
||||
FLASH_MGR_FREE(tmpbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从Flash读充电订单记录(内部函数)
|
||||
*
|
||||
* @param order_data 充电订单数据指针(用于存储读取到的数据)
|
||||
* @param position 循环缓冲区中的位置(1-N范围)
|
||||
* @return U8_T 读取结果:0-成功,1-失败
|
||||
*/
|
||||
static U8_T v_read_chg_order_record_internal(S_LOG_DATA *order_data, U16_T position)
|
||||
{
|
||||
U16_T u16_oneRcdSize = CHG_ORDER_RECORD_SIZE; // 一条记录大小(可能大于255)
|
||||
U32_T u32_addr; // 读取地址
|
||||
U16_T SectorIndex = 0; // 扇区偏移索引
|
||||
U16_T SectorRcdIndex = 0; // 扇区内记录索引号
|
||||
U16_T OneSectorRcdCnt = CHG_ORDER_PER_SECTOR_CNT; // 一个扇区内最多存储记录数
|
||||
|
||||
// position参数范围是1-N,转换为0-N范围用于内部计算
|
||||
U16_T internal_position = position - 1;
|
||||
|
||||
U8_T *tmpbuf = (U8_T *)FLASH_MGR_MALLOC(CHG_ORDER_FULL_RECORD_SIZE);
|
||||
if (tmpbuf == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 确定读取位置(使用internal_position,范围0-N)
|
||||
SectorIndex = internal_position / OneSectorRcdCnt;
|
||||
SectorRcdIndex = internal_position % OneSectorRcdCnt;
|
||||
u32_addr = DATAFLASH_HIS_RECORD_ADDR + SectorIndex * SPI_SECTOR_SIZE +
|
||||
SectorRcdIndex * CHG_ORDER_FULL_RECORD_SIZE;
|
||||
|
||||
// 读取数据
|
||||
s32_flash_dataflash_read(u32_addr, tmpbuf, CHG_ORDER_FULL_RECORD_SIZE);
|
||||
|
||||
// 调试信息:打印读取到的数据
|
||||
#ifdef DEBUG_FLASH_READ
|
||||
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] Read from addr 0x%08X (position %u, internal %u):", u32_addr, position, internal_position);
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " Header: 0x%02X (expected: 0x%02X)", tmpbuf[0], RECORD_HEADER);
|
||||
|
||||
// 计算CRC
|
||||
U16_T calculated_crc = u16_crc_checksum(tmpbuf + 1, u16_oneRcdSize);
|
||||
U8_T stored_crc = tmpbuf[u16_oneRcdSize + 1];
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " Stored CRC: 0x%02X", stored_crc);
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " Calculated CRC: 0x%04X -> low byte: 0x%02X", calculated_crc, (U8_T)calculated_crc);
|
||||
#endif
|
||||
|
||||
// 验证数据完整性
|
||||
if ((tmpbuf[0] == RECORD_HEADER) &&
|
||||
(tmpbuf[u16_oneRcdSize + 1] == (U8_T)u16_crc_checksum(tmpbuf + 1, u16_oneRcdSize))) {
|
||||
#ifdef DEBUG_FLASH_READ
|
||||
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] v_read_chg_order_record_internal: data ok, memcpy dst=0x%08X, size=%u",
|
||||
(U32_T)order_data, u16_oneRcdSize);
|
||||
#endif
|
||||
memcpy(order_data, tmpbuf + 1, u16_oneRcdSize);
|
||||
FLASH_MGR_FREE(tmpbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FLASH_READ
|
||||
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] CRC check failed!");
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " Header match: %s", (tmpbuf[0] == RECORD_HEADER) ? "YES" : "NO");
|
||||
MYLOG_MSG(TASK_ID_Meterfee, " CRC match: %s", (tmpbuf[u16_oneRcdSize + 1] == (U8_T)u16_crc_checksum(tmpbuf + 1, u16_oneRcdSize)) ? "YES" : "NO");
|
||||
#endif
|
||||
|
||||
FLASH_MGR_FREE(tmpbuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* 公共接口函数实现
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* @brief 初始化充电订单Flash存储模块
|
||||
*/
|
||||
void v_chg_order_flash_init(void)
|
||||
{
|
||||
// 从EEPROM读取管理信息
|
||||
v_read_eeprom_chgOrderMng(&s_chgOrderMng);
|
||||
|
||||
// 如果头标志无效,初始化默认值
|
||||
if (s_chgOrderMng.u16_head != 0x5AA5)
|
||||
{
|
||||
s_chgOrderMng.u16_head = 0x5AA5;
|
||||
s_chgOrderMng.u16_chgOrderCnt = 0;
|
||||
s_chgOrderMng.u16_currIndex = RECORD_ZERO_SAVE;
|
||||
s_chgOrderMng.u32_lastIndex = 0;
|
||||
|
||||
// 写入EEPROM
|
||||
v_write_eeprom_chgOrderMng(&s_chgOrderMng);
|
||||
}
|
||||
|
||||
// MYLOG_MSG(TASK_ID_Meterfee, "OrderMng: init ok, cnt=%u",
|
||||
// s_chgOrderMng.u16_chgOrderCnt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有充电订单记录
|
||||
*/
|
||||
void v_chg_order_clear_all(void)
|
||||
{
|
||||
// 重置管理信息
|
||||
s_chgOrderMng.u16_head = 0x5AA5;
|
||||
s_chgOrderMng.u16_chgOrderCnt = 0;
|
||||
s_chgOrderMng.u16_currIndex = RECORD_ZERO_SAVE;
|
||||
s_chgOrderMng.u32_lastIndex = 0;
|
||||
|
||||
// 写入EEPROM
|
||||
v_write_eeprom_chgOrderMng(&s_chgOrderMng);
|
||||
|
||||
// 注意:这里不清除Flash中的实际数据,因为Flash擦除成本高
|
||||
// 实际数据会在后续写入时被覆盖
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存充电订单记录到Flash
|
||||
*
|
||||
* 新设计:索引号直接对应Flash物理位置
|
||||
* 索引号 = u32_lastIndex + 1
|
||||
* 存储位置 = (u32_lastIndex % CHG_ORDER_MAX_CNT)
|
||||
* 注意:此函数会修改order_data中的u32_index字段
|
||||
*/
|
||||
U32_T u32_chg_order_save_record(S_LOG_DATA *order_data)
|
||||
{
|
||||
U32_T assignedIndex;
|
||||
|
||||
// 分配唯一递增索引
|
||||
s_chgOrderMng.u32_lastIndex++;
|
||||
assignedIndex = s_chgOrderMng.u32_lastIndex;
|
||||
|
||||
// 计算存储位置(基于索引号的循环缓冲区)
|
||||
U16_T storagePosition = (s_chgOrderMng.u32_lastIndex - 1) % CHG_ORDER_MAX_CNT;
|
||||
|
||||
// 更新当前索引位置(存储0-N范围)
|
||||
s_chgOrderMng.u16_currIndex = storagePosition;
|
||||
|
||||
// 记录条数递增(不超过最大限制)
|
||||
if (s_chgOrderMng.u16_chgOrderCnt < CHG_ORDER_MAX_CNT) {
|
||||
s_chgOrderMng.u16_chgOrderCnt++;
|
||||
}
|
||||
|
||||
// 直接修改传入的订单数据,设置索引
|
||||
order_data->u32_index = assignedIndex;
|
||||
|
||||
// 写入Flash记录,传递1-N范围的position
|
||||
v_write_chg_order_record(order_data, storagePosition + 1);
|
||||
|
||||
// 更新管理信息到EEPROM
|
||||
v_write_eeprom_chgOrderMng(&s_chgOrderMng);
|
||||
|
||||
return assignedIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从Flash读取指定索引的充电订单记录
|
||||
*
|
||||
* 新设计:根据索引号直接计算存储位置,无需遍历查找
|
||||
* 存储位置 = (index - 1) % CHG_ORDER_MAX_CNT
|
||||
*/
|
||||
U8_T v_read_chg_order_record(S_LOG_DATA *order_data, U32_T index)
|
||||
{
|
||||
// 检查索引号有效性
|
||||
if (index == 0 || index > s_chgOrderMng.u32_lastIndex) {
|
||||
return 1; // 索引号无效
|
||||
}
|
||||
|
||||
// 如果没有记录,直接返回失败
|
||||
if (s_chgOrderMng.u16_chgOrderCnt == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// 计算存储位置(基于索引号的循环缓冲区)
|
||||
U16_T storagePosition = (index - 1) % CHG_ORDER_MAX_CNT;
|
||||
|
||||
// 直接读取到传入的缓冲区,传递1-N范围的position
|
||||
if (v_read_chg_order_record_internal(order_data, storagePosition + 1) == 0) {
|
||||
// 检查索引是否匹配(防止数据被覆盖)
|
||||
if (order_data->u32_index == index) {
|
||||
return 0; // 成功
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // 读取失败或索引不匹配
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取指定位置的充电订单记录(基于循环缓冲区位置)
|
||||
*/
|
||||
U8_T v_read_chg_order_by_position(S_LOG_DATA *order_data, U16_T position)
|
||||
{
|
||||
// 检查位置是否有效
|
||||
if (position >= s_chgOrderMng.u16_chgOrderCnt) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 计算实际Flash位置(考虑循环缓冲区)
|
||||
U16_T actual_position;
|
||||
if (s_chgOrderMng.u16_chgOrderCnt < CHG_ORDER_MAX_CNT) {
|
||||
// 缓冲区未满,直接按顺序读取
|
||||
actual_position = position;
|
||||
} else {
|
||||
// 缓冲区已满,需要计算循环位置
|
||||
actual_position = (s_chgOrderMng.u16_currIndex + 1 + position) % CHG_ORDER_MAX_CNT;
|
||||
}
|
||||
|
||||
// 传递1-N范围的position给内部函数
|
||||
return v_read_chg_order_record_internal(order_data, actual_position + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取充电订单记录总数
|
||||
*/
|
||||
U16_T v_get_chg_order_count(void)
|
||||
{
|
||||
return s_chgOrderMng.u16_chgOrderCnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前索引位置
|
||||
*/
|
||||
U16_T v_get_chg_order_curr_index(void)
|
||||
{
|
||||
return s_chgOrderMng.u16_currIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最后分配的索引值
|
||||
*/
|
||||
U32_T v_get_chg_order_last_index(void)
|
||||
{
|
||||
return s_chgOrderMng.u32_lastIndex;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* EEPROM临时充电记录存储函数
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* @brief 获取指定枪号的EEPROM地址
|
||||
*/
|
||||
static U32_T v_get_eeprom_addr_for_gun(U8_T gunNo)
|
||||
{
|
||||
if (gunNo == 0) {
|
||||
return EEPROM_ADDR_A_LOG_DATA;
|
||||
} else {
|
||||
return EEPROM_ADDR_B_LOG_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存临时充电记录到EEPROM(用于断电续传)
|
||||
*/
|
||||
U8_T v_save_temp_chg_record_to_eeprom(U8_T gunNo, const S_LOG_DATA *order_data, E_TEMP_CHG_FLAG flag)
|
||||
{
|
||||
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
|
||||
U8_T *buffer = NULL;
|
||||
U8_T ret = 1;
|
||||
|
||||
// 检查参数有效性
|
||||
if (order_data == NULL || flag >= TEMP_CHG_FLAG_INVALID) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 动态分配内存
|
||||
buffer = (U8_T *)FLASH_MGR_MALLOC(TEMP_CHG_RECORD_SIZE + 1);
|
||||
if (buffer == NULL) {
|
||||
return 1; // 内存分配失败
|
||||
}
|
||||
|
||||
// 构建缓冲区:标志位 + 订单数据
|
||||
buffer[0] = (U8_T)flag;
|
||||
memcpy(buffer + 1, order_data, TEMP_CHG_RECORD_SIZE);
|
||||
|
||||
// 写入EEPROM,eeprom_write返回HAL_OK(0)表示成功
|
||||
if (eeprom_write(eeprom_addr, buffer, TEMP_CHG_RECORD_SIZE + 1) == HAL_OK) {
|
||||
ret = 0; // 成功
|
||||
}
|
||||
|
||||
// 释放内存
|
||||
FLASH_MGR_FREE(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从EEPROM读取临时充电记录(用于断电续传恢复)
|
||||
*/
|
||||
U8_T v_read_temp_chg_record_from_eeprom(U8_T gunNo, S_LOG_DATA *order_data, E_TEMP_CHG_FLAG *flag)
|
||||
{
|
||||
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
|
||||
U8_T *buffer = NULL;
|
||||
|
||||
// 检查参数有效性
|
||||
if (order_data == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 动态分配内存
|
||||
buffer = (U8_T *)FLASH_MGR_MALLOC(TEMP_CHG_RECORD_SIZE + 1);
|
||||
if (buffer == NULL) {
|
||||
return 1; // 内存分配失败
|
||||
}
|
||||
|
||||
// 从EEPROM读取,eeprom_read返回HAL_OK(0)表示成功
|
||||
if (eeprom_read(eeprom_addr, buffer, TEMP_CHG_RECORD_SIZE + 1) != HAL_OK) {
|
||||
FLASH_MGR_FREE(buffer);
|
||||
return 1; // 读取失败
|
||||
}
|
||||
|
||||
// 检查标志位有效性
|
||||
U8_T read_flag = buffer[0];
|
||||
if (read_flag >= TEMP_CHG_FLAG_INVALID) {
|
||||
FLASH_MGR_FREE(buffer);
|
||||
return 1; // 无效标志位
|
||||
}
|
||||
|
||||
// 返回标志位(如果提供了指针)
|
||||
if (flag != NULL) {
|
||||
*flag = (E_TEMP_CHG_FLAG)read_flag;
|
||||
}
|
||||
|
||||
// 复制订单数据
|
||||
memcpy(order_data, buffer + 1, TEMP_CHG_RECORD_SIZE);
|
||||
|
||||
// 释放内存
|
||||
FLASH_MGR_FREE(buffer);
|
||||
return 0; // 成功
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除EEPROM中的临时充电记录
|
||||
*/
|
||||
U8_T v_clear_temp_chg_record_in_eeprom(U8_T gunNo)
|
||||
{
|
||||
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
|
||||
U8_T invalid_flag = TEMP_CHG_FLAG_INVALID;
|
||||
|
||||
// 只写入无效标志位,不清除整个区域(节省EEPROM写寿命)
|
||||
// eeprom_write返回HAL_OK(0)表示成功
|
||||
if (eeprom_write(eeprom_addr, &invalid_flag, 1) == HAL_OK) {
|
||||
return 0; // 成功
|
||||
}
|
||||
|
||||
return 1; // 失败
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查EEPROM中是否有有效的临时充电记录
|
||||
*/
|
||||
U8_T v_check_temp_chg_record_valid(U8_T gunNo)
|
||||
{
|
||||
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
|
||||
U8_T flag_byte;
|
||||
|
||||
// 读取标志位,eeprom_read返回HAL_OK(0)表示成功
|
||||
if (eeprom_read(eeprom_addr, &flag_byte, 1) != HAL_OK) {
|
||||
return 0; // 读取失败,视为无效
|
||||
}
|
||||
|
||||
// 检查标志位是否有效
|
||||
if (flag_byte == TEMP_CHG_FLAG_UNSETTLED || flag_byte == TEMP_CHG_FLAG_SETTLED) {
|
||||
return 1; // 有有效记录
|
||||
}
|
||||
|
||||
return 0; // 无效记录
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* @file meter_calculate_flash_impl.h
|
||||
* @brief 充电订单 / 临时订单 EEPROM+SPI Flash 存储接口声明
|
||||
*
|
||||
* 与 `meter_calculate_flash_impl.c` 配套:管理元数据在 EEPROM,订单记录在 `DATAFLASH_HIS_RECORD_ADDR`
|
||||
* 区域;并提供双枪临时订单 EEPROM 备份与恢复接口。具体地址与记录格式见 `public_define.h`、`flash_external_data.h`。
|
||||
*/
|
||||
|
||||
#ifndef METER_CALCULATE_FLASH_IMPL_H
|
||||
#define METER_CALCULATE_FLASH_IMPL_H
|
||||
|
||||
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*------ Exported includes(头文件) ----------------------*/
|
||||
#include "main.h"
|
||||
#include "meter_calculate/meter_calculate_impl.h" /* S_LOG_DATA 等 */
|
||||
#include "eeprom/fm24cl16.h"
|
||||
#include "externalflash/flash_external_data.h"
|
||||
|
||||
/*------ Exported macro(宏定义) -------------------------*/
|
||||
|
||||
// 调试宏定义
|
||||
// #define DEBUG_FLASH_READ 0 // 启用Flash读取调试
|
||||
// #define DEBUG_FLASH_WRITE 0 // 启用Flash写入调试
|
||||
|
||||
/* 充电订单记录容量相关宏(CHG_ORDER_* / TEMP_CHG_RECORD_SIZE)
|
||||
* 统一由 `BSP/spi_Flash/flash_external_data.h` 提供,避免多处重复定义。
|
||||
*/
|
||||
|
||||
// 断电续传标志位定义
|
||||
typedef enum
|
||||
{
|
||||
TEMP_CHG_FLAG_UNSETTLED = 0x00, // 未结算状态(充电中)
|
||||
TEMP_CHG_FLAG_SETTLED = 0x01, // 已结算状态(充电完成)
|
||||
TEMP_CHG_FLAG_INVALID = 0xFF // 无效状态
|
||||
} E_TEMP_CHG_FLAG;
|
||||
|
||||
/*------ Exported struct(结构体定义) --------------------*/
|
||||
|
||||
/**
|
||||
* @brief 充电订单管理信息结构体
|
||||
*
|
||||
* 存储在EEPROM中,地址:EEPROM_ADDR_CHGRCD_MNG (0x0000)
|
||||
* 用于管理充电订单的索引和计数信息
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
U16_T u16_head; // 头标志 (0x5AA5)
|
||||
U16_T u16_chgOrderCnt; // 充电订单总条数
|
||||
U16_T u16_currIndex; // 当前索引(循环缓冲区中的位置)
|
||||
U32_T u32_lastIndex; // 最后分配的索引值(保证索引唯一性递增)
|
||||
} S_CHG_ORDER_MNG;
|
||||
|
||||
/*------ Exported variables(变量定义) -------------------*/
|
||||
|
||||
// 充电订单管理信息全局变量
|
||||
extern S_CHG_ORDER_MNG s_chgOrderMng;
|
||||
|
||||
/*------ Exported function prototypes (函数声明) -------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化充电订单Flash存储模块
|
||||
*
|
||||
* 从EEPROM读取管理信息,如果不存在则初始化默认值
|
||||
*/
|
||||
void v_chg_order_flash_init(void);
|
||||
|
||||
/**
|
||||
* @brief 写入充电订单管理信息到EEPROM
|
||||
*
|
||||
* @param chgOrderMng 充电订单管理信息指针
|
||||
*/
|
||||
void v_write_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng);
|
||||
|
||||
/**
|
||||
* @brief 从EEPROM读取充电订单管理信息
|
||||
*
|
||||
* @param chgOrderMng 充电订单管理信息指针(用于存储读取到的数据)
|
||||
*/
|
||||
void v_read_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng);
|
||||
|
||||
/**
|
||||
* @brief 清除所有充电订单记录
|
||||
*
|
||||
* 重置管理信息并清除Flash中的记录
|
||||
*/
|
||||
void v_chg_order_clear_all(void);
|
||||
|
||||
/**
|
||||
* @brief 保存充电订单记录到Flash
|
||||
*
|
||||
* 自动分配索引并保存记录,更新管理信息
|
||||
* 注意:此函数会修改order_data中的u32_index字段
|
||||
*
|
||||
* @param order_data 充电订单数据指针(会被修改)
|
||||
* @return U32_T 分配的索引值(唯一标识)
|
||||
*/
|
||||
U32_T u32_chg_order_save_record(S_LOG_DATA *order_data);
|
||||
|
||||
/**
|
||||
* @brief 从Flash读取指定索引的充电订单记录
|
||||
*
|
||||
* @param order_data 充电订单数据指针(用于存储读取到的数据)
|
||||
* @param index 要读取的记录索引
|
||||
* @return U8_T 读取结果:0-成功,1-失败
|
||||
*/
|
||||
U8_T v_read_chg_order_record(S_LOG_DATA *order_data, U32_T index);
|
||||
|
||||
/**
|
||||
* @brief 获取充电订单记录总数
|
||||
*
|
||||
* @return U16_T 充电订单记录总数
|
||||
*/
|
||||
U16_T v_get_chg_order_count(void);
|
||||
|
||||
/**
|
||||
* @brief 获取当前索引位置
|
||||
*
|
||||
* @return U16_T 当前索引位置
|
||||
*/
|
||||
U16_T v_get_chg_order_curr_index(void);
|
||||
|
||||
/**
|
||||
* @brief 获取最后分配的索引值
|
||||
*
|
||||
* @return U32_T 最后分配的索引值(唯一递增)
|
||||
*/
|
||||
U32_T v_get_chg_order_last_index(void);
|
||||
|
||||
/**
|
||||
* @brief 读取指定位置的充电订单记录(基于循环缓冲区位置)
|
||||
*
|
||||
* @param order_data 充电订单数据指针(用于存储读取到的数据)
|
||||
* @param position 循环缓冲区中的位置(0-based)
|
||||
* @return U8_T 读取结果:0-成功,1-失败
|
||||
*/
|
||||
U8_T v_read_chg_order_by_position(S_LOG_DATA *order_data, U16_T position);
|
||||
|
||||
/**
|
||||
* @brief 保存临时充电记录到EEPROM(用于断电续传)
|
||||
*
|
||||
* 在充电过程中周期更新实时充电信息,第一个字节为断电续传标志位
|
||||
*
|
||||
* @param gunNo 枪号(0-A枪,1-B枪)
|
||||
* @param order_data 充电订单数据指针
|
||||
* @param flag 断电续传标志位(0-未结算,1-已结算)
|
||||
* @return U8_T 保存结果:0-成功,1-失败
|
||||
*/
|
||||
U8_T v_save_temp_chg_record_to_eeprom(U8_T gunNo, const S_LOG_DATA *order_data, E_TEMP_CHG_FLAG flag);
|
||||
|
||||
/**
|
||||
* @brief 从EEPROM读取临时充电记录(用于断电续传恢复)
|
||||
*
|
||||
* @param gunNo 枪号(0-A枪,1-B枪)
|
||||
* @param order_data 充电订单数据指针(用于存储读取到的数据)
|
||||
* @param flag 读取到的断电续传标志位指针(可选)
|
||||
* @return U8_T 读取结果:0-成功,1-失败
|
||||
*/
|
||||
U8_T v_read_temp_chg_record_from_eeprom(U8_T gunNo, S_LOG_DATA *order_data, E_TEMP_CHG_FLAG *flag);
|
||||
|
||||
/**
|
||||
* @brief 清除EEPROM中的临时充电记录
|
||||
*
|
||||
* 将断电续传标志位设置为无效状态
|
||||
*
|
||||
* @param gunNo 枪号(0-A枪,1-B枪)
|
||||
* @return U8_T 清除结果:0-成功,1-失败
|
||||
*/
|
||||
U8_T v_clear_temp_chg_record_in_eeprom(U8_T gunNo);
|
||||
|
||||
/**
|
||||
* @brief 检查EEPROM中是否有有效的临时充电记录
|
||||
*
|
||||
* @param gunNo 枪号(0-A枪,1-B枪)
|
||||
* @return U8_T 检查结果:0-无有效记录,1-有有效记录
|
||||
*/
|
||||
U8_T v_check_temp_chg_record_valid(U8_T gunNo);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* METER_CALCULATE_FLASH_IMPL_H */
|
||||
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* @file ocpp_mv_offline_flash_impl.h
|
||||
* @brief OCPP 离线 MeterValues:仿 `fault_flash_impl` — SPI Flash 线性池 + FRAM 流水号→序号映射
|
||||
*
|
||||
* **为何更简单**
|
||||
* - Flash 侧为「定长记录数组」:每条在 **该枪半区** 内逻辑序号与物理槽一一对应(`seq` → 地址由宏公式算出)。
|
||||
* - FRAM 侧存:**每枪下一单调序号** `u32_next_seq[2]`、以及若干行「流水号(tx)+枪号 ↔ 起始序号 + 条数」;
|
||||
* 上送时 `u8_ocpp_mv_offline_map_get(gun, tx...)` 得 `seq_start,count`,再
|
||||
* `for (k=0;k<count;k++) u8_ocpp_mv_offline_mv_read_by_seq(gun, seq_start+k, &buf)`。
|
||||
*
|
||||
* **Flash 分区(双枪互不穿插)**
|
||||
* - 区间 `0x2C0000`~`0x2CFFFF`(64KB)见 `flash_external_data.h`:**前半 32KB 仅枪0、后半 32KB 仅枪1**,
|
||||
* 保证连续 Flash 地址均为同一把枪的环形池;双枪同时充电时不会在物理上交错两条订单的 MV。
|
||||
*
|
||||
* **Flash 格式(与故障记录一致思路)**
|
||||
* - 每条:`RECORD_HEADER`(1B) + `S_OCPP_MV_OFFLINE_MV`(32B) + CRC8低字节(1B),总长 `OCPP_MV_OFFLINE_FRAME_BYTES`。
|
||||
* - 扇区内可容纳条数 = `SPI_SECTOR_SIZE / OCPP_MV_OFFLINE_FRAME_BYTES`(向下取整,尾部截断不用)。
|
||||
* - **每枪**最大条数 = `OCPP_MV_OFFLINE_POOL_MAX_PER_GUN`(每枪 8 扇区 × 每扇区条数)。
|
||||
*
|
||||
* **环形滚动(按枪)**
|
||||
* - 每枪单调序号 `u32_next_seq[gun]`(存 FRAM):`seq = next_seq[gun]++`;物理槽 `idx = seq % POOL_MAX_PER_GUN`(仅在该枪半区内取模)。
|
||||
* - 池满后自然覆盖该枪最旧物理槽;上送完成后应 `u8_ocpp_mv_offline_map_remove(gun, tx...)` 删除对应行。
|
||||
*/
|
||||
|
||||
#ifndef OCPP_MV_OFFLINE_FLASH_IMPL_H
|
||||
#define OCPP_MV_OFFLINE_FLASH_IMPL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include "externalflash/flash_external_data.h"
|
||||
#include "eeprom/fm24cl16.h"
|
||||
|
||||
/** @brief 单包 MeterValues 负载长度(字节),与结构体 `S_OCPP_MV_OFFLINE_MV` 一致(与 BS_ocpp_ctrl 已实现 measurand 对齐) */
|
||||
#define OCPP_MV_OFFLINE_PAYLOAD_BYTES 32u
|
||||
|
||||
/** @brief 单条落盘帧长:帧头 + 负载 + CRC1(与 `fault_flash_impl` 相同思路) */
|
||||
#define OCPP_MV_OFFLINE_FRAME_BYTES (1u + OCPP_MV_OFFLINE_PAYLOAD_BYTES + 1u)
|
||||
|
||||
/** @brief 每个物理扇区内可容纳的 MV 条数(4096 / FRAME,向下取整,尾部截断) */
|
||||
#define OCPP_MV_OFFLINE_RECORDS_PER_SECTOR (SPI_SECTOR_SIZE / OCPP_MV_OFFLINE_FRAME_BYTES)
|
||||
|
||||
/** @brief 枪号:0 = 前半区(枪1),1 = 后半区(枪2) */
|
||||
#define OCPP_MV_OFFLINE_GUN_CNT 2u
|
||||
|
||||
/** @brief 每个枪在 Flash 内占用的连续扇区数(半区 32KB / 4096) */
|
||||
#define OCPP_MV_OFFLINE_SECTORS_PER_GUN (DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE / SPI_SECTOR_SIZE)
|
||||
|
||||
/** @brief 单枪环形池最大条数(每枪 8 扇区 × 每扇区条数) */
|
||||
#define OCPP_MV_OFFLINE_POOL_MAX_PER_GUN (OCPP_MV_OFFLINE_RECORDS_PER_SECTOR * OCPP_MV_OFFLINE_SECTORS_PER_GUN)
|
||||
|
||||
/** @brief FRAM 中可同时保存的「流水号→序号区间」映射行数(受 128B 控制块限制) */
|
||||
#define OCPP_MV_OFFLINE_MAP_ROWS 6u
|
||||
|
||||
/** @brief 负载内魔数(小端可视 OCMV) */
|
||||
#define OCPP_MV_OFFLINE_FLASH_MAGIC 0x564D434Fu
|
||||
|
||||
/** @brief 负载格式版本(32B 布局为 4;与旧 64B 不兼容,升级后建议整池擦除) */
|
||||
#define OCPP_MV_OFFLINE_FLASH_VER 4u
|
||||
|
||||
/** @brief FRAM 管理块头 */
|
||||
#define OCPP_MV_OFFLINE_FRAM_HEAD 0x5AA9u
|
||||
|
||||
/** @brief FRAM 管理块布局版本(变更字段时递增;5:每枪 next_seq + 映射行含枪号) */
|
||||
#define OCPP_MV_OFFLINE_FRAM_MNG_VER 5u
|
||||
|
||||
/**
|
||||
* @brief 一条待补发的 MeterValues「测量快照」负载(固定 32B)
|
||||
*
|
||||
* 字段与 `BS_ocpp_ctrl.c` 中 `u8_get_ocpp_measurand_data` **已实现** 的 measurand 对齐,便于离线存盘与联网后按同口径组 SampledValue:
|
||||
* - `ENUM_EnergyActiveImportRegister`:累计 Wh(源码为 u64/10,此处存 u32,超高 Wh 截断)
|
||||
* - `ENUM_EnergyActiveImportInterval`:事务段增量 Wh → `u32_energy_interval_wh`
|
||||
* - `ENUM_Voltage` / `ENUM_CurrentImport`:电表电压 0.1V、电流 0.01A(原 0.1A×10)
|
||||
* - `ENUM_PowerActiveImport`:有功功率 W(饱和 65535,极大桩可再约定用比例)
|
||||
* - `ENUM_PowerOffered`:额定功率,单位 **0.1 kW**(与 `E_BS_GET_SYS_DATA_RATED_POWER` 一致)
|
||||
* - `ENUM_CurrentOffered`:最大电流 0.01A
|
||||
* - `ENUM_SoC` + `u8_soc_data_type`:SOC% 与 0当前/1起始/2结束(与函数入参 `u8_data_type` 一致)
|
||||
*
|
||||
* 流水号 **tx** 不在本结构重复保存,以 FRAM 映射 `aMap[]` 为准。
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
U32_T u32_magic; /**< 须为 OCPP_MV_OFFLINE_FLASH_MAGIC */
|
||||
U32_T u32_timestamp_unix; /**< 取样时刻日历秒:`xDate2Seconds(GetCurrentTime)`,与补发 JSON 中 timestamp 一致(勿存上电单调秒) */
|
||||
U32_T u32_meter_import_wh; /**< Energy.Active.Import.Register:累计 Wh(u64/10 截断 u32) */
|
||||
U32_T u32_energy_interval_wh; /**< Energy.Active.Import.Interval:事务段增量 Wh */
|
||||
U16_T u16_voltage_v_x10; /**< Voltage:输出电压 0.1V */
|
||||
U16_T u16_current_import_a_x100; /**< Current.Import:电流 0.01A */
|
||||
U16_T u16_power_active_w; /**< Power.Active.Import:功率 W(饱和) */
|
||||
U16_T u16_power_offered_01kw; /**< Power.Offered:额定功率 0.1kW 单位 */
|
||||
U16_T u16_current_offered_a_x100; /**< Current.Offered:最大电流 0.01A */
|
||||
U8_T u8_soc; /**< SoC:0~100 */
|
||||
U8_T u8_soc_data_type; /**< SoC 上下文:0 当前 / 1 Transaction.Begin / 2 Transaction.End */
|
||||
U8_T u8_connector_id; /**< 连接器号 */
|
||||
U8_T u8_evse_id; /**< EVSE/枪逻辑号 */
|
||||
U16_T u16_version; /**< 须为 OCPP_MV_OFFLINE_FLASH_VER */
|
||||
} S_OCPP_MV_OFFLINE_MV;
|
||||
|
||||
/**
|
||||
* @brief FRAM 中一行:会话键(当前实现为开始充电秒)↔ 在 Flash 池中的 **连续序号区间**
|
||||
*
|
||||
* 第 k 条(0<=k<count)在该枪下的单调序号为 `u32_seq_start + k`,读 Flash 时用
|
||||
* `idx = (u32_seq_start + k) % OCPP_MV_OFFLINE_POOL_MAX_PER_GUN`(与本模块 `mv_read_by_seq` 一致)。
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
U32_T u32_tx_sn_lo; /**< 当前:`xDate2Seconds(StartChargeTime)`;字段名历史兼容 */
|
||||
U32_T u32_tx_sn_hi; /**< 当前约定恒 0(保留);非 64 位键的高半字 */
|
||||
U32_T u32_seq_start; /**< 该流水号在该枪下第一条 MV 的单调序号 */
|
||||
U16_T u16_count; /**< 连续条数 */
|
||||
U8_T u8_gun_no; /**< 0=前半区枪1,1=后半区枪2(与 `u8_ocpp_mv_offline_mv_save` 入参一致) */
|
||||
U8_T u8_reserved; /**< 预留 */
|
||||
} S_OCPP_MV_OFFLINE_MAP_ROW;
|
||||
|
||||
/**
|
||||
* @brief FRAM 管理块(128B,`EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL`)
|
||||
*
|
||||
* CRC:整结构 `u16_crc` 置 0 后按 `u16_calculate_crc` 计算(与未结算模块相同)。
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
U16_T u16_head; /**< OCPP_MV_OFFLINE_FRAM_HEAD */
|
||||
U16_T u16_version; /**< OCPP_MV_OFFLINE_FRAM_MNG_VER */
|
||||
U16_T u16_crc; /**< CRC-16 */
|
||||
U8_T u8_flags; /**< 预留标志位 */
|
||||
U8_T u8_reserved1; /**< 预留 */
|
||||
U32_T u32_next_seq[OCPP_MV_OFFLINE_GUN_CNT]; /**< 每枪下一条将分配的单调序号(掉电连续) */
|
||||
S_OCPP_MV_OFFLINE_MAP_ROW aMap[OCPP_MV_OFFLINE_MAP_ROWS]; /**< 会话键+枪→序号区间 */
|
||||
U8_T u8_reserved2[16]; /**< 补齐至 128 字节 */
|
||||
} S_OCPP_MV_OFFLINE_FRAM_MNG;
|
||||
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_MV) == OCPP_MV_OFFLINE_PAYLOAD_BYTES, "MV payload 32B");
|
||||
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) == 128u, "FRAM mng 128B");
|
||||
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) <= EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE, "FRAM fit");
|
||||
#else
|
||||
/* ARMCC v5 不支持 _Static_assert,使用编译期断言替代 */
|
||||
typedef int __ocpp_mv_size_check_1[(sizeof(S_OCPP_MV_OFFLINE_MV) == OCPP_MV_OFFLINE_PAYLOAD_BYTES) ? 1 : -1];
|
||||
typedef int __ocpp_mv_size_check_2[(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) == 128u) ? 1 : -1];
|
||||
typedef int __ocpp_mv_size_check_3[(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) <= EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE) ? 1 : -1];
|
||||
#endif
|
||||
|
||||
/** @brief 上电:从 FRAM 恢复管理块 RAM 镜像,非法则默认并写回 */
|
||||
void v_ocpp_mv_offline_init(void);
|
||||
|
||||
/**
|
||||
* @brief 保存一条离线 MV:分配该枪单调序号、写 Flash、更新/追加映射行
|
||||
* @param u8_gun_no 枪号:0=前半区(枪1),1=后半区(枪2)
|
||||
* @param tx_sn_lo 会话键:当前为 `xDate2Seconds(StartChargeTime)`(单路 U32)
|
||||
* @param tx_sn_hi 当前恒 0(与 `u32_tx_sn_hi` 一致;接口双参仅为与 FRAM 行字段对齐)
|
||||
* @param out_seq 输出本次在该枪下的单调序号
|
||||
* @return 0 成功;1 参数非法(含枪号越界);2 映射表满;3 Flash 失败;4 FRAM 失败;
|
||||
* 5 流水号已存在但本次序号不连续(调用顺序错误)
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief 按该枪单调序号读一条 MV(物理槽:`seq % POOL_MAX_PER_GUN`,落在该枪半区)
|
||||
* @return 0 成功;1 参数非法;2 校验/帧头失败
|
||||
*/
|
||||
U8_T u8_ocpp_mv_offline_mv_read_by_seq(U8_T u8_gun_no, U32_T seq, S_OCPP_MV_OFFLINE_MV *out);
|
||||
|
||||
/** @brief 判断负载是否为当前版本有效数据 */
|
||||
U8_T u8_ocpp_mv_offline_mv_is_valid(const S_OCPP_MV_OFFLINE_MV *mv);
|
||||
|
||||
/**
|
||||
* @brief 查询指定枪上会话键(`tx_sn_lo` + `tx_sn_hi`,当前 hi 恒 0)对应的序号区间(用于上送循环)
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief 上送完成后删除该枪上映射行(不擦整池 Flash;若需整池清空在 `ocpp_mv_offline_flash_impl.c` 内维护调用)
|
||||
* @return 0 成功;1 未找到;2 FRAM 写失败
|
||||
*/
|
||||
U8_T u8_ocpp_mv_offline_map_remove(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi);
|
||||
|
||||
/**
|
||||
* @brief 新一笔充电开始前:清除该枪在 FRAM 中的全部 MV 映射行(释放 map 槽位;不擦 SPI Flash 池)
|
||||
* @param[in] u8_gun_no 枪索引
|
||||
* @return 0 成功;1 枪号非法;2 FRAM 写失败
|
||||
* @see u8_ocpp_mv_offline_mv_save(映射满时按枪淘汰最旧一行)
|
||||
*/
|
||||
U8_T u8_ocpp_mv_offline_map_clear_gun(U8_T u8_gun_no);
|
||||
|
||||
/* 整池擦除、FRAM 读写默认块、池容量查询等为模块内部实现,无对外声明。 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OCPP_MV_OFFLINE_FLASH_IMPL_H */
|
||||
@@ -0,0 +1,441 @@
|
||||
/**
|
||||
* @file unsettled_order_mng.c
|
||||
* @brief 未结算订单列表:EEPROM 持久化与 CRC 校验
|
||||
*
|
||||
* 功能说明:
|
||||
* - 在 EEPROM `EEPROM_ADDR_UNSETTLED_MNG` 保存 `S_UNSETTLED_ORDER_MNG`:头标志 `UNSETTLED_HEADER`、
|
||||
* 未结条数、以及除 CRC 字段外的 CRC-16 校验(与 `u16_calculate_crc` 一致)。
|
||||
* - 每条未结订单仅保存其 Flash 订单号(`u32_chg_order_save_record` 返回的 `u32_index`)的 24 位压缩形式,
|
||||
* 最多 `UNSETTLED_MAX_CNT` 条;列表按顺序追加,删除时前移尾部元素。
|
||||
* - `u8_check_order_settled`:返回 1 表示该索引仍在未结列表中(未结算),0 表示不在列表中(视为已结或从未登记)。
|
||||
* 命名与注释以头文件为准。
|
||||
*/
|
||||
|
||||
#include "unsettled_order_mng.h"
|
||||
#include "mylog/mylog.h"
|
||||
|
||||
/* 本文件打印使能:0=不编译打印,1=编译打印 */
|
||||
#ifndef UNSETTLED_MNG_LOG_EN
|
||||
#define UNSETTLED_MNG_LOG_EN (1)
|
||||
#endif
|
||||
|
||||
#if UNSETTLED_MNG_LOG_EN
|
||||
#define UNSETTLED_MNG_LOG(fmt, ...) MYLOG_MSG(TASK_ID_Meterfee, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define UNSETTLED_MNG_LOG(fmt, ...) ((void)0)
|
||||
#endif
|
||||
|
||||
/*------ Local variables(局部变量) ----------------------*/
|
||||
|
||||
// 未结算订单管理信息全局变量
|
||||
S_UNSETTLED_ORDER_MNG s_unsettledOrderMng;
|
||||
|
||||
/*------ Local function prototypes(局部函数声明) ---------*/
|
||||
|
||||
static void v_write_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng);
|
||||
static void v_read_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng);
|
||||
static U16_T u16_calculate_unsettled_crc(S_UNSETTLED_ORDER_MNG *unsettledMng);
|
||||
static U8_T u8_find_order_index(U32_T orderIndex, U16_T *position);
|
||||
static void v_uint32_to_3bytes(U32_T value, U8_T *bytes);
|
||||
static U32_T u32_3bytes_to_uint32(const U8_T *bytes);
|
||||
|
||||
/*------ Exported functions(导出函数实现) ----------------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化未结算订单管理模块
|
||||
*/
|
||||
void v_unsettled_order_init(void)
|
||||
{
|
||||
// 读取EEPROM中的管理信息
|
||||
v_read_eeprom_unsettledMng(&s_unsettledOrderMng);
|
||||
|
||||
// 检查数据完整性
|
||||
if (s_unsettledOrderMng.u16_head != UNSETTLED_HEADER)
|
||||
{
|
||||
// 头标志错误,初始化默认值
|
||||
s_unsettledOrderMng.u16_head = UNSETTLED_HEADER;
|
||||
s_unsettledOrderMng.u16_unsettledCnt = 0;
|
||||
|
||||
// 清空索引数组(3字节数组)
|
||||
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
|
||||
{
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = 0;
|
||||
}
|
||||
|
||||
// 计算并保存CRC
|
||||
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
|
||||
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
|
||||
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: init default");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 验证CRC
|
||||
U16_T calculatedCrc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
|
||||
if (calculatedCrc != s_unsettledOrderMng.u16_crc)
|
||||
{
|
||||
// CRC校验失败,恢复默认值
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: CRC fail, reset default");
|
||||
s_unsettledOrderMng.u16_unsettledCnt = 0;
|
||||
|
||||
// 清空索引数组(3字节数组)
|
||||
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
|
||||
{
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = 0;
|
||||
}
|
||||
|
||||
// 重新计算CRC并保存
|
||||
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
|
||||
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if 0 // 测试读取
|
||||
s_unsettledOrderMng.u16_unsettledCnt = 1;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[0][0] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[0][1] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[0][2] = 1;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[1][0] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[1][1] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[1][2] = 0;
|
||||
#endif
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: init ok, cnt=%u",
|
||||
s_unsettledOrderMng.u16_unsettledCnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加未结算订单索引
|
||||
*/
|
||||
U8_T u8_add_unsettled_order(U32_T orderIndex)
|
||||
{
|
||||
// 检查订单索引是否有效
|
||||
if (orderIndex == 0)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: invalid index");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 检查索引是否超过3字节范围(最大16,777,215)
|
||||
if (orderIndex > 0xFFFFFF)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: index >24b, idx=%u", orderIndex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
U16_T position;
|
||||
if (u8_find_order_index(orderIndex, &position) == 0)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: index exists, idx=%u", orderIndex);
|
||||
return 0; // 已存在,不算失败
|
||||
}
|
||||
|
||||
// 检查列表是否已满
|
||||
if (s_unsettledOrderMng.u16_unsettledCnt >= UNSETTLED_MAX_CNT)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: list full, add reject");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 添加到列表末尾(使用3字节存储)
|
||||
v_uint32_to_3bytes(orderIndex, s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt]);
|
||||
s_unsettledOrderMng.u16_unsettledCnt++;
|
||||
|
||||
// 更新CRC并保存到EEPROM
|
||||
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
|
||||
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
|
||||
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: add ok, idx=%u, cnt=%u",
|
||||
orderIndex, s_unsettledOrderMng.u16_unsettledCnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移除已结算订单索引
|
||||
*/
|
||||
U8_T u8_remove_unsettled_order(U32_T orderIndex)
|
||||
{
|
||||
// 查找订单索引位置
|
||||
U16_T position;
|
||||
if (u8_find_order_index(orderIndex, &position) != 0)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: idx not found, idx=%u", orderIndex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 从列表中移除(将后面的元素前移)
|
||||
for (U16_T i = position; i < s_unsettledOrderMng.u16_unsettledCnt - 1; i++)
|
||||
{
|
||||
// 复制3字节索引
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = s_unsettledOrderMng.u8_unsettledIndexes[i + 1][0];
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = s_unsettledOrderMng.u8_unsettledIndexes[i + 1][1];
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = s_unsettledOrderMng.u8_unsettledIndexes[i + 1][2];
|
||||
}
|
||||
|
||||
// 清空最后一个元素
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt - 1][0] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt - 1][1] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt - 1][2] = 0;
|
||||
s_unsettledOrderMng.u16_unsettledCnt--;
|
||||
|
||||
// 更新CRC并保存到EEPROM
|
||||
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
|
||||
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
|
||||
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: rm ok, idx=%u, cnt=%u",
|
||||
orderIndex, s_unsettledOrderMng.u16_unsettledCnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查订单是否已结算
|
||||
*/
|
||||
U8_T u8_check_order_settled(U32_T orderIndex)
|
||||
{
|
||||
U16_T position;
|
||||
|
||||
// 在未结算列表中查找
|
||||
if (u8_find_order_index(orderIndex, &position) == 0)
|
||||
{
|
||||
return 1; // 找到,说明未结算
|
||||
}
|
||||
|
||||
return 0; // 未找到,说明已结算
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取未结算订单数量
|
||||
*/
|
||||
U16_T u16_get_unsettled_order_count(void)
|
||||
{
|
||||
return s_unsettledOrderMng.u16_unsettledCnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取所有未结算订单索引
|
||||
*/
|
||||
void v_get_unsettled_order_indexes(U32_T *indexArray, U16_T *count)
|
||||
{
|
||||
if (indexArray == NULL || count == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*count = s_unsettledOrderMng.u16_unsettledCnt;
|
||||
|
||||
for (U16_T i = 0; i < s_unsettledOrderMng.u16_unsettledCnt; i++)
|
||||
{
|
||||
// 将3字节索引转换为32位整数
|
||||
indexArray[i] = u32_3bytes_to_uint32(s_unsettledOrderMng.u8_unsettledIndexes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有未结算订单记录
|
||||
*/
|
||||
void v_clear_all_unsettled_orders(void)
|
||||
{
|
||||
s_unsettledOrderMng.u16_unsettledCnt = 0;
|
||||
|
||||
// 清空索引数组(3字节数组)
|
||||
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
|
||||
{
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = 0;
|
||||
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = 0;
|
||||
}
|
||||
|
||||
// 更新CRC并保存到EEPROM
|
||||
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
|
||||
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
|
||||
|
||||
UNSETTLED_MNG_LOG("UnsettledMng: clear all");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打印未结算订单状态(调试用)
|
||||
*/
|
||||
void v_print_unsettled_order_status(void)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("=== Unsettled status ===");
|
||||
UNSETTLED_MNG_LOG("Unsettled cnt: %u", s_unsettledOrderMng.u16_unsettledCnt);
|
||||
|
||||
if (s_unsettledOrderMng.u16_unsettledCnt > 0)
|
||||
{
|
||||
UNSETTLED_MNG_LOG("Unsettled idx list:");
|
||||
for (U16_T i = 0; i < s_unsettledOrderMng.u16_unsettledCnt; i++)
|
||||
{
|
||||
U32_T orderIndex = u32_3bytes_to_uint32(s_unsettledOrderMng.u8_unsettledIndexes[i]);
|
||||
UNSETTLED_MNG_LOG(" [%u] index=%u", i, orderIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UNSETTLED_MNG_LOG("No unsettled order");
|
||||
}
|
||||
|
||||
UNSETTLED_MNG_LOG("=====================");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算CRC校验值
|
||||
*/
|
||||
U16_T u16_calculate_crc(const U8_T *data, U16_T length)
|
||||
{
|
||||
U16_T crc = 0xFFFF;
|
||||
|
||||
if (data == NULL || length == 0)
|
||||
{
|
||||
return crc;
|
||||
}
|
||||
|
||||
for (U16_T i = 0; i < length; i++)
|
||||
{
|
||||
crc ^= (U16_T)data[i] << 8;
|
||||
|
||||
for (U8_T j = 0; j < 8; j++)
|
||||
{
|
||||
if (crc & 0x8000)
|
||||
{
|
||||
crc = (crc << 1) ^ UNSETTLED_CRC_POLY;
|
||||
}
|
||||
else
|
||||
{
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/*------ Local functions(局部函数实现) -------------------*/
|
||||
|
||||
/**
|
||||
* @brief 写入未结算订单管理信息到EEPROM
|
||||
*/
|
||||
static void v_write_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng)
|
||||
{
|
||||
if (unsettledMng == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 先计算CRC
|
||||
unsettledMng->u16_crc = u16_calculate_unsettled_crc(unsettledMng);
|
||||
|
||||
// 写入到EEPROM
|
||||
eeprom_write(EEPROM_ADDR_UNSETTLED_MNG, (U8_T *)unsettledMng, sizeof(S_UNSETTLED_ORDER_MNG));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从EEPROM读取未结算订单管理信息
|
||||
*/
|
||||
static void v_read_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng)
|
||||
{
|
||||
if (unsettledMng == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 从EEPROM读取,eeprom_read返回HAL_OK(0)表示成功
|
||||
if (eeprom_read(EEPROM_ADDR_UNSETTLED_MNG, (U8_T *)unsettledMng, sizeof(S_UNSETTLED_ORDER_MNG)) != HAL_OK)
|
||||
{
|
||||
// 读取失败,初始化默认值
|
||||
unsettledMng->u16_head = 0;
|
||||
unsettledMng->u16_unsettledCnt = 0;
|
||||
unsettledMng->u16_crc = 0;
|
||||
|
||||
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
|
||||
{
|
||||
unsettledMng->u8_unsettledIndexes[i][0] = 0;
|
||||
unsettledMng->u8_unsettledIndexes[i][1] = 0;
|
||||
unsettledMng->u8_unsettledIndexes[i][2] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算未结算订单管理信息的CRC
|
||||
*/
|
||||
static U16_T u16_calculate_unsettled_crc(S_UNSETTLED_ORDER_MNG *unsettledMng)
|
||||
{
|
||||
if (unsettledMng == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 手动计算结构体大小,避免结构体对齐问题
|
||||
// 结构体布局:u16_head(2) + u16_unsettledCnt(2) + u16_crc(2) + u8_unsettledIndexes[30][3](90)
|
||||
// 总大小:2 + 2 + 2 + 90 = 96字节
|
||||
// 计算除CRC字段外的数据:u16_head + u16_unsettledCnt + u8_unsettledIndexes
|
||||
U8_T tempBuffer[94]; // 2 + 2 + 90 = 94字节
|
||||
U16_T offset = 0;
|
||||
|
||||
// 复制头标志
|
||||
tempBuffer[offset++] = (unsettledMng->u16_head >> 8) & 0xFF;
|
||||
tempBuffer[offset++] = unsettledMng->u16_head & 0xFF;
|
||||
|
||||
// 复制未结算订单数量
|
||||
tempBuffer[offset++] = (unsettledMng->u16_unsettledCnt >> 8) & 0xFF;
|
||||
tempBuffer[offset++] = unsettledMng->u16_unsettledCnt & 0xFF;
|
||||
|
||||
// 复制索引数组(3字节数组)
|
||||
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
|
||||
{
|
||||
for (U8_T j = 0; j < 3; j++)
|
||||
{
|
||||
tempBuffer[offset++] = unsettledMng->u8_unsettledIndexes[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// 计算CRC
|
||||
return u16_calculate_crc(tempBuffer, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 在未结算订单列表中查找订单索引
|
||||
*/
|
||||
static U8_T u8_find_order_index(U32_T orderIndex, U16_T *position)
|
||||
{
|
||||
for (U16_T i = 0; i < s_unsettledOrderMng.u16_unsettledCnt; i++)
|
||||
{
|
||||
U32_T storedIndex = u32_3bytes_to_uint32(s_unsettledOrderMng.u8_unsettledIndexes[i]);
|
||||
if (storedIndex == orderIndex)
|
||||
{
|
||||
if (position != NULL)
|
||||
{
|
||||
*position = i;
|
||||
}
|
||||
return 0; // 找到
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // 未找到
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将32位整数转换为3字节数组
|
||||
*/
|
||||
static void v_uint32_to_3bytes(U32_T value, U8_T *bytes)
|
||||
{
|
||||
bytes[0] = (value >> 16) & 0xFF; // 最高字节
|
||||
bytes[1] = (value >> 8) & 0xFF; // 中间字节
|
||||
bytes[2] = value & 0xFF; // 最低字节
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将3字节数组转换为32位整数
|
||||
*/
|
||||
static U32_T u32_3bytes_to_uint32(const U8_T *bytes)
|
||||
{
|
||||
return ((U32_T)bytes[0] << 16) | ((U32_T)bytes[1] << 8) | (U32_T)bytes[2];
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @file unsettled_order_mng.h
|
||||
* @brief 未结算订单索引列表(EEPROM 持久化)
|
||||
*
|
||||
* 与充电订单 Flash 模块配合:订单保存成功后可将 `u32_index` 加入本列表;平台确认结算后移除。
|
||||
* 数据区独立 EEPROM 段,结构含头、计数、CRC 与 3 字节压缩索引表,详见 `S_UNSETTLED_ORDER_MNG`。
|
||||
*/
|
||||
|
||||
#ifndef UNSETTLED_ORDER_MNG_H
|
||||
#define UNSETTLED_ORDER_MNG_H
|
||||
|
||||
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*------ Exported includes(头文件) ----------------------*/
|
||||
#include "main.h"
|
||||
#include "eeprom/fm24cl16.h"
|
||||
|
||||
/*------ Exported macro(宏定义) -------------------------*/
|
||||
|
||||
|
||||
// 最大未结算订单数(基于可用空间计算)
|
||||
// 地址范围:0x0040 - 0x00FF = 192字节
|
||||
// 结构体大小:头标志(2) + 计数(2) + CRC(2) = 6字节
|
||||
// 剩余空间:192 - 6 = 186字节
|
||||
// 每个索引使用3字节压缩存储(支持最大索引值16,777,215)
|
||||
// 最大存储数量:186 / 3 = 62个(向下取整)
|
||||
// 用户要求最大数量为60,满足要求
|
||||
#define UNSETTLED_MAX_CNT 30 // 最大未结算订单数
|
||||
|
||||
// 头标志定义
|
||||
#define UNSETTLED_HEADER 0x5AA6 // 未结算订单管理信息头标志
|
||||
|
||||
// CRC多项式(简单校验)
|
||||
#define UNSETTLED_CRC_POLY 0x1021 // CRC-16-CCITT多项式
|
||||
|
||||
/*------ Exported struct(结构体定义) --------------------*/
|
||||
|
||||
/**
|
||||
* @brief 未结算订单管理信息结构体
|
||||
*
|
||||
* 存储在EEPROM地址:EEPROM_ADDR_UNSETTLED_MNG (0x0040)
|
||||
* 用于管理未结算订单的索引列表
|
||||
* 使用3字节压缩存储索引,支持最大60个未结算订单
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
U16_T u16_head; // 头标志 (0x5AA6)
|
||||
U16_T u16_unsettledCnt; // 未结算订单数量
|
||||
U16_T u16_crc; // CRC校验值
|
||||
U8_T u8_unsettledIndexes[UNSETTLED_MAX_CNT][3]; // 未结算订单索引数组(每个索引3字节)
|
||||
} S_UNSETTLED_ORDER_MNG;
|
||||
|
||||
/*------ Exported variables(变量定义) -------------------*/
|
||||
|
||||
// 未结算订单管理信息全局变量
|
||||
extern S_UNSETTLED_ORDER_MNG s_unsettledOrderMng;
|
||||
|
||||
/*------ Exported function prototypes (函数声明) -------*/
|
||||
|
||||
/**
|
||||
* @brief 初始化未结算订单管理模块
|
||||
*
|
||||
* 从EEPROM读取管理信息,如果不存在则初始化默认值
|
||||
* 验证数据完整性(CRC校验)
|
||||
*/
|
||||
void v_unsettled_order_init(void);
|
||||
|
||||
/**
|
||||
* @brief 添加未结算订单索引
|
||||
*
|
||||
* 当新的充电订单保存到Flash时调用此函数
|
||||
*
|
||||
* @param orderIndex 订单索引(由u32_chg_order_save_record返回)
|
||||
* @return U8_T 添加结果:0-成功,1-失败(列表已满)
|
||||
*/
|
||||
U8_T u8_add_unsettled_order(U32_T orderIndex);
|
||||
|
||||
/**
|
||||
* @brief 移除已结算订单索引
|
||||
*
|
||||
* 当平台确认结算后调用此函数
|
||||
*
|
||||
* @param orderIndex 要移除的订单索引
|
||||
* @return U8_T 移除结果:0-成功,1-失败(未找到该索引)
|
||||
*/
|
||||
U8_T u8_remove_unsettled_order(U32_T orderIndex);
|
||||
|
||||
/**
|
||||
* @brief 检查订单是否已结算
|
||||
*
|
||||
* @param orderIndex 要检查的订单索引
|
||||
* @return U8_T 检查结果:0-已结算,1-未结算
|
||||
*/
|
||||
U8_T u8_check_order_settled(U32_T orderIndex);
|
||||
|
||||
/**
|
||||
* @brief 获取未结算订单数量
|
||||
*
|
||||
* @return U16_T 未结算订单数量
|
||||
*/
|
||||
U16_T u16_get_unsettled_order_count(void);
|
||||
|
||||
/**
|
||||
* @brief 获取所有未结算订单索引
|
||||
*
|
||||
* @param indexArray 索引数组指针(用于存储读取到的数据)
|
||||
* @param count 实际读取到的索引数量指针
|
||||
*/
|
||||
void v_get_unsettled_order_indexes(U32_T *indexArray, U16_T *count);
|
||||
|
||||
/**
|
||||
* @brief 清除所有未结算订单记录
|
||||
*
|
||||
* 用于系统重置或测试
|
||||
*/
|
||||
void v_clear_all_unsettled_orders(void);
|
||||
|
||||
/**
|
||||
* @brief 打印未结算订单状态(调试用)
|
||||
*/
|
||||
void v_print_unsettled_order_status(void);
|
||||
|
||||
/**
|
||||
* @brief 计算CRC校验值
|
||||
*
|
||||
* @param data 数据指针
|
||||
* @param length 数据长度(字节)
|
||||
* @return U16_T CRC校验值
|
||||
*/
|
||||
U16_T u16_calculate_crc(const U8_T *data, U16_T length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UNSETTLED_ORDER_MNG_H */
|
||||
Reference in New Issue
Block a user