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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:28:36 +08:00
commit 9ceb218f80
1597 changed files with 724159 additions and 0 deletions
@@ -0,0 +1,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);
// 写入EEPROMeeprom_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; // 无效记录
}