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
+195
View File
@@ -0,0 +1,195 @@
#include "app_init.h"
#include "app_init/task_lock.h"
#include "mylog/mylog.h"
#include "bms/bms_task.h"
#include "collect_ctrl/collect_task.h"
#include "flowctrl/flowctrl_task.h"
#include "fault_cheak/faultcheck_task.h"
#include "mdu_comm/mdu_comm_task.h"
#include "plat_comm/plat_comm_task.h"
#include "ui/ui_task.h"
#include "wdt_task/wdt_task.h"
#include "meter_calculate/meter_coll_task.h"
#include "meter_calculate/calculate_task.h"
#include "tcp_ser/tcp_ser_task.h"
#include "sys_drv_init.h"
#include "publicdata/publicdata.h"
#include "bsp_include.h"
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
extern struct netif gnetif;
#include "adc/app_adc.h"
#include "app_fatfs/fatfs_init.h"
#include "net_lwip/ethernetif.h"
#elif SYSTEM_TYPE == SYSTEM_TYPE_CCU621_M
#include "netconf.h"
#include "net_init.h"
#include "eth_link.h"
#include "sub_comm/sub_comm_task.h"
#else
#endif
#if SD_CARD_EN
#include "sdmmc/sdmmc_sd_drv.h"
#include "sdmmc/app_sdmmc.h"
#endif
#if(MY_SHELL_EN) //Shell 使能
#include "letter_shell/shell_port.h"
#endif
void v_fault_blink_code_loop(uint32_t blink_count)
{
uint32_t i;
if (blink_count == 0U) {
blink_count = 1U;
}
for (;;) {
for (i = 0U; i < blink_count; i++) {
RUN_LED_ON();
delay_ms(200U);
RUN_LED_OFF();
delay_ms(200U);
}
/* 码间隔:灭 500ms,便于快速识别状态 */
RUN_LED_OFF();
delay_ms(1000U);
}
}
void delay_ms(uint32_t ms)
{
uint32_t start;
uint32_t ticks;
/* 基于 DWT CYCCNT 的精确延时:按 SystemCoreClock 计时 */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
start = DWT->CYCCNT;
ticks = (SystemCoreClock / 1000U) * ms;
while ((DWT->CYCCNT - start) < ticks) {
__NOP();
}
}
/*
### CMSIS-RTOS2 优先级与任务周期匹配表
| 优先级范围 | 适用任务类型 | 典型执行周期 | 说明 |
| **osPriorityRealtime**<br>(48~55) | 硬件中断延迟处理、安全关键任务 | **<1ms** | 最高优先级,用于不能容忍任何延迟的任务(如电机紧急停止) |
| **osPriorityHigh**<br>(40~47) | 实时控制、高频传感器处理 | **1~10ms** | 要求严格定时执行的任务(如PID控制、IMU数据融合) |
| **osPriorityAboveNormal**<br>(32~39) | 通信协议栈、用户输入响应 | **10~50ms** | 需要快速响应但允许轻微抖动的任务(如UART命令解析、触摸屏响应) |
| **osPriorityNormal**<br>(24~31) | 主要业务逻辑、中等频率任务 | **50~200ms** | 常规任务(如状态机更新、中等频率数据采集) |
| **osPriorityBelowNormal**<br>(16~23) | 后台处理、低优先级服务 | **200ms~1s** | 允许延迟的任务(如日志写入、非实时数据分析) |
| **osPriorityLow**<br>(8~15) | 维护性任务 | **1s~10s** | 对时间不敏感的任务(如LED心跳灯、内存碎片整理) |
| **osPriorityIdle**<br>(1) | 空闲任务 | **无固定周期** | 仅当系统空闲时运行(如低功耗模式准备) |
#### 配置原则
1. **周期越短 → 优先级越高**
- 例:100us级电机控制 → `osPriorityRealtime7` (55)
2. **关键性补偿**
- 重要但低频的任务(如看门狗)可提高1-2级优先级
3. **避免优先级反转**
- 共享资源的任务间优先级差建议≤8级
*/
const MY_TASK_DATA my_task_data[MY_TASK_NUM] =
{
/* 格式:{名称, 优先级, 栈大小(字节), 执行周期(ms), 任务入口, 任务参数} */
/* FreeRTOS 优先级规则(重要):
* - 数值越大,优先级越高;0 为最低优先级(通常是 IDLE)。
* - 当前工程 configMAX_PRIORITIES=32,可用范围 0~31。
* - 现场要求任务优先级不重复,以下按周期和关键性分配唯一优先级。 */
{"EthIf", (UBaseType_t)(tskIDLE_PRIORITY + 30U), TASK_SIZE_SIZE_2048, TASK_SLEEP_MS_1, NULL, NULL},
{"TCP/IP", (UBaseType_t)(tskIDLE_PRIORITY + 29U), TASK_SIZE_SIZE_8k, TASK_SLEEP_MS_1, NULL, NULL},
{"BmsComm", (UBaseType_t)(tskIDLE_PRIORITY + 27U), TASK_SIZE_SIZE_4k, TASK_SLEEP_MS_5, v_bms_task, NULL},
{"DataColl",(UBaseType_t)(tskIDLE_PRIORITY + 26U), TASK_SIZE_SIZE_2048, TASK_SLEEP_MS_10, v_collect_task, NULL},
{"FlowCtrl",(UBaseType_t)(tskIDLE_PRIORITY + 25U), TASK_SIZE_SIZE_4k, TASK_SLEEP_MS_20, v_flowctrl_task, NULL},
{"FaultCheck",(UBaseType_t)(tskIDLE_PRIORITY + 24U), TASK_SIZE_SIZE_6k, TASK_SLEEP_MS_50, v_fault_task, NULL},
{"MduComm", (UBaseType_t)(tskIDLE_PRIORITY + 23U), TASK_SIZE_SIZE_2048, TASK_SLEEP_MS_50, v_Mdu_Comm_task, NULL},
{"SubComm", (UBaseType_t)(tskIDLE_PRIORITY + 22U), TASK_SIZE_SIZE_2048, TASK_SLEEP_MS_50, v_sub_comm_task, NULL},
{"PlatComm",(UBaseType_t)(tskIDLE_PRIORITY + 21U), TASK_SIZE_SIZE_8k*2, TASK_SLEEP_MS_100, v_plat_comm_task, NULL},
#if (MY_SHELL_EN)
{"MyShell", (UBaseType_t)(tskIDLE_PRIORITY + 20U), TASK_SIZE_SIZE_4k, TASK_SLEEP_MS_100, v_myshell_task, &shell},
#endif
{"UiComm", (UBaseType_t)(tskIDLE_PRIORITY + 19U), TASK_SIZE_SIZE_3k, TASK_SLEEP_MS_100, v_ui_task, NULL},
/* WatchDog 放在 EthIf/TCP-IP 之后,保留较高优先级 */
{"WatchDog",(UBaseType_t)(tskIDLE_PRIORITY + 28U), TASK_SIZE_SIZE_1024, TASK_SLEEP_MS_150, v_wdt_task, NULL},
{"MeterColl",(UBaseType_t)(tskIDLE_PRIORITY + 18U), TASK_SIZE_SIZE_2048, TASK_SLEEP_MS_200, v_Meter_Coll_task, NULL},
{"Meterfee",(UBaseType_t)(tskIDLE_PRIORITY + 17U), TASK_SIZE_SIZE_3k, TASK_SLEEP_MS_200, v_meterfee_task, NULL},
{"MyLog", (UBaseType_t)(tskIDLE_PRIORITY + 16U), TASK_SIZE_SIZE_8k*2, TASK_SLEEP_MS_200, v_mylog_task, NULL},
#if TCP_DEBUG_EN
{"TcpSer", (UBaseType_t)(tskIDLE_PRIORITY + 15U), TASK_SIZE_SIZE_8k*2, TASK_SLEEP_MS_200, v_tcp_ser_task, NULL},
#endif
{"EthLink", (UBaseType_t)(tskIDLE_PRIORITY + 1U), TASK_SIZE_SIZE_1024, TASK_SLEEP_MS_1000, ethernet_link_thread, NETCONF_ETH_NETIF}
};
void app_os_init(void)
{
BaseType_t create_ret;
v_public_cfg_data_init(); //参数初始化
#if (MY_SHELL_EN)
userShellInit();
#endif
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
v_adc_data_init();
extern void Netif_Config_init(void);
Netif_Config_init(); //网络及lwip 初始化 内部创建3个线程 分别是EthIf TCP/IP EthLink
CANSPI_Initialize(); //SPI CAN 配置
#elif SYSTEM_TYPE == SYSTEM_TYPE_CCU621_M
create_ret = net_init_task_create();
if (create_ret != pdPASS) {
MYLOG_MSG(0xFFU, "NetInit create failed, ret=%ld", (long)create_ret);
} else {
MYLOG_MSG(0xFFU, "NetInit create ok");
}
#else
#endif
for (uint32_t i = 0U; i < (uint32_t)MY_TASK_NUM; ++i) {
if (my_task_data[i].task_entry == NULL) {
continue;
}
create_ret = xTaskCreate(my_task_data[i].task_entry,
my_task_data[i].task_name,
(uint16_t)(my_task_data[i].task_size / sizeof(StackType_t)),
my_task_data[i].task_arg,
my_task_data[i].task_prio,
NULL);
if (create_ret != pdPASS) {
MYLOG_MSG(0xFFU, "Task[%s] create FAILED! ret=%ld", my_task_data[i].task_name, (long)create_ret);
} else {
MYLOG_MSG(0xFFU, "Task[%s] create ok", my_task_data[i].task_name);
}
}
#if SD_CARD_EN
sdmmc_sd_drv_init();
app_sdmmc_init();
#endif
#if SD_CARD_EN
fatfs_init();
#endif
}
+97
View File
@@ -0,0 +1,97 @@
#ifndef APP_INIT_H
#define APP_INIT_H
#include "FreeRTOS.h"
#include "task.h"
#include "publicdata/public_define.h"
//任务最小堆栈空间大小 --注意单位是字节!!!!
#define TASK_SIZE_SIZE_512 (configMINIMAL_STACK_SIZE*4) //512字节
#define TASK_SIZE_SIZE_1024 (configMINIMAL_STACK_SIZE*8) //1024字节=1k
#define TASK_SIZE_SIZE_2048 (TASK_SIZE_SIZE_1024*2) //2048字节=2k
#define TASK_SIZE_SIZE_3k (TASK_SIZE_SIZE_1024*3) //3k
#define TASK_SIZE_SIZE_4k (TASK_SIZE_SIZE_1024*4) //4k
#define TASK_SIZE_SIZE_5k (TASK_SIZE_SIZE_1024*5) //5k
#define TASK_SIZE_SIZE_6k (TASK_SIZE_SIZE_1024*6) //6k
#define TASK_SIZE_SIZE_7k (TASK_SIZE_SIZE_1024*7) //7k
#define TASK_SIZE_SIZE_8k (TASK_SIZE_SIZE_1024*8) //8k
//任务延时周期
#define TASK_SLEEP_MS_1000 (1000U)
#define TASK_SLEEP_MS_500 (500U)
#define TASK_SLEEP_MS_300 (300U)
#define TASK_SLEEP_MS_200 (200U)
#define TASK_SLEEP_MS_150 (150U)
#define TASK_SLEEP_MS_100 (100U)
#define TASK_SLEEP_MS_50 (50U)
#define TASK_SLEEP_MS_30 (30U)
#define TASK_SLEEP_MS_20 (20U)
#define TASK_SLEEP_MS_10 (10U)
#define TASK_SLEEP_MS_5 (5U)
#define TASK_SLEEP_MS_1 (1U)
typedef struct
{
char task_name[16];
UBaseType_t task_prio;
uint16_t task_size;
uint16_t task_timer;
TaskFunction_t task_entry;
void *task_arg;
} MY_TASK_DATA;
typedef enum
{
TASK_ID_EthIf = 0,
TASK_ID_TCPIP,
TASK_ID_BmsComm,
TASK_ID_DataColl,
TASK_ID_FlowCtrl,
TASK_ID_FaultCheck,
TASK_ID_MduComm,
TASK_ID_SubComm,
TASK_ID_PlatComm,
#if (MY_SHELL_EN)
TASK_ID_MyShell,
#endif
TASK_ID_UI,
TASK_ID_WATCHDOG,
TASK_ID_MeterColl,
TASK_ID_Meterfee,
TASK_ID_MyLog,
#if (TCP_DEBUG_EN)
TASK_ID_TcpSer,
#endif
TASK_ID_EthLink,
MY_TASK_NUM
} TASK_ID;
extern const MY_TASK_DATA my_task_data[MY_TASK_NUM];
/* lwIP TCP/IP 线程:任务表与 lwipopts.h 中 TCPIP_THREAD_* 须一致(lwipopts 不可再 include 本头文件) */
#define MY_TCPIP_NAME (my_task_data[TASK_ID_TCPIP].task_name)
#define MY_TCPIP_PRIO (my_task_data[TASK_ID_TCPIP].task_prio)
#define MY_TCPIP_SIZE (my_task_data[TASK_ID_TCPIP].task_size)
/* 兼容参考工程的任务访问宏 */
#ifndef mSleep
#define mSleep(x) (vTaskDelay(pdMS_TO_TICKS((x))))
#endif
#define MY_TASK_NEW(task_id, func, para) \
xTaskCreate((func), \
my_task_data[(task_id)].task_name, \
(uint16_t)(my_task_data[(task_id)].task_size / sizeof(StackType_t)), \
(para), \
my_task_data[(task_id)].task_prio, \
NULL)
#define MY_SLEEP_TIME(task_id) mSleep(my_task_data[(task_id)].task_timer)
#define MY_GET_SLEEP_TIME(task_id) (my_task_data[(task_id)].task_timer)
#define MY_GET_TASK_NAME(task_id) (my_task_data[(task_id)].task_name)
void app_os_init(void);
void delay_ms(uint32_t ms);
void v_fault_blink_code_loop(uint32_t blink_count);
#endif
+129
View File
@@ -0,0 +1,129 @@
# app_init 模块说明(app_init.c / app_init.h
> 本文档描述 `CCU621_M` 工程中 `app_init.c` 与 `app_init.h` 的职责、接口与使用约束。
[返回主说明](./../../README.md)
## 快速跳转
- [1. 模块职责](#1-模块职责)
- [2. 涉及文件](#2-涉及文件)
- [3. 对外接口](#3-对外接口)
- [4. app_os_init 初始化流程](#4-app_os_init-初始化流程)
- [5. 任务表说明(当前版本)](#5-任务表说明当前版本)
- [6. 依赖关系](#6-依赖关系)
- [7. 维护注意事项](#7-维护注意事项)
- [返回主说明 README](./../../README.md)
---
## 1. 模块职责
`app_init` 是应用层任务初始化总入口,负责:
- 维护统一任务表 `my_task_data[]`(名称/优先级/栈/周期/入口函数/参数);
-`app_os_init()` 中统一创建任务(`xTaskCreate`);
- 维护任务ID枚举 `TASK_ID` 与任务表下标一一对应;
- 提供任务周期、任务名称等统一访问宏;
- 为 lwIP 提供 `MY_TCPIP_NAME/MY_TCPIP_PRIO/MY_TCPIP_SIZE` 参数来源。
> 说明:当前工程是“集中创建任务”模式,不同于 `CCU601E_D` 的“各模块内部 `*_init()` 分散创建”模式。
---
## 2. 涉及文件
| 文件 | 说明 |
|---|---|
| `app/app_init/app_init.c` | 任务表定义 + `app_os_init()` 创建逻辑 |
| `app/app_init/app_init.h` | 栈/周期宏、`TASK_ID`、任务结构体、访问宏 |
| `app/app_init/task_lock.c/h` | 任务锁统一接口(每任务一个锁句柄) |
---
## 3. 对外接口
### 3.1 函数
| 符号 | 说明 |
|---|---|
| `void app_os_init(void)` | 应用任务初始化总入口(由 `main.c` 调用) |
### 3.2 全局数据
| 符号 | 说明 |
|---|---|
| `const MY_TASK_DATA my_task_data[MY_TASK_NUM]` | 全部任务配置表 |
### 3.3 关键类型与宏
- `MY_TASK_DATA`:任务名称、优先级、栈(字节)、周期(ms)、入口函数、入口参数;
- `TASK_ID`:任务ID**必须与 `my_task_data[]` 顺序一致**
- `MY_TASK_NUM`:任务数量(由 `TASK_ID` 枚举尾项决定);
- `mSleep(x)``vTaskDelay(pdMS_TO_TICKS(x))` 封装;
- `MY_GET_SLEEP_TIME(task_id)`:取任务周期;
- `MY_GET_TASK_NAME(task_id)`:取任务名称;
- `MY_TCPIP_NAME/MY_TCPIP_PRIO/MY_TCPIP_SIZE`:由 `my_task_data[TASK_ID_TCPIP]` 映射得出,供 lwIP 配置使用。
---
## 4. `app_os_init()` 初始化流程
当前流程(以 `app_init.c` 为准):
1.`MY_SHELL_EN=1`:执行 `userShellInit()`
2. 遍历 `my_task_data[]`
3.`task_entry != NULL` 的任务执行 `xTaskCreate(...)`
4. 忽略 `task_entry == NULL` 的条目(如 `EthIf``TCP/IP` 由 lwIP 内部创建)。
---
## 5. 任务表说明(当前版本)
`my_task_data[]` 当前主要任务:
- `EthIf`(占位,lwIP线程)
- `TCP/IP`(占位,lwIP线程)
- `DataColl`
- `FaultCheck`
- `MduComm`
- `MyShell`(条件编译)
- `UiComm`
- `WatchDog`
- `MeterColl`
- `MyLog`
- `EthLink`
> `EthIf`、`TCP/IP` 仅用于参数对齐与状态展示,不由 `app_os_init` 直接创建。
---
## 6. 依赖关系
-`main.c` 调用:`app_os_init()`
- 依赖模块:
- `mylog/collect_ctrl/fault_cheak/mdu_comm/ui/meter_calculate/wdt_task`
- `BSP/net_lwip``eth_link_thread`
- `BSP/letter_shell`(条件编译)
---
## 7. 维护注意事项
1. 新增任务时必须同步修改:
- `TASK_ID` 枚举;
- `my_task_data[]` 表项;
- 相关任务源文件中的任务ID宏(若使用模板化风格)。
2. `my_task_data[]``TASK_ID` 顺序不一致会导致:
- 周期读取错误;
- 日志任务名错误;
- 看门狗计数映射异常。
3. 若调整 `TCP/IP` 参数,建议只改任务表对应项,不在其他位置重复写常量。
4. `MY_SHELL_EN` 会影响任务数量与任务ID布局(`TASK_ID_MyShell` 条件编译)。
---
## 关联文档
- 主索引:[`CCU621_M/README.md`](./../../README.md)
- 本文位置:`app/app_init/app_init模块说明.md`
+905
View File
@@ -0,0 +1,905 @@
#include "app_init/app_test.h"
#include "app_init/app_init.h"
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "portable.h"
#include "flash_external_data.h"
#include "fm24cl16.h"
#include "app_rtc/app_rtc.h"
#include "usart.h"
#include "can.h"
#include "publicdata/publicdata.h"
#include "mylog/mylog.h"
#include "collect_ctrl/collect_task.h"
#include "sys_drv_init.h"
#define APP_TEST_LOG(fmt, ...) MYLOG_MSG(TASK_ID_MyLog, (fmt), ##__VA_ARGS__)
#ifndef APP_TEST_ENABLE
#define APP_TEST_ENABLE (0U)
#endif
#define APP_TEST_FLASH_ADDR (0x00010000U)
#define APP_TEST_FLASH_RW_SIZE (1024U)
#define APP_TEST_FLASH_PERIOD_S (10U)
#define APP_TEST_MIN_TEST_MS (1U)
#define APP_TEST_ALIGN (32U)
#define APP_TEST_GUARD_SIZE (32U)
#define APP_TEST_FLASH_TOTAL_32M (0x02000000U)
#define APP_TEST_FLASH_STEP_1M (0x00100000U)
#define APP_TEST_ENABLE_FLASH_SPEED (0U)
#define APP_TEST_EEPROM_ADDR (0x0600U)
#define APP_TEST_EEPROM_SIZE (32U)
#define APP_TEST_EEPROM_MULTI_SIZE (64U)
#define APP_TEST_EEPROM_SPEED_SIZE (64U)
#define APP_TEST_EEPROM_SPEED_LOOP (10U)
#define APP_TEST_EEPROM_SPEED_RETRY (3U)
/* Use dedicated test window, avoid business hot partitions (e.g. 0x0100/0x0400). */
#define APP_TEST_EEPROM_TEST_ADDR_BASE (0x0600U)
typedef enum
{
APP_TEST_STEP_RTC = 0,
APP_TEST_STEP_EEPROM_BASIC,
APP_TEST_STEP_EEPROM_MULTI,
APP_TEST_STEP_EEPROM_SPEED,
APP_TEST_STEP_CAN,
APP_TEST_STEP_UART,
#if (APP_TEST_ENABLE_FLASH_SPEED)
APP_TEST_STEP_FLASH_SPEED,
#endif
APP_TEST_STEP_FLASH_STRIDE,
APP_TEST_STEP_DONE
} APP_TEST_STEP_T;
#if (APP_TEST_ENABLE)
/**
* @brief 将指针向上对齐到指定字节边界。
* @param p 原始指针。
* @param align 对齐值(2 的幂)。
* @return 对齐后的指针。
*/
static uint8_t *align_up_u8(uint8_t *p, uint32_t align)
{
uintptr_t v = (uintptr_t)p;
v = (v + (align - 1U)) & ~(uintptr_t)(align - 1U);
return (uint8_t *)v;
}
/**
* @brief 为 Flash DMA 测试申请对齐缓冲。
* @param tx_raw 输出:TX 原始指针(用于释放)。
* @param rx_raw 输出:RX 原始指针(用于释放)。
* @param tx_buf 输出:对齐后的 TX 指针(用于读写)。
* @param rx_buf 输出:对齐后的 RX 指针(用于读写)。
* @return 1 成功,0 失败。
*/
static uint8_t app_test_alloc_aligned_buffers(uint8_t **tx_raw, uint8_t **rx_raw, uint8_t **tx_buf, uint8_t **rx_buf)
{
uint32_t alloc_size = APP_TEST_FLASH_RW_SIZE + APP_TEST_ALIGN + APP_TEST_GUARD_SIZE;
*tx_raw = (uint8_t *)pvPortMalloc(alloc_size);
*rx_raw = (uint8_t *)pvPortMalloc(alloc_size);
if ((*tx_raw == NULL) || (*rx_raw == NULL)) {
if (*tx_raw != NULL) {
vPortFree(*tx_raw);
}
if (*rx_raw != NULL) {
vPortFree(*rx_raw);
}
*tx_raw = NULL;
*rx_raw = NULL;
*tx_buf = NULL;
*rx_buf = NULL;
return 0U;
}
*tx_buf = align_up_u8(*tx_raw, APP_TEST_ALIGN);
*rx_buf = align_up_u8(*rx_raw, APP_TEST_ALIGN);
return 1U;
}
/**
* @brief 释放由 app_test_alloc_aligned_buffers 申请的原始缓冲。
* @param tx_raw TX 原始指针。
* @param rx_raw RX 原始指针。
*/
static void app_test_free_aligned_buffers(uint8_t *tx_raw, uint8_t *rx_raw)
{
if (tx_raw != NULL) {
vPortFree(tx_raw);
}
if (rx_raw != NULL) {
vPortFree(rx_raw);
}
}
#if (APP_TEST_ENABLE_FLASH_SPEED)
/**
* @brief 单次 Flash 擦写读回测速与校验。
* @details
* - 使用 1KB 数据块在固定地址做擦除/写入/读取;
* - 打印 erase/write/read 耗时与吞吐;
* - 读回校验失败会打印 seed 便于复现。
*/
static void v_app_test_flash_rw_speed_once(void)
{
uint32_t i;
uint64_t t0;
uint64_t t1;
uint32_t seed;
uint32_t erase_ms;
uint32_t write_ms;
uint32_t read_ms;
uint32_t write_kbs;
uint32_t read_kbs;
uint8_t *tx_raw;
uint8_t *rx_raw;
uint8_t *tx_buf;
uint8_t *rx_buf;
if (app_test_alloc_aligned_buffers(&tx_raw, &rx_raw, &tx_buf, &rx_buf) == 0U) {
APP_TEST_LOG("[APP_TEST] exflash malloc fail, size=%u\r\n", (unsigned int)APP_TEST_FLASH_RW_SIZE);
return;
}
seed = (uint32_t)u64_get_current_millis();
for (i = 0U; i < APP_TEST_FLASH_RW_SIZE; i++) {
tx_buf[i] = (uint8_t)((seed + i) & 0xFFU);
}
(void)memset(rx_buf, 0, APP_TEST_FLASH_RW_SIZE);
t0 = u64_get_current_millis();
(void)s32_flash_dataflash_erase_sector(APP_TEST_FLASH_ADDR);
t1 = u64_get_current_millis();
erase_ms = (uint32_t)(t1 - t0);
t0 = u64_get_current_millis();
(void)s32_flash_dataflash_write(APP_TEST_FLASH_ADDR, tx_buf, APP_TEST_FLASH_RW_SIZE);
t1 = u64_get_current_millis();
write_ms = (uint32_t)(t1 - t0);
if (write_ms < APP_TEST_MIN_TEST_MS) {
write_ms = APP_TEST_MIN_TEST_MS;
}
t0 = u64_get_current_millis();
(void)s32_flash_dataflash_read(APP_TEST_FLASH_ADDR, rx_buf, APP_TEST_FLASH_RW_SIZE);
t1 = u64_get_current_millis();
read_ms = (uint32_t)(t1 - t0);
if (read_ms < APP_TEST_MIN_TEST_MS) {
read_ms = APP_TEST_MIN_TEST_MS;
}
if (memcmp(tx_buf, rx_buf, APP_TEST_FLASH_RW_SIZE) != 0) {
APP_TEST_LOG("[APP_TEST] exflash rw verify fail, seed=%lu\r\n", (unsigned long)seed);
app_test_free_aligned_buffers(tx_raw, rx_raw);
return;
}
write_kbs = (APP_TEST_FLASH_RW_SIZE * 1000U) / (write_ms * 1024U);
read_kbs = (APP_TEST_FLASH_RW_SIZE * 1000U) / (read_ms * 1024U);
APP_TEST_LOG("[APP_TEST] exflash speed seed=%lu, erase=%lums, write=%lums(%luKB/s), read=%lums(%luKB/s)\r\n",
(unsigned long)seed,
(unsigned long)erase_ms,
(unsigned long)write_ms,
(unsigned long)write_kbs,
(unsigned long)read_ms,
(unsigned long)read_kbs);
app_test_free_aligned_buffers(tx_raw, rx_raw);
}
#endif
void v_app_test_rtc_rw_once(void)
{
Comm_Time backup_time;
Comm_Time write_time;
Comm_Time read_time;
GetCurrentTime(&backup_time);
write_time = backup_time;
write_time.ucSec = (uint8_t)((write_time.ucSec + 1U) % 60U);
if ((write_time.ucSec == 0U) && (write_time.ucMin < 59U)) {
write_time.ucMin++;
}
v_rtc_set_time(&write_time);
GetCurrentTime(&read_time);
APP_TEST_LOG("[APP_TEST] rtc rw test, write=%04u-%02u-%02u %02u:%02u:%02u, read=%04u-%02u-%02u %02u:%02u:%02u\r\n",
write_time.iYear, write_time.ucMonth, write_time.ucDay,
write_time.ucHour, write_time.ucMin, write_time.ucSec,
read_time.iYear, read_time.ucMonth, read_time.ucDay,
read_time.ucHour, read_time.ucMin, read_time.ucSec);
v_rtc_set_time(&backup_time);
}
#if defined(__CC_ARM)
#pragma diag_suppress 177
#endif
/**
* @brief EEPROM(FM24CL16) 单次读写校验测试。
* @return 1 通过,0 失败。
*/
uint8_t u8_app_test_eeprom_rw_once(void)
{
uint8_t tx[APP_TEST_EEPROM_SIZE];
uint8_t rx[APP_TEST_EEPROM_SIZE];
uint32_t i;
int32_t first_mismatch = -1;
uint8_t probe_ret;
uint32_t err_after_write;
uint32_t err_after_read;
uint8_t pr;
APP_TEST_LOG("[APP_TEST] eeprom rw test start, addr=0x%02X size=%u\r\n",
(unsigned int)APP_TEST_EEPROM_ADDR,
(unsigned int)APP_TEST_EEPROM_SIZE);
for (i = 0U; i < APP_TEST_EEPROM_SIZE; i++) {
tx[i] = (uint8_t)(0xA0U + i);
}
(void)memset(rx, 0, sizeof(rx));
probe_ret = eeprom_probe((uint16_t)APP_TEST_EEPROM_ADDR);
APP_TEST_LOG("[APP_TEST] eeprom probe ret=%u last_err=%lu\r\n",
(unsigned int)probe_ret,
(unsigned long)eeprom_get_last_error());
/* quick probe different blocks to verify dev-addr mapping */
pr = eeprom_probe(0x0000U);
APP_TEST_LOG("[APP_TEST] eeprom probe @0x0000 ret=%u err=%lu\r\n",
(unsigned int)pr, (unsigned long)eeprom_get_last_error());
pr = eeprom_probe(0x0100U);
APP_TEST_LOG("[APP_TEST] eeprom probe @0x0100 ret=%u err=%lu\r\n",
(unsigned int)pr, (unsigned long)eeprom_get_last_error());
pr = eeprom_probe(0x0200U);
APP_TEST_LOG("[APP_TEST] eeprom probe @0x0200 ret=%u err=%lu\r\n",
(unsigned int)pr, (unsigned long)eeprom_get_last_error());
pr = eeprom_probe(0x0700U);
APP_TEST_LOG("[APP_TEST] eeprom probe @0x0700 ret=%u err=%lu\r\n",
(unsigned int)pr, (unsigned long)eeprom_get_last_error());
eeprom_buffer_write(tx, (uint16_t)APP_TEST_EEPROM_ADDR, (uint16_t)APP_TEST_EEPROM_SIZE);
err_after_write = eeprom_get_last_error();
eeprom_buffer_read(rx, (uint16_t)APP_TEST_EEPROM_ADDR, (uint16_t)APP_TEST_EEPROM_SIZE);
err_after_read = eeprom_get_last_error();
if (memcmp(tx, rx, APP_TEST_EEPROM_SIZE) != 0) {
for (i = 0U; i < APP_TEST_EEPROM_SIZE; i++) {
if (tx[i] != rx[i]) {
first_mismatch = (int32_t)i;
break;
}
}
APP_TEST_LOG("[APP_TEST] eeprom rw verify fail, addr=0x%02X size=%u\r\n",
(unsigned int)APP_TEST_EEPROM_ADDR,
(unsigned int)APP_TEST_EEPROM_SIZE);
APP_TEST_LOG("[APP_TEST] eeprom err write=%lu read=%lu\r\n",
(unsigned long)err_after_write,
(unsigned long)err_after_read);
APP_TEST_LOG("[APP_TEST] eeprom dump first16 @0x%02X:\r\n",
(unsigned int)APP_TEST_EEPROM_ADDR);
for (i = 0U; i < 16U; i++) {
APP_TEST_LOG(" [%02u] wr=0x%02X rd=0x%02X\r\n",
(unsigned int)i,
(unsigned int)tx[i],
(unsigned int)rx[i]);
}
if (first_mismatch >= 0) {
APP_TEST_LOG("[APP_TEST] eeprom mismatch idx=%ld wr=0x%02X rd=0x%02X\r\n",
(long)first_mismatch,
(unsigned int)tx[(uint32_t)first_mismatch],
(unsigned int)rx[(uint32_t)first_mismatch]);
}
return 0U;
}
APP_TEST_LOG("[APP_TEST] eeprom rw verify pass, addr=0x%02X size=%u\r\n",
(unsigned int)APP_TEST_EEPROM_ADDR,
(unsigned int)APP_TEST_EEPROM_SIZE);
return 1U;
}
/**
* @brief EEPROM 多地址点读写校验(覆盖关键分区起点与页边界)。
* @return 1 通过,0 失败。
*/
uint8_t u8_app_test_eeprom_multi_addr_rw_once(void)
{
static const uint16_t rw_addrs[] = {
0x0000U,
0x0040U,
0x0070U,
0x0080U,
0x00C0U,
0x00E0U,
0x00F0U,
0x0580U,
0x0600U,
0x0680U,
0x07C0U
};
static const uint16_t observe_addrs[] = {
(uint16_t)EEPROM_ADDR_HISALARM_MNG,
(uint16_t)EEPROM_ADDR_CARD_MNG,
(uint16_t)EEPROM_ADDR_A_LOG_DATA,
(uint16_t)EEPROM_ADDR_B_LOG_DATA
};
uint8_t tx[APP_TEST_EEPROM_MULTI_SIZE];
uint8_t rx[APP_TEST_EEPROM_MULTI_SIZE];
uint8_t bk[APP_TEST_EEPROM_MULTI_SIZE];
uint32_t i;
uint32_t a;
uint16_t addr;
for (i = 0U; i < APP_TEST_EEPROM_MULTI_SIZE; i++) {
tx[i] = (uint8_t)(0x55U ^ (uint8_t)i);
}
APP_TEST_LOG("[APP_TEST] eeprom multi-addr rw test start, size=%u, points=%u\r\n",
(unsigned int)APP_TEST_EEPROM_MULTI_SIZE,
(unsigned int)(sizeof(rw_addrs) / sizeof(rw_addrs[0])));
for (a = 0U; a < (uint32_t)(sizeof(rw_addrs) / sizeof(rw_addrs[0])); a++) {
addr = rw_addrs[a];
if (!EEPROM_ADDR_IN_RANGE(addr) || !EEPROM_ADDR_IN_RANGE((uint16_t)(addr + APP_TEST_EEPROM_MULTI_SIZE - 1U))) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr skip out-of-range addr=0x%04X\r\n",
(unsigned int)addr);
continue;
}
(void)memset(rx, 0, sizeof(rx));
(void)memset(bk, 0, sizeof(bk));
/* backup original content to avoid corrupting real partitions */
eeprom_buffer_read(bk, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr backup read fail addr=0x%04X err=%lu\r\n",
(unsigned int)addr, (unsigned long)eeprom_get_last_error());
return 0U;
}
eeprom_buffer_write(tx, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr write fail addr=0x%04X err=%lu\r\n",
(unsigned int)addr, (unsigned long)eeprom_get_last_error());
/* best-effort restore */
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
return 0U;
}
eeprom_buffer_read(rx, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr read fail addr=0x%04X err=%lu\r\n",
(unsigned int)addr, (unsigned long)eeprom_get_last_error());
/* best-effort restore */
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
return 0U;
}
if (memcmp(tx, rx, APP_TEST_EEPROM_MULTI_SIZE) != 0) {
for (i = 0U; i < APP_TEST_EEPROM_MULTI_SIZE; i++) {
if (tx[i] != rx[i]) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr mismatch addr=0x%04X idx=%lu wr=0x%02X rd=0x%02X\r\n",
(unsigned int)addr,
(unsigned long)i,
(unsigned int)tx[i],
(unsigned int)rx[i]);
break;
}
}
/* restore original before returning */
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
return 0U;
}
APP_TEST_LOG("[APP_TEST] eeprom multi-addr pass addr=0x%04X\r\n", (unsigned int)addr);
/* restore original content */
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr restore fail addr=0x%04X err=%lu\r\n",
(unsigned int)addr, (unsigned long)eeprom_get_last_error());
return 0U;
}
}
/* Observe-only check for business hot partitions (no write/restore). */
for (a = 0U; a < (uint32_t)(sizeof(observe_addrs) / sizeof(observe_addrs[0])); a++) {
addr = observe_addrs[a];
if (!EEPROM_ADDR_IN_RANGE(addr) || !EEPROM_ADDR_IN_RANGE((uint16_t)(addr + APP_TEST_EEPROM_MULTI_SIZE - 1U))) {
continue;
}
(void)memset(bk, 0, sizeof(bk));
eeprom_buffer_read(bk, addr, (uint16_t)APP_TEST_EEPROM_MULTI_SIZE);
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom multi-addr observe fail addr=0x%04X err=%lu\r\n",
(unsigned int)addr, (unsigned long)eeprom_get_last_error());
return 0U;
}
APP_TEST_LOG("[APP_TEST] eeprom multi-addr observe pass addr=0x%04X\r\n", (unsigned int)addr);
}
APP_TEST_LOG("[APP_TEST] eeprom multi-addr rw test pass\r\n");
return 1U;
}
/**
* @brief EEPROM 读写速度测试(循环读写固定大小并统计吞吐)。
* @return 1 通过,0 失败。
*/
uint8_t u8_app_test_eeprom_speed_once(void)
{
uint8_t tx[APP_TEST_EEPROM_SPEED_SIZE];
uint8_t rx[APP_TEST_EEPROM_SPEED_SIZE];
uint8_t bk[APP_TEST_EEPROM_SPEED_SIZE];
uint32_t i;
uint32_t j;
uint32_t r;
int32_t first_mismatch;
uint64_t t0;
uint64_t t1;
uint64_t write_ms;
uint64_t read_ms;
uint32_t write_kbs;
uint32_t read_kbs;
uint32_t loops = APP_TEST_EEPROM_SPEED_LOOP;
uint16_t addr = (uint16_t)APP_TEST_EEPROM_TEST_ADDR_BASE;
if (!EEPROM_ADDR_IN_RANGE(addr) || !EEPROM_ADDR_IN_RANGE((uint16_t)(addr + APP_TEST_EEPROM_SPEED_SIZE - 1U))) {
APP_TEST_LOG("[APP_TEST] eeprom speed addr out-of-range\r\n");
return 0U;
}
for (i = 0U; i < APP_TEST_EEPROM_SPEED_SIZE; i++) {
tx[i] = (uint8_t)(i & 0xFFU);
}
APP_TEST_LOG("[APP_TEST] eeprom speed test start, addr=0x%04X size=%u loop=%u\r\n",
(unsigned int)addr,
(unsigned int)APP_TEST_EEPROM_SPEED_SIZE,
(unsigned int)loops);
/* backup original test window and restore at the end */
(void)memset(bk, 0, sizeof(bk));
eeprom_buffer_read(bk, addr, (uint16_t)APP_TEST_EEPROM_SPEED_SIZE);
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom speed backup read fail err=%lu\r\n",
(unsigned long)eeprom_get_last_error());
return 0U;
}
t0 = u64_get_current_millis();
for (i = 0U; i < loops; i++) {
for (r = 0U; r < APP_TEST_EEPROM_SPEED_RETRY; r++) {
eeprom_buffer_write(tx, addr, (uint16_t)APP_TEST_EEPROM_SPEED_SIZE);
if (eeprom_get_last_error() == 0U) {
break;
}
vTaskDelay(pdMS_TO_TICKS(2U));
}
if (eeprom_get_last_error() != 0U) {
APP_TEST_LOG("[APP_TEST] eeprom speed write fail loop=%lu retry=%lu err=%lu\r\n",
(unsigned long)i,
(unsigned long)r,
(unsigned long)eeprom_get_last_error());
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_SPEED_SIZE);
return 0U;
}
/* Allow write cycle settle time under stress loops. */
vTaskDelay(pdMS_TO_TICKS(2U));
}
t1 = u64_get_current_millis();
write_ms = (t1 >= t0) ? (t1 - t0) : 0U;
if (write_ms < APP_TEST_MIN_TEST_MS) {
write_ms = APP_TEST_MIN_TEST_MS;
}
t0 = u64_get_current_millis();
for (i = 0U; i < loops; i++) {
for (r = 0U; r < APP_TEST_EEPROM_SPEED_RETRY; r++) {
(void)memset(rx, 0, sizeof(rx));
eeprom_buffer_read(rx, addr, (uint16_t)APP_TEST_EEPROM_SPEED_SIZE);
if ((eeprom_get_last_error() == 0U) &&
(memcmp(tx, rx, APP_TEST_EEPROM_SPEED_SIZE) == 0)) {
break;
}
vTaskDelay(pdMS_TO_TICKS(2U));
}
if ((eeprom_get_last_error() != 0U) || (memcmp(tx, rx, APP_TEST_EEPROM_SPEED_SIZE) != 0)) {
first_mismatch = -1;
for (j = 0U; j < APP_TEST_EEPROM_SPEED_SIZE; j++) {
if (tx[j] != rx[j]) {
first_mismatch = (int32_t)j;
break;
}
}
if (first_mismatch >= 0) {
APP_TEST_LOG("[APP_TEST] eeprom speed verify fail loop=%lu retry=%lu idx=%ld wr=0x%02X rd=0x%02X err=%lu\r\n",
(unsigned long)i,
(unsigned long)r,
(long)first_mismatch,
(unsigned int)tx[(uint32_t)first_mismatch],
(unsigned int)rx[(uint32_t)first_mismatch],
(unsigned long)eeprom_get_last_error());
} else {
APP_TEST_LOG("[APP_TEST] eeprom speed verify fail loop=%lu retry=%lu err=%lu\r\n",
(unsigned long)i,
(unsigned long)r,
(unsigned long)eeprom_get_last_error());
}
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_SPEED_SIZE);
return 0U;
}
}
t1 = u64_get_current_millis();
read_ms = (t1 >= t0) ? (t1 - t0) : 0U;
if (read_ms < APP_TEST_MIN_TEST_MS) {
read_ms = APP_TEST_MIN_TEST_MS;
}
write_kbs = (uint32_t)(((uint64_t)APP_TEST_EEPROM_SPEED_SIZE * (uint64_t)loops * 1000ULL) / (write_ms * 1024ULL));
read_kbs = (uint32_t)(((uint64_t)APP_TEST_EEPROM_SPEED_SIZE * (uint64_t)loops * 1000ULL) / (read_ms * 1024ULL));
APP_TEST_LOG("[APP_TEST] eeprom speed result: write=%llums(%luKB/s) read=%llums(%luKB/s)\r\n",
(unsigned long long)write_ms,
(unsigned long)write_kbs,
(unsigned long long)read_ms,
(unsigned long)read_kbs);
eeprom_buffer_write(bk, addr, (uint16_t)APP_TEST_EEPROM_SPEED_SIZE);
return 1U;
}
#define APP_TEST_CAN_TX_PERIOD_MS (1000ULL)
#define APP_TEST_CAN_EXT_ID_BASE (0x18FEE000U)
/* ================= USART 单路测试(可改参数) =================
* 简化版:每次执行 UART step 时
* - 发送一次(固定数据,可改)
* - 接收一次并打印(如有数据)
* 每次仅测试一个串口。
*/
static E_USART_ID s_uart_test_port = E_USART_3; /* 可改:E_USART_0/2/3/4/5/6/7 */
static uint8_t s_uart_test_tx_data[16] = { 0x55U, 0xAAU, 0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U };
static uint16_t s_uart_test_tx_len = 8U;
static void v_app_test_uart_poll(void)
{
uint8_t rx_buf[128];
uint16_t rx_len;
uint16_t send_len = s_uart_test_tx_len;
if (send_len > (uint16_t)sizeof(s_uart_test_tx_data)) {
send_len = (uint16_t)sizeof(s_uart_test_tx_data);
}
if (send_len > 0U) {
(void)u16_usart_send(s_uart_test_port, s_uart_test_tx_data, send_len);
APP_TEST_LOG("[APP_TEST] uart tx port=%u len=%u\r\n",
(unsigned int)s_uart_test_port,
(unsigned int)send_len);
}
rx_len = u16_usart_recv(s_uart_test_port, rx_buf, (uint16_t)sizeof(rx_buf));
if (rx_len > 0U) {
char hex[3U * sizeof(rx_buf) + 4U];
uint32_t pos = 0U;
uint16_t i;
hex[0] = '\0';
for (i = 0U; i < rx_len && pos + 3U < sizeof(hex); i++) {
(void)snprintf(&hex[pos], sizeof(hex) - pos, "%02X ", (unsigned int)rx_buf[i]);
pos = (uint32_t)strlen(hex);
}
APP_TEST_LOG("[APP_TEST] uart rx port=%u len=%u %s\r\n",
(unsigned int)s_uart_test_port,
(unsigned int)rx_len,
hex);
}
}
/**
* @brief 读空三路 CAN 软件缓存并打印(扩展帧由驱动层标记)。
*/
static void v_app_test_can_rx_drain(void)
{
uint8_t port;
CAN_DATA rx[CAN_RX_BUFFER_SIZE];
uint8_t rx_cnt;
uint8_t i;
uint8_t k;
for (port = 0U; port < 3U; port++) {
rx_cnt = u8_can_rx((E_CAN_ID)port, &rx[0]);
for (k = 0U; k < rx_cnt; k++) {
char hex[3U * 8U + 4U] = {0};
uint32_t pos = 0U;
for (i = 0U; i < rx[k].Len && i < 8U && pos + 3U < sizeof(hex); i++) {
(void)snprintf(&hex[pos], sizeof(hex) - pos, "%02X ", (unsigned int)rx[k].Data[i]);
pos = (uint32_t)strlen(hex);
}
APP_TEST_LOG("[APP_TEST] can rx port=%u id=0x%08lX dlc=%u %s\r\n",
(unsigned int)port, (unsigned long)rx[k].ID, (unsigned int)rx[k].Len, hex);
}
}
}
/**
* @brief 三路 CAN 各发一帧扩展数据帧(8 字节,前 4 字节为递增序号)。
*/
static void v_app_test_can_tx_all_ports(uint32_t seq)
{
uint8_t port;
CAN_DATA tx;
for (port = 0U; port < 3U; port++) {
(void)memset(&tx, 0, sizeof(tx));
tx.ID = APP_TEST_CAN_EXT_ID_BASE + (uint32_t)port;
tx.Len = 8U;
tx.Data[0] = (uint8_t)(seq & 0xFFU);
tx.Data[1] = (uint8_t)((seq >> 8) & 0xFFU);
tx.Data[2] = (uint8_t)((seq >> 16) & 0xFFU);
tx.Data[3] = (uint8_t)((seq >> 24) & 0xFFU);
tx.Data[4] = port;
tx.Data[5] = 0xC0U;
tx.Data[6] = 0xAAU;
tx.Data[7] = 0x55U;
v_can_tx((E_CAN_ID)port, &tx);
}
APP_TEST_LOG("[APP_TEST] can tx 3ch ext seq=%lu base_id=0x%08lX\r\n",
(unsigned long)seq, (unsigned long)APP_TEST_CAN_EXT_ID_BASE);
}
/**
* @brief CAN 周期任务:每 1s 三路各发一帧扩展帧;每次调用先收完并打印。
* @note 挂在 v_app_test_periodic 入口,随 MyLog 任务节拍轮询(不受 10s 自检节流影响)。
*/
static void v_app_test_can_poll(void)
{
static uint64_t s_next_tx_ms = 0ULL;
static uint32_t s_tx_seq = 0U;
uint64_t now_ms;
v_app_test_can_rx_drain();
if (u8_can_is_initialized() == 0U) {
v_can_interface_init();
}
now_ms = u64_get_current_millis();
if (s_next_tx_ms == 0ULL) {
s_next_tx_ms = now_ms;
}
if (now_ms < s_next_tx_ms) {
return;
}
s_next_tx_ms = now_ms + APP_TEST_CAN_TX_PERIOD_MS;
v_app_test_can_tx_all_ports(s_tx_seq);
s_tx_seq++;
}
/**
* @brief 通过 u8_get_yxyc_data 读取整桩/枪实时采集结构并打印(与 collect_task 内数据源一致)。
*/
static void v_app_test_collect_yxyc_snapshot_print(void)
{
SYS_RT_DATA pile;
GUN_RT_DATA gun;
U8_T gun_cnt;
U8_T i;
if (u8_get_yxyc_data(&pile, NULL, 0U) == 0U) {
APP_TEST_LOG("[APP_TEST] coll get sys fail (lock/busy)\r\n");
return;
}
APP_TEST_LOG("[APP_TEST] ****************************************************** start ******************************************************\r\n");
APP_TEST_LOG("[APP_TEST] coll SYS yx_raw=0x%08lX\r\n",
(unsigned long)pile.yx_sys_data.u32_yx_sys_data);
APP_TEST_LOG("[APP_TEST] SYS_yx estp=%u door=%u water=%u smoke=%u busKM=%u spd=%u acKM=%u qf=%u\r\n",
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_emergency,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_door,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_water,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_smoke,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_bus_KM,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_spd,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_KM,
(unsigned int)pile.yx_sys_data.bit_yx_sys_data.yx_QF);
APP_TEST_LOG("[APP_TEST] SYS_yc temp1_x1000=%ld temp2_x1000=%ld\r\n",
(long)(pile.f32_yc_sys_temp1 * 1000.0f),
(long)(pile.f32_yc_sys_temp2 * 1000.0f));
APP_TEST_LOG("[APP_TEST] hw_DOgpio(1=H 0=L) K1-K5 : %u %u %u %u %u\r\n",
(unsigned int)DO1_READ(), (unsigned int)DO2_READ(), (unsigned int)DO3_READ(),
(unsigned int)DO4_READ(), (unsigned int)DO5_READ());
APP_TEST_LOG("[APP_TEST] hw_DOgpio(1=H 0=L) K6-K10: %u %u %u %u %u | ELOCK_A=%u ELOCK_B=%u\r\n",
(unsigned int)DO6_READ(), (unsigned int)DO7_READ(), (unsigned int)DO8_READ(),
(unsigned int)DO9_READ(), (unsigned int)DO10_READ(),
(unsigned int)ELOCK_A_READ(), (unsigned int)ELOCK_B_READ());
gun_cnt = (U8_T)GUN_MAX_CNT;
for (i = 0U; i < gun_cnt; i++) {
if (u8_get_yxyc_data(NULL, &gun, i) == 0U) {
APP_TEST_LOG("[APP_TEST] coll get gun%u fail\r\n", (unsigned int)i);
continue;
}
APP_TEST_LOG("[APP_TEST] coll GUN%u link=%u\r\n",
(unsigned int)i,
(unsigned int)gun.u8_gunlink_state);
APP_TEST_LOG("[APP_TEST] GUN_yx raw=0x%08lX lock=%u dcP=%u dcN=%u rlf=%u fuse=%u apw=%u home=%u\r\n",
(unsigned long)gun.yx_gun_data.u32_yx_gun_data,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_gun_lock,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_switch_pos,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_switch_neg,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_relief_swich,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_fuse,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_gun_power,
(unsigned int)gun.yx_gun_data.bit_yx_gun_data.yx_gun_homing);
APP_TEST_LOG("[APP_TEST] GUN_yc bus_mV V+=%ld V-=%ld I_mA=%ld\r\n",
(long)(gun.f32_yc_gun_vol_pos * 1000.0f),
(long)(gun.f32_yc_gun_vol_neg * 1000.0f),
(long)(gun.f32_yc_gun_curr * 1000.0f));
APP_TEST_LOG("[APP_TEST] GUN_yc bat_mV V+=%ld V-=%ld | gunTemp_x1000 Tpos=%ld Tneg=%ld\r\n",
(long)(gun.f32_yc_bat_vol_pos * 1000.0f),
(long)(gun.f32_yc_bat_vol_neg * 1000.0f),
(long)(gun.f32_yc_gun_temp_pos * 1000.0f),
(long)(gun.f32_yc_gun_temp_neg * 1000.0f));
}
APP_TEST_LOG("[APP_TEST] ****************************************************** end ******************************************************\r\n");
}
/**
* @brief 对外:以 1MB 步长扫描 32MB 地址空间读写校验。
* @details
* - 每个步长点执行:扇区擦除 -> 写入 1KB 模式数据 -> 读回校验;
* - 任一点失败立即返回并打印失败地址;
* - 全部通过后打印总耗时。
* @return 1 通过,0 失败。
*/
uint8_t u8_app_test_flash_32m_stride_rw(void)
{
uint32_t addr;
uint32_t i;
uint32_t seed;
uint64_t t0;
uint64_t t1;
uint64_t elapsed_ms;
uint8_t *tx_raw;
uint8_t *rx_raw;
uint8_t *tx_buf;
uint8_t *rx_buf;
if (app_test_alloc_aligned_buffers(&tx_raw, &rx_raw, &tx_buf, &rx_buf) == 0U) {
APP_TEST_LOG("[APP_TEST] 32M stride malloc fail\r\n");
return 0U;
}
APP_TEST_LOG("[APP_TEST] 32M stride test start, step=0x%08X, size=%u\r\n",
(unsigned int)APP_TEST_FLASH_STEP_1M,
(unsigned int)APP_TEST_FLASH_RW_SIZE);
t0 = u64_get_current_millis();
for (addr = 0U; addr < APP_TEST_FLASH_TOTAL_32M; addr += APP_TEST_FLASH_STEP_1M) {
seed = (uint32_t)(addr ^ 0x5A5A0000U);
for (i = 0U; i < APP_TEST_FLASH_RW_SIZE; i++) {
tx_buf[i] = (uint8_t)((seed + i) & 0xFFU);
}
(void)memset(rx_buf, 0, APP_TEST_FLASH_RW_SIZE);
(void)s32_flash_dataflash_erase_sector(addr);
(void)s32_flash_dataflash_write(addr, tx_buf, APP_TEST_FLASH_RW_SIZE);
(void)s32_flash_dataflash_read(addr, rx_buf, APP_TEST_FLASH_RW_SIZE);
if (memcmp(tx_buf, rx_buf, APP_TEST_FLASH_RW_SIZE) != 0) {
APP_TEST_LOG("[APP_TEST] 32M stride verify fail, addr=0x%08lX, seed=0x%08lX\r\n",
(unsigned long)addr,
(unsigned long)seed);
app_test_free_aligned_buffers(tx_raw, rx_raw);
return 0U;
}
}
t1 = u64_get_current_millis();
elapsed_ms = t1 - t0;
APP_TEST_LOG("[APP_TEST] 32M stride test pass, range=0x00000000~0x01FFFFFF, elapsed=%llums\r\n", elapsed_ms);
app_test_free_aligned_buffers(tx_raw, rx_raw);
return 1U;
}
/**
* @brief 周期触发综合测试(RTC/EEPROM/Flash)。
* @details 首次调用仅记录起始时间,不立即执行测试。
*/
void v_app_test_periodic(void)
{
static uint32_t s_last_test_sec = 0U;
static uint8_t s_test_step = (uint8_t)APP_TEST_STEP_RTC;
uint32_t now_sec;
if (s_last_test_sec == 0U) {
s_last_test_sec = get_current_seconds();
return;
}
now_sec = get_current_seconds();
/* 改为 1 秒节拍执行一次 step(按你的要求)。 */
if (u32_safe_seconds_since(s_last_test_sec) < 1U) {
return;
}
// APP_TEST_LOG("[APP_TEST] app test periodic\r\n");
/* 改用“数字递增 step”的方式,避免 bitmask 维护成本。 */
switch ((APP_TEST_STEP_T)s_test_step) {
case APP_TEST_STEP_RTC:
// v_app_test_rtc_rw_once();
test_rtc(); //测试RTC
s_test_step++;
break;
case APP_TEST_STEP_EEPROM_BASIC:
// (void)u8_app_test_eeprom_rw_once();
s_test_step++;
break;
case APP_TEST_STEP_EEPROM_MULTI:
// (void)u8_app_test_eeprom_multi_addr_rw_once();
s_test_step++;
break;
case APP_TEST_STEP_EEPROM_SPEED:
// (void)u8_app_test_eeprom_speed_once();
s_test_step++;
break;
case APP_TEST_STEP_CAN:
/* 每秒发送一次 + 周期接收打印(函数内部有 1s 节拍控制)。 */
// v_app_test_can_poll();
s_test_step++;
break;
case APP_TEST_STEP_UART:
// v_app_test_uart_poll();
v_app_test_collect_yxyc_snapshot_print();
s_test_step++;
break;
#if (APP_TEST_ENABLE_FLASH_SPEED)
case APP_TEST_STEP_FLASH_SPEED:
// v_app_test_flash_rw_speed_once();
s_test_step++;
break;
#endif
case APP_TEST_STEP_FLASH_STRIDE:
// if (u8_app_test_flash_32m_stride_rw() == 0U) {
// APP_TEST_LOG("[APP_TEST] 32M stride test failed in periodic flow\r\n");
// }
s_test_step++;
break;
case APP_TEST_STEP_DONE:
s_test_step = (uint8_t)APP_TEST_STEP_RTC;
break;
default:
break;
}
s_last_test_sec = now_sec;
}
#else
void v_app_test_periodic(void)
{
/* test disabled by APP_TEST_ENABLE */
}
#endif /* APP_TEST_ENABLE */
+8
View File
@@ -0,0 +1,8 @@
#ifndef APP_TEST_H
#define APP_TEST_H
#include <stdint.h>
void v_app_test_periodic(void);
#endif
+102
View File
@@ -0,0 +1,102 @@
#include "app_init/task_lock.h"
/**
* @brief 每任务锁句柄数组(外部可见)。
*
* @details
* - 下标与 `TASK_ID` 一一对应;
* - 每个任务默认预留一个 mutex;
* - 具体何时初始化由各任务自行调用 `u8_task_lock_ctrl(..., TASK_LOCK_OP_INIT)`。
*/
static SemaphoreHandle_t g_task_lock_handles[MY_TASK_NUM] = {0};
/**
* @brief 自定义锁句柄控制接口。
*
* @details
* 通过外部传入的 `SemaphoreHandle_t` 句柄指针执行统一锁操作:
* - INIT:若句柄为空则创建 mutex;
* - TAKE:阻塞获取 mutex
* - GIVE:释放 mutex
* - DELETE:删除 mutex 并清空句柄。
*
* @param p_handle 外部锁句柄地址。
* @param op 锁操作动作(初始化、上锁、解锁、删除)。
* @return uint8_t 1-成功,0-失败(指针非法/句柄为空/RTOS调用失败)。
*/
uint8_t u8_task_lock_handle_ctrl(SemaphoreHandle_t *p_handle, TASK_LOCK_OP_E op)
{
if (p_handle == NULL) {
return 0U;
}
switch (op) {
case TASK_LOCK_OP_INIT:
if (*p_handle == NULL) {
*p_handle = xSemaphoreCreateMutex();
}
return (*p_handle != NULL) ? 1U : 0U;
case TASK_LOCK_OP_TAKE:
if (*p_handle == NULL) {
return 0U;
}
return (xSemaphoreTake(*p_handle, portMAX_DELAY) == pdPASS) ? 1U : 0U;
case TASK_LOCK_OP_GIVE:
if (*p_handle == NULL) {
return 0U;
}
return (xSemaphoreGive(*p_handle) == pdTRUE) ? 1U : 0U;
case TASK_LOCK_OP_DELETE:
if (*p_handle != NULL) {
vSemaphoreDelete(*p_handle);
*p_handle = NULL;
}
return 1U;
default:
return 0U;
}
}
/**
* @brief 统一线程锁控制接口。
*
* @details
* 根据任务ID选择对应互斥锁句柄,并执行指定动作:
* - INIT:若未创建则创建 mutex
* - TAKE:阻塞获取 mutex
* - GIVE:释放 mutex
* - DELETE:删除 mutex 并清空句柄。
*
* @param task_id 任务ID`TASK_ID` 枚举,作为锁数组下标)。
* @param op 锁操作动作(初始化、上锁、解锁、删除)。
* @return uint8_t 1-成功,0-失败(ID非法/句柄为空/RTOS调用失败)。
*/
uint8_t u8_task_lock_ctrl(TASK_ID task_id, TASK_LOCK_OP_E op)
{
uint8_t id = (uint8_t)task_id;
if (id >= (uint8_t)MY_TASK_NUM) {
return 0U;
}
switch (op) {
case TASK_LOCK_OP_INIT:
return u8_task_lock_handle_ctrl(&g_task_lock_handles[id], TASK_LOCK_OP_INIT);
case TASK_LOCK_OP_TAKE:
return u8_task_lock_handle_ctrl(&g_task_lock_handles[id], TASK_LOCK_OP_TAKE);
case TASK_LOCK_OP_GIVE:
return u8_task_lock_handle_ctrl(&g_task_lock_handles[id], TASK_LOCK_OP_GIVE);
case TASK_LOCK_OP_DELETE:
return u8_task_lock_handle_ctrl(&g_task_lock_handles[id], TASK_LOCK_OP_DELETE);
default:
return 0U;
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef TASK_LOCK_H
#define TASK_LOCK_H
#include "app_init/app_init.h"
#include "semphr.h"
typedef enum
{
TASK_LOCK_OP_INIT = 0,
TASK_LOCK_OP_TAKE,
TASK_LOCK_OP_GIVE,
TASK_LOCK_OP_DELETE
} TASK_LOCK_OP_E;
uint8_t u8_task_lock_ctrl(TASK_ID task_id, TASK_LOCK_OP_E op);
uint8_t u8_task_lock_handle_ctrl(SemaphoreHandle_t *p_handle, TASK_LOCK_OP_E op);
#endif
+382
View File
@@ -0,0 +1,382 @@
#include "bms/bms_interface.h"
#include "publicdata/publicdata.h"
#include "app_rtc/app_rtc.h"
#include "flowctrl/flow_interface.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "bms/bms_task.h"
extern S_BMS_DATA s_bms_data; //BMS数据
/**
* ************************************************************************
* @brief - 获取BMS流程控制状态
*
* @param[in] u8_gunNo - 抢号 0-1
*
* @return U8_T - 0无意义 1空闲 2开始握手 3绝缘完成,开始握手辨识 4预充完成,发送cro_aa 5充电停止 发送cst 6停止充电完成 停止发送CSD 7BMS通讯超时
*
* @author jiankalka
* @date 2025-08-21
* ************************************************************************
*/
static U8_T u8_bms_get_flow_status(U8_T u8_gunNo)
{
U8_T ret_state = 0;
E_FLOW_REAL_STATE flow_state;
flow_state = (E_FLOW_REAL_STATE)u16_flow_put_data(u8_gunNo , E_FLOW_PUT_DATA_GUN_REAL_STATE,0); //0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
switch (flow_state)
{
case E_FLOW_REAL_FREE : //空闲
ret_state = 1; //空闲
break;
case E_FLOW_REAL_CHARGE_START : //充电启动
ret_state = 2; //开始握手
break;
case E_FLOW_REAL_INSUL_START : //绝缘启动
break;
case E_FLOW_REAL_INSUL_OK : //绝缘计算完成
break;
case E_FLOW_REAL_INSUL_MDU_STOP : //绝缘检测模块停止
if ((s_bms_data.u8_gun_type[u8_gunNo] == 0))
ret_state = 4; //欧标:发送cro_aa
else
ret_state = 3; //国标:绝缘完成,开始握手辨识
break;
case E_FLOW_REAL_PRE_START : //开始预充
break;
case E_FLOW_REAL_PRE_OK : //预充完成
ret_state = 4; //预充完成,发送cro_aa
break;
case E_FLOW_REAL_CHARGEING : //充电中
break;
case E_FLOW_REAL_BMS_OUTTIME : //BMS通讯超时
break;
case E_FLOW_REAL_STOP : //充电停止
ret_state = 5; //停止充电(充电模块退出运行可给其他充电端口使用)
break;
case E_FLOW_REAL_STOP_OK : //充电停止完成
ret_state = 6; //停止充电完成
break;
default:
ret_state = 0;
break;
}
return ret_state;
}
/**
* ************************************************************************
* @brief - BMS任务获取 系统参数
*
* @param[in] gunNo - 枪号 0-1 当获取数据不分枪号时,该值无意义
* @param[in] e_type -
* E_BMS_GET_PARA_GUN_CNT=0, //枪数量 返回值1-N
E_BMS_GET_PARA_GB_EN, //国标使能 返回值 0不使能 1使能
E_BMS_GET_PARA_SC_EN, //团标使能 返回值 0不使能 1使能
E_BMS_GET_PARA_MAX_VOL, //最大电压 精度 0.1
E_BMS_GET_PARA_MIN_VOL, //最小电压 精度 0.1
E_BMS_GET_PARA_MAX_CUR, //最大电流 精度 0.1
E_BMS_GET_PARA_MIN_CUR, //最小电流 精度 0.1
E_BMS_GET_GUN_OUT_VOL, //输出电压 精度0.1
E_BMS_GET_GUN_OUT_CUR, //输出电流 精度0.1
E_BMS_GET_GUN_CHR_TIME, //充电时长 单位min
E_BMS_GET_GUN_CHR_POWER, //充电电量 单位0.1 kWh
E_BMS_GET_GUN_FIOW_CTRL, //BMS充电状态 0无意义 1空闲 2开始握手 3绝缘完成,开始握手辨识 4预充完成,发送cro_aa 5充电停止 发送cst 6停止充电完成 停止发送CSD 7BMS通讯超时
E_BMS_GET_GUN_FLOW_RENEW, //获取重连标识
E_BMS_CLEAR_GUN_FLOW_RENEW, //清空重连标识
* @return U16_T -
*
* @author jiankalka
* @date 2025-08-13
* ************************************************************************
*/
U16_T u16_bms_get_sys_para(U8_T gunNo , E_BMS_GET_PARA_TYPE e_type)
{
U16_T u16_ret = 0;
switch (e_type)
{
case E_BMS_GET_PARA_GUN_CNT: //枪数量
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_gunCnt;
break;
case E_BMS_GET_PARA_GB_EN: //国标使能
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_GBEnable;
break;
case E_BMS_GET_PARA_SC_EN: //团标使能
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_YeLenEn;
break;
case E_BMS_GET_PARA_MAX_VOL: //最大电压 精度 0.1
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_max_volt;
break;
case E_BMS_GET_PARA_MIN_VOL: //最小电压 精度 0.1
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_min_volt;
break;
case E_BMS_GET_PARA_MAX_CUR: //最大电流 精度 0.1
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_max_cur;
break;
case E_BMS_GET_PARA_MIN_CUR: //最小电流 精度 0.1
u16_ret = 50;
break;
case E_BMS_GET_GUN_OUT_VOL: //输出电压 精度 0.1
u16_ret = (U16_T)(u64_get_meter_data(gunNo,0,E_METER_DATA_VOL)/1000);
break;
case E_BMS_GET_GUN_OUT_CUR: //输出电流 精度 0.1
u16_ret = (U16_T)(u64_get_meter_data(gunNo,0,E_METER_DATA_CUR)/1000);
break;
case E_BMS_GET_GUN_CHR_TIME: //充电时长 单位min
u16_ret = u32_meterlog_put_data(gunNo,E_METERLOG_PUT_LOG_INFO_CHAR_TIME,NULL,0); //获取充电时间
break;
case E_BMS_GET_GUN_CHR_POWER: //充电电量 单位0.1 kWh
u16_ret = (U16_T)u32_flow_put_data(gunNo,E_FLOW_PUT_DATA_USED_ENERGY) / NUM_100; //精度转换,由0.0001转换为0.1
break;
case E_BMS_GET_GUN_FIOW_CTRL: //BMS充电状态 0无意义 1空闲 2开始握手 3绝缘完成,开始握手辨识 4预充完成,发送cro_aa 5充电停止 发送cst 6停止充电完成 停止发送CSD 7BMS通讯超时
u16_ret = u8_bms_get_flow_status(gunNo);
break;
case E_BMS_GET_GUN_FLOW_RENEW: //获取重连标识
u16_ret = u16_flow_put_data(gunNo ,E_FLOW_PUT_DATA_GUN_RECONN_FLAG,0); //输出bms重连开始标志 参数:0获取 1清空
break;
case E_BMS_CLEAR_GUN_FLOW_RENEW: //清空重连标识
u16_ret = u16_flow_put_data(gunNo ,E_FLOW_PUT_DATA_GUN_RECONN_FLAG,1); //输出bms重连开始标志 参数:0获取 1清空
break;
default:
break;
}
return u16_ret;
}
U16_T u16_bms_put_data(U8_T gunNo , E_BMS_PUT_DATA_TYPE e_type,U8_T u8_dataPara)
{
U16_T u16_ret = 0;
S_BMS_RT_DATA *pt_bms = &s_bms_data.s_bms_rt_data[gunNo];
switch (e_type)
{
case E_BMS_PUT_DATA_BMS_GB_VER : //BMS 国标协议版本 0 2011版 1 2015版
{
u16_ret = pt_bms->e_bms_standard;
break;
}
case E_BMS_PUT_DATA_BMS_STATE : //BMS 获取BMS状态 0无 1握手完成 2握手失败 3开始预充 4开始充电 5禁止充电 6停止充电 7报文接收超时
{
u16_ret = pt_bms->e_real_state;
break;
}
case E_BMS_PUT_DATA_BMS_MAX_VOL : //BMS 最大充电电压值 精度 0.1V
{
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
if ((s_bms_data.u8_gun_type[gunNo] == 0))
{
u16_ret = (U16_T)(pt_bms->t_bcp_data.f32_batt_charge_max_volt*10);
if(u16_ret < (U16_T)(pt_bms->t_bcl_data.f32_batt_charge_max_volt*10))
u16_ret = (U16_T)(pt_bms->t_bcl_data.f32_batt_charge_max_volt*10);
}
else
#endif
u16_ret = (U16_T)(pt_bms->t_bhm_data.f32_max_charge_volt*10);
break;
}
case E_BMS_PUT_DATA_BMS_MAX_CUR : //BMS 最大充电电流值 精度 0.1V
{
u16_ret = (U16_T)(pt_bms->t_bcp_data.f32_batt_charge_max_curr*10);
break;
}
case E_BMS_PUT_DATA_BMS_REAL_BATT_VOL : //BMS 当前电池电压值 精度 0.1V
{
u16_ret = (U16_T)(pt_bms->t_bcp_data.f32_batt_volt*10);
break;
}
case E_BMS_PUT_DATA_BMS_NEED_VOL : //BMS 需求电压值 精度 0.1V
{
u16_ret = (U16_T)(pt_bms->t_bcl_data.f32_batt_need_volt *10);
break;
}
case E_BMS_PUT_DATA_BMS_NEED_CUR : //BMS 需求电流值 精度 0.1A
{
u16_ret = (U16_T)(pt_bms->t_bcl_data.f32_batt_need_curr *10);
break;
}
case E_BMS_PUT_DATA_BMS_CAN_SEND_CNT : //BMS 报文发送次数 参数值 0代表cem 1代表csd
{
if(u8_dataPara == 0)
u16_ret = pt_bms->u8_cem_send_cnt;
else
u16_ret = pt_bms->u8_csd_send_cnt;
break;
}
case E_BMS_PUT_DATA_BMS_STOP_RES : //BMS 停止原因 详看E_FAULT_INFO枚举内容 BMS部分
{
u16_ret = pt_bms->e_stop_info;
break;
}
case E_BMS_PUT_DATA_BMS_RECONN_FLAG : //BMS 重连标志 0不重连 1重连
{
u16_ret = pt_bms->u8_reconn_flag;
break;
}
case E_BMS_PUT_DATA_GUN_SOC: //BMS SOC %
{
u16_ret = pt_bms->t_bcs_data.u8_batt_soc;
break;
}
case E_BMS_PUT_DATA_GUN_ONE_BATT_MAX_TEMP: //单体电池最大温度 -50度
{
u16_ret = pt_bms->t_bsm_data.s16_batt_max_temp;
break;
}
case E_BMS_PUT_DATA_GUN_ONE_BATT_MAX_VOL: //单体电池最大电压 0.1V
{
u16_ret = (U16_T)(pt_bms->t_bcs_data.f32_cell_max_volt * 10);
break;
}
case E_BMS_PUT_DATA_GUN_BATT_TYPE: //电池类型 01H,铅酸电池;02H:镍氢电池;03H:磷酸铁锂电池;04H:锰酸锂电池;05H:钴酸锂电池;
{
u16_ret = pt_bms->t_brm_data.u8_batt_type;
break;
}
case E_BMS_PUT_DATA_GUN_BATT_RATED_C10: //整车动力蓄电池系统额定容量,数据范围0~1000A.h,精度0.1
{
u16_ret = (U16_T)(pt_bms->t_brm_data.f32_batt_rated_c10*10);
break;
}
default:
break;
}
return u16_ret;
}
void v_bms_get_rt_time(Comm_Time *time)
{
GetCurrentTime(time);
}
/****************************************************
*函数名称: v_get_cst(U8_T gunNo,U8_T *u8_data)
*功能说明: 获取cst停止原因
*参数说明:
* 输入参数:
* 输出参数: void
*最后修改时间: 20230602
*修改内容: 20230602 第一次创建子函数
****************************************************/
U8_T u8_bms_get_cst(U8_T gunNo,U8_T *u8_data)
{
BMS_CST_DATA_U cst_data;
E_FAULT_INFO e_stop_res = (E_FAULT_INFO)u16_flow_get_sys_data(gunNo, E_FLOW_GET_DATA_GUN_STOP_REASON_0, 0);
memset(&cst_data , 0 ,sizeof(BMS_CST_DATA_U));
if(e_stop_res == E_NORMAL_1001 || e_stop_res == E_NORMAL_1002)
cst_data.CST_TMO_FLAG.MAN_MADE_STOP = 1;
else if(e_stop_res == E_NORMAL_1003 || e_stop_res == E_NORMAL_1004)
cst_data.CST_TMO_FLAG.PILE_NORMAL_STOP = 1;
else if(e_stop_res == E_FAULT_13017 )
{
cst_data.CST_TMO_FLAG.FAULT_STOP = 1;
cst_data.CST_TMO_FLAG.EMERGENCY_STOP = 1;
}
else if(e_stop_res == E_FAULT_13016 || /* 充电中导引/连接类 */
e_stop_res == E_FAULT_13015 ||
e_stop_res == E_FAULT_15019)
{
cst_data.CST_TMO_FLAG.FAULT_STOP = 1;
cst_data.CST_TMO_FLAG.PILE_CONNET_FAULT = 1;
}
else if(e_stop_res == E_FAULT_15037 || /* 输出超车辆最高允许充电电压 D0.4504 等 */
e_stop_res == E_FAULT_13048 ||
e_stop_res == E_FAULT_15039 ||
e_stop_res == E_FAULT_15045)
{
cst_data.CST_TMO_FLAG.FAULT_STOP = 1;
cst_data.CST_TMO_FLAG.OVER_VOLT = 1;
}
else if(s_bms_data.s_bms_rt_data[gunNo].t_cst_data.CST_TMO_FLAG.BMS_STOP) //BMS主动中止
cst_data.CST_TMO_FLAG.BMS_STOP = 1;
else
{
cst_data.CST_TMO_FLAG.FAULT_STOP = 1;
cst_data.CST_TMO_FLAG.OTHER = 1;
}
cst_data.CST_TMO_FLAG.reserve1 = 0x0F;
cst_data.CST_TMO_FLAG.reserve = 0x0F;
memcpy(u8_data,&cst_data,sizeof(BMS_CST_DATA_U));
return 1;
//logMsg("fault_info= %x , %x %x %x %x\n",fault_info,u8_data[0],u8_data[1],u8_data[2],u8_data[3]);
}
/****************************************************
*函数名称: v_bms_get_pile_nun()
*功能说明: 获取获取充电桩桩编号
*参数说明:
* 输入参数:U8_T *pilenum
* 输出参数:
****************************************************/
void v_bms_get_pile_nun(U8_T *pilenum)
{
U8_T len = strlen((char *)g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_platA_pileNum);
memset(pilenum,0,len+1);
memcpy(pilenum,g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_platA_pileNum,len);
}
/**
* ************************************************************************
* @brief - 对外提供VIN信息获取接口函数
*
* @param[in] u8_gunNo - 抢号 0-N
* @param[in] u8_vin - 获取数据数组
* @param[in] u8_len - 数组长度
*
*
* @author jiankalka
* @date 2025-09-15
* ************************************************************************
*/
void v_bms_put_vin_str(U8_T u8_gunNo,U8_T *u8_vin,U8_T u8_len)
{
if(u8_vin == NULL ) return;
if(u8_gunNo >= 2) return;
memset(u8_vin , 0 ,u8_len);
strncpy((char *)u8_vin , (char *)s_bms_data.s_bms_rt_data[u8_gunNo].t_brm_data.u8_car_vin ,u8_len);
}
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_test_gunLink[GUN_MAX_CNT];
/**
* ************************************************************************
* @brief - can 转PLC 获取枪连接状态
*
* @param[in] gunNo - 枪号 0-1
*
* @return U8_T - 抢连接状态 0未连接 1已连接
*
* @author jiankalka
* @date 2025-11-14
* ************************************************************************
*/
U8_T u8_bms_get_gunLink(U8_T gunNo)
{
U8_T u8_gunLink;
u8_gunLink = s_bms_data.u8_gun_linkState[gunNo];
//u8_gunLink = u8_test_gunLink[gunNo];
return u8_gunLink;
}
#endif
+368
View File
@@ -0,0 +1,368 @@
#ifndef BMS_INTERFACE_H
#define BMS_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
//BMS接收数据类型 0bhm 1brm 2bcp 3bro 4bcs 5bcl 6bsm 7bmv 8bmt 9bst 10bsd 11bem
typedef enum
{
E_BMS_TYPE_BHM = 0,
E_BMS_TYPE_BRM,
E_BMS_TYPE_BCP,
E_BMS_TYPE_BRO,
E_BMS_TYPE_BCS,
E_BMS_TYPE_BCL,
E_BMS_TYPE_BSM,
E_BMS_TYPE_BMV,
E_BMS_TYPE_BMT,
E_BMS_TYPE_BST,
E_BMS_TYPE_BSD,
E_BMS_TYPE_BEM,
BMS_RECV_TYPE_NUM,//BMS接收数据类型数量
}E_BSM_CAR_DATA_TYPE;
//BMS_BHM报文数据结构体
typedef struct
{
F32_T f32_max_charge_volt; //最高允许充电总电压
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_CR_Plc_Aagvalue; // 信号衰减值
U8_T u8_CR_Plc_CpStatus; // CP状态 0:12v未插枪;1:9v已插枪;2:9v占空比状态;3:6v占空比准备充电状态
U8_T u8_CR_Plc_CommStart; // 通信启动 0:通信未启动;1:启动PLC通信
U8_T u8_SECC_ver[10]; //SECC固件版本编译日期-年月日
#endif
}BMS_BHM_DATA_T;
//BMS_BRM报文数据结构体
typedef struct
{
//握手辨识BRM
U32_T u32_bms_version; //BMS通信协议版本,例如版本V1.0,表示为0x000100
U8_T u8_batt_type; //电池类型,01H,铅酸电池;02H:镍氢电池;03H:磷酸铁锂电池;04H:锰酸锂电池;05H:钴酸锂电池;
//06H:三元材料电池;07H:聚合物锂离子电池;08H;钛酸锂电池;FFH:其他电池
F32_T f32_batt_rated_c10; //整车动力蓄电池系统额定容量,数据范围0~1000A.h,精度0.1
F32_T f32_batt_rated_volt; //整车动力蓄电池系统额定总电压,数据范围0~750V,精度0.1
U8_T u8_batt_vender[4]; //电池生产厂商,标准ASCII
U32_T u32_batt_serial; //电池组序号
U8_T u8_batt_make[4]; //电池生产年月日
U32_T u32_batt_charge_times; //BMS电池组充电次数
U8_T u8_batt_belong; //电池产权标识
U8_T u8_Bms_edition; //充电机通信协议版本号 5A 2015+团标协议 FF 国标协议
U8_T u8_car_vin[18]; //车辆识别码VIN
U8_T u8_bms_soft_version[8]; //BMS软件版本号
}BMS_BRM_DATA_T;
//BMS_BCP报文数据结构体
typedef struct
{
F32_T f32_cell_charge_max_volt; //单体动力蓄电池最高允许充电电压,数据范围0~24V,精度0.01
F32_T f32_batt_charge_max_curr; //最高允许充电电流,数据范围0~400A,精度0.1
F32_T f32_batt_rated_enery; //动力蓄电池标称总能量,数据范围0~1000kW.h,精度0.1
F32_T f32_batt_charge_max_volt; //最高允许充电总电压,数据范围0~750V,精度0.1,此变量用于判断电压超出最高允许充电电压
S16_T s16_batt_allow_max_temp; //最高允许动力蓄电池温度,数据范围-50度~200度
F32_T f32_batt_start_soc; //整车动力蓄电池荷电状态(SOC),数据范围0~100%,精度0.1,这个SOC是充电开始时的SOC
F32_T f32_batt_volt; //整车动力蓄当前电池总电压,数据范围0~750V,精度0.1
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_CR_Plc_EvTransferType; //电动车传输协议能量类型 默认0x03,支持的能量传输类型
#endif
}BMS_BCP_DATA_T;
//BMS_BSD报文数据结构体
typedef struct
{
U8_T u8_batt_end_soc; //BMS终止荷电状态SOC
F32_T f32_cell_batt_min_volt; //BMS动力蓄电池单体最低电压
F32_T f32_cell_batt_max_volt; //BMS动力蓄电池单体最高电压
U8_T u8_batt_min_temp; //BMS动力蓄电池最低温度
U8_T u8_batt_max_temp; //BMS动力蓄电池最高温度
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_CF_Plc_WeldDetectionReq; //粘连检测要求 0:无焊接检测1:焊接检测请求(发送CSD当前充电枪电压)
#endif
}BMS_BSD_DATA_T;
//BMS_BCL报文数据结构体
typedef struct
{
F32_T f32_batt_need_volt; //电压需求,数据范围0~750V,精度0.1
F32_T f32_batt_need_curr; //电流需求,数据范围0~400V,精度0.1
U8_T u8_charge_mode; //充电模式,0x01:恒压充电;0x02:恒流充电
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
F32_T f32_batt_charge_max_volt; //欧标车辆充电中最高允许电压在BCL中
U8_T u8_preCharge; //预充电状态 1:预充电状态: 0:无预充电状态
#endif
}BMS_BCL_DATA_T;
//BMS_BCS报文数据结构体
typedef struct
{
F32_T f32_charge_volt; //充电电压测量值,数据范围0~750V,精度0.1
F32_T f32_charge_curr; //充电电流测量值,数据范围0~400A,精度0.1
F32_T f32_cell_max_volt; //最高单体动力蓄电池电压,数据范围0~24V,精度0.01
U8_T u8_cell_max_volt[2];
U8_T u8_cell_max_volt_group_num; //最高单体动力蓄电池电压所在组号,范围1~16
U8_T u8_batt_soc; //当前荷电状态,数据范围0~100%
U16_T u16_remain_charge_time; //估算剩余充电时间,数据范围0~600min
}BMS_BCS_DATA_T;
//BMS_BMV报文数据结构体
typedef struct
{
// 1-12位:单体动力蓄电池电压,数据分辨率:0.01 V/位,0 V偏移量;数据范围:0~24 V;
// 13-16位:电池分组号,数据分辨率:1/位,0偏移量;数据范围:0~15。
union
{
U16_T u16_data;
struct
{
U16_T bit_cell_volt:12; //单体电压
U16_T bit_cell_group_numt:4; //单体电池组号
}s_bit;
}u_data[NUM_256];
}BMS_BMV_DATA_T;
//BMS_BMT报文数据结构体
typedef struct
{
U8_T u8_batt_temp[NUM_128]; //动力蓄电池温度,数据范围-50度~200度 原始数据存储
}BMS_BMT_DATA_T;
//BMS_BSM报文数据结构体
typedef struct
{
U8_T u8_cell_max_volt_id; //最高单体动力蓄电池电压所在编号,数据范围1~256
S16_T s16_batt_max_temp; //最高动力蓄电池温度,数据范围-50度~200度
U8_T u8_max_temp_id; //最高温度检测点编号,数据范围1~128
S16_T s16_batt_min_temp; //最低动力蓄电池温度,数据范围-50度~200度
U8_T u8_min_temp_id; //最低温度检测点编号,数据范围1~128
U16_T u16_batt_state; //电池状态
//bit0:单体动力蓄电池电压过高
//bit1:单体动力蓄电池电压过低
//bit2:整车动力蓄电池荷电状态SOC过高
//bit3:整车动力蓄电池荷电状态SOC过低
//bit4:动力蓄电池充电过流
//bit5:动力蓄电池温度过高
//bit6:动力蓄电池绝缘状态不正常
//bit7:动力蓄电池组输出连接器连接状态不正常
//bit8:充电允许,0:禁止,1:允许
}BMS_BSM_DATA_T;
//BMS_BEM报文数据联合体
typedef union
{
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_bem_data[8];
struct
{
U8_T RECV_CRM_00_TMO:2; //接收CRM=00超时
U8_T RECV_CRM_AA_TMO:2; //接收CRM=AA超时
U8_T reserve1:4; //保留1
U8_T RECV_CML_TMO:2; //接收CML超时
U8_T RECV_CRO_AA_TMO:2; //接收CRO=AA超时
U8_T reserve2:4; //保留2
U8_T RECV_CCS_TMO:2; //接收CCS超时
U8_T RECV_CST_TMO:2; //接收CTS超时
U8_T reserve3:4; //保留3
U8_T RECV_CSD_TMO:2; //接收CSD超时
U8_T reserve4:6; //保留3
U8_T Message_Sequence:8; //通信流程消息序列
U8_T CR_Plc_ErrorCodeII:8; //错误代码II 见附录
U8_T CR_Plc_ErrorCode:8; //错误代码 见附录
U8_T ErrorCode_CPorPP_state:3; //CP或PP状态的错误代码 见附录
U8_T CR_PLc_EvErrCode:4; //EV车辆发送的错误代码 见附录
U8_T reserve5:1; //预留位
}BEM_TMO_FLAG;
#else
U8_T u8_bem_data[4];
struct
{
U8_T RECV_CRM_00_TMO:2; //接收CRM=00超时
U8_T RECV_CRM_AA_TMO:2; //接收CRM=AA超时
U8_T reserve1:4; //保留1
U8_T RECV_CML_TMO:2; //接收CML超时
U8_T RECV_CRO_AA_TMO:2; //接收CRO=AA超时
U8_T reserve2:4; //保留2
U8_T RECV_CCS_TMO:2; //接收CCS超时
U8_T RECV_CST_TMO:2; //接收CTS超时
U8_T reserve3:4; //保留3
U8_T RECV_CSD_TMO:2; //接收CSD超时
U8_T reserve4:6; //保留3
}BEM_TMO_FLAG;
#endif
}BMS_BEM_DATA_U;
//BMS_CEM报文数据联合体
typedef union
{
U8_T u8_cem_data[4];
struct
{
U8_T RECV_BRM_TMO:2; //接收RRM超时
U8_T reserve1:6; //保留1
U8_T RECV_BCP_TMO:2; //接收BCP超时
U8_T RECV_BRO_AA_TMO:2; //接收BRO=AA超时
U8_T reserve2:4; //保留2
U8_T RECV_BCS_TMO:2; //接收BCS超时
U8_T RECV_BCL_TMO:2; //接收BCL超时
U8_T RECV_BST_TMO:2; //接收BTS超时
U8_T reserve3:2; //保留3
U8_T RECV_BSD_TMO:2; //接收BSD超时
U8_T reserve4:6; //保留3
}CEM_TMO_FLAG;
}BMS_CEM_DATA_U;
//BMS_BST报文数据联合体
typedef union
{
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_bst_data[8];
struct
{
U8_T ACHIEVE_AIM_SOC:2; //达到目标SOC
U8_T ACHIEVE_VOLT:2; //接收总电压设定值
U8_T ACHIEVE_CELL_VOLT:2; //达到单体电压设定值
U8_T CHARGER_STOP:2; //充电机主动终止
U8_T INSU_FAULT:2; //绝缘故障
U8_T DC_OUT_OVER_TEMP:2; //输出连接器过温故障
U8_T BMS_OVER_TEMP:2; //BMS元件、输出连接器过温故障
U8_T CONNECT_FAULT:2; //充电连接器故障
U8_T BATT_OVER_TEMP:2; //电池组温度过高故障
U8_T HV_RELAYS_FAULT:2; //高压继电器故障
U8_T CONN_CHECK_TWO_FAULT:2; //检测点2电压检测故障
U8_T OTHER_FAULT:2; //其它故障
U8_T OVER_CURR:2; //电流过大
U8_T OVER_VOLT:2; //电压过大
U8_T reserve:2; //预留位
U8_T CF_Plc_FullChgComplete:1; //车充电是否完成 0:未满1:完全充电完成
U8_T CF_Plc_ChgFinished:1; //桩充电状态 0:充电,1:充电停止请求
U8_T Message_Sequence:8; //通信流程消息序列
U8_T CR_Plc_ErrorCodeII:8; //错误代码II 见附录
U8_T CR_Plc_ErrorCode:8; //错误代码 见附录
U8_T ErrorCode_CPorPP_state:3; //CP或PP状态的错误代码 见附录
U8_T CR_PLc_EvErrCode:4; //EV车辆发送的错误代码 见附录
U8_T reserve2:1; //预留位
}BST_TMO_FLAG;
#else
U8_T u8_bst_data[4];
struct
{
U8_T ACHIEVE_AIM_SOC:2; //达到目标SOC
U8_T ACHIEVE_VOLT:2; //接收总电压设定值
U8_T ACHIEVE_CELL_VOLT:2; //达到单体电压设定值
U8_T CHARGER_STOP:2; //充电机主动终止
U8_T INSU_FAULT:2; //绝缘故障
U8_T DC_OUT_OVER_TEMP:2; //输出连接器过温故障
U8_T BMS_OVER_TEMP:2; //BMS元件、输出连接器过温故障
U8_T CONNECT_FAULT:2; //充电连接器故障
U8_T BATT_OVER_TEMP:2; //电池组温度过高故障
U8_T HV_RELAYS_FAULT:2; //高压继电器故障
U8_T CONN_CHECK_TWO_FAULT:2; //检测点2电压检测故障
U8_T OTHER_FAULT:2; //其它故障
U8_T OVER_CURR:2; //电流过大
U8_T OVER_VOLT:2; //电压过大
U8_T reserve:4; //预留位
}BST_TMO_FLAG;
#endif
}BMS_BST_DATA_U;
//BMS_CST报文数据联合体
typedef union
{
U8_T u8_cst_data[4];
struct
{
U8_T PILE_NORMAL_STOP:2; //达到充电机设定的条件中止
U8_T MAN_MADE_STOP:2; //人工中止
U8_T FAULT_STOP:2; //故障中止
U8_T BMS_STOP:2; //BMS主动中止
U8_T PILE_TEMP_OVER:2; //充电机过温故障
U8_T PILE_CONNET_FAULT:2; //充电连接器故障
U8_T PILE_TEMP_CVER2:2; //充电机内部过温故障
U8_T NOT_NEOUGH_ENERGY:2; //所需电量不能传送
U8_T EMERGENCY_STOP:2; //充电机急停故障
U8_T OTHER:2; //其他故障
U8_T reserve:4; //预留位
U8_T OVER_CURR:2; //电流过大
U8_T OVER_VOLT:2; //电压过大
U8_T reserve1:4; //预留位
}CST_TMO_FLAG;
}BMS_CST_DATA_U;
typedef enum
{
E_BMS_GET_PARA_GUN_CNT=0, //枪数量
E_BMS_GET_PARA_GB_EN, //国标使能
E_BMS_GET_PARA_SC_EN, //团标使能
E_BMS_GET_PARA_MAX_VOL, //最大电压 精度0.1
E_BMS_GET_PARA_MIN_VOL, //最小电压 精度0.1
E_BMS_GET_PARA_MAX_CUR, //最大电流 精度0.1
E_BMS_GET_PARA_MIN_CUR, //最小电流 精度0.1
E_BMS_GET_GUN_OUT_VOL, //输出电压 精度0.1
E_BMS_GET_GUN_OUT_CUR, //输出电流 精度0.1
E_BMS_GET_GUN_CHR_TIME, //充电时长 单位min
E_BMS_GET_GUN_CHR_POWER, //充电电量 单位0.1 kWh
E_BMS_GET_GUN_FIOW_CTRL, //BMS充电状态 0无意义 1空闲 2开始握手 3绝缘完成,开始握手辨识 4预充完成,发送cro_aa 5充电停止 发送cst 6停止充电完成 停止发送CSD 7BMS通讯超时
E_BMS_GET_GUN_FLOW_RENEW, //获取重连标识
E_BMS_CLEAR_GUN_FLOW_RENEW, //清空重连标识
}E_BMS_GET_PARA_TYPE;
typedef enum
{
E_BMS_PUT_DATA_BMS_GB_VER =0, //BMS 国标协议版本 0 2011版 1 2015版
E_BMS_PUT_DATA_BMS_STATE, //BMS 获取BMS状态 0无 1握手完成 2握手失败 3开始预充 4开始充电 5禁止充电 6停止充电 7报文接收超时
E_BMS_PUT_DATA_BMS_MAX_VOL, //BMS 最大充电电压值 精度 0.1V
E_BMS_PUT_DATA_BMS_MAX_CUR, //BMS 最大充电电流值 精度 0.1A
E_BMS_PUT_DATA_BMS_REAL_BATT_VOL, //BMS 当前电池电压值 精度 0.1V
E_BMS_PUT_DATA_BMS_NEED_VOL, //BMS 需求电压值 精度 0.1V
E_BMS_PUT_DATA_BMS_NEED_CUR, //BMS 需求电流值 精度 0.1A
E_BMS_PUT_DATA_BMS_CAN_SEND_CNT, //BMS 报文发送次数 参数值 0代表cem 1代表csd
E_BMS_PUT_DATA_BMS_STOP_RES, //BMS 停止原因 详看E_FAULT_INFO枚举内容 BMS部分
E_BMS_PUT_DATA_BMS_RECONN_FLAG, //BMS 重连标志 0不重连 1重连
E_BMS_PUT_DATA_GUN_SOC, //BMS SOC %
E_BMS_PUT_DATA_GUN_ONE_BATT_MAX_TEMP, //单体电池最大温度 度
E_BMS_PUT_DATA_GUN_ONE_BATT_MAX_VOL, //单体电池最大电压 0.1V
E_BMS_PUT_DATA_GUN_BATT_TYPE, //电池类型 01H,铅酸电池;02H:镍氢电池;03H:磷酸铁锂电池;04H:锰酸锂电池;05H:钴酸锂电池;
E_BMS_PUT_DATA_GUN_BATT_RATED_C10, //整车动力蓄电池系统额定容量,数据范围0~1000A.h,精度0.1
}E_BMS_PUT_DATA_TYPE;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_bms_add_timer(void);
void v_delay_set_tymer_val(void);
U16_T u16_bms_get_sys_para(U8_T gunNo , E_BMS_GET_PARA_TYPE e_type);
U16_T u16_bms_put_data(U8_T gunNo , E_BMS_PUT_DATA_TYPE e_type,U8_T u8_dataPara);
void v_bms_put_vin_str(U8_T u8_gunNo,U8_T *u8_vin,U8_T u8_len);
void v_bms_get_rt_time(Comm_Time *time);
U8_T u8_bms_get_cst(U8_T gunNo,U8_T *u8_data);
void v_bms_get_pile_nun(U8_T *pilenum);
#if CAN_PLC_FLAG //欧标 CAN转PLC使能
U8_T u8_bms_get_gunLink(U8_T gunNo); //can 转PLC 获取枪连接状态
#endif
#ifdef __cplusplus
}
#endif
#endif /* BMS_INTERFACE_H */
+2681
View File
File diff suppressed because it is too large Load Diff
+247
View File
@@ -0,0 +1,247 @@
#ifndef BMS_TASK_H
#define BMS_TASK_H
#ifdef __cplusplus /*C++¼C*/
extern "C" {
#endif
/*------ Exported includes(ͷļ) ----------------------*/
#include "main.h"
#include "bms/bms_interface.h"
#include "bms/sae1939.h"
/*------ Exported macro() -------------------------*/
// BMS֡
#define GB_BMS_ADDR 0xF4
#define GB_CHARGER_ADDR 0x56
#define GB_BMS_PGN_CHM 0x002600
#define GB_BMS_PGN_BHM 0x002700
#define GB_BMS_PGN_CRM 0x000100
#define GB_BMS_PGN_BRM 0x000200
#define GB_BMS_PGN_BCP 0x000600
#define GB_BMS_PGN_CTS 0x000700
#define GB_BMS_PGN_CML 0x000800
#define GB_BMS_PGN_BRO 0x000900
#define GB_BMS_PGN_CRO 0x000A00
#define GB_BMS_PGN_BCL 0x001000
#define GB_BMS_PGN_BCS 0x001100
#define GB_BMS_PGN_CCS 0x001200
#define GB_BMS_PGN_BSM 0x001300
#define GB_BMS_PGN_BMV 0x001500
#define GB_BMS_PGN_BMT 0x001600
#define GB_BMS_PGN_BSP 0x001700
#define GB_BMS_PGN_BST 0x001900
#define GB_BMS_PGN_CST 0x001A00
#define GB_BMS_PGN_BSD 0x001C00
#define GB_BMS_PGN_CSD 0x001D00
#define GB_BMS_PGN_BEM 0x001E00
#define GB_BMS_PGN_CEM 0x001F00
#define GB_BRM_TIMEOUT_FLAG 0x01 //bit0:BRMijʱ
#define GB_BCP_TIMEOUT_FLAG 0x02 //bit1:BCPijʱ
#define GB_BRO_TIMEOUT_FLAG 0x04 //bit2:BROijʱ
#define GB_BCS_TIMEOUT_FLAG 0x08 //bit3:BCSijʱ
#define GB_BCL_TIMEOUT_FLAG 0x10 //bit4:BCLijʱ
#define GB_BST_TIMEOUT_FLAG 0x20 //bit5:BSTijʱ
#define GB_BSD_TIMEOUT_FLAG 0x40 //bit6:BSDijʱ
//ʱʱ -- ʱʹã
#define BMS_TIM_10MS (1*1)
#define BMS_TIM_50MS (5*1+1)
#define BMS_TIM_250MS (29*1)
#define BMS_TIM_500MS (50*1)
#define BMS_TIM_1S ((100+11+29)*1) //͵һcem Ҫ250ms
#define BMS_TIM_5S ((500+22+135)*1)
#define BMS_TIM_10S ((1000+12+250)*1)
#define BMS_TIM_60S ((6000+12+1498)*1)
/*------ Exported struct() --------------------*/
//BMSڶʱ־
typedef struct
{
U8_T cnt50ms; //50ms
U8_T cnt250ms; //250ms
U8_T cnt500ms; //500ms
U16_T u16_global_timerCnt; //ʱʱ,ʱ,ʱ£յĿ0
U16_T u16_bcl_timerCnt; //׶ͬʱж֡Ľӳʱʱbsmʹglobal
U16_T u16_bro_timerCnt; //׶Σglobal¼bro_aa60sʱøñ¼bro5볬ʱ
}T_BMS_TIM;
typedef enum
{
cnt50ms = 0,
cnt250ms,
cnt500ms,
cnt_global,
cnt_bcl, //BSD״̬
cnt_bro_aa,
}E_BMS_TIM;
typedef enum
{
GB_BMS_STOP=0, //ֹͣ
GB_BMS_HANDSHAKE_TX_CHM, //׶ΣͳCHMȴճBHM
//GB_BMS_HANDSHAKE_RX_BHM_OK, //׶ΣͳCHMѽճֳɹBHMϵͳԵȴʼֱʶCRM
GB_BMS_HANDSHAKE_TX_CRM_00, //ֱʶ׶ΣCRM_00ȴBMSͳʶBRM
GB_BMS_HANDSHAKE_TX_CRM_AA, //ֱʶ׶ΣյBMSͳʶBRMCRM_AAȴյسBCP
GB_BMS_CFG_TX_CTS_CML, //ý׶ΣյسBCPCTSCMLȴBMSɳ׼BRO
GB_BMS_CFG_RX_BRO_AA, //ý׶ΣյسBRO_00CTSCMLȴBMSɳ׼BRO_AA
GB_BMS_CFG_TX_CRO_00, //ý׶ΣBMSɳ׼BRO_AAͳ׼ɱCRO_00ȴCRO-AA
GB_BMS_CFG_TX_CRO_AA, //ý׶ΣBMSɳ׼BROͳ׼ɱCRO_AAȴյܳ״̬BCS/سBCL
//GB_BMS_CHARGE_RX_BCL, //׶Σյܳ״̬BCSȴյسBCL
GB_BMS_CHARGE_STAGE, //׶ΣCCSѽձBCSBCLBST/BSM(Ƿ쳣)/BMV/BMT/BSP(ѡ)
GB_BMS_CHARGE_TX_CST, //׶ΣͳֹCSTȴBMSֹBSTBMSͳƱBSD
GB_BMS_FINISH_RX_BSD, //׶ΣյBSDͳֹCSD
//GB_BMS_FINISH_RX_BSD_OK, //׶ΣյBSD
//GB_BMS_FINISH_TX_CSD, //׶ΣͳͳƱCSD
GB_BMS_TX_CEM, //ʹ
}GB_BMS_STATE_E;
/* BMS״̬ */
typedef enum
{
BMS_STATE_NONE=0, //ͨţBMSͳƱݲ
BMS_STATE_HANDSHAKE_START_OK, //
BMS_STATE_HANDSHAKE_FAIL, //ʧ
BMS_STATE_PRECHARGE, //Ԥ
BMS_STATE_CHARGING, //
BMS_STATE_BSM_FORBID, //УBSMʾֹ
BMS_STATE_STOP, //ֹͣУCST
BMS_STATE_TIMEOUT, //ijʱ(CEM/BEM)
BMS_STATE_ERR, //ݴ
}BMS_STATE_E;
typedef enum
{
E_BMS_STOP_NULL =0,
E_BMS_STOP_1000 = 1000,
E_BMS_STOP_1001 = 1001,
E_BMS_STOP_1011 = 1011,
E_BMS_STOP_15000 = 15000,
E_BMS_STOP_15001,
E_BMS_STOP_15002,
E_BMS_STOP_15003,
E_BMS_STOP_15004,
E_BMS_STOP_15005,
E_BMS_STOP_15006,
E_BMS_STOP_15007,
E_BMS_STOP_15008,
E_BMS_STOP_15009,
E_BMS_STOP_15010,
E_BMS_STOP_15011,
E_BMS_STOP_15012,
E_BMS_STOP_15013,
E_BMS_STOP_15014,
E_BMS_STOP_15015,
E_BMS_STOP_15016,
E_BMS_STOP_15017,
E_BMS_STOP_15018,
E_BMS_STOP_15019,
E_BMS_STOP_15020,
E_BMS_STOP_15021,
E_BMS_STOP_15022,
E_BMS_STOP_15023,
E_BMS_STOP_15024,
E_BMS_STOP_15025,
E_BMS_STOP_15026,
E_BMS_STOP_15027,
E_BMS_STOP_15028,
E_BMS_STOP_15029,
E_BMS_STOP_15030,
E_BMS_STOP_15031,
}E_BMS_STOP_INFO;
//BMSЭ
typedef enum
{
BMS_2011=0, //2011BMSЭ
BMS_2015, //2015BMSЭ
BMS_2023, //2023BMSЭ
BMS_PLC, //CANתPLC
}BMS_STANDARD_E;
/* BMSʵʱ */
typedef struct
{
GB_BMS_STATE_E e_state; //BMS״̬
BMS_STATE_E e_real_state; // bmsʵʱ״̬ //BMS״̬
E_BMS_STOP_INFO e_stop_info; //bmsֹͣԭ
BMS_STANDARD_E e_bms_standard; //BMS汾Э 2011 2015
U8_T u8_reconn_flag; //ǣϣ1ʼ
U8_T u8_cst_send_cnt; //cstʹ
U8_T u8_csd_send_cnt; //csdʹ
U8_T u8_cem_send_cnt; //cemʹ
U8_T u8_bms_rx_flag[BMS_RECV_TYPE_NUM]; //bmsѽձʶ01 //0bhm 1brm 2bcp 3bro 4bcs 5bcl 6bsm 7bmv 8bmt 9bst 10bsd 11bem
U16_T u16_timeout_flag; //ձijʱ CEM
U16_T u16_stoptimeflag ; //SC1ֹͣͱ־
//=============================BMSʵ==============================
BMS_BHM_DATA_T t_bhm_data;
BMS_BRM_DATA_T t_brm_data;
BMS_BCP_DATA_T t_bcp_data;
BMS_BSD_DATA_T t_bsd_data;
BMS_BCL_DATA_T t_bcl_data;
BMS_BCS_DATA_T t_bcs_data;
BMS_BSM_DATA_T t_bsm_data;
BMS_BMV_DATA_T t_bmv_data; //δʹ
BMS_BMT_DATA_T t_bmt_data; //δʹ
BMS_BEM_DATA_U t_bem_data;
BMS_CEM_DATA_U t_cem_data;
BMS_BST_DATA_U t_bst_data;
BMS_CST_DATA_U t_cst_data;
}S_BMS_RT_DATA;
typedef struct
{
CAN_DATA_T pt_msg; //ձĴ洢
DLL_DATA_T pt_dll; //SAE̿
}S_GB_BMS_CAN;
typedef struct
{
U8_T u8_gunCnt; //ǹ
U8_T u8_gb_enable; //ʹ
U8_T u8_gun_type[GUN_MAX_CNT]; //ǹ 1
#if CAN_PLC_FLAG //ŷ CANתPLCʹ
U8_T u8_gun_linkState[GUN_MAX_CNT]; //ӱ־ 0δǹ 1Ѳǹ
#endif
T_BMS_TIM t_bms_tim[GUN_MAX_CNT]; //bmsڶʱ־
S_GB_BMS_CAN s_gb_bms_can[GUN_MAX_CNT]; //BMS CANͽսӿ
S_BMS_RT_DATA s_bms_rt_data[GUN_MAX_CNT]; //BMSʵʱ
}S_BMS_DATA;
/*------ Exported variables() -------------------*/
/*------ Exported function prototypes -------*/
void v_bms_task(void *arg);
#ifdef __cplusplus
}
#endif
#endif /* BMS_TASK_H */
+46
View File
@@ -0,0 +1,46 @@
# bms 模块说明
> 路径:`app/bms/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 负责 BMS 通信报文收发与协议状态机处理;
- 管理 BMS 充电阶段控制、超时处理与异常状态;
- 提供与流程控制模块对接的 BMS 数据接口。
## 2. 关键文件
- `bms_task.c/h`
- `bms_interface.c/h`
- `sae1939.c/h`
## 3. 功能说明
- 周期接收/发送 BMS 报文并更新运行状态;
- 维护关键定时器(超时、等待、状态切换计时);
- 将 BMS 结果映射为流程层可用的控制/状态数据。
## 4. 调度与对外接口
该模块由 FreeRTOS 任务 `BmsComm` 周期运行(单位:ms),周期来自 `app/app_init/app_init.c`
- `BmsComm``5ms`(任务入口:`v_bms_task(void *arg)`
对外接口(来自 `bms_task.h / bms_interface.h`,列出主要用法入口):
- `uint16_t u16_bms_get_sys_para(U8_T gunNo, E_BMS_GET_PARA_TYPE e_type)`:读取 BMS 系统参数
- `uint16_t u16_bms_put_data(U8_T gunNo, E_BMS_PUT_DATA_TYPE e_type, U8_T u8_dataPara)`:设置/上报 BMS 数据(按 e_type 不同)
- `void v_bms_put_vin_str(U8_T u8_gunNo, U8_T *u8_vin, U8_T u8_len)`:写入 VIN 字符串
- `void v_bms_get_rt_time(Comm_Time *time)`:读取 BMS/系统时间(取决于实现)
- `U8_T u8_bms_get_cst(U8_T gunNo, U8_T *u8_data)`:读取充电阶段/状态相关值
- `void v_bms_get_pile_nun(U8_T *pilenum)`:读取桩侧枚举/数量(命名以代码为准)
## 5. 维护建议
- 修改报文处理流程时需同步校验超时逻辑;
- 与流程控制交互字段应保持枚举/状态码一致。
## 6. 关联文档
- 主索引:[README](../../README.md)
+417
View File
@@ -0,0 +1,417 @@
#include "bms/sae1939.h"
/*
缩写 全称 翻译
DLL data link layer 数据链路层
*/
#define TIMEOUT_T1 750 //750ms
#define TIMEOUT_T2 1250 //1250ms
#define TIMEOUT_T3 1250 //1250ms
#define TIMEOUT_T4 1050 //1050ms
#define TP_CM_PGN 0x00EC00 //CM-多包传输的连接管理
#define TP_DT_PGN 0x00EB00 //DT-多包数据传输
#define TP_CM_RTS 16 //RTS-请求发送
#define TP_CM_CTS 17 //CTS-允许发送
#define TP_CM_ENDOFMSGACK 19 //结束应答
#define TP_CM_ABORT 255 //放弃连接
static U16_T u16_timer_count = 0; //定时其计数值
/**
* ************************************************************************
* @brief - 用于上层清除底层的多包状态
*
* @param[in] pt_dll_data - 1393层数据
*
*
* @author jiankalka
* @date 2025-08-12
* ************************************************************************
*/
void v_sae1393_clear_multi_pacet(DLL_DATA_T *pt_dll_data)
{
pt_dll_data->e_msg_state = NONE;
}
/**
* ************************************************************************
* @brief - SAE1393层发送函数,单包报文直接发送,多包报文此函数只传送RTS,多包传输处理在u32_sae1393_rx内进行
* 注:27930中 不存在多包发送,此处仅考虑发送单包数据
* @param[in] pt_dll_data - 1393层数据
* @param[in] pt_can_data - 要送的报文
*
* @return U32_T - 0:发送成功,1:发送失败
*
* @author jiankalka
* @date 2025-08-12
* ************************************************************************
*/
U32_T u32_sae1393_tx(DLL_DATA_T *pt_dll_data, CAN_DATA_T *pt_can_data)
{
CAN_DATA t_msg;
U32_T i;
U8_T SendCmd = ((pt_can_data->u32_pgn & 0x0000FF00) >> 8);
memset(&t_msg, 0, sizeof(CAN_DATA));
if (pt_can_data->u16_len <= 8)
{
//单包发送
t_msg.ID = u32_get_can_ID(pt_can_data->u8_pri,SendCmd,pt_can_data->u8_dest_addr,pt_can_data->u8_src_addr); //组帧ID
t_msg.Len = pt_can_data->u16_len;
for (i=0; i<t_msg.Len; i++)
t_msg.Data[i] = pt_can_data->u8_data[i];
return pt_dll_data->u32_can_tx(&t_msg); //这样定义有助于多个can口复用
}
return 1;
}
void v_delay_set_tymer_val(void)
{
u16_timer_count ++;
}
U32_T u32_delay_get_timer_val()
{
return u16_timer_count;
}
//计算时间间隔(单位:ms) (b+MAX-a)%MAX
U32_T u32_delay_time_elapse(U32_T tim3_cnt_start, U32_T tim3_cnt_end)
{
if(tim3_cnt_end >= tim3_cnt_start)
return (tim3_cnt_end-tim3_cnt_start)*10;
else
return (65535-tim3_cnt_end+tim3_cnt_start-1)*10;
}
/**
* ************************************************************************
* @brief - SAE1393层接收函数,接收到多包请求后 发送cts应答报文
*
* @param[in] ExtId - 帧ID
* @param[in] Pgn - 多包帧pgn
* @param[in] pack_num - 包总数
* @param[in] pack_cnt - 请求包数
* @param[in] t_tx_msg - 组帧报文内容
*
*
* @author jiankalka
* @date 2025-08-13
* ************************************************************************
*/
void v_sae1939_cts_send(U32_T ExtId,U32_T Pgn, U8_T pack_num , U8_T pack_cnt, CAN_DATA *t_tx_msg)
{
t_tx_msg->ID = (ExtId & 0xFFFF0000);
t_tx_msg->ID |= ((ExtId & 0xFF00) >> 8 ); //帧ID目标地址 源地址转换
t_tx_msg->ID |= ((ExtId & 0x00FF) << 8 );
t_tx_msg->ID = (t_tx_msg->ID & 0xFF00FFFF)|(TP_CM_PGN << 8); //将帧ID PGN赋值为0xEC
t_tx_msg->Len = 8;
t_tx_msg->Data[0] = TP_CM_CTS;
t_tx_msg->Data[1] = pack_num;
t_tx_msg->Data[2] = pack_cnt;
t_tx_msg->Data[3] = 0xFF;
t_tx_msg->Data[4] = 0xFF;
t_tx_msg->Data[5] = ((Pgn >> 16) & 0xFF);
t_tx_msg->Data[6] = ((Pgn >> 8 ) & 0xFF);
t_tx_msg->Data[7] = ((Pgn ) & 0xFF);
}
/**
* ************************************************************************
* @brief - sae1939 放弃连接发送
*
* @param[in] ExtID - 帧ID
* @param[in] Pgn - 多包帧报文对应 PGN
* @param[in] t_tx_msg - 发送can数据组包
*
*
* @author jiankalka
* @date 2025-08-13
* ************************************************************************
*/
void v_sae1939_abort_send(U32_T ExtID, U32_T Pgn, CAN_DATA *t_tx_msg)
{
t_tx_msg->ID = (ExtID & 0xFFFF0000);
t_tx_msg->ID |= ((ExtID & 0xFF00) >> 8 ); //帧ID目标地址 源地址转换
t_tx_msg->ID |= ((ExtID & 0x00FF) << 8 );
t_tx_msg->Len = 8;
t_tx_msg->Data[0] = TP_CM_ABORT;
t_tx_msg->Data[1] = 0xFF;
t_tx_msg->Data[2] = 0xFF;
t_tx_msg->Data[3] = 0xFF;
t_tx_msg->Data[4] = 0xFF;
t_tx_msg->Data[5] = ((Pgn >> 16) & 0xFF);
t_tx_msg->Data[6] = ((Pgn >> 8 ) & 0xFF);
t_tx_msg->Data[7] = ((Pgn ) & 0xFF);
}
/*************************************************************
函数名称: v_sae1939_EndofMsgACK_send
函数功能: SAE1393层接收函数,放弃连接函数
输入参数: pt_dll_data 接收报文
输出参数: t_tx_msg 发送报文
返回值 void
**************************************************************/
/**
* ************************************************************************
* @brief - 多包完成接收 发送结束应答帧
*
* @param[in] pt_dll_data - sae1939 控制数据
* @param[in] t_tx_msg - 发送组包CAN数据
*
*
* @author jiankalka
* @date 2025-08-13
* ************************************************************************
*/
void v_sae1939_EndofMsgACK_send(DLL_DATA_T *pt_dll_data, CAN_DATA *t_tx_msg)
{
t_tx_msg->ID = ((U32_T)(pt_dll_data->u8_pri & 0x07) << 26);
t_tx_msg->ID |= ((TP_CM_PGN & 0x0000FF00) << 8);
t_tx_msg->ID |= ((pt_dll_data->u8_dest_addr & 0xFF) );
t_tx_msg->ID |= ((pt_dll_data->u8_src_addr & 0x00FF) << 8 );
t_tx_msg->Len = 8;
t_tx_msg->Data[0] = TP_CM_ENDOFMSGACK;
t_tx_msg->Data[1] = (U8_T)(pt_dll_data->u16_len);
t_tx_msg->Data[2] = (U8_T)(pt_dll_data->u16_len>>8);
t_tx_msg->Data[3] = (pt_dll_data->u16_len+6)/7;
t_tx_msg->Data[4] = 0xFF;
t_tx_msg->Data[5] = (U8_T)(pt_dll_data->u32_pgn);
t_tx_msg->Data[6] = (U8_T)(pt_dll_data->u32_pgn>>8);
t_tx_msg->Data[7] = (U8_T)(pt_dll_data->u32_pgn>>16);
}
/*************************************************************
函数名称: u32_sae1393_rx
函数功能: SAE1393层接收函数,此函数需要在BMS处理层主循环内调用,
处理报文的接收和多包报文的发送,也处理1393层的超时事件
输入参数: pt_dll_data -- 1393层数据, rxMsg -- 待解析的报文
输出参数: pt_can_data -- 保存接收到的完整报文
返回值 0:接收成功,pt_can_data指向的报文可用;1:接收失败,pt_can_data指向的报文不可用
**************************************************************/
/**
* ************************************************************************
* @brief - SAE1393层接收函数,此函数需要在BMS处理层主循环内调用,处理报文的接收和多包报文的发送,也处理1393层的超时事件
*
* @param[in] pt_dll_data - 1393层控制数据
* @param[in] pt_can_data - CAN解析存储数据
* @param[in] t_rx_msg - 接收CAN数据-原报文数据
*
* @return U32_T - 1说明接收完整帧 0未接收完整帧
*
* @author jiankalka
* @date 2025-08-13
* ************************************************************************
*/
U32_T u32_sae1393_rx(DLL_DATA_T *pt_dll_data, CAN_DATA_T *pt_can_data, CAN_DATA t_rx_msg)
{
CAN_DATA t_tx_msg;
U32_T u32_ret = 0;
U32_T i, u32_pgn,u32_data;
S32_T j;
U8_T u8_src_addr, u8_dest_addr, u8_pri, u8_r_dp, u8_data_len;
u8_pri = ((t_rx_msg.ID & 0x1C000000) >> 26); //优先级
u8_r_dp = ((t_rx_msg.ID & 0x03000000) >> 24); //0
u32_pgn = ((t_rx_msg.ID & 0x00FF0000) >> 8); //pgn - 是分辨多包使用,而不是bms数据内的bgn
u8_src_addr = t_rx_msg.ID & 0xFF; //源地址
u8_dest_addr = ((t_rx_msg.ID & 0x0000FF00) >> 8); //目标
u8_data_len = t_rx_msg.Len; //数据长度
(void)u8_r_dp;
if (u32_pgn == TP_CM_PGN) //接收帧为多包连接管理帧
{
if(u8_data_len != 8) //管理帧帧长度都为8
return u32_ret;
switch(t_rx_msg.Data[0])
{
case TP_CM_RTS: //接收到对方发送的请求发送报文,回复CTS(允许发送)
if ((pt_dll_data->e_msg_state == NONE) || //一级 状态为空闲时,直接接收
(((pt_dll_data->e_msg_state == RX_WAIT_DATA_T2) || (pt_dll_data->e_msg_state == RX_WAIT_DATA_T1)) && //一级 接收状态下 重复接收相同帧
(pt_dll_data->u32_pgn == ((t_rx_msg.Data[7]<<16) | (t_rx_msg.Data[6]<<8) | t_rx_msg.Data[5])) && //二级 接受帧pgn与正在接受的数据相同
(pt_dll_data->u8_src_addr == u8_src_addr) && (pt_dll_data->u8_dest_addr == u8_dest_addr))) //二级 接收帧 目标地址和发送地址相同
{
//回复CTS
u32_data = (U32_T)((t_rx_msg.Data[7]<<16) | (t_rx_msg.Data[6]<<8) | t_rx_msg.Data[5]);
v_sae1939_cts_send(t_rx_msg.ID ,u32_data, t_rx_msg.Data[3],1, &t_tx_msg);
if (pt_dll_data->u32_can_tx(&t_tx_msg) == 0) //发送成功
{
pt_dll_data->e_msg_state = RX_WAIT_DATA_T2;
pt_dll_data->u32_time = u32_delay_get_timer_val();
pt_dll_data->u32_pgn = (t_rx_msg.Data[7]<<16) | (t_rx_msg.Data[6]<<8) | t_rx_msg.Data[5]; //记录接收多包报文的pgn,有多个多包时也先收此pgn
pt_dll_data->u8_src_addr = u8_src_addr;
pt_dll_data->u8_dest_addr = u8_dest_addr;
pt_dll_data->u8_pri = u8_pri;
pt_dll_data->u16_len = ((U16_T)(t_rx_msg.Data[2])<<8) | t_rx_msg.Data[1];
pt_dll_data->u8_packet_cnt = t_rx_msg.Data[3];
pt_dll_data->u8_retry = 0;
//等待接收数据,将缓存区置0xFF
for (i=0; i<SAE1939_MULTI_PACKET_MAX_NUM; i++)
{
memset(pt_dll_data->t_packet[i].u8_data, 0xFF, 7);
pt_dll_data->t_packet[i].u8_flag = 0;
}
}
}
else //放弃连接
{
//回复Abort
u32_data = (U32_T)((t_rx_msg.Data[7]<<16) | (t_rx_msg.Data[6]<<8) | t_rx_msg.Data[5]);
v_sae1939_abort_send(t_rx_msg.ID , u32_data, &t_tx_msg);
pt_dll_data->u32_can_tx(&t_tx_msg);
pt_dll_data->e_msg_state = NONE;
}
break;
case TP_CM_ABORT: //结束应答
if ((pt_dll_data->e_msg_state == RX_WAIT_DATA_T2) ||
(pt_dll_data->e_msg_state == RX_WAIT_DATA_T1))
{
if ((u8_src_addr == pt_dll_data->u8_src_addr) &&
(pt_dll_data->u8_dest_addr == u8_dest_addr) &&
(pt_dll_data->u32_pgn == ((t_rx_msg.Data[7]<<16) | (t_rx_msg.Data[6]<<8) | t_rx_msg.Data[5])))
{
pt_dll_data->e_msg_state = NONE;
}
}
break;
default:
break;
}
}
else if (u32_pgn == TP_DT_PGN) //接收多包数据帧
{
if ((t_rx_msg.Len == 8) && //长度为8
(pt_dll_data->u8_src_addr == u8_src_addr) && (pt_dll_data->u8_dest_addr == u8_dest_addr) && //接收报文目标、发送地址与报存的一致
((pt_dll_data->e_msg_state == RX_WAIT_DATA_T2) || (pt_dll_data->e_msg_state == RX_WAIT_DATA_T1)) && //正在接收多包数据状态下
(t_rx_msg.Data[0] != 0)) //包序号不为0
{
pt_dll_data->e_msg_state = RX_WAIT_DATA_T1;
pt_dll_data->u32_time = u32_delay_get_timer_val();
pt_dll_data->t_packet[t_rx_msg.Data[0]-1].u8_flag = 1;
memcpy(pt_dll_data->t_packet[t_rx_msg.Data[0]-1].u8_data, &(t_rx_msg.Data[1]), 7);
for (i=0; i<pt_dll_data->u8_packet_cnt; i++) //找到未接收的包,即下一个要求上送的包
{
if (pt_dll_data->t_packet[i].u8_flag == 0)
break;
}
//多包数据接收完成
if (i >= pt_dll_data->u8_packet_cnt)
{
pt_dll_data->e_msg_state = NONE;
u32_ret = 1;
pt_can_data->u32_pgn = pt_dll_data->u32_pgn;
pt_can_data->u8_src_addr = u8_src_addr;
pt_can_data->u8_dest_addr = u8_dest_addr;
pt_can_data->u8_pri = u8_pri;
pt_can_data->u16_len = pt_dll_data->u16_len;
if (pt_can_data->u16_len > (SAE1939_MULTI_PACKET_MAX_NUM*7))
pt_can_data->u16_len = (SAE1939_MULTI_PACKET_MAX_NUM*7);
for (i=0; i<pt_can_data->u16_len; i++)
pt_can_data->u8_data[i] = pt_dll_data->t_packet[i/7].u8_data[i%7];
//发送EndofMsgACK
v_sae1939_EndofMsgACK_send(pt_dll_data,&t_tx_msg);
pt_dll_data->u32_can_tx(&t_tx_msg);
}
else if (pt_dll_data->t_packet[pt_dll_data->u8_packet_cnt-1].u8_flag == 1) //有包未接收,但是最后一包接收了,说明有丢包的 则重新申请丢的包
{
pt_dll_data->e_msg_state = RX_WAIT_DATA_T2;
pt_dll_data->u32_time = u32_delay_get_timer_val();
for (j=pt_dll_data->u8_packet_cnt-1; j>=0; j--)
{
if (pt_dll_data->t_packet[j].u8_flag == 0)
break;
}
//回复CTS
u32_data = pt_dll_data->u32_pgn;
t_rx_msg.ID = (t_rx_msg.ID & 0xFF00FFFF)|(TP_CM_PGN << 8); //将帧ID PGN赋值为0xEC
v_sae1939_cts_send(t_rx_msg.ID ,u32_data, j-i+1,i+1, &t_tx_msg);
pt_dll_data->u32_can_tx(&t_tx_msg);
}
}
}
else //单包数据
{
u32_ret = 1; //返回解析成功
pt_can_data->u32_pgn = u32_pgn;
pt_can_data->u8_src_addr = u8_src_addr;
pt_can_data->u8_dest_addr = u8_dest_addr;
pt_can_data->u8_pri = u8_pri;
pt_can_data->u16_len = t_rx_msg.Len;
for (i=0; i<t_rx_msg.Len; i++)
pt_can_data->u8_data[i] = t_rx_msg.Data[i];
}
#if 1
//超时处理
switch (pt_dll_data->e_msg_state)
{
case RX_WAIT_DATA_T2:
if (u32_delay_time_elapse(pt_dll_data->u32_time, u32_delay_get_timer_val()) > TIMEOUT_T2)
{
//回复Abort
u32_data = ((U32_T)(pt_dll_data->u8_pri & 0x07) << 26);
u32_data |= ((TP_CM_PGN & 0x0000FF00) << 8);
u32_data |= ((U32_T)(pt_dll_data->u8_src_addr) <<8 );
u32_data |= pt_dll_data->u8_dest_addr;
v_sae1939_abort_send(u32_data , pt_dll_data->u32_pgn, &t_tx_msg);
pt_dll_data->u32_can_tx(&t_tx_msg);
pt_dll_data->e_msg_state = NONE;
}
break;
case RX_WAIT_DATA_T1:
if (u32_delay_time_elapse(pt_dll_data->u32_time, u32_delay_get_timer_val()) > TIMEOUT_T1)
{
//回复Abort
u32_data = ((U32_T)(pt_dll_data->u8_pri & 0x07) << 26);
u32_data |= ((TP_CM_PGN & 0x0000FF00) << 8);
u32_data |= ((U32_T)(pt_dll_data->u8_src_addr) <<8 );
u32_data |= pt_dll_data->u8_dest_addr;
v_sae1939_abort_send(u32_data , pt_dll_data->u32_pgn, &t_tx_msg);
pt_dll_data->u32_can_tx(&t_tx_msg);
pt_dll_data->e_msg_state = NONE;
}
break;
default:
break;
}
#endif
return u32_ret;
}
+81
View File
@@ -0,0 +1,81 @@
#ifndef SAE1939_H
#define SAE1939_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "bsp_include.h"
/*------ Exported macro(宏定义) -------------------------*/
#define SAE1939_MULTI_PACKET_MAX_NUM 80 //80*7 -- bmv需要512字节
typedef U32_T (*U32_CAN_RX)(CAN_DATA *s_canData);
typedef U32_T (*U32_CAN_TX)(CAN_DATA *s_canData);
/*------ Exported struct(结构体定义) --------------------*/
typedef struct
{
U32_T u32_pgn; //参数组编号
U8_T u8_src_addr; //源地址
U8_T u8_dest_addr; //目标地址
U8_T u8_pri; //优先级
U16_T u16_len; //长度,不能大于SAE1939_MULTI_PACKET_MAX_NUM*7
U8_T u8_data[SAE1939_MULTI_PACKET_MAX_NUM*7]; //数据
}CAN_DATA_T;
typedef enum
{
NONE=0, //无多包发送和接收
// //作为多包发送方使用
// TX_WAIT_CTS_T3, //己发送RTS,等待接收CTS,超时T3=1250ms
// TX_WAIT_CTS_T4, //等待接收保持连接CTS,超时T4=1050ms
//作为多包接收方使用
RX_WAIT_DATA_T2, //己发送CTS,等待接收数据,超时T2=1250ms
RX_WAIT_DATA_T1, //接收数据阶段,等待接收下一个数据包,超时T1=750ms
}MULTI_MSG_STATE_E;
//此数据结构在BMS通信开始化进行初始化,初始化完成后BMS层不能再修改此结构
typedef struct
{
MULTI_MSG_STATE_E e_msg_state; //多包解析流程状态
U8_T u8_packet_cnt; //包数量
U8_T u8_src_addr; //源地址
U8_T u8_dest_addr; //目标地址
U8_T u8_pri; //优先级
U8_T u8_retry; //重试次数
U16_T u16_len; //长度,不能大于 80*7
U32_T u32_pgn; //参数组编号
U32_T u32_time; //用于多包数据处理的计时
struct
{
U8_T u8_data[7]; //数据
U8_T u8_flag; //标记,0:未接收或未发送,1:己接收或己发送
}t_packet[SAE1939_MULTI_PACKET_MAX_NUM];
U32_CAN_TX u32_can_tx; //驱动层发送函数
U32_CAN_RX u32_can_rx; //驱动层接收函数 - 从缓冲区里读,不只接收一帧
}DLL_DATA_T;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_sae1393_clear_multi_pacet(DLL_DATA_T *pt_dll_data);
U32_T u32_sae1393_tx(DLL_DATA_T *pt_dll_data, CAN_DATA_T *pt_can_data);
U32_T u32_sae1393_rx(DLL_DATA_T *pt_dll_data, CAN_DATA_T *pt_can_data, CAN_DATA t_rx_msg);
#ifdef __cplusplus
}
#endif
#endif /* SAE1939_H */
+387
View File
@@ -0,0 +1,387 @@
#include "card_ctrl/card_ctrl.h"
#include "usart.h"
#include "publicdata/publicdata.h"
#include "app_init/app_init.h"
#include "mylog/mylog.h"
#define STX 0x02 //开始符
#define ETX 0x03 //结束符
#define CARD_RET_Y 0x59 //操作成功
#define CARD_RET_N 0x4E //操作失败
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
#define CARD_COM_ID E_USART_1
#elif SYSTEM_TYPE == SYSTEM_TYPE_CCU621_M
#define CARD_COM_ID E_USART_0
#else
#define CARD_COM_ID E_USART_0
#endif
#define D_Print_flag (0) //测试打印
#define CARD_BUF_LENGTH 50
#define CARD_RECV_WAIT_TICK 100 //每次等待5个TICK,也就是50ms
#define CARD_RECV_TIMEOUT_CNT 5 //超时计数最大值为10,超时值为500ms
typedef struct
{
U16_T u16_len; //数据域长度
U8_T u8_cmd; //命令字
U8_T u8_para; //命令字参数
U8_T u8_state; //状态字
U8_T u8_data[32]; //数据域
}CARD_CMD_T;
#if 1 //收发接口
/**
* ************************************************************************
* @brief - 数据发送接口函数 发送
*
* @param[in] u8_sendBuf - 发送数据地址
* @param[in] u16_len - 发送数据长度
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
void v_card_send_data(U8_T *u8_sendBuf , U16_T u16_len)
{
u16_usart_send(CARD_COM_ID,u8_sendBuf, u16_len);
#if D_Print_flag //调试打印
printf("card send,len is [%d]\r\n", u16_len);
printf_hex("--",u8_sendBuf,u16_len);
#endif
}
/**
* ************************************************************************
* @brief - 数据接收接口函数 接收
*
* @param[in] u8_recvBuf - 接收数据地址
* @param[in] u16_len - 预期接收数据长度
*
* @return U16_T - 已接收数据长度
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
U16_T u16_card_recv_data(U8_T *u8_recvBuf ,U16_T u16_len)
{
U16_T recv_len;
memset(u8_recvBuf , 0 , u16_len);
recv_len = u16_usart_recv(CARD_COM_ID,u8_recvBuf,u16_len);
#if D_Print_flag //调试打印
if(recv_len == 0) return 0;
printf("card recv,len is [%d]\r\n", recv_len);
printf_hex("--",u8_recvBuf,recv_len);
#endif
return recv_len;
}
#endif
#if 1 //内部控制
/**
* ************************************************************************
* @brief - 组发送帧函数
*
* @param[in] pt_cmd - 发送内容 结构体
*
*
* @author jiankalka
* @date 2025-11-28
* ************************************************************************
*/
static void v_card_send_cmd(CARD_CMD_T *pt_cmd) //发送
{
U32_T i;
U8_T u8_card_tx_buf[CARD_BUF_LENGTH] = {0};
u8_card_tx_buf[0] = STX;
u8_card_tx_buf[1] = (U8_T)(pt_cmd->u16_len>>8);
u8_card_tx_buf[2] = (U8_T)(pt_cmd->u16_len);
u8_card_tx_buf[3] = pt_cmd->u8_cmd;
u8_card_tx_buf[4] = pt_cmd->u8_para;
for (i=0; i<pt_cmd->u16_len-2; i++)
u8_card_tx_buf[5+i] = pt_cmd->u8_data[i];
u8_card_tx_buf[pt_cmd->u16_len+3] = ETX;
u8_card_tx_buf[pt_cmd->u16_len+4] = u8_calculate_bcc(u8_card_tx_buf, pt_cmd->u16_len+4);
v_card_send_data(u8_card_tx_buf , pt_cmd->u16_len+5);
}
/**
* ************************************************************************
* @brief - 接收数据解帧操作
*
* @param[in] u16_byte_len - 数据长度
* @param[in] u8_card_rx_buf - 数据内容
* @param[in] pt_rx_cmd - 解析存储位置
*
* @return U8_T - 0 解析失败 1解析成功
*
* @author jiankalka
* @date 2025-11-28
* ************************************************************************
*/
static U8_T u8_card_recv_unpack(U16_T u16_byte_len,U8_T *u8_card_rx_buf ,CARD_CMD_T *pt_rx_cmd)
{
U8_T j;
pt_rx_cmd->u16_len = u16_byte_len;
pt_rx_cmd->u8_cmd = u8_card_rx_buf[3]; //命令字
pt_rx_cmd->u8_para = u8_card_rx_buf[4]; //命令参数
//pt_cmd->u8_state = u8_card_rx_buf[i+5]; //状态字
if(pt_rx_cmd->u8_cmd == 0x34)
{
if(pt_rx_cmd->u8_para == 0x30) //寻卡
{
pt_rx_cmd->u8_state = u8_card_rx_buf[5];
return 1;
}
else if(pt_rx_cmd->u8_para == 0x32) //验证密码
{
pt_rx_cmd->u8_state = u8_card_rx_buf[6];
return 1;
}
else if(pt_rx_cmd->u8_para == 0x33) //读卡号
{
if(u16_byte_len == 0x15) //判长度或判返回状态字是否为0x59
{
pt_rx_cmd->u8_state = u8_card_rx_buf[7];
for(j=0; j<u16_byte_len-5; j++)
{
pt_rx_cmd->u8_data[j] = u8_card_rx_buf[8+j];
}
return 1;
}
}
}
return 0;
}
/**
* ************************************************************************
* @brief - 读卡器收发控制
*
* @param[in] pt_tx_cmd - 发送数据内容
* @param[in] pt_rx_cmd - 对应接收数据内容
*
* @return U32_T -
*
* @author jiankalka
* @date 2025-11-28
* ************************************************************************
*/
static U32_T u32_card_unpacket(CARD_CMD_T *pt_tx_cmd, CARD_CMD_T *pt_rx_cmd) //解帧
{
U16_T u16_byte_len, u16_rx_len, u16_timeout = 0;
U32_T i, j;
U8_T u8_card_rx_buf[CARD_BUF_LENGTH];
while (u16_timeout++ < CARD_RECV_TIMEOUT_CNT) //是否超时 10
{
v_card_send_cmd(pt_tx_cmd);
mSleep(CARD_RECV_WAIT_TICK); //等待接收
u16_rx_len = u16_card_recv_data(u8_card_rx_buf, CARD_BUF_LENGTH);
if (u16_rx_len <= 5)
continue;
for (i=0; i<u16_rx_len-5; i++)
{
if ((u8_card_rx_buf[i] == STX) //STX
&& (u8_card_rx_buf[i+3] == pt_tx_cmd->u8_cmd) //命令字
&& (u8_card_rx_buf[i+4] == pt_tx_cmd->u8_para)) //命令参数
{
u16_byte_len = ((u8_card_rx_buf[i+1]<<8) | u8_card_rx_buf[i+2]);
if ((u16_rx_len - i) < (u16_byte_len + 5))
{
//logMsg("接收到的为非完整报文\n");
break;
}
else
{
if ((u8_card_rx_buf[i+u16_byte_len+3] == ETX) &&
(u8_card_rx_buf[i+u16_byte_len+4] == u8_calculate_bcc(&u8_card_rx_buf[i], u16_byte_len+4)))
{
//logMsg("card 解帧 校验成功\n");
return u8_card_recv_unpack(u16_byte_len , &u8_card_rx_buf[i] , pt_rx_cmd);
}
}
}
//logMsg("STX校验未通过\n");
}
}
return 0;
}
/**
* ************************************************************************
* @brief - 寻卡操作
*
*
* @return U8_T - 0失败 1成功
*
* @author jiankalka
* @date 2025-11-28
* ************************************************************************
*/
static U8_T u8_card_seek_card() //寻卡
{
CARD_CMD_T t_card_tx_cmd; //命令
CARD_CMD_T t_card_rx_cmd; //命令
//发送寻卡命令
memset(&t_card_tx_cmd, 0, sizeof(t_card_tx_cmd));
t_card_tx_cmd.u16_len = 2;
t_card_tx_cmd.u8_cmd = 0x34;
t_card_tx_cmd.u8_para = 0x30;
//解包
memset(&t_card_rx_cmd, 0, sizeof(t_card_rx_cmd));
if (u32_card_unpacket(&t_card_tx_cmd, &t_card_rx_cmd) == 1)
{
if (t_card_rx_cmd.u8_state == CARD_RET_Y)
{
return 1;
}
}
return 0;
}
/**
* ************************************************************************
* @brief - 验证密码
*
* @param[in] u8_sectorNo - 扇区号
* @param[in] u8_key - 验证密码
* @param[in] u8_keyLen - 密码长度
*
* @return U8_T - 0失败 1成功
*
* @author jiankalka
* @date 2025-11-28
* ************************************************************************
*/
static U8_T u8_check_keys(U8_T u8_sectorNo ,const U8_T *u8_key ,U8_T u8_keyLen) //验证密码
{
CARD_CMD_T t_card_tx_cmd; //命令
CARD_CMD_T t_card_rx_cmd; //命令
if(u8_key == NULL || u8_keyLen > 30) return 0;
//发送验证密码命令
memset(&t_card_tx_cmd, 0, sizeof(t_card_tx_cmd));
t_card_tx_cmd.u16_len = 3+u8_keyLen;
t_card_tx_cmd.u8_cmd = 0x34;
t_card_tx_cmd.u8_para = 0x32;
t_card_tx_cmd.u8_data[0] = u8_sectorNo; //扇区号
memcpy(&t_card_tx_cmd.u8_data[1] ,u8_key , u8_keyLen); //扇区密码
//解包
memset(&t_card_rx_cmd, 0, sizeof(t_card_rx_cmd));
if (u32_card_unpacket(&t_card_tx_cmd, &t_card_rx_cmd) == 1)
{
if (t_card_rx_cmd.u8_state == CARD_RET_Y)
{
return 1;
}
}
return 0;
}
/**
* ************************************************************************
* @brief - d读取卡号
*
* @param[in] u8_sectorNo - 扇区号
* @param[in] u8_blockNo -
* @param[in] u8_cardId - 卡号缓存
* @param[in] u8_cardLen - 卡号缓存长度
*
* @return U8_T -
*
* @author jiankalka
* @date 2025-12-04
* ************************************************************************
*/
static U8_T u8_read_card(U8_T u8_sectorNo ,U8_T u8_blockNo ,U8_T *u8_cardId , U8_T u8_cardLen) //读卡号
{
char i,index=0;
unsigned char cardNo_hex[16] = {0};
unsigned char cardNo_ascii[16] = {0};
unsigned char cardNum[18] = {0};
CARD_CMD_T t_card_tx_cmd; //命令
CARD_CMD_T t_card_rx_cmd; //命令
memset(&t_card_tx_cmd, 0, sizeof(t_card_tx_cmd));
t_card_tx_cmd.u16_len = 4;
t_card_tx_cmd.u8_cmd = 0x34;
t_card_tx_cmd.u8_para = 0x33;
t_card_tx_cmd.u8_data[0] = u8_sectorNo;//扇区号
t_card_tx_cmd.u8_data[1] = u8_blockNo;//块号
//解包
memset(&t_card_rx_cmd, 0, sizeof(t_card_rx_cmd));
if(u32_card_unpacket(&t_card_tx_cmd, &t_card_rx_cmd) == 1)
{
if(u8_cardId == NULL) return 1;
memset(u8_cardId, 0, u8_cardLen);
if(t_card_rx_cmd.u16_len-5 > u8_cardLen)
return 0;
memcpy(&u8_cardId[0], &t_card_rx_cmd.u8_data[0], t_card_rx_cmd.u16_len-5);
return t_card_rx_cmd.u16_len-5;
}
return 0;
}
#endif
/**
* ************************************************************************
* @brief - 读卡号
*
* @param[in] u8_data - 卡号接收数据 不接收只读卡时填NULL
* @param[in] u8_len - 读取缓存长度 长度要预留'/0'的位置
*
* @return U8_T - 0 未读取到 非0 已读取到 且为卡号长度
*
* @author jiankalka
* @date 2025-11-28
* ************************************************************************
*/
U8_T u8_read_card_id(U8_T *u8_data , U8_T u8_len)
{
U8_T u8_sectorNo = 0; //扇区
U8_T u8_blockNo = 1; //块
U8_T u8_recvlen;
const U8_T u8_key[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; //卡密钥
U8_T u8_card[20];
if(u8_card_seek_card() &&
u8_check_keys(u8_sectorNo,u8_key,sizeof(u8_key)))
{
u8_recvlen = u8_read_card(u8_sectorNo,u8_blockNo,u8_card,20);
//printf_hex("card",u8_card,u8_recvlen);
bcd_to_ascii((const U8_T *)u8_card, 8, (char *)u8_data, u8_len); //模块读取卡号最大16位
printf("读取卡号成功 %s\r\n",u8_data);
return u8_recvlen;
}
//printf("读取卡号失败\r\n");
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef CARD_CTRL_H
#define CARD_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
U8_T u8_read_card_id(U8_T *u8_data , U8_T u8_len);
#ifdef __cplusplus
}
#endif
#endif /* CARD_CTRL_H */
@@ -0,0 +1,189 @@
# collect_ctrl 模块说明
> 路径:`app/collect_ctrl/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 数据采集任务(DataColl)实现:周期调度、任务锁、喂狗、周期运行日志;
- 维护“遥信/遥测(YXYC)”实时数据镜像,并对外提供统一读取接口;
- 维护“遥控(YK)”命令缓存,提供对外设置接口与任务内统一执行入口;
- 提供温度/枪温采集的循环框架(片选切换 + 周期读取 + 缓存),为后续接入真实 ADC/SPI 采集预留入口。
## 2. 关键文件
- `collect_task.c`
- `collect_task.h`
- `yk_ctrl.c`
- `yk_ctrl.h`
- `led_ctrl.c`
- `led_ctrl.h`
## 3. 功能说明
### 3.1 任务模型(DataColl
- **任务 ID 与调度**:任务 ID 使用 `TASK_ID_DataColl`;睡眠周期通过 `MY_GET_SLEEP_TIME(TASK_ID_DataColl)` 获取(宏 `DATACOLL_SLEEP_TIME_MS()`)。
- **任务锁**:通过 `u8_task_lock_ctrl()` 实现互斥(宏 `DATACOLL_LOCK_INIT/TAKE/GIVE`),保证“写入更新/对外读取”之间的数据一致性。
- **喂狗**:任务循环内调用 `v_wdt_setFlag(TASK_ID_DataColl)`(宏 `DATACOLL_FEED_DOG()`)。
- **运行日志**:按 `TASK_RUN_PRINT_TIME` 周期尝试获取锁并输出运行日志(避免与数据更新互相干扰)。
对应入口:`v_collect_task(void *arg)`、初始化:`v_collect_task_init()``v_init_collect()`
### 3.2 实时数据镜像(遥信/遥测,YXYC)
模块内部维护全局镜像 `sys_yxyc_data`(类型 `YXYC_DATA`),包含:
- **整桩数据**`SYS_RT_DATA s_sys_rt_data`
- **单枪数据**`GUN_RT_DATA s_gun_rt_data[GUN_MAX_CNT]`
- **枪数量/适配项**`u8_gunNo``u8_gunLockNeg[]`(用于“电子锁输入取反”适配)
数据更新入口:
- `v_update_gun_yxyc(GUN_RT_DATA* p_gun, U8_T u8_gunNo)`:更新指定枪的遥信/遥测字段(内部会根据 `u8_gunLockNeg` 处理锁输入取反)。
- 采集来源按“对接外部接口”集中在四个函数内(后续需要改点位时,直接改这些 switch-case 即可):
- `u8_coll_get_yx_sys()`:整桩遥信(目前主要来自**副板 DI13~DI19**,通过 `SubComm` 缓存读取)。
- `u8_coll_get_yx_gun()`:单枪遥信(目前来自**主板 DI1~DI10** 等)。
- `f32_coll_get_yc_gun()`:单枪遥测(目前来自 ADC 缓存读取 `f_get_gun_data(E_ADC_TYPE_xxx)`)。
- `f32_coll_get_yc_sys()`:整桩遥测(目前用进/出风口温度缓存映射为桩温1/2)。
对外读取接口(均带任务锁保护):
- `U8_T u8_get_yxyc_data(SYS_RT_DATA *pile_data, GUN_RT_DATA *gun_data, U8_T u8_gunNo)`:一次性拷贝获取整桩 + 指定枪数据(任一入参为 NULL 则跳过该部分)。
- `U8_T u8_get_gun_yx_data(E_YX_GUN gun_yx_type, U8_T u8_gunNo)`:读取单枪遥信(如枪连接、电子锁、接触器、熔断器、归位等)。
- `F32_T f32_get_gun_yc_data(E_YC_GUN gun_yc_type, U8_T u8_gunNo)`:读取单枪遥测(母线电压、枪温、回路电流等)。
- `U8_T u8_get_sys_yx_data(E_YX_SYS sys_yx_type)`:读取整桩遥信(急停、门禁、水浸、防雷、烟感、交流接触器/断路器等)。
- `F32_T f32_get_sys_yc_data(E_YC_SYS sys_yc_type)`:读取整桩遥测(例如桩温 1/2)。
### 3.3 遥控命令缓存与执行(YK)
为了避免外部模块“直接打 DO”导致状态不可追踪/动作不可控,本模块提供“遥控命令缓存 → 任务内统一执行”的机制:
- **代码位置**YK 逻辑已从 `collect_task.c` 拆分为独立文件 `yk_ctrl.c/.h`,由采集任务周期调用执行。
- **缓存结构**`yk_ctrl.c` 内部维护遥控缓存(`U_YK_CTRL_DATA`),按 `YK_DATA_NUM` 维护多路控制命令(直流接触器1/2、母联、交流接触器、风扇、泄放继电器、枪 A/B 电子锁等)。
- **对外设置接口**`v_yk_ctrl_impl(E_YK_CTRL_TYPE e_type, E_CMD e_cmd)`,仅更新缓存,不直接控制 IO。
- **任务内执行入口**
- `v_yk_ctrl_init()`:清空缓存;
- `v_yk_ctrl_cheak()`:遍历缓存并调用 `v_yk_ctrl()` 执行动作;
- `v_yk_ctrl(E_YK_CTRL_TYPE e_type, E_CMD e_cmd)`:底层执行:
- **主板 DO**:直流接触器/母联/交流接触器/风机/泄放等走 `DOx()`
- **主板 PK1/PK2**:电子锁走 `ELOCK_A()/ELOCK_B()`
- **副板 DO11/DO15/DO16**12/24V 切换与辅源控制通过 `v_sub_comm_yk_set()` 下发到副板。
> 注意:`v_yk_ctrl_cheak()` 需要在 DataColl 任务周期里调用才符合“统一时序执行”的设计初衷;若外部在其他上下文频繁写入缓存,应评估并发一致性(当前 `u_yk_data` 未加锁)。
### 3.4 温度/枪温采集框架(ADC)
模块内提供“片选切换 + 周期读取 + 缓存”的枪温/温度采集框架:
- `S_ADC_GUN_CTRL s_adc_gun_ctrl`:维护通道切换状态(`flag_set/flag_read`)、计时(`u16_timer_cnt`)、缓存数组:
- `f_adc_data[6]`:4 路枪温 + 2 路进/出风口温度;
- `f_adc_dc_vol[4]`:直流母线电压采集缓存(目前示例代码保留)。
- `v_set_adc()`:按状态机切换温度传感器通道(调用 `select_temp_channel()`)。
- `v_gun_temp_loop(U16_T u16_leep_time)`:按固定节拍读取 `adc_readval()` 并推进通道切换。
- `float f_get_gun_data(E_ADC_TYPE e_type)`:统一读取缓存并做简单限幅(如温度超过 `TEMP_MAX_NUM` 则返回 0)。
> 目前枪连接状态 `u8_get_gunLink()` 以及部分 ADC 电压采集为“预留对接点”,系统集成时按硬件方案补齐实现即可。
### 3.5 指示灯状态缓存(LED
为适配“本项目不执行 `v_led_do_ctrl()` 硬件灯控动作”的现状,`led_ctrl.c` 目前主要承担**状态计算与缓存**职责:
- `v_led_state_refresh()`:按现有规则计算 A/B 两把枪 LED 状态,并写入全局缓存;
- `u8_led_state_get(u8_gunNo)`:读取指定枪 LED 状态缓存(`E_LED_STATE_TYPE` 编码);
- `v_led_ctrl(u16_sleepTime)`:不再直接下发硬件动作,内部改为刷新缓存并维护周期计数;
- `v_led_init()`:初始化时清零缓存并执行一次刷新。
`sub_comm_task.c` 在组 0x04 枪信息帧时通过上述接口读取 LED 状态,避免在 `sub_comm` 内重复维护一套灯态判定逻辑。
### 3.6 遥控/遥信/遥测点位与板卡位置表(主板/副板)
说明:
- **主板 DI/DO/PK**:见 `BSP/sys_drv_init.h``DI1~DI12``DO1~DO10``ELOCK_A/B(PK1/PK2)`)。
- **副板 DI/DO**:通过 `app/sub_comm/sub_comm_task.c` 的 USART0 协议接入:
- DI 状态命令 `0x01`:更新 `u8_sub_comm_yx_get(E_SUB_YX_xx)`(当前用 bit-map 解包)。
- DO 控制命令 `0x02`:下发 `v_sub_comm_yk_set(E_SUB_YK_DOxx, CMD_ON/OFF)`(当前 2 字节 bit-map 编码)。
#### 3.6.1 整桩遥信(系统 YX,来源:副板 DI13~DI19
```text
YX_PILE_EMERGENCY 急停开关状态 = 副板 DI13 = u8_sub_comm_yx_get(E_SUB_YX_13)
YX_PILE_DOOR 门禁开关状态 = 副板 DI14 = u8_sub_comm_yx_get(E_SUB_YX_14)
YX_PILE_WARTER 水浸开关状态 = 副板 DI15 = u8_sub_comm_yx_get(E_SUB_YX_15)
YX_PILE_KM 交流接触器状态 = 副板 DI16 = u8_sub_comm_yx_get(E_SUB_YX_16)
YX_PILE_SPD 防雷器状态 = 副板 DI17 = u8_sub_comm_yx_get(E_SUB_YX_17)
YX_PILE_QF 交流进线开关状态 = 副板 DI18 = u8_sub_comm_yx_get(E_SUB_YX_18)
YX_PILE_BUS_KM 母联直流接触器状态 = 副板 DI19 = u8_sub_comm_yx_get(E_SUB_YX_19)
YX_PILE_SMOKE 烟感状态 = 预留/待对接(当前默认 0)
```
对应实现:`collect_task.c``u8_coll_get_yx_sys()`
#### 3.6.2 单枪遥信(枪 YX,来源:主板 DI1~DI10 等)
```text
YX_GUN_LOCK 枪电子锁状态 = 主板 DI1/DI7 (A枪/B枪) (注:实际电子锁输出为 PK1/PK2,锁“状态”输入点位需按硬件对接)
YX_GUN_DC_KM 直流接触器状态 = 主板 DI2/DI6
YX_GUN_RELIF 泄放继电器状态 = 主板 DI4/DI9
YX_GUN_DC_FUSE 直流熔断器状态 = 主板 DI3/DI8
YX_GUN_HOMING 枪归位状态 = 主板 DI5/DI10
YX_GUN_AUXPOWER 低压辅源状态 = 预留/待对接(当前默认 0)
YX_GUN_LINK 枪连接状态 = u8_get_gunLink(u8_gunNo)(对接点)
```
对应实现:`collect_task.c``u8_coll_get_yx_gun()`
#### 3.6.3 单枪遥测(枪 YC,来源:ADC 缓存 `f_get_gun_data()`
```text
ADC_GUN_VOLT_PLUS 直流母线正对地电压 = f_get_gun_data(E_ADC_TYPE_GUN_DC_VOL_A1/B1)
ADC_GUN_VOLT_MINUS 直流母线负对地电压 = f_get_gun_data(E_ADC_TYPE_GUN_DC_VOL_A2/B2)
ADC_GUN_BAT_PLUS 接触器外侧正对地电压 = 复用 DC_VOL_A1/B1(后续按硬件改)
ADC_GUN_BAT_MINUS 接触器外侧负对地电压 = 复用 DC_VOL_A2/B2(后续按硬件改)
ADC_GUN_TEMP_PLUS 枪正极温度 = f_get_gun_data(E_ADC_TYPE_GUN_TEMP_A1/B1)
ADC_GUN_TEMP_MINUS 枪负极温度 = f_get_gun_data(E_ADC_TYPE_GUN_TEMP_A2/B2)
ADC_GUN_MVI 回路电流 = 预留/待对接(当前默认 0)
```
对应实现:`collect_task.c``f32_coll_get_yc_gun()`
#### 3.6.4 整桩遥测(系统 YC,来源:进/出风口温度缓存)
```text
ADC_PILE_TEMP1 桩温1 = f_get_gun_data(E_ADC_TYPE_FAN_IN_TEMP)
ADC_PILE_TEMP2 桩温2 = f_get_gun_data(E_ADC_TYPE_FAN_OUT_TEMP)
```
对应实现:`collect_task.c``f32_coll_get_yc_sys()`
#### 3.6.5 遥控(YK,主板 DO/PK + 副板 DO
```text
E_KY_TYPE_DC_CONT1 直流接触器1 = 主板 DO3
E_KY_TYPE_DC_CONT2 直流接触器2 = 主板 DO6
E_KY_TYPE_DC_CONT_BUS 直流母联接触器 = 主板 DO9
E_KY_TYPE_AC_CONT 交流接触器 = 主板 DO7
E_KY_TYPE_FAN_CONT 散热风机 = 主板 DO10
E_KY_TYPE_RELIEF_A A枪泄放 = 主板 DO2
E_KY_TYPE_RELIEF_B B枪泄放 = 主板 DO5
E_KY_TYPE_GUN_LOCK1 A枪电子锁 = 主板 PK1 (ELOCK_A)
E_KY_TYPE_GUN_LOCK2 B枪电子锁 = 主板 PK2 (ELOCK_B)
E_KY_TYPE_V12_24_SWITCH 12/24V切换 = 副板 DO11SubComm 下发)
E_KY_TYPE_AUX_POWER1 辅源1 = 副板 DO15SubComm 下发)
E_KY_TYPE_AUX_POWER2 辅源2 = 副板 DO16SubComm 下发)
```
对应实现:`yk_ctrl.c``v_yk_ctrl()`
## 4. 维护建议
- **更新与读取要保持一致性**:对外读取接口已加任务锁;如果增加“周期更新整桩/整枪数据”的逻辑,建议也在同一把 `DATACOLL` 锁内完成。
- **按职责分层扩展**:建议按“采集(驱动对接)/数据整形(单位换算、限幅、去抖)/对外发布(接口、共享区)”分层维护,避免在任务循环里堆叠业务。
- **日志节流**:保持 `TASK_RUN_PRINT_TIME` 这类周期日志输出,避免高频打印影响实时性。
- **遥控并发**:若外部可能在中断或多任务环境写 `v_yk_ctrl_impl()`,建议补充锁/临界区,或改为消息队列/事件投递。
## 5. 关联文档
- 主索引:[README](../../README.md)
- 备注:`collect_task.c` 头部注释里提到的 `collectTask_readme.md` 当前目录下未找到;如该设计文档实际存在,建议补充到本目录并在此处建立链接,便于维护对照。
+920
View File
@@ -0,0 +1,920 @@
#include "collect_ctrl/collect_task.h"
#include "collect_ctrl/yk_ctrl.h"
#include "collect_ctrl/led_ctrl.h"
#include "sub_comm/sub_comm_task.h"
#include "app_init/app_init.h"
#include "app_init/task_lock.h"
#include "mylog/mylog.h"
#include "publicdata/publicdata.h"
#include "wdt_task/wdt_task.h"
#include <stdio.h>
#include "usart.h"
#include "adc/adc_temp.h"
#include "adc/adc_spi5_batVoltCurr.h"
#include "adc/adc_spi3_insuVolt.h"
// 数据采集任务ID
#define DATACOLL_TASK_ID (TASK_ID_DataColl)
// 获取数据采集任务睡眠时间(毫秒)
#define DATACOLL_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(DATACOLL_TASK_ID))
// 初始化数据采集任务锁
#define DATACOLL_LOCK_INIT() (u8_task_lock_ctrl(DATACOLL_TASK_ID, TASK_LOCK_OP_INIT))
// 获取数据采集任务锁
#define DATACOLL_LOCK_TAKE() (u8_task_lock_ctrl(DATACOLL_TASK_ID, TASK_LOCK_OP_TAKE))
// 释放数据采集任务锁
#define DATACOLL_LOCK_GIVE() (u8_task_lock_ctrl(DATACOLL_TASK_ID, TASK_LOCK_OP_GIVE))
// 喂狗(数据采集任务)
#define DATACOLL_FEED_DOG() (v_wdt_setFlag(DATACOLL_TASK_ID))
// 数据采集任务日志记录
#define DATACOLL_LOG(fmt, ...) MYLOG_MSG(DATACOLL_TASK_ID, (fmt), ##__VA_ARGS__)
#define TEMP_MAX_NUM (2000) //温度最大值 超过则为0
#if 1 //结构定义及变量声明
typedef struct
{
U8_T u8_gunNo; //枪数量
U8_T u8_gunLockNeg[GUN_MAX_CNT]; //枪锁取反标志
SYS_RT_DATA s_sys_rt_data; //整桩遥信、遥测
GUN_RT_DATA s_gun_rt_data[GUN_MAX_CNT]; //充电枪相关遥信、遥测
} YXYC_DATA; //系统遥信、遥测结构体
typedef struct
{
U8_T flag_set; //设置标志
U8_T flag_read; //读取标志
U8_T gunLink_exit[2]; //枪连接中断
U16_T u16_timer_cnt; //周期计时
float f_adc_data[6]; //4路枪温数据 + 2路温度监测
float f_adc_dc_vol[4]; //直流母线adc电压采集值
float f_adc_bat_vol[2]; //直流外侧adc电压采集值
float f_adc_dc_cur[2]; //直流外侧adc电压采集值
float f_adc_gunLink[2]; //枪连接 adc
} S_ADC_GUN_CTRL;
S_ADC_GUN_CTRL s_adc_gun_ctrl;
YXYC_DATA sys_yxyc_data; //系统遥信、遥测
#endif
#if 1 //外部参数获取函数
/********************************
* 函数说明: 获取枪数量
* *参数说明:
* 输入参数:void
* 输出参数:U8_T u8_ret 枪数量
* 备注:
**********************************/
static U8_T u8_coll_get_gun_num()
{
U8_T u8_ret = 0;
u8_ret = g_t_share_data.t_sys_fix_cfg.u8_gunCnt; //获取充电枪数量,需调用外部参数
return u8_ret;
}
/****************************************************
*功能说明: 获取充电桩对应遥信状态或数据(功能框架)
*参数说明:
* 输入参数: E_YX_SYS sys_yx_type , 要读取的整桩遥信状态或数据
* 输出参数: U8_T 遥信状态或数据(当前默认返回 0,待系统集成对接真实数据来源)
*备注:
****************************************************/
static U8_T u8_coll_get_yx_sys(E_YX_SYS sys_yx_type)
{
U8_T u8_ret = 0;
/*
* 数据来源在此处对接(先给默认值,后续你可以直接改每个 case 的赋值):
* - 例如对接 DIu8_ret = DIxx;
* - 或对接共享数据:u8_ret = g_t_share_data.xxx;
* - 或对接 CAN 状态:u8_ret = xxx_get();
*/
switch (sys_yx_type)
{
case YX_PILE_EMERGENCY: //急停(副板 DI13
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_13);
break;
case YX_PILE_DOOR: //门禁(副板 DI14
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_14);
break;
case YX_PILE_WARTER: //水浸(副板 DI15
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_15);
break;
case YX_PILE_KM: //交流接触器状态(副板 DI16
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_16);
break;
case YX_PILE_SPD: //防雷器状态(副板 DI17
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_17);
break;
case YX_PILE_QF: //交流进线开关状态(副板 DI18)
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_18);
break;
case YX_PILE_BUS_KM: //母联直流接触器状态(副板 DI19)
u8_ret = u8_sub_comm_yx_get(E_SUB_YX_19);
break;
case YX_PILE_SMOKE: //烟感(
u8_ret = 0;
break;
default:
u8_ret = 0;
break;
}
return u8_ret;
}
/****************************************************
*功能说明: 获取充电枪对应遥信状态或数据
*参数说明:
* 输入参数: E_YX_GUN e_yx_gun , 要读取的充电枪状态或数据
U8_T u8_gunNo 枪号
* 输出参数: -1 失败 u8_ret 充电枪对应遥信遥测状态或数据
*备注:
****************************************************/
static U8_T u8_coll_get_yx_gun(E_YX_GUN gun_yx_type, U8_T u8_gunNo)
{
U8_T u8_ret;
switch(gun_yx_type)
{
case YX_GUN_LINK: //枪连接状态
u8_ret = u8_get_gunLink(u8_gunNo);
break;
case YX_GUN_LOCK: //枪电子锁状态
u8_ret = u8_gunNo ? DI7 : DI1;
break;
case YX_GUN_DC_KM: //直流输出接触器状态
u8_ret = u8_gunNo ? DI6 : DI2;
break;
case YX_GUN_RELIF: //泄放继电器状态
u8_ret = u8_gunNo ? DI9 : DI4;;
break;
case YX_GUN_DC_FUSE: //直流熔断器状态
u8_ret = u8_gunNo ? DI8 : DI3;
break;
case YX_GUN_AUXPOWER: //低压辅源状态
u8_ret = u8_gunNo ? AUX_B : AUX_A;
break;
case YX_GUN_HOMING: //充电枪归位
u8_ret = u8_gunNo ? DI10 : DI5;
break;
}
return u8_ret;
}
/****************************************************
*功能说明: 获取充电枪对应遥测状态或数据
*参数说明:
* 输入参数: E_YC_GUN e_yc_gun , 要读取的充电枪状态或数据
U8_T u8_gunNo 枪号
* 输出参数: F32_T -1 失败 f32_ret 充电枪对应遥信遥测状态或数据
*备注:
****************************************************/
static F32_T f32_coll_get_yc_gun(E_YC_GUN gun_yc_type, U8_T u8_gunNo)
{
F32_T f32_ret;
switch(gun_yc_type)
{
case ADC_GUN_VOLT_PLUS: //直流母线正对地电压
f32_ret = ((u8_gunNo == 0) ? f_get_gun_data(E_ADC_TYPE_GUN_DC_VOL_A1) : f_get_gun_data(E_ADC_TYPE_GUN_DC_VOL_B1));
break;
case ADC_GUN_VOLT_MINUS: //直流母线负对地电压
f32_ret = ((u8_gunNo == 0) ? f_get_gun_data(E_ADC_TYPE_GUN_DC_VOL_A2) : f_get_gun_data(E_ADC_TYPE_GUN_DC_VOL_B2));
break;
case ADC_GUN_MVI: //直流回路测量电流
f32_ret = ((u8_gunNo == 0) ? f_get_gun_data(E_ADC_TYPE_GUN_MVI_A) : f_get_gun_data(E_ADC_TYPE_GUN_MVI_B));
break;
case ADC_GUN_BAT: //接触器外侧电压
f32_ret = ((u8_gunNo == 0) ? f_get_gun_data(E_ADC_TYPE_GUN_BAT_VOL_A1) : f_get_gun_data(E_ADC_TYPE_GUN_BAT_VOL_B1));
break;
case ADC_GUN_TEMP_PLUS: //充电枪正极温度
f32_ret = ((u8_gunNo == 0) ? f_get_gun_data(E_ADC_TYPE_GUN_TEMP_A1) : f_get_gun_data(E_ADC_TYPE_GUN_TEMP_B1));
break;
case ADC_GUN_TEMP_MINUS: //充电枪负极温度
f32_ret = ((u8_gunNo == 0) ? f_get_gun_data(E_ADC_TYPE_GUN_TEMP_A2) : f_get_gun_data(E_ADC_TYPE_GUN_TEMP_B2));
break;
}
return f32_ret;
}
/****************************************************
*功能说明: 获取充电桩对应遥信遥测状态或数据
*参数说明:
* 输入参数: E_YX_SYS sys_yc_type , 要读取的整桩遥测状态或数据
U8_T u8_gunNo 枪号
* 输出参数: F32_T -1 失败 f32_ret 充电枪对应遥测状态或数据
*备注:
****************************************************/
F32_T f32_coll_get_yc_sys(E_YC_SYS sys_yc_type)
{
F32_T f32_ret;
switch(sys_yc_type)
{
case ADC_PILE_TEMP1: //桩温1
f32_ret = f_get_gun_data(E_ADC_TYPE_FAN_IN_TEMP);
break;
case ADC_PILE_TEMP2: //桩温2
f32_ret = f_get_gun_data(E_ADC_TYPE_FAN_OUT_TEMP);
break;
}
return f32_ret;
}
#endif
#if 1 //整桩遥信/遥测更新
/****************************************************
*功能说明: 更新桩体相关遥信、遥测数据
*参数说明:
* 输入参数: SYS_RT_DATA* p_sys 整桩遥测遥信结构体
* 输出参数: void
*备注:
****************************************************/
static void v_updata_sys_yxyc(SYS_RT_DATA* p_sys)
{
if (p_sys == NULL)
return;
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return;
/* 整桩相关遥信状态更新(数据来源当前默认 0,仅保留功能框架) */
p_sys->yx_sys_data.bit_yx_sys_data.yx_emergency = u8_coll_get_yx_sys(YX_PILE_EMERGENCY);
p_sys->yx_sys_data.bit_yx_sys_data.yx_door = u8_coll_get_yx_sys(YX_PILE_DOOR);
p_sys->yx_sys_data.bit_yx_sys_data.yx_water = u8_coll_get_yx_sys(YX_PILE_WARTER);
p_sys->yx_sys_data.bit_yx_sys_data.yx_smoke = u8_coll_get_yx_sys(YX_PILE_SMOKE);
p_sys->yx_sys_data.bit_yx_sys_data.yx_bus_KM = u8_coll_get_yx_sys(YX_PILE_BUS_KM);
p_sys->yx_sys_data.bit_yx_sys_data.yx_spd = u8_coll_get_yx_sys(YX_PILE_SPD);
p_sys->yx_sys_data.bit_yx_sys_data.yx_KM = u8_coll_get_yx_sys(YX_PILE_KM);
p_sys->yx_sys_data.bit_yx_sys_data.yx_QF = u8_coll_get_yx_sys(YX_PILE_QF);
/* 整桩相关遥测状态更新 */
p_sys->f32_yc_sys_temp1 = f32_coll_get_yc_sys(ADC_PILE_TEMP1);
p_sys->f32_yc_sys_temp2 = f32_coll_get_yc_sys(ADC_PILE_TEMP2);
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
}
#endif
#if 1 //对外接口函数
/****************************************************
*功能说明: 获取遥信遥测数据结构体
*参数说明:
* 输入参数: SYS_RT_DATA *pile_data , 桩数据
GUN_RT_DATA *gun_data , 枪数据
U8_T u8_gunNo 枪号
* 输出参数: U8_T 0 失败 1成功
*最后修改时间: 20230923
****************************************************/
U8_T u8_get_yxyc_data(SYS_RT_DATA *pile_data, GUN_RT_DATA *gun_data, U8_T u8_gunNo)
{
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return 0;
if(pile_data != NULL)
{
memset(pile_data, 0, sizeof(SYS_RT_DATA));
memcpy(pile_data, &sys_yxyc_data.s_sys_rt_data, sizeof(SYS_RT_DATA));
}
if(gun_data != NULL)
{
memset(gun_data, 0, sizeof(GUN_RT_DATA));
memcpy(gun_data, &sys_yxyc_data.s_gun_rt_data[u8_gunNo], sizeof(GUN_RT_DATA));
}
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
return 1;
}
/****************************************************
*功能说明: 获取充电枪对应遥信状态或数据
*参数说明:
* 输入参数: E_YX_GUN gun_yx_type , 要读取的充电枪状态或数据
U8_T u8_gunNo 枪号
* 输出参数: U8_T -1 失败 u8_ret 充电枪对应遥信状态或数据
*备注:
****************************************************/
U8_T u8_get_gun_yx_data(E_YX_GUN gun_yx_type, U8_T u8_gunNo)
{
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return 0xFFU;
GUN_RT_DATA *gun_data = &sys_yxyc_data.s_gun_rt_data[u8_gunNo];
U8_T u8_ret;
switch(gun_yx_type)
{
case YX_GUN_LINK: //枪连接状态
u8_ret = gun_data->u8_gunlink_state;
break;
case YX_GUN_LOCK: //枪电子锁状态
u8_ret = gun_data->yx_gun_data.bit_yx_gun_data.yx_gun_lock;
break;
case YX_GUN_DC_KM: //母线直流输出接触器状态
case YX_GUN_DC_KM_NEG:
u8_ret = gun_data->yx_gun_data.bit_yx_gun_data.yx_switch;
break;
case YX_GUN_RELIF: //泄放继电器状态
u8_ret = gun_data->yx_gun_data.bit_yx_gun_data.yx_relief_swich;
break;
case YX_GUN_DC_FUSE: //直流熔断器状态
u8_ret = gun_data->yx_gun_data.bit_yx_gun_data.yx_fuse;
break;
case YX_GUN_AUXPOWER: //低压辅源状态
u8_ret = gun_data->yx_gun_data.bit_yx_gun_data.yx_gun_power;
break;
case YX_GUN_HOMING: //充电枪归位
u8_ret = gun_data->yx_gun_data.bit_yx_gun_data.yx_gun_homing;
break;
}
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
return u8_ret;
}
/****************************************************
*功能说明: 获取充电枪对应遥测状态或数据
*参数说明:
* 输入参数: E_YC_GUN gun_yc_type , 要读取的充电枪状态或数据
U8_T u8_gunNo 枪号
* 输出参数: F32_T -1 失败 f32_ret 充电枪对应遥测状态或数据
*备注:
****************************************************/
F32_T f32_get_gun_yc_data(E_YC_GUN gun_yc_type, U8_T u8_gunNo)
{
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return -1;
GUN_RT_DATA *gun_data = &sys_yxyc_data.s_gun_rt_data[u8_gunNo];
F32_T f32_ret;
switch(gun_yc_type)
{
case ADC_GUN_VOLT_PLUS: //直流母线正对地电压
f32_ret = gun_data->f32_yc_gun_vol_pos;
break;
case ADC_GUN_VOLT_MINUS: //直流母线负对地电压
f32_ret = gun_data->f32_yc_gun_vol_neg;
break;
case ADC_GUN_MVI: //直流回路测量电流
f32_ret = gun_data->f32_yc_gun_curr;
break;
case ADC_GUN_BAT: //接触器外侧正对地电压
f32_ret = gun_data->f32_yc_bat_vol_pos;
break;
case ADC_GUN_TEMP_PLUS: //充电枪正极温度
f32_ret = gun_data->f32_yc_gun_temp_pos;
break;
case ADC_GUN_TEMP_MINUS: //充电枪负极温度
f32_ret = gun_data->f32_yc_gun_temp_neg;
break;
}
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
return f32_ret;
}
/****************************************************
*功能说明: 外部使用 yk功能接口,使外部不直接进行遥控动作,便于保存遥控状态且确保遥控执行完成
*参数说明:
* 输入参数: E_YK_CTRL_TYPE e_type 控制数据类型
* E_CMD e_cmd 控制数据内容 CMD_OFF = 0, ST_ON = !CMD_OFF
* 输出参数: void
*备注:
****************************************************/
/****************************************************
*功能说明: 获取充电桩对应遥信状态或数据
*参数说明:
* 输入参数: E_YX_SYS sys_yx_type , 要读取的整桩遥信状态或数据
* 输出参数: U8_T -1 失败 u8_ret 充电桩对应遥信状态或数据
*备注:
****************************************************/
U8_T u8_get_sys_yx_data(E_YX_SYS sys_yx_type)
{
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return 0xFFU;
SYS_RT_DATA *sys_data = &sys_yxyc_data.s_sys_rt_data;
U8_T u8_ret;
switch(sys_yx_type)
{
case YX_PILE_EMERGENCY: //急停
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_emergency;
break;
case YX_PILE_DOOR: //门禁
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_door;
break;
case YX_PILE_WARTER: //水浸
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_water;
break;
case YX_PILE_SPD: //防雷器
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_spd;
break;
case YX_PILE_SMOKE: //烟感
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_smoke;
break;
case YX_PILE_BUS_KM: //母联接触器
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_bus_KM;
break;
case YX_PILE_KM: //交流输入接触器
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_KM;
break;
case YX_PILE_QF: //交流输入断路器
u8_ret = sys_data->yx_sys_data.bit_yx_sys_data.yx_QF;
break;
}
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
return u8_ret;
}
/****************************************************
*功能说明: 获取充电桩对应遥测状态或数据
*参数说明:
* 输入参数: E_YC_SYS sys_yc_type , 要读取的整桩遥测状态或数据
* 输出参数: F32_T -1 失败 f32_ret 充电桩对应遥测状态或数据
*备注:
****************************************************/
F32_T f32_get_sys_yc_data(E_YC_SYS sys_yc_type)
{
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return -1;
SYS_RT_DATA *sys_data = &sys_yxyc_data.s_sys_rt_data;
F32_T f32_ret;
switch(sys_yc_type)
{
case ADC_PILE_TEMP1: //桩温1
f32_ret = sys_data->f32_yc_sys_temp1;
break;
case ADC_PILE_TEMP2: //桩温2
f32_ret = sys_data->f32_yc_sys_temp2;
break;
}
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
return f32_ret;
}
#endif
#if 1 //遥信数据更新
/****************************************************
*功能说明: 更新充电枪相关遥信、遥测数据
*参数说明:
* 输入参数: GUN_RT_DATA* p_gun 充电枪遥测遥信结构体
U8_T u8_gunNo 枪号
* 输出参数: void
*备注:
****************************************************/
void v_update_gun_yxyc(GUN_RT_DATA* p_gun, U8_T u8_gunNo)
{
if (DATACOLL_LOCK_TAKE() != 1U) // 获取数据采集锁
return;
U8_T gunlockNeg = sys_yxyc_data.u8_gunLockNeg[u8_gunNo]; //获取充电枪电子锁取反状态
/****充电枪遥信状态更新****/
p_gun->u8_gunlink_state = u8_coll_get_yx_gun(YX_GUN_LINK, u8_gunNo); //充电枪连接状态,需调用外部接口
if(gunlockNeg)
p_gun->yx_gun_data.bit_yx_gun_data.yx_gun_lock = (u8_coll_get_yx_gun(YX_GUN_LOCK, u8_gunNo) ? 0 : 1); //电磁锁状态
else
p_gun->yx_gun_data.bit_yx_gun_data.yx_gun_lock = u8_coll_get_yx_gun(YX_GUN_LOCK, u8_gunNo); //电磁锁状态,需调用外部接口
p_gun->yx_gun_data.bit_yx_gun_data.yx_switch = u8_coll_get_yx_gun(YX_GUN_DC_KM, u8_gunNo); //直流输出接触器状态,需调用外部接口
p_gun->yx_gun_data.bit_yx_gun_data.yx_relief_swich = u8_coll_get_yx_gun(YX_GUN_RELIF, u8_gunNo); //直流回路泄放继电器状态,需调用外部接口
p_gun->yx_gun_data.bit_yx_gun_data.yx_fuse = u8_coll_get_yx_gun(YX_GUN_DC_FUSE, u8_gunNo); //直流回路熔断器状态,需调用外部接口
p_gun->yx_gun_data.bit_yx_gun_data.yx_gun_power = u8_coll_get_yx_gun(YX_GUN_AUXPOWER, u8_gunNo); //充电枪辅助电源状态,需调用外部接口
p_gun->yx_gun_data.bit_yx_gun_data.yx_gun_homing = u8_coll_get_yx_gun(YX_GUN_HOMING, u8_gunNo); //充电枪归位状态,需调用外部接口
/****充电枪遥测状态更新****/
p_gun->f32_yc_gun_vol_pos = f32_coll_get_yc_gun(ADC_GUN_VOLT_PLUS, u8_gunNo); //直流母线正对地电压,需调用外部接口
p_gun->f32_yc_gun_vol_neg = f32_coll_get_yc_gun(ADC_GUN_VOLT_MINUS, u8_gunNo); //直流母线负对地电压,需调用外部接口
p_gun->f32_yc_gun_curr = f32_coll_get_yc_gun(ADC_GUN_MVI, u8_gunNo); //直流回路测量电流,需调用外部接口
p_gun->f32_yc_bat_vol_pos = f32_coll_get_yc_gun(ADC_GUN_BAT, u8_gunNo); //接触器外侧正对地电压,需调用外部接口
p_gun->f32_yc_gun_temp_pos = f32_coll_get_yc_gun(ADC_GUN_TEMP_PLUS, u8_gunNo); //充电枪正极温度,需调用外部接口
p_gun->f32_yc_gun_temp_neg = f32_coll_get_yc_gun(ADC_GUN_TEMP_MINUS, u8_gunNo); //充电枪负极温度,需调用外部接口
DATACOLL_LOCK_GIVE(); // 释放数据采集锁
}
#endif
#if 1 //遥测数据更新
//枪温采集是使用一个采集口,通过片选的方式控制
void v_set_adc()
{
//温度采集选通,
/* 选择温度传感器通道的函数
通道0 TEMP5
通道1 VOL
通道2 VOL
通道3 TEMP6
通道4 TEMP1
通道5 TEMP4
通道6 TEMP2
通道7 TEMP3
*/
switch (s_adc_gun_ctrl.flag_set)
{
case 0: //设置采集A枪正极温度值 2
{
select_temp_channel(2);
s_adc_gun_ctrl.flag_read = s_adc_gun_ctrl.flag_set;
s_adc_gun_ctrl.flag_set ++;
break;
}
case 1: //设置采集A枪负极温度值 1
{
select_temp_channel(1);
s_adc_gun_ctrl.flag_read = s_adc_gun_ctrl.flag_set;
s_adc_gun_ctrl.flag_set ++;
break;
}
case 2: //设置采集B枪正极温度值
{
select_temp_channel(4);
s_adc_gun_ctrl.flag_read = s_adc_gun_ctrl.flag_set;
s_adc_gun_ctrl.flag_set ++;
break;
}
case 3: //设置采集B枪负极温度值
{
select_temp_channel(6);
s_adc_gun_ctrl.flag_read = s_adc_gun_ctrl.flag_set;
s_adc_gun_ctrl.flag_set ++;
break;
}
case 4: //设置出风口温度检测值
{
select_temp_channel(5);
s_adc_gun_ctrl.flag_read = s_adc_gun_ctrl.flag_set;
s_adc_gun_ctrl.flag_set ++;
break;
}
case 5: //设置进风口温度检测值
{
select_temp_channel(7);
s_adc_gun_ctrl.flag_read = s_adc_gun_ctrl.flag_set;
s_adc_gun_ctrl.flag_set = 0;
break;
}
default:
{
s_adc_gun_ctrl.flag_set = 0;
break;
}
}
}
/****************************************************
*功能说明: 枪温循环处理函数,用于控制片选信号并获取对应位置的温度
*参数说明:
* 输入参数: U16_T u16_leep_time 执行周期
* 输出参数: void
*备注:
*作者: jiankalka
*日期: 2025-08-07
****************************************************/
void v_gun_temp_loop(U16_T u16_leep_time)
{
if(s_adc_gun_ctrl.u16_timer_cnt % NUM_500 == 0) //100ms
{
// s_adc_gun_ctrl.f_adc_data[s_adc_gun_ctrl.flag_read] = adc_readval(); //获取枪温
// v_set_adc();
}
/* SPI5:CC1 / 外侧电压 / 回路电流,每次调用实时 SPI 采样 */
s_adc_gun_ctrl.f_adc_gunLink[0] = spi5_read_analog_voltage(0); /* A枪 CC1 */
s_adc_gun_ctrl.f_adc_gunLink[1] = spi5_read_analog_voltage(1); /* B枪 CC1 */
s_adc_gun_ctrl.f_adc_bat_vol[0] = spi5_read_analog_voltage(4); /* A枪外侧电压 */
s_adc_gun_ctrl.f_adc_bat_vol[1] = spi5_read_analog_voltage(2); /* B枪外侧电压 */
s_adc_gun_ctrl.f_adc_dc_cur[0] = spi5_read_analog_voltage(7); /* A枪回路电流 */
s_adc_gun_ctrl.f_adc_dc_cur[1] = spi5_read_analog_voltage(6); /* B枪回路电流 */
if((s_adc_gun_ctrl.u16_timer_cnt / u16_leep_time) % NUM_4 == 0) /* 4个周期采一次,10ms 任务约 40ms */
{
s_adc_gun_ctrl.f_adc_dc_vol[0] = spi3_read_analog_voltage(1, 0); //A枪正极adc电压采集值
s_adc_gun_ctrl.f_adc_dc_vol[2] = spi3_read_analog_voltage(2, 0); //B枪正极adc电压采集值
s_adc_gun_ctrl.f_adc_dc_vol[1] = spi3_read_analog_voltage(1, 1); //A枪负极adc电压采集值
s_adc_gun_ctrl.f_adc_dc_vol[3] = spi3_read_analog_voltage(2, 1); //B枪负极adc电压采集值
}
//周期计数
if(s_adc_gun_ctrl.u16_timer_cnt < 0xFF00)
s_adc_gun_ctrl.u16_timer_cnt += u16_leep_time;
else
s_adc_gun_ctrl.u16_timer_cnt = 0;
}
/****************************************************
*功能说明: 枪温处理函数初始化
*参数说明:
* 输入参数: void
* 输出参数: void
*备注:
*作者: jiankalka
*日期: 2025-08-07
****************************************************/
void v_gun_temp_init()
{
memset(&s_adc_gun_ctrl, 0, sizeof(S_ADC_GUN_CTRL));
init_adc();
}
/****************************************************
*功能说明: 充电中( CC1 在 3.2~4.8V )用比较器开关量获取枪连接状态
* PF10/PD3 高表示>4.8V断开,PF9/PA15 低表示<3.2V断开
*返回值: 0未连接 1已连接
****************************************************/
U8_T u8_get_gunLink_cmp(U8_T u8_gunNo)
{
if(u8_gunNo == 0U)
{
if(GUN1_UBAL_READ_H == SET) //A枪CC1检测(高于4.8V,未连接)
return 0U;
if(GUN1_UBAL_READ_L == RESET) //A枪CC1检测(低于3.2V,未连接)
return 0U;
}
else
{
if(GUN2_UBAL_READ_H == SET) //B枪CC1检测(高于4.8V,未连接)
return 0U;
if(GUN2_UBAL_READ_L == RESET) //B枪CC1检测(低于3.2V,未连接)
return 0U;
}
return 1U;
}
/****************************************************
*功能说明: 获取枪连接状态
*参数说明:
* 输入参数: U8_T u8_gunNo 枪号地址 0-1
* 输出参数: U8_T 插枪状态 0未插枪 1插枪
*备注:
*作者: jiankalka
*日期: 2025-08-11
****************************************************/
U8_T u8_get_gunLink(U8_T u8_gunNo)
{
//获取枪连接ADC电压值
float gunLink = s_adc_gun_ctrl.f_adc_gunLink[u8_gunNo];
//根据电压值判断枪连接状态,若在3.2~4.8V范围内
if(gunLink >= GUNLINK_CC1_VOLT_LO && gunLink <= GUNLINK_CC1_VOLT_HI)
return u8_get_gunLink_cmp(u8_gunNo);
return 0U;
}
/****************************************************
*功能说明: 获取枪adc采样值
*参数说明:
* 输入参数: E_ADC_TYPE e_type ADC类型
E_ADC_TYPE_GUN_TEMP_A1, //2 A枪温+
E_ADC_TYPE_GUN_TEMP_A2, //3 A枪温-
E_ADC_TYPE_GUN_TEMP_B1, //4 B枪温正;
E_ADC_TYPE_GUN_TEMP_B2, //5 B枪温-
E_ADC_TYPE_FAN_IN_TEMP, //6 进风口温度
E_ADC_TYPE_FAN_OUT_TEMP, //7 出风口温度
E_ADC_TYPE_GUN_DC_VOL_A1, //8 A枪DC 正极电压采集值
E_ADC_TYPE_GUN_DC_VOL_A2, //9 A枪DC 负极电压采集值
E_ADC_TYPE_GUN_DC_VOL_B1, //10 B枪DC 正极电压采集值
E_ADC_TYPE_GUN_DC_VOL_B2, //11 B枪DC 负极电压采集值
* 输出参数: float ADC采样值
*备注:
****************************************************/
float f_get_gun_data(E_ADC_TYPE e_type)
{
float f_ret = 0;
switch (e_type)
{
case E_ADC_TYPE_GUN_TEMP_A1: //2 A枪温+
f_ret = s_adc_gun_ctrl.f_adc_data[0];
if(f_ret > TEMP_MAX_NUM)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_TEMP_A2: //3 A枪温-
f_ret = s_adc_gun_ctrl.f_adc_data[1];
if(f_ret > TEMP_MAX_NUM)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_TEMP_B1: //4 B枪温正;
f_ret = s_adc_gun_ctrl.f_adc_data[2];
if(f_ret > TEMP_MAX_NUM)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_TEMP_B2: //5 B枪温-
f_ret = s_adc_gun_ctrl.f_adc_data[3];
if(f_ret > TEMP_MAX_NUM)
f_ret = 0;
break;
case E_ADC_TYPE_FAN_IN_TEMP: //6 进风口温度
f_ret = s_adc_gun_ctrl.f_adc_data[4];
if(f_ret > TEMP_MAX_NUM)
f_ret = 0;
break;
case E_ADC_TYPE_FAN_OUT_TEMP: //7 出风口温度
f_ret = s_adc_gun_ctrl.f_adc_data[5];
if(f_ret > TEMP_MAX_NUM)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_DC_VOL_A1: //8 A枪DC 正极电压采集值
f_ret = s_adc_gun_ctrl.f_adc_dc_vol[0];
if(f_ret < 0)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_DC_VOL_A2: //9 A枪DC 负极电压采集值
f_ret = s_adc_gun_ctrl.f_adc_dc_vol[1];
if(f_ret < 0)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_DC_VOL_B1: //10 B枪DC 正极电压采集值
f_ret = s_adc_gun_ctrl.f_adc_dc_vol[2];
if(f_ret < 0)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_DC_VOL_B2: //11 B枪DC 负极电压采集值
f_ret = s_adc_gun_ctrl.f_adc_dc_vol[3];
if(f_ret < 0)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_MVI_A: //12 A枪直流回路测量电流
f_ret = s_adc_gun_ctrl.f_adc_dc_cur[0];
if(f_ret < 0)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_MVI_B: //13 B枪直流回路测量电流
f_ret = s_adc_gun_ctrl.f_adc_dc_cur[1];
if(f_ret < 0)
f_ret = 0;
break;
case E_ADC_TYPE_GUN_BAT_VOL_A1: //14 A枪接触器外侧电压采集值
f_ret = s_adc_gun_ctrl.f_adc_bat_vol[0];
if (f_ret < 0) {
f_ret = 0;
}
break;
;
case E_ADC_TYPE_GUN_BAT_VOL_B1: //16 B枪接触器外侧电压采集值
f_ret = s_adc_gun_ctrl.f_adc_bat_vol[1];
if (f_ret < 0) {
f_ret = 0;
}
break;
default:
break;
}
return f_ret;
}
#endif
void v_init_collect()
{
v_yk_ctrl_init(); //遥控初始化
v_led_init(); //LED状态缓存初始化
v_gun_temp_init(); //枪温采集初始化
memset(&sys_yxyc_data, 0, sizeof(YXYC_DATA)); //初始化清空数据
// 外部数据初始化,可以减少系统运行时对外部数据的获取
sys_yxyc_data.u8_gunNo = u8_coll_get_gun_num(); //获取系统充电枪数量
}
void v_collect_task(void *arg)
{
uint16_t sleep_time_ms; // 任务睡眠时间(毫秒)
uint32_t last_send_time; // 上次发送时间
uint32_t current_time; // 当前时间
U8_T i;
(void)arg; // 忽略未使用的参数
(void)DATACOLL_LOCK_INIT(); // 初始化数据采集任务锁
sleep_time_ms = DATACOLL_SLEEP_TIME_MS(); // 获取任务睡眠时间
last_send_time = get_current_seconds(); // 获取当前时间作为上次发送时间
v_init_collect(); //初始化
for (;;)
{
DATACOLL_FEED_DOG(); // 任务喂狗
current_time = get_current_seconds(); // 获取当前时间
// 检查是否需要打印任务运行信息
if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME))
{
// 尝试获取锁
if (DATACOLL_LOCK_TAKE() == 1U)
{
DATACOLL_LOG("data coll task is runing, sleep time is %ums", sleep_time_ms); // 打印任务运行日志
(void)DATACOLL_LOCK_GIVE(); // 释放锁
}
last_send_time = current_time; // 更新上次发送时间
}
/* 1) 执行遥控缓冲命令(统一在采集任务内下发) */
v_yk_ctrl_cheak();
/* 2) LED状态刷新(供sub_comm读取) */
v_led_ctrl(sleep_time_ms);
/* 3) 采集循环(枪温/环境温度等缓存更新) */
v_gun_temp_loop(sleep_time_ms);
/* 4) 遥信/遥测镜像刷新(按枪循环更新) */
for (i = 0; i < sys_yxyc_data.u8_gunNo; i++)
{
v_update_gun_yxyc(&sys_yxyc_data.s_gun_rt_data[i], i);
}
/* 5) 整桩遥信/遥测刷新 */
v_gun_temp_loop(sleep_time_ms);
v_updata_sys_yxyc(&sys_yxyc_data.s_sys_rt_data);
mSleep(sleep_time_ms); // 睡眠指定时间
}
}
+159
View File
@@ -0,0 +1,159 @@
#ifndef COLLECT_TASK_H
#define COLLECT_TASK_H
#include "publicdata/type.h"
#include "publicdata/publicdata.h"
/* CC1电压阈值*/
#define GUNLINK_CC1_VOLT_LO (3.2f)
#define GUNLINK_CC1_VOLT_HI (4.8f)
/*结构定义及变量声明*/
typedef union{ //整桩相关的遥信联合体,2bit表示一个状态,预留16bit
U32_T u32_yx_sys_data;
struct
{
U32_T yx_emergency:2; //急停状态
U32_T yx_door:2; //门禁状态
U32_T yx_water:2; //水浸状态
U32_T yx_smoke:2; //烟感状态
U32_T yx_bus_KM:2; //母联直流接触器状态
U32_T yx_spd:2; //防雷器状态
U32_T yx_KM:2; //交流输入接触器状态
U32_T yx_QF:2; //交流输入断路器状态
U32_T reserve:16; //预留,16bit
}bit_yx_sys_data;
}SYS_YX_DATA;
typedef union{ //充电枪相关的遥信联合体,2bit表示一个状态,预留18bit
U32_T u32_yx_gun_data;
struct
{
U32_T yx_gun_lock:2; //充电枪电子锁状态
U32_T yx_switch:2; //直流输出接触器状态+
U32_T yx_relief_swich:2; //泄放回路接触器状态
U32_T yx_fuse:2; //直流回路熔断器状态
U32_T yx_gun_power:2; //充电枪辅助电源状态
U32_T yx_gun_homing:2; //充电枪归位状态
U32_T reserve:20; //预留,18bit
}bit_yx_gun_data;
}GUN_YX_DATA;
typedef struct //整桩遥信、遥测结构体
{
/****遥信****/
SYS_YX_DATA yx_sys_data; //整桩相关的遥信数据
/****遥测****/
F32_T f32_yc_sys_temp1; //环境温度1
F32_T f32_yc_sys_temp2; //环境温度2
}SYS_RT_DATA;
typedef struct //充电枪遥信、遥测结构体
{
U8_T u8_gunlink_state; //充电枪连接状态,0 枪未连接,1 枪已连接
/****遥信****/
GUN_YX_DATA yx_gun_data; //充电枪相关的遥信数据
/****遥测****/
F32_T f32_yc_gun_vol_pos; //直流母线正对地电压
F32_T f32_yc_gun_vol_neg; //直流母线负对地电压
F32_T f32_yc_gun_curr; //直流回路测量电流
F32_T f32_yc_bat_vol_pos; //接触器外侧正对地电压
F32_T f32_yc_bat_vol_neg; //接触器外侧负对地电压
F32_T f32_yc_gun_temp_pos; //充电枪正极温度
F32_T f32_yc_gun_temp_neg; //充电枪负极温度
}GUN_RT_DATA;
/*充电枪遥测遥信宏定义*/
typedef enum
{
/*充电枪遥信状态宏定义*/
YX_GUN_LINK, //枪连接状态
YX_GUN_LOCK, //枪电子锁
YX_GUN_DC_KM, //直流输出接触器状态
YX_GUN_DC_KM_POS = YX_GUN_DC_KM, //CCU601E_D 兼容: 正母线
YX_GUN_DC_KM_NEG , //CCU601E_D 兼容: 负母线
YX_GUN_RELIF, //泄放继电器状态
YX_GUN_DC_FUSE, //直流熔断器状态
YX_GUN_AUXPOWER, //低压辅源状态
YX_GUN_HOMING, //充电枪归位
}E_YX_GUN;
typedef enum
{
/*充电枪遥测宏定义*/
ADC_GUN_VOLT_PLUS, //直流母线正对地电压
ADC_GUN_VOLT_MINUS, //直流母线负对地电压
ADC_GUN_MVI, //直流回路测量电流
ADC_GUN_BAT, //接触器外侧正对地电压
ADC_GUN_TEMP_PLUS, //充电枪正极温度
ADC_GUN_TEMP_MINUS, //充电枪负极温度
}E_YC_GUN;
/*整桩遥信宏定义*/
typedef enum
{
/*整桩遥信状态宏定义*/
YX_PILE_EMERGENCY, //急停
YX_PILE_DOOR, //门禁
YX_PILE_WARTER, //水浸
YX_PILE_SPD, //防雷状态
YX_PILE_SMOKE, //烟感
YX_PILE_BUS_KM, //母联接触器
YX_PILE_KM, //交流输入接触器
YX_PILE_QF, //交流输入断路器
}E_YX_SYS;
/*整桩遥测宏定义*/
typedef enum
{
/*整桩遥测宏定义*/
ADC_PILE_TEMP1, //桩温1
ADC_PILE_TEMP2, //桩温2
}E_YC_SYS;
#define LED_DO_NUM (4) //LED DO控制数量
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
E_ADC_TYPE_GUN_LINK_1=0, //0 A枪连接;
E_ADC_TYPE_GUN_LINK_2, //1 B枪连接;
E_ADC_TYPE_GUN_TEMP_A1, //2 A枪温+
E_ADC_TYPE_GUN_TEMP_A2, //3 A枪温-
E_ADC_TYPE_GUN_TEMP_B1, //4 B枪温正;
E_ADC_TYPE_GUN_TEMP_B2, //5 B枪温-
E_ADC_TYPE_FAN_IN_TEMP, //6 进风口温度
E_ADC_TYPE_FAN_OUT_TEMP, //7 出风口温度
E_ADC_TYPE_GUN_DC_VOL_A1, //8 A枪DC 正极电压采集值
E_ADC_TYPE_GUN_DC_VOL_A2, //9 A枪DC 负极电压采集值
E_ADC_TYPE_GUN_DC_VOL_B1, //10 B枪DC 正极电压采集值
E_ADC_TYPE_GUN_DC_VOL_B2, //11 B枪DC 负极电压采集值
E_ADC_TYPE_GUN_MVI_A, //12 A枪直流回路测量电流(待对接)
E_ADC_TYPE_GUN_MVI_B, //13 B枪直流回路测量电流(待对接)
E_ADC_TYPE_GUN_BAT_VOL_A1, //14 A枪接触器外侧正对地电压采集值
E_ADC_TYPE_GUN_BAT_VOL_B1, //16 B枪接触器外侧正对地电压采集值
}E_ADC_TYPE;
/*------ Exported function prototypes (函数声明) -------*/
U8_T u8_get_yxyc_data(SYS_RT_DATA *pile_data ,GUN_RT_DATA *gun_data ,U8_T u8_gunNo); //获取数据采集结构体
U8_T u8_get_gun_yx_data(E_YX_GUN gun_yx_type ,U8_T u8_gunNo); //获取枪遥信遥测数据
F32_T f32_get_gun_yc_data(E_YC_GUN gun_yc_type ,U8_T u8_gunNo); //获取枪adc采集数据
U8_T u8_get_sys_yx_data(E_YX_SYS sys_yx_type); //获取桩遥信遥测数据
F32_T f32_get_sys_yc_data(E_YC_SYS sys_yc_type); //获取桩adc采集数据
U8_T u8_get_gunLink(U8_T u8_gunNo); //获取枪连接状态
float f_get_gun_data(E_ADC_TYPE e_type);
void v_collect_task(void *arg);
U8_T u8_coll_get_gun_num(void);
#endif
+244
View File
@@ -0,0 +1,244 @@
#include "collect_ctrl/led_ctrl.h"
#include "collect_ctrl/collect_task.h"
#include "flowctrl/flow_interface.h"
#define LED_TYPE (1)
static U8_T g_u8_led_state_cache[GUN_MAX_CNT]; //两把枪指示灯状态缓存(供sub_comm读取)
#if 1 //接口函数
/**
* ************************************************************************
* @brief - 获取枪状态 返回led状态值
*
* @param[in] u8_gunNo - 抢号 0-N
*
* @return E_LED_STATE_TYPE - LED 显示状态
*
* E_LED_TYPE_GREEN_STEADY = 0, //绿灯常亮
* E_LED_TYPE_GREEN_BLINK , //绿灯闪烁
* E_LED_TYPE_RED_STEADY , //红灯常亮
* E_LED_TYPE_RED_BLINK , //红灯闪烁
* E_LED_TYPE_YELLOW_BLINK , //黄灯闪烁
*
* @author jiankalka
* @date 2025-09-05
* ************************************************************************
*/
static E_LED_STATE_TYPE e_led_get_gun_state(U8_T u8_gunNo)
{
E_LED_STATE_TYPE e_ret = E_LED_TYPE_GREEN_STEADY;
E_PILE_STATE_TYPE e_pile_state;
U8_T u8_gunLink = u8_get_gun_yx_data(YX_GUN_LINK,u8_gunNo); //枪连接状态
E_FLOW_REAL_STATE flow_state = (E_FLOW_REAL_STATE)u16_flow_put_data(u8_gunNo , E_FLOW_PUT_DATA_GUN_REAL_STATE,0); //0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
U8_T u8_fault_num = u8_get_fault_sum(0,0); //整桩故障信息数量
U8_T u8_alarm_num = u8_get_fault_sum(0,1); //整桩告警信息数量
U8_T u8_gun_fault_num = u8_get_fault_sum(u8_gunNo+1,0); //该枪 故障信息数量
U8_T u8_gun_alarm_num = u8_get_fault_sum(u8_gunNo+1,1); //该枪 故障信息数量
if(u8_fault_num != 0 || u8_alarm_num != 0 || //整桩故障 则整桩报告警
u8_gun_fault_num != 0 || u8_gun_alarm_num != 0)
{
e_pile_state = E_PILE_STATE_TYPE_FAULT;//存在故障
}
else if(u8_gunLink == 0)
{
e_pile_state = E_PILE_STATE_TYPE_IDLE; //空闲未插枪
}
else if(flow_state > E_FLOW_REAL_FREE && flow_state < E_FLOW_REAL_CHARGEING) //
{
e_pile_state = E_PILE_STATE_TYPE_STARTING; //启动中
}
else if(flow_state >= E_FLOW_REAL_CHARGEING && flow_state < E_FLOW_REAL_STOP_OK) //
{
e_pile_state = E_PILE_STATE_TYPE_CHARGING; //充电中
}
else //
{
e_pile_state = E_PILE_STATE_TYPE_PLUGGED_NOT_START; //插枪未启动
}
if(g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_ledGbEn == 0)
{
/*
"黄灯常亮,表示 枪处于空闲状况
红灯常亮,表示 出现故障
绿灯常亮,表示 插枪、充电完成
绿色呼吸灯,表示 准备充电阶段、绝缘检测阶段、充电阶段
若是流水类型,充电中 绿灯流水显示"
*/
switch (e_pile_state)
{
case E_PILE_STATE_TYPE_FAULT : //存在故障
{
e_ret = E_LED_TYPE_RED_STEADY; //存在故障 红灯常亮
break;
}
case E_PILE_STATE_TYPE_IDLE: //空闲未插枪
{
e_ret = E_LED_TYPE_YELLOW_STEADY; //未插枪 黄灯常亮
break;
}
case E_PILE_STATE_TYPE_STARTING: //启动中
{
e_ret = E_LED_TYPE_GREEN_BLINK; //启动中 绿灯闪烁
break;
}
case E_PILE_STATE_TYPE_CHARGING: //充电中
{
if(g_t_share_data.t_sys_fix_cfg.e_ledType == E_LED_TYPE_3)
{
e_ret = E_LED_TYPE_GREEN_RUNNING; //充电中 绿灯流水显示
}
else
{
e_ret = E_LED_TYPE_GREEN_BLINK; //充电中 绿灯闪烁
}
break;
}
case E_PILE_STATE_TYPE_PLUGGED_NOT_START: //插枪未启动
{
e_ret = E_LED_TYPE_GREEN_STEADY ; //插枪未启动 绿灯常量
break;
}
default:
break;
}
}
else
{
/*
故障 黄灯闪烁
空闲未插枪 绿灯常量
启动中 红灯闪烁
充电中 红灯常量,若是流水类型,充电中红灯流水显示
插枪未启动 绿灯闪烁
*/
switch (e_pile_state)
{
case E_PILE_STATE_TYPE_FAULT : //存在故障
{
e_ret = E_LED_TYPE_YELLOW_BLINK; //存在故障 黄灯闪烁
break;
}
case E_PILE_STATE_TYPE_IDLE: //空闲未插枪
{
e_ret = E_LED_TYPE_GREEN_STEADY; //未插枪 绿灯常亮
break;
}
case E_PILE_STATE_TYPE_STARTING: //启动中
{
e_ret = E_LED_TYPE_RED_BLINK; //启动中 红灯闪烁
break;
}
case E_PILE_STATE_TYPE_CHARGING: //充电中
{
if(g_t_share_data.t_sys_fix_cfg.e_ledType == E_LED_TYPE_3)
{
e_ret = E_LED_TYPE_RED_RUNNING; //充电中 红灯流水显示
}
else
{
e_ret = E_LED_TYPE_RED_STEADY; //充电中 红灯常亮
}
break;
}
case E_PILE_STATE_TYPE_PLUGGED_NOT_START: //插枪未启动
{
e_ret = E_LED_TYPE_GREEN_BLINK ; //插枪未启动 绿灯闪烁
break;
}
default:
break;
}
}
return e_ret;
}
#endif
/**
* ************************************************************************
* @brief - LED 控制函数
*
* @param[in] u16_sleepTime - 函数执行周期
*
*
* @author jiankalka
* @date 2025-08-05
* ************************************************************************
*/
void v_led_ctrl(U16_T u16_sleepTime)
{
/*
E_LED_TYPE_GREEN_STEADY = 0, //绿灯常亮
E_LED_TYPE_GREEN_BLINK , //绿灯闪烁
E_LED_TYPE_RED_STEADY , //红灯常亮
E_LED_TYPE_RED_BLINK , //红灯闪烁
E_LED_TYPE_YELLOW_BLINK , //黄灯闪烁
E_LED_TYPE_YELLOW_STEADY , //黄灯常亮
E_LED_TYPE_GREEN_RUNNING , //绿灯流水显示
E_LED_TYPE_RED_RUNNING , //红灯流水显示
E_LED_TYPE_YELLOW_RUNNING , //黄灯流水显示
*/
/* 本项目当前不执行 v_led_do_ctrl() 硬件动作,仅刷新全局状态缓存 */
v_led_state_refresh();
}
/**
* ************************************************************************
* @brief - LED 灯初始化
*
*
*
* @author jiankalka
* @date 2025-08-05
* ************************************************************************
*/
void v_led_init(void)
{
memset(g_u8_led_state_cache, 0, sizeof(g_u8_led_state_cache));
v_led_state_refresh(); /* 上电默认灯态,后续由 SubComm 发 0x04 前周期刷新 */
}
/**
* ************************************************************************
* @brief - 刷新两把枪 LED 状态缓存
*
* @author jiankalka
* @date 2026-04-29
* ************************************************************************
*/
void v_led_state_refresh(void)
{
g_u8_led_state_cache[GUN_A] = (U8_T)e_led_get_gun_state(GUN_A);
g_u8_led_state_cache[GUN_B] = (U8_T)e_led_get_gun_state(GUN_B);
}
/**
* ************************************************************************
* @brief - 获取指定枪 LED 状态缓存
*
* @param[in] u8_gunNo - 枪号 0-N
*
* @return U8_T - LED 状态(E_LED_STATE_TYPE 编码)
*
* @author jiankalka
* @date 2026-04-29
* ************************************************************************
*/
U8_T u8_led_state_get(U8_T u8_gunNo)
{
if (u8_gunNo >= (U8_T)GUN_MAX_CNT)
{
return 0U;
}
return g_u8_led_state_cache[u8_gunNo];
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef LED_CTRL_H
#define LED_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
E_PILE_STATE_TYPE_FAULT = 0, //存在故障
E_PILE_STATE_TYPE_IDLE, //空闲未插枪
E_PILE_STATE_TYPE_STARTING, //启动中
E_PILE_STATE_TYPE_CHARGING, //充电中
E_PILE_STATE_TYPE_PLUGGED_NOT_START, //插枪未启动
}E_PILE_STATE_TYPE;
typedef enum
{
E_LED_TYPE_GREEN_STEADY = 0, //绿灯常亮
E_LED_TYPE_GREEN_BLINK , //绿灯闪烁
E_LED_TYPE_RED_STEADY , //红灯常亮
E_LED_TYPE_RED_BLINK , //红灯闪烁
E_LED_TYPE_YELLOW_STEADY , //黄灯常量
E_LED_TYPE_YELLOW_BLINK , //黄灯闪烁
E_LED_TYPE_GREEN_RUNNING, //绿灯流水
E_LED_TYPE_RED_RUNNING, //红灯流水
E_LED_TYPE_YELLOW_RUNNING, //黄灯流水
}E_LED_STATE_TYPE;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_led_init(void);
void v_led_ctrl(U16_T u16_sleepTime);
U8_T u8_led_state_get(U8_T u8_gunNo);
void v_led_state_refresh(void);
#ifdef __cplusplus
}
#endif
#endif /* LED_CTRL_H */
+305
View File
@@ -0,0 +1,305 @@
#include "collect_ctrl/yk_ctrl.h"
#include "usart.h"
#include "sys_drv_init.h"
#include "sub_comm/sub_comm_task.h"
#include <string.h>
/* 遥控通道总数自动跟随枚举 E_YK_CTRL_TYPE。 */
#define YK_DATA_NUM ((U8_T)E_KY_TYPE_NUM)
/* 遥控缓存区:
* - u8_data[] 便于按索引批量扫描执行;
* - s_yk_data 便于按“语义字段”点对点写入;
* 两者共用同一块内存,用于“写入命令”和“周期执行命令”的桥接。 */
typedef union
{
/* 按枚举下标访问的原始命令缓存。 */
U8_T u8_data[YK_DATA_NUM];
struct
{
E_CMD e_ctrl_dc_cont1; /* 直流接触器1(一枪主回路)。 */
E_CMD e_ctrl_dc_cont2; /* 直流接触器2(二枪主回路)。 */
E_CMD e_ctrl_dc_bus; /* 直流母联接触器。 */
E_CMD e_ctrl_ac_cont; /* 交流接触器。 */
E_CMD e_ctrl_fan; /* 散热风机。 */
E_CMD e_ctrl_relief_a; /* A枪泄放控制。 */
E_CMD e_ctrl_relief_b; /* B枪泄放控制。 */
E_CMD e_ctrl_lock1; /* 枪A电子锁。 */
E_CMD e_ctrl_lock2; /* 枪B电子锁。 */
E_CMD e_ctrl_aux_power1; /* 辅源1。 */
E_CMD e_ctrl_aux_power2; /* 辅源2。 */
E_CMD e_ctrl_v12_24_sw; /* 12/24V切换控制。 */
E_CMD e_ctrl_ac1; /* 模块交流接触器1。 */
E_CMD e_ctrl_ac2; /* 模块交流接触器2。 */
} s_yk_data;
} U_YK_CTRL_DATA;
/* 遥控命令全局缓存(模块私有):
* - 写入口:v_yk_ctrl_impl()
* - 执行入口:v_yk_ctrl_cheak()
* 当前由采集任务周期调用,默认单任务上下文访问。 */
static U_YK_CTRL_DATA u_yk_data;
/**
* @brief 周期执行遥控缓存中的全部命令。
*
* 执行策略:
* 1) 按 E_YK_CTRL_TYPE 的顺序遍历;
* 2) 将缓存命令传入 v_yk_ctrl() 下发到 DO
* 3) 不在本函数中清命令,保持“电平保持型”行为。
*/
void v_yk_ctrl_cheak(void)
{
U8_T i;
for (i = 0; i < YK_DATA_NUM; i++)
{
v_yk_ctrl((E_YK_CTRL_TYPE)i, (E_CMD)u_yk_data.u8_data[i]); //依次执行遥控命令
}
}
/**
* @brief 将指定遥控类型下发到具体硬件 DO。
*
* @param e_type 遥控类型(决定映射到哪个 DO)。
* @param e_cmd 遥控命令(开/关)。
*
* 说明:
* - 本函数只负责“硬件动作”;
* - 命令缓存维护由 v_yk_ctrl_impl() 负责。
*/
void v_yk_ctrl(E_YK_CTRL_TYPE e_type, E_CMD e_cmd)
{
switch (e_type)
{
case E_KY_TYPE_DC_CONT1: /* 直流接触器1 */
{
DO3(e_cmd);
break;
}
case E_KY_TYPE_DC_CONT2: /* 直流接触器2 */
{
DO6(e_cmd);
break;
}
case E_KY_TYPE_DC_CONT_BUS: /* 母联接触器 */
{
DO9(e_cmd);
break;
}
case E_KY_TYPE_AC_CONT: /* 交流接触器 */
{
DO7(e_cmd);
break;
}
case E_KY_TYPE_FAN_CONT: /* 风扇接触器 */
{
DO10(e_cmd);
break;
}
case E_KY_TYPE_RELIEF_A: /* A枪泄放控制 */
{
DO2(e_cmd);
break;
}
case E_KY_TYPE_RELIEF_B: /* B枪泄放控制 */
{
DO5(e_cmd);
break;
}
case E_KY_TYPE_GUN_LOCK1: /* 枪A电子锁 */
{
ELOCK_A(e_cmd);
break;
}
case E_KY_TYPE_GUN_LOCK2: /* 枪B电子锁 */
{
ELOCK_B(e_cmd);
break;
}
case E_KY_TYPE_AUX_POWER1: /* 辅源1 */
{
/* 辅源1 */
DO1(e_cmd);
break;
}
case E_KY_TYPE_AUX_POWER2: /* 辅源2 */
{
/* 辅源2 */
DO4(e_cmd);
break;
}
case E_KY_TYPE_V12_24_SWITCH: /* 12/24V切换控制 */
{
/* 12/24V切换:下发到副板 DO11 */
v_sub_comm_yk_set(E_SUB_YK_DO11, e_cmd);
break;
}
case E_KY_TYPE_AC1: /* 交流接触器1,主板 DO7 */
{
DO7(e_cmd);
break;
}
case E_KY_TYPE_AC2: /* 交流接触器2,主板 DO8 */
{
DO8(e_cmd);
break;
}
default:
break;
}
}
/**
* @brief 外部遥控写入接口(仅更新缓存,不直接动作)。
*
* @param e_type 遥控类型。
* @param e_cmd 遥控命令。
*
* 使用方式:
* - 上层流程调用本函数写入命令;
* - 采集任务周期调用 v_yk_ctrl_cheak() 统一执行;
* 这样可以把遥控执行节拍收敛到单任务上下文,降低并发风险。
*/
static void v_yk_cache_set(E_YK_CTRL_TYPE e_type, E_CMD e_cmd)
{
switch (e_type)
{
case E_KY_TYPE_DC_CONT1: /* 直流接触器1 */
{
u_yk_data.s_yk_data.e_ctrl_dc_cont1 = e_cmd;
break;
}
case E_KY_TYPE_DC_CONT2: /* 直流接触器2 */
{
u_yk_data.s_yk_data.e_ctrl_dc_cont2 = e_cmd;
break;
}
case E_KY_TYPE_DC_CONT_BUS: /* 母联接触器 */
{
u_yk_data.s_yk_data.e_ctrl_dc_bus = e_cmd;
break;
}
case E_KY_TYPE_AC_CONT: /* 交流接触器 */
{
u_yk_data.s_yk_data.e_ctrl_ac_cont = e_cmd;
break;
}
case E_KY_TYPE_FAN_CONT: /* 风扇接触器 */
{
u_yk_data.s_yk_data.e_ctrl_fan = e_cmd;
break;
}
case E_KY_TYPE_RELIEF_A: /* A枪泄放控制 */
{
u_yk_data.s_yk_data.e_ctrl_relief_a = e_cmd;
break;
}
case E_KY_TYPE_RELIEF_B: /* B枪泄放控制 */
{
u_yk_data.s_yk_data.e_ctrl_relief_b = e_cmd;
break;
}
case E_KY_TYPE_GUN_LOCK1: /* 枪A电子锁 */
{
u_yk_data.s_yk_data.e_ctrl_lock1 = e_cmd;
break;
}
case E_KY_TYPE_GUN_LOCK2: /* 枪B电子锁 */
{
u_yk_data.s_yk_data.e_ctrl_lock2 = e_cmd;
break;
}
case E_KY_TYPE_AUX_POWER1: /* 辅源1 */
{
u_yk_data.s_yk_data.e_ctrl_aux_power1 = e_cmd;
break;
}
case E_KY_TYPE_AUX_POWER2: /* 辅源2 */
{
u_yk_data.s_yk_data.e_ctrl_aux_power2 = e_cmd;
break;
}
case E_KY_TYPE_V12_24_SWITCH: /* 12/24V切换控制 */
{
u_yk_data.s_yk_data.e_ctrl_v12_24_sw = e_cmd;
break;
}
case E_KY_TYPE_AC1: /* 模块交流接触器1 */
{
u_yk_data.s_yk_data.e_ctrl_ac1 = e_cmd;
break;
}
case E_KY_TYPE_AC2: /* 模块交流接触器2 */
{
u_yk_data.s_yk_data.e_ctrl_ac2 = e_cmd;
break;
}
default:
break;
}
}
/**
* @brief 遥控模块初始化。
*
* 初始化互斥锁并清空所有遥控命令缓存,确保上电后不携带历史控制状态。
*/
void v_yk_ctrl_init(void)
{
memset(&u_yk_data, 0, sizeof(U_YK_CTRL_DATA));
}
/**
* @brief 立即执行遥控(更新缓存并下发 DO)。
*
* @param e_type 遥控类型。
* @param e_cmd 遥控命令。
*/
void v_yk_ctrl_impl(E_YK_CTRL_TYPE e_type, E_CMD e_cmd)
{
//更新控制命令状态
v_yk_cache_set(e_type, e_cmd);
//执行控制命令
v_yk_ctrl(e_type, e_cmd);
}
/**
* @brief 获取遥控命令缓存中的对应控制状态。
*
* @param e_type 遥控类型。
* @return E_CMD 遥控命令。
*/
E_CMD e_yk_get_ctrl_cmd(E_YK_CTRL_TYPE e_type)
{
if (e_type < E_KY_TYPE_NUM)
{
return (E_CMD)u_yk_data.u8_data[e_type];
}
return CMD_OFF;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef YK_CTRL_H
#define YK_CTRL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "collect_ctrl/collect_task.h"
/* 遥控类型定义 */
typedef enum
{
E_KY_TYPE_DC_CONT1 = 0, /* 直流接触器1 - 一枪接触器 */
E_KY_TYPE_DC_CONT2, /* 直流接触器2 - 二枪接触器 */
E_KY_TYPE_DC_CONT_BUS, /* 母联接触器 */
E_KY_TYPE_AC_CONT, /* 交流接触器 */
E_KY_TYPE_FAN_CONT, /* 风扇接触器 */
E_KY_TYPE_RELIEF_A, /* A枪泄放控制 */
E_KY_TYPE_RELIEF_B, /* B枪泄放控制 */
E_KY_TYPE_GUN_LOCK1, /* 枪A电子锁控制 */
E_KY_TYPE_GUN_LOCK2, /* 枪B电子锁控制 */
E_KY_TYPE_AUX_POWER1, /* 辅源1遥控 */
E_KY_TYPE_AUX_POWER2, /* 辅源2遥控 */
E_KY_TYPE_V12_24_SWITCH, /* 12/24V切换控制 */
E_KY_TYPE_AC1, /* 交流接触器1,主板 DO7(80kW脉冲上电) */
E_KY_TYPE_AC2, /* 交流接触器2,主板 DO8(80kW脉冲下电) */
E_KY_TYPE_NUM /* 遥控类型数量 */
} E_YK_CTRL_TYPE;
void v_yk_ctrl_init(void);
void v_yk_ctrl_cheak(void);
void v_yk_ctrl(E_YK_CTRL_TYPE e_type, E_CMD e_cmd);
void v_yk_ctrl_impl(E_YK_CTRL_TYPE e_type, E_CMD e_cmd);
E_CMD e_yk_get_ctrl_cmd(E_YK_CTRL_TYPE e_type);
#ifdef __cplusplus
}
#endif
#endif /* YK_CTRL_H */
@@ -0,0 +1,81 @@
# fault_cheak 模块说明
> 路径:`app/fault_cheak/`
> 作用:完成故障检测、故障动作缓存、实时故障状态维护,以及历史故障持久化触发。
## 1. 模块组成
- `fault_interface.h/.c`
- 对外故障接口(`v_set_fault_data``v_get_fault_data``u8_get_fault_sum` 等)
- 故障动作缓存队列管理(`v_fault_cache_init``u8_fault_cache_add``v_fault_cache_process`
- 故障信息中文描述与故障位置描述
- `faultcheck_task.h/.c`
- 故障检测任务主循环
- 故障输入状态检查(急停、门禁、水浸、防雷、烟感、枪温等)
- 拔枪恢复逻辑与任务周期调度
## 2. 功能职责
### 2.1 实时故障状态管理
- 维护 `s_real_fault`
- `u8_fault_cnt`:当前故障数量
- `u8_warn_cnt`:当前告警数量
- `fault_data[]`:当前故障列表
- `warn_data[]`:当前告警列表
- 支持“同位置+同故障码”去重,避免重复入表。
### 2.2 故障动作缓存解耦
- 对外 `v_set_fault_data()` 只做“入缓存队列”,不直接重处理逻辑。
- 任务周期内由 `v_fault_cache_process()` 分批执行,避免单周期耗时过长影响主循环实时性。
### 2.3 历史故障持久化触发
- 故障/告警新增时,触发历史记录持久化:
- 主路径:`flash_file_mgr`(索引 EEPROM + 数据 externalflash
- 可选路径:`FATFS_ENABLE_FAULT` 打开时同步缓存到 FATFS 故障缓存
- 恢复动作当前仅更新实时状态(是否保存恢复动作可按业务再打开)。
## 3. 关键数据结构
- `HIS_FAULT_DATA_T``faultcheck_task.h`
- 故障位置 `e_site`
- 故障码 `e_fault_info`
- 发生时间 `s_occur_time`
- 动作类型 `ActType`(恢复/故障/告警)
- 扩展值 `s16_value1/s16_value2`
- `REAL_FAULT_DATA_T``fault_interface.h`
- 当前态故障/告警存储
- `FAULT_ACTION_CACHE_T``fault_interface.h`
- 故障动作缓存环形队列
## 4. 运行流程(简述)
1. 外部模块调用 `v_set_fault_data(...)` 上报动作;
2. 动作进入缓存队列;
3. `v_fault_task()` 周期调用 `v_fault_cache_process()`
4. 内部 `v_set_fault_data_internal()` 完成:
- 实时故障表增删
- 必要日志输出
- 新增故障/告警时持久化到历史存储模块。
## 5. 与 flash_file_mgr 的关系
- `fault_cheak` 负责“故障业务判断与动作触发”;
- `flash_file_mgr/fault_flash_impl` 负责“历史故障持久化实现”;
- 两者通过 `v_hisFault_save_record()` 解耦。
## 6. 配置与注意事项
- `FAULT_ACTION_CACHE_SIZE`:缓存大小,过小会丢动作;
- `FAULT_TEST`:测试开关,打开后可屏蔽实际故障处理;
- 当前接口运行在任务上下文,若在中断上下文调用需增加保护或改为事件投递。
## 7. 相关文档
- [主说明 README](../../README.md)
- [flash_file_mgr 模块说明](../../BSP/flash_file_mgr/flash_file_mgr模块说明.md)
- [eeprom 模块说明](../../BSP/eeprom/eeprom模块说明.md)
- [externalflash 模块说明](../../BSP/externalflash/externalflash模块说明.md)
+954
View File
@@ -0,0 +1,954 @@
#include "fault_cheak/faultcheck_task.h"
#include "flash_file_mgr/fault_flash_impl.h"
#include "app_init/app_init.h"
#include "mylog/mylog.h"
#include "app_rtc/app_rtc.h"
#include "fault_cheak/fault_interface.h"
#include "collect_ctrl/collect_task.h"
#include "flowctrl/flow_interface.h"
#if(FATFS_EN)
#include "app_fatfs/fatfs_init.h"
#endif
#if(MY_SHELL_EN) //Shell 使能
#include "letter_shell/shell_port.h"
#endif
#define FAULT_TEST 0 //测试使能 1则屏蔽故障信息
#define FAULT_LOG(fmt, ...) MYLOG_MSG(TASK_ID_FaultCheck, (fmt), ##__VA_ARGS__)
//extern S_HIS_FAULT_MNG s_hisFaultMng; //故障告警信息记录
extern REAL_FAULT_DATA_T s_real_fault; //实时故障告警记录存储
//故障动作缓存
static FAULT_ACTION_CACHE_T s_fault_action_cache = {0};
static void v_set_fault_data_internal(E_FAULT_SITE f_site ,E_FAULT_TYPE type , E_FAULT_INFO e_info, U8_T action_type, S16_T data1 , S16_T data2);
static void v_fault_persist_record(HIS_FAULT_DATA_T *fault_data);
/**
* @brief 将故障动作持久化到 Flash,并在 FATFS 故障模块使能时写入缓存
*/
static void v_fault_persist_record(HIS_FAULT_DATA_T *fault_data)
{
#if (FAULT_FLASH_MGR_ENABLE)
v_hisFault_save_record(fault_data);
#endif
#if FATFS_ENABLE_FAULT
cache_add_fault(fault_data);
#endif
}
#if 1 //故障说明映射表
const static S_FAULT_INFO fault_info[] =
{
//常规停止代码
{E_NORMAL_1000, "Charge complete stop"}, // 充满停止
{E_NORMAL_1001, "Local stop charging"}, // 本地停止充电
{E_NORMAL_1002, "Remote stop charging"}, // 后台停止充电
{E_NORMAL_1003, "Stop at set charging time"}, // 达到设置充电时长停止
{E_NORMAL_1004, "Stop at set energy"}, // 达到设置充电电量停止
{E_NORMAL_1005, "Stop at set amount"}, // 达到设置充电金额停止
{E_NORMAL_1006, "Stop at offline condition"}, // 达到离线停机条件
{E_NORMAL_1007, "Stop at SOC threshold"}, // 达到SOC终止条件停止
{E_NORMAL_1008, "User unplug stop"}, // 用户拔枪停止
{E_NORMAL_1009, "APP stop charging"}, // APP停止充电
// {E_NORMAL_1010, "Vehicle S2 proactive disconnect"}, // 车端S2主动断开
{E_NORMAL_1011, "BMS stop charging"}, // BMS停止充电
{E_NORMAL_1012, "Insufficient account balance"}, // 账户余额不足
{E_NORMAL_1013, "Insufficient prepay amount"}, // 预付金额不足
// {E_NORMAL_1014, "Vehicle S2 close timeout"}, // 车辆S2闭合超时
// {E_NORMAL_1015, "Vehicle S2 disconnect timeout while charging"}, // 充电中车辆S2断开超时
{E_NORMAL_1016, "Stop at trickle charging condition"}, // 达到涓流充电停机条件
//直流充电设备异常代码
{E_FAULT_13000, "AC input breaker fault"}, // 交流输入断路器故障
{E_FAULT_13001, "SPD fault"}, // 避雷器故障
{E_FAULT_13002, "AC input contactor sticking fault"}, // 交流输入接触器粘连故障
{E_FAULT_13003, "AC input contactor action fault"}, // 交流输入接触器拒动/误动故障
{E_FAULT_13004, "DC output contactor sticking fault"}, // 直流输出接触器粘连故障
{E_FAULT_13005, "DC output contactor action fault"}, // 直流输出接触器拒动/误动故障
{E_FAULT_13006, "DC output fuse fault"}, // 直流输出熔断器故障
{E_FAULT_13007, "Auxiliary power fault"}, // 辅助电源故障
{E_FAULT_13008, "Discharge relay action fault"}, // 泄放继电器拒动/误动故障
{E_FAULT_13009, "Discharge relay sticking"}, // 泄放继电器粘连
{E_FAULT_13010, "Gun e-lock lock fault"}, // 充电枪电子锁上锁故障
{E_FAULT_13011, "Gun e-lock unlock fault"}, // 充电枪电子锁解锁故障
// {E_FAULT_13012, "PE disconnection fault"}, // PE 断线故障
{E_FAULT_13013, "Charging connector overtemperature fault"}, // 充电接口过温故障
{E_FAULT_13014, "Charging connector overtemperature warning"}, // 充电接口过温告警
{E_FAULT_13015, "Control pilot voltage abnormal"}, // 控制导引电压异常
{E_FAULT_13016, "Control pilot fault while charging"}, // 充电中车辆控制导引故障
{E_FAULT_13017, "Emergency stop fault"}, // 急停
{E_FAULT_13018, "Door access fault"}, // 门禁
{E_FAULT_13019, "Cabinet door access fault"}, // 充电机柜门禁故障
{E_FAULT_13020, "Power control module fault"}, // 功率控制模块故障
{E_FAULT_13021, "Power control module warning"}, // 功率控制模块告警
{E_FAULT_13022, "Power control module communication fault"}, // 功率控制模块通信故障
{E_FAULT_13023, "Bridge contactor action fault"}, // 桥接接触器拒动/误动故障
{E_FAULT_13024, "Bridge contactor sticking fault"}, // 桥接接触器粘连故障
// {E_FAULT_13025, "ESAM fault"}, // ESAM 故障
{E_FAULT_13026, "Liquid cooling oil overtemperature"}, // 液冷油温过高
// {E_FAULT_13027, "Charger overtemperature fault optional"}, // 充电桩过温故障( 可选)
// {E_FAULT_13028, "Charger overtemperature warning optional"}, // 充电桩过温告警( 可选)
// {E_FAULT_13029, "Cabinet overtemperature warning"}, // 充电机柜过温告警
// {E_FAULT_13030, "Cabinet overtemperature fault"}, // 充电机柜过温故障
{E_FAULT_13031, "Liquid cooling warning"}, // 液冷报警
{E_FAULT_13032, "Smoke alarm warning"}, // 烟感报警
{E_FAULT_13033, "Charger water immersion fault"}, // 充电桩水浸故障
// {E_FAULT_13034, "Cabinet water immersion fault"}, // 充电机柜水浸故障
{E_FAULT_13035, "Cabinet humidity warning"}, // 充电机环境湿度告警
// {E_FAULT_13036, "Data validity check failure"}, // 数据合法性校验失败
// {E_FAULT_13037, "Controller version check not finished"}, // 充电控制器 版本校验 未完成
// {E_FAULT_13038, "Controller charging parameter download not finished"}, // 充电控制器 下发充电参数 未完成
// {E_FAULT_13039, "Charger paused service reject start"}, // 充电桩暂停服务, 拒绝启动
{E_FAULT_13040, "Previous charge complete but not unplugged"}, // 上次充电完成未拔枪
// {E_FAULT_13041, "Vehicle not connected reject start"}, // 充电桩与车辆处于未连接状态,拒绝启动
{E_FAULT_13042, "Insulation monitoring fault"}, // 绝缘监测故障
{E_FAULT_13043, "Insulation monitoring warning"}, // 绝缘监测告警
// {E_FAULT_13044, "Power allocation strategy execution failure"}, // 充电桩执行功率分配策略失败故障
// {E_FAULT_13045, "Reserved input overvoltage fault"}, // 预留( 输入电压过压故障)
// {E_FAULT_13046, "Reserved input undervoltage fault"}, // 预留( 输入电压欠压故障)
{E_FAULT_13047, "Discharge circuit fault"}, // 泄放回路故障
{E_FAULT_13048, "DC output overvoltage fault"}, // 直流输出电压过压故障
{E_FAULT_13049, "DC output overcurrent fault"}, // 直流输出电流过流故障
{E_FAULT_13050, "DC output undervoltage fault"}, // 直流输出电压欠压故障
// {E_FAULT_13051, "No idle module available"}, // 无空闲模块可用( 限智能分配功率)
// {E_FAULT_13052, "Battery reverse connection fault"}, // 电池反接故障
{E_FAULT_13053, "Precharge regulation failure"}, // 预充阶段调压失败
{E_FAULT_13054, "Output current exceeds vehicle demand"}, // 输出电流大于车辆需求电流
{E_FAULT_13055, "Power controller not in working state while charging"}, // 充电过程中功率控制器为非工作状态
{E_FAULT_13056, "Energy metering abnormal"}, // 电能计量异常
{E_FAULT_13057, "Vehicle authentication code mismatch"}, // 车辆鉴权码不一致
// {E_FAULT_13058, "Unreported transaction records full"}, // 未上送交易记录已满
// {E_FAULT_13059, "Transaction record storage failure"}, // 交易记录存储失败
// {E_FAULT_13060, "CAN protocol version check abnormal"}, // CAN 通讯版本校验异常
// {E_FAULT_13061, "Process running abnormal"}, // 进程运行异常
// {E_FAULT_13062, "Program file checksum failure"}, // 程序文件校验失败
// {E_FAULT_13063, "Program file damaged"}, // 程序文件损坏
{E_FAULT_13064, "Illegal VIN"}, // 非法 VIN
// {E_FAULT_13065, "Non plug and charge device"}, // 非即插即充设备
// {E_FAULT_13066, "Charger in pause state"}, // 充电桩处于“暂停” 状态( 27930中的暂停状态)
{E_FAULT_13067, "Plug and charge authentication failed"}, // 即插即充鉴权失败
// {E_FAULT_13068, "Pause timeout while charging"}, // 充电桩充电中暂停超时( 27930中的暂停充电)
{E_FAULT_13069, "Charging controller communication timeout"}, // 充电控制器通讯超时
{E_FAULT_13070, "Card reader communication timeout"}, // 读卡器通讯超时
{E_FAULT_13071, "Meter communication timeout"}, // 电表通讯超时
{E_FAULT_13072, "Controller communication timeout in dual gun one vehicle mode"}, // 两枪同充一车模式下充电控制器间通信超时
{E_FAULT_13074, "Liquid cooling communication timeout"}, // 液冷通讯超时
// {E_FAULT_13073, "Abnormal power loss"}, // 异常掉电
// {E_FAULT_13074, "Reserved"}, // 预留
{E_FAULT_13075, "Power controller inter-communication timeout"}, // 功率控制器间通讯超时
// {E_FAULT_13076, "Power controller remote control telemetry timeout"}, // 功率控制器接收遥控、 遥信报文超时故障
// {E_FAULT_13077, "Power controller environment module communication timeout"}, // 功率控制器与环境信息采集模块通讯超时
// {E_FAULT_13078, "Vehicle identification frame receive timeout"}, // 车辆识别数据帧接收超时
// {E_FAULT_13079, "Charge start command response timeout"}, // 充电启动命令应答超时
// {E_FAULT_13080, "Wait charge start complete timeout"}, // 等待充电启动完成状态超时
// {E_FAULT_13081, "Charge stop command response timeout"}, // 充电停止命令应答超时
// {E_FAULT_13082, "Wait charge stop complete timeout"}, // 等待充电停止完成状态超时
// {E_FAULT_13083, "Charge start complete confirm timeout"}, // 充电启动完成状态确认超时
// {E_FAULT_13084, "Power regulation command response timeout"}, // 功率调节指令应答超时
// {E_FAULT_13085, "Charge stop complete confirm timeout"}, // 充电停止完成状态确认超时
// {E_FAULT_13086, "Controller communication abnormal in parallel charging"}, // 充电控制器间并枪时通信异常
// {E_FAULT_13087, "Power controller address conflict"}, // 功率控制器地址冲突
// {E_FAULT_13088, "Charging controller address conflict"}, // 充电控制器地址冲突
{E_FAULT_13089, "HMI communication abnormal"}, // 人机通信异常
// {E_FAULT_13090, "Device self-check timeout fault"}, // 设备自检超时故障
// {E_FAULT_13091, "Internal communication fault"}, // 内部通讯故障
// {E_FAULT_13092, "Parking lock fault"}, // 车位锁故障
// {E_FAULT_13093, "Parking lock battery exhausted"}, // 车位锁电池耗尽故障
// {E_FAULT_13094, "Parking lock down lock failure"}, // 车位锁落锁失败故障
// {E_FAULT_13095, "Terminate order number abnormal"}, // 终止订单号异常
{E_FAULT_13096, "DCU output box communication fault"}, // 开出盒通讯故障(DCU)
// {E_FAULT_13097, "Input box communication fault"}, // 开入盒通讯故障
// {E_FAULT_13098, "Insulation sampling box communication fault"}, // 绝缘采样盒通讯故障
{E_FAULT_13099, "AB board communication fault"}, // AB板通讯故障
// {E_FAULT_13100, "Pilot board communication fault"}, // 导引板通讯故障
// {E_FAULT_13101, "Lamp board communication warning"}, // 灯板通讯告警
// {E_FAULT_13102, "Unknown stop reason from charging controller"}, // 充电控制器未知停止原因
{E_FAULT_13103, "Aux power voltage switching circuit fault"}, // 辅助电源电压切换回路故障
{E_FAULT_13104, "Output current exceeds vehicle maximum"}, // 输出电流超过车辆最高允许电流
// {E_FAULT_13105, "System clock abnormal"}, // 系统时钟异常
// {E_FAULT_13106, "Billing model request timeout fault"}, // 计费模型请求超时故障
// {E_FAULT_13107, "Billing model invalid fault"}, // 计费模型不合法故障
// {E_FAULT_13108, "Time synchronization timeout fault"}, // 对时超时故障
// {E_FAULT_13109, "Invalid time synchronization fault"}, // 对时时间不合法故障
//直流充电电源异常代码
{E_FAULT_14000, "Switch module fault"}, // 开关模块故障
{E_FAULT_14001, "Switch module warning"}, // 开关模块告警
{E_FAULT_14002, "Switch module communication fault"}, // 开关模块通信故障
{E_FAULT_14003, "Switch module action fault"}, // 开关模块拒动/误动故障
{E_FAULT_14004, "Switch module sticking fault"}, // 开关模块粘连故障
// {E_FAULT_14005, "Reserved switch module action warning"}, // 预留(开关模块拒动/误动告警)
// {E_FAULT_14006, "Reserved switch module sticking warning"}, // 预留(开关模块粘连告警)
{E_FAULT_14007, "Charging module fault"}, // 充电模块故障
// {E_FAULT_14008, "Charging module overtemperature fault"}, // 充电模块过温故障
// {E_FAULT_14009, "Charging module overtemperature warning"}, // 充电模块过温告警
// {E_FAULT_14010, "Charging module output short-circuit fault"}, // 充电模块输出短路故障
// {E_FAULT_14011, "Charging module output overcurrent warning"}, // 充电模块输出过流告警
// {E_FAULT_14012, "Charging module output overvoltage warning"}, // 充电模块输出过压告警
// {E_FAULT_14013, "Charging module output undervoltage warning"}, // 充电模块输出欠压告警
// {E_FAULT_14014, "Charging module AC input warning"}, // 充电模块交流输入告警
// {E_FAULT_14015, "Charging module input overvoltage warning"}, // 充电模块输入过压告警
// {E_FAULT_14016, "Charging module input undervoltage warning"}, // 充电模块输入欠压告警
// {E_FAULT_14017, "Charging module input phase loss warning"}, // 充电模块输入缺相告警
// {E_FAULT_14018, "Charging module input unbalance warning"}, // 充电模块输入不平衡告警
{E_FAULT_14019, "Charging module communication fault"}, // 充电模块通信故障
{E_FAULT_14020, "Charging module start failure"}, // 充电模块启动失败
{E_FAULT_14021, "Charging module shutdown failure"}, // 充电模块关机失败
// {E_FAULT_14022, "Charging module fan fault"}, // 充电模块风扇故障
// {E_FAULT_14023, "Charging module discharge fault"}, // 充电模块泄放故障
// {E_FAULT_14024, "Charging module address abnormal"}, // 充电模块地址异常
// {E_FAULT_14025, "PFC circuit overtemperature fault"}, // PFC 电路过温故障
// {E_FAULT_14026, "Main transformer overtemperature fault"}, // 主变压器过温故障
// {E_FAULT_14027, "DC/DC circuit overtemperature fault"}, // DC/DC 电路过温故障
// {E_FAULT_14028, "Output isolation diode fault"}, // 输出隔离二极管故障
// {E_FAULT_14029, "Module start fault non-protection induced"}, // 模块启动故障(非保护动作造成的模块启动失败)
// {E_FAULT_14030, "PFC circuit general fault"}, // PFC 电路总故障
// {E_FAULT_14031, "PFC circuit overvoltage fault"}, // PFC 电路过压故障
// {E_FAULT_14032, "PFC circuit bias fault"}, // PFC 电路偏压故障
// {E_FAULT_14033, "DC/DC circuit general fault"}, // DC/DC 电路总故障
// {E_FAULT_14034, "High/low voltage switching circuit fault"}, // 高低压切换电路故障
// {E_FAULT_14035, "Output circuit unbalance fault"}, // 输出电路不平衡故障
// {E_FAULT_14036, "Charging module no-output warning"}, // 充电模块不输出告警
// {E_FAULT_14037, "Outside contactor voltage above charger max before start"}, // 启动充电前直流输出接触器外侧电压大于充电机最大输出电压
// {E_FAULT_14038, "Outside contactor voltage below charger min before start"}, // 启动充电前直流输出接触器外侧电压小于充电机最小输出电压
//直流充电车辆异常代码
{E_FAULT_15000, "BMS communication fault"}, // BMS 通讯故障
{E_FAULT_15001, "DC charging controller judges BMS fault"}, // 直流充电控制模块判断BMS故障
// {E_FAULT_15002, "BMS fault information reported"}, // BMS 发送故障信息
{E_FAULT_15003, "BCP charging parameter frame timeout"}, // BCP 充电参数配置报文超时
{E_FAULT_15004, "BRO ready frame timeout"}, // BRO 充电准备就绪报文超时
{E_FAULT_15005, "BCS battery status frame timeout"}, // BCS 电池充电状态报文超时
{E_FAULT_15006, "BCL battery demand frame timeout"}, // BCL 电池充电需求报文超时
{E_FAULT_15007, "BST stop charging frame timeout"}, // BST 中止充电报文超时
{E_FAULT_15008, "BSM battery status frame timeout"}, // BSM 动力蓄电池状态报文超时
{E_FAULT_15009, "BRO frame 0x00 receive timeout"}, // BRO 报文(0x00 接收超时
{E_FAULT_15010, "BRO frame 0xAA receive timeout"}, // BRO 报文(0xAA 接收超时
{E_FAULT_15011, "BRM vehicle identification frame timeout"}, // BRM 车辆辨识报文超时
// {E_FAULT_15012, "BSM single cell overvoltage"}, // BSM 报文中单体动力蓄电池电压过高
// {E_FAULT_15013, "BSM single cell undervoltage"}, // BSM 报文中单体动力蓄电池电压过低
// {E_FAULT_15014, "BSM SOC too high"}, // BSM 报文中 SOC 过高
// {E_FAULT_15015, "BSM SOC too low"}, // BSM 报文中 SOC 过低
// {E_FAULT_15016, "BSM charging overcurrent"}, // BSM 报文中充电过流
// {E_FAULT_15017, "BSM battery overtemperature"}, // BSM 报文中动力蓄电池温度过高
// {E_FAULT_15018, "BSM battery insulation abnormal"}, // BSM 报文中动力蓄电池绝缘状态异常
// {E_FAULT_15019, "BSM connector connection abnormal"}, // BSM 报文中连接器连接状态异
// {E_FAULT_15020, "BRO canceled after ready"}, // BRO 准备就绪后取消
// {E_FAULT_15021, "BST unknown stop fault"}, // BST 停止未知故障
// {E_FAULT_15022, "BST insulation fault"}, // BST 绝缘故障
// {E_FAULT_15023, "BST connector overtemperature"}, // BST 连接器过温
// {E_FAULT_15024, "BST component and output connector overtemperature"}, // BST 元件、 输出连接器过温
// {E_FAULT_15025, "BST connector fault"}, // BST 连接器故障
// {E_FAULT_15026, "BST battery pack over-temperature"}, // BST 电池组过温
// {E_FAULT_15027, "BST other fault"}, // BST 其他故障
// {E_FAULT_15028, "BST detection point 2 voltage detection fault"}, // BST 检测点 2 电压检测故障
// {E_FAULT_15029, "BST high-voltage relay fault"}, // BST 高压继电器故障
// {E_FAULT_15030, "BST overcurrent"}, // BST 电流过大
// {E_FAULT_15031, "BST voltage abnormal"}, // BST 电压异常
// {E_FAULT_15032, "BRM frame data item abnormal"}, // BRM 报文数据项异常
// {E_FAULT_15033, "BCP frame data item abnormal"}, // BCP 报文数据项异常
{E_FAULT_15034, "Outside contactor voltage exceeds 60V before insulation test"}, // 绝缘监测前直流输出接触器外侧电压越限(60V)
{E_FAULT_15035, "Outside contactor voltage differs from battery frame over +/-5%"}, // 启动充电前直流输出接触器外侧电压与通信报文电池电压相差>±5%
{E_FAULT_15036, "Battery max charge voltage below charger minimum output voltage"}, // 电池最高允许充电电压小于充电机最小输出电压
{E_FAULT_15037, "Output voltage exceeds vehicle max charge voltage"}, // 输出电压大于车辆最高允许充电电压
{E_FAULT_15038, "Demand current exceeds vehicle max charge current"}, // 需求电流超过车辆最高允许充电电流
{E_FAULT_15039, "Demand voltage exceeds vehicle max charge voltage"}, // 需求电压超过车辆最高允许充电电压
{E_FAULT_15040, "BCS max cell voltage exceeds BCP allowed max cell voltage"}, // BCS 中最高单体电池电压超过BCP 中最高允许单体电池电压
// {E_FAULT_15041, "BSM max battery temp exceeds BCP allowed max temp"}, // BSM 中最高动力蓄电池温度超过BCP 中单体动力蓄电池最高允许温度
{E_FAULT_15042, "SOC reached upper limit"}, // SOC 达到上限值
// {E_FAULT_15043, "BMS communication protocol version mismatch"}, // BMS 通信协议版本不匹配
{E_FAULT_15044, "Output current exceeds maximum allowed charging current"}, // 输出电流大于最高允许充电电流
{E_FAULT_15045, "Battery terminal voltage exceeds maximum allowed charge voltage"}, // 电池端电压大于电池最高允许充电电压
// {E_FAULT_15046, "SOC reaches 100 with continuous large current request optional"}, // SOC 达到 100 持续请求大电流(可选)
};
#endif
#if 1 //故障动作缓存管理
/**
* ************************************************************************
* @brief - 故障动作缓存初始化
*
*
* @author jiankalka
* @date 2025-12-18
* ************************************************************************
*/
void v_fault_cache_init(void)
{
memset(&s_fault_action_cache, 0, sizeof(FAULT_ACTION_CACHE_T));
s_fault_action_cache.front = 0;
s_fault_action_cache.rear = 0;
s_fault_action_cache.count = 0;
}
/**
* ************************************************************************
* @brief - 添加故障动作到缓存队列
*
* @param[in] f_site - 故障位置
* @param[in] type - 动作类型
* @param[in] e_info - 故障信息
* @param[in] action_type - 拔枪恢复使能
* @param[in] data1 - 测量值1
* @param[in] data2 - 测量值2
*
* @return U8_T - 1成功,0失败(缓存已满)
*
* @author jiankalka
* @date 2025-12-18
* ************************************************************************
*/
U8_T u8_fault_cache_add(E_FAULT_SITE f_site ,E_FAULT_TYPE type , E_FAULT_INFO e_info, U8_T action_type, S16_T data1 , S16_T data2)
{
if(s_fault_action_cache.count >= FAULT_ACTION_CACHE_SIZE)
{
return 0; //缓存已满
}
//添加到队列尾部
s_fault_action_cache.items[s_fault_action_cache.rear].f_site = f_site;
s_fault_action_cache.items[s_fault_action_cache.rear].type = type;
s_fault_action_cache.items[s_fault_action_cache.rear].e_info = e_info;
s_fault_action_cache.items[s_fault_action_cache.rear].action_type = action_type;
s_fault_action_cache.items[s_fault_action_cache.rear].data1 = data1;
s_fault_action_cache.items[s_fault_action_cache.rear].data2 = data2;
s_fault_action_cache.rear = (s_fault_action_cache.rear + 1) % FAULT_ACTION_CACHE_SIZE;
s_fault_action_cache.count++;
return 1;
}
/**
* ************************************************************************
* @brief - 从缓存队列获取一个故障动作
*
* @param[out] p_item - 故障动作项指针
*
* @return U8_T - 1成功,0失败(缓存为空)
*
* @author jiankalka
* @date 2025-12-18
* ************************************************************************
*/
static U8_T u8_fault_cache_get(FAULT_ACTION_CACHE_ITEM_T *p_item)
{
if(s_fault_action_cache.count == 0)
{
return 0; //缓存为空
}
if(p_item == NULL)
{
return 0;
}
//从队列头部获取
memcpy(p_item, &s_fault_action_cache.items[s_fault_action_cache.front], sizeof(FAULT_ACTION_CACHE_ITEM_T));
s_fault_action_cache.front = (s_fault_action_cache.front + 1) % FAULT_ACTION_CACHE_SIZE;
s_fault_action_cache.count--;
return 1;
}
/**
* ************************************************************************
* @brief - 处理缓存中的故障动作
*
*
* @author jiankalka
* @date 2025-12-18
* ************************************************************************
*/
void v_fault_cache_process(void)
{
FAULT_ACTION_CACHE_ITEM_T item;
U8_T processed_count = 0;
const U8_T MAX_PROCESS_PER_CYCLE = 5; //每个周期最多处理5个动作,避免占用太多时间
//每个周期处理最多MAX_PROCESS_PER_CYCLE个缓存动作
while(processed_count < MAX_PROCESS_PER_CYCLE && u8_fault_cache_get(&item))
{
//调用内部函数执行实际的故障动作
v_set_fault_data_internal(item.f_site, item.type, item.e_info,
item.action_type, item.data1, item.data2);
processed_count++;
}
//如果缓存仍然有未处理的动作,记录日志
if(s_fault_action_cache.count > 0)
{
// FAULT_LOG("Fault cache pending=%d\n", s_fault_action_cache.count);
}
}
#endif
#if 1 //对外函数接口
/**
* ************************************************************************
* @brief - 设置故障内容-故障动作记录(内部实现,不对外公开)
*
* @param[in] f_site - 故障位置 E_WHOLE_PILE/整桩 E_GUN/A枪 E_GUN_B/B枪
* @param[in] type - 动作类型 E_RES = 0/恢复 E_FAULT/故障 E_WARN/警告
* @param[in] e_info - 故障信息 详看枚举
* @param[in] action_type - 拔枪恢复使能
* @param[in] data1 - 测量值1 故障内容自定义
* @param[in] data2 - 测量值2 故障内容自定义
*
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
static void v_set_fault_data_internal(E_FAULT_SITE f_site ,E_FAULT_TYPE type , E_FAULT_INFO e_info, U8_T action_type, S16_T data1 , S16_T data2)
{
HIS_FAULT_DATA_T fault_data;
Comm_Time curTime;
U8_T i;
char mag[FAULT_GET_MAG_MAX_LEN]= {0};
char c_site_mag[NUM_30] = {0};
if(FAULT_TEST == 1)
return;
memset(&fault_data,0,sizeof(HIS_FAULT_DATA_T));
GetCurrentTime(&curTime);
memcpy(&fault_data.s_occur_time, &curTime, sizeof(Comm_Time)); //发生时间
fault_data.e_site = f_site;
fault_data.ActType = type;
fault_data.e_fault_info = e_info;
fault_data.s16_value1 = data1;
fault_data.s16_value2 = data2;
v_fault_get_site_mag(f_site,c_site_mag,NUM_30);
if(type == E_RES) //恢复
{
if(s_real_fault.u8_fault_cnt != 0) //实时故障记录不为0,找到对应的故障数据 清空
{
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag == e_info && s_real_fault.fault_data[i].e_ingun == f_site)
{
// v_hisFault_save_record(&fault_data); //保存动作记录
s_real_fault.u8_fault_cnt -- ;
memset(&s_real_fault.fault_data[i],0,sizeof(Fault_data));
v_get_fault_info(e_info , mag ,FAULT_GET_MAG_MAX_LEN);
FAULT_LOG("Fault RES %s:%d %s\n",c_site_mag,e_info,mag);
break;
}
}
}
if(s_real_fault.u8_warn_cnt != 0) //实时告警记录不为0,找到对应的故障数据 清空
{
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag == e_info && s_real_fault.warn_data[i].e_ingun == f_site)
{
// v_hisFault_save_record(&fault_data); //保存动作记录
s_real_fault.u8_warn_cnt -- ;
memset(&s_real_fault.warn_data[i],0,sizeof(Fault_data));
v_get_fault_info(e_info , mag ,FAULT_GET_MAG_MAX_LEN);
FAULT_LOG("Warn RES %s:%d %s\n",c_site_mag,e_info,mag);
break;
}
}
}
}
else if(type == E_FAULT) //故障
{
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag == e_info && s_real_fault.fault_data[i].e_ingun == f_site)
{
return; //故障粗在 则退出
}
}
if(s_real_fault.u8_fault_cnt < CURR_FAULT_MAX_CNT)
{
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag == E_FAULT_NULL)
{
// 故障动作持久化(默认 Flash;可按宏同时写 FATFS 缓存)
v_fault_persist_record(&fault_data);
s_real_fault.u8_fault_cnt ++ ;
s_real_fault.fault_data[i].e_mag = e_info;
s_real_fault.fault_data[i].e_ingun = f_site;
s_real_fault.fault_data[i].u8_type = action_type;
memcpy(&s_real_fault.fault_data[i].time , &curTime , sizeof(Comm_Time));
v_get_fault_info(e_info , mag ,FAULT_GET_MAG_MAX_LEN);
FAULT_LOG("Fault SET %s:%d %s\n",c_site_mag,e_info,mag);
break;
}
}
}
}
else if(type == E_WARN) //告警
{
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag == e_info && s_real_fault.warn_data[i].e_ingun == f_site)
{
return; //故障粗在 则退出
}
}
if(s_real_fault.u8_warn_cnt < CURR_ALARM_MAX_CNT)
{
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag == E_FAULT_NULL)
{
// 告警动作持久化(默认 Flash;可按宏同时写 FATFS 缓存)
v_fault_persist_record(&fault_data);
s_real_fault.u8_warn_cnt ++ ;
s_real_fault.warn_data[i].e_mag = e_info;
s_real_fault.warn_data[i].e_ingun = f_site;
s_real_fault.warn_data[i].u8_type = action_type;
memcpy(&s_real_fault.warn_data[i].time ,&curTime, sizeof(Comm_Time)); //发生时间
v_get_fault_info(e_info , mag ,FAULT_GET_MAG_MAX_LEN);
FAULT_LOG("Warn SET %s:%d %s\n",c_site_mag,e_info,mag);
break;
}
}
}
}
}
/**
* ************************************************************************
* @brief - 设置故障内容-故障动作记录(对外接口,将动作缓存)
*
* @param[in] f_site - 故障位置 E_WHOLE_PILE/整桩 E_GUN/A枪 E_GUN_B/B枪
* @param[in] type - 动作类型 E_RES = 0/恢复 E_FAULT/故障 E_WARN/警告
* @param[in] e_info - 故障信息 详看枚举
* @param[in] action_type - 拔枪恢复使能
* @param[in] data1 - 测量值1 故障内容自定义
* @param[in] data2 - 测量值2 故障内容自定义
*
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
void v_set_fault_data(E_FAULT_SITE f_site ,E_FAULT_TYPE type , E_FAULT_INFO e_info, U8_T action_type, S16_T data1 , S16_T data2)
{
// 将故障动作添加到缓存队列
if(u8_fault_cache_add(f_site, type, e_info, action_type, data1, data2) == 0)
{
// FAULT_LOG("Fault cache add fail: %d %d %d\n", f_site, type, e_info);
}
}
/**
* ************************************************************************
* @brief - 获取故障告警数据的 中文解析数据
*
* @param[in] e_info - 故障告警数据
* @param[in] mag - 对应中文说明缓存数组
* @param[in] u16_len - 缓存数组长度
*
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
void v_get_fault_info(E_FAULT_INFO e_info , char *mag ,U16_T u16_len)
{
U16_T i;
U16_T info_size;
if ((mag == NULL) || (u16_len == 0u)) {
return;
}
info_size = (U16_T)(sizeof(fault_info) / sizeof(fault_info[0]));
memset(mag, 0, u16_len);
for (i = 0u; i < info_size; i++) {
if (e_info == fault_info[i].e_mag) {
strncpy(mag, fault_info[i].info, u16_len - 1u);
mag[u16_len - 1u] = '\0';
return;
}
}
(void)snprintf(mag, u16_len, "Unknown fault: %u", (unsigned int)e_info);
}
/**
* ************************************************************************
* @brief - 对外获取故障告警数量
*
* @param[in] u8_seat - 故障位置 0整桩,不包含A、B枪故障 1A枪 2B枪
* @param[in] flag - 故障类型 0故障数量 1告警数量
*
* @return U8_T - 故障、告警数量值返回
*
* @author jiankalka
* @date 2025-09-09
*
* ************************************************************************
*/
U8_T u8_get_fault_sum(U8_T u8_seat, U8_T u8_flag)
{
U8_T i,u8_ret = 0;
if(u8_seat >= 1 && u8_seat <= GUN_MAX_CNT) //1-N 是枪的范围
{
if(u8_flag == 0 && s_real_fault.u8_fault_cnt != 0)
{
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag != 0 &&
s_real_fault.fault_data[i].e_ingun == E_GUN+u8_seat-1)
{
u8_ret++;
}
}
}
if(u8_flag == 1 && s_real_fault.u8_warn_cnt != 0)
{
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag != 0 &&
s_real_fault.warn_data[i].e_ingun == E_GUN+u8_seat-1)
{
u8_ret++;
}
}
}
}
else //整桩故障、告警数量
{
if(u8_flag == 0 && s_real_fault.u8_fault_cnt != 0)
{
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag != 0 &&
(s_real_fault.fault_data[i].e_ingun == E_WHOLE_PILE || //整桩
s_real_fault.fault_data[i].e_ingun == E_WHOLE_HOST)) //主机故障
//s_real_fault.fault_data[i].e_ingun >= E_CHAR_MDU)) //模块或开关故障 此处先不加了 模块开关故障 可能可以充电
{
u8_ret++;
}
}
}
if(u8_flag == 1 && s_real_fault.u8_warn_cnt != 0)
{
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag != 0 &&
(s_real_fault.warn_data[i].e_ingun == E_WHOLE_PILE ||
s_real_fault.warn_data[i].e_ingun == E_WHOLE_HOST ))
//s_real_fault.warn_data[i].e_ingun >= E_CHAR_MDU))
{
u8_ret++;
}
}
}
}
return u8_ret;
}
/**
* ************************************************************************
* @brief - 对外接口 获取故障告警数据
*
* @param[in] rl_data - 故障告警获取结构体指针
*
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
void v_get_fault_data(REAL_FAULT_DATA_T *rl_data)
{
if(rl_data == NULL)
return;
memset(rl_data,0,sizeof(REAL_FAULT_DATA_T));
memcpy(rl_data,&s_real_fault,sizeof(REAL_FAULT_DATA_T));
}
/**
* ************************************************************************
* @brief - 获取 故障是否存在在列表中
*
* @param[in] e_info - 告警数据
* @param[in] f_site - 故障位置
*
* @return U8_T - 0 未存在 1存在为故障 2存在为告警
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
U8_T u8_cheak_fault_flag(E_FAULT_INFO e_info , E_FAULT_SITE f_site)
{
U8_T i,ret = 0;
if(s_real_fault.u8_fault_cnt != 0)
{
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag == e_info && s_real_fault.fault_data[i].e_ingun == f_site)
{
ret = 1;
break;
}
}
}
if(s_real_fault.u8_warn_cnt != 0)
{
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag == e_info && s_real_fault.warn_data[i].e_ingun == f_site)
{
ret = 2;
break;
}
}
}
return ret;
}
#endif
#if 1 //内部数据获取
/**
* ************************************************************************
* @brief - 故障检测任务 获取外部数据
*
* @param[in] u8_gunNo - 枪号 0-N 部分数据无意义
* @param[in] e_dataType - 数据类型 详看枚举
* @param[in] u16_dataPara - 数据参数 部分数据无意义
*
* @return U16_T - 返回值
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
U16_T u16_fault_get_sys_data(U8_T u8_gunNo,E_FAULT_GET_DATA_TYPE e_dataType,U16_T u16_dataPara)
{
U16_T u16_ret = 0;
switch (e_dataType)
{
case E_FAULT_GET_DATA_EMERGENCY : //急停
{
u16_ret = u8_get_sys_yx_data(YX_PILE_EMERGENCY);
break;
}
case E_FAULT_GET_DATA_DOOR: //门禁
{
u16_ret = u8_get_sys_yx_data(YX_PILE_DOOR);
break;
}
case E_FAULT_GET_DATA_WARTER: //水浸
{
u16_ret = u8_get_sys_yx_data(YX_PILE_WARTER);
break;
}
case E_FAULT_GET_DATA_SPD: //防雷状态
{
u16_ret = u8_get_sys_yx_data(YX_PILE_SPD);
break;
}
case E_FAULT_GET_DATA_SMOKE: //烟感
{
u16_ret = u8_get_sys_yx_data(YX_PILE_SMOKE);
break;
}
case E_FAULT_GET_DATA_GUN_DC_FUSE: //直流熔断器状态
{
u16_ret = u8_get_gun_yx_data(YX_GUN_DC_FUSE,u8_gunNo );
break;
}
case E_FAULT_GET_DATA_GUN_TEMP_THRESHOLD: //枪温阈值
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_threshold_gunTemp*10;
break;
}
case E_FAULT_GET_DATA_GUN_TEMP: //枪温 精度 0.1度
{
if(u16_dataPara == 0) //正极枪温
u16_ret = (U16_T)(f32_get_gun_yc_data(ADC_GUN_TEMP_PLUS,u8_gunNo)*10);
else //负极枪温
u16_ret = (U16_T)(f32_get_gun_yc_data(ADC_GUN_TEMP_MINUS,u8_gunNo)*10);
break;
}
case E_FAULT_GET_DATA_GUN_CNT: //枪数量
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_gunCnt;
break;
}
case E_FAULT_GET_DATA_DOOR_EN: //门禁使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_doorEnable;
break;
}
case E_FAULT_GET_DATA_AC_CONTACT_EN: //交流接触器使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_AcContactEna;
break;
}
case E_FAULT_GET_DATA_GUN_LINK: //枪连接标志 0未连接 1已连接
{
u16_ret = u8_get_gun_yx_data(YX_GUN_LINK,u8_gunNo);
break;
}
case E_FAULT_GET_DATA_GUN_CHAR_FLAG: //充电标志 0未充电 1充电中
{
E_FLOW_REAL_STATE flow_state = (E_FLOW_REAL_STATE)u16_flow_put_data(u8_gunNo , E_FLOW_PUT_DATA_GUN_REAL_STATE,0); //0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
if(flow_state == E_FLOW_REAL_CHARGEING) //充电中
{
u16_ret = 1 ;
}
else
{
u16_ret = 0 ;
}
break;
}
case E_FAULT_GET_DATA_AC_KM: //交流输入接触器反馈
{
u16_ret = u8_get_sys_yx_data(YX_PILE_KM);
break;
}
case E_FAULT_GET_DATA_BUS_KM: //母联直流接触器反馈
{
u16_ret = u8_get_sys_yx_data(YX_PILE_BUS_KM);
break;
}
case E_FAULT_GET_DATA_BUS_KM_EN: //母联继电器使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_dcBusEn;
break;
}
case E_FAULT_GET_DATA_FLOW_STATE: //本枪流程状态(供故障检测,不对外混用 flow 枚举头时可只读数值)
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = u16_flow_get_sys_data(u8_gunNo, E_FLOW_GET_DATA_FLOW_STATE, 0);
break;
}
case E_FAULT_GET_DATA_CHAR_TIME: //本枪充电持续时间 秒
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = u16_flow_get_sys_data(u8_gunNo, E_FLOW_GET_DATA_CHAR_TIME, 0);
break;
}
case E_FAULT_GET_DATA_GUN_DC_KM_POS: //正母线直流输出接触器状态
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = u16_flow_get_sys_data(u8_gunNo, E_FLOW_GET_DATA_KM_POS, 0);
break;
}
case E_FAULT_GET_DATA_GUN_DC_KM_NEG: //负母线直流输出接触器状态
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = u16_flow_get_sys_data(u8_gunNo, E_FLOW_GET_DATA_KM_NEG, 0);
break;
}
default:
break;
}
return u16_ret;
}
void v_fault_get_site_mag(E_FAULT_SITE e_site , char *mag , U8_T u8_len)
{
memset(mag , 0 ,u8_len);
if(e_site == E_WHOLE_PILE)
snprintf(mag ,u8_len, "Pile-Terminal");
else if(e_site == E_WHOLE_HOST)
snprintf(mag ,u8_len, "Pile-Host");
else if(e_site < E_CHAR_MDU)
snprintf(mag ,u8_len, "[%d] Gun",e_site-E_GUN);
else if(e_site < E_SWH_MDU)
snprintf(mag ,u8_len, "[%d] Module",e_site-E_CHAR_MDU);
else if(e_site < E_OTHER)
snprintf(mag ,u8_len, "[%d] Switch",e_site-E_SWH_MDU);
else
snprintf(mag ,u8_len, "[%d] Other",e_site-E_OTHER);
}
#endif
#if(MY_SHELL_EN) //Shell 使能
//shell 注册命令
void v_fault_data_show()
{
char buffer[64] = {0};
char c_site_mag[NUM_30] = {0};
U8_T i,mag[FAULT_GET_MAG_MAX_LEN]={0};
E_FAULT_INFO e_info;
E_FAULT_SITE e_site;
sprintf( &buffer[0], "故障数量= %u ,告警数量= %u\r\n",
s_real_fault.u8_fault_cnt,s_real_fault.u8_warn_cnt);
shell_send_String(buffer);
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag != 0)
{
e_info = s_real_fault.fault_data[i].e_mag;
e_site = s_real_fault.fault_data[i].e_ingun;
v_fault_get_site_mag(e_site,c_site_mag,NUM_30);
v_get_fault_info(e_info , (char*)mag,FAULT_GET_MAG_MAX_LEN);
memset(buffer , 0,64);
sprintf( &buffer[0], "%s故障 %d %s \r\n",c_site_mag,e_info,mag);
shell_send_String(buffer);
}
}
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag != 0)
{
e_info = s_real_fault.warn_data[i].e_mag;
e_site = s_real_fault.warn_data[i].e_ingun;
v_fault_get_site_mag(e_site,c_site_mag,NUM_30);
v_get_fault_info(e_info , (char*)mag,FAULT_GET_MAG_MAX_LEN);
memset(buffer , 0,64);
sprintf( &buffer[0], "%s告警 %d %s \r\n",c_site_mag,e_info,mag);
shell_send_String(buffer);
}
}
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), fault_data, v_fault_data_show,myshlle-- fault data info);
#endif
+372
View File
@@ -0,0 +1,372 @@
#ifndef FAULT_INTERFACE_H
#define FAULT_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
/*------ Exported macro(宏定义) -------------------------*/
#define FAULT_ACTION_CACHE_SIZE 16 //故障动作缓存大小
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
E_FAULT_NULL = 0,
//常规停止代码
E_NORMAL_1000 = 1000, //1 1000 充满停止
E_NORMAL_1001, //1 1001 本地停止充电
E_NORMAL_1002, //1 1002 后台停止充电
E_NORMAL_1003, //1 1003 达到设置充电时长停止
E_NORMAL_1004, //1 1004 达到设置充电电量停止
E_NORMAL_1005, //1 1005 达到设置充电金额停止
E_NORMAL_1006, //1 1006 达到离线停机条件
E_NORMAL_1007, //1 1007 达到SOC终止条件停止
E_NORMAL_1008, //1008 用户拔枪停止
E_NORMAL_1009, //1 1009 APP停止充电
E_NORMAL_1010, //1010 车端S2主动断开
E_NORMAL_1011, //1 1011 BMS停止充电
E_NORMAL_1012, //1 1012 账户余额不足
E_NORMAL_1013, //1 1013 预付金额不足
E_NORMAL_1014, //1014 车辆S2闭合超时
E_NORMAL_1015, //1015 充电中车辆S2断开超时
E_NORMAL_1016, //1016 达到涓流充电停机条件
//直流充电设备异常代码
E_FAULT_13000 = 13000, //1 13000 交流输入断路器故障
E_FAULT_13001, //1 13001 避雷器故障
E_FAULT_13002, //1 13002 交流输入接触器粘连故障
E_FAULT_13003, //1 13003 交流输入接触器拒动/误动故障
E_FAULT_13004, //1 13004 直流输出接触器粘连故障
E_FAULT_13005, //1 13005 直流输出接触器拒动/误动故障
E_FAULT_13006, //1 13006 直流输出熔断器故障
E_FAULT_13007, //1 13007 辅助电源故障
E_FAULT_13008, //13008 泄放继电器拒动/误动故障
E_FAULT_13009, //13009 泄放继电器粘连
E_FAULT_13010, //1 13010 充电枪电子锁上锁故障
E_FAULT_13011, //1 13011 充电枪电子锁解锁故障
E_FAULT_13012, //13012 PE 断线故障
E_FAULT_13013, //1 13013 充电接口过温故障
E_FAULT_13014, //1 13014 充电接口过温告警
E_FAULT_13015, //1 13015 控制导引电压异常
E_FAULT_13016, //1 13016 充电中车辆控制导引故障
E_FAULT_13017, //1 13017 急停按钮动作故障
E_FAULT_13018, //1 13018 充电桩门禁故障
E_FAULT_13019, //1 13019 充电机柜门禁故障
E_FAULT_13020, //13020 功率控制模块故障
E_FAULT_13021, //13021 功率控制模块告警
E_FAULT_13022, //1 13022 功率控制模块通信故障
E_FAULT_13023, //13023 桥接接触器拒动/误动故障
E_FAULT_13024, //13024 桥接接触器粘连故障
E_FAULT_13025, //13025 ESAM 故障
E_FAULT_13026, //13026 预留( 充电机柜加热部件故障)
E_FAULT_13027, //13027 充电桩过温故障( 可选)
E_FAULT_13028, //13028 充电桩过温告警( 可选)
E_FAULT_13029, //13029 充电机柜过温告警
E_FAULT_13030, //1 13030 充电机柜过温故障
E_FAULT_13031, //1 13031 预留( 散热系统故障)
E_FAULT_13032, //1 13032 烟感报警
E_FAULT_13033, //1 13033 充电桩水浸故障
E_FAULT_13034, //1 13034 充电机柜水浸故障
E_FAULT_13035, //13035 充电机环境湿度告警
E_FAULT_13036, //13036 数据合法性校验失败(“ 充电启动帧” 中“ 负荷控制开关” 等数据项合法性校验失败)
E_FAULT_13037, //13037 充电控制器“ 版本校验” 未完成
E_FAULT_13038, //13038 充电控制器“ 下发充电参数” 未完成
E_FAULT_13039, //1 13039 充电桩暂停服务, 拒绝启动
E_FAULT_13040, //1 13040 充电桩处于“工作” 状态, 拒绝启动
E_FAULT_13041, //1 13041 充电桩与车辆处于未连接状态,拒绝启动
E_FAULT_13042, //1 13042 绝缘监测故障
E_FAULT_13043, //1 13043 绝缘监测告警
E_FAULT_13044, //13044 充电桩执行功率分配策略失败故障( TBD)
E_FAULT_13045, //13045 预留( 输入电压过压故障) 改主机液冷故障
E_FAULT_13046, //13046 预留( 输入电压欠压故障) 改为主机柜急停
E_FAULT_13047, //1 13047 泄放回路故障
E_FAULT_13048, //1 13048 直流输出电压过压故障
E_FAULT_13049, //1 13049 直流输出电流过流故障
E_FAULT_13050, //1 13050 直流输出电压欠压故障
E_FAULT_13051, //13051 无空闲模块可用( 限智能分配功率) ( TBD)
E_FAULT_13052, //13052 电池反接故障( TBD)
E_FAULT_13053, //1 13053 预充阶段调压失败
E_FAULT_13054, //1 13054 输出电流大于车辆需求电流
E_FAULT_13055, //1 13055 充电过程中功率控制器为非工作状态
E_FAULT_13056, //1 13056 电能计量异常
E_FAULT_13057, //1 13057 车辆鉴权码不一致
E_FAULT_13058, //13058 未上送交易记录已满
E_FAULT_13059, //13059 交易记录存储失败
E_FAULT_13060, //13060 CAN 通讯版本校验异常
E_FAULT_13061, //13061 进程运行异常
E_FAULT_13062, //13062 程序文件校验失败
E_FAULT_13063, //13063 程序文件损坏
E_FAULT_13064, //1 13064 非法 VIN
E_FAULT_13065, //13065 非即插即充设备
E_FAULT_13066, //13066 充电桩处于“暂停” 状态( 27930中的暂停状态)
E_FAULT_13067, //1 13067 即插即充鉴权失败
E_FAULT_13068, //1 13068 充电桩充电中暂停超时( 27930中的暂停充电)
E_FAULT_13069, //13069 充电控制器通讯超时
E_FAULT_13070, //13070 读卡器通讯超时
E_FAULT_13071, //1 13071 电表通讯超时
E_FAULT_13072, //13072 两枪同充一车模式下充电控制器间通信超时
E_FAULT_13073, //1 13073 异常掉电
E_FAULT_13074, //1 13074 预留 液冷通讯超时
E_FAULT_13075, //13075 功率控制器间通讯超时( 功率并
E_FAULT_13076, //13076 功率控制器接收遥控、 遥信报文超时故障
E_FAULT_13077, //1 13077 功率控制器与环境信息采集模块通讯超时
E_FAULT_13078, //1 13078 车辆识别数据帧接收超时
E_FAULT_13079, //13079 充电启动命令应答超时
E_FAULT_13080, //13080 等待充电启动完成状态超时
E_FAULT_13081, //13081 充电停止命令应答超时
E_FAULT_13082, //13082 等待充电停止完成状态超时
E_FAULT_13083, //13083 充电启动完成状态确认超时
E_FAULT_13084, //13084 功率调节指令应答超时
E_FAULT_13085, //13085 充电停止完成状态确认超时
E_FAULT_13086, //13086 充电控制器间并枪时通信异常(充电控制器与充电控制器通讯超时)
E_FAULT_13087, //13087 功率控制器地址冲突(并柜时)
E_FAULT_13088, //13088 充电控制器地址冲突
E_FAULT_13089, //13089 人机通信异常
E_FAULT_13090, //1 13090 设备自检超时故障
E_FAULT_13091, //13091 内部通讯故障
E_FAULT_13092, //13092 车位锁故障
E_FAULT_13093, //13093 车位锁电池耗尽故障
E_FAULT_13094, //13094 车位锁落锁失败故障
E_FAULT_13095, //13095 终止订单号异常
E_FAULT_13096, //13096 开出盒通讯故障
E_FAULT_13097, //13097 开入盒通讯故障
E_FAULT_13098, //13098 绝缘采样盒通讯故障
E_FAULT_13099, //1 13099 直流采样盒通讯故障(AB板通讯故障)
E_FAULT_13100, //13100 导引板通讯故障
E_FAULT_13101, //13101 灯板通讯告警 //改为主机液冷通信超时
E_FAULT_13102, //13102 充电控制器未知停止原因
E_FAULT_13103, //13103 辅助电源电压切换回路故障
E_FAULT_13104, //1 13104 输出电流超过车辆最高允许电流
E_FAULT_13105, //13105 系统时钟异常
E_FAULT_13106, //13106 计费模型请求超时故障
E_FAULT_13107, //13107 计费模型不合法故障
E_FAULT_13108, //13108 对时超时故障
E_FAULT_13109, //13109 对时时间不合法故障
//直流充电电源异常代码
E_FAULT_14000 = 14000, //14000 开关模块故障
E_FAULT_14001, //1 14001 开关模块告警
E_FAULT_14002, //1 14002 开关模块通信故障
E_FAULT_14003, //14003 开关模块拒动/误动故障
E_FAULT_14004, //14004 开关模块粘连故障
E_FAULT_14005, //14005 预留(开关模块拒动/误动告警)
E_FAULT_14006, //14006 预留(开关模块粘连告警)
E_FAULT_14007, //1 14007 充电模块故障
E_FAULT_14008, //14008 充电模块过温故障
E_FAULT_14009, //1 14009 充电模块过温告警
E_FAULT_14010, //1 14010 充电模块输出短路故障
E_FAULT_14011, //1 14011 充电模块输出过流告警
E_FAULT_14012, //1 14012 充电模块输出过压告警
E_FAULT_14013, //1 14013 充电模块输出欠压告警
E_FAULT_14014, //1 14014 充电模块交流输入告警
E_FAULT_14015, //1 14015 充电模块输入过压告警
E_FAULT_14016, //1 14016 充电模块输入欠压告警
E_FAULT_14017, //1 14017 充电模块输入缺相告警
E_FAULT_14018, //14018 充电模块输入不平衡告警
E_FAULT_14019, //1 14019 充电模块通信故障
E_FAULT_14020, //1 14020 充电模块启动失败
E_FAULT_14021, //1 14021 充电模块关机失败
E_FAULT_14022, //1 14022 充电模块风扇故障
E_FAULT_14023, //1 14023 充电模块泄放故障
E_FAULT_14024, //14024 充电模块地址异常
E_FAULT_14025, //14025 PFC 电路过温故障
E_FAULT_14026, //14026 主变压器过温故障
E_FAULT_14027, //14027 DC/DC 电路过温故障
E_FAULT_14028, //14028 输出隔离二极管故障
E_FAULT_14029, //14029 模块启动故障(非保护动作造成的模块启动失败)
E_FAULT_14030, //14030 PFC 电路总故障
E_FAULT_14031, //14031 PFC 电路过压故障
E_FAULT_14032, //14032 PFC 电路偏压故障
E_FAULT_14033, //14033 DC/DC 电路总故障
E_FAULT_14034, //14034 高低压切换电路故障
E_FAULT_14035, //14035 输出电路不平衡故障
E_FAULT_14036, //1 14036 充电模块不输出告警
E_FAULT_14037, //1 14037 启动充电前直流输出接触器外侧电压大于充电机最大输出电压
E_FAULT_14038, //1 14038 启动充电前直流输出接触器外侧电压小于充电机最小输出电压
//直流充电车辆异常代码
E_FAULT_15000 = 15000, //1 15000 BMS 通讯故障
E_FAULT_15001, //1 15001 直流充电控制模块判断BMS故障
E_FAULT_15002, //1 15002 BMS 发送故障信息
E_FAULT_15003, //1 15003 BCP 充电参数配置报文超时
E_FAULT_15004, //1 15004 BRO 充电准备就绪报文超时
E_FAULT_15005, //1 15005 BCS 电池充电状态报文超时
E_FAULT_15006, //1 15006 BCL 电池充电需求报文超时
E_FAULT_15007, //1 15007 BST 中止充电报文超时
E_FAULT_15008, //1 15008 BSM 动力蓄电池状态报文超时
E_FAULT_15009, //1 15009 BRO 报文(0x00 接收超时
E_FAULT_15010, //1 15010 BRO 报文(0xAA 接收超时
E_FAULT_15011, //1 15011 BRM 车辆辨识报文超时
E_FAULT_15012, //1 15012 BSM 报文中单体动力蓄电池电压过高
E_FAULT_15013, //1 15013 BSM 报文中单体动力蓄电池电压过低
E_FAULT_15014, //1 15014 BSM 报文中 SOC 过高
E_FAULT_15015, //1 15015 BSM 报文中 SOC 过低
E_FAULT_15016, //1 15016 BSM 报文中充电过流
E_FAULT_15017, //1 15017 BSM 报文中动力蓄电池温度过高
E_FAULT_15018, //1 15018 BSM 报文中动力蓄电池绝缘状态异常
E_FAULT_15019, //1 15019 BSM 报文中连接器连接状态异
E_FAULT_15020, //1 15020 BRO 准备就绪后取消
E_FAULT_15021, //1 15021 BST 停止未知故障
E_FAULT_15022, //1 15022 BST 绝缘故障
E_FAULT_15023, //1 15023 BST 连接器过温
E_FAULT_15024, //1 15024 BST 元件、 输出连接器过温
E_FAULT_15025, //1 15025 BST 连接器故障
E_FAULT_15026, //1 15026 BST 电池组过温
E_FAULT_15027, //1 15027 BST 其他故障
E_FAULT_15028, //1 15028 BST 检测点 2 电压检测故障
E_FAULT_15029, //1 15029 BST 高压继电器故障
E_FAULT_15030, //1 15030 BST 电流过大
E_FAULT_15031, //1 15031 BST 电压异常
E_FAULT_15032, //1 15032 BRM 报文数据项异常(报文数据长度错误, BMS 通信协议版本号错误, 其他数据错误)
E_FAULT_15033, //1 15033 BCP 报文数据项异常(报文数据长度错误, 电池当前电压越限,其他数据项错误)
E_FAULT_15034, //1 15034 绝缘监测前直流输出接触器外侧电压越限(60V)
E_FAULT_15035, //1 15035 启动充电前直流输出接触器外侧电压与通信报文电池电压相差>±5%
E_FAULT_15036, //1 15036 电池最高允许充电电压小于充电机最小输出电压
E_FAULT_15037, //1 15037 输出电压大于车辆最高允许充电电压
E_FAULT_15038, //1 15038 需求电流超过车辆最高允许充电电流
E_FAULT_15039, //1 15039 需求电压超过车辆最高允许充电电压
E_FAULT_15040, //1 15040 BCS 中最高单体电池电压超过BCP 中最高允许单体电池电压
E_FAULT_15041, //1 15041 BSM 中最高动力蓄电池温度超过BCP 中单体动力蓄电池最高允许温度
E_FAULT_15042, //1 15042 SOC 达到上限值
E_FAULT_15043, //1 15043 BMS 通信协议版本不匹配
E_FAULT_15044, //1 15044 输出电流大于最高允许充电电流
E_FAULT_15045, //1 15045 电池端电压大于电池最高允许充电电压
E_FAULT_15046, //1 15046 SOC 达到 100 持续请求大电流(可选)
E_FAULT_16000 = 16000, //1 16000 液冷高压力故障
E_FAULT_16001, //1 16001 液冷低压力故障
E_FAULT_16002, //1 16002 液冷低液位故障
E_FAULT_16003, //1 16003 液冷循环泵过流故障
E_FAULT_16004, //1 16004 循环泵欠压报警
E_FAULT_16005, //1 16005 循环泵过压报警
E_FAULT_16006, //1 16006 循环泵过温报警
E_FAULT_16007, //1 16007 循环泵堵转报警
E_FAULT_16008, //1 16008 循环泵通信故障报警
E_FAULT_16009, //1 16009 低流量报警报警
E_FAULT_16010, //1 16010 高流量报警报警
E_FAULT_16011, //1 16011 风机过流报警
//主机故障
E_FAULT_17000 = 17000, //主机急停故障
E_FAULT_17001, //主机门禁
E_FAULT_17002, //主机水浸
E_FAULT_17003, //主机防雷
E_FAULT_17004, //主机烟感
E_FAULT_17005, //主机充电枪通信超时
E_FAULT_17006, //主机风扇调速通信超时
E_FAULT_17007, //主机计量计费 电表跳变故障
E_FAULT_17008, //主机计量计费 长时间未更新故障
E_FAULT_17009, //主机计量计费 费率异常故障
E_FAULT_17010, //主机
E_FAULT_17999, //主机故障范围
}E_FAULT_INFO;
//故障位置
typedef enum
{
E_WHOLE_PILE = 0, //整桩-终端
E_WHOLE_HOST = 500, //整桩-主机-功率分配
E_GUN = 1000, //枪地址
E_CHAR_MDU = 2000, //充电模块地址
E_SWH_MDU = 3000, //开关模块地址
E_OTHER = 4000, //其他位置
}E_FAULT_SITE;
//故障类型
typedef enum
{
E_RES = 0, //恢复
E_FAULT, //故障
E_WARN, //警告
}E_FAULT_TYPE;
typedef struct
{
U8_T u8_type; //拔枪恢复使能 0拔枪不恢复 1拔枪恢复
E_FAULT_SITE e_ingun; //归属枪号 0无 1一枪 2二枪
E_FAULT_INFO e_mag; //当前故障结构体数组
Comm_Time time; //动作时间
}Fault_data;
//实时故障告警数据
typedef struct
{
U8_T u8_fault_cnt; //故障数量
U8_T u8_warn_cnt; //告警数量
Fault_data fault_data[CURR_FAULT_MAX_CNT];
Fault_data warn_data[CURR_ALARM_MAX_CNT];
}REAL_FAULT_DATA_T;
typedef enum
{
E_FAULT_GET_DATA_EMERGENCY = 0, //急停
E_FAULT_GET_DATA_DOOR, //门禁
E_FAULT_GET_DATA_WARTER, //水浸
E_FAULT_GET_DATA_SPD, //防雷状态
E_FAULT_GET_DATA_SMOKE, //烟感
E_FAULT_GET_DATA_GUN_DC_FUSE, //直流熔断器状态
E_FAULT_GET_DATA_GUN_TEMP_THRESHOLD, //枪温阈值 精度 0.1度
E_FAULT_GET_DATA_GUN_TEMP, //枪温 精度 0.1度 参数值 0正极枪温 1负极枪温
E_FAULT_GET_DATA_GUN_CNT, //枪数量
E_FAULT_GET_DATA_DOOR_EN, //门禁使能
E_FAULT_GET_DATA_AC_CONTACT_EN, //交流接触器使能(与 sharedata bit_AcContactEna 一致)
E_FAULT_GET_DATA_GUN_LINK, //枪连接标志 0未连接 1已连接
E_FAULT_GET_DATA_GUN_CHAR_FLAG, //充电充电标志 0未充电 1充电中
E_FAULT_GET_DATA_AC_KM, //交流输入接触器反馈 0断开 1闭合(YX_PILE_KM
E_FAULT_GET_DATA_BUS_KM, //母联直流接触器反馈 0断开 1闭合(YX_PILE_BUS_KM
E_FAULT_GET_DATA_BUS_KM_EN, //母联继电器使能标识
E_FAULT_GET_DATA_FLOW_STATE, //枪 u8_gunNo 充电流程状态,与 flow 中 E_FLOW_STATE 一致
E_FAULT_GET_DATA_CHAR_TIME, //枪充电持续时间 秒(故障模块统一取数,内部转调 flow)
E_FAULT_GET_DATA_GUN_DC_KM_POS, //枪正母线直流输出接触器遥信 0断 1合
E_FAULT_GET_DATA_GUN_DC_KM_NEG, //枪负母线直流输出接触器遥信 0断 1合
}E_FAULT_GET_DATA_TYPE;
//故障动作缓存项
typedef struct
{
E_FAULT_SITE f_site; //故障位置
E_FAULT_TYPE type; //动作类型
E_FAULT_INFO e_info; //故障信息
U8_T action_type; //拔枪恢复使能
S16_T data1; //测量值1
S16_T data2; //测量值2
}FAULT_ACTION_CACHE_ITEM_T;
//故障动作缓存队列
typedef struct
{
FAULT_ACTION_CACHE_ITEM_T items[FAULT_ACTION_CACHE_SIZE];
U8_T front; //队列头
U8_T rear; //队列尾
U8_T count; //队列中元素数量
}FAULT_ACTION_CACHE_T;
/*------ Exported variables(变量定义) -------------------*/
extern const char fault_site_mag[3][20];
/*------ Exported function prototypes (函数声明) -------*/
void v_get_fault_info(E_FAULT_INFO e_info , char *mag ,U16_T u16_len);
void v_set_fault_data(E_FAULT_SITE f_site ,E_FAULT_TYPE type , E_FAULT_INFO e_info, U8_T action_type, S16_T data1 , S16_T data2);
U8_T u8_get_fault_sum(U8_T u8_seat, U8_T u8_flag);
void v_get_fault_data(REAL_FAULT_DATA_T *rl_data);
U8_T u8_cheak_fault_flag(E_FAULT_INFO e_info , E_FAULT_SITE f_site);
void v_fault_get_site_mag(E_FAULT_SITE e_site , char *mag , U8_T u8_len);
//故障动作缓存相关函数
void v_fault_cache_init(void);
U8_T u8_fault_cache_add(E_FAULT_SITE f_site ,E_FAULT_TYPE type , E_FAULT_INFO e_info, U8_T action_type, S16_T data1 , S16_T data2);
void v_fault_cache_process(void);
//对内使用函数接口
U16_T u16_fault_get_sys_data(U8_T u8_gunNo,E_FAULT_GET_DATA_TYPE e_dataType,U16_T u16_dataPara);
#ifdef __cplusplus
}
#endif
#endif /* FAULT_INTERFACE_H */
+418
View File
@@ -0,0 +1,418 @@
#include "fault_cheak/faultcheck_task.h"
#include "app_init/app_init.h"
#include "app_init/task_lock.h"
#include "mylog/mylog.h"
#include "publicdata/publicdata.h"
#include "flash_file_mgr/fault_flash_impl.h"
#include "app_rtc/app_rtc.h"
#include "wdt_task/wdt_task.h"
#include "collect_ctrl/yk_ctrl.h"
#include "flowctrl/flowctrl_task.h"
/* 本任务ID定义:用于统一索引任务表、日志与锁资源。 */
#define FAULTCHECK_TASK_ID (TASK_ID_FaultCheck)
/* 获取本任务睡眠时间(ms)。 */
#define FAULTCHECK_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(FAULTCHECK_TASK_ID))
/* 本任务锁操作宏:简化锁接口调用。 */
#define FAULTCHECK_LOCK_INIT() (u8_task_lock_ctrl(FAULTCHECK_TASK_ID, TASK_LOCK_OP_INIT))
#define FAULTCHECK_LOCK_TAKE() (u8_task_lock_ctrl(FAULTCHECK_TASK_ID, TASK_LOCK_OP_TAKE))
#define FAULTCHECK_LOCK_GIVE() (u8_task_lock_ctrl(FAULTCHECK_TASK_ID, TASK_LOCK_OP_GIVE))
/* 本任务喂狗宏:标记本任务周期正常运行。 */
#define FAULTCHECK_FEED_DOG() (v_wdt_setFlag(FAULTCHECK_TASK_ID))
/* 本任务打印接口。 */
#define FAULTCHECK_LOG(fmt, ...) MYLOG_MSG(FAULTCHECK_TASK_ID, (fmt), ##__VA_ARGS__)
S_HIS_FAULT_MNG s_hisFaultMng; //故障告警信息记录
REAL_FAULT_DATA_T s_real_fault; //实时故障告警记录存储
#if 1 //故障检测
/**
* ************************************************************************
* @brief - 遥信数据故障检查- 多次调用 因此统一函数接口
*
* @param[in] u8_yx_state - 遥信状态 0正常 1故障
* @param[in] e_info - 对应故障信息
* @param[in] f_site - 故障位置
* @param[in] data1 - 数据1
* @param[in] data2 - 数据2
*
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
static void v_fault_yx_action_cheak(U8_T u8_yx_state, E_FAULT_INFO e_info , E_FAULT_SITE f_site, S16_T data1 , S16_T data2)
{
U8_T u8_fault_in_flag = u8_cheak_fault_flag(e_info , f_site); //故障存在标志
if(u8_yx_state == 1 && u8_fault_in_flag == 0) //故障动作 且故障不存在,则设置故障动作
v_set_fault_data(f_site,E_FAULT,e_info,0,data1,data2);
else if(u8_yx_state == 0 && u8_fault_in_flag != 0) //故障恢复 且故障仍然存在,则设置故障恢复
v_set_fault_data(f_site,E_RES,e_info,0,data1,data2);
}
/** 遥控命令与反馈不一致时的防抖:u8_slot 1=拒动类 2=粘连类,同一时间只累计一类(整桩或单枪 deb 块独立) */
typedef struct
{
U8_T u8_slot; /* 0 未在防抖累计;1 拒动;2 粘连 */
U32_T u32_sec0; /* 当前 u8_slot 对应异常持续计时的起点秒 */
} S_FAULT_KM_MISMATCH_DEB;
/** 交流、母联、各枪直流 13004/13005 防抖块在 s_km_deb[] 中的下标;枪 g 的 13004/13005 为 DC04_BASE+g、DC05_BASE+g */
typedef enum
{
E_FAULT_KM_DEB_AC = 0,
E_FAULT_KM_DEB_BUS = 1,
E_FAULT_KM_DEB_DC04_BASE = 2,
E_FAULT_KM_DEB_DC05_BASE = 2 + GUN_MAX_CNT,
E_FAULT_KM_DEB_COUNT = 2 + 2 * GUN_MAX_CNT
} E_FAULT_KM_DEB_IDX;
static S_FAULT_KM_MISMATCH_DEB s_km_deb[E_FAULT_KM_DEB_COUNT];
/**
* @brief 接触器命令与反馈不一致:NUM_10 秒防抖后置故障或恢复
* @param[in] f_site E_WHOLE_PILE 走 v_set_fault_data;枪位 E_GUN+枪号 走 v_flow_set_fault_data
* @param[in] u8_action_type 拔枪恢复等,与 v_flow_set_fault_data 一致
* @param[in] s16_data1 故障记录 data1(交流/母联为遥控命令,直流枪为命令或正极反馈见调用处)
* @param[in] s16_data2 故障记录 data2(交流/母联为单路反馈;直流枪为负极 KM 遥信)
*/
static void v_fault_km_mismatch_debounce_cheak(S_FAULT_KM_MISMATCH_DEB *p_deb, U8_T u8_mismatch, U8_T u8_slot, E_FAULT_INFO e_info, S16_T s16_data1, S16_T s16_data2, E_FAULT_SITE f_site, U8_T u8_action_type)
{
if (u8_mismatch == 0) /* 本类异常已消失 */
{
if (p_deb->u8_slot == u8_slot) /* 正在累计的正是本类:清零防抖状态 */
{
p_deb->u8_slot = 0;
p_deb->u32_sec0 = 0;
}
if (u8_cheak_fault_flag(e_info, f_site) != 0) /* 列表中仍有本码则恢复 */
{
if (f_site == E_WHOLE_PILE)
v_set_fault_data(f_site, E_RES, e_info, u8_action_type, s16_data1, s16_data2);
else
v_set_fault_data(f_site, E_RES, e_info, u8_action_type, s16_data1, s16_data2);
}
return;
}
/* 本类异常成立:若刚切换异常类型或未开始计时,则重新开始 NUM_10 秒窗口 */
if (p_deb->u8_slot != u8_slot || p_deb->u32_sec0 == 0)
{
p_deb->u8_slot = u8_slot;
p_deb->u32_sec0 = get_current_seconds();
}
if (u32_safe_seconds_since(p_deb->u32_sec0) > NUM_10) /* 防抖时间到 */
{
if (u8_cheak_fault_flag(e_info, f_site) == 0) /* 尚未登记 */
{
if (f_site == E_WHOLE_PILE)
v_set_fault_data(f_site, E_FAULT, e_info, u8_action_type, s16_data1, s16_data2);
else
{
v_set_fault_data(f_site, E_FAULT, e_info, u8_action_type, s16_data1, s16_data2);
}
}
}
}
/**
* @brief 单枪直流输出接触器:e_yk_get_ctrl_cmd(DC_CONT1/2) 与 u16_fault_get_sys_data(KM/流程/时长)
* 13004 空闲粘连、13005 充电中拒动,均经 v_fault_km_mismatch_debounce_cheak
* @param[in] u8_gunNo 枪索引 0..GUN_MAX_CNT-1
*/
static void v_fault_dc_output_km_check_gun(U8_T u8_gunNo)
{
E_FAULT_SITE f_dc_site;
E_CMD e_cmd_dc;
E_YK_CTRL_TYPE e_ky_dc;
U16_T u16_st;
U16_T u16_pos;
U16_T u16_neg;
U16_T u16_char;
if (u8_gunNo >= GUN_MAX_CNT)
return;
f_dc_site = (E_FAULT_SITE)(E_GUN + u8_gunNo);
e_ky_dc = (u8_gunNo == 0u) ? E_KY_TYPE_DC_CONT1 : E_KY_TYPE_DC_CONT2;
e_cmd_dc = e_yk_get_ctrl_cmd(e_ky_dc);
u16_pos = u16_fault_get_sys_data(u8_gunNo, E_FAULT_GET_DATA_GUN_DC_KM_POS, 0);
u16_neg = u16_fault_get_sys_data(u8_gunNo, E_FAULT_GET_DATA_GUN_DC_KM_NEG, 0);
u16_st = u16_fault_get_sys_data(u8_gunNo, E_FAULT_GET_DATA_FLOW_STATE, 0);
u16_char = u16_fault_get_sys_data(u8_gunNo, E_FAULT_GET_DATA_CHAR_TIME, 0);
if (u16_st == (U16_T)E_FLOW_FREE)
{
v_fault_km_mismatch_debounce_cheak(&s_km_deb[E_FAULT_KM_DEB_DC04_BASE + u8_gunNo],
(U8_T)(e_cmd_dc == CMD_OFF && (u16_pos == 1u || u16_neg == 1u)),
2, E_FAULT_13004, (S16_T)u16_pos, (S16_T)u16_neg, f_dc_site, 0);
}
if (u16_st == (U16_T)E_FLOW_CHARGE_RUNING && u16_char > NUM_60)
{
v_fault_km_mismatch_debounce_cheak(&s_km_deb[E_FAULT_KM_DEB_DC05_BASE + u8_gunNo],
(U8_T)(e_cmd_dc == CMD_ON && (u16_pos == 0u || u16_neg == 0u)),
1, E_FAULT_13005, (S16_T)u16_pos, (S16_T)u16_neg, f_dc_site, 1);
}
}
/**
* ************************************************************************
* @brief - 桩故障信息检查
*
*
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
void v_fault_action_cheak()
{
U16_T u16_yx_emergency = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_EMERGENCY, 0); //急停
U16_T u16_yx_door = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_DOOR, 0); //门禁
U16_T u16_yx_warter = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_WARTER, 0); //水浸
U16_T u16_yx_spd = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_SPD, 0); //防雷状态
U16_T u16_yx_smoke = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_SMOKE, 0); //烟感
U16_T u16_yx_door_en = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_DOOR_EN, 0); //门禁使能
U16_T u16_gunTemp_thrd = u16_fault_get_sys_data(WHOLE_PILE,E_FAULT_GET_DATA_GUN_TEMP_THRESHOLD,0); //枪温阈值 0.1度
U16_T u16_ac_km_en = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_AC_CONTACT_EN, 0); //交流接触器检测使能
U16_T u16_bus_km_en = u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_BUS_KM_EN, 0); //母联继电器命令/反馈检测使能(双枪及以上)
U16_T u16_yx_DC_FUSE ; //直流熔断器状态
U16_T u16_gunTemp_plus,u16_gunTemp_minus;
U8_T u8_gunNo;
E_CMD e_cmd_ac;
U8_T u8_ac_km_fb;
E_CMD e_cmd_bus;
U8_T u8_bus_km_fb;
v_fault_yx_action_cheak(u16_yx_emergency ,E_FAULT_13017, E_WHOLE_PILE,0,0); //急停 故障动作检查
if(u16_yx_door_en) //门禁使能 检测
v_fault_yx_action_cheak(u16_yx_door ,E_FAULT_13018, E_WHOLE_PILE,0,0); //门禁 故障动作检查
v_fault_yx_action_cheak(u16_yx_warter ,E_FAULT_13033, E_WHOLE_PILE,0,0); //水浸 故障动作检查
v_fault_yx_action_cheak(u16_yx_spd ,E_FAULT_13001, E_WHOLE_PILE,0,0); //防雷状态 故障动作检查
v_fault_yx_action_cheak(u16_yx_smoke ,E_FAULT_13032, E_WHOLE_PILE,0,0); //烟感 故障动作检查
for (u8_gunNo = 0; u8_gunNo < GUN_MAX_CNT; u8_gunNo++)
{
//直流熔断器状态
u16_yx_DC_FUSE = u16_fault_get_sys_data(u8_gunNo, E_FAULT_GET_DATA_GUN_DC_FUSE, 0); //
v_fault_yx_action_cheak(u16_yx_DC_FUSE ,E_FAULT_13006, (E_FAULT_SITE)(E_GUN + u8_gunNo),0,0); //直流熔断器状态 故障动作检查
//枪温阈值检测
u16_gunTemp_plus = u16_fault_get_sys_data(u8_gunNo ,E_FAULT_GET_DATA_GUN_TEMP ,0 ); //正极枪温值
u16_gunTemp_minus = u16_fault_get_sys_data(u8_gunNo ,E_FAULT_GET_DATA_GUN_TEMP ,1 ); //负极枪温值
u16_gunTemp_plus = (u16_gunTemp_plus > u16_gunTemp_minus)?u16_gunTemp_plus:u16_gunTemp_minus;
v_fault_yx_action_cheak((u16_gunTemp_plus > u16_gunTemp_thrd) ,E_FAULT_13014, (E_FAULT_SITE)(E_GUN + u8_gunNo),u16_gunTemp_plus,u16_gunTemp_thrd); //直流熔断器状态 故障动作检查
v_fault_dc_output_km_check_gun(u8_gunNo); //直流输出接触器 13004/13005(见 static 实现)
}
if (u16_ac_km_en) //交流接触器使能:遥控与反馈比对,防抖后判 13003 拒动 / 13002 粘连;使能 0 不检测、不清计时
{
e_cmd_ac = e_yk_get_ctrl_cmd(E_KY_TYPE_AC1);
u8_ac_km_fb = (U8_T)u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_AC_KM, 0);
v_fault_km_mismatch_debounce_cheak(&s_km_deb[E_FAULT_KM_DEB_AC], (U8_T)(e_cmd_ac == CMD_ON && u8_ac_km_fb == 0), 1, E_FAULT_13003, (S16_T)e_cmd_ac, (S16_T)u8_ac_km_fb, E_WHOLE_PILE, 0);
v_fault_km_mismatch_debounce_cheak(&s_km_deb[E_FAULT_KM_DEB_AC], (U8_T)(e_cmd_ac == CMD_OFF && u8_ac_km_fb == 1), 2, E_FAULT_13002, (S16_T)e_cmd_ac, (S16_T)u8_ac_km_fb, E_WHOLE_PILE, 0);
}
if (u16_bus_km_en) //母联:e_yk_get_ctrl_cmd(E_KY_TYPE_DC_CONT_BUS) 与 YX_PILE_BUS_KM,防抖后判 13023 拒动 / 13024 粘连
{
e_cmd_bus = e_yk_get_ctrl_cmd(E_KY_TYPE_DC_CONT_BUS);
u8_bus_km_fb = (U8_T)u16_fault_get_sys_data(WHOLE_PILE, E_FAULT_GET_DATA_BUS_KM, 0);
v_fault_km_mismatch_debounce_cheak(&s_km_deb[E_FAULT_KM_DEB_BUS], (U8_T)(e_cmd_bus == CMD_ON && u8_bus_km_fb == 0), 1, E_FAULT_13023, (S16_T)e_cmd_bus, (S16_T)u8_bus_km_fb, E_WHOLE_PILE, 0);
v_fault_km_mismatch_debounce_cheak(&s_km_deb[E_FAULT_KM_DEB_BUS], (U8_T)(e_cmd_bus == CMD_OFF && u8_bus_km_fb == 1), 2, E_FAULT_13024, (S16_T)e_cmd_bus, (S16_T)u8_bus_km_fb, E_WHOLE_PILE, 0);
}
}
#endif
#if 1 //拔枪恢复故障检查
/****************************************************
*函数名称: v_fault_res_cheak()
*功能说明: 拔枪故障恢复检查
*参数说明:
* 输入参数:
* 输出参数: void
****************************************************/
void v_fault_res_cheak(U8_T u8_gunNo)
{
U8_T i;
HIS_FAULT_DATA_T fault_data;
Comm_Time curTime;
U32_T u32_now_time;
static U32_T u32_gun_free_time[GUN_MAX_CNT] = {0}; //枪空闲时间-从工作变到空闲的时间 时间戳
U8_T u8_gunLink = u16_fault_get_sys_data(u8_gunNo,E_FAULT_GET_DATA_GUN_LINK,0); //枪连接标志
U8_T u8_gunWork = u16_fault_get_sys_data(u8_gunNo,E_FAULT_GET_DATA_GUN_CHAR_FLAG,0); //充电标志 1充电中 0未充电
//获取当前时间
GetCurrentTime(&curTime);
u32_now_time = xDate2Seconds(&curTime); //获取当前时间 时间戳
//更新工作空闲时间
if(u8_gunWork == 1)
{
u32_gun_free_time[u8_gunNo] = 0; //工作中,时间清零
}
else //非工作 获取当前时间
{
if(u32_gun_free_time[u8_gunNo] == 0) u32_gun_free_time[u8_gunNo] = u32_now_time;
}
//故障数量检查
if(s_real_fault.u8_fault_cnt == 0 &&
s_real_fault.u8_warn_cnt == 0) //不存在故障 则不需要判断故障恢复
return;
//判断拔枪恢复 或 充电完成5分钟后恢复条件是否满足
if(u8_gunLink == 1 && //枪连接 且时间没有到到超时时间,不做恢复判断
(u32_gun_free_time[u8_gunNo] == 0 || (u32_now_time - u32_gun_free_time[u8_gunNo]) < FAULT_GUN_NOLINK_RES_TIME))
return;
//遍历所有故障信息 检查故障恢复内容
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_ingun == (u8_gunNo+1+E_GUN) && //故障归属本枪(故障存储枪号从1000开始,+E_GUN) 且为 拔枪恢复
s_real_fault.fault_data[i].u8_type == 1)
{
memset(&fault_data,0,sizeof(HIS_FAULT_DATA_T));
memcpy(&fault_data.s_occur_time, &curTime, sizeof(Comm_Time)); //发生时间
fault_data.e_site = (E_FAULT_SITE)(u8_gunNo + E_GUN);
fault_data.ActType = E_RES;
fault_data.e_fault_info = s_real_fault.fault_data[i].e_mag;
//v_hisFault_save_record(&fault_data); //保存动作记录
if(s_real_fault.u8_fault_cnt != 0)
s_real_fault.u8_fault_cnt -- ;
memset(&s_real_fault.fault_data[i],0,sizeof(Fault_data));
}
}
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_ingun == (u8_gunNo+1+E_GUN) && s_real_fault.warn_data[i].u8_type == 1) //拔枪恢复
{
memset(&fault_data,0,sizeof(HIS_FAULT_DATA_T));
memcpy(&fault_data.s_occur_time, &curTime, sizeof(Comm_Time)); //发生时间
fault_data.e_site = (E_FAULT_SITE)(u8_gunNo + E_GUN);
fault_data.ActType = E_RES;
fault_data.e_fault_info = s_real_fault.warn_data[i].e_mag;
//v_hisFault_save_record(&fault_data); //保存动作记录
if(s_real_fault.u8_warn_cnt != 0)
s_real_fault.u8_warn_cnt -- ;
memset(&s_real_fault.warn_data[i],0,sizeof(Fault_data));
}
}
}
#endif
#if 1 //故障信息打印
/**
* ************************************************************************
* @brief - 故障信息打印
*
*
*
* @author jiankalka
* @date 2025-09-10
* ************************************************************************
*/
static void v_fault_info_print()
{
U8_T i,mag[FAULT_GET_MAG_MAX_LEN]={0};
char c_site_mag[NUM_30] = {0};
E_FAULT_INFO e_info;
E_FAULT_SITE e_site;
if(s_real_fault.u8_fault_cnt != 0 || s_real_fault.u8_warn_cnt != 0) //存在故障 或者告警
{
FAULTCHECK_LOG("**** Fault=%d, Warn=%d ****\n",
s_real_fault.u8_fault_cnt,s_real_fault.u8_warn_cnt);
for(i=0;i<CURR_FAULT_MAX_CNT;i++)
{
if(s_real_fault.fault_data[i].e_mag != 0)
{
e_info = s_real_fault.fault_data[i].e_mag;
e_site = s_real_fault.fault_data[i].e_ingun;
v_fault_get_site_mag(e_site,c_site_mag,NUM_30);
v_get_fault_info(e_info , (char*)mag,FAULT_GET_MAG_MAX_LEN);
FAULTCHECK_LOG("%s Fault %d %s\n",c_site_mag,e_info,mag);
}
}
for(i=0;i<CURR_ALARM_MAX_CNT;i++)
{
if(s_real_fault.warn_data[i].e_mag != 0)
{
e_info = s_real_fault.warn_data[i].e_mag;
e_site = s_real_fault.warn_data[i].e_ingun;
v_fault_get_site_mag(e_site,c_site_mag,NUM_30);
v_get_fault_info(e_info , (char*)mag,FAULT_GET_MAG_MAX_LEN);
FAULTCHECK_LOG("%s Warn %d %s\n",c_site_mag,e_info,mag);
}
}
FAULTCHECK_LOG("*************************************\n",
s_real_fault.u8_fault_cnt,s_real_fault.u8_warn_cnt);
}
}
#endif
//故障记录相关初始化
void v_fault_data_init(void)
{
/* 统一走 flash_file_mgr 初始化,避免与 BSP 初始化逻辑分叉。 */
#if (FAULT_FLASH_MGR_ENABLE)
v_fault_flash_init();
#endif
//初始化后 增加历史故障数量打印
FAULTCHECK_LOG("history fault count: %d", u16_fault_get_his_count());
}
void v_fault_task(void *argument)
{
uint16_t sleep_time_ms;
uint32_t last_send_time;
uint32_t current_time;
v_fault_data_init();
(void)argument;
(void)FAULTCHECK_LOCK_INIT();
sleep_time_ms = FAULTCHECK_SLEEP_TIME_MS();
last_send_time = get_current_seconds();
U8_T u8_gunNo, u8_gunCnt = u16_fault_get_sys_data(0,E_FAULT_GET_DATA_GUN_CNT,0); //枪数量
for (;;) {
FAULTCHECK_FEED_DOG();
current_time = get_current_seconds();
if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) {
if (FAULTCHECK_LOCK_TAKE() == 1U) {
FAULTCHECK_LOG("faultcheck task is runing, sleep time is %ums", sleep_time_ms);
(void)FAULTCHECK_LOCK_GIVE();
}
last_send_time = current_time;
}
//故障信息打印
if (FUALT_INFO_PRINT_TIME != 0 && u32_safe_seconds_since(last_send_time) >= FUALT_INFO_PRINT_TIME) {
v_fault_info_print(); //故障信息打印
last_send_time = current_time;
}
for (u8_gunNo = 0; u8_gunNo < u8_gunCnt; u8_gunNo++)
{
v_fault_res_cheak(u8_gunNo); //故障拔枪恢复检测
}
v_fault_action_cheak() ; //故障动作检查
// 处理缓存中的故障动作
v_fault_cache_process();
mSleep(sleep_time_ms);
}
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef FAULTCHECK_TASK_H
#define FAULTCHECK_TASK_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "fault_cheak/fault_interface.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
//历史故障结构体
typedef struct
{
E_FAULT_SITE e_site; //故障位置
E_FAULT_INFO e_fault_info; //故障信息
Comm_Time s_occur_time; //发生时间
E_FAULT_TYPE ActType; //动作类型(0:复归;1:告警;2:故障)
S16_T s16_value1; //动作测量值 - 如果有的话
S16_T s16_value2; //动作测量值 - 如果有的话
}HIS_FAULT_DATA_T;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_fault_task(void *argument);
#ifdef __cplusplus
}
#endif
#endif /* FAULTCHECK_TASK_H */
+822
View File
@@ -0,0 +1,822 @@
#include "flowctrl/flow_interface.h"
#include "publicdata/publicdata.h"
#include "flowctrl/flowctrl_task.h"
#include "ui/ui_interface.h"
#include "collect_ctrl/yk_ctrl.h"
#include "collect_ctrl/collect_task.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "bms/bms_interface.h"
#include "fault_cheak/fault_interface.h"
#include "flowctrl/insulation_ctrl.h"
#include "plat_comm/impl/bs_public_impl.h"
extern S_FLOW_DATA s_flow_data;
/**
* ************************************************************************
* @brief - 流程控制任务 获取外部数据接口
*
* @param[in] u8_gunNo - 枪号 0-N 对于不分区枪号的数据,无意义
* @param[in] e_dateType - 数据类型标志 详看枚举定义
* @param[in] u16_dataPara - 数据参数 不同数据类型,参数含义不同 对于不分区参数的数据,无意义
*
* @return U16_T - 数据返回值
*
* @author jiankalka
* @date 2025-09-03
* ************************************************************************
*/
U16_T u16_flow_get_sys_data(U8_T u8_gunNo,E_FLOW_GET_DATA_TYPE e_dateType,U16_T u16_dataPara)
{
U16_T u16_ret = 0;
switch (e_dateType)
{
case E_FLOW_GET_DATA_GUN_CNT: //枪数量
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_gunCnt;
break;
}
case E_FLOW_GET_DATA_GB_EN: //国标使能标志
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_GBEnable;
break;
}
case E_FLOW_GET_DATA_GUN_FAN_EN: //枪风扇使能 0不使能 1使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_gunFanEn;
break;
}
case E_FLOW_GET_DATA_GUNLOCK_EN: //枪锁使能 0不使能 1使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_gunLockEna;
break;
}
case E_FLOW_GET_DATA_AUXPOWER_EN: //辅源状态监测使能 0不使能 1使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_auxpower;
break;
}
case E_FLOW_GET_DATA_MDU_ZERO_POW_CTRL: //零功控制方式
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_mduzeropowEn;
break;
}
case E_FLOW_GET_DATA_FAULT_FLAG: //获取故障内容存在标志 0不存在 1存在;需要传入 故障代码参数
{
u16_ret = u8_flow_cheak_fault_flag((E_FAULT_INFO)u16_dataPara, (E_FAULT_SITE)u8_gunNo);
break;
}
case E_FLOW_GET_DATA_GUN_TOW_CHR: //双枪同充标志 0单枪充电 1双枪充电且为主枪 2双枪充电且为辅枪
{
u16_ret = 0;
break;
}
case E_FLOW_GET_DATA_GUN_LINK: //枪连接状态 0未连接 1已连接
{
u16_ret = u8_get_gun_yx_data(YX_GUN_LINK,u8_gunNo);
break;
}
case E_FLOW_GET_DATA_GUN_LOCK: //枪锁状态 0未锁 1已锁
{
u16_ret = u8_get_gun_yx_data(YX_GUN_LOCK,u8_gunNo);
break;
}
case E_FLOW_GET_DATA_GUN_AUXPOWER: //辅源闭合状态 0未 1已
{
u16_ret = u8_get_gun_yx_data(YX_GUN_AUXPOWER,u8_gunNo);
break;
}
case E_FLOW_GET_DATA_KM_POS: //正母线直流输出接触器状态 0:断开; 1:闭合
{
u16_ret = u8_get_gun_yx_data(YX_GUN_DC_KM_POS,u8_gunNo);
break;
}
case E_FLOW_GET_DATA_KM_NEG: //负母线直流输出接触器状态 0:断开; 1:闭合
{
u16_ret = u8_get_gun_yx_data(YX_GUN_DC_KM_NEG,u8_gunNo);
break;
}
case E_FLOW_GET_DATA_XF_KM: //泄放接触器状态 0:断开; 1:闭合
{
u16_ret = u8_get_gun_yx_data(YX_GUN_RELIF,u8_gunNo);
break;
}
case E_FLOW_GET_DATA_BUS_KM: //母联接触器状态 0:断开; 1:闭合
{
u16_ret = u8_get_sys_yx_data(YX_PILE_BUS_KM);
break;
}
case E_FLOW_GET_DATA_DC_BUS_EN: //直流母联接触器使能 0 不使能 1使能
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_dcBusEn;
break;
}
case E_FLOW_GET_DATA_GUN_BATT_ADC_VOL: //电池外侧采集电压 精度 0.1V
{
u16_ret = (U16_T)(f32_get_gun_yc_data(ADC_GUN_BAT,u8_gunNo)*10);
break;
}
case E_FLOW_GET_DATA_GUN_MAX_VOL: //充电枪最大数据电压值 精度 0.1V
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_max_volt;
break;
}
case E_FLOW_GET_DATA_GUN_MIN_VOL: //充电枪最小数据电压值 精度 0.1V
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_min_volt;
break;
}
case E_FLOW_GET_DATA_GUN_MAX_CUR: //充电枪最大数据电流值 精度 0.1V
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_max_cur;
break;
}
case E_FLOW_GET_DATA_GUN_MIN_CUR: //充电枪最小数据电流值 精度 0.1V
{
u16_ret = 50; //默认5A
break;
}
case E_FLOW_GET_DATA_GUN_SOC_LIMIT: //充电枪SOC阈值
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_soc_limit;
break;
}
case E_FLOW_GET_DATA_GUN_TRICKLE_LIMIT: //充电枪涓流充电停机阈值
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u16_trickle_limit;
break;
}
case E_FLOW_GET_DATA_GUN_BUS_VOL: //充电枪接触器内侧电压值 精度 0.1V
{
u16_ret = (U16_T)((f32_get_gun_yc_data(ADC_GUN_VOLT_PLUS,u8_gunNo) + f32_get_gun_yc_data(ADC_GUN_VOLT_MINUS,u8_gunNo))*10);
//printf("[%d]枪内侧adc %f,%f\n",u8_gunNo,f32_get_gun_yc_data(ADC_GUN_VOLT_PLUS,u8_gunNo) , f32_get_gun_yc_data(ADC_GUN_VOLT_MINUS,u8_gunNo));
break;
}
case E_FLOW_GET_DATA_GUN_FAULT_INFO: //获取整桩和该枪首个故障内容 0无故障 详见E_FAULT_INFO枚举内容 -整桩故障也代表本枪无法使用
{
if(u8_get_fault_sum(0,0) == 0 && u8_get_fault_sum(u8_gunNo+E_GUN,0) == 0) //整桩故障
u16_ret = 0;
else
{
REAL_FAULT_DATA_T rl_data;
v_get_fault_data(&rl_data); //获取故障信息
u16_ret = E_FAULT_13102; //控制器 未知停机故障
for (U8_T i = 0; i < CURR_FAULT_MAX_CNT; i++) //遍历所有故障位置
{
if(rl_data.fault_data[i].e_mag != 0 &&
(rl_data.fault_data[i].e_ingun == E_WHOLE_PILE ||
rl_data.fault_data[i].e_ingun == E_WHOLE_HOST ||
rl_data.fault_data[i].e_ingun == E_GUN+u8_gunNo))
{
u16_ret = rl_data.fault_data[i].e_mag;
}
}
}
break;
}
case E_FLOW_GET_DATA_GUN_POWER_LIMIT: //充电枪的限制功率值 单位kW 0表示不限制
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_gun_power_limit[u8_gunNo];
break;
}
case E_FLOW_GET_DATA_BS_VIN_RES: //VIN 鉴权结果 0wu 1成功 2平台鉴权超时 3非法VIN 4车辆识别鉴权帧超时 5即插即充鉴权失败 6鉴权VIN与车辆VIN不一致
{
u16_ret = u8_bs_get_txFlag_autoRwsult(u8_gunNo);
break;
}
case E_FLOW_GET_DATA_METER_VOL: //电表采集电压 精度0.1V
{
u16_ret = (U16_T)(u64_get_meter_data(u8_gunNo,0,E_METER_DATA_VOL)/1000);
break;
}
case E_FLOW_GET_DATA_METER_CUR: //电表采集电流 精度0.1V
{
u16_ret = (U16_T)(u64_get_meter_data(u8_gunNo,0,E_METER_DATA_CUR)/1000);
break;
}
case E_FLOW_GET_DATA_MDU_RATED_POW: //单模块额定功率 单位kw
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_mdu_rated_pow;
break;
}
case E_FLOW_GET_DATA_MDU_POWER: //模块额定功率 单位kw
{
u16_ret = g_t_share_data.t_sys_fix_cfg.u8_mdu_rated_pow;
break;
}
case E_FLOW_GET_DATA_PILE_TYPE: //获取桩类型 0一体一枪 1一体双枪 2分体一枪 3分体双枪
{
u16_ret = (U16_T)g_t_share_data.t_sys_fix_cfg.u8_pile_type;
break;
}
case E_FLOW_GET_DATA_MDU_WORK: //获取模块运行状态 0空闲 1工作
{
u16_ret = u16_mdu_put_data(u8_gunNo,E_MDU_PUT_DATA_GROUP_WORK_STATE);
break;
}
case E_FLOW_GET_DATA_MDU_PUT_VOL: //获取模块输出电压值 精度0.1V
{
u16_ret = u16_mdu_put_data(u8_gunNo,E_MDU_PUT_DATA_GROUP_putVol);
break;
}
case E_FLOW_GET_DATA_MDU_PUT_CUR: //获取模块输出电流值 精度0.1A
{
u16_ret = u16_mdu_put_data(u8_gunNo,E_MDU_PUT_DATA_GROUP_putCur);
break;
}
case E_FLOW_GET_DATA_MDU_IN_NUM: //获取模块分配数量
{
u16_ret = u16_mdu_put_data(u8_gunNo,E_MDU_PUT_DATA_GROUP_mduNum);
break;
}
case E_FLOW_GET_DATA_MDU_RUN_NUM: //获取模块运行数量
{
u16_ret = u16_mdu_put_data(u8_gunNo,E_MDU_PUT_DATA_GROUP_RUNING_mduNum);
break;
}
case E_FLOW_GET_DATA_BMS_GB_VER: //BMS 国标协议版本 0 2011版 1 2015版
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_GB_VER,0);
break;
}
case E_FLOW_GET_DATA_BMS_STATE: //BMS 获取BMS状态 0无 1握手完成 2握手失败 3开始预充 4开始充电 5禁止充电 6停止充电 7报文接收超时
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_STATE,0);
break;
}
case E_FLOW_GET_DATA_BMS_SOC: //BMS 获取车辆SOC
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_GUN_SOC,0);
break;
}
case E_FLOW_GET_DATA_BMS_MAX_VOL: //BMS 最大充电电压值 精度 0.1V
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_MAX_VOL,0);
break;
}
case E_FLOW_GET_DATA_BMS_MAX_CUR: //BMS 最大充电电流值 精度 0.1V
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_MAX_CUR,0);
break;
}
case E_FLOW_GET_DATA_BMS_REAL_BATT_VOL: //BMS 当前电池电压值 精度 0.1V
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_REAL_BATT_VOL,0);
break;
}
case E_FLOW_GET_DATA_BMS_NEED_VOL: //BMS 需求电压值 精度 0.1V
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_NEED_VOL,0);
break;
}
case E_FLOW_GET_DATA_BMS_NEED_CUR: //BMS 需求电流值 精度 0.1V
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_NEED_CUR,0);
break;
}
case E_FLOW_GET_DATA_BMS_CAN_SEND_CNT: //BMS 报文发送次数 参数值 0代表cem 1代表csd
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_CAN_SEND_CNT,u16_dataPara);
break;
}
case E_FLOW_GET_DATA_BMS_STOP_RES: //BMS 停止原因 详看E_FAULT_INFO枚举内容 BMS部分
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_STOP_RES,0);
break;
}
case E_FLOW_GET_DATA_BMS_RECONN_FLAG: //BMS 重连标志 0不重连 1重连
{
u16_ret = u16_bms_put_data(u8_gunNo,E_BMS_PUT_DATA_BMS_RECONN_FLAG,0);
break;
}
case E_FLOW_GET_DATA_GUN_STOP_REASON_0: //枪停止原因 e_stop_reason[0]
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = (U16_T)s_flow_data.s_real_data[u8_gunNo].e_stop_reason[0];
break;
}
case E_FLOW_GET_DATA_METER_CHARGE: //充电金额 u16_dataPara: 0已充合计低16 1高16 2下周期预估低16 3高16 单位0.0001元
{
U32_T u32v;
if (u8_gunNo >= GUN_MAX_CNT)
break;
if (u16_dataPara == 0 || u16_dataPara == 1)
{
u32v = u32_meterlog_put_data(u8_gunNo, E_METERLOG_PUT_LOG_INFO_USED_MONEY, NULL, 0)
+ u32_meterlog_put_data(u8_gunNo, E_METERLOG_PUT_LOG_INFO_USED_SERVICE, NULL, 0);
u16_ret = (u16_dataPara == 0) ? (U16_T)(u32v & 0xFFFFu) : (U16_T)((u32v >> 16) & 0xFFFFu);
}
else if (u16_dataPara == 2 || u16_dataPara == 3)
{
u32v = u32_meterlog_next_cycle_money(u8_gunNo);
u16_ret = (u16_dataPara == 2) ? (U16_T)(u32v & 0xFFFFu) : (U16_T)((u32v >> 16) & 0xFFFFu);
}
break;
}
case E_FLOW_GET_DATA_FLOW_STATE:
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = (U16_T)s_flow_data.s_real_data[u8_gunNo].e_flow_state;
break;
}
case E_FLOW_GET_DATA_CHAR_TIME:
{
if (u8_gunNo < GUN_MAX_CNT)
u16_ret = s_flow_data.s_log_data[u8_gunNo].u16_char_time;
break;
}
default:
break;
}
return u16_ret;
}
/**
* ************************************************************************
* @brief - 流程控制任务 对外输出数据
*
* @param[in] u8_gunNo - 枪号 0-N 对于不分区枪号的数据,无意义
* @param[in] e_dateType - 数据类型标志 详看枚举定义
* @param[in] u16_dataPara - 数据参数 不同数据类型,参数含义不同 对于不分区参数的数据,无意义
*
* @return U16_T - 数据返回值
*
* @author jiankalka
* @date 2025-09-03
* ************************************************************************
*/
U16_T u16_flow_put_data(U8_T u8_gunNo,E_FLOW_PUT_DATA_TYPE e_dateType,U16_T u16_dataPara)
{
U16_T u16_ret = 0;
switch (e_dateType)
{
case E_FLOW_PUT_DATA_GUN_REAL_STATE : //对外状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
u16_ret = s_flow_data.s_real_data[u8_gunNo].e_flow_real_state;
break;
case E_FLOW_PUT_DATA_GUN_RECONN_FLAG: //输出bms重连开始标志 参数:0获取 1清空
if(u16_dataPara == 0)
u16_ret = s_flow_data.s_real_data[u8_gunNo].u8_bms_renew_ctrl;
else
s_flow_data.s_real_data[u8_gunNo].u8_bms_renew_ctrl = 0;
break;
case E_FLOW_PUT_DATA_GUN_NEED_VOL: //BMS 需求电压值 精度 0.1V
{
u16_ret = s_flow_data.s_real_data[u8_gunNo].u16_need_vol;
break;
}
case E_FLOW_PUT_DATA_GUN_NEED_CUR: //BMS 需求电流值 精度 0.1A
{
u16_ret = s_flow_data.s_real_data[u8_gunNo].u16_need_cur;
break;
}
case E_FLOW_PUT_DATA_GUN_START_TIME: //充电开始时间 时间戳
{
u16_ret = s_flow_data.s_log_data[u8_gunNo].u32_satrt_time;
break;
}
case E_FLOW_PUT_DATA_GUN_STOP_TIME: //充电停止时间 时间戳
{
u16_ret = s_flow_data.s_log_data[u8_gunNo].u32_stop_time;
break;
}
case E_FLOW_PUT_DATA_GUN_STOP_RES: //充电停止原因 详看枚举
{
u16_ret = s_flow_data.s_log_data[u8_gunNo].u_stop_res;
break;
}
case E_FLOW_PUT_DATA_USED_ENERGY: //以充电量,精度 0.0001kWh
{
u16_ret = (U16_T)s_flow_data.s_log_data[u8_gunNo].u32_used_energy;
break;
}
case E_FLOW_PUT_DATA_MDU_STAR_FLAG: //模块任务 开始标志
{
u16_ret = (U16_T)s_flow_data.u8_start_flag;
break;
}
case E_FLOW_PUT_DATA_MDU_YK_CMD: //模块遥控命令 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
{
u16_ret = (U16_T)s_flow_data.s_mdu_allocate_ctrl[u8_gunNo].u8_yk_cmd;
break;
}
case E_FLOW_PUT_DATA_MDU_NEED_VOL: //模块需求电压值 精度0.1
{
u16_ret = (U16_T)s_flow_data.s_mdu_allocate_ctrl[u8_gunNo].u16_need_vol;
break;
}
case E_FLOW_PUT_DATA_MDU_ONE_NEED_CUR: //单模块需求电流值 精度0.1
{
u16_ret = (U16_T)s_flow_data.s_mdu_allocate_ctrl[u8_gunNo].u16_one_mdu_need_cur;
break;
}
default:
break;
}
return u16_ret;
}
/**
* ************************************************************************
* @brief - 流程控制任务 对外输出无符号整型数据
*
* @param[in] u8_gunNo -枪号
* @param[in] e_dateType -数据类型标志 详看枚举定义
*
* @return U32_T -数据返回值
*
* @author YZG
* @date 2025-09-29
* ************************************************************************
*/
U32_T u32_flow_put_data(U8_T u8_gunNo,E_FLOW_PUT_DATA_TYPE e_dateType)
{
U32_T u32_ret = 0;
switch (e_dateType)
{
case E_FLOW_PUT_DATA_USED_ENERGY: //以充电量,精度 0.0001kWh
{
u32_ret = s_flow_data.s_log_data[u8_gunNo].u32_used_energy;
break;
}
case E_FLOW_PUT_DATA_USED_MONEY: //以充电费,精度 0.0001元
{
u32_ret = s_flow_data.s_log_data[u8_gunNo].u32_used_money;
break;
}
case E_FLOW_PUT_DATA_USED_SERV_MONEY: //已充服务费,精度 0.0001元
{
u32_ret = s_flow_data.s_log_data[u8_gunNo].u32_used_service_money;
break;
}
default:
break;
}
return u32_ret;
}
/**
* ************************************************************************
* @brief - 流程控制任务 对外控制 -例如枪锁控制 风扇控制等
*
* @param[in] u8_gunNo - 枪号 0-N 对于不分区枪号的数据,无意义
* @param[in] e_dateType - 数据类型标志 详看枚举定义
* @param[in] u16_dataPara1 -数据参数1 不同数据类型,参数含义不同 对于不分区参数的数据,无意义
* @param[in] u16_dataPara2 -数据参数2 不同数据类型,参数含义不同 对于不分区参数的数据,无意义
*
* @return U16_T - 0 执行成功 1执行失败
*
* @author jiankalka
* @date 2025-09-03
* ************************************************************************
*/
U16_T u16_flow_put_ctrl(U8_T u8_gunNo,E_FLOW_CTRL_DATA_TYPE e_dateType,U16_T u16_dataPara1,U16_T u16_dataPara2)
{
U16_T u16_ret = 0;
(void)u16_dataPara2;
switch (e_dateType)
{
case E_FLOW_CTRL_DATA_GUN_LOCK : //枪锁控制 0解锁 1锁枪
{
if(u8_gunNo == 0)
v_yk_ctrl_impl(E_KY_TYPE_GUN_LOCK1 , (E_CMD) u16_dataPara1);
else
v_yk_ctrl_impl(E_KY_TYPE_GUN_LOCK2 , (E_CMD) u16_dataPara1);
break;
}
case E_FLOW_CTRL_DATA_AUXPOWER : //辅源控制 0断开 1闭合
{
if(u8_gunNo == 0)
v_yk_ctrl_impl(E_KY_TYPE_AUX_POWER1 , (E_CMD) u16_dataPara1);
else
v_yk_ctrl_impl(E_KY_TYPE_AUX_POWER2 , (E_CMD) u16_dataPara1);
break;
}
case E_FLOW_CTRL_DATA_K1K2 : //K1K2控制 0断开 1闭合
{
if(u8_gunNo == 0)
v_yk_ctrl_impl(E_KY_TYPE_DC_CONT1 , (E_CMD) u16_dataPara1);
else
v_yk_ctrl_impl(E_KY_TYPE_DC_CONT2 , (E_CMD) u16_dataPara1);
break;
}
case E_FLOW_CTRL_DATA_AC_YK : //交流接触器控制 0断开 1闭合
{
if(u8_gunNo == 1)
v_yk_ctrl_impl(E_KY_TYPE_AC1 , (E_CMD) u16_dataPara1);
else
v_yk_ctrl_impl(E_KY_TYPE_AC2 , (E_CMD) u16_dataPara1);
break;
}
case E_FLOW_CTRL_DATA_BUS_YK : //母联接触器控制 0断开 1闭合
{
v_yk_ctrl_impl(E_KY_TYPE_DC_CONT_BUS , (E_CMD) u16_dataPara1);
break;
}
case E_FLOW_CTRL_DATA_FAN_YK: //风扇
{
v_yk_ctrl_impl(E_KY_TYPE_FAN_CONT , (E_CMD) u16_dataPara1);
break;
}
default:
break;
}
return u16_ret;
}
/**
* ************************************************************************
* @brief - 获取 故障是否存在在列表中
*
* @param[in] e_info - 告警数据
* @param[in] f_site - 故障位置
*
* @return U8_T - 0 未存在 1存在为故障 2存在为告警
*
* @author jiankalka
* @date 2025-09-09
* ************************************************************************
*/
U8_T u8_flow_cheak_fault_flag(E_FAULT_INFO e_info , E_FAULT_SITE f_site)
{
U8_T u8_ret;
if(f_site > 0)
{
f_site += E_GUN; //故障存储枪地址从100开始
}
u8_ret = u8_cheak_fault_flag(e_info,f_site);
return u8_ret;
}
/**
* ************************************************************************
* @brief - 流程控制任务 触发故障告警
*
* @param[in] f_site - 故障位置 0整桩 1A枪 2B枪
* @param[in] e_type - 动作类型 0回复 1故障 2告警
* @param[in] e_info - 故障名称 详看故障枚举
* @param[in] u8_action_type - 拔枪恢复标志 0拔枪不恢复 1拔枪恢复
* @param[in] s16_data1 - 故障参数1 自定义
* @param[in] s16_data2 - 故障参数2 自定义
*
*
* @author jiankalka
* @date 2025-09-03
* ************************************************************************
*/
void v_flow_set_fault_data(E_FAULT_SITE f_site ,E_FAULT_TYPE e_type , E_FAULT_INFO e_info, U8_T u8_action_type, S16_T s16_data1 , S16_T s16_data2)
{
if(f_site > 0)
{
f_site = (E_FAULT_SITE)(f_site - 1);
f_site += E_GUN; //故障存储枪地址从1000开始
}
v_set_fault_data(f_site,e_type,e_info,u8_action_type,s16_data1,s16_data2);
}
/**
* ************************************************************************
* @brief - 流程控制任务 获取故障代码内容 中文
*
* @param[in] e_info - 故障代码
* @param[in] mag - 故障代码对应中文字符
*
*
* @author jiankalka
* @date 2025-09-03
* ************************************************************************
*/
void v_flow_get_fault_mag(E_FAULT_INFO e_info , char *mag,U16_T u16_len)
{
v_get_fault_info(e_info,mag,u16_len);
}
/**
* ************************************************************************
* @brief - 绝缘监测执行函数
*
* @param[in] u8_gunNo - 枪号
* @param[in] u32_insul_Rx - 绝缘监测电阻值 DC正对地
* @param[in] u32_insul_Ry - 绝缘监测电阻值 DC负对地
*
* @return S8_T - -1 未完成 0失败 1成功 2成功但告警
*
* @author jiankalka
* @date 2025-09-04
* ************************************************************************
*/
S8_T s8_flow_gun_insulation(U8_T u8_gunNo,U32_T *u32_insul_Rx,U32_T *u32_insul_Ry)
{
S8_T s8_ret = -1;
F32_T f32_rz, f32_rf;
s8_ret = s8_insulation_action(u8_gunNo ,&f32_rz, &f32_rf);
*u32_insul_Rx = (U32_T)f32_rz;
*u32_insul_Ry = (U32_T)f32_rf;
// printf("flow 绝缘检查结果= %d %d\n",s8_ret,u16_data );
return s8_ret;
}
/**
* ************************************************************************
* @brief - 绝缘监测资源控制
*
* @param[in] u8_gunNo - 枪号 0-N
* @param[in] u8_ctrlType - 控制类型 0绝缘监测资源初始化-绝缘监测开始前调用 1绝缘监测资源释放-绝缘监测结束后调用
*
*
* @author jiankalka
* @date 2025-09-04
* ************************************************************************
*/
void v_flow_gun_insulation_data_ctrl(U8_T u8_gunNo,U8_T u8_ctrlType)
{
if(u8_ctrlType == 1)
v_insulation_stop( u8_gunNo); //设置绝缘检测停止
// printf("flow 绝缘启停= %d %d\n",u8_gunNo,u8_ctrlType );
}
/**
* ************************************************************************
* @brief - 电压泄放执行
*
* @param[in] u8_gunNo - 枪号 0-N
*
* @return S8_T - 泄放执行结果 -1未完成 0泄放失败 1完成且泄放成功
*
* @author jiankalka
* @date 2025-09-04
* ************************************************************************
*/
S8_T s8_flow_volt_relief_do(U8_T u8_gunNo)
{
S8_T s8_ret = 1;
return s8_ret;
}
/**
* ************************************************************************
* @brief - 泄放控制资源操作
*
* @param[in] u8_gunNo - 枪号 0-N
* @param[in] u8_ctrlType - 控制类型 0泄放控制资源初始化-泄放开始前调用 1泄放控制资源释放-泄放控制结束后调用
*
*
* @author jiankalka
* @date 2025-09-04
* ************************************************************************
*/
void v_flow_gun_volt_relief_free(U8_T u8_gunNo ,U8_T u8_ctrlType)
{
}
/**
* ************************************************************************
* @brief - 设置启停命令
*
* @param[in] u8_gunNo - 抢号 0-N
* @param[in] s_charge_ctrl_info - 充电启停控制信息
*
*
* @author jiankalka
* @date 2025-09-08
* ************************************************************************
*/
void v_flow_start_stop_ctrl(U8_T u8_gunNo , S_FLOW_CHARGE_CTRL_INFO *s_charge_ctrl_info )
{
S_FLOW_CTRL_DATA *s_ctrl_data = &s_flow_data.s_ctrl_data[u8_gunNo];
S_FLOW_REAL_DATA *s_real_data = &s_flow_data.s_real_data[u8_gunNo]; //实时数据指针
if(s_charge_ctrl_info->e_cmd == CMD_OFF) //停止充电
{
s_ctrl_data->e_stop_flag = s_charge_ctrl_info->e_stop_res;
s_ctrl_data->s16_fault_stop_cnt = TIMER_NONE; //默认调用此接口停止充电的操作 都是正常停止(异常停止情况在本任务其他地方判断)
FLOWCTRL_LOG("Stop cmd, reason=%d\n",s_ctrl_data->e_stop_flag);
}
else //开始充电
{
if(s_real_data->e_flow_state != E_FLOW_FREE) //不是空闲 直接停止
{
return;
}
memset(s_ctrl_data, 0 , sizeof(S_FLOW_CTRL_DATA)); //清空控制信息
// 防止 s_over_time 初值为0导致首次判定直接超时
{
U32_T now_s = get_current_seconds();
s_ctrl_data->s_over_time.u32_trickle_limit_time = now_s;
s_ctrl_data->s_over_time.u32_dc_km_time = now_s;
s_ctrl_data->s_over_time.u32_mdu_free_time = now_s;
s_ctrl_data->s_over_time.u32_over_vol_time = now_s;
s_ctrl_data->s_over_time.u32_under_vol_time = now_s;
s_ctrl_data->s_over_time.u32_over_curr_time = now_s;
s_ctrl_data->s_over_time.u32_over_needCur_time = now_s;
s_ctrl_data->s_over_time.u32_over_needCur_max_time = now_s;
s_ctrl_data->s_over_time.u32_over_max_vol_time = now_s;
}
s_ctrl_data->s16_fault_stop_cnt = TIMER_NONE;
s_ctrl_data->e_start_flag = s_charge_ctrl_info->e_start_res; //启动原因
s_ctrl_data->e_sdk_chargMode = s_charge_ctrl_info->e_sdk_chargMode;
s_ctrl_data->u32_limit_data = s_charge_ctrl_info->u32_limit_data;
FLOWCTRL_LOG("Start cmd, reason=%d\n",s_ctrl_data->e_start_flag);
}
}
/**
* ************************************************************************
* @brief - 设置充电订单结算信息
*
* @param[in] u8_gunNo - 抢号0-N
* @param[in] u16_res - 结果
*
*
* @author jiankalka
* @date 2025-09-24
* ************************************************************************
*/
void v_flow_set_stop_log_info(U8_T u8_gunNo , U16_T u16_res)
{
//v_pcu_set_gun_char_flag(u8_gunNo,0 ,u16_res);
v_meterlog_set_stop_char_log_info(u8_gunNo, (E_FAULT_INFO)u16_res);
}
/**
* ************************************************************************
* @brief - 设置平台充电鉴权标志
*
* @param[in] u8_gunNo - 0-N
*
*
* @author jiankalka
* @date 2025-12-04
* ************************************************************************
*/
void v_flow_set_plat_vin_up(U8_T u8_gunNo)
{
v_bs_log_txFlag_autoStart(u8_gunNo); //设置充电鉴权上送标志
}
/**
* ************************************************************************
* @brief -充电中,更新充电订单中的充电电量与充电金额
*
* @param[in] u8_gunNo -充电枪号
*
*
* @author YZG
* @date 2025-09-29
* ************************************************************************
*/
void v_flow_log_info_updata(U8_T u8_gunNo)
{
S_FLOW_LOG_DATA *s_flow_log = &s_flow_data.s_log_data[u8_gunNo];
s_flow_log->u32_used_energy = u32_meterlog_put_data(u8_gunNo,E_METERLOG_PUT_LOG_INFO_USED_ENERGY,NULL,0); //获取充电电量
s_flow_log->u32_used_money = u32_meterlog_put_data(u8_gunNo,E_METERLOG_PUT_LOG_INFO_USED_MONEY,NULL,0);//获取充电金额
s_flow_log->u32_used_service_money = u32_meterlog_put_data(u8_gunNo,E_METERLOG_PUT_LOG_INFO_USED_SERVICE,NULL,0); //充电服务金额
s_flow_log->u16_char_time = u32_meterlog_put_data(u8_gunNo,E_METERLOG_PUT_LOG_INFO_CHAR_TIME,NULL,0); //获取充电时间
}
/**
* ************************************************************************
* @brief -设置充电枪流程控制状态初始化
*
* @param[in] u8_gunNo -
*
*
* @author jiankalka
* @date 2026-03-03
* ************************************************************************
*/
void v_flow_set_gun_flow_real_state_init(U8_T u8_gunNo)
{
s_flow_data.s_real_data[u8_gunNo].e_flow_real_state = E_FLOW_REAL_FREE;
}
void v_flow_request_ui_idle_recover(void)
{
v_ui_request_idle_screen_recover();
}
+177
View File
@@ -0,0 +1,177 @@
#ifndef FLOW_INTERFACE_H
#define FLOW_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "fault_cheak/fault_interface.h"
#include "meter_calculate/meter_calculate_impl.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
typedef enum //充电流程控制对外状态
{
E_FLOW_REAL_FREE=0 , //空闲
E_FLOW_REAL_CHARGE_START , //充电启动
E_FLOW_REAL_INSUL_START , //绝缘启动
E_FLOW_REAL_INSUL_OK , //绝缘计算完成
E_FLOW_REAL_INSUL_MDU_STOP , //绝缘检测模块停止
E_FLOW_REAL_PRE_START , //开始预充
E_FLOW_REAL_PRE_OK , //预充完成
E_FLOW_REAL_CHARGEING , //充电中
E_FLOW_REAL_BMS_OUTTIME , //BMS通讯超时
E_FLOW_REAL_STOP , //充电停止
E_FLOW_REAL_STOP_OK , //充电停止完成
}E_FLOW_REAL_STATE;
typedef enum
{
E_FLOW_GET_DATA_GUN_CNT = 0, //枪数量
E_FLOW_GET_DATA_GB_EN, //国标使能标志 0不使能 1使用
E_FLOW_GET_DATA_GUN_FAN_EN, //枪风扇使能 0不使能 1使能
E_FLOW_GET_DATA_GUNLOCK_EN, //枪锁使能 0不使能 1使能
E_FLOW_GET_DATA_AUXPOWER_EN, //辅源状态监测使能 0不使能 1使能
E_FLOW_GET_DATA_FAULT_FLAG, //获取故障内容存在标志 0不存在 1存在;需要传入 故障代码参数
E_FLOW_GET_DATA_GUN_TOW_CHR, //双枪同充标志 0单枪充电 1双枪充电且为主枪 2双枪充电且为辅枪
E_FLOW_GET_DATA_GUN_LINK, //枪连接状态 0未连接 1已连接
E_FLOW_GET_DATA_GUN_LOCK, //枪锁状态 0未锁 1已锁
E_FLOW_GET_DATA_GUN_AUXPOWER, //辅源闭合状态 0未 1已
E_FLOW_GET_DATA_KM_POS, //正母线直流输出接触器状态 0:断开; 1:闭合
E_FLOW_GET_DATA_KM_NEG, //负母线直流输出接触器状态 0:断开; 1:闭合
E_FLOW_GET_DATA_XF_KM, //泄放接触器状态 0:断开; 1:闭合
E_FLOW_GET_DATA_BUS_KM, //母联接触器状态 0:断开; 1:闭合
E_FLOW_GET_DATA_DC_BUS_EN, //直流母联接触器使能 0 不使能 1使能
E_FLOW_GET_DATA_GUN_BATT_ADC_VOL, //电池外侧采集电压 精度 0.1V
E_FLOW_GET_DATA_GUN_MAX_VOL, //充电枪最大数据电压值 精度 0.1V
E_FLOW_GET_DATA_GUN_MIN_VOL, //充电枪最小数据电压值 精度 0.1V
E_FLOW_GET_DATA_GUN_MAX_CUR, //充电枪最大数据电流值 精度 0.1A
E_FLOW_GET_DATA_GUN_MIN_CUR, //充电枪最小数据电流值 精度 0.1A
E_FLOW_GET_DATA_GUN_SOC_LIMIT, //充电枪SOC阈值
E_FLOW_GET_DATA_GUN_TRICKLE_LIMIT, //充电枪涓流充电停机阈值
E_FLOW_GET_DATA_GUN_BUS_VOL, //充电枪接触器内侧电压值 精度 0.1V
E_FLOW_GET_DATA_GUN_FAULT_INFO, //获取整桩和该枪首个故障内容 0无故障 详见E_FAULT_INFO枚举内容 -整桩故障也代表本枪无法使用
E_FLOW_GET_DATA_GUN_POWER_LIMIT, //充电枪的限制功率值 单位kW 0表示不限制
E_FLOW_GET_DATA_METER_VOL, //电表采集电压 精度0.1V
E_FLOW_GET_DATA_METER_CUR, //电表采集电流 精度0.1V
E_FLOW_GET_DATA_BS_VIN_RES, //VIN 鉴权结果 0wu 1成功 2平台鉴权超时 3非法VIN 4车辆识别鉴权帧超时 5即插即充鉴权失败 6鉴权VIN与车辆VIN不一致
E_FLOW_GET_DATA_MDU_RATED_POW, //单模块额定功率 单位kw
E_FLOW_GET_DATA_MDU_POWER, //模块额定功率 单位kw(与 MDU_RATED_POW 同源)
E_FLOW_GET_DATA_MDU_ZERO_POW_CTRL, //零功控制方式 0普通控制 1脉冲控制
E_FLOW_GET_DATA_PILE_TYPE, //获取桩类型 0一体一枪 1一体双枪 2分体一枪 3分体双枪
E_FLOW_GET_DATA_MDU_WORK, //获取模块运行状态 0空闲 1工作
E_FLOW_GET_DATA_MDU_PUT_VOL, //获取模块输出电压值 精度0.1V
E_FLOW_GET_DATA_MDU_PUT_CUR, //获取模块输出电流值 精度0.1A
E_FLOW_GET_DATA_MDU_IN_NUM, //获取模块分配数量
E_FLOW_GET_DATA_MDU_RUN_NUM, //获取模块运行数量
E_FLOW_GET_DATA_BMS_GB_VER, //BMS 国标协议版本 0 2011版 1 2015版
E_FLOW_GET_DATA_BMS_STATE, //BMS 获取BMS状态 0无 1握手完成 2握手失败 3开始预充 4开始充电 5禁止充电 6停止充电 7报文接收超时
E_FLOW_GET_DATA_BMS_SOC, //BMS 获取车辆SOC
E_FLOW_GET_DATA_BMS_MAX_VOL, //BMS 最大充电电压值 精度 0.1V
E_FLOW_GET_DATA_BMS_MAX_CUR, //BMS 最大充电电流值 精度 0.1A
E_FLOW_GET_DATA_BMS_REAL_BATT_VOL, //BMS 当前电池电压值 精度 0.1V
E_FLOW_GET_DATA_BMS_NEED_VOL, //BMS 需求电压值 精度 0.1V
E_FLOW_GET_DATA_BMS_NEED_CUR, //BMS 需求电流值 精度 0.1A
E_FLOW_GET_DATA_BMS_CAN_SEND_CNT, //BMS 报文发送次数 参数值 0代表cem 1代表csd
E_FLOW_GET_DATA_BMS_STOP_RES, //BMS 停止原因 详看E_FAULT_INFO枚举内容 BMS部分
E_FLOW_GET_DATA_BMS_RECONN_FLAG, //BMS 重连标志 0不重连 1重连
/** 充电金额(计量订单),u16_dataPara:0已充(电费+服务费)低16位 1同左高16位 2下周期预估低16位 3同左高16位;单位0.0001元;内部 u32_meterlog_put_data / u32_meterlog_next_cycle_money */
E_FLOW_GET_DATA_METER_CHARGE,
E_FLOW_GET_DATA_FLOW_STATE, //本枪流程状态 E_FLOW_STATE(与 s_real_data.e_flow_state 一致)
E_FLOW_GET_DATA_GUN_STOP_REASON_0, //枪停止原因 e_stop_reason[0]
E_FLOW_GET_DATA_CHAR_TIME, //本枪充电持续时间 秒(s_log_data.u16_char_time
}E_FLOW_GET_DATA_TYPE;
typedef enum
{
E_FLOW_PUT_DATA_GUN_REAL_STATE =0, //对外状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
E_FLOW_PUT_DATA_GUN_RECONN_FLAG, //输出bms重连开始标志 参数:0获取 1清空
E_FLOW_PUT_DATA_GUN_NEED_VOL, //BMS 需求电压值 精度 0.1V
E_FLOW_PUT_DATA_GUN_NEED_CUR, //BMS 需求电流值 精度 0.1A
E_FLOW_PUT_DATA_GUN_START_TIME, //充电开始时间 时间戳
E_FLOW_PUT_DATA_GUN_STOP_TIME, //充电停止时间 时间戳
E_FLOW_PUT_DATA_GUN_STOP_RES, //充电停止原因 详看枚举
E_FLOW_PUT_DATA_USED_ENERGY, //以充电量,精度0.0001kWh
E_FLOW_PUT_DATA_USED_MONEY, //以充金额,精度0.0001元
E_FLOW_PUT_DATA_USED_SERV_MONEY, //以充服务金额,精度0.0001元
E_FLOW_PUT_DATA_MDU_STAR_FLAG, //模块运行开始标志 0未开始 1开始
E_FLOW_PUT_DATA_MDU_YK_CMD, //模块遥控命令 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
E_FLOW_PUT_DATA_MDU_NEED_VOL, //模块需求电压值 精度0.1
E_FLOW_PUT_DATA_MDU_ONE_NEED_CUR, //单模块需求电流值 精度0.1
}E_FLOW_PUT_DATA_TYPE;
typedef enum
{
E_FLOW_CTRL_DATA_GUN_LOCK =0, //枪锁控制 0解锁 1锁枪
E_FLOW_CTRL_DATA_AUXPOWER , //辅源控制 0断开 1闭合
E_FLOW_CTRL_DATA_K1K2 , //K1K2控制 0断开 1闭合
E_FLOW_CTRL_DATA_AC_YK , //交流接触器控制 0断开 1闭合
E_FLOW_CTRL_DATA_BUS_YK , //母联接触器控制 0断开 1闭合
E_FLOW_CTRL_DATA_FAN_YK, //风扇
E_FLOW_CTRL_DATA_GUN_FAN_YK, //枪风扇
}E_FLOW_CTRL_DATA_TYPE;
typedef enum //充电模式
{
E_SDK_CHGMOD_NONE = 0, //不做限制的充电(默认)
E_SDK_CHGMOD_MONEY, //限制金额
E_SDK_CHGMOD_ELE, //限制电量 electricity
E_SDK_CHGMOD_SOC, //限制SOC
E_SDK_CHGMOD_TIME, //限制充电时长
E_SDK_CHGMOD_PWOER, //限制功率
}E_CHRG_LIMIT_MOD;
typedef struct
{
E_CMD e_cmd; //充电启停标志 CMD_ON 启动 CMD_OFF停止
START_REASON e_start_res; //启动原因
E_FAULT_INFO e_stop_res; //停止原因
E_CHRG_LIMIT_MOD e_sdk_chargMode; //充电限制方式
U32_T u32_limit_data; //限制参数:金额模式为0.01元;电量0.1kWh等见流程使用处
}S_FLOW_CHARGE_CTRL_INFO;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
U16_T u16_flow_get_sys_data(U8_T u8_gunNo,E_FLOW_GET_DATA_TYPE e_dateType,U16_T u16_dataPara);
U16_T u16_flow_put_data(U8_T u8_gunNo,E_FLOW_PUT_DATA_TYPE e_dateType,U16_T u16_dataPara);
U16_T u16_flow_put_ctrl(U8_T u8_gunNo,E_FLOW_CTRL_DATA_TYPE e_dateType,U16_T u16_dataPara1,U16_T u16_dataPara2);
U32_T u32_flow_put_data(U8_T u8_gunNo,E_FLOW_PUT_DATA_TYPE e_dateType);
void v_flow_set_fault_data(E_FAULT_SITE f_site ,E_FAULT_TYPE e_type , E_FAULT_INFO e_info, U8_T u8_action_type, S16_T s16_data1 , S16_T s16_data2);
U8_T u8_flow_cheak_fault_flag(E_FAULT_INFO e_info , E_FAULT_SITE f_site);
void v_flow_get_fault_mag(E_FAULT_INFO e_info , char *mag,U16_T u16_len);
S8_T s8_flow_gun_insulation(U8_T u8_gunNo,U32_T *u32_insul_Rx,U32_T *u32_insul_Ry);
void v_flow_gun_insulation_data_ctrl(U8_T u8_gunNo,U8_T u8_ctrlType);
S8_T s8_flow_volt_relief_do(U8_T u8_gunNo);
void v_flow_gun_volt_relief_free(U8_T u8_gunNo ,U8_T u8_ctrlType);
void v_flow_set_stop_log_info(U8_T u8_gunNo , U16_T u16_res);
void v_flow_start_stop_ctrl(U8_T u8_gunNo , S_FLOW_CHARGE_CTRL_INFO *s_charge_ctrl_info );
void v_flow_set_plat_vin_up(U8_T u8_gunNo);
void v_flow_log_info_updata(U8_T u8_gunNo);
void v_flow_set_gun_flow_real_state_init(U8_T u8_gunNo);
/** 空闲态在线鉴权失败/超时等:请求界面回到待机(封装 UI 接口,供流程任务调用) */
void v_flow_request_ui_idle_recover(void);
#ifdef __cplusplus
}
#endif
#endif /* FLOW_INTERFACE_H */
File diff suppressed because it is too large Load Diff
+193
View File
@@ -0,0 +1,193 @@
#ifndef FLOWCTRL_TASK_H
#define FLOWCTRL_TASK_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
#include "main.h"
#include "flowctrl/flow_interface.h"
#include "flowctrl/mdu_swh_ctrl.h"
#include "mylog/mylog.h"
#define FLOWCTRL_LOG(fmt, ...) MYLOG_MSG(TASK_ID_FlowCtrl, (fmt), ##__VA_ARGS__)
#define TIMER_NONE -1 //正常动作
#define TIMER_SPECIAL_2 -2 //状态未恢复 依旧响应结束充电命令
#define TIMER_0ms NUM_0 //计时0ms
#define TIMER_20ms NUM_20 //计时20ms
#define TIMER_100ms NUM_100 //计时100ms
#define TIMER_1s NUM_1000 //计时1s
#define TIMER_10s (NUM_1000 * NUM_10) //计时30s
#define TIMER_30s (NUM_1000 * NUM_30) //计时30s
#define FLOW_TIMEOUT_GUNLOCK 15 //枪锁辅源检查超时时间5秒
#define FLOW_TIMEOUT_PCM_STATE 15 //从发送01快速启机/02停止充电,PCM状态切换
#define FLOW_TIMEOUT_VOLT 10 //接触器内测电压在绝缘检测电压/电池电压(设置电压值)-(1~10V)范围内
#define FLOW_TIMEOUT_INSULATION 30 //绝缘检测30s 27930
#define FLOW_TIMEOUT_PRECHARGE (60+5) //预充60s 自发送BRO-0xAA起60未收到CRO-0xAA 27930 模块输出电压调整15s
#define FLOW_TIMEOUT_CHANGE_STOPTIME (60*10+1) //充电暂停 10分钟 测试快了500ms 加一
/*------ Exported struct(结构体定义) --------------------*/
//充电详细过程枚举
typedef enum
{
e_free = 0 , // 0 正常
e_gunLink , // 1 枪连接检查
e_gunlock , // 2 枪锁动作
e_auxpower , // 3 辅源动作
e_wiat_bms_hand , // 4 获取bms握手完成标志
e_check_bms_vol , // 5 检查bms最大允许充电电压
e_battvol_check , // 6 绝缘前电池测电压检查
e_insulmdu_check, // 7 绝缘检测模块启动
e_insulvol_check, // 8 绝缘检测电压达标
e_insul_ing_ok , // 9 绝缘检测完成
e_insul_mdu_stop, // 10 绝缘检测完成
e_insul_relief , // 11 绝缘泄放
e_wiat_bms_pre , // 12 等待bms预充电状态
e_wiat_batt_vol , // 13 检查电压测电压与标准是否符合
e_wiat_pre_mdu , // 14 等待预充模块启动
e_wiat_pre_vol , // 15 等待预充电压达标
e_wiat_bms_change, // 16 等待bms开始进入充电
e_change, // 17 进入充电中
e_change_suspend, // 18 充电暂停
}CHARGE_STATE;
typedef enum //充电流程控制状态
{
E_FLOW_FREE = 0, //空闲
E_FLOW_START_CHECK, //启动前检查
E_FLOW_START_CHECK_OK, //启动前检查完成
E_FLOW_INSUL_BEFORE, //绝缘检查前准备
E_FLOW_INSUL_RUNING, //绝缘检测中
E_FLOW_INSUL_OK, //绝缘检测完成
E_FLOW_WAIT_PRE_CHARGE, //等待预充
E_FLOW_PRE_CHARGE_BEFORE, //预充前检查
E_FLOW_PRE_CHARGE_RUNING, //预充电中
E_FLOW_PRE_CHARGE_OK, //预充完成
E_FLOW_CHARGE_RUNING, //充电中
E_FLOW_CHARGE_SUSPEND, //充电暂停
E_FLOW_BMS_TIMEOUT, //BMS通讯超时
E_FLOW_STOP_CHARGE, //停止充电
E_FLOW_STOP_CHARGE_OK, //停止充电完成
}E_FLOW_STATE;
typedef struct //flow实时运行变量
{
E_FLOW_STATE e_flow_state; //流程控制状态 0x0D
E_FLOW_REAL_STATE e_flow_real_state; //流程控制对外状态 0x09
CHARGE_STATE e_charge_state; //充电详细步骤
START_REASON e_start_reason; //启动充电原因
E_FAULT_INFO e_stop_reason[5]; //停止原因
U8_T u8_bms_renew_cnt; //bms重连次数
U8_T u8_bms_renew_ctrl; //BMS重连控制标志
U8_T log_stop_flag; //订单停止标志
U32_T ulong_flow_timer; //状态超时计时
U32_T ulong_mdu_stop_timer; //状态超时计时
U16_T u16_anti_shake_cnt; //防抖计数
U16_T u16_anti_shake_cnt2; //防抖计数
//bms状态标志位
U8_T u8_bms_chm_flag; //bms握手辨识标志 0无 1成功 2失败
U8_T u8_bms_pre_flag; //bms开始预充标志 0无 1成功
U8_T u8_bms_charge_flag; //bms进入充电标志 0无 1成功
U8_T u8_bms_suspend_flag; //bms禁止充电标志 0无 1成功
U8_T u8_bms_stop_flag; //bms停止充电标志 0无 1成功
U8_T u8_insul_ret; //绝缘检测结果
U32_T u32_insul_Rx; //绝缘检测正对地电阻 单位 0.1欧姆
U32_T u32_insul_Ry; //绝缘检测负对地电阻 单位 0.1欧姆
U16_T u16_need_vol; //需求电压 精度0.1V
U16_T u16_need_cur; //需求电流 精度0.1A
U16_T u16_real_put_vol; //实际输出电压
U16_T u16_real_put_cur; //实际输出电流
}S_FLOW_REAL_DATA;
typedef struct //故障超时判定起始时间
{
U32_T u32_trickle_limit_time; //涓流停机超时起始时间
U32_T u32_dc_km_time; //直流输出接触器超时起始时间
U32_T u32_mdu_free_time; //充电模块空闲超时起始时间
U32_T u32_over_vol_time; //直流输出过压超时起始时间
U32_T u32_under_vol_time; //直流输出欠压超时起始时间
U32_T u32_over_curr_time; //直流输出欠压超时起始时间
U32_T u32_over_needCur_time; //直流输出大于需求超时起始时间
U32_T u32_over_needCur_max_time; //输出大于最高允许超时起始时间
U32_T u32_over_max_vol_time; //直流输出电压超过最高允许超时起始时间
}S_FLOW_OVER_TIME;
typedef struct
{
U8_T u8_twoGun_flag; //双枪同充标志 0不是单枪充电 1双枪同充且是主枪 2双枪同充且是辅助枪
U8_T u8_gunlock_cnt; //枪锁动作次数
START_REASON e_start_flag; //充电启停标志 0不启动 1正常启动 2即插即充 0x05
E_FAULT_INFO e_stop_flag; //外部停止充电标志
S16_T s16_fault_stop_cnt; //异常停止计数 -1 正常动作 0 直接都做 +N需要延时
E_CHRG_LIMIT_MOD e_sdk_chargMode; //充电限制方式 0 不做限制的充电(默认) 1 限制金额 2 限制电量 electricity 3 限制SOC 4 限制充电时长 5 限制功率
S_FLOW_OVER_TIME s_over_time; //故障超时判定起始时间
U32_T u32_limit_data; //限制参数 限制金额默认精度 0.01元 限制电量默认精度0.1kWh
}S_FLOW_CTRL_DATA;
typedef struct
{
U32_T u32_satrt_time; //启动充电时间 时间戳
U32_T u32_stop_time; //停止充电时间 时间戳
U16_T u16_char_time; //充电时间 单位秒
U32_T u32_used_energy; //己用电量,单位是0.0001KWh
U32_T u32_used_money; //充电金额,,单位是0.0001元
U32_T u32_used_service_money; //充电服务,单位是0.0001元
E_FAULT_INFO u_stop_res; //停止充电原因
}S_FLOW_LOG_DATA;
typedef enum
{
E_PULSE_STATE_IDLE = 0, //脉冲空闲
E_PULSE_STATE_ON, //脉冲闭合阶段
E_PULSE_STATE_OFF, //脉冲断开阶段
E_PULSE_STATE_STOP, //脉冲完成
} E_PULSE_STATE;
typedef struct //流程任务变量结构体定义
{
U8_T u8_gunCnt; //充电枪数量
U8_T u8_gb_en; //国标使能标志 0不使能 1使能
U8_T u8_gun_type[GUN_MAX_CNT]; //枪类型
U8_T u8_gunlock_num; //枪锁解锁最大次数
U8_T u8_relief_time; //泄放时间 单位 秒
U8_T u8_not_enable_flag; //不允许充电标志
U16_T u16_timer_cnt; //计时计数
U16_T u16_insul_before_vol; //绝缘前电压判断值 精度 0.1V
U16_T u16_insul_putvol_range; //绝缘检测输出电压误差值 精度 0.1V
U8_T u8_start_flag; //模块任务运行标志 0停止 1运行
U8_T u8_pulse_target_ac; //脉冲目标AC 1=AC1 2=AC2
U8_T u8_pulse_cycle_count; //脉冲周期计数
E_PULSE_STATE e_pulse_state; //脉冲控制状态
U64_T u64_pulse_timer; //脉冲计时器 ms
S_FLOW_CTRL_DATA s_ctrl_data[GUN_MAX_CNT]; //控制命令
S_FLOW_REAL_DATA s_real_data[GUN_MAX_CNT]; //充电实时信息 空闲时清零
S_FLOW_LOG_DATA s_log_data[GUN_MAX_CNT]; //充电订单信息 -- 用于显示
S_MDU_ALLOCATE_CTRL s_mdu_allocate_ctrl[GUN_MAX_CNT];//模块控制信息
}S_FLOW_DATA;
void v_flowctrl_task(void *arg);
U8_T u8_flowctrl_get_gun_num(void);
void v_flow_set_stop_charge(U8_T u8_gunNo , E_FAULT_INFO e_stop_reason , S16_T s16_fault_stop_cnt);
#endif
+86
View File
@@ -0,0 +1,86 @@
# flowctrl 模块说明
> 路径:`app/flowctrl/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 实现充电流程控制主逻辑与状态机;
- 提供流程对外数据接口与控制接口;
- 处理流程关键保护与异常停机条件。
## 2. 关键文件
- `flowctrl_task.c/h`
- `flow_interface.c/h`
- `insulation_ctrl.c/h`
- `mdu_swh_ctrl.c/h`
## 3. 功能说明
- 管理充电开始、运行、停止、故障等流程状态切换;
- 对接故障模块、BMS、计量模块及外部控制命令;
- 对外提供流程运行数据和控制入口,供 UI/TCP 等模块调用。
## 4. 调度与对外接口
该模块由 FreeRTOS 任务 `FlowCtrl` 周期运行(单位:ms),周期来自 `app/app_init/app_init.c`
- `FlowCtrl``20ms`(任务入口:`v_flowctrl_task(void *arg)`
对外接口(来自 `flowctrl_task.h`):
- `void v_flowctrl_task_init(void)`:初始化流程状态机/内部缓存
- `void v_flowctrl_task(void *arg)`:流程控制任务入口
- `U8_T u8_flowctrl_get_gun_num(void)`:返回枪数量(供上层界面/通信使用)
## 5. 维护建议
- 流程状态跳转新增分支时需同步检查故障与恢复路径;
- 对外接口字段建议统一经 `flow_interface` 暴露,减少模块耦合。
## 6. 绝缘检查步骤(详细)
绝缘检查主流程由 `flowctrl_task.c` 状态机调度,核心计算在 `insulation_ctrl.c`
- 状态入口:`E_FLOW_INSUL_BEFORE``v_flow_ctrl_insul_before`
- 条件:模块进入工作态,且绝缘电压到位(或欧标 15s 跳过电压到位检查);
- 动作:切到 `E_FLOW_INSUL_RUNING`,调用 `v_flow_gun_insulation_data_ctrl(gun,0)` 开始绝缘检测资源控制;
- 典型日志:`INS V ok...``15s skip V chk...`
- 检测执行:`E_FLOW_INSUL_RUNING``v_flow_ctrl_insul_runing`
- 调用:`s8_flow_gun_insulation()` -> `s8_insulation_action()` -> `v_insulation_Calculate()`
- 返回:`-1` 检测中,`0` 失败,`1` 正常,`2` 告警;
- 完成后记录 `u32_insul_Rx/Ry``u8_insul_ret`,并切到 `E_FLOW_INSUL_OK`
- 典型日志:`INS res=%d(0F,1OK,2WARN), R+=..., R-=...`
- 结果收尾:`E_FLOW_INSUL_OK``v_flow_ctrl_insul_ok`
- 先等待模块停机/母线下降并执行泄放;
- 再根据 `u8_insul_ret` 判定:
- `0`:失败,停机故障 `E_FAULT_13042`(绝缘检测失败);
- `2`:告警,置 `E_FAULT_13043`(绝缘告警)后允许继续;
- `1`:通过,进入后续预充/充电流程。
`insulation_ctrl.c` 内部两阶段计算要点:
- 阶段1(大地继电器闭合后):
- 采样 `U+gnd``U-gnd`
- 计算单侧绝缘电阻粗判,若小于阈值直接失败(结果码2);
- 阈值规则:`R_min = 100 * (U+gnd + U-gnd)``R_max = 500 * (U+gnd + U-gnd)`
- 阶段2(光耦继电器闭合后):
- 二次采样并按公式计算 `R+gnd``R-gnd`
- 判定规则:
- 任一 `< R_min` => 失败(结果码3);
- 任一 `<= R_max` => 告警(结果码4);
- 否则 => 正常(结果码5)。
调试建议(结合日志):
- 先看 `Th: min/max` 是否与当时 `U+gnd + U-gnd` 一致;
- 再看 `R+gnd/R-gnd` 是否显著低于 `R_min`(会触发 `13042`);
- 若经常走 `15s skip V chk`,优先排查模块电压到位与采样链路。
## 7. 关联文档
- 主索引:[README](../../README.md)
+325
View File
@@ -0,0 +1,325 @@
#include "flowctrl/insulation_ctrl.h"
#include "flowctrl/flowctrl_task.h"
#include "collect_ctrl/collect_task.h"
#include <float.h>
typedef struct
{
U32_T u32_ins_timer; //绝缘开始秒数存储,用于与当前时间比较
F32_T f32_ADC_VOL1[2]; //控制大地闭合后,ADC_VOL1[0]正对地电压,ADC_VOL1[1]负对地电压
F32_T f32_R_min; //绝缘检测失败阈值(100欧/伏)
F32_T f32_R_max; //绝缘检测告警阈值(500欧/伏)
F32_T f32_R_m; //控制大地闭合后,最小电阻值;
F32_T f32_ADC_VOL2[2]; //控制绝缘检测光电继电器闭合后,ADC_VOL2[0]正对地电压,ADC_VOL2[1]负对地电压
F32_T f32_ins_RZ; //控制绝缘检测光电继电器闭合后,正对地电阻
F32_T f32_ins_RF; //控制绝缘检测光电继电器闭合后,负对地电阻
F64_T f64_V1;
F64_T f64_V2;
U8_T u8_step; //绝缘步骤控制
}INS_EXTE_DATA; //绝缘检测相关数据
INS_EXTE_DATA s_ins_ctrl[GUN_MAX_CNT];
static void v_insulation_Calculate(U8_T gunNo,U8_T *u8_result,F32_T *f32_rz, F32_T *f32_rf) ;
#if 1 //外部数据获取
/**
* ************************************************************************
* @brief - 获取直流母线adc采样电压值
*
* @param[in] u8_gunNo - 枪号 0-1
* @param[in] u8_type - 数据类型 1直流母线正对地电压 0直流母线负对地电压
*
* @return F32_T -
*
* @author jiankalka
* @date 2025-11-14
* ************************************************************************
*/
static F32_T f32_insl_get_adc_vol(U8_T u8_gunNo, U8_T u8_type )
{
F32_T f32_ret;
f32_ret = u8_type?f32_get_gun_yc_data(ADC_GUN_VOLT_PLUS,u8_gunNo):f32_get_gun_yc_data(ADC_GUN_VOLT_MINUS,u8_gunNo);
return f32_ret;
}
#endif
#if 1 //对外提供控制接口
/*
函数功能:绝缘控制终止 断开大地与光电继电器
输入:U8_T gunNo 充电枪号
*/
void v_insulation_stop(U8_T gunNo)
{
INS_EXTE_DATA *ins_data = NULL;
if (gunNo >= GUN_MAX_CNT) return;
ins_data = &(s_ins_ctrl[gunNo]);
switch(gunNo) //大地、光电继电器控分
{
case 0:
GUN1_INS_CTL(0);
GUN1_UBAL_CTL(0);
break;
case 1:
GUN2_INS_CTL(0);
GUN2_UBAL_CTL(0);
break;
}
ins_data->u8_step = 0; //绝缘检测终止,step置0
ins_data->u32_ins_timer = 0; //绝缘检测终止,u32_insu_timer置0
}
/**
* ************************************************************************
* @brief - 绝缘检查动作执行
*
* @param[in] gunNo - 枪号 0-1
* @param[in] f32_rz - 正对地电阻
* @param[in] f32_rf - 负对地电阻
*
* @return S8_T - -1 未完成 0失败 1成功 2成功但告警
*
* @author jiankalka
* @date 2025-11-14
* ************************************************************************
*/
S8_T s8_insulation_action(U8_T gunNo ,F32_T *f32_rz, F32_T *f32_rf)
{
S8_T s8_ret = -1;
U8_T u8_result = 1; //1:绝缘检测中; 2:大地闭合阶段绝缘检测失败; 3:光电继电器闭合阶段绝缘检测失败; 4:绝缘检测告警,但可以正常充电; 5:绝缘检测正常
v_insulation_Calculate(gunNo,&u8_result,f32_rz,f32_rf); //绝缘监测执行
//FLOWCTRL_LOG("insulation res = %d\n",u8_result);
if(u8_result == 1) //绝缘检测中;
{
s8_ret = -1;
}
else if(u8_result == 5) //绝缘监测正常
{
s8_ret = 1;
}
else if(u8_result == 4) //绝缘监测告警
{
s8_ret = 2;
}
else //绝缘监测失败
{
s8_ret = 0;
}
return s8_ret;
}
#endif
#if 1 //绝缘监测控制
/*
函数功能:进行绝缘检测流程 计算绝缘检测电阻
输入:U8_T gunNo 充电枪号
输出:绝缘检测结果:U8_T *u8_result 1:绝缘检测中;
2:大地闭合阶段绝缘检测失败;
3:光电继电器闭合阶段绝缘检测失败;
4:绝缘检测告警,但可以正常充电;
5:绝缘检测正常
*f32_rz 正对地电阻
*f32_rf 负对地电阻
备注:绝缘流程及公式说明
1、控制系统接大地(PE)高压继电器®闭合
U𝑃:充电机输出正极(DC+)对大地(PE)的电压值
U𝑁:大地(PE)对充电机输出负极(DC-)的电压值
V1 = U𝑁/801.5K - U𝑃/801.5K
(1)如果U𝑃>U𝑁: R𝑁 = (801.5K × U𝑁)/(U𝑃 U𝑁)
判断 R𝑁绝缘达标,控制短接正桥臂电阻的光电继电器®闭合,不符合直接报绝缘故障;
(2)如果U𝑃<U𝑁:R𝑃 = (801.5K × U𝑃)/(U𝑁 U𝑃)
判断 R𝑃绝缘达标,控制短接正桥臂电阻的光电继电器®闭合,不符合直接报绝缘故障;
(3)如果U𝑃=U𝑁,控制短接正桥臂电阻的光电继电器®闭合;
2、闭合光电继电器® 绝缘电阻值计算
U𝑃′:充电机输出正极(DC+)对大地(PE)的电压值
U𝑁′:大地(PE)对充电机输出负极(DC-)的电压值
V2 = U𝑁′/801.5K - U𝑃′/801.5K
R𝑃 = (U𝑁′ × U𝑃 − U𝑁 × U𝑃′)/(U𝑁′ × V1 U𝑁 × V2)
R𝑁 = (U𝑃 × U𝑁′ − U𝑃′ × U𝑁)/(U𝑃′ × V1 U𝑃 × V2)
*/
static void v_insulation_Calculate(U8_T gunNo,U8_T *u8_result,F32_T *f32_rz, F32_T *f32_rf)
{
INS_EXTE_DATA *ins_data = NULL;
float volt = 0.0;
if (gunNo >= GUN_MAX_CNT || u8_result == NULL ||
f32_rz == NULL || f32_rf == NULL)
return;
ins_data = &(s_ins_ctrl[gunNo]);
F32_T f32_vol1 = f32_insl_get_adc_vol(gunNo,1); //直流母线 正对地电压
F32_T f32_vol2 = f32_insl_get_adc_vol(gunNo,0); //直流母线 负对地电压
//FLOWCTRL_LOG("INS adcV=%f,%f\n",f32_vol1,f32_vol2);
if(ins_data->u8_step == 0)
{
if(ins_data->u32_ins_timer == 0) //参数初始化
{
memset(ins_data,0,sizeof(INS_EXTE_DATA)); //开始绝缘检测前,对本枪绝缘检测数据存储结构体初始化
volt = f32_vol1 + f32_vol2; //绝缘检测开始前获取当前回路输出电压,计算出该电压等级下绝缘告警与失败的电阻值
ins_data->f32_R_min = 100 * volt;
ins_data->f32_R_max = 500 * volt;
ins_data->u32_ins_timer = get_current_seconds(); //记录绝缘检测开始的时间
FLOWCTRL_LOG("Gun%d(0-N) INS start, adcV=%f,%f\n",gunNo,f32_vol1,f32_vol2);
switch(gunNo) //绝缘检测开始,大地控合,光电继电器控分
{
case 0:
GUN1_INS_CTL(1);
GUN1_UBAL_CTL(0);
break;
case 1:
GUN2_INS_CTL(1);
GUN2_UBAL_CTL(0);
break;
}
*u8_result = 1; // 1:绝缘检测中;
}
else if(u32_safe_seconds_since(ins_data->u32_ins_timer) > 1) //大地控合1S后开始采集电压,进行绝缘电阻计算
{
ins_data->f64_V1 = (f32_vol2 - f32_vol1) / 801500;
if(f32_vol1 == f32_vol2) //正对地与负对地电压相等,控合光电继电器,进入下一步
{
switch(gunNo)
{
case 0:
GUN1_UBAL_CTL(1);
break;
case 1:
GUN2_UBAL_CTL(1);
break;
}
ins_data->u8_step = 1;
*u8_result = 1; // 1:绝缘检测中;
}
else if (f32_vol1 > f32_vol2)
{
ins_data->f32_R_m = (801500 * f32_vol2) / (f32_vol1 - f32_vol2);
*f32_rf = ins_data->f32_R_m; //负对地电压小,只计算负对地电阻
*f32_rz = FLT_MAX; //正对地赋值为最大值
}
else
{
ins_data->f32_R_m = (801500 * f32_vol1) / (f32_vol2 - f32_vol1);
*f32_rz = ins_data->f32_R_m; //正对地电压小,只计算负对地电阻
*f32_rf = FLT_MAX; //负对地赋值为最大值
}
if(ins_data->f32_R_m < ins_data->f32_R_min) //判定不达标,大地控合阶段绝缘检测失败,断开大地与光电继电器,返回1
{
FLOWCTRL_LOG("Gun%d(0-N) INS1 fail, adcV=%f,%f\n",gunNo,f32_vol1,f32_vol2);
FLOWCTRL_LOG("R= %f ,%f\n",ins_data->f32_R_m,ins_data->f32_R_min);
switch(gunNo)
{
case 0:
GUN1_INS_CTL(0);
GUN1_UBAL_CTL(0);
break;
case 1:
GUN2_INS_CTL(0);
GUN2_UBAL_CTL(0);
break;
}
*u8_result = 2; //2:大地闭合阶段绝缘检测失败;
}
else //判定达标,控合光电继电器,step置1,进入下一阶段,
{
switch(gunNo)
{
case 0:
GUN1_UBAL_CTL(1);
break;
case 1:
GUN2_UBAL_CTL(1);
break;
}
//FLOWCTRL_LOG("Close opto relay\n");
ins_data->f32_ADC_VOL1[0] = f32_vol1; //保存此时的采样值 在第二部计算时使用
ins_data->f32_ADC_VOL1[1] = f32_vol2;
ins_data->u8_step = 1;
*u8_result = 1 ; // 1:绝缘检测中;
}
}
}
else if (ins_data->u8_step == 1)
{
if(u32_safe_seconds_since(ins_data->u32_ins_timer) > 2)//光电继电器控合1s后,开始绝缘计算
{
volt = f32_vol1 + f32_vol2; //光电继电器闭合后,采集DC+、DC-对地电压
ins_data->f32_R_min = 100 * volt;
ins_data->f32_R_max = 500 * volt;
ins_data->f64_V2 = f32_vol2/801500 - f32_vol1/801500; //计算 V2
ins_data->f32_ADC_VOL2[0] = f32_vol1;
ins_data->f32_ADC_VOL2[1] = f32_vol2;
ins_data->f32_ins_RZ = (ins_data->f32_ADC_VOL2[1]*ins_data->f32_ADC_VOL1[0] - ins_data->f32_ADC_VOL1[1]*ins_data->f32_ADC_VOL2[0])
/(ins_data->f32_ADC_VOL2[1]*ins_data->f64_V1 - ins_data->f32_ADC_VOL1[1]*ins_data->f64_V2);
ins_data->f32_ins_RF = (ins_data->f32_ADC_VOL1[0]*ins_data->f32_ADC_VOL2[1] - ins_data->f32_ADC_VOL2[0]*ins_data->f32_ADC_VOL1[1])
/(ins_data->f32_ADC_VOL2[0]*ins_data->f64_V1 - ins_data->f32_ADC_VOL1[0]*ins_data->f64_V2);
FLOWCTRL_LOG("1 V+gnd=%.2f, V-gnd=%.2f\n",ins_data->f32_ADC_VOL1[0],ins_data->f32_ADC_VOL1[1]);
FLOWCTRL_LOG("2 V+gnd=%.2f, V-gnd=%.2f\n",ins_data->f32_ADC_VOL2[0],ins_data->f32_ADC_VOL2[1]);
FLOWCTRL_LOG("V1: %.10f ,V2: %.10f\n",ins_data->f64_V1,ins_data->f64_V2);
ins_data->f32_ins_RZ = fabs(ins_data->f32_ins_RZ);
ins_data->f32_ins_RF = fabs(ins_data->f32_ins_RF);
*f32_rz = ins_data->f32_ins_RZ;
*f32_rf = ins_data->f32_ins_RF;
switch(gunNo) //绝缘计算完成,大地与光耦控分
{
case 0:
GUN1_INS_CTL(0);
GUN1_UBAL_CTL(0);
break;
case 1:
GUN2_INS_CTL(0);
GUN2_UBAL_CTL(0);
break;
}
ins_data->u8_step = 0; //绝缘检测完成,step置0
ins_data->u32_ins_timer = 0; //绝缘检测完成,u32_insu_timer置0
FLOWCTRL_LOG("Th: min=%.2f, max=%.2f\n",ins_data->f32_R_min,ins_data->f32_R_max);
FLOWCTRL_LOG("R+gnd=%.2f, R-gnd=%.2f\n",ins_data->f32_ins_RZ,ins_data->f32_ins_RF);
//光电继电器闭合后,直流回路绝缘情况判定
if(ins_data->f32_ins_RF < ins_data->f32_R_min || ins_data->f32_ins_RZ < ins_data->f32_R_min )
{
*u8_result = 3; //3:光电继电器闭合阶段绝缘检测失败;
}
else if (ins_data->f32_ins_RF <= ins_data->f32_R_max || ins_data->f32_ins_RZ <= ins_data->f32_R_max )
{
*u8_result = 4; //4:绝缘检测告警,但可以正常充电;
}
else
{
*u8_result = 5; //5:绝缘检测正常
}
}
}
}
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef INSULATION_CTRL_H
#define INSULATION_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
S8_T s8_insulation_action(U8_T gunNo ,F32_T *f32_rz, F32_T *f32_rf);
void v_insulation_stop(U8_T gunNo);
#ifdef __cplusplus
}
#endif
#endif /* INSULATION_CTRL_H */
+706
View File
@@ -0,0 +1,706 @@
#include "flowctrl/mdu_swh_ctrl.h"
#include "flowctrl/flowctrl_task.h"
#include "flowctrl/flow_interface.h"
#include "collect_ctrl/yk_ctrl.h"
#include "publicdata/publicdata.h"
extern S_FLOW_DATA s_flow_data;
#if 1 //母联逻辑控制
/**
* ************************************************************************
* @brief - 功率切换计算 -- 检查另一把枪充电状态 控制母联闭合
*
* @param[in] u8_gunNo - 枪号
*
* @return U8_T - 借用模块动作标志 0正常使用 1新增借用模块 2 退出借用模块
*
* @author jiankalka
* @date 2025-11-20
* ************************************************************************
*/
static U8_T u8_mdu_shw_power_switching(U8_T u8_gunNo)
{
U8_T u8_other_mdu_action = 0; //
U8_T u8_otherGunNo = 1-u8_gunNo; // 双枪桩 另一把枪号
static U32_T u32_bus_action_time = 0;
S_FLOW_REAL_DATA *s_real_data = &s_flow_data.s_real_data[u8_gunNo]; //获取充电实时信息
S_FLOW_REAL_DATA *s_other_real_data = &s_flow_data.s_real_data[u8_otherGunNo]; //获取充电实时信息
U16_T u16_other_k1k2_pos,u16_other_k1k2_neg,u16_other_xiefang ;
U16_T u16_mdu_put_vol ,u16_other_mdu_put_vol;
U8_T u8_mdu_inpile_num = u16_flow_get_sys_data(u8_gunNo , E_FLOW_GET_DATA_MDU_IN_NUM , 0); //获取模块分配数量
U16_T u16_other_mdu_state = u16_flow_get_sys_data(u8_otherGunNo, E_FLOW_GET_DATA_MDU_WORK,0); //获取模块运行状态 0空闲 1工作
U8_T u8_mdu_rated_pow = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_MDU_RATED_POW,0); //单模块额定功率 单位kw
U8_T u8_bus_km_state = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_BUS_KM,0); //母联接触器状态 0:断开; 1:闭合
U8_T u8_dc_bus_en = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_DC_BUS_EN,0); //直流母联接触器使能 0 不使能 1使能
U64_T u64_need_pow = (U64_T)(s_real_data->u16_need_vol * s_real_data->u16_need_cur)/100; //需求功率 单位w
U64_T u64_usable_max_pow = u8_mdu_rated_pow * 1000 * u8_mdu_inpile_num; //本枪最大可用功率 单位w
if(u8_bus_km_state) //母联闭合 检查母联断开条件
{
//另一把枪 不可借用时 断开母联并退出模块
if(s_other_real_data->e_flow_real_state != E_FLOW_REAL_FREE &&
s_other_real_data->e_flow_real_state != E_FLOW_REAL_STOP_OK)
{
if(u32_bus_action_time == 0)
u32_bus_action_time = get_current_seconds();
u8_other_mdu_action = 2; //退出借用模块
//借用模块停止运行 或者 借用枪状态大于等于绝缘启动状态 或者 停止时间超过5秒 断开母联
if(u16_other_mdu_state == 0 ||
s_other_real_data->e_flow_real_state >= E_FLOW_REAL_INSUL_START ||
u32_safe_seconds_since(u32_bus_action_time) > 5)
{
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_BUS_YK,CMD_OFF,0); //断开母联
FLOWCTRL_LOG("[%d](0-N)Other gun NA[%d], open bus\n",u8_gunNo,s_other_real_data->e_flow_real_state);
}
}
//如果需求功率 小于本枪最大可用功率(5kw冗余防抖) 说明不需要借用模块 则控制模块退出并断开模块
else if(u64_need_pow < u64_usable_max_pow - 5*1000 )
{
if(u32_bus_action_time == 0)
u32_bus_action_time = get_current_seconds();
u8_other_mdu_action = 2; //退出借用模块
//借用模块停止运行 或者 停止时间超过5秒 断开母联
if(u16_other_mdu_state == 0 ||
u32_safe_seconds_since(u32_bus_action_time) > 5)
{
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_BUS_YK,CMD_OFF,0); //断开母联
FLOWCTRL_LOG("[%d](0-N)Need drop, open bus\n",u8_gunNo);
}
}
else //正常借用
{
u32_bus_action_time = 0;
}
}
else //母联断开 检查母联闭合条件
{
if(u8_dc_bus_en == 0) //直流母联接触器不使能 则不借用模块
{
return u8_other_mdu_action;
}
//如果需求功率 大于本枪最大可用功率 (此处不单独增加功率防抖,仅在退出时使用防抖)
if(u64_need_pow > u64_usable_max_pow )
{
//另一把枪处于空闲状态 可以借用
if(s_other_real_data->e_flow_real_state == E_FLOW_REAL_FREE ||
s_other_real_data->e_flow_real_state == E_FLOW_REAL_STOP_OK)
{
u16_other_k1k2_pos = u16_flow_get_sys_data(u8_otherGunNo,E_FLOW_GET_DATA_KM_POS,0); //正母线直流输出接触器状态
u16_other_k1k2_neg = u16_flow_get_sys_data(u8_otherGunNo,E_FLOW_GET_DATA_KM_NEG,0); //负母线直流输出接触器状态
u16_other_xiefang = u16_flow_get_sys_data(u8_otherGunNo,E_FLOW_GET_DATA_XF_KM,0); //泄放接触器状态
// 另一把枪K1K2 和 泄放状态都是断开
if(u16_other_k1k2_pos == 0 &&
u16_other_k1k2_neg == 0 &&
u16_other_xiefang == 0)
{
u8_other_mdu_action = 1; //新增借用模块
//控制模块启动
if(u16_other_mdu_state) //模块运行 则检查两组模块输出电压差值
{
u16_mdu_put_vol = u16_flow_get_sys_data(u8_gunNo,E_FLOW_GET_DATA_MDU_PUT_VOL,0); //获取模块输出电压值 精度0.1V
u16_other_mdu_put_vol = u16_flow_get_sys_data(u8_otherGunNo,E_FLOW_GET_DATA_MDU_PUT_VOL,0); //获取模块输出电压值 精度0.1V
//借用模块输出电压 和 本枪模块输出电压 差值在10以内 则闭合母联
if(abs(u16_mdu_put_vol - u16_other_mdu_put_vol) < 10*10)
{
if(u32_bus_action_time == 0)
u32_bus_action_time = get_current_seconds();
if(u32_safe_seconds_since(u32_bus_action_time) > 2) //延时2秒
{
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_BUS_YK,CMD_ON,0); //闭合母联
FLOWCTRL_LOG("[%d](0-N)V bal(%d,%d)(0.1V), close bus\n",u8_gunNo,u16_mdu_put_vol,u16_other_mdu_put_vol);
}
}
else
u32_bus_action_time = 0;
}
else
u32_bus_action_time = 0;
}
}
}
}
return u8_other_mdu_action;
}
/**
* ************************************************************************
* @brief - 模块母联控制 充电中切换控制
*
* @param[in] u8_gunNo - 枪号
*
*
* @author jiankalka
* @date 2025-11-20
* ************************************************************************
*/
static void v_mdu_swh_chargeing_ctrl(U8_T u8_gunNo)
{
U8_T u8_otherGunNo = 1-u8_gunNo; // 双枪桩 另一把枪号
S_MDU_ALLOCATE_CTRL *s_mdu_allocate_ctrl = &s_flow_data.s_mdu_allocate_ctrl[u8_gunNo];
S_FLOW_REAL_DATA *s_real_data = &s_flow_data.s_real_data[u8_gunNo]; //获取充电实时信息
U16_T u16_mdu_put_vol; //获取模块输出电压值 精度0.1V
U8_T u8_mdu_rung_num = u16_flow_get_sys_data(u8_gunNo , E_FLOW_GET_DATA_MDU_RUN_NUM , 0); //获取模块运行数量
U8_T u8_mdu_inpile_num = u16_flow_get_sys_data(u8_gunNo , E_FLOW_GET_DATA_MDU_IN_NUM , 0); //获取模块分配数量
U8_T u8_othergun_mdu_rung_num = u16_flow_get_sys_data(u8_otherGunNo , E_FLOW_GET_DATA_MDU_RUN_NUM , 0); //获取模块运行数量
U8_T u8_bus_km_state = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_BUS_KM,0); //母联接触器状态 0:断开; 1:闭合
//功率切换计算 -- 检查另一把枪充电状态 控制母联闭合
U8_T u8_other_mdu_action = u8_mdu_shw_power_switching(u8_gunNo); // 模块退出时 要单独控制另外一组模块停止
if(u8_bus_km_state) //母联闭合 则两组模块都为一把枪充电
{
if(u8_other_mdu_action == 0) //借用模块正常使用
{
s_mdu_allocate_ctrl->u8_yk_cmd = 3; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_mdu_allocate_ctrl->u16_need_vol = s_real_data->u16_need_vol; //需求电压 精度0.1
s_mdu_allocate_ctrl->u16_need_cur = s_real_data->u16_need_cur; //需求电流 精度0.1
if(u8_mdu_rung_num + u8_othergun_mdu_rung_num != 0)
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = s_real_data->u16_need_cur/(u8_mdu_rung_num + u8_othergun_mdu_rung_num); //单模块需求电流 精度0.1
else
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = 0;
//控制借用模块 启动
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u8_yk_cmd = 3; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u16_need_vol = s_real_data->u16_need_vol; //需求电压 精度0.1
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u16_need_cur = s_real_data->u16_need_cur; //需求电流 精度0.1
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u16_one_mdu_need_cur = s_mdu_allocate_ctrl->u16_one_mdu_need_cur;
}
else if(u8_other_mdu_action == 2) //模块退出
{
s_mdu_allocate_ctrl->u8_yk_cmd = 3; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_mdu_allocate_ctrl->u16_need_vol = s_real_data->u16_need_vol; //需求电压 精度0.1
s_mdu_allocate_ctrl->u16_need_cur = s_real_data->u16_need_cur; //需求电流 精度0.1
if(u8_mdu_rung_num != 0)
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = s_real_data->u16_need_cur/(u8_mdu_rung_num); //单模块需求电流 精度0.1
else
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = 0;
//另一组控制停止
memset(&s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo] , 0 ,sizeof(S_MDU_ALLOCATE_CTRL));
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u8_yk_cmd = 4; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
}
}
else //母联断开 则各算各的
{
s_mdu_allocate_ctrl->u8_yk_cmd = 3; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_mdu_allocate_ctrl->u16_need_vol = s_real_data->u16_need_vol; //需求电压 精度0.1
s_mdu_allocate_ctrl->u16_need_cur = s_real_data->u16_need_cur; //需求电流 精度0.1
if(u8_mdu_rung_num != 0)
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = s_real_data->u16_need_cur/u8_mdu_rung_num; //单模块需求电流 精度0.1
else
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = 0;
if(u8_other_mdu_action == 1) //新增借用模块
{
u16_mdu_put_vol = u16_flow_get_sys_data(u8_gunNo,E_FLOW_GET_DATA_MDU_PUT_VOL,0); //获取模块输出电压值 精度0.1V
if(abs(s_real_data->u16_need_vol - u16_mdu_put_vol) < 50) //如果输出电压和需求电压 误差5V以内 则认为是测试仪 避免优优绿能模块卡500V飘动 导致模块反复启停
u16_mdu_put_vol = s_real_data->u16_need_vol;
//控制借用模块 启动
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u8_yk_cmd = 3; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u16_need_vol = u16_mdu_put_vol; //需求电压 精度0.1
s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo].u16_one_mdu_need_cur = 0;
}
}
}
/**
* ************************************************************************
* @brief - 充电停止判断函数 检测母联状态和借用模块状态
*
* @param[in] u8_gunNo -
*
*
* @author jiankalka
* @date 2025-11-21
* ************************************************************************
*/
static void v_mdu_swh_charge_stop_ctrl(U8_T u8_gunNo)
{
U8_T u8_otherGunNo = 1-u8_gunNo; // 双枪桩 另一把枪号
S_MDU_ALLOCATE_CTRL *s_mdu_allocate_ctrl = &s_flow_data.s_mdu_allocate_ctrl[u8_gunNo];
S_MDU_ALLOCATE_CTRL *s_other_mdu_allocate_ctrl = &s_flow_data.s_mdu_allocate_ctrl[u8_otherGunNo];
S_FLOW_REAL_DATA *s_other_real_data = &s_flow_data.s_real_data[u8_otherGunNo]; //获取充电实时信息
U8_T u8_bus_km_state = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_BUS_KM,0); //母联接触器状态 0:断开; 1:闭合
U16_T u16_other_mdu_state = u16_flow_get_sys_data(u8_otherGunNo, E_FLOW_GET_DATA_MDU_WORK,0); //获取模块运行状态 0空闲 1工作
//首先停掉自身模块
memset(s_mdu_allocate_ctrl , 0 ,sizeof(S_MDU_ALLOCATE_CTRL));
s_mdu_allocate_ctrl->u8_yk_cmd = 4; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
//判断母联状态 母联闭合时 需要判断借用枪的状态是否为空闲 空闲时则控制停止与断开
if(u8_bus_km_state)
{
if(s_other_real_data->e_flow_real_state == E_FLOW_REAL_FREE || //另一把枪未充电
s_other_real_data->e_flow_real_state == E_FLOW_REAL_STOP_OK)
{
if(u16_other_mdu_state) //借用模块在运行 则停止运行
{
memset(s_other_mdu_allocate_ctrl , 0 ,sizeof(S_MDU_ALLOCATE_CTRL));
s_other_mdu_allocate_ctrl->u8_yk_cmd = 4; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
}
else
{
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_BUS_YK,CMD_OFF,0); //断开母联
FLOWCTRL_LOG("[%d](0-N)Gun stop, open bus\n",u8_gunNo);
}
}
}
}
/**
* ************************************************************************
* @brief - 母联切换控制
*
* @param[in] u16_leep_time -
*
*
* @author jiankalka
* @date 2025-11-18
* ************************************************************************
*/
static void v_mdu_shw_ctrl_bus_coupler(U16_T u16_leep_time)
{
U8_T u8_gunNo,u8_otherGunNo;
S_FLOW_REAL_DATA *s_real_data = NULL; //充电实时信息 空闲时清零
S_MDU_ALLOCATE_CTRL *s_mdu_allocate_ctrl = NULL;
for ( u8_gunNo = 0; u8_gunNo < s_flow_data.u8_gunCnt; u8_gunNo++)
{
s_real_data = &s_flow_data.s_real_data[u8_gunNo]; //获取充电实时信息
s_mdu_allocate_ctrl = &s_flow_data.s_mdu_allocate_ctrl[u8_gunNo];
u8_otherGunNo = 1-u8_gunNo; // 双枪桩 另一把枪号
switch (s_real_data->e_flow_real_state)
{
case E_FLOW_REAL_FREE : //空闲
case E_FLOW_REAL_STOP_OK : //充电停止完成
{
U8_T u8_bus_km_state = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_BUS_KM,0); //母联接触器状态 0:断开; 1:闭合
U16_T u16_mdu_state = u16_flow_get_sys_data(u8_gunNo, E_FLOW_GET_DATA_MDU_WORK,0); //获取模块运行状态 0空闲 1工作
if(s_flow_data.s_real_data[u8_otherGunNo].e_flow_real_state == E_FLOW_REAL_FREE ||
s_flow_data.s_real_data[u8_otherGunNo].e_flow_real_state == E_FLOW_REAL_STOP_OK) //另一把枪也是空闲 则清空、停止 否则可能在借用
{
memset(s_mdu_allocate_ctrl , 0 ,sizeof(S_MDU_ALLOCATE_CTRL));
s_mdu_allocate_ctrl->u8_yk_cmd = 4; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
}
//对面不是空闲 但是本枪模块是空闲 则断开母联(防止充电停止时未能断开母联,此时如果另一把枪借用模块也是先启动模块再闭合母联 不会影响)
else if(u8_bus_km_state == 1 && u16_mdu_state == 0)
{
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_BUS_YK,CMD_OFF,0); //断开母联
// FLOWCTRL_LOG("[%d](0-N)Gun stop, MDU idle, open bus\n",u8_gunNo);
}
break;
}
case E_FLOW_REAL_CHARGE_START : //充电启动 --借用后的模块如何能确保此状态下能完全停止?
{
memset(s_mdu_allocate_ctrl , 0 ,sizeof(S_MDU_ALLOCATE_CTRL));
s_mdu_allocate_ctrl->u8_yk_cmd = 4; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
break;
}
case E_FLOW_REAL_INSUL_START : //绝缘启动 -- 控制模块启动充电
{
s_mdu_allocate_ctrl->u8_yk_cmd = 1; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_mdu_allocate_ctrl->u16_need_vol = s_real_data->u16_need_vol; //需求电压 精度0.1
s_mdu_allocate_ctrl->u16_need_cur = 5; //需求电流 精度0.1
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = 5; //单模块需求电流 精度0.1
break;
}
case E_FLOW_REAL_INSUL_OK : //绝缘计算完成
case E_FLOW_REAL_INSUL_MDU_STOP : //绝缘检测模块停止
case E_FLOW_REAL_BMS_OUTTIME : //BMS通讯超时
{
memset(s_mdu_allocate_ctrl , 0 ,sizeof(S_MDU_ALLOCATE_CTRL));
s_mdu_allocate_ctrl->u8_yk_cmd = 2; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
break;
}
case E_FLOW_REAL_PRE_START : //开始预充 --预充启动电压
{
s_mdu_allocate_ctrl->u8_yk_cmd = 3; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
s_mdu_allocate_ctrl->u16_need_vol = s_real_data->u16_need_vol; //需求电压 精度0.1
s_mdu_allocate_ctrl->u16_need_cur = 5; //需求电流 精度0.1
s_mdu_allocate_ctrl->u16_one_mdu_need_cur = 5; //单模块需求电流 精度0.1
break;
}
case E_FLOW_REAL_PRE_OK : //预充完成 -- 充电中 计算需求电流和分配模块
case E_FLOW_REAL_CHARGEING : //充电中
{
v_mdu_swh_chargeing_ctrl(u8_gunNo); //充电中控制函数
break;
}
case E_FLOW_REAL_STOP : //充电停止
{
v_mdu_swh_charge_stop_ctrl(u8_gunNo);
break;
}
default:
break;
}
}
}
#endif
#if 1 //交流接触器 风扇 控制
/**
* ************************************************************************
* @brief - 交流接触器闭合条件判断
* 如没有枪闭合,直接断开
* 如有枪闭合,且工作状态不为空闲或充电完成,即为充电状态中,闭合交流接触器
* 如有枪闭合,且工作状态为空闲或充电完成,则充电借宿5分钟内闭合接触器,5分钟外,断开接触器
*
* @return U8_T - 0断开 1闭合
*
* @author jiankalka
* @date 2025-09-05
* ************************************************************************
*/
static U8_T u8_flow_get_acky_action()
{
U8_T i, u8_ret = 0; //默认断开
U8_T u8_state = 0; //状态检查 0都未插枪 1存在插枪且不存在充电中 2存在插枪且存在充电中
static ULONG long_timer = 0;
static U8_T u8_old_state = 0; //保存上一次的状态
static U8_T u8_first_run = 1; //首次运行标志,用于排除系统刚启动时的误动作
U8_T u8_gunLink; //枪连接状态 0未连接 1已连接
S_FLOW_REAL_DATA *s_real_data = NULL; //充电实时信息 空闲时清零
for (i = 0; i < s_flow_data.u8_gunCnt; i++) //遍历所有枪
{
u8_gunLink = u16_flow_get_sys_data(i, E_FLOW_GET_DATA_GUN_LINK, 0); //枪连接状态 0未连接 1已连接
s_real_data = &s_flow_data.s_real_data[i]; //获取充电实时信息
if(u8_gunLink == 1) //枪连接
{
u8_state = 1; //1存在插枪且不存在充电中
if(s_real_data->e_flow_real_state > E_FLOW_REAL_FREE && //充电状态 大于空闲 且小于充电完成,则为充电中
s_real_data->e_flow_real_state < E_FLOW_REAL_STOP_OK)
{
u8_state = 2; //2存在插枪且存在充电中
break;
}
}
}
/*
* 状态改变时重置计时器
* 特殊要求:
* - 1->0(插枪未充电超过5分钟后拔枪)时,不希望再次闭合3秒,因此该场景下不重置long_timer,
* 让“无连接”分支直接按已超时状态处理(保持断开)。
*/
U8_T u8_prev_state = u8_old_state;
if(u8_state != u8_old_state)
{
// 除了 1->0 的场景,其余状态切换都重置计时器
if (!(u8_prev_state == 1 && u8_state == 0)) {
long_timer = get_current_seconds();
}
u8_first_run = 0; //一旦发生过状态变化,即退出“首次运行保护”
// printf("acky_action state %d -> %d\r\n",u8_prev_state,u8_state);
u8_old_state = u8_state;
}
if(u8_state == 2) //存在插枪且存在充电中,直接闭合交流接触器
{
u8_ret = 1; //控制闭合
}
else if(u8_state == 1) //存在插枪但未充电 == 延时5分钟后断开
{
if(u32_safe_seconds_since(long_timer) < 10 * 60) //5分钟以内闭合
{
u8_ret = 1;
}
else
{
u8_ret = 0; //5分钟后断开
}
}
else //状态0:无连接
{
// 特殊情况:刚从状态1切换到0(即拔枪),如果之前在状态1时已经因为超过5分钟而断开,
// 不希望再次闭合3秒,这里直接保持断开。
if (u8_prev_state == 1)
{
u8_ret = 0; //系统刚启动,直接断开,避免误动作
}
else
{
// 普通“无连接”场景:考虑首次运行保护 + 3秒延时断开
// 排除系统刚运行时的误动作:系统运行时间小于30秒时直接断开
if(u8_first_run && get_current_seconds() < 30)
{
u8_ret = 0; //系统刚启动,直接断开,避免误动作
}
else if(u32_safe_seconds_since(long_timer) < 3) //3秒以内保持闭合
{
u8_ret = 1;
}
else
{
u8_ret = 0; //3秒后断开
}
}
}
return u8_ret;
}
#endif
#if 1 //风扇控制
static void v_fan_ctrl(U16_T u16_leep_time)
{
U8_T u8_gunNo,u8_run_flag = 0;
S_FLOW_REAL_DATA *s_real_data = NULL; //充电实时信息 空闲时清零
/* 风扇关机延时逻辑:
* - 只在“从工作->不工作”切换瞬间开始计时
* - 若 5 分钟内又进入“工作”(任一枪充电中),则取消关机并保持开启
*/
static U8_T u8_prev_run_flag = 0;
static U32_T u32_fan_off_start_sec = 0; //进入“需要延时关机”的起始秒数
static U8_T u8_fan_off_delay_active = 0; //是否处于延时关机有效期
const U32_T u32_fan_off_delay_sec = (5U * 60U); // 5分钟
for ( u8_gunNo = 0; u8_gunNo < s_flow_data.u8_gunCnt; u8_gunNo++)
{
s_real_data = &s_flow_data.s_real_data[u8_gunNo]; //获取充电实时信息
// printf("gun %d state %d\r\n",u8_gunNo,s_real_data->e_flow_real_state);
if(s_real_data->e_flow_real_state >= E_FLOW_REAL_CHARGEING &&
s_real_data->e_flow_real_state < E_FLOW_REAL_STOP_OK) //大于等于充电中且小于充电停止完成
{
u8_run_flag = 1;
break;
}
}
//printf("fan ctrl %d\r\n",u8_run_flag);
if(u8_run_flag) //风扇保持开启
{
u32_fan_off_start_sec = 0; //取消延时关机
u8_fan_off_delay_active = 0;
u8_prev_run_flag = 1;
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_FAN_YK,CMD_ON,0);
}
else //不工作:从工作->不工作切换时延时5分钟关机
{
if(u8_prev_run_flag == 1 && u32_fan_off_start_sec == 0)
{
u32_fan_off_start_sec = get_current_seconds();
u8_fan_off_delay_active = 1;
}
if(u8_fan_off_delay_active &&
u32_safe_seconds_since(u32_fan_off_start_sec) < u32_fan_off_delay_sec)
{
// 延时未到:保持风扇开启
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_FAN_YK,CMD_ON,0);
}
else
{
// 延时到或非延时场景:关机
u16_flow_put_ctrl(0,E_FLOW_CTRL_DATA_FAN_YK,CMD_OFF,0);
u32_fan_off_start_sec = 0;
u8_fan_off_delay_active = 0;
}
u8_prev_run_flag = 0;
}
}
#endif
#if 1 //模块交流脉冲上电控制
/**
* ************************************************************************
* @brief - 启动脉冲控制(带互斥机制,确保AC1和AC2不会同时闭合)
*
* @param[in] e_cmd - CMD_ON=AC1 CMD_OFF=AC2
*
* @author YZG
* @date 2026-05-03
* ************************************************************************
*/
static void v_yk_ctrl_pulse(E_CMD e_cmd)
{
U8_T u8_ac_ctrlNo;
u8_ac_ctrlNo = (e_cmd == CMD_ON) ? 1U : 2U;
if(u8_ac_ctrlNo != s_flow_data.u8_pulse_target_ac)
{
s_flow_data.e_pulse_state = E_PULSE_STATE_IDLE;
}
if(u8_ac_ctrlNo == s_flow_data.u8_pulse_target_ac &&
s_flow_data.e_pulse_state != E_PULSE_STATE_IDLE)
{
return;
}
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
s_flow_data.u8_pulse_target_ac = u8_ac_ctrlNo;
s_flow_data.u8_pulse_cycle_count = 0;
s_flow_data.e_pulse_state = E_PULSE_STATE_ON;
s_flow_data.u64_pulse_timer = u64_get_current_millis();
if(s_flow_data.u8_pulse_target_ac == 1U)
{
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_ON ,0);
}
else
{
u16_flow_put_ctrl(2,E_FLOW_CTRL_DATA_AC_YK , CMD_ON ,0);
}
}
/**
* ************************************************************************
* @brief - 脉冲控制状态处理
*
*
*
* @author YZG
* @date 2026-05-03
* ************************************************************************
*/
static void v_yk_pulse_process(void)
{
switch(s_flow_data.e_pulse_state)
{
case E_PULSE_STATE_ON:
{
if(u64_safe_millis_since(s_flow_data.u64_pulse_timer) >= 200U)
{
if(s_flow_data.u8_pulse_target_ac == 1U)
{
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
}
else
{
u16_flow_put_ctrl(2,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
}
s_flow_data.u64_pulse_timer = u64_get_current_millis();
s_flow_data.e_pulse_state = E_PULSE_STATE_OFF;
}
break;
}
case E_PULSE_STATE_OFF:
{
if(u64_safe_millis_since(s_flow_data.u64_pulse_timer) >= 2000U)
{
s_flow_data.u8_pulse_cycle_count++;
if(s_flow_data.u8_pulse_cycle_count >= 3U)
{
s_flow_data.e_pulse_state = E_PULSE_STATE_STOP;
}
else
{
if(s_flow_data.u8_pulse_target_ac == 1U)
{
u16_flow_put_ctrl(2,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_ON ,0);
}
else
{
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
u16_flow_put_ctrl(2,E_FLOW_CTRL_DATA_AC_YK , CMD_ON ,0);
}
s_flow_data.u64_pulse_timer = u64_get_current_millis();
s_flow_data.e_pulse_state = E_PULSE_STATE_ON;
}
}
break;
}
default:
break;
}
}
#endif
#if 1 //对外接口提供
/**********************************************************
* 函数名称: v_mdu_shw_control()
* 函数说明: 充电模块、开关模块控制函数
* 根据 充电模块控制命令 控制充电模块和开关模块
*
* 参数说明: u16_leep_time 执行周期 单位ms
* 输出说明: void
***********************************************************/
void v_mdu_shw_control(U16_T u16_leep_time)
{
v_mdu_shw_ctrl_bus_coupler(u16_leep_time);
}
/**
* ************************************************************************
* @brief - 风扇 交流接触器控制
*
* @param[in] u16_sleep - 线程任务睡眠周期值
*
*
* @author jiankalka
* @date 2025-09-05
* ************************************************************************
*/
void v_flow_ac_fan_ctrl(U16_T u16_sleep)
{
v_fan_ctrl(u16_sleep); //风扇控制
if(u8_flow_get_acky_action() == 1)
{
s_flow_data.u8_start_flag = 1; //模块任务运行
if(u16_flow_get_sys_data(0, E_FLOW_GET_DATA_MDU_RATED_POW, 0) == 80)
{
v_yk_ctrl_pulse(CMD_ON); //模块上电,控AC1脉冲
v_yk_pulse_process(); //脉冲控制状态处理
}
else
{
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_ON ,0);
u16_flow_put_ctrl(2,E_FLOW_CTRL_DATA_AC_YK , CMD_ON ,0);
}
}
else
{
s_flow_data.u8_start_flag = 0; //模块任务停止
if(u16_flow_get_sys_data(0, E_FLOW_GET_DATA_MDU_RATED_POW, 0) == 80)
{
v_yk_ctrl_pulse(CMD_OFF); //模块下电,控AC2脉冲
v_yk_pulse_process(); //脉冲控制状态处理
}
else
{
u16_flow_put_ctrl(1,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
u16_flow_put_ctrl(2,E_FLOW_CTRL_DATA_AC_YK , CMD_OFF ,0);
}
}
}
#endif
+33
View File
@@ -0,0 +1,33 @@
#ifndef MDU_SWH_CTRL_H
#define MDU_SWH_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "mdu_comm/mdu_interface.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
typedef struct
{
U8_T u8_start_flag; //模块运行开始标志 0未开始 1开始
U8_T u8_other_borrow_flag; //另一把枪 借用标志
U8_T u8_yk_cmd; //遥控命令枚举 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
U16_T u16_need_vol; //需求电压 精度0.1
U16_T u16_need_cur; //需求电流 精度0.1
U16_T u16_one_mdu_need_cur; //单模块需求电流 精度0.1
}S_MDU_ALLOCATE_CTRL;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_flow_ac_fan_ctrl(U16_T u16_sleep);
void v_mdu_shw_control(U16_T u16_leep_time);
#ifdef __cplusplus
}
#endif
#endif /* MDU_SWH_CTRL_H */
+58
View File
@@ -0,0 +1,58 @@
#include "gd32h7xx.h"
#include "sys_drv_init.h"
#include "FreeRTOS.h"
#include "task.h"
#include "app_init/app_init.h"
/** IAP 占用 0x08000000..0x08007FFF32KB),APP 链接 @ 0x08008000(见 GD32H759_APP.sct */
#define APP_VECTOR_TABLE_OFFSET 0x8000U
int main(void)
{
/* SystemInit 默认 VTOR=0x08000000;在此重定位到 APP 向量表,不修改 CMSIS 库文件 */
nvic_vector_table_set(NVIC_VECTTAB_FLASH, APP_VECTOR_TABLE_OFFSET);
//初始化
v_sys_hardware_init();
// 初始化应用任务并启动调度器
app_os_init();
vTaskStartScheduler();
// 理论上不会执行到这里;若调度器异常返回,则进入运行灯闪烁兜底
while(1)
{
RUN_LED_TOGGLE();
delay_ms(1000U);
}
}
/* FreeRTOS 空闲任务钩子函数:当系统无就绪任务时会反复进入此回调。
* 当前用于输出“系统空闲心跳”(慢闪运行灯),便于观察系统仍在正常调度。 */
void vApplicationIdleHook(void)
{
/* Run LED heartbeat moved to timer ISR (v_app_tim_tick_1ms). */
}
/* FreeRTOS 栈溢出钩子:检测到任务栈溢出时回调。
* 处理策略:关闭中断并进入故障灯循环,故障码为 5。 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
(void)xTask;
(void)pcTaskName;
/* 编码 5FreeRTOS 栈溢出 */
taskDISABLE_INTERRUPTS();
v_fault_blink_code_loop(5U);
}
/* FreeRTOS 内存申请失败钩子:pvPortMalloc 分配失败时回调。
* 处理策略:关闭中断并进入故障灯循环,故障码为 6。 */
void vApplicationMallocFailedHook(void)
{
/* 编码 6FreeRTOS malloc 失败 */
taskDISABLE_INTERRUPTS();
v_fault_blink_code_loop(6U);
}
+102
View File
@@ -0,0 +1,102 @@
/*!
\file main.h
\brief the header file of main
\version 2025-02-19, V2.1.0, demo for GD32H7xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef MAIN_H
#define MAIN_H
#include "gd32h7xx.h"
#include "stdint.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#include "publicdata/type.h"
#include "FreeRTOS.h"
#include "task.h"
#include "sys_drv_init.h"
#include "publicdata/public_define.h"
//#define USE_DHCP /* enable DHCP, if disabled static address is used */
/*The USE_ENET0 and USE_ENET1 macros cannot be opened at the same time*/
#define USE_ENET0
//#define USE_ENET1
/* MAC address: BOARD_MAC_ADDR0:BOARD_MAC_ADDR1:BOARD_MAC_ADDR2:BOARD_MAC_ADDR3:BOARD_MAC_ADDR4:BOARD_MAC_ADDR5 */
#define BOARD_MAC_ADDR0 2
#define BOARD_MAC_ADDR1 0xA
#define BOARD_MAC_ADDR2 0xF
#define BOARD_MAC_ADDR3 0xE
#define BOARD_MAC_ADDR4 0xD
#define BOARD_MAC_ADDR5 6
/* static IP address: BOARD_IP_ADDR0.BOARD_IP_ADDR1.BOARD_IP_ADDR2.BOARD_IP_ADDR3 */
#define BOARD_IP_ADDR0 192
#define BOARD_IP_ADDR1 168
#define BOARD_IP_ADDR2 1
#define BOARD_IP_ADDR3 100
/* remote station IP address: IP_S_ADDR0.IP_S_ADDR1.IP_S_ADDR2.IP_S_ADDR3 */
#define IP_S_ADDR0 10
#define IP_S_ADDR1 50
#define IP_S_ADDR2 3
#define IP_S_ADDR3 140
/* net mask */
#define BOARD_NETMASK_ADDR0 255
#define BOARD_NETMASK_ADDR1 255
#define BOARD_NETMASK_ADDR2 255
#define BOARD_NETMASK_ADDR3 0
/* gateway address */
#define BOARD_GW_ADDR0 192
#define BOARD_GW_ADDR1 168
#define BOARD_GW_ADDR2 1
#define BOARD_GW_ADDR3 1
/* MII and RMII mode selection */
#define RMII_MODE // user have to provide the 50 MHz clock by soldering a 50 MHz oscillator
//#define MII_MODE
/* clock the PHY from external 25MHz crystal (only for MII mode) */
#ifdef MII_MODE
#define PHY_CLOCK_MCO
#endif
#endif /* MAIN_H */
Binary file not shown.
+834
View File
@@ -0,0 +1,834 @@
#include "PCM_comm.h"
#include "lwip/sockets.h"
#include "mdu_comm/mdu_comm_task.h"
#include "mdu_comm/mdu_interface.h"
#include "app_init/app_init.h"
#include "mylog/mylog.h"
#include "publicdata/publicdata.h"
#include "wdt_task/wdt_task.h"
#include "app_init/app_init.h"
#include "app_init/app_test.h"
#include "app_init/task_lock.h"
#include "flowctrl/flowctrl_task.h"
#include "flowctrl/flow_interface.h"
#include "collect_ctrl/collect_task.h"
#include <string.h>
#include <stdio.h>
/*============================================================================
* PCM UDP 通信模块(终端角色)
*
* 功能:实现群充终端(分桩)与主机之间的UDP通信协议
* 协议定义参考:主机终端通信协议.md
*
* 本机角色:终端(分桩)
* - 主动发送:分桩心跳帧(0x10) 周期1000ms
* - 主动发送:分桩请求帧(0x12) 周期200ms
* - 接收处理:主机心跳帧(0x11)
* - 接收处理:分桩请求应答帧(0x13)
*
* 帧结构:
* 5A 99 - 帧头(2B)
* xx - 帧功能码(1B)
* xx - 数据长度(1B)
* data - 帧数据(NB)
* xx - CRC校验(1B)
* A5 - 帧尾(1B)
*============================================================================*/
/*============================================================================
* 局部宏定义
*============================================================================*/
#define PCM_COMM_TASK_ID (TASK_ID_MduComm)
#define PCM_COMM_LOG(fmt, ...) MYLOG_MSG(PCM_COMM_TASK_ID, (fmt), ##__VA_ARGS__)
/* 分桩请求帧:每组枪数据长度(11字节) */
#define PCM_TERM_REQUEST_GUN_DATA_LEN 11
/* 分桩请求应答帧:每组枪数据长度(9字节) */
#define PCM_HOST_RESPONSE_GUN_DATA_LEN 9
/* 0x12 分桩请求帧测试开关:
* 0: 使用系统实时数据
* 1: 使用本文件内置测试结构体数据(脱离系统数据源联调) */
#ifndef PCM_TERM_REQUEST_TEST_EN
#define PCM_TERM_REQUEST_TEST_EN 0
#endif
/* UDP收发内容打印开关(默认关闭,避免刷屏/占用CPU)
* 0: 关闭
* 1: 打印 TX/RX 数据(十六进制,截断到最大字节数) */
#ifndef PCM_UDP_PRINT_EN
#define PCM_UDP_PRINT_EN 0
#endif
#ifndef PCM_UDP_PRINT_MAX_BYTES
#define PCM_UDP_PRINT_MAX_BYTES 64U
#endif
/*============================================================================
* 局部变量
*============================================================================*/
/* UDP 套接字句柄 */
static int g_pcm_udp_sock = -1;
/* UDP 目标地址结构体(主机地址) */
static struct sockaddr_in g_pcm_peer_addr;
/* PCM 通信数据 */
S_PCM_COMM_DATA s_pcm_comm;
/* 接收缓冲区 */
static U8_T g_u8_recv_buf[PCM_FRAME_MAX_LEN];
#if (PCM_UDP_PRINT_EN != 0)
static void v_pcm_log_hex(const char *tag, const U8_T *data, U16_T len)
{
char line[PCM_UDP_PRINT_MAX_BYTES * 3U + 16U];
U16_T n = len;
U16_T i;
int pos = 0;
if ((tag == NULL) || (data == NULL)) {
return;
}
if (n > (U16_T)PCM_UDP_PRINT_MAX_BYTES) {
n = (U16_T)PCM_UDP_PRINT_MAX_BYTES;
}
for (i = 0U; i < n; i++) {
int w = snprintf(&line[pos], sizeof(line) - (size_t)pos, "%02X ", (unsigned)data[i]);
if (w <= 0) {
break;
}
pos += w;
if (pos >= (int)sizeof(line) - 1) {
break;
}
}
line[(pos < (int)sizeof(line)) ? pos : ((int)sizeof(line) - 1)] = '\0';
if (len > n) {
printf("%s len=%u show=%u: %s...\r\n", tag, (unsigned)len, (unsigned)n, line);
} else {
printf("%s len=%u: %s\r\n", tag, (unsigned)len, line);
}
}
#endif
#if (PCM_UDP_PRINT_EN != 0)
static void v_pcm_log_addr_port(const char *tag, const struct sockaddr_in *addr)
{
U32_T ip_host;
U16_T port_host;
if ((tag == NULL) || (addr == NULL)) {
return;
}
ip_host = PP_NTOHL(addr->sin_addr.s_addr);
port_host = PP_NTOHS(addr->sin_port);
printf("%s %u.%u.%u.%u:%u\r\n",
tag,
(unsigned)((ip_host >> 24) & 0xFFU),
(unsigned)((ip_host >> 16) & 0xFFU),
(unsigned)((ip_host >> 8) & 0xFFU),
(unsigned)(ip_host & 0xFFU),
(unsigned)port_host);
}
#endif
#if (PCM_TERM_REQUEST_TEST_EN != 0)
typedef struct
{
U8_T u8_gun_num;
S_PCM_GUN_TERM_DATA s_gun_data[GUN_MAX_CNT];
} S_PCM_TERM_REQUEST_TEST_DATA;
/* 0x12 测试固定数据(仅在 PCM_TERM_REQUEST_TEST_EN=1 时生效) */
static S_PCM_TERM_REQUEST_TEST_DATA s_pcm_term_req_test_data = {
2U,
{
/* gun_addr, work_state, contactor_dc, contactor_discharge, need_vol(0.1V), need_cur(0.1A), fault_state, fault_reason */
{1U, 0U, 0U, 0U, 0U, 0U, 0U, 0U},
{2U, 0U, 0U, 0U, 0U, 0U, 0U, 0U}
}
};
#endif
/*============================================================================
* 局部函数声明
*============================================================================*/
static void v_pcm_udp_socket_init(void);
//static U8_T u8_pcm_verify_frame(const S_PCM_FRAME *ps_frame);
static S8_T s8_pcm_send_frame(U8_T u8_cmd, const U8_T *pu8_data, U8_T u8_len);
static void v_pcm_parse_host_heartbeat(const S_PCM_FRAME *ps_frame);
static void v_pcm_parse_host_response(const S_PCM_FRAME *ps_frame);
static void v_pcm_check_comm_timeout(U16_T u16_sleep_time_ms);
/*============================================================================
* 函数实现
*============================================================================*/
/**
* @brief 初始化 PCM UDP socket
*
* 参照 mylog.c 中 v_mylog_udp_init 的实现方式:
* - 创建 UDP socket
* - 绑定本地端口
* - 失败时关闭socket并重试
*/
static void v_pcm_udp_socket_init(void)
{
struct sockaddr_in local_addr;
if (g_pcm_udp_sock >= 0) {
return;
}
g_pcm_udp_sock = lwip_socket(AF_INET, SOCK_DGRAM, 0);
if (g_pcm_udp_sock < 0) {
PCM_COMM_LOG("pcm udp socket create fail");
return;
}
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = PP_HTONS(PCM_UDP_SRC_PORT);
local_addr.sin_addr.s_addr = PP_HTONL(INADDR_ANY);
if (lwip_bind(g_pcm_udp_sock, (const struct sockaddr *)&local_addr, sizeof(local_addr)) != 0) {
PCM_COMM_LOG("pcm udp bind fail, port=%d", PCM_UDP_SRC_PORT);
lwip_close(g_pcm_udp_sock);
g_pcm_udp_sock = -1;
return;
}
PCM_COMM_LOG("pcm udp socket init ok, port=%d", PCM_UDP_SRC_PORT);
}
///**
// * @brief 校验接收帧的合法性
// *
// * @param ps_frame 帧指针
// * @return U8_T 0无效 1有效
// */
//static U8_T u8_pcm_verify_frame(const S_PCM_FRAME *ps_frame)
//{
// U8_T u8_calc_crc;
// /* 校验帧头 */
// if (ps_frame->u16_head != PCM_FRAME_HEAD) {
// return 0;
// }
// /* 校验帧尾 */
// if (ps_frame->u8_tail != PCM_FRAME_TAIL) {
// return 0;
// }
// /* 校验数据长度合法性 */
// if (ps_frame->u8_len > PCM_FRAME_MAX_DATA_LEN) {
// return 0;
// }
// /* 校验CRC:计算范围 = 帧头(2) + 功能码(1) + 长度(1) + 数据(u8_len) */
// u8_calc_crc = u8_pcm_calc_crc((const U8_T *)&ps_frame->u16_head,
// (U16_T)(sizeof(U16_T) + sizeof(U8_T) + sizeof(U8_T) + ps_frame->u8_len));
// if (u8_calc_crc != ps_frame->u8_crc) {
// return 0;
// }
// return 1;
//}
/**
* @brief 发送PCM帧
*
* @param u8_cmd 帧功能码
* @param pu8_data 数据指针
* @param u8_len 数据长度
* @return S8_T 0成功 -1失败
*/
static S8_T s8_pcm_send_frame(U8_T u8_cmd, const U8_T *pu8_data, U8_T u8_len)
{
U8_T au8_send_buf[PCM_FRAME_MIN_LEN + PCM_FRAME_MAX_DATA_LEN];
U8_T u8_send_len;
U8_T u8_crc;
S8_T s8_ret;
if (g_pcm_udp_sock < 0) {
v_pcm_udp_socket_init();
}
if (g_pcm_udp_sock < 0) {
return -1;
}
if (u8_len > PCM_FRAME_MAX_DATA_LEN) {
return -1;
}
/* 手动组帧:紧凑排列,无空洞 */
u8_send_len = 0;
/* 帧头(2B) */
au8_send_buf[u8_send_len++] = (U8_T)(PCM_FRAME_HEAD >> 8);
au8_send_buf[u8_send_len++] = (U8_T)(PCM_FRAME_HEAD & 0xFF);
/* 功能码(1B) */
au8_send_buf[u8_send_len++] = u8_cmd;
/* 数据长度(1B) */
au8_send_buf[u8_send_len++] = u8_len;
/* 数据(NB) */
if (pu8_data != NULL && u8_len > 0) {
memcpy(&au8_send_buf[u8_send_len], pu8_data, u8_len);
u8_send_len += u8_len;
}
/* CRC校验(1B):计算范围 = 帧头 + 功能码 + 长度 + 数据 */
u8_crc = u16_crc_checksum(&au8_send_buf[4], u8_len);
au8_send_buf[u8_send_len++] = u8_crc;
/* 帧尾(1B) */
au8_send_buf[u8_send_len++] = PCM_FRAME_TAIL;
#if (PCM_UDP_PRINT_EN != 0)
v_pcm_log_addr_port("[PCM][UDP][TX][DST]", &g_pcm_peer_addr);
v_pcm_log_hex("[PCM][UDP][TX]", au8_send_buf, (U16_T)u8_send_len);
#endif
/* 发送 */
s8_ret = (S8_T)lwip_sendto(g_pcm_udp_sock,
(const void *)au8_send_buf,
(size_t)u8_send_len,
0,
(const struct sockaddr *)&g_pcm_peer_addr,
sizeof(g_pcm_peer_addr));
if (s8_ret < 0) {
return -1;
}
return 0;
}
/**
* @brief 解析主机心跳帧(功能码0x11)
*
* 数据格式:
* - 终端枪数量(1B)
* - 终端枪地址(1B) x N
*/
static void v_pcm_parse_host_heartbeat(const S_PCM_FRAME *ps_frame)
{
U8_T u8_gun_num;
if (ps_frame->u8_len < 1) {
return;
}
u8_gun_num = ps_frame->u8_data[0];
if (u8_gun_num == 0 || u8_gun_num > GUN_MAX_CNT) {
return;
}
/* 校验数据长度:1(枪数量) + u8_gun_num(枪地址) */
if (ps_frame->u8_len < (U8_T)(1 + u8_gun_num)) {
return;
}
/* 更新主机通信状态 */
s_pcm_comm.s_host.u32_last_recv_time = (U32_T)u64_get_current_millis();
s_pcm_comm.s_host.u8_comm_ok = 1;
PCM_COMM_LOG("recv host heartbeat, gun_num=%d", u8_gun_num);
}
/**
* @brief 解析分桩请求应答帧(功能码0x13)
*
* 数据格式(每组枪数据):
* - 终端枪地址(1B)
* - 模块启停状态(1B)
* - 输出电压(2B) BE 0.1V
* - 输出电流(2B) BE 0.1A
* - 主机故障状态(1B)
* - 主机故障原因(2B) BE
* 每组共 9 字节
*/
static void v_pcm_parse_host_response(const S_PCM_FRAME *ps_frame)
{
U8_T u8_gun_num;
U8_T i;
U8_T u8_offset;
U8_T u8_expected_len;
if (ps_frame->u8_len < 1) {
return;
}
u8_gun_num = ps_frame->u8_data[0];
if (u8_gun_num == 0 || u8_gun_num > GUN_MAX_CNT) {
return;
}
/* 校验数据长度:1(枪数量) + u8_gun_num * 9(每组枪数据) */
u8_expected_len = (U8_T)(1 + u8_gun_num * PCM_HOST_RESPONSE_GUN_DATA_LEN);
if (ps_frame->u8_len < u8_expected_len) {
return;
}
/* 更新主机通信状态 */
// s_pcm_comm.s_host.u32_last_recv_time = (U32_T)u64_get_current_millis();
s_pcm_comm.s_host.u8_comm_ok = 1;
s_pcm_comm.s_host.u8_gun_num = u8_gun_num;
u8_offset = 1;
for (i = 0; i < u8_gun_num && i < GUN_MAX_CNT; i++) {
S_PCM_GUN_HOST_DATA *ps_gun = &s_pcm_comm.s_host.s_gun_data[i];
U16_T u16_value;
ps_gun->u8_gun_addr = ps_frame->u8_data[u8_offset++];
ps_gun->u8_module_state = ps_frame->u8_data[u8_offset++];
/* 输出电压 BE: 高字节在前 */
u16_value = (U16_T)ps_frame->u8_data[u8_offset++] << 8;
u16_value |= (U16_T)ps_frame->u8_data[u8_offset++];
ps_gun->u16_out_vol = u16_value;
/* 输出电流 BE: 高字节在前 */
u16_value = (U16_T)ps_frame->u8_data[u8_offset++] << 8;
u16_value |= (U16_T)ps_frame->u8_data[u8_offset++];
ps_gun->u16_out_cur = u16_value;
ps_gun->u8_host_fault = ps_frame->u8_data[u8_offset++];
/* 主机故障原因 BE: 高字节在前 */
u16_value = (U16_T)ps_frame->u8_data[u8_offset++] << 8;
u16_value |= (U16_T)ps_frame->u8_data[u8_offset++];
ps_gun->u16_host_fault_reason = u16_value;
if(ps_gun->u16_out_vol > 2000 && ps_gun->u8_module_state == 0)
ps_gun->u8_module_state = 1;
// u8_offset += PCM_HOST_RESPONSE_GUN_DATA_LEN;
// PCM_COMM_LOG("recv host response, gun_addr=%d, module_state=%d, out_vol=%d, out_cur=%d, host_fault=%d, host_fault_reason=%d",
// ps_gun->u8_gun_addr, ps_gun->u8_module_state, ps_gun->u16_out_vol, ps_gun->u16_out_cur, ps_gun->u8_host_fault, ps_gun->u16_host_fault_reason);
}
}
/**
* @brief 检查主机通信超时
*
* @param u16_sleep_time_ms 任务周期时间(ms
*/
static void v_pcm_check_comm_timeout(U16_T u16_sleep_time_ms)
{ U64_T u64_now_ms __attribute__((unused));
U64_T u64_elapsed;
// (void)u16_sleep_time_ms;
if (s_pcm_comm.s_host.u32_last_recv_time == 0) {
return;
}
u64_now_ms = u64_get_current_millis();
u64_elapsed = u64_safe_millis_since(s_pcm_comm.s_host.u32_last_recv_time);
if (u64_elapsed >= PCM_COMM_TIMEOUT_MS) {
if (s_pcm_comm.s_host.u8_comm_ok != 0) {
s_pcm_comm.s_host.u8_comm_ok = 0;
PCM_COMM_LOG("pcm host comm timeout");
}
}
}
#if 1 //获取外部数据接口
#endif
/*============================================================================
* 全局函数实现
*============================================================================*/
/**
* @brief 初始化 PCM UDP 通信(终端角色)
*
* @param u8_term_no 终端编号(1开始,对应主机编号)
* @return S8_T 0成功 -1失败
*/
S8_T s8_pcm_udp_init(void)
{
/* 初始化socket */
v_pcm_udp_socket_init();
if (g_pcm_udp_sock < 0) {
return -1;
}
/* 初始化通信数据 */
memset(&s_pcm_comm, 0, sizeof(S_PCM_COMM_DATA));
//s_pcm_comm.u8_term_no = u8_term_no;
/* 构建本机(终端)IP地址:192.168.1.100 + (终端编号-1)*10 */
{
s_pcm_comm.u32_term_ip = PP_HTONL(LWIP_MAKEU32(g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip[0],
g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip[1],
g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip[2],
g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip[3]));
}
s_pcm_comm.u16_term_port = PP_HTONS(PCM_UDP_SRC_PORT);
/* 构建目标主机IP地址:192.168.1.200 + (终端编号-1) */
{
U8_T u8_ip3 = (U8_T)(PCM_HOST_IP_BASE3 + ((g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip[3] - 100)/PCM_TERM_IP_OFFSET)*PCM_TERM_IP_OFFSET);
s_pcm_comm.u32_host_ip = PP_HTONL(LWIP_MAKEU32(PCM_HOST_IP_BASE0,
PCM_HOST_IP_BASE1,
PCM_HOST_IP_BASE2,
u8_ip3));
}
s_pcm_comm.u16_host_port = PP_HTONS(PCM_UDP_DST_PORT);
/* 设置目标地址(主机地址) */
memset(&g_pcm_peer_addr, 0, sizeof(g_pcm_peer_addr));
g_pcm_peer_addr.sin_family = AF_INET;
g_pcm_peer_addr.sin_port = s_pcm_comm.u16_host_port;
g_pcm_peer_addr.sin_addr.s_addr = s_pcm_comm.u32_host_ip;
/* 初始化定时器 */
s_pcm_comm.u32_heartbeat_tick = 0;
s_pcm_comm.u32_request_tick = 0;
return 0;
}
/**
* @brief 发送分桩心跳帧(功能码0x10)
*
* 数据格式:
* - 终端枪数量(1B)
* - 终端枪地址(1B) x N
*
* @return S8_T 0成功 -1失败
*/
S8_T s8_pcm_send_term_heartbeat()
{
SYS_FIX_CFG_T *t_sys_fix_cfg = &g_t_share_data.t_sys_fix_cfg;
U8_T au8_data[1 + GUN_MAX_CNT];
U8_T u8_len = 0;
au8_data[u8_len++] = u16_flow_get_sys_data(0,E_FLOW_GET_DATA_GUN_CNT,0); //获取枪数量
au8_data[u8_len++] = t_sys_fix_cfg->u8_terminal[0]; //枪1地址
au8_data[u8_len++] = t_sys_fix_cfg->u8_terminal[1]; //枪2地址
return s8_pcm_send_frame(PCM_CMD_TERM_HEARTBEAT, au8_data, u8_len);
}
/**
* @brief 发送分桩请求帧(功能码0x12)
*
* 数据格式(每组枪数据):
* - 终端枪地址(1B)
* - 终端枪工作状态(1B)
* - 直流输出接触器状态(1B)
* - 直流输出泄放接触器状态(1B)
* - 需求电压(2B) BE 0.1V
* - 需求电流(2B) BE 0.1A
* - 终端枪故障状态(1B)
* - 终端枪故障原因(2B) BE
* 每组共 11 字节
*
* @return S8_T 0成功 -1失败
*/
S8_T s8_pcm_send_term_request(void)
{
U8_T au8_data[1 + GUN_MAX_CNT * PCM_TERM_REQUEST_GUN_DATA_LEN];
U8_T u8_len = 0;
U8_T i;
U8_T u8_gun_num;
U16_T u16_data;
SYS_FIX_CFG_T *t_sys_fix_cfg = &g_t_share_data.t_sys_fix_cfg;
#if (PCM_TERM_REQUEST_TEST_EN != 0)
u8_gun_num = s_pcm_term_req_test_data.u8_gun_num;
if (u8_gun_num > GUN_MAX_CNT) {
u8_gun_num = GUN_MAX_CNT;
}
#else
u8_gun_num = u16_flow_get_sys_data(0, E_FLOW_GET_DATA_GUN_CNT, 0); //获取枪数量
au8_data[u8_len++] = u8_gun_num; /* 枪数量 */
#endif
// /* 枪数量 */
for (i = 0; i < u8_gun_num ; i++) {
#if (PCM_TERM_REQUEST_TEST_EN != 0)
const S_PCM_GUN_TERM_DATA *ps_test = &s_pcm_term_req_test_data.s_gun_data[i];
au8_data[u8_len++] = u16_flow_get_sys_data(0, E_FLOW_GET_DATA_GUN_CNT, 0);
au8_data[u8_len++] = ps_test->u8_gun_addr; /* 枪地址 */
au8_data[u8_len++] = ps_test->u8_work_state; /* 工作状态 */
au8_data[u8_len++] = ps_test->u8_contactor_dc; /* 直流接触器 */
au8_data[u8_len++] = ps_test->u8_contactor_discharge; /* 泄放接触器 */
/* 需求电压 BE */
u16_data = ps_test->u16_need_vol;
au8_data[u8_len++] = (U8_T)((u16_data >> 8) & 0xFF);
au8_data[u8_len++] = (U8_T)(u16_data & 0xFF);
/* 需求电流 BE */
u16_data = ps_test->u16_need_cur;
au8_data[u8_len++] = (U8_T)((u16_data >> 8) & 0xFF);
au8_data[u8_len++] = (U8_T)(u16_data & 0xFF);
au8_data[u8_len++] = ps_test->u8_fault_state; /* 故障状态 */
/* 故障原因 BE */
u16_data = ps_test->u16_fault_reason;
au8_data[u8_len++] = (U8_T)((u16_data >> 8) & 0xFF);
au8_data[u8_len++] = (U8_T)(u16_data & 0xFF);
#else
au8_data[u8_len++] = t_sys_fix_cfg->u8_terminal[i]; /* 枪地址 */
au8_data[u8_len++] = u8_mdu_put_data(i); /* 工作状态 */
au8_data[u8_len++] = (U8_T)u8_get_gun_yx_data(YX_GUN_DC_KM,i); /* 直流接触器 */
au8_data[u8_len++] = (U8_T)u8_get_gun_yx_data(YX_GUN_RELIF,i); /* 泄放接触器 */
/* 需求电压 BE */
u16_data = (U16_T)u16_flow_put_data(i, E_FLOW_PUT_DATA_GUN_NEED_VOL, 0);
au8_data[u8_len++] = (U8_T)((u16_data >> 8) & 0xFF);
au8_data[u8_len++] = (U8_T)(u16_data & 0xFF);
/* 需求电流 BE */
u16_data = (U16_T)u16_flow_put_data(i, E_FLOW_PUT_DATA_GUN_NEED_CUR, 0);
au8_data[u8_len++] = (U8_T)((u16_data >> 8) & 0xFF);
au8_data[u8_len++] = (U8_T)(u16_data & 0xFF);
/* 故障状态 */
if(u8_get_fault_sum(i,0) > 0)
{
au8_data[u8_len++] = 1;
}
else
{
au8_data[u8_len++] = 0;
}
/* 故障原因 BE */
u16_data = u16_flow_get_sys_data(i, E_FLOW_GET_DATA_GUN_FAULT_INFO, 0);
au8_data[u8_len++] = (U8_T)((u16_data >> 8) & 0xFF);
au8_data[u8_len++] = (U8_T)(u16_data & 0xFF);
#endif
}
return s8_pcm_send_frame(PCM_CMD_TERM_REQUEST, au8_data, u8_len);
}
/**
* @brief PCM UDP 非阻塞接收处理
*
* 参照 mylog.c 中 v_mylog_udp_recv_echo 的实现方式:
* - 使用 MSG_DONTWAIT 非阻塞接收
* - 解析接收到的帧
*
* @return U8_T 0无数据 1收到有效数据
*/
U8_T u8_pcm_udp_recv_process(void)
{
int recv_len;
socklen_t peer_len;
struct sockaddr_in peer_addr;
S_PCM_FRAME s_frame;
S_PCM_FRAME *ps_frame = &s_frame;
if (g_pcm_udp_sock < 0) {
return 0;
}
peer_len = (socklen_t)sizeof(peer_addr);
recv_len = lwip_recvfrom(g_pcm_udp_sock,
(void *)g_u8_recv_buf,
sizeof(g_u8_recv_buf),
MSG_DONTWAIT,
(struct sockaddr *)&peer_addr,
&peer_len);
if (recv_len <= 0) {
return 0;
}
#if (PCM_UDP_PRINT_EN != 0)
v_pcm_log_addr_port("[PCM][UDP][RX][SRC]", &peer_addr);
v_pcm_log_hex("[PCM][UDP][RX]", g_u8_recv_buf, (U16_T)recv_len);
#endif
/* 检查最小帧长度 */
if (recv_len < PCM_FRAME_MIN_LEN) {
return 0;
}
memset(&s_frame, 0, sizeof(S_PCM_FRAME));
ps_frame->u16_head = (U16_T)((g_u8_recv_buf[0] << 8) | g_u8_recv_buf[1]);
ps_frame->u8_cmd = g_u8_recv_buf[2];
ps_frame->u8_len = g_u8_recv_buf[3];
// if (ps_frame->u8_len > PCM_FRAME_MAX_DATA_LEN) {
// return 0;
// }
memcpy(ps_frame->u8_data, &g_u8_recv_buf[4], ps_frame->u8_len);
ps_frame->u8_crc = g_u8_recv_buf[4 + ps_frame->u8_len];
ps_frame->u8_tail = g_u8_recv_buf[4 + ps_frame->u8_len + 1];
/* 校验帧合法性 */
// if (u8_pcm_verify_frame(ps_frame) == 0) {
// return 0;
// }
/* 根据功能码分发处理 */
switch (ps_frame->u8_cmd) {
case PCM_CMD_HOST_HEARTBEAT: /* 0x11 主机心跳帧 */
v_pcm_parse_host_heartbeat(ps_frame);
break;
case PCM_CMD_HOST_RESPONSE: /* 0x13 分桩请求应答帧 */
v_pcm_parse_host_response(ps_frame);
break;
default:
/* 未知功能码,忽略 */
break;
}
return 1;
}
/**
* @brief PCM UDP 周期处理函数(终端角色)
*
* 处理逻辑:
* - 每1000ms发送分桩心跳帧(0x10)
* - 每200ms发送分桩请求帧(0x12)
* - 每周期检查通信超时
*
* @param u16_sleep_time_ms 任务周期时间(ms
*/
void v_pcm_udp_periodic_handler(U16_T u16_sleep_time_ms)
{
if (g_pcm_udp_sock < 0) {
return;
}
/* 更新定时器 */
s_pcm_comm.u32_heartbeat_tick += u16_sleep_time_ms;
s_pcm_comm.u32_request_tick += u16_sleep_time_ms;
/* 每1000ms发送分桩心跳帧(0x10) */
if (s_pcm_comm.u32_heartbeat_tick >= PCM_HEARTBEAT_PERIOD_MS) {
s_pcm_comm.u32_heartbeat_tick = 0;
(void)s8_pcm_send_term_heartbeat();
}
/* 每200ms发送分桩请求帧(0x12) */
if (s_pcm_comm.u32_request_tick >= PCM_REQUEST_PERIOD_MS) {
s_pcm_comm.u32_request_tick = 0;
s8_pcm_send_term_request();
}
/* 检查通信超时 */
v_pcm_check_comm_timeout(u16_sleep_time_ms);
}
/**
* @brief 获取主机通信状态
*
* @return U8_T 0超时 1正常
*/
U8_T u8_pcm_get_host_comm_status(void)
{
return s_pcm_comm.s_host.u8_comm_ok;
}
/**
* @brief 获取主机应答枪数据
*
* @param u8_gun_index 枪索引
* @return S_PCM_GUN_HOST_DATA* 主机应答数据指针,失败返回NULL
*/
S_PCM_GUN_HOST_DATA* ps_pcm_get_host_gun_data(U8_T u8_gun_index)
{
if (u8_gun_index >= GUN_MAX_CNT) {
return NULL;
}
if (u8_gun_index >= s_pcm_comm.s_host.u8_gun_num) {
return NULL;
}
return &s_pcm_comm.s_host.s_gun_data[u8_gun_index];
}
/**
* @brief 更新本机(终端)枪数据(由外部模块调用)
*
* @param u8_gun_index 枪索引
* @param ps_data 终端枪数据指针
*/
void v_pcm_update_term_gun_data(U8_T u8_gun_index, const S_PCM_GUN_TERM_DATA *ps_data)
{
if (u8_gun_index >= GUN_MAX_CNT || ps_data == NULL) {
return;
}
memcpy(&s_pcm_comm.s_gun_data[u8_gun_index], ps_data, sizeof(S_PCM_GUN_TERM_DATA));
}
/**
* @brief PCM 通信主处理函数(由 mdu_comm_task 调用)
*
* 该函数在 u8_mdu_type == 0x02 时被 mdu_comm_task 主循环调用。
* 负责 PCM UDP 通信的初始化、周期收发和接收处理。
*
* @param u16_sleep_time_ms 任务周期时间(ms
*/
void v_pcm_comm_gw_action(U16_T u16_sleep_time_ms)
{
static U8_T u8_pcm_init_done = 0;
/* 首次运行时初始化 PCM UDP */
if (u8_pcm_init_done == 0) {
S8_T s8_ret = s8_pcm_udp_init();
if (s8_ret == 0) {
u8_pcm_init_done = 1;
PCM_COMM_LOG("pcm udp init success");
} else {
/* 初始化失败(可能lwIP网络协议栈未就绪),下次循环继续重试 */
return;
}
}
/* PCM UDP 周期处理:发送心跳(1000ms)、发送请求(200ms)、超时检测(5000ms) */
v_pcm_udp_periodic_handler(u16_sleep_time_ms);
/* PCM UDP 非阻塞接收处理 */
(void)u8_pcm_udp_recv_process();
}
+246
View File
@@ -0,0 +1,246 @@
#ifndef PCM_COMM_H
#define PCM_COMM_H
#ifdef __cplusplus
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "publicdata/publicdata.h"
/*------ Exported macro(宏定义) -------------------------*/
/* PCM UDP 网络参数 */
#define PCM_UDP_SRC_PORT 502 /* 本机(终端)UDP socket绑定的源端口 */
#define PCM_UDP_DST_PORT 502 /* 目标(主机)UDP端口 */
/* 终端IP地址基址:本机(终端)从192.168.1.100开始 */
#define PCM_TERM_IP_BASE0 192
#define PCM_TERM_IP_BASE1 168
#define PCM_TERM_IP_BASE2 1
#define PCM_TERM_IP_BASE3 100
/* 主机IP地址基址:1号主机从192.168.1.200开始 */
#define PCM_HOST_IP_BASE0 192
#define PCM_HOST_IP_BASE1 168
#define PCM_HOST_IP_BASE2 1
#define PCM_HOST_IP_BASE3 200
/* 每台主机占用的终端IP地址偏移量(10个地址) */
#define PCM_TERM_IP_OFFSET 10
/* 帧结构定义 */
#define PCM_FRAME_HEAD 0x5A99 /* 帧头 */
#define PCM_FRAME_TAIL 0xA5 /* 帧尾 */
/* 帧功能码 */
#define PCM_CMD_TERM_HEARTBEAT 0x10 /* 分桩心跳帧:终端->主机 */
#define PCM_CMD_HOST_HEARTBEAT 0x11 /* 主机心跳帧:主机->终端 */
#define PCM_CMD_TERM_REQUEST 0x12 /* 分桩请求帧:终端->主机 */
#define PCM_CMD_HOST_RESPONSE 0x13 /* 分桩请求应答帧:主机->终端 */
/* 帧最小长度:帧头(2) + 功能码(1) + 长度(1) + CRC(1) + 帧尾(1) = 6 */
#define PCM_FRAME_MIN_LEN 6
/* 帧最大数据长度 */
#define PCM_FRAME_MAX_DATA_LEN 128
/* 帧最大总长度 */
#define PCM_FRAME_MAX_LEN (PCM_FRAME_MIN_LEN + PCM_FRAME_MAX_DATA_LEN)
/* 通信周期(ms */
#define PCM_HEARTBEAT_PERIOD_MS 1000 /* 心跳周期 */
#define PCM_REQUEST_PERIOD_MS 200 /* 请求/应答周期 */
/* 通信超时时间(ms */
#define PCM_COMM_TIMEOUT_MS 5000 /* 5秒无通信判定超时 */
/* 终端枪工作状态 */
#define PCM_GUN_STATE_IDLE 0 /* 空闲 */
#define PCM_GUN_STATE_PLUG 1 /* 插枪 */
#define PCM_GUN_STATE_INSU_START 2 /* 绝缘启动 */
#define PCM_GUN_STATE_INSU_STOP 3 /* 绝缘停止 */
#define PCM_GUN_STATE_PRE_CHARGE 4 /* 预充启动 */
#define PCM_GUN_STATE_CHARGING 5 /* 充电中 */
#define PCM_GUN_STATE_CHARGE_STOP 6 /* 充电停止 */
#define PCM_GUN_STATE_CHARGE_STOPPED 7 /* 充电停止完成 */
/* 接触器状态 */
#define PCM_CONTACTOR_OPEN 0 /* 断开 */
#define PCM_CONTACTOR_CLOSE 1 /* 闭合 */
/* 模块启停状态 */
#define PCM_MODULE_STANDBY 0 /* 待机 */
#define PCM_MODULE_WORK 1 /* 工作 */
/* 故障状态 */
#define PCM_FAULT_NONE 0 /* 无故障 */
#define PCM_FAULT_EXIST 1 /* 故障 */
/*------ Exported struct(结构体定义) --------------------*/
/**
* @brief PCM 通用帧头结构
*/
typedef struct {
U16_T u16_head; /* 帧头:0x5A99 */
U8_T u8_cmd; /* 帧功能码 */
U8_T u8_len; /* 数据长度 */
}__attribute__((packed)) S_PCM_FRAME_HEAD;
/**
* @brief PCM 通用帧结构(完整帧)
*/
typedef struct {
U16_T u16_head; /* 帧头:0x5A99 */
U8_T u8_cmd; /* 帧功能码 */
U8_T u8_len; /* 数据长度 */
U8_T u8_data[PCM_FRAME_MAX_DATA_LEN]; /* 帧数据 */
U8_T u8_crc; /* CRC校验 */
U8_T u8_tail; /* 帧尾:0xA5 */
}__attribute__((packed)) S_PCM_FRAME;
/**
* @brief 单组终端枪数据(用于终端发送请求帧)
* 对应协议中:终端枪地址 + 工作状态 + 接触器状态x2 + 需求电压 + 需求电流 + 故障状态 + 故障原因
* 每组 11 字节
*/
typedef struct {
U8_T u8_gun_addr; /* 终端枪地址 */
U8_T u8_work_state; /* 终端枪工作状态 */
U8_T u8_contactor_dc; /* 直流输出接触器状态 */
U8_T u8_contactor_discharge; /* 直流输出泄放接触器状态 */
U16_T u16_need_vol; /* 需求电压 BE 0.1V */
U16_T u16_need_cur; /* 需求电流 BE 0.1A */
U8_T u8_fault_state; /* 终端枪故障状态 */
U16_T u16_fault_reason; /* 终端枪故障原因 BE */
}__attribute__((packed)) S_PCM_GUN_TERM_DATA;
/**
* @brief 单组主机应答数据(用于解析主机应答帧)
* 对应协议中:终端枪地址 + 模块启停 + 输出电压 + 输出电流 + 主机故障状态 + 主机故障原因
* 每组 9 字节
*/
typedef struct {
U8_T u8_gun_addr; /* 终端枪地址 */
U8_T u8_module_state; /* 模块启停状态 */
U16_T u16_out_vol; /* 输出电压 BE 0.1V */
U16_T u16_out_cur; /* 输出电流 BE 0.1A */
U8_T u8_host_fault; /* 主机故障状态 */
U16_T u16_host_fault_reason; /* 主机故障原因 BE */
}__attribute__((packed)) S_PCM_GUN_HOST_DATA;
/**
* @brief PCM 主机通信状态(收到的对端信息)
*/
typedef struct {
U32_T u32_host_ip; /* 主机IP地址(网络字节序) */
U16_T u16_host_port; /* 主机端口(网络字节序) */
/* 主机下发的应答数据 */
U8_T u8_gun_num; /* 主机回复的枪数量 */
S_PCM_GUN_HOST_DATA s_gun_data[GUN_MAX_CNT]; /* 各枪应答数据 */
/* 通信状态 */
U32_T u32_last_recv_time; /* 最后接收时间(ms */
U8_T u8_comm_ok; /* 通信状态:0超时 1正常 */
}__attribute__((packed)) S_PCM_HOST_STATE;
/**
* @brief PCM 终端通信数据
*/
typedef struct {
U8_T u8_term_no; /* 终端编号(1开始,对应主机编号) */
U32_T u32_term_ip; /* 本机(终端)IP地址(网络字节序) */
U16_T u16_term_port; /* 本机(终端)端口(网络字节序) */
/* 主机信息 */
U32_T u32_host_ip; /* 目标主机IP地址(网络字节序) */
U16_T u16_host_port; /* 目标主机端口(网络字节序) */
S_PCM_HOST_STATE s_host; /* 主机通信状态 */
/* 本机(终端)枪数据(待发送给主机) */
U8_T u8_gun_num; /* 本机终端枪数量 */
S_PCM_GUN_TERM_DATA s_gun_data[GUN_MAX_CNT]; /* 各枪数据 */
/* 定时器 */
U32_T u32_heartbeat_tick; /* 心跳发送计时 */
U32_T u32_request_tick; /* 请求发送计时 */
}__attribute__((packed)) S_PCM_COMM_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
/**
* @brief 初始化 PCM UDP 通信(终端角色)
*
* @param u8_term_no 终端编号(1开始,对应主机编号)
* @return S8_T 0成功 -1失败
*/
S8_T s8_pcm_udp_init(void);
/**
* @brief PCM UDP 周期处理函数
*
* @param u16_sleep_time_ms 任务周期时间(ms
*/
void v_pcm_udp_periodic_handler(U16_T u16_sleep_time_ms);
/**
* @brief PCM UDP 非阻塞接收处理
*
* @return U8_T 0无数据 1收到有效数据
*/
U8_T u8_pcm_udp_recv_process(void);
/**
* @brief PCM 通信主处理函数(由 mdu_comm_task 调用)
*
* @param u16_sleep_time_ms 任务周期时间(ms
*/
void v_pcm_comm_gw_action(U16_T u16_sleep_time_ms);
/**
* @brief 发送分桩心跳帧(功能码0x10)
*
* @return S8_T 0成功 -1失败
*/
S8_T s8_pcm_send_term_heartbeat(void);
/**
* @brief 发送分桩请求帧(功能码0x12)
*
* @return S8_T 0成功 -1失败
*/
S8_T s8_pcm_send_term_request(void);
/**
* @brief 获取主机通信状态
*
* @return U8_T 0超时 1正常
*/
U8_T u8_pcm_get_host_comm_status(void);
/**
* @brief 获取主机应答枪数据
*
* @param u8_gun_index 枪索引
* @return S_PCM_GUN_HOST_DATA* 主机应答数据指针,失败返回NULL
*/
S_PCM_GUN_HOST_DATA* ps_pcm_get_host_gun_data(U8_T u8_gun_index);
/**
* @brief 更新本机(终端)枪数据(由外部模块调用)
*
* @param u8_gun_index 枪索引
* @param ps_data 终端枪数据指针
*/
void v_pcm_update_term_gun_data(U8_T u8_gun_index, const S_PCM_GUN_TERM_DATA *ps_data);
#ifdef __cplusplus
}
#endif
#endif /* PCM_COMM_H */
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
#ifndef MDU_COMM_TASK_H
#define MDU_COMM_TASK_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "mdu_comm/mdu_interface.h"
#include "bsp_include.h"
#if MDU_UULN_EN
#include "mdu_comm/mdu_uuln_comm.h"
#endif
#include "mylog/mylog.h"
/*------ Exported macro(宏定义) -------------------------*/
#define MDU_CAN_PRINT_FLAG (0) //模块报文 调试打印标志
#define MODULE_BROADCAST_ADDR 0x9F //广播地址
#define MODULE_PILE_ADDR 0xA0 //主机地址
#define MODULE_MDU_FIRST_ADDR 0x80 //充电模块首地址
#define MODULE_CMD_HEARTBEAT 0x40 /* 模块心跳命令 */
#define MODULE_CMD_SETTING 0x80 /* 定值设置命令 */
#define MODULE_CMD_GETTING 0x82 /* 定值查询命令 */
#define MODULE_CMD_REMOTE_CONTROL 0x05 /* 遥控命令 */
#define MODULE_CMD_GROUP_SET 0x03 /* 组号设置命令 */
#define MODULE_CMD_HEARTBEAT_ACK 0x41 //心跳应答
#define MODULE_CMD_SETTING_ACK 0x81 //定值设置应答
#define MODULE_CMD_GETTING_ACK 0x83 //定值查询应答
#define MODULE_CMD_YXYC_20 0x20 //遥信遥测20帧
#define MODULE_CMD_YXYC_21 0x21 //遥信遥测21帧
#define MODULE_CMD_TEMP 0xE6 //温度查询帧
#define MODULE_TEMP_OFFSET (50) //模块温度偏移量,50
#define MODULE_ALL (0xFF) //模块接收数据 全部清空
/*------ Exported struct(结构体定义) --------------------*/
#define Mdu_Comm_LOG(fmt, ...) \
MYLOG_MSG(TASK_ID_MduComm,fmt, ##__VA_ARGS__) //任务打印
typedef enum {E_MDU_IDLE = 0, E_MDU_WORK = !E_MDU_IDLE} E_MDU_WORK_STATE;
//模块状态 枚举
typedef enum
{
E_MDU_STATE_NONE=0, //未通信
E_MDU_STATE_NORMAL, //正常
E_MDU_STATE_FAULT, //故障
}E_MDU_STATE;
//模块故障告警数据
typedef union
{
U8_T u8_mdu_alarm;
struct
{
U8_T b0_other:1; //其他故障,0正常,1故障
U8_T b1_relief:1; //泄放故障
U8_T b2_fan:1; //风扇故障
U8_T b3_DC_short:1; //直流输出短路
U8_T b4_overTemp:1; //过温
U8_T b5_DC_UV:1; //直流输出欠压
U8_T b6_DC_OV:1; //直流输出过压
U8_T b7_AC_IN:1; //交流输入故障
}bit_mdu_alarm;
}U_MDU_ALARM_DATA;
typedef union {
uint8_t raw; // 原始字节值
struct {
uint8_t operationCommand : 4; // Bit3~Bit0: 操作指令
/*0x01 --- 快速开机(绝缘检测阶段使用)
0x02 --- 停止充电(保留分组信息)
0x03 --- 软起开机(预启动阶段使用)
0x04 --- 显示地址(显示模块通信地址)
0x05 --- 参数修改
0x06 --- 停止充电(自动清除分组信息)
*/
uint8_t voltageRange : 1; // Bit4: 电压输出范围选择
uint8_t powerContactor : 1; // Bit5: 功率分配回路接触器状态
uint8_t mainContactor : 1; // Bit6: 充电回路主接触器状态
uint8_t reserved : 1; // Bit7: 预留
} bits;
}U_MDU_YK_DEVICE_STATUS;
typedef struct
{
U_MDU_YK_DEVICE_STATUS u_dev_state; //设备状态 包含功率分配回路接触器状态、充电回路主接触器状态等数据
U16_T u16_needVol; //需求电压 精度0.1
U16_T u16_oneMdu_needCur; //单模块需求电流 精度0.1
U16_T u16_battVol; //电池电压 精度0.1
}S_MDU_RemoteCtlrData;
typedef enum {
SEND_MODE_UNICAST, ///< 单点发送:命令发送给特定目标设备
SEND_MODE_BROADCAST ///< 广播发送:命令发送给所有设备
}E_CommandSendMode;
//充电模块接收状态 结构体
typedef struct
{
E_MDU_STATE e_state; //模块状态 未通讯、通信正常、故障
E_MDU_WORK_STATE e_work_state; //模块工作状态 空闲、工作
U8_T u8_groupNo; //模块组号
U8_T u8_GrpMode; //分组模式:【遥测分组模式:0固定分组,1动态分组】
U16_T u16_outVol; //输出电压 精度0.1
U16_T u16_outCur; //输出电流 精度0.1
U16_T u16_outtimecnt; //通信超时计数
U_MDU_ALARM_DATA u_alarm_data; //故障告警数据 0x20 遥信遥测帧上送
#if MDU_UULN_EN
U_UULN_MDU_STATUS u_uuln_state; //优优绿能模块状态保存
E_TrueHiLo_Status e_TrueHiLo_Status; //高低压状态读取 1: 1000V 2: 500V
#endif
//模块温度读取使能
#if( MDU_TEMP_READ_EN == 1)
S8_T s8_mdutemp_1; //模块温度1 进风口温度 单位摄氏度 注意有正负号 范围是 -127 ~ 127
S8_T s8_mduPara_2; //模块温度2 模块PFC温度 单位摄氏度 注意有正负号 范围是 -127 ~ 127
#endif
}S_MDU_STATE;
//充电模块控制命令 结构体
typedef struct
{
E_CMD e_mdu_start_flag; //模块启停状态控制 ON / OFF
U8_T u8_mdu_in_pile; //模块归属枪号 0未分配 1-N分配枪号
U8_T u8_swh_flag; //模块对应输出回路继电器 闭合标志
U8_T u8_mdu_fault_num; //模块故障次数 区分不充电正常,一充电就报错 直接就不能用得了-暂未使用
}S_MDU_CTRL;
typedef struct
{
U8_T u8_mdu_addr; //模块地址 0x80/0x60
S_MDU_CTRL s_ctrl; //模块控制
S_MDU_STATE s_state; //模块状态
}S_MDU_ONE_DATA; //单模块数据
typedef struct
{
U8_T u8_on_group_num; //模块组内模块数量 已分组模块数量
U8_T u8_work_state; //模块组工作状态 0空闲 1工作 --组内有一个模块输出 则是工作
U_MDU_YK_DEVICE_STATUS u_yk_dev_state; //遥控命令状态
U16_T u16_needVol; //需求电压 0.1V
U16_T u16_oneMdu_needCur; //单模块需求电流 0.1A 流程控制计算号后直接使用
U16_T u16_battVol; //电池电压 0.1V
U16_T u16_outVol; //模块组内输出电压0.1V-- 指已投入充电回路的模块
U16_T u16_outCur; //模块组内输出电流0.1A
}S_MDU_GROUP_DATA;
typedef struct
{
U8_T u8_mdu_type; //模块类型
U8_T u8_mdu_num; //模块数量
U8_T u8_mdu_group_num; //模块组数量-- 枪数量
U8_T u8_mdu_usable_num; //可用模块数量 所有通信正常 且无故障模块
U8_T u8_mdu_using_num; //在用模块的数量 所有以分配模块
U8_T u8_can_send_cnt; //can报文发送数量 用于报文发送延时
U16_T u16_timer; //周期计数值
U32_T u32_runing_time; //任务运行时间 刚插枪延时5秒再判断通信超时
U8_T u8_recv_most_pack[20]; //多包数据接收 最多2包,2*7=14个字节数据
//模块参数读取使能 -- 默认同一主机使用的模块参数一致 - 查询第一个可通信的模块参数
#if( MDU_PARA_READ_EN == 1)
U16_T u16_mduPara_MaxOutVol; //模块参数 最大输出电压 精度0.1
U16_T u16_mduPara_MinOutVol; //模块参数 最小输出电压 精度0.1
U16_T u16_mduPara_MaxOutCur; //模块参数 最大输出电流 精度0.1
U16_T u16_mduPara_MinOutCur; //模块参数 最小输出电流 精度0.1
#endif
S_MDU_ONE_DATA s_mdu[MDU_MAX_NUM]; //模块状态
S_MDU_GROUP_DATA s_mdu_group[GUN_MAX_CNT]; //模块组状态
}S_MDU_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_MduCanMsg_send(CAN_DATA* can_data);
U8_T u8_MduCanMsg_recv(CAN_DATA *rx_can_data);
void v_mdu_clear_recv_data(U8_T u8_id);
void v_mdu_data_summ(void);
/* STM32 状态 CAN 数据获取接口 */
U8_T u8_stm_status_get_gun_plug(U8_T u8_gunNo); // 0:A枪 1:B枪 返回0未插枪 1插枪
U8_T u8_stm_status_get_di(U8_T u8_index); // 0:DI1 1:DI2 返回0/1
void v_Mdu_Comm_task(void *argument);
#ifdef __cplusplus
}
#endif
#endif /* MDU_COMM_TASK_H */
+113
View File
@@ -0,0 +1,113 @@
# mdu_comm 模块说明
> 路径:`app/mdu_comm/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 实现 MDU 模块通信任务与报文处理;
- 维护模块在线/离线、通信状态与超时判断;
- 提供 MDU 对接系统数据的接口层;
- **可选**:当配置为群充「终端(分桩)」角色时,通过 **PCM UDP** 与主机交互(见 `PCM_comm.c`)。
## 2. 关键文件
- `mdu_comm_task.c/h`MDU 任务主循环;根据 `u8_mdu_type` 分支调用 CAN 网关逻辑或 PCM UDP 逻辑。
- `mdu_interface.c/h`MDU 数据与控制接口层。
- **`PCM_comm.c` / `PCM_comm.h`**:群充 **PCM 终端 ↔ 主机****UDP** 协议栈(lwIP `socket`/`sendto`/`recvfrom`),详见下文第 6 节。
## 3. 功能说明
- 处理模块 CAN 报文收发和任务周期调度;
- 对外暴露 MDU 数据读取与控制动作接口;
- 当前包含部分可配置/可屏蔽的系统取数接口(便于分阶段集成)。
## 4. 调度与对外接口
该模块由 FreeRTOS 任务 `MduComm` 周期运行(单位:ms),周期来自 `app/app_init/app_init.c`
- `MduComm``50ms`(任务入口:`v_Mdu_Comm_task(void *argument)`
任务内根据 **`s_mdu_data.u8_mdu_type`** 选择路径:
- **`0`**:充电模块 **CAN** 通信(`v_mdu_comm_gw_action` 等)。
- **`0x02`**PCM **UDP** 终端通信(`v_pcm_comm_gw_action`,实现位于 `PCM_comm.c`)。
对外接口(来自 `mdu_comm_task.h`):
- `void v_MduCanMsg_send(CAN_DATA* can_data)`:发送 MDU 报文(封装)
- `U8_T u8_MduCanMsg_recv(CAN_DATA *rx_can_data)`:接收 MDU 报文
- `void v_mdu_clear_recv_data(U8_T u8_id)`:清空指定接收缓存
- `void v_mdu_data_summ(void)`:汇总/刷新统计数据
并提供与 STM/PLC 相关的状态查询接口(根据配置启用):
- `U8_T u8_stm_status_get_gun_plug(U8_T u8_gunNo)`
- `U8_T u8_stm_status_get_di(U8_T u8_index)`
## 5. 维护建议
- 新增命令类型时建议先扩展接口层,再接入任务主循环;
- 保持超时与故障上报路径一致,避免重复告警;
- PCM 与 CAN 共用 `MduComm` 任务时,注意 **`u8_mdu_type`** 与网络初始化顺序(lwIP 就绪后再 `bind`)。
## 6. PCM 群充 UDP`PCM_comm.c` / `PCM_comm.h`
### 6.1 定位与引用协议
- **本机角色**:群充 **终端(分桩)**,通过 **UDP****主机** 通信。
- **代码注释约定**:协议细节可参考工程内 **`主机终端通信协议.md`**(与 `PCM_comm.c` 文件头说明一致)。
- **接入点**:由 `mdu_comm_task.c`**`u8_mdu_type == 0x02`** 时每周期调用 **`v_pcm_comm_gw_action(sleep_time_ms)`**;首次调用内完成 **`s8_pcm_udp_init()`**(失败则下周期重试)。
- **日志**:使用 `TASK_ID_MduComm`,宏 `PCM_COMM_LOG`
### 6.2 传输与地址
- **Socket**UDP`AF_INET`;本地 **`bind`** 端口 **`PCM_UDP_SRC_PORT`502**;发往主机 **`PCM_UDP_DST_PORT`502**。
- **本机 IP**:取自共享配置 **`g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip`**(写入 `S_PCM_COMM_DATA` 中的终端 IP 字段)。
- **主机 IP**:按终端 IP 末字节推算——**`192.168.1.(200 + ((终端末字节-100)/10)*10)`**(与 `PCM_HOST_IP_BASE3``PCM_TERM_IP_OFFSET` 等宏对应;用于群充多终端分段寻址)。
### 6.3 帧格式摘要
| 字段顺序 | 长度 | 说明 |
| --- | --- | --- |
| 帧头 | 2 B | 固定 **`0x5A 0x99`**`PCM_FRAME_HEAD` |
| 功能码 | 1 B | 见下表 |
| 数据长度 | 1 B | `N`,数据域字节数,`N ≤ 128` |
| 数据 | N B | 依功能码变化 |
| CRC | 1 B | **CRC8**,多项式 **`x^8+x^2+x+1`0x07**,计算范围为 **帧头+功能码+长度+数据**(与组帧顺序一致) |
| 帧尾 | 1 B | 固定 **`0xA5`** |
最小帧长 **6 字节**(无数据域时);接收路径校验帧头、帧尾、长度、`CRC` 后按功能码分发。
### 6.4 功能码与周期行为(终端侧)
| 功能码 | 方向 | 含义 |
| --- | --- | --- |
| `0x10` | 终端→主机 | **分桩心跳**:数据含枪数量 + 各枪终端地址(配置 `u8_terminal[]` |
| `0x11` | 主机→终端 | **主机心跳**:解析后刷新主机通信「存活」与时间戳 |
| `0x12` | 终端→主机 | **分桩请求**:按枪拼接多组 **11 字节** 数据(枪地址、流程状态、直流/泄放接触器、需求电压电流 BE、故障状态与原因 BE 等),数据来自 **`flow_interface`**、**`u8_get_gun_yx_data`**、**`u8_get_fault_sum` / `u16_flow_get_sys_data`** |
| `0x13` | 主机→终端 | **请求应答**:解析多组 **9 字节**(枪地址、模块启停、输出电压电流 BE、主机故障及原因 BE),写入 **`S_PCM_HOST_STATE`** |
**定时**(在 **`v_pcm_udp_periodic_handler`** 中累加任务周期 `sleep_time_ms`):
- **`PCM_HEARTBEAT_PERIOD_MS`1000 ms**:发送 **`0x10`**
- **`PCM_REQUEST_PERIOD_MS`200 ms**:发送 **`0x12`**
- **`PCM_COMM_TIMEOUT_MS`(5000 ms)**:若距上次有效接收超时,置 **`u8_comm_ok = 0`**(主机通信异常)。
接收:**`u8_pcm_udp_recv_process`** 使用 **`MSG_DONTWAIT`** 非阻塞 `recvfrom`,仅处理 **`0x11` / `0x13`**。
### 6.5 对外可查接口(节选)
- **`u8_pcm_get_host_comm_status()`**:主机链路是否正常(`0` 超时 / `1` 正常)。
- **`ps_pcm_get_host_gun_data(u8_gun_index)`**:最近一次 **`0x13`** 解析得到的某枪应答(索引非法或超出主机回复枪数则返回 `NULL`)。
- **`v_pcm_update_term_gun_data(...)`**:可选写入本地缓存的终端枪数据(当前 **`0x12`** 组帧仍以实时采集接口为准,该接口便于后续扩展)。
### 6.6 与 CAN MDU 路径的关系
- **互斥分支**`u8_mdu_type == 0` 走 CAN**`== 0x02`** 走 PCM UDP,二者在同一任务周期内不同时执行主通路。
- **喂狗、锁、睡眠**:仍由 `mdu_comm_task` 统一调度;PCM 仅作为分支内的网络协议处理。
## 7. 关联文档
- 主索引:[README](../../README.md)
- 群充 PCM`主机终端通信协议.md`(与 `PCM_comm.c` 注释引用一致;若文档路径调整请在工程中同步)
+553
View File
@@ -0,0 +1,553 @@
#include "mdu_comm/mdu_interface.h"
#include "mdu_comm/mdu_comm_task.h"
#include "publicdata/publicdata.h"
#include "fault_cheak/fault_interface.h"
#include "collect_ctrl/collect_task.h"
#include "flowctrl/flow_interface.h"
#include "mdu_comm/PCM_comm.h"
extern S_MDU_DATA s_mdu_data;
extern S_PCM_COMM_DATA s_pcm_comm;
#define TEST_MDU_CTRL_FLAG (0) //模块控制 测试标志
/* 0: 屏蔽系统取数(流程/采样/故障联动), 1: 使能系统取数 */
#define MDU_SYS_FETCH_ENABLE (1)
#if TEST_MDU_CTRL_FLAG
typedef struct
{
U8_T u8_start_flag; //模块任务执行标志 0 不执行 1执行
U8_T u8_cmd; //模块控制命令 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
U16_T u16_need_vol; //单模块需求电压 0.1V
U16_T u16_one_mdu_need_cur; //单模块需求电压 0.1V
}TEST_MDU_TRL_DATA;
TEST_MDU_TRL_DATA test_mdu_ctrl[GUN_MAX_CNT]; //按枪控制
#endif
#if 1 //外部参数获取
/**
* ************************************************************************
* @brief - 流程控制任务 对外输出数据
* 0 空闲 1插枪 2绝缘启动 3绝缘停止 4预充启动 5充电中 6充电停止 7充电停止完成
* @param[in] u8_gunNo - 枪号 0-N 对于不分区枪号的数据,无意义
*
* @return U8_T - 数据返回值
*
* @author jiankalka
* @date 2025-09-03
* ************************************************************************
*/
U8_T u8_mdu_put_data(U8_T u8_gunNo)
{
U8_T u8_ret = 0;
U8_T u8_gunlink = u8_get_gun_yx_data(YX_GUN_LINK,u8_gunNo);
U16_T u16_gun_real_state = u16_flow_put_data(u8_gunNo, E_FLOW_PUT_DATA_GUN_REAL_STATE, 0);
if(u8_gunlink == 0 && u16_gun_real_state == 0)
{
u8_ret = 0; //枪未连接且状态为0空闲
}
else if(u8_gunlink == 1 && (u16_gun_real_state == 0 || u16_gun_real_state == 1))
{
u8_ret = 1; //枪已连接且状态为0空闲
}
else if(u8_gunlink == 1 && u16_gun_real_state == 2 )
{
u8_ret = 2; //枪已连接且状态为2绝缘启动
}
else if(u8_gunlink == 1 && (u16_gun_real_state == 4 || u16_gun_real_state == 3 || u16_gun_real_state == 8))
{
u8_ret = 3; //枪已连接且状态为4绝缘检测模块停止
}
else if(u8_gunlink == 1 && (u16_gun_real_state == 5 || u16_gun_real_state == 6))
{
u8_ret = 4; //枪已连接且状态为5开始预充
}
else if(u8_gunlink == 1 && u16_gun_real_state == 7)
{
u8_ret = 5; //枪已连接且状态为7充电中
}
else if(u8_gunlink == 1 && u16_gun_real_state == 9 )
{
u8_ret = 6; //枪已连接且状态为9充电停止
}
else if(u8_gunlink == 1 && u16_gun_real_state == 10)
{
u8_ret = 7; //枪已连接且状态为10充电停止完成
}
return u8_ret;
}
/**
* ************************************************************************
* @brief -获取充电枪 充电需求
*
* @param[in] u8_id -id号 当作为枪号时,0-N; 当作为模块地址号时,0-N
* @param[in] type - 数据类型
* E_MDU_GET_DATA_GUN_CMD = 0, //枪命令状态 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
E_MDU_GET_DATA_MAIN_KY, //主回路接触器状态
E_MDU_GET_DATA_POWER_KY, //功率回路接触器状
E_MDU_GET_DATA_NEED_VOL, //需求电压 精度 0.1
E_MDU_GET_DATA_NEED_CUR, //需求电流 精度 0.1
E_MDU_GET_DATA_BAAT_VOL, //电池电压 精度 0.1
* @return U16_T - 对应数据值
*
* @author jiankalka
* @date 2025-07-04
* ************************************************************************
*/
U16_T u16_mdu_get_charge_need(U8_T u8_id, E_MDU_GET_DATA_TYPE type)
{
U16_T u16_res = 0;
switch (type)
{
case E_MDU_GET_DATA_GUN_CMD: //枪命令状态 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = u16_flow_put_data(u8_id , E_FLOW_PUT_DATA_MDU_YK_CMD,0); //模块遥控命令 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
#else
u16_res = 0;
#endif
#if TEST_MDU_CTRL_FLAG
u16_res = test_mdu_ctrl[u8_id].u8_cmd;
#endif
break;
}
case E_MDU_GET_DATA_MAIN_KY: //主回路接触器状态
{
u16_res = 1;
break;
}
case E_MDU_GET_DATA_POWER_KY: //功率回路接触器状
{
u16_res = 1;
break;
}
case E_MDU_GET_DATA_NEED_VOL: //需求电压 精度 0.1
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = u16_flow_put_data(u8_id , E_FLOW_PUT_DATA_MDU_NEED_VOL,0); //需求电压 精度 0.1
#else
u16_res = 0;
#endif
#if TEST_MDU_CTRL_FLAG
u16_res = test_mdu_ctrl[u8_id].u16_need_vol;
#endif
break;
}
case E_MDU_GET_DATA_NEED_CUR_ONE: //单模块需求电流 精度 0.1 -- 流程控制计算好
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = u16_flow_put_data(u8_id , E_FLOW_PUT_DATA_MDU_ONE_NEED_CUR,0); //单模块需求电流值 精度0.1
#else
u16_res = 0;
#endif
#if TEST_MDU_CTRL_FLAG
u16_res = test_mdu_ctrl[u8_id].u16_one_mdu_need_cur;
#endif
break;
}
case E_MDU_GET_DATA_BAAT_VOL: //电池电压 精度 0.1
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = (U16_T)((f32_get_gun_yc_data(ADC_GUN_VOLT_PLUS,u8_id) + f32_get_gun_yc_data(ADC_GUN_VOLT_MINUS,u8_id))*10);
#else
u16_res = 0;
#endif
#if TEST_MDU_CTRL_FLAG
u16_res = test_mdu_ctrl[u8_id].u16_need_vol;
#endif
break;
}
case E_MDU_GET_DATA_START_FALG: //模块启停标志
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = u16_flow_put_data(u8_id ,E_FLOW_PUT_DATA_MDU_STAR_FLAG, 0);
#else
u16_res = 0;
#endif
break;
}
case E_MDU_GET_DATA_SHW_ON_FLAG: //模块对应开关闭合标志
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = 1;
// u16_res = u16_flow_put_data(u8_id , E_FLOW_PUT_DATA_MDU_SHW_ON_FLAG, 0);
#else
u16_res = 1;
#endif
break;
}
case E_MDU_GET_DATA_IN_PILE_ID: //模块归属终端号
{
if(g_t_share_data.t_sys_fix_cfg.u8_gunCnt == 1 ||
u8_id < ((s_mdu_data.u8_mdu_num+1) / 2)) // 前半部分为1枪 后半部分为2枪 模块数量为奇数时,1枪多一个
u16_res = 1;
else
u16_res = 2;
break;
}
case E_MDU_GET_DATA_MDU_NUM: //充电模块数量
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = g_t_share_data.t_sys_fix_cfg.u8_mdu_num;
#else
u16_res = s_mdu_data.u8_mdu_num;
#endif
break;
}
case E_MDU_GET_DATA_GUN_NUM: //充电枪数量
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = g_t_share_data.t_sys_fix_cfg.u8_gunCnt;
#else
u16_res = s_mdu_data.u8_mdu_group_num;
#endif
break;
}
case E_MDU_GET_DATA_RUNING_FLAG: //模块任务运行标志 0模块任务不运行并清空模块数据 1模块任务运行-执行报文收发和超时判断
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = u16_flow_put_data(u8_id , E_FLOW_PUT_DATA_MDU_STAR_FLAG,0); //模块运行开始标志 0未开始 1开始
#else
u16_res = 0;
#endif
#if TEST_MDU_CTRL_FLAG
u16_res = test_mdu_ctrl[u8_id].u8_start_flag;
#endif
break;
}
case E_MDU_GET_DATA_MDU_TYPE:
{
#if (MDU_SYS_FETCH_ENABLE == 1)
u16_res = g_t_share_data.t_sys_fix_cfg.u8_MduType;
#else
u16_res = s_mdu_data.u8_mdu_type;
#endif
break;
}
default:
break;
}
return u16_res;
}
#endif
#if 1 //对外数据提供
/**
* ************************************************************************
* @brief - 获取模块组内运行模块的数量 模块工作状态为 工作
*
* @param[in] u8_gunNo - 抢号序号 0-N
* @param[in] type - type 数据类型 0归属本枪模块数量 1归属本枪且正在运行中模块数量
*
* @return U8_T -
*
* @author jiankalka
* @date 2025-08-28
* ************************************************************************
*/
static U8_T u8_mdu_swh__get_runing_num(U8_T u8_gunNo ,U8_T type)
{
U8_T i;
U8_T u8_inPile_num = 0 , u8_runing_num = 0;
for ( i = 0; i < s_mdu_data.u8_mdu_num; i++) //遍历所有模块
{
if(s_mdu_data.s_mdu[i].s_ctrl.u8_mdu_in_pile == u8_gunNo+1)
{
u8_inPile_num ++;
if(s_mdu_data.s_mdu[i].s_state.e_work_state == E_MDU_WORK)
u8_runing_num ++;
}
}
if(type == 0)
return u8_inPile_num;
else
return u8_runing_num;
}
/**
* ************************************************************************
* @brief - 获取模块温度值
* 模块采集两个温度,进风口温度,内部元器件温度;咱们之前PT1000采集的是出风口温度。
* 之前不调速的风机方案:出风口温度取值为各个模块进风口温度与内部元器件温度的平均值的最大值。
*
* @return U16_T - 温度最大值
*
* @author jiankalka
* @date 2025-09-16
* ************************************************************************
*/
static U16_T u16_mdu_get_temp()
{
U8_T i;
S8_T s8_temp_1,s8_temp_2,s8_temp_3,s8_temp_max = 0;
for (i = 0; i < s_mdu_data.u8_mdu_num; i++) //遍历所有模块
{
if(s_mdu_data.s_mdu[i].s_state.e_state == E_MDU_STATE_NORMAL) //模块状态正常
{
s8_temp_1 = s_mdu_data.s_mdu[i].s_state.s8_mdutemp_1;
s8_temp_2 = s_mdu_data.s_mdu[i].s_state.s8_mduPara_2;
if(s8_temp_1 != 0 && s8_temp_2 != 0) //数据不为0
{
s8_temp_3 = (s8_temp_1+s8_temp_2)/2;
s8_temp_max = (s8_temp_max > s8_temp_3)?s8_temp_max:s8_temp_3; //获取最大值
}
}
}
if(s8_temp_max < 0) s8_temp_max = 0; //最小为0
return s8_temp_max;
}
U16_T u16_mdu_put_data(U8_T u8_id , E_MDU_PUT_DATA_TYPE e_type)
{
U16_T u16_res;
// 根据桩类型判断通讯方式:一体机(0/1)使用CAN模块通讯,分体机(2/3)使用PCM通讯
if(u16_flow_get_sys_data(0, E_FLOW_GET_DATA_PILE_TYPE,0) < 2)
{
switch (e_type)
{
case E_MDU_PUT_DATA_usable_num: //可用模块数量
{
u16_res = s_mdu_data.u8_mdu_usable_num;
break;
}
case E_MDU_PUT_DATA_using_num: //在用模块的数量
{
u16_res = s_mdu_data.u8_mdu_using_num;
break;
}
case E_MDU_PUT_DATA_ID_commFlag: //模块通信状态 0 未通信 1通信
{
u16_res = (s_mdu_data.s_mdu[u8_id].s_state.e_state == E_MDU_STATE_NONE)?0:1;
break;
}
case E_MDU_PUT_DATA_ID_workSate: //模块工作状态 0空闲 1工作
{
u16_res = s_mdu_data.s_mdu[u8_id].s_state.e_work_state;
break;
}
case E_MDU_PUT_DATA_ID_faultFlag: //模块故障标志 0正常 1故障
{
u16_res = (s_mdu_data.s_mdu[u8_id].s_state.e_state == E_MDU_STATE_FAULT)?1:0;
break;
}
case E_MDU_PUT_DATA_ID_gropNum: //模块组号 0-N
{
u16_res = s_mdu_data.s_mdu[u8_id].s_state.u8_groupNo;
break;
}
case E_MDU_PUT_DATA_ID_putVol: //模块输出电压 精度 0.1V
{
u16_res = s_mdu_data.s_mdu[u8_id].s_state.u16_outVol;
break;
}
case E_MDU_PUT_DATA_ID_putCur: //模块输出电流 精度 0.1A
{
u16_res = s_mdu_data.s_mdu[u8_id].s_state.u16_outCur;
break;
}
case E_MDU_PUT_DATA_GROUP_WORK_STATE:
{
u16_res = s_mdu_data.s_mdu_group[u8_id].u8_work_state; //模块组工作状态 0空闲 1工作
break;
}
case E_MDU_PUT_DATA_GROUP_putVol: //模块组输出电压 精度0.1V
{
u16_res = s_mdu_data.s_mdu_group[u8_id].u16_outVol;
break;
}
case E_MDU_PUT_DATA_GROUP_putCur: //模块组输出电流 精度0.1A
{
u16_res = s_mdu_data.s_mdu_group[u8_id].u16_outCur;
break;
}
case E_MDU_PUT_DATA_GROUP_mduNum: //模块组分配模块数量
{
u16_res = s_mdu_data.s_mdu_group[u8_id].u8_on_group_num;
break;
}
case E_MDU_PUT_DATA_GROUP_RUNING_mduNum: //模块组运行模块数量
{
u16_res = u8_mdu_swh__get_runing_num(u8_id,1);
break;
}
case E_MDU_PUT_DATA_MDU_TEMP: //获取模块上送温度值- 单位 度,范围0-100,注意最低为0度
{
u16_res = u16_mdu_get_temp();
break;
}
default:
break;
}
}
else // 分体机:使用网线PCM通讯
{
switch (e_type)
{
case E_MDU_PUT_DATA_usable_num: //可用模块数量
{
u16_res = s_mdu_data.u8_mdu_usable_num;
break;
}
case E_MDU_PUT_DATA_using_num: //在用模块的数量
{
u16_res = s_mdu_data.u8_mdu_using_num;
break;
}
case E_MDU_PUT_DATA_ID_commFlag: //模块通信状态 0 未通信 1通信
{
u16_res = (s_mdu_data.s_mdu[u8_id].s_state.e_state == E_MDU_STATE_NONE)?0:1;
break;
}
case E_MDU_PUT_DATA_ID_workSate: //模块工作状态 0空闲 1工作
{
u16_res = s_pcm_comm.s_host.s_gun_data[u8_id].u8_module_state;
break;
}
case E_MDU_PUT_DATA_ID_faultFlag: //模块故障标志 0正常 1故障
{
u16_res = (s_mdu_data.s_mdu[u8_id].s_state.e_state == E_MDU_STATE_FAULT)?1:0;
break;
}
case E_MDU_PUT_DATA_ID_gropNum: //模块组号 0-N
{
u16_res = s_mdu_data.s_mdu[u8_id].s_state.u8_groupNo;
break;
}
case E_MDU_PUT_DATA_ID_putVol: //模块输出电压 精度 0.1V
{
u16_res = s_pcm_comm.s_host.s_gun_data[u8_id].u16_out_vol;
break;
}
case E_MDU_PUT_DATA_ID_putCur: //模块输出电流 精度 0.1A
{
u16_res = s_pcm_comm.s_host.s_gun_data[u8_id].u16_out_cur;
break;
}
case E_MDU_PUT_DATA_GROUP_WORK_STATE:
{
u16_res = s_pcm_comm.s_host.s_gun_data[u8_id].u8_module_state; //模块组工作状态 0空闲 1工作
break;
}
case E_MDU_PUT_DATA_GROUP_putVol: //模块组输出电压 精度0.1V
{
u16_res = s_pcm_comm.s_host.s_gun_data[u8_id].u16_out_vol;
break;
}
case E_MDU_PUT_DATA_GROUP_putCur: //模块组输出电流 精度0.1A
{
u16_res = s_pcm_comm.s_host.s_gun_data[u8_id].u16_out_cur;
break;
}
case E_MDU_PUT_DATA_GROUP_mduNum: //模块组分配模块数量
{
u16_res = s_mdu_data.s_mdu_group[u8_id].u8_on_group_num;
break;
}
case E_MDU_PUT_DATA_GROUP_RUNING_mduNum: //模块组运行模块数量
{
u16_res = u8_mdu_swh__get_runing_num(u8_id,1);
break;
}
case E_MDU_PUT_DATA_MDU_TEMP: //获取模块上送温度值- 单位 度,范围0-100,注意最低为0度
{
u16_res = u16_mdu_get_temp();
break;
}
default:
break;
}
}
return u16_res;
}
#endif
#if 1 //模块故障告警控制
/**
* ************************************************************************
* @brief - 模块通信故障 设置
*
* @param[in] u8_id - 模块地址 0-N ,单模块通信超时 为告警; 值为0xFF时 表示所有模块通信超时,为故障
* @param[in] u8_flag - 0 回复 1超时
*
*
* @author jiankalka
* @date 2025-09-17
* ************************************************************************
*/
void v_mdu_set_comm_fault_info(U8_T u8_id,U8_T u8_flag)
{
#if (MDU_SYS_FETCH_ENABLE == 1)
U8_T u8_fault_flag;
E_FAULT_SITE e_fite;
E_FAULT_TYPE e_type;
if(u32_safe_seconds_since(s_mdu_data.u32_runing_time) < NUM_10) //模块上电10秒内 不进行故障判断
return;
if(u8_id == MODULE_ALL) //所有模块通信故障 动作
{
e_fite = E_WHOLE_HOST; //故障位置为整机
e_type = E_FAULT; //故障
}
else //单个模块 故障动作
{
e_fite = (E_FAULT_SITE)(E_CHAR_MDU+u8_id); //故障位置为模块
e_type = E_WARN; //告警
}
u8_fault_flag = u8_cheak_fault_flag(E_FAULT_14019,e_fite); //获取故障标志
if(u8_fault_flag == 0 && u8_flag == 1)
v_set_fault_data(e_fite,e_type,E_FAULT_14019,0,0,0);
else if(u8_fault_flag != 0 && u8_flag == 0)
v_set_fault_data(e_fite,E_RES,E_FAULT_14019,0,0,0);
#else
(void)u8_id;
(void)u8_flag;
#endif
}
/**
* ************************************************************************
* @brief - 模块故障告警动作
*
* @param[in] u8_id - 模块地址 0-N ,单模块通信超时 为告警; 值为0xFF时 表示所有模块通信超时,为故障
* @param[in] u8_flag - 0 回复 1超时
* @param[in] u_alarm_data - 故障信息
*
*
* @author jiankalka
* @date 2025-09-17
* ************************************************************************
*/
void v_mdu_set_alarm_info(U8_T u8_id,U8_T u8_flag, U_MDU_ALARM_DATA *u_alarm_data)
{
}
#endif
+74
View File
@@ -0,0 +1,74 @@
#ifndef MDU_INTERFACE_H
#define MDU_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "publicdata/publicdata.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
E_MDU_GET_DATA_GUN_CMD = 0, //枪命令状态 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
E_MDU_GET_DATA_MAIN_KY, //主回路接触器状态
E_MDU_GET_DATA_POWER_KY, //功率回路接触器状
E_MDU_GET_DATA_NEED_VOL, //需求电压 精度 0.1
E_MDU_GET_DATA_NEED_CUR_ONE, //单模块需求电流 精度 0.1 -- 流程控制计算好
E_MDU_GET_DATA_BAAT_VOL, //电池电压 精度 0.1
E_MDU_GET_DATA_START_FALG, //模块启停标志
E_MDU_GET_DATA_SHW_ON_FLAG, //模块对应开关闭合标志
E_MDU_GET_DATA_IN_PILE_ID, //模块归属终端号
E_MDU_GET_DATA_MDU_NUM, //充电模块数量
E_MDU_GET_DATA_GUN_NUM, //充电枪数量
E_MDU_GET_DATA_MDU_TYPE, //模块类型 0 三统一 1优优绿能
E_MDU_GET_DATA_RUNING_FLAG, //模块任务运行标志 0模块任务不运行并清空模块数据 1模块任务运行
}E_MDU_GET_DATA_TYPE; //枪需求数据 类型
typedef enum
{
E_MDU_PUT_DATA_usable_num=0, //可用模块数量
E_MDU_PUT_DATA_using_num, //在用模块的数量
E_MDU_PUT_DATA_ID_commFlag, //模块通信状态 0 未通信 1通信
E_MDU_PUT_DATA_ID_workSate, //模块工作状态 0空闲 1工作
E_MDU_PUT_DATA_ID_faultFlag, //模块故障标志 0正常 1故障
E_MDU_PUT_DATA_ID_gropNum, //模块组号 0-N
E_MDU_PUT_DATA_ID_putVol, //模块输出电压 精度 0.1V
E_MDU_PUT_DATA_ID_putCur, //模块输出电流 精度 0.1A
E_MDU_PUT_DATA_GROUP_WORK_STATE, //模块组工作状态 0空闲 1工作
E_MDU_PUT_DATA_GROUP_putVol, //模块组输出电压 精度0.1V
E_MDU_PUT_DATA_GROUP_putCur, //模块组输出电流 精度0.1A
E_MDU_PUT_DATA_GROUP_mduNum, //模块组分配模块数量 已分组模块数量
E_MDU_PUT_DATA_GROUP_RUNING_mduNum, //模块组运行模块数量
E_MDU_PUT_DATA_MDU_TEMP, //获取模块上送温度值- 单位 度,范围0-100,注意最低为0度
}E_MDU_PUT_DATA_TYPE; //模块信息对外输出数据类型
//1快速开机(绝缘检测阶段使用) 2停止充电(保留分组信息) 3软启开机(预启动阶段使用) 4显示地址(显示模块通信地址) 5参数修改 6停止充电(自动清除分组信息)
typedef enum //遥控命令
{
MDU_YK_FASTBOOT=1, //快速开机(绝缘检测阶段使用)
MDU_YK_STOP_SUSPEND, //停止充电(保留分组信息)
MDU_YK_SOFTSTART, //软启开机(预启动阶段使用)
MDU_YK_SHOWADDR, //显示地址(显示模块通信地址)
MDU_YK_SETPARA, //参数修改
MDU_YK_STOP, //停止充电(自动清除分组信息)
}E_MDU_YK_TYPE;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
U16_T u16_mdu_get_charge_need(U8_T gunNo, E_MDU_GET_DATA_TYPE type);
U16_T u16_mdu_put_data(U8_T u8_id , E_MDU_PUT_DATA_TYPE e_type);
U8_T u8_mdu_put_data(U8_T u8_gunNo);
void v_mdu_set_comm_fault_info(U8_T u8_id,U8_T u8_flag);
#ifdef __cplusplus
}
#endif
#endif /* MDU_INTERFACE_H */
+125
View File
@@ -0,0 +1,125 @@
#include "mdu_comm/mdu_stm_status_comm.h"
#include "mdu_comm/mdu_comm_task.h"
#include "collect_ctrl/yk_ctrl.h"
#if (GUN_LINK_COMM_EN)
/* STM32072 板状态 CAN 帧:扩展帧 ID 0x1801AABB
* data[0] = A枪插枪(0=未插枪 1=插枪)
* data[1] = B枪插枪
* data[2] = DI1
* data[3] = DI2
*/
#define STM_STATUS_CAN_ID ((U32_T)0x1801AABB)
/* STM32072 板控制 CAN 帧:扩展帧 ID 0x1802BBAA
* data[0] = DO0(本板 DO1,对应辅源1
* data[1] = DO1(本板 DO2,对应辅源2
*/
#define STM_CTRL_CAN_ID ((U32_T)0x1802BBAA)
typedef struct
{
U8_T u8_gun_plug[2]; // 0:A枪 1:B枪
U8_T u8_di[2]; // 0:DI1 1:DI2
U8_T u8_aux[2]; // 0:AUX_A 1:AUX_B
} S_STM_STATUS_CAN;
static S_STM_STATUS_CAN s_stm_status_can;
U8_T u8_stm_status_can_rx(CAN_DATA *rxMsg)
{
if (rxMsg->ID != STM_STATUS_CAN_ID)
{
return 0;
}
s_stm_status_can.u8_gun_plug[0] = (rxMsg->Data[0] ? 1U : 0U);
s_stm_status_can.u8_gun_plug[1] = (rxMsg->Data[1] ? 1U : 0U);
s_stm_status_can.u8_di[0] = (rxMsg->Data[2] ? 1U : 0U);
s_stm_status_can.u8_di[1] = (rxMsg->Data[3] ? 1U : 0U);
s_stm_status_can.u8_aux[0] = (rxMsg->Data[4] ? 1U : 0U); // AUX_PADET_A
s_stm_status_can.u8_aux[1] = (rxMsg->Data[5] ? 1U : 0U); // AUX_PADET_B
return 1;
}
void v_stm_status_can_send_aux_frame(void)
{
CAN_DATA can_data;
memset(&can_data , 0 , sizeof(CAN_DATA));
can_data.ID = STM_CTRL_CAN_ID; /* 与 STM32 侧 APP_CTRL_CAN_ID 保持一致 */
can_data.Len = 8;
/* 辅源1/2 控制:E_CMD 为 0/1,直接作为 DO0/DO1 的控制位 */
can_data.Data[0] = (U8_T)e_yk_get_ctrl_cmd(E_KY_TYPE_AUX_POWER1);
can_data.Data[1] = (U8_T)e_yk_get_ctrl_cmd(E_KY_TYPE_AUX_POWER2);
v_MduCanMsg_send(&can_data);
}
U8_T u8_stm_status_get_gun_plug(U8_T u8_gunNo)
{
if (u8_gunNo < 2)
{
return s_stm_status_can.u8_gun_plug[u8_gunNo];
}
return 0;
}
U8_T u8_stm_status_get_di(U8_T u8_index)
{
if (u8_index < 2)
{
return s_stm_status_can.u8_di[u8_index];
}
return 0;
}
U8_T u8_stm_status_get_aux(U8_T u8_index)
{
if (u8_index < 2)
{
return s_stm_status_can.u8_aux[u8_index];
}
return 0;
}
// 判断是否有国标枪,若有则启用国标相关功能
U8_T u8_stm_gb_enable(void)
{
return (g_t_share_data.t_sys_fix_cfg.u8_gun_type[0] == GUN_TYPE_GB ||
g_t_share_data.t_sys_fix_cfg.u8_gun_type[1] == GUN_TYPE_GB);
}
#else /* GUN_LINK_COMM_EN == 0 */
U8_T u8_stm_status_can_rx(CAN_DATA *rxMsg)
{
(void)rxMsg;
return 0;
}
void v_stm_status_can_send_aux_frame(void)
{
}
U8_T u8_stm_status_get_gun_plug(U8_T u8_gunNo)
{
(void)u8_gunNo;
return 0;
}
U8_T u8_stm_status_get_di(U8_T u8_index)
{
(void)u8_index;
return 0;
}
// 判断是否有国标枪,若有则启用国标相关功能
U8_T u8_stm_gb_enable(void)
{
return 0;
}
#endif /* GUN_LINK_COMM_EN */
+57
View File
@@ -0,0 +1,57 @@
#ifndef MDU_STM_STATUS_COMM_H
#define MDU_STM_STATUS_COMM_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "bsp_include.h"
/*------ Exported function prototypes (函数声明) -------*/
/**
* @brief 解析 STM32072 状态帧(ID 0x1801AABB
* @param rxMsg - 接收的 CAN 帧
* @return U8_T - 1:已处理该帧并更新内部状态 0:非本模块帧
*/
U8_T u8_stm_status_can_rx(CAN_DATA *rxMsg);
/**
* @brief 发送辅源/DO 控制帧(ID 0x1802BBAA)到 STM32072
*/
void v_stm_status_can_send_aux_frame(void);
/**
* @brief 获取 STM32 状态 CAN 中的枪插枪状态
* @param u8_gunNo - 枪号 0:A枪 1:B枪
* @return U8_T - 0未插枪 1插枪
*/
U8_T u8_stm_status_get_gun_plug(U8_T u8_gunNo);
/**
* @brief 获取 STM32 状态 CAN 中的 DI 状态
* @param u8_index - 0:DI1 1:DI2
* @return U8_T - 0/1
*/
U8_T u8_stm_status_get_di(U8_T u8_index);
/**
* @brief 获取 STM32 状态 CAN 中的 AUX 状态
* @param u8_index - 0:AUX_A 1:AUX_B
* @return U8_T - 0/1
*/
U8_T u8_stm_status_get_aux(U8_T u8_index);
/**
* @brief 判断是否启用国标相关功能(是否有国标枪)
* @return U8_T - 0:不启用 1:启用
*/
U8_T u8_stm_gb_enable(void);
#ifdef __cplusplus
}
#endif
#endif /* MDU_STM_STATUS_COMM_H */
+801
View File
@@ -0,0 +1,801 @@
#include "mdu_comm/mdu_uuln_comm.h"
#if MDU_UULN_EN
#include "mdu_comm/mdu_interface.h"
#include "mdu_comm/mdu_comm_task.h"
#include "mdu_comm/mdu_stm_status_comm.h"
#include "bsp_include.h"
extern S_MDU_DATA s_mdu_data;
/*
本协议使用优优绿能自定义协议,此协议与常规的CAN协议内容理解不一样 现做以下说明:
ID 域报文格式定义如下:
| 28:25 | 24:21 | 20:14 | 13:9 | 8:0 |
|----------------|---------------------------|---------------------------|-------------------|-----------------------|
| Protocol | MonitorAddress | ModuleAddress | ProductionDay | SerialNumberLowPart |
| (协议号) | (监控地址) | (模块地址) | (生产日期) | (序列号低位) |
|----------------|---------------------------|---------------------------|-------------------|-----------------------|
| 0x01 | Monitor - (0x01~0x0F) | Module - (0x01~0x7F) | 1~31 (可选) | 0 (可选) |
| | BroadCast - 0 | BroadCast - 0 | | |
详细内容说明详见协议文档,或者下方变量定义 U_UULN_CAN_ID_TYPE。
DATA 域规定内容如下:
字节0 : 字节 0 数据由两部分组成,高 4 位为组地址,低 4 位为信息类型; 变量定义为 U_UULN_DATA1_TYPE
字节1 : 命令信息类型。表示数据信息对应的命令类型。 变量定义为 E_UULN_CMD_TYPE
字节2 预留
字节3 预留
字节4 - 7 : 数据部分 详看协议文档
控制流程关键点(与厂家技术人员沟通所得):
1、正常充电,根据电池电压来设置模块高低压模式,然后设置需求电压,需求电流,开机
2、500v是切换点,如果跨过500v,先关机,切换高低压模式,然后设置电压电流开机
3、绝缘检测是根据绝缘电压先设定模块高低压模式,然后设置电压开机
*/
#if 1 //发送组帧
/**
* ************************************************************************
* @brief - 读取所有模块的状态标志位
*
* @param[in] u8_grp_on - 读取组号 0为广播 1-2
* @param[in] e_cmd_type - 读取数据类型 详看枚举 E_UULN_CMD_TYPE
*
* send 2200000, 22 08 00 00 00 00 00 00
* recv 2209c2e, 23 08 00 00 00 00 00 00
* recv 2205c1c, 23 08 00 00 00 00 00 00
*
* @author jiankalka
* @date 2025-11-13
* ************************************************************************
*/
static void v_uuln_send_read_all_data(U8_T u8_grp_on , E_UULN_CMD_TYPE e_cmd_type)
{
CAN_DATA can_data;
U_UULN_CAN_ID_TYPE u_ID;
U_UULN_DATA1_TYPE u_Data1;
memset(&can_data , 0 , sizeof(CAN_DATA));
//帧ID赋值
u_ID.u32_ID = 0;
u_ID.bit.Protocol = D_MDU_UULN_VER ; // 协议号 0x01 表示整流模块与监控之间的通讯协议号
u_ID.bit.MonitorAddress = 1 ; // 监控地址 地址设定在 0x010x0F,默认 0x01 0x00:代表广播通信
u_ID.bit.ModuleAddres = 0 ; // 整流模块地址 为整流模块地址,地址确定在 0x01-0x7F。 0x00:代表广播通信
u_ID.bit.ProductionDay = 0 ; // 产品生产日期(01-31)。
u_ID.bit.SerialNumberLowPart = 0 ; // 产品序列号低位(默认填 0)。
//DATA1 赋值 包括组地址和信息类型
u_Data1.u8_data = 0;
u_Data1.bit.GroupAddress = u8_grp_on; // 0 广播
u_Data1.bit.MessageType = E_MAG_TYPE_READ_DATA; // 0x02 读模块信息
//组帧 CAN
can_data.ID = u_ID.u32_ID; //组帧ID
can_data.Len = 8;
//组帧DATA
can_data.Data[0] = u_Data1.u8_data;
can_data.Data[1] = e_cmd_type ; //读取数据命令码
v_MduCanMsg_send(&can_data);
}
/**
* ************************************************************************
* @brief - 模块参数设置
*
* @param[in] u8_grp_on - 设置组号 1/2 不能设置0广播,控制分组控
* @param[in] e_cmd_type - 数据命令码
* @param[in] u32_data - 数据内容 注意精度转换 其中 电压电流精度为0.0001
*
*
* @author jiankalka
* @date 2025-11-13
* ************************************************************************
*/
static void v_uuln_send_set_all_data(U8_T u8_grp_on , E_UULN_CMD_TYPE e_cmd_type , U32_T u32_data)
{
CAN_DATA can_data;
U_UULN_CAN_ID_TYPE u_ID;
U_UULN_DATA1_TYPE u_Data1;
if(u8_grp_on == 0) //组号为0 不做处理
return;
memset(&can_data , 0 , sizeof(CAN_DATA));
//帧ID赋值
u_ID.u32_ID = 0;
u_ID.bit.Protocol = D_MDU_UULN_VER ; // 协议号 0x01 表示整流模块与监控之间的通讯协议号
u_ID.bit.MonitorAddress = 1 ; // 监控地址 地址设定在 0x010x0F,默认 0x01 0x00:代表广播通信
u_ID.bit.ModuleAddres = 0 ; // 整流模块地址 为整流模块地址,地址确定在 0x01-0x7F。 0x00:代表广播通信
u_ID.bit.ProductionDay = 0 ; // 产品生产日期(01-31)。
u_ID.bit.SerialNumberLowPart = 0 ; // 产品序列号低位(默认填 0)。
//DATA1 赋值 包括组地址和信息类型
u_Data1.u8_data = 0;
u_Data1.bit.GroupAddress = u8_grp_on; // 0 广播
u_Data1.bit.MessageType = E_MAG_TYPE_SET_PARA; // 0x00 设置模块参数
//组帧 CAN
can_data.ID = u_ID.u32_ID; //组帧ID
can_data.Len = 8;
//组帧DATA
can_data.Data[0] = u_Data1.u8_data;
can_data.Data[1] = e_cmd_type ; //读取数据命令码
can_data.Data[2] = 0;
can_data.Data[3] = 0;
can_data.Data[4] = GetHigh24Bits(u32_data); //赋值数据
can_data.Data[5] = GetHigh16Bits(u32_data);
can_data.Data[6] = GetHigh8Bits(u32_data);
can_data.Data[7] = u32_data&0xFF;
v_MduCanMsg_send(&can_data);
}
/**
* ************************************************************************
* @brief - 单个模块参数设置
*
* @param[in] u8_addr - 设置模块号 1-N 不能设置0广播
* @param[in] e_cmd_type - 数据命令码
* @param[in] u32_data - 数据内容 注意精度转换 其中 电压电流精度为0.0001
*
*
* @author jiankalka
* @date 2025-11-13
* ************************************************************************
*/
static void v_uuln_send_set_one_data(U8_T u8_addr , E_UULN_CMD_TYPE e_cmd_type , U32_T u32_data)
{
CAN_DATA can_data;
U_UULN_CAN_ID_TYPE u_ID;
U_UULN_DATA1_TYPE u_Data1;
S_MDU_STATE *s_mdu_state = NULL; //模块状态
if(u8_addr == 0 || u8_addr > MDU_MAX_NUM) //模块号为0 或超过最大数量 不做处理
return;
s_mdu_state = &s_mdu_data.s_mdu[u8_addr-1].s_state; //i号模块 状态数据结构体
memset(&can_data , 0 , sizeof(CAN_DATA));
//帧ID赋值
u_ID.u32_ID = 0;
u_ID.bit.Protocol = D_MDU_UULN_VER ; // 协议号 0x01 表示整流模块与监控之间的通讯协议号
u_ID.bit.MonitorAddress = 1 ; // 监控地址 地址设定在 0x010x0F,默认 0x01 0x00:代表广播通信
u_ID.bit.ModuleAddres = u8_addr ; // 整流模块地址 为整流模块地址,地址确定在 0x01-0x7F。 0x00:代表广播通信
u_ID.bit.ProductionDay = 0 ; // 产品生产日期(01-31)。
u_ID.bit.SerialNumberLowPart = 0 ; // 产品序列号低位(默认填 0)。
//DATA1 赋值 包括组地址和信息类型
u_Data1.u8_data = 0;
u_Data1.bit.GroupAddress = s_mdu_state->u8_groupNo; // 模块组号
u_Data1.bit.MessageType = E_MAG_TYPE_SET_PARA; // 0x00 设置模块参数
//组帧 CAN
can_data.ID = u_ID.u32_ID; //组帧ID
can_data.Len = 8;
//组帧DATA
can_data.Data[0] = u_Data1.u8_data;
can_data.Data[1] = e_cmd_type ; //读取数据命令码
can_data.Data[2] = 0;
can_data.Data[3] = 0;
can_data.Data[4] = GetHigh24Bits(u32_data); //赋值数据
can_data.Data[5] = GetHigh16Bits(u32_data);
can_data.Data[6] = GetHigh8Bits(u32_data);
can_data.Data[7] = u32_data&0xFF;
v_MduCanMsg_send(&can_data);
}
/**
* ************************************************************************
* @brief - 设置模块1的组地址
*
* @param[in] u8_addr -
* @param[in] u8_grp_on -
*
*
* @author jiankalka
* @date 2025-11-12
* ************************************************************************
*/
static void v_uuln_send_set_grp_no(U8_T u8_addr , U8_T u8_grp_on)
{
CAN_DATA can_data;
U_UULN_CAN_ID_TYPE u_ID;
U_UULN_DATA1_TYPE u_Data1;
if(u8_addr >= MDU_MAX_NUM)
return;
memset(&can_data , 0 , sizeof(CAN_DATA));
//帧ID赋值
u_ID.u32_ID = 0;
u_ID.bit.Protocol = D_MDU_UULN_VER ; // 协议号 0x01 表示整流模块与监控之间的通讯协议号
u_ID.bit.MonitorAddress = 1 ; // 监控地址 地址设定在 0x010x0F,默认 0x01 0x00:代表广播通信
u_ID.bit.ModuleAddres = u8_addr ; // 整流模块地址 为整流模块地址,地址确定在 0x01-0x7F。 0x00:代表广播通信
u_ID.bit.ProductionDay = 0 ; // 产品生产日期(01-31)。
u_ID.bit.SerialNumberLowPart = 0 ; // 产品序列号低位(默认填 0)。
//DATA1 赋值 包括组地址和信息类型
u_Data1.u8_data = 0;
u_Data1.bit.GroupAddress = 0; // 0 广播
u_Data1.bit.MessageType = E_MAG_TYPE_SET_PARA; // 0x00 设置模块参数
//组帧 CAN
can_data.ID = u_ID.u32_ID; //组帧ID
can_data.Len = 8;
//组帧DATA
can_data.Data[0] = u_Data1.u8_data;
can_data.Data[1] = E_GROUP_ADDRESS ; // 0x59 组地址 R/W Y
can_data.Data[7] = u8_grp_on; //设置组号
v_MduCanMsg_send(&can_data);
}
#endif
#if 1 //发送
/**
* ************************************************************************
* @brief - 设置模块高低压状态
*
* @param[in] u8_grp_on - 组号 1/2 0无意义
* @param[in] u8_flag - 11000V 2500V
*
* @return U8_T - 是否执行切换步骤 1是 0否
* 执行切换步骤,包含控制模块停机、设置高低压状态等操作 执行时不许再控制模块启动
*
* @author jiankalka
* @date 2025-11-13
* ************************************************************************
*/
static U8_T u8_uuln_set_grp_mdu_hlvol(U8_T u8_grp_on , E_TrueHiLo_Status e_flag)
{
U8_T i,u8_ret = 0;
S_MDU_STATE *s_mdu_state = NULL; //模块状态
if(u8_grp_on == 0) return 0; //组号为0 无意义
for (i = 0; i < s_mdu_data.u8_mdu_num; i++) //遍历所有模块 检查模块状态和分组情况
{
s_mdu_state = &s_mdu_data.s_mdu[i].s_state; //i号模块 状态数据结构体
if(s_mdu_state->e_state != E_MDU_STATE_NORMAL || //模块正常 且组号一致
s_mdu_state->u8_groupNo != u8_grp_on)
continue;
if(e_flag != s_mdu_state->e_TrueHiLo_Status) //状态不一致 则设置状态
{
u8_ret = 1;
if(s_mdu_state->e_work_state == E_MDU_WORK) //停模块
{
v_uuln_send_set_one_data(i+1,E_DCDC_MODULE_POWER_CONTROL,1); // 0x04 DCDC模块开关机 W N 模块开关机(0 开机,1 关机)
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) set hl vol state ,ctrl mdu off\r\n",i);
#endif
}
else
{
v_uuln_send_set_one_data(i+1,E_MODULE_HIGH_LOW_VOLTAGE_MODE,e_flag); // 0x5F 模块高低压模式 W Y 11000V 2500V
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) set hl vol state [%d] -> [%d]\r\n",i,s_mdu_state->e_TrueHiLo_Status,e_flag );
#endif
}
}
}
return u8_ret;
}
/**
* ************************************************************************
* @brief - 充电模块启停控制 包含需求电压电流下发和启停控制等 内部包含模块高低压状态切换功能
*
*
*
* @author jiankalka
* @date 2025-11-13
* ************************************************************************
*/
static void v_uuln_mdu_set_ctrl()
{
/* 周期发送
*/
U8_T gunNo,u8_flag,u8_gunState;
S_MDU_GROUP_DATA *s_mdu_group = NULL; //模块组数据
for (gunNo = 0; gunNo < s_mdu_data.u8_mdu_group_num; gunNo++) //遍历模块组数量
{
s_mdu_group = &s_mdu_data.s_mdu_group[gunNo]; //获取i模块组数据结构体
//模块 高低压状态检查 500V
if(s_mdu_group->u16_needVol != 0) //需求电压为0时 无意义 不用做检查
{
if(s_mdu_group->u16_needVol > 5000) //需求电压大于500V 则设置为高压状态
{
//返回值1 则代表存在设置过程 其中包含停机操作 因此不必执行下方的控制命令
if(u8_uuln_set_grp_mdu_hlvol(gunNo+1 , E_TrueHiLo_H)) //设置模块高低压状态 1000V
{
continue;
}
}
else //需求电压小于500V 则设置为底压状态
{
//返回值1 则代表存在设置过程 其中包含停机操作 因此不必执行下方的控制命令
if(u8_uuln_set_grp_mdu_hlvol(gunNo+1 , E_TrueHiLo_L)) //设置模块高低压状态 500V
{
continue;
}
}
}
#if( MDU_PARA_READ_EN == 1) //模块参数读取使能
if(s_mdu_data.u16_mduPara_MaxOutVol != 0 && s_mdu_group->u16_needVol > s_mdu_data.u16_mduPara_MaxOutVol)
s_mdu_group->u16_needVol = s_mdu_data.u16_mduPara_MaxOutVol;
else if(s_mdu_group->u16_needVol < s_mdu_data.u16_mduPara_MinOutVol)
s_mdu_group->u16_needVol = s_mdu_data.u16_mduPara_MinOutVol;
if(s_mdu_data.u16_mduPara_MaxOutCur != 0 && s_mdu_group->u16_oneMdu_needCur > s_mdu_data.u16_mduPara_MaxOutCur)
s_mdu_group->u16_oneMdu_needCur = s_mdu_data.u16_mduPara_MaxOutCur;
else if(s_mdu_group->u16_oneMdu_needCur < s_mdu_data.u16_mduPara_MinOutCur)
s_mdu_group->u16_oneMdu_needCur = s_mdu_data.u16_mduPara_MinOutCur;
#endif
v_uuln_send_set_all_data(gunNo+1,E_MODULE_VOLTAGE_SETPOINT,s_mdu_group->u16_needVol*100); // 0x02 模块输出电压设定值 R/W N 数据精度 0.0001V mV
v_uuln_send_set_all_data(gunNo+1,E_MODULE_CURRENT_LIMIT_SETPOINT,s_mdu_group->u16_oneMdu_needCur *100); // 0x03 模块输出限流点设定值 R/W N 数据精度 0.0001V mV
u8_gunState = u16_mdu_get_charge_need(gunNo, E_MDU_GET_DATA_GUN_CMD) ; //枪状态 0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
/*0无 1快速启机 2快速启机停(不清空分组) 3软启开机 4充电停止(清空分组)
*/
if(u8_gunState == 0x01 || u8_gunState== 0x03)
{
u8_flag = 0; //开机
}
else
{
u8_flag = 1; //关机
}
v_uuln_send_set_all_data(gunNo+1,E_DCDC_MODULE_POWER_CONTROL,u8_flag); // 0x04 DCDC模块开关机 W N DCDC 模块开关机(0 开机,1 关机)
}
}
/**
* ************************************************************************
* @brief - 模块组号设置检查 设置模块数量除以2 前半部分设置组号为1 后半部分设置组号为2
*
*
*
* @author jiankalka
* @date 2025-11-12
* ************************************************************************
*/
static void v_uuln_mdu_grp_cheak()
{
U8_T i,u8_mdu_grp;
S_MDU_CTRL *s_mdu_ctrl = NULL; //模块控制
S_MDU_STATE *s_mdu_state = NULL; //模块状态
for ( i = 0; i < s_mdu_data.u8_mdu_num; i++) //遍历所有模块
{
s_mdu_ctrl = &s_mdu_data.s_mdu[i].s_ctrl; //i号模块 控制数据结构体
s_mdu_state = &s_mdu_data.s_mdu[i].s_state; //i号模块 状态数据结构体
if(s_mdu_state->e_state == E_MDU_STATE_NONE)
continue;
// if(i < ((s_mdu_data.u8_mdu_num+1) / 2)) // 前半部分为1枪 后半部分为2枪 模块数量为奇数时,1枪多一个
// u8_mdu_grp = 1;
// else
// u8_mdu_grp = 2;
u8_mdu_grp = s_mdu_ctrl->u8_mdu_in_pile;
if(s_mdu_state->u8_groupNo != u8_mdu_grp) //模块组号不正确 设置模块组号
{
v_uuln_send_set_grp_no(i+1,u8_mdu_grp);
}
}
}
/**
* ************************************************************************
* @brief -模块通信发送控制函数
*
*
*
* @author jiankalka
* @date 2025-07-03
* ************************************************************************
*/
static void v_uuln_mdu_comm_send_ctrl(U16_T u16_sleepTime)
{
if(s_mdu_data.u16_timer % NUM_250 == 0) //250ms
{
v_mdu_data_summ(); //模块组信息更新
v_uuln_mdu_set_ctrl(); //模块设置控制
}
if(s_mdu_data.u16_timer % NUM_1000 == 0) //1秒
{
v_uuln_send_read_all_data(0,E_MODULE_STATUS_FLAGS); //读取所有模块的状态标志位 组1 组2
}
else if(s_mdu_data.u16_timer % NUM_1000 == u16_sleepTime) //1秒 与上一帧间隔 一个周期
{
v_uuln_send_read_all_data(0,E_MODULE_OUTPUT_VOLTAGE); //0x00 模块输出电压 R N
}
else if(s_mdu_data.u16_timer % NUM_1000 == u16_sleepTime*2) //1秒 与上一帧间隔 一个周期
{
v_uuln_send_read_all_data(0,E_MODULE_OUTPUT_CURRENT); //0x00 0x01 模块输出电流 R N
if(s_mdu_data.u16_mduPara_MaxOutVol == 0xFF ||
s_mdu_data.u16_mduPara_MaxOutCur == 0xFF)
v_uuln_send_read_all_data(0,E_MODULE_CURRENT_AND_CAPABILITY); // 0x72 读取模块输出电流和输出电流能力 R N 单位:0.1A(高十六位:模块的实际输出电流,低十六位:实际的电流能力)
}
else if(s_mdu_data.u16_timer % NUM_1000 == u16_sleepTime*3) //1秒 与上一帧间隔 一个周期
{
v_uuln_send_read_all_data(0,E_MODULE_HIGH_LOW_VOLTAGE_STATUS); // 0x60 读取模块高低压模式状态 R N
}
else if(s_mdu_data.u16_timer % NUM_1000 == u16_sleepTime*4) //1秒 与上一帧间隔 一个周期
{
v_uuln_send_read_all_data(0,E_INLET_AIR_TEMPERATURE); // 0x1E 进风口温度 R N
}
if(s_mdu_data.u16_timer % NUM_5000 == 0) //5秒
{
v_uuln_mdu_grp_cheak(); //模块组号设置检查
}
}
#endif
#if 1 //接收
static void v_uuln_mdu_rx_unpack(CAN_DATA *rxMsg)
{
U_UULN_CAN_ID_TYPE u_ID;
E_UULN_CMD_TYPE e_cmd_type;
U_UULN_DATA1_TYPE u_Data1;
U8_T u8_addr;
U32_T u32_recv_data;
S_MDU_STATE *s_mdu_state = NULL;
u_ID.u32_ID = rxMsg->ID; //获取ID
u8_addr = u_ID.bit.ModuleAddres-1; //模块地址
e_cmd_type = rxMsg->Data[1]; //数据类型
u_Data1.u8_data = rxMsg->Data[0]; //信息类型 和 组地址
/* 抢连接/DI 状态:交给 STM 状态通信模块解析(ID 0x1801AABB */
#if (GUN_LINK_COMM_EN)
if (u8_stm_gb_enable() && u8_stm_status_can_rx(rxMsg))
return;
#endif
if(u_ID.bit.Protocol != D_MDU_UULN_VER) //协议号 不一致
return;
if(u_Data1.bit.MessageType != E_MAG_TYPE_SET_ACK &&
u_Data1.bit.MessageType != E_MAG_TYPE_READ_ACK )//信息类型 不正确
return;
if(u8_addr >= MDU_MAX_NUM)
return;
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) cmd is [%d]\r\n",u8_addr,e_cmd_type );
#endif
s_mdu_state = &s_mdu_data.s_mdu[u8_addr].s_state; //获取模块状态
s_mdu_state->u16_outtimecnt = 0; //超时计数 清零
//获取4-7字节数据内容 转换为整形
u32_recv_data = (rxMsg->Data[4] << 24) | (rxMsg->Data[5] << 16) | (rxMsg->Data[6] << 8) | rxMsg->Data[7];
switch (e_cmd_type)
{
case E_MODULE_STATUS_FLAGS : // 0x08 模块状态标志位 R N
{
U_UULN_MDU_STATUS u_state;
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) grp is [%d],work state is [%d]\r\n",u8_addr,u_Data1.bit.GroupAddress,1-u_state.bit.DcdcNotRun );
#endif
u_state.all = u32_recv_data;
s_mdu_state->u8_GrpMode = 1; //默认都是动态分组 模块分组模式 0固定分组 1动态分组
s_mdu_state->u8_groupNo = u_Data1.bit.GroupAddress; //组号
if (u_state.bit.DcdcNotRun == 0) //工作状态,1待机,2工作
s_mdu_state->e_work_state = E_MDU_WORK;
else
s_mdu_state->e_work_state = E_MDU_IDLE;
// 故障判断:如果存在任何故障点位或预留点位非零则判为故障,否则正常
if (u_state.all & FAULT_MASK) { // 存在故障,进行故障处理
if(s_mdu_state->e_state != E_MDU_STATE_NONE)
s_mdu_state->e_state = E_MDU_STATE_FAULT; //故障状态
s_mdu_state->u_alarm_data.bit_mdu_alarm.b0_other = 1; //其他故障,0正常,1故障
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b1_relief = ISBITSET(rxMsg->Data[1],1); //泄放故障
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b2_fan = ISBITSET(rxMsg->Data[1],2); //风扇故障
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b3_DC_short = ISBITSET(rxMsg->Data[1],3); //直流输出短路
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b4_overTemp = ISBITSET(rxMsg->Data[1],4); //过温
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b5_DC_UV = ISBITSET(rxMsg->Data[1],5); //直流输出欠压
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b6_DC_OV = ISBITSET(rxMsg->Data[1],6); //直流输出过压
// s_mdu_state->u_alarm_data.bit_mdu_alarm.b7_AC_IN = ISBITSET(rxMsg->Data[1],7); //交流输入故障
} else {
if(s_mdu_state->e_state == E_MDU_STATE_FAULT)
s_mdu_state->e_state = E_MDU_STATE_NONE;
s_mdu_state->u_alarm_data.u8_mdu_alarm = 0;
}
s_mdu_state->u_uuln_state.all = u_state.all;
break;
}
case E_GROUP_ADDRESS : // 0x59 组地址 R/W Y
{
break;
}
case E_MODULE_OUTPUT_VOLTAGE: // 0x00 模块输出电压 R N
{
//模块输出电压 mV
s_mdu_state->u16_outVol = u32_recv_data / 100; //直流输出电压 0.1V
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) grp is [%d],put vol is [%d](0.1V)\r\n",u8_addr,u_Data1.bit.GroupAddress,s_mdu_state->u16_outVol );
#endif
break;
}
case E_MODULE_OUTPUT_CURRENT: // 0x01 模块输出电流 R N
{
//模块输出电流 mA
s_mdu_state->u16_outCur = u32_recv_data/100; //直流输出电流 0.1A
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d] grp is [%d],put cur is [%d](0.1A)\r\n",u8_addr,u_Data1.bit.GroupAddress,s_mdu_state->u16_outCur );
#endif
break;
}
case E_MODULE_HIGH_LOW_VOLTAGE_STATUS: // 0x60 读取模块高低压模式状态 R N
{
//读取模块实际的高低压档位状态(1:1000V2:500V
s_mdu_state->e_TrueHiLo_Status = (E_TrueHiLo_Status)u32_recv_data; //高低压状态读取 1: 1000V 2: 500V
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) grp is [%d],高低压模式状态 is [%d](1:1000V 2:500V)\r\n",u8_addr,u_Data1.bit.GroupAddress,s_mdu_state->e_TrueHiLo_Status );
#endif
}
case E_INLET_AIR_TEMPERATURE: // 0x1E 进风口温度 R N 毫度
{
s_mdu_state->s8_mdutemp_1 = u32_recv_data / 1000; //毫度
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d](0-N) grp is [%d], in fan temp is [%d](1:1000V 2:500V)\r\n",u8_addr,u_Data1.bit.GroupAddress,s_mdu_state->s8_mdutemp_1 );
#endif
break;
}
case E_MODULE_CURRENT_AND_CAPABILITY: // 0x72 读取模块输出电流和输出电流能力 R N 单位:0.1A(高十六位:模块的实际输出电流,低十六位:实际的电流能力)
{
//模块输出电流 mA
s_mdu_state->u16_outCur = ((u32_recv_data >> 16)&0xFFFF); //直流输出电流 0.1A
if(s_mdu_data.u16_mduPara_MaxOutCur == 0xFF) //未赋值 则赋值模块最大输出电流值
{
s_mdu_data.u16_mduPara_MaxOutVol = 1000*10; //默认1000V
s_mdu_data.u16_mduPara_MinOutVol = 0;
s_mdu_data.u16_mduPara_MaxOutCur = (u32_recv_data & 0xFFFF)/10;
s_mdu_data.u16_mduPara_MinOutCur = 0;
}
#if MDU_CAN_PRINT_FLAG
printf("mdu[%d] grp is [%d],put cur is [%d][%d](0.1A)\r\n",u8_addr,u_Data1.bit.GroupAddress,s_mdu_state->u16_outCur ,s_mdu_data.u16_mduPara_MaxOutCur);
#endif
break;
}
default:
break;
}
}
/**
* ************************************************************************
* @brief -模块通信接收解包函数
*
*
*
* @author jiankalka
* @date 2025-07-03
* ************************************************************************
*/
static void v_uuln_mdu_comm_recv_unpack()
{
U8_T i, canRcvCnt = 0; //接收can报文数目
CAN_DATA rx_can_data[CAN_RECV_BUF_SIZE] = {0};
canRcvCnt= u8_MduCanMsg_recv(&rx_can_data[0]);
//DEBUG_LOG("fdcan1 recv num = %d\n",ret);
for (i = 0; i < canRcvCnt; i++)
{
v_uuln_mdu_rx_unpack(&rx_can_data[i]);
}
}
#endif
#if 1 //通信超时
/**
* ************************************************************************
* @brief -模块控制 报文接收超时检查
*
*
*
* @author jiankalka
* @date 2025-07-03
* ************************************************************************
*/
static void v_uuln_mdu_comm_outtime_cheak(U16_T u16_sleepTime)
{
U8_T i,u8_mdu_num = s_mdu_data.u8_mdu_num; //模块数量
U8_T u8_mdu_comm_err_num = 0; //通信超时模块数量
S_MDU_STATE *s_mdu_state = NULL;
for(i=0;i<u8_mdu_num;i++)
{
s_mdu_state = &s_mdu_data.s_mdu[i].s_state; //获取模块状态
if(s_mdu_state->e_state == E_MDU_STATE_NONE) //模块未通信 判断通信
{
u8_mdu_comm_err_num ++;
if(s_mdu_state->u16_outtimecnt < 10*NUM_1000) //10秒超时
{
s_mdu_state->e_state = E_MDU_STATE_NORMAL; //通信正常
Mdu_Comm_LOG("(%d)号充电模块通讯正常\n",i);
}
v_mdu_set_comm_fault_info(i,1); //故障动作触发
}
else{ //模块已通信 判断超时
s_mdu_state->u16_outtimecnt += u16_sleepTime; //超时计数递增
if(s_mdu_state->u16_outtimecnt > 20*NUM_1000) //10秒超时
{
s_mdu_state->e_state = E_MDU_STATE_NONE;
v_mdu_clear_recv_data(i);
Mdu_Comm_LOG("(%d)号充电模块通讯超时\n",i);
}
else if(s_mdu_state->e_state == E_MDU_STATE_NORMAL &&
s_mdu_state->u16_outtimecnt > 10*NUM_1000 &&
s_mdu_state->u16_outtimecnt < 20*NUM_1000)
{
/* 10s无报文:按需可强制重初始化,这里默认强制 */
uint8_t act = CANSPI_HealthCheckAndRecover(1U);
if (act == 0U) {
printf("time=%d, 模块(%d) 10s无报文,但总线/控制器自检正常,暂不复位\r\n",
get_current_seconds(), i);
}
s_mdu_state->u16_outtimecnt = 0;
}
else
{
}
v_mdu_set_comm_fault_info(i,0); //故障恢复触发
}
}
if(u8_mdu_comm_err_num == u8_mdu_num) //所有模块通信故障
{
v_mdu_set_comm_fault_info(MODULE_ALL,1); //故障恢复触发
}
else
{
v_mdu_set_comm_fault_info(MODULE_ALL,0); //故障恢复触发
}
}
/**
* ************************************************************************
* @brief - 枪未连接 充电模块通信超时恢复
*
*
*
* @author jiankalka
* @date 2025-11-20
* ************************************************************************
*/
static void v_uuln_mdu_gun_no_link_outtime_claer()
{
U8_T i,u8_mdu_num = s_mdu_data.u8_mdu_num; //模块数量
v_mdu_set_comm_fault_info(MODULE_ALL,0); //故障恢复触发
for(i=0;i<u8_mdu_num;i++)
{
v_mdu_set_comm_fault_info(i,0); //故障恢复触发
}
}
#endif
/**
* ************************************************************************
* @brief - 优优绿能协议控制动作
*
*
*
* @author jiankalka
* @date 2025-11-12
* ************************************************************************
*/
void v_mdu_comm_uuln_action(U16_T u16_leep_time)
{
U16_T u16_runing_flag;
static U8_T u8_spican_init_flag = 0;
//获取模块任务运行标志 0模块任务不运行并清空模块数据 1模块任务运行-执行报文收发和超时判断
u16_runing_flag = u16_mdu_get_charge_need(0, E_MDU_GET_DATA_RUNING_FLAG);
v_uuln_mdu_comm_recv_unpack();
if(u16_runing_flag == 1)
{
v_uuln_mdu_comm_send_ctrl(u16_leep_time);
v_uuln_mdu_comm_outtime_cheak(u16_leep_time);
if(u8_spican_init_flag == 0) //模块任务运行后第一次进入 则进行SPI-CAN初始化
{
CANSPI_HealthCheckAndRecover(1U); //强制进行一次SPI-CAN健康检查和恢复,确保通信正常
u8_spican_init_flag = 1;
}
}
else
{
u8_spican_init_flag = 0;
s_mdu_data.u32_runing_time = get_current_seconds();
v_mdu_clear_recv_data(MODULE_ALL); //清空所有模块接收数据
v_uuln_mdu_gun_no_link_outtime_claer();
}
if(s_mdu_data.u16_timer < 0xFF00) //因为在
s_mdu_data.u16_timer += u16_leep_time; //计数周期累加
else
s_mdu_data.u16_timer = 0;
/* 辅源控制 CAN 帧:利用 s_mdu_data.u16_timer,每 1000ms 发送一次 */
#if (GUN_LINK_COMM_EN)
if (u8_stm_gb_enable() && (s_mdu_data.u16_timer % NUM_1000 == 0))
{
v_stm_status_can_send_aux_frame();
}
#endif
/* 周期性 SPI-CAN 健康检查:独立于模块通信超时运行 */
if (s_mdu_data.u16_timer % NUM_1000 == 0)
{
(void)CANSPI_HealthCheckAndRecover(0U);
}
}
#endif /* MDU_UULN_EN */
+122
View File
@@ -0,0 +1,122 @@
#ifndef MDU_YYLN_COMM_H
#define MDU_YYLN_COMM_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/public_define.h"
/*------ Exported macro(宏定义) -------------------------*/
#define D_MDU_UULN_VER (0x01) //协议号 0x01 表示整流模块与监控之间的通讯协议号
#define FAULT_MASK 0xB035FBFF // 定义故障掩码:所有故障点位为1,非故障点位为0
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
E_MAG_TYPE_SET_PARA = 0, // 0x00 设置模块参数
E_MAG_TYPE_SET_ACK, // 0x01 模块设置应答
E_MAG_TYPE_READ_DATA, // 0x02 读模块信息
E_MAG_TYPE_READ_ACK, // 0x03 模块读信息应答
E_MAG_TYPE_READ_SERIAL, // 0x04 模块读序列号应答
}E_UULN_MAG_TYPE;
typedef enum
{
E_MODULE_OUTPUT_VOLTAGE = 0, // 0x00 模块输出电压 R N
E_MODULE_OUTPUT_CURRENT, // 0x01 模块输出电流 R N
E_MODULE_VOLTAGE_SETPOINT, // 0x02 模块输出电压设定值 R/W N
E_MODULE_CURRENT_LIMIT_SETPOINT, // 0x03 模块输出限流点设定值 R/W N
E_DCDC_MODULE_POWER_CONTROL, // 0x04 DCDC模块开关机 W N 模块开关机(0 开机,1 关机)
E_MODULE_SERIAL_NUMBER, // 0x05 读取模块序列号 R Y
E_MODULE_STATUS_FLAGS = 8, // 0x08 模块状态标志位 R N
E_LINE_VOLTAGE_AB = 20, // 0x14 线电压AB R N
E_LINE_VOLTAGE_BC, // 0x15 线电压BC R N
E_LINE_VOLTAGE_CA, // 0x16 线电压CA R N
E_INLET_AIR_TEMPERATURE = 30, // 0x1E 进风口温度 R N
E_GROUP_ADDRESS = 89, // 0x59 组地址 R/W Y
E_MODULE_HIGH_LOW_VOLTAGE_MODE = 95,// 0x5F 模块高低压模式 W Y 11000V 2500V
E_MODULE_HIGH_LOW_VOLTAGE_STATUS, // 0x60 读取模块高低压模式状态 R N
E_MODULE_OUTPUT_CURRENT_CAPABILITY = 104, // 0x68 读取模块输出电流能力 R N
E_MODULE_CURRENT_AND_CAPABILITY = 114, // 0x72 读取模块输出电流和输出电流能力 R N 单位:0.1A(高十六位:模块的实际输出电流,低十六位:实际的电流能力)
}E_UULN_CMD_TYPE; //type 1
typedef enum {
E_TrueHiLo_H = 1, //模块高低压模式 W Y 11000V )
E_TrueHiLo_L //模块高低压模式 W Y (2:500V )
}E_TrueHiLo_Status;
typedef union
{
U32_T u32_ID;
struct
{
U32_T SerialNumberLowPart : 9; // 产品序列号低位(默认填 0)。
U32_T ProductionDay : 5; // 产品生产日期(01-31)。
U32_T ModuleAddres : 7; // 整流模块地址 为整流模块地址,地址确定在 0x01-0x7F。 0x00:代表广播通信
U32_T MonitorAddress : 4; // 监控地址 地址设定在 0x010x0F,默认 0x01 0x00:代表广播通信
U32_T Protocol : 4; // 协议号 0x01 表示整流模块与监控之间的通讯协议号
U32_T res : 3;
}bit;
}U_UULN_CAN_ID_TYPE;
typedef union
{
U8_T u8_data;
struct
{
U8_T MessageType : 4; // 信息类型 详看枚举 E_UULN_MAG_TYPE
U8_T GroupAddress : 4; // 组地址 组地址范围为 0x01~0x0F,0x00 为广播通信;
}bit;
}U_UULN_DATA1_TYPE;
typedef union {
U32_T all;
struct MODULE_STATUS_BITS {
U32_T AcOV:1; // 0 交流输入过压
U32_T AcUV:1; // 1 交流输入欠压
U32_T AcOVDisconnected:1; // 2 交流输入过压脱离
U32_T PfcOV:1; // 3 PFC母线过压
U32_T PfcUV:1; // 4 PFC母线欠压
U32_T PfcUnbalance:1; // 5 PFC母线不平衡
U32_T DcOV:1; // 6 直流输出过压
U32_T DcOVShutDown:1; // 7 直流输出过压关机
U32_T DcUV:1; // 8 直流输出欠压
U32_T FanNotRun:1; // 9 风扇故障
U32_T Reserved_10:1; // 10 保留
U32_T FanDriverFail:1; // 11 风扇电压驱动电路损坏
U32_T AmbientOT:1; // 12 环境温度过温保护
U32_T AmbientUT:1; // 13 环境温度过低
U32_T Pfc1_OT:1; // 14 PFC过温保护1
U32_T OutputRelayFault:1; // 15 输出继电器故障
U32_T Dcdc1_OT:1; // 16 DC过温保护1
U32_T Reserved_17:1; // 17 保留
U32_T SciIsNotOK:1; // 18 原副边通信故障
U32_T Reserved_19:1; // 19 保留
U32_T PfcFail:1; // 20 原边故障
U32_T DcdcFail:1; // 21 副边故障
U32_T Reserved_22:1; // 22 保留
U32_T Reserved_23:1; // 23 保留
U32_T Reserved_24:1; // 24 保留
U32_T DcdcNotRun:1; // 25 开关机状态
U32_T OutputMode:2; // 26:27 输出环路状态
U32_T DcVoltageUnbalance:1; // 28 DC输出电压不平衡
U32_T GetSameSN:1; // 29 发现相同序列号的模块
U32_T Reserved_30:1; // 30 保留
U32_T BleederNotWork:1; // 31 泄放电路损坏
}bit;
}U_UULN_MDU_STATUS;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_mdu_comm_uuln_action(U16_T u16_leep_time);
#ifdef __cplusplus
}
#endif
#endif /* MDU_YYLN_COMM_H */
+340
View File
@@ -0,0 +1,340 @@
#include "meter_calculate/calculate_task.h"
#include "app_init/app_init.h"
#include "app_init/task_lock.h"
#include "mylog/mylog.h"
#include "wdt_task/wdt_task.h"
#include "publicdata/publicdata.h"
#include "flash_file_mgr/meter_calculate_flash_impl.h"
#include "flash_file_mgr/unsettled_order_mng.h"
#include "app_rtc/app_rtc.h"
#include <string.h>
#define METERFEE_TASK_ID (TASK_ID_Meterfee)
#define METERFEE_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(METERFEE_TASK_ID))
#define METERFEE_LOCK_INIT() (u8_task_lock_ctrl(METERFEE_TASK_ID, TASK_LOCK_OP_INIT))
#define METERFEE_LOCK_TAKE() (u8_task_lock_ctrl(METERFEE_TASK_ID, TASK_LOCK_OP_TAKE))
#define METERFEE_LOCK_GIVE() (u8_task_lock_ctrl(METERFEE_TASK_ID, TASK_LOCK_OP_GIVE))
#define METERFEE_FEED_DOG() (v_wdt_setFlag(METERFEE_TASK_ID))
#define METERFEE_LOG(fmt, ...) MYLOG_MSG(METERFEE_TASK_ID, (fmt), ##__VA_ARGS__)
S_METERING_BILLING_CTRL s_metering_ctrl;
#if 1 //计量费率控制
/**
* ************************************************************************
* @brief - 订单数据周期更新 -更新结束电量、结束soc、充电时间等信息
*
* @param[in] u8_gunNo - 枪号 0-N
*
*
* @author jiankalka
* @date 2025-09-12
* ************************************************************************
*/
static void v_meterlog_log_updata(U8_T u8_gunNo)
{
S_LOG_DATA *s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo];
Comm_Time time;
U8_T u8_soc = u32_meterlog_get_sys_data(u8_gunNo,E_METERLOG_GET_DATA_GUN_SOC,0);
GetCurrentTime(&time);
U32_T u32_time = xDate2Seconds(&time); //获取时间戳
if(s_gun_log->StartChargeTime.iYear == 0)
memcpy(&s_gun_log->StartChargeTime, &time,sizeof(Comm_Time));
U32_T u32_start_time = xDate2Seconds(&s_gun_log->StartChargeTime); //获取起始时间时间戳
// 修复后的充电时间计算
s_gun_log->u16_ChargedTime = (u32_time >= u32_start_time)
? (u32_time - u32_start_time) / 60 // 正常情况
: (u32_start_time - u32_time) / 60; // 处理时间反转情况
//获取结束时间值
memcpy(&s_gun_log->EndChargeTime, &time,sizeof(Comm_Time)); //充电开始时间
s_gun_log->u64_endKWh = u64_meterlog_get_meter_data(u8_gunNo,0); //当前电表数据
if(u8_soc != 0)
s_gun_log->u8_endSoc = u8_soc; //SOC
if(s_gun_log->u8_startSoc == 0)
s_gun_log->u8_startSoc = s_gun_log->u8_endSoc;
}
/**
* ************************************************************************
* @brief - 实时获取当前费率信息 - 计算当前时段 获取费率信息
*
*
*
* @author jiankalka
* @date 2025-09-12
* ************************************************************************
*/
static void v_meterlog_real_fee_updata()
{
Comm_Time time;
U8_T u8_period_time;
GetCurrentTime(&time); //获取当前时间
u8_period_time = (time.ucHour*60 + time.ucMin) / 15; //计算当前所处时段 最大96
//METERFEE_LOG("u8_period_time=%d\n",u8_period_time);
if(u8_period_time >= 96 ) //异常数值
{
if(u8_cheak_fault_flag(E_FAULT_17009,E_WHOLE_HOST)==0)
{
METERFEE_LOG("Time invalid, now %d-%d-%d %d:%d; period=%d", time.iYear, time.ucMonth, time.ucDay, time.ucHour, time.ucMin, u8_period_time);
v_meterlog_set_fault(0,2,1,time.ucHour,time.ucMin); //时间不合法
return;
}
}
else
{
if(u8_cheak_fault_flag(E_FAULT_17009,E_WHOLE_HOST) == 1)
{
v_meterlog_set_fault(0,2,0,time.ucHour,time.ucMin);
}
}
//赋值当前费率信息
s_metering_ctrl.s_real_fee_data[0].u8_period_time = u8_period_time;
s_metering_ctrl.s_real_fee_data[0].u32_money_rate = u32_meterlog_get_sys_data(u8_period_time,E_METERLOG_GET_DATA_FEE_RATE_DATA,0); //费率电费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
s_metering_ctrl.s_real_fee_data[0].u32_money_service = u32_meterlog_get_sys_data(u8_period_time,E_METERLOG_GET_DATA_FEE_SERVER_DATA,0); //费率服务费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
s_metering_ctrl.s_real_fee_data[1].u8_period_time = u8_period_time; //放电
s_metering_ctrl.s_real_fee_data[1].u32_money_rate = u32_meterlog_get_sys_data(u8_period_time,E_METERLOG_GET_DATA_FEE_RATE_DATA,1); //费率电费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
s_metering_ctrl.s_real_fee_data[1].u32_money_service = u32_meterlog_get_sys_data(u8_period_time,E_METERLOG_GET_DATA_FEE_SERVER_DATA,1); //费率服务费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
}
/**
* ************************************************************************
* @brief - 枪周期充电电量、计量计算
*
* @param[in] u8_gunNo - 枪号
*
*
* @author jiankalka
* @date 2025-09-12
* ************************************************************************
*/
static void v_meterlog_charge_calculate(U8_T u8_gunNo)
{
S_CHAR_DATA *s_char_data = &s_metering_ctrl.s_char_data[u8_gunNo]; //获取枪控制信息
S_LOG_DATA *s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo]; //获取枪订单信息
S_REAL_FEE_DATA *s_real_fee_data = &s_metering_ctrl.s_real_fee_data[s_char_data->u8_discharge_flag]; //获取当前费率信息 默认已经更新正确
U32_T u64_meter_data = u64_meterlog_get_meter_data(u8_gunNo,0); //获取枪电表值 精度 0.0001kwh
U32_T u32_cyc_pow = 0; //本次循环电量 精度 0.0001kwh
U32_T u32_cyc_money = 0; //本次循环费用 精度 0.0001kwh
//计算周期电表差值
if(s_char_data->u64_whm_start_energy == 0) //本次充电 首次计量
{
s_char_data->u64_whm_start_energy = u64_meter_data;
return;
}
else if(s_char_data->u64_whm_start_energy > u64_meter_data && u64_meter_data !=0 ) //上次电表电量 大于本次 则说明电表电量 超出上限进行了归0
{
u32_cyc_pow = METER_MAX_ELE - s_char_data->u64_whm_start_energy + u64_meter_data;
}
else //正常计算差值
{
u32_cyc_pow = u64_meter_data - s_char_data->u64_whm_start_energy;
}
if(u32_cyc_pow > 10000 ) // 跳变大于10度判定电表异常
{
if(u8_cheak_fault_flag(E_FAULT_17007, (E_FAULT_SITE)((U16_T)E_GUN + (U16_T)u8_gunNo)) == 0U)//无告警
{
METERFEE_LOG("[%d]Gun meter jump fault, delta=%d, thr=%d\n", u8_gunNo, u32_cyc_pow, 10000);
v_meterlog_set_fault(u8_gunNo,0,1,s_char_data->u64_whm_start_energy,u64_meter_data); //设置系统故障
}
return;
}
//更新存储的电量,本次存储电量作为下一循环的起始电量
s_char_data->u64_whm_start_energy = u64_meter_data;
s_gun_log->u32_used_energy += u32_cyc_pow;
s_gun_log->u32_part_energy[s_real_fee_data->u8_period_time] += u32_cyc_pow; //将本次循环电量累加到本时段电量中
u32_cyc_money = (u32_cyc_pow*(s_real_fee_data->u32_money_rate)) / 10000; //周期电费 精度 0.0001kwh
s_gun_log->u32_used_money += u32_cyc_money; //电费累加
u32_cyc_money = (u32_cyc_pow*(s_real_fee_data->u32_money_service)) / 10000; //周期电费 精度 0.0001kwh
s_gun_log->u32_used_service += u32_cyc_money;
//METERFEE_LOG("[%d]Gun period=%d, rate=%d, svc=%d\n", u8_gunNo, s_real_fee_data->u8_period_time, s_real_fee_data->u32_money_rate, s_real_fee_data->u32_money_service);
METERFEE_LOG("[%d]Gun addE(0.0001kWh)=%d, totalE=%d, ChgTime=%d,\n", u8_gunNo, u32_cyc_pow, s_gun_log->u32_used_energy,s_gun_log->u16_ChargedTime);
//METERFEE_LOG(" fee=%d, svcFee=%d\n", s_gun_log->u32_used_money, s_gun_log->u32_used_service);
}
/**
* ************************************************************************
* @brief -计量计费流程
*
*
*
*
* @author jiankalka
* @date 2025-09-24
* ************************************************************************
*/
static void v_meterlog_ctrl()
{
U8_T u8_gunNo;
S_CHAR_DATA *s_char_ctrl = NULL;
if(s_metering_ctrl.u16_timer % NUM_5000 != 0) //5秒 计算一次
return;
v_meterlog_real_fee_updata(); //更新当前费率信息
//遍历所有的枪 依次进行计量判断
for (u8_gunNo = 0; u8_gunNo < s_metering_ctrl.u8_gunCnt; u8_gunNo++)
{
s_char_ctrl = &s_metering_ctrl.s_char_data[u8_gunNo]; //获取枪控制数据
if(s_char_ctrl->u8_billing_flag == 1) //进行充电计量计费
{
v_meterlog_charge_calculate(u8_gunNo); //周期计量计费
v_meterlog_log_updata(u8_gunNo); //更新充电结束信息
/* 充电中:每 60 秒将订单临时记录写入 EEPROM,支持掉电续传 */
{
U32_T now = get_current_seconds();
if (s_char_ctrl->u32_last_temp_save_sec == 0 ||
u32_safe_seconds_since(s_char_ctrl->u32_last_temp_save_sec) >= 60)
{
v_meterlog_flash_save_log(0, u8_gunNo);
s_char_ctrl->u32_last_temp_save_sec = now;
}
}
}
else //不进行计量计费
{
s_char_ctrl->u64_whm_start_energy = 0; //周期电表电量值清零
s_char_ctrl->u32_last_temp_save_sec = 0;
if(u8_cheak_fault_flag(E_FAULT_17007, (E_FAULT_SITE)((U16_T)E_GUN + (U16_T)u8_gunNo)) == 1U) //若电表跳变异常,不计量计费时恢复
{
v_meterlog_set_fault(u8_gunNo,0,0,0,0); //设置系统故障恢复
}
}
}
}
/**
* ************************************************************************
* @brief -计量计费判断
*
*
*
* @author jiankalka
* @date 2025-09-24
* ************************************************************************
*/
static void v_meterlog_action_cheak()
{
U8_T u8_gunNo;
S_CHAR_DATA *s_char_ctrl = NULL;
S_LOG_DATA *s_gun_log = NULL;
//终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
U8_T u8_flow_state;
//遍历所有的枪 依次进行计量判断
for (u8_gunNo = 0; u8_gunNo < s_metering_ctrl.u8_gunCnt; u8_gunNo++)
{
s_char_ctrl = &s_metering_ctrl.s_char_data[u8_gunNo]; //获取枪控制数据
s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo]; //获取枪订单信息
u8_flow_state = u32_meterlog_get_sys_data(u8_gunNo,E_METERLOG_GET_DATA_GUN_FLOW_STATE,0);
if(u8_flow_state > 1 && u8_flow_state != 10) //后续考虑是否需要增加 充电后5秒钟再进行一次计量的判断--计量计费检测
{
if(s_char_ctrl->u8_billing_flag == 0)
{
s_char_ctrl->u8_billing_flag = 1; //开始计量计费
METERFEE_LOG("[%d]Gun(0-N) metering start\n", u8_gunNo);
}
if(u8_flow_state == 7)
s_gun_log->u8_charge_flag = 1; //进入充电中标志
s_char_ctrl->u32_billing_time = get_current_seconds(); //更新超时起始时间
}
else
{
if(u8_flow_state == 0 || u8_flow_state == 10)
{
if(u32_safe_seconds_since(s_char_ctrl->u32_billing_time) > 7) //处于充电完成状态7S后停止计量,确保最后一次计量完成
{
if(s_char_ctrl->u8_billing_flag != 0)
{
s_char_ctrl->u8_billing_flag = 0;
v_set_plat_log_flag(u8_gunNo); //设置平台订单上送标志
v_meterlog_flash_save_log(1 , u8_gunNo); //设置订单信息存储
METERFEE_LOG("[%d]Gun(0-N) metering stop\n", u8_gunNo);
}
}
}
else
{
s_char_ctrl->u32_billing_time = get_current_seconds(); //更新超时起始时间
}
}
}
}
#endif
void v_metering_billing_data_init(void)
{
memset(&s_metering_ctrl , 0 ,sizeof(S_METERING_BILLING_CTRL));
s_metering_ctrl.u8_gunCnt = u32_meterlog_get_sys_data(0, E_METERLOG_GET_DATA_GUN_CNT , 0); //获取枪数量
// 初始化充电订单Flash存储模块
v_chg_order_flash_init();
//v_chg_order_clear_all();
v_unsettled_order_init();
v_meterlog_restore_temp_from_eeprom();
//Calculate_LOG("充电订单Flash存储模块初始化完成\r\n");
}
void v_meterfee_task(void *arg)
{
uint16_t sleep_time_ms;
uint32_t last_send_time;
uint32_t current_time;
(void)arg;
(void)METERFEE_LOCK_INIT();
sleep_time_ms = METERFEE_SLEEP_TIME_MS();
last_send_time = get_current_seconds();
v_metering_billing_data_init();
for (;;) {
METERFEE_FEED_DOG();
current_time = get_current_seconds();
if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) {
if (METERFEE_LOCK_TAKE() == 1U) {
METERFEE_LOG("meter fee task is runing, sleep time is %ums", sleep_time_ms);
(void)METERFEE_LOCK_GIVE();
}
last_send_time = current_time;
}
v_meterlog_action_cheak();
v_meterlog_ctrl();
if (s_metering_ctrl.u16_timer < 0xFF00U) {
s_metering_ctrl.u16_timer += sleep_time_ms;
} else {
s_metering_ctrl.u16_timer = 0U;
}
mSleep(sleep_time_ms);
}
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef CALCULATE_TASK_H
#define CALCULATE_TASK_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "meter_calculate/meter_calculate_impl.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
typedef struct
{
U8_T u8_billing_flag; //计费标志 0不进行计量计费 1进行计量计费 -- 注意,充电结束后应延时足够的时间,保证电量更新完全
U8_T u8_discharge_flag; //充放电标志 0常规充电 1放电
U32_T u32_billing_time; //充电结束后计量延时起始时间
U64_T u64_whm_start_energy; //循环起始电量值 精度 0.0001kW 用于存储上次周期计算电表低值
/* 充电中临时订单写入节流(秒):上次写入时间戳 */
U32_T u32_last_temp_save_sec;
}S_CHAR_DATA;
typedef struct
{
U8_T u8_period_time; //当前费率时段 0-95 代表一天15分钟的时段位置
U32_T u32_money_rate; //当前电费 单位0.0001元
U32_T u32_money_service; //当前服务费 单位0.0001元
}S_REAL_FEE_DATA;
typedef struct
{
U8_T u8_gunCnt; //枪数量
U16_T u16_timer; //周期计数
S_REAL_FEE_DATA s_real_fee_data[NUM_2]; //实时电费信息 - 0代表充电电费 1代表放电电费
S_CHAR_DATA s_char_data[GUN_MAX_CNT]; //充电计费 控制信息
S_LOG_DATA s_gun_log[GUN_MAX_CNT]; //枪订单信息
}S_METERING_BILLING_CTRL;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_meterfee_task(void *arg);
#ifdef __cplusplus
}
#endif
#endif /* CALCULATE_TASK_H */
+583
View File
@@ -0,0 +1,583 @@
#include "meter_calculate/meter_calculate_impl.h"
#include "publicdata/publicdata.h"
#include "flowctrl/flow_interface.h"
#include "bms/bms_interface.h"
#include "meter_calculate/meter_coll_task.h"
#include "flash_file_mgr/meter_calculate_flash_impl.h"
#include "flash_file_mgr/unsettled_order_mng.h"
#include "mylog/mylog.h"
#include "plat_comm/impl/bs_public_impl.h"
#include "app_rtc/app_rtc.h"
#include "meter_calculate/calculate_task.h"
#include <string.h>
#define METERFEE_LOG(fmt, ...) MYLOG_MSG(TASK_ID_Meterfee, (fmt), ##__VA_ARGS__)
/*
* 说明:
* - CCU601E_D 的实现包含订单Flash/EEPROM持久化与平台结算逻辑;
* - CCU621_M 当前工程目录未包含对应 flash_file_mgr/unsettled_order_mng 模块,
* 因此此处先提供“计量计费核心数据获取 + 故障上报”功能,持久化接口留空实现,
* 后续如引入存储模块再补齐。
*/
extern S_METERING_BILLING_CTRL s_metering_ctrl;
/**
* ************************************************************************
* @brief - 电量计量任务 获取外部数据
*
* @param[in] u8_gunNo - 枪号 0-N
* @param[in] e_dataType - 数据类型 详看枚举定义
* @param[in] u16_dataPara - 数据参数
*
* @return U32_T - 获取数据返回值
*
* @author jiankalka
* @date 2025-09-15
* ************************************************************************
*/
U32_T u32_meterlog_get_sys_data(U8_T u8_gunNo , E_METERLOG_GET_DATA_TYPE e_dataType , U16_T u16_dataPara)
{
U32_T u32_ret = 0;
(void)u16_dataPara;
switch (e_dataType)
{
case E_METERLOG_GET_DATA_GUN_CNT:
u32_ret = g_t_share_data.t_sys_fix_cfg.u8_gunCnt;
break;
case E_METERLOG_GET_DATA_FEE_RATE_DATA: //费率电费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
/* 费率数据结构若未初始化,返回 0 */
u32_ret = 0;
if (u8_gunNo < FEE_MODEL_CNT) {
U8_T u8_type = (U8_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.u8_fee_type[u8_gunNo]);
if (u8_type > 0) {
u8_type = (U8_T)(u8_type - 1U);
}
if (u8_type >= FEE_MODEL_TYPE_CNT) {
u8_type = 0;
}
u32_ret = (U32_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.s_fee_data[u8_type].u32_rate/10U);
}
break;
case E_METERLOG_GET_DATA_FEE_SERVER_DATA: //费率服务费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
u32_ret = 0;
if (u8_gunNo < FEE_MODEL_CNT) {
U8_T u8_type = (U8_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.u8_fee_type[u8_gunNo]);
if (u8_type > 0) {
u8_type = (U8_T)(u8_type - 1U);
}
if (u8_type >= FEE_MODEL_TYPE_CNT) {
u8_type = 0;
}
u32_ret = (U32_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.s_fee_data[u8_type].u32_sevice/10U);
}
break;
case E_METERLOG_GET_DATA_GUN_SOC: //当前soc值
u32_ret = (U32_T)u16_bms_put_data(u8_gunNo, E_BMS_PUT_DATA_GUN_SOC, 0);
break;
case E_METERLOG_GET_DATA_GUN_PUT_VOL: //当前充电电压 精度 0.1V
u32_ret = (U32_T)(u64_get_meter_data(u8_gunNo, 0, E_METER_DATA_VOL) / 1000U);
break;
case E_METERLOG_GET_DATA_GUN_PUT_CUR: //当前充电电流 精度 0.1A
u32_ret = (U32_T)(u64_get_meter_data(u8_gunNo, 0, E_METER_DATA_CUR) / 1000U);
break;
case E_METERLOG_GET_DATA_GUN_FLOW_STATE: //终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
u32_ret = (U32_T)u16_flow_put_data(u8_gunNo, E_FLOW_PUT_DATA_GUN_REAL_STATE, 0);
break;
default:
break;
}
return u32_ret;
}
/**
* ************************************************************************
* @brief - 获取枪电表电量值
*
* @param[in] u8_gunNo - 枪号 0-n
* @param[in] u8_type - 数据类型 0组合有功电量 1正向有功电量 2反向有功电量
*
* @return U64_T -
*
* @author jiankalka
* @date 2025-09-15
* ************************************************************************
*/
U64_T u64_meterlog_get_meter_data(U8_T u8_gunNo, U8_T u8_type)
{
U64_T u64_ret = 0;
/* 当前工程仅提供组合/正向有功电量:统一取 E_METER_DATA_POWER */
(void)u8_type;
u64_ret = u64_get_meter_data(u8_gunNo, 0, E_METER_DATA_POWER);
return u64_ret;
}
/**
* ************************************************************************
* @brief - 设置电量计量异常故障 用于外部触发故障告警
*
* @param[in] u8_flag - 故障类型 0电表跳变故障 1电量长时间未更新故障 2对时时间不合法故障
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
void v_meterlog_set_fault(U8_T u8_gunNo, U8_T u8_flag,U8_T u8_fault_flag,U16_T u16_data1,U16_T u16_data2)
{
E_FAULT_TYPE e_fault_status = (u8_fault_flag != 0U) ? E_FAULT : E_RES;
if (u8_flag == 0U) {
v_set_fault_data((E_FAULT_SITE)((U16_T)E_GUN + (U16_T)u8_gunNo), e_fault_status, E_FAULT_17007, 0U, u16_data1, u16_data2);
} else if (u8_flag == 1U) {
v_set_fault_data((E_FAULT_SITE)((U16_T)E_GUN + (U16_T)u8_gunNo), e_fault_status, E_FAULT_17008, 0U, u16_data1, u16_data2);
} else if (u8_flag == 2U) {
v_set_fault_data(E_WHOLE_HOST, e_fault_status, E_FAULT_17009, 0U, u16_data1, u16_data2);
} else {
/* ignore */
}
}
#if 1 //对外提供接口
/**
* ************************************************************************
* @brief - 启动充电时 赋值订单信息
*
* @param[in] u8_gunNo - 枪号 0-N
* @param[in] s_log_start_data - 启动充电订单数据信息
*
*
* @author jiankalka
* @date 2025-09-12
* ************************************************************************
*/
void v_meterlog_set_start_char_log_info(U8_T u8_gunNo,S_CHAR_LOG_START_DATA *s_log_start_data)
{
if(u8_gunNo >= GUN_MAX_CNT ) return;
S_LOG_DATA *s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo];
Comm_Time time;
GetCurrentTime(&time);
memset(s_gun_log , 0 ,sizeof(S_LOG_DATA));
//赋值订单信息
s_gun_log->e_start_reason = s_log_start_data->e_start_reason ; //启动充电原因
s_gun_log->e_sdk_startType = s_log_start_data->e_sdk_startType; //启动方式 sdk订单上传使用
s_gun_log->e_user_type = s_log_start_data->e_user_type ; //用户类型 --积成平台使用
s_gun_log->u32_balance_begin = s_log_start_data->u32_balance_begin; ////启动充电前金额 0.0001元
memcpy(s_gun_log->u8_preTradeNo ,s_log_start_data->u8_preTradeNo, EVS_MAX_TRADE_LEN); //平台交易流水号:8位桩地址+2位枪号+12位时间+补0(ascii码后面补0,相当于结束符)
memcpy(s_gun_log->u8_tradeNo ,s_log_start_data->u8_tradeNo, EVS_MAX_TRADE_LEN); //设备交易流水号
memcpy(s_gun_log->u8_stop_cmd ,s_log_start_data->u8_stop_cmd, 4); //停机码 4位
memcpy(s_gun_log->u8_user_id ,s_log_start_data->u8_user_id, 32); //用户标识
memcpy(s_gun_log->u8_vinCode ,s_log_start_data->u8_vinCode, 18); //车辆vin
//获取初始值
memcpy(&s_gun_log->StartChargeTime, &time,sizeof(Comm_Time)); //充电开始时间
s_gun_log->u64_startKWh = u64_meterlog_get_meter_data(u8_gunNo,0); //当前电表数据
s_gun_log->u8_gunNo = u8_gunNo; //抢号信息
METERFEE_LOG("[%d]Gun(0-N) start order info set\n", u8_gunNo);
}
/**
* ************************************************************************
* @brief - 停止充电时 赋值订单信息
*
* @param[in] u8_gunNo - 枪号 0-N
* @param[in] e_stop_reason - 故障原因
*
*
* @author jiankalka
* @date 2025-09-12
* ************************************************************************
*/
void v_meterlog_set_stop_char_log_info(U8_T u8_gunNo,E_FAULT_INFO e_stop_reason)
{
if(u8_gunNo >= GUN_MAX_CNT ) return;
S_LOG_DATA *s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo];
Comm_Time time;
U8_T i,u8_soc;
GetCurrentTime(&time);
for (i = 0; i < NUM_5; i++)
{
if(s_gun_log->e_stop_reason[i] == 0)
{
s_gun_log->e_stop_reason[i] = e_stop_reason;
break;
}
}
//获取车辆VIN码
v_bms_put_vin_str(u8_gunNo,s_gun_log->u8_vinCode,18);
//获取结束时间值
memcpy(&s_gun_log->EndChargeTime, &time,sizeof(Comm_Time)); //充电开始时间
s_gun_log->u64_endKWh = u64_meterlog_get_meter_data(u8_gunNo,0); //当前电表数据
u8_soc = u32_meterlog_get_sys_data(u8_gunNo,E_METERLOG_GET_DATA_GUN_SOC,0);
if(u8_soc != 0)
s_gun_log->u8_endSoc = u8_soc; //SOC
METERFEE_LOG("[%d]Gun(0-N) stop order info set, stopRsn=%d\n", u8_gunNo, e_stop_reason);
}
/**
* ************************************************************************
* @brief - 对外获取充电订单信息
*
* @param[in] u8_gunNo - 抢号 0-N
* @param[in] s_log - 订单结构体指针
*
*
* @author jiankalka
* @date 2025-09-22
* ************************************************************************
*/
void v_meterlog_put_char_log_data(U8_T u8_gunNo,S_LOG_DATA *s_log)
{
memset(s_log , 0, sizeof(S_LOG_DATA));
if(u8_gunNo > GUN_MAX_CNT) return;
memcpy(s_log , &s_metering_ctrl.s_gun_log[u8_gunNo],sizeof(S_LOG_DATA));
}
/**
* ************************************************************************
* @brief - 设置充电订单信息
*
* @param[in] u8_gunNo - 抢号 0-N
* @param[in] s_log_start_data - 设置订单信息 其中数组数据设置NULL代表无需改动
*
*
* @author jiankalka
* @date 2025-09-22
* ************************************************************************
*/
void v_meterlog_set_char_log_info(U8_T u8_gunNo, E_METERLOG_SET_LOG_INFO_TYPE e_type , U32_T *u32_data, U8_T *u8_str)
{
S_LOG_DATA *s_gun_log;
if (u8_gunNo >= GUN_MAX_CNT) {
return;
}
s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo];
switch (e_type)
{
case E_METERLOG_SET_LOG_INFO_u32_start_reason : //启动充电原因
if (u32_data != NULL) s_gun_log->e_start_reason = (START_REASON)(*u32_data);
break;
case E_METERLOG_SET_LOG_INFO_u32_sdk_startType: //启动方式 sdk订单上传使用
if (u32_data != NULL) s_gun_log->e_sdk_startType = (E_SDK_START_TYPE)(*u32_data);
break;
case E_METERLOG_SET_LOG_INFO_u32_user_type: //用户类型 --积成平台使用
if (u32_data != NULL) s_gun_log->e_user_type = (USER_TYPE_E)(*u32_data);
break;
case E_METERLOG_SET_LOG_INFO_u32_balance_begin: //启动充电前金额 0.0001元
if (u32_data != NULL) s_gun_log->u32_balance_begin = *u32_data;
break;
case E_METERLOG_SET_LOG_INFO_char_vinCode: //车辆vin
if (u8_str != NULL) strncpy((char *)s_gun_log->u8_vinCode, (char *)u8_str, sizeof(s_gun_log->u8_vinCode));
break;
case E_METERLOG_SET_LOG_INFO_char_user_id: //用户标识
if (u8_str != NULL) strncpy((char *)s_gun_log->u8_user_id, (char *)u8_str, sizeof(s_gun_log->u8_user_id));
break;
case E_METERLOG_SET_LOG_INFO_char_stop_cmd: //停机码 4位
if (u8_str != NULL) strncpy((char *)s_gun_log->u8_stop_cmd, (char *)u8_str, sizeof(s_gun_log->u8_stop_cmd));
break;
case E_METERLOG_SET_LOG_INFO_char_tradeNo: //设备交易流水号
if (u8_str != NULL) strncpy((char *)s_gun_log->u8_tradeNo, (char *)u8_str, sizeof(s_gun_log->u8_tradeNo));
break;
case E_METERLOG_SET_LOG_INFO_char_preTradeNo: //平台交易流水号:
if (u8_str != NULL) strncpy((char *)s_gun_log->u8_preTradeNo, (char *)u8_str, sizeof(s_gun_log->u8_preTradeNo));
/* StartTransaction 取得 txId 后立即落 EEPROM,避免掉电前 60s 周期未到而丢失流水号 */
if (s_metering_ctrl.s_char_data[u8_gunNo].u8_billing_flag != 0U) {
(void)v_save_temp_chg_record_to_eeprom(u8_gunNo, s_gun_log, TEMP_CHG_FLAG_UNSETTLED);
}
break;
default:
break;
}
}
/**
* ************************************************************************
* @brief - 设置充电订单信息上送
*
* @param[in] u8_gunNo - 抢号 0-N
*
*
* @author jiankalka
* @date 2025-09-25
* ************************************************************************
*/
void v_set_plat_log_flag(U8_T u8_gunNo)
{
v_bs_log_txFlag_tradUpload(u8_gunNo);
}
/**
* ************************************************************************
* @brief -输出订单中指定数据
*
* @param[in] u8_gunNo -枪号
* @param[in] e_typeData -数据枚举
*
* @return U32_T -读取的数据
*
* @author YZG
* @date 2025-09-29
* ************************************************************************
*/
U32_T u32_meterlog_put_data(U8_T u8_gunNo,E_METERLOG_PUT_LOG_INFO_TYPE e_typeData ,U8_T *u8_str , U16_T u16_len)
{
U32_T u32_ret = 0;
if(u8_str != NULL)
memset(u8_str , 0 , u16_len);
switch (e_typeData)
{
case E_METERLOG_PUT_LOG_INFO_USED_MONEY: //以充电费金额,精度0.0001元
{
u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u32_used_money;
break;
}
case E_METERLOG_PUT_LOG_INFO_USED_SERVICE: //以充服务费金额,精度0.0001元
{
u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u32_used_service;
break;
}
case E_METERLOG_PUT_LOG_INFO_USED_ENERGY: //以充电量,精度0.0001kWh
{
u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u32_used_energy;
break;
}
case E_METERLOG_PUT_LOG_INFO_START_TYPE: //启动充电方式 START_REASON
{
u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].e_start_reason;
break;
}
case E_METERLOG_PUT_LOG_INFO_USER_ID: //用户ID
{
if(u8_str == NULL) break;
strncpy((char *)u8_str,(char *)s_metering_ctrl.s_gun_log[u8_gunNo].u8_user_id,u16_len);
}
case E_METERLOG_PUT_LOG_INFO_CHAR_TIME: //充电开始时间
{
u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u16_ChargedTime;
break;
}
default:
break;
}
return u32_ret;
}
/**
* ************************************************************************
* @brief -计算下一循环的金额
*
* @param[in] u8_gunNo -枪号
*
* @return U32_T -下一循环金额
*
* @author YZG
* @date 2025-10-09
* ************************************************************************
*/
U32_T u32_meterlog_next_cycle_money(U8_T u8_gunNo)
{
Comm_Time time;
U32_T u32_next_cycle_money = 0,u32_out_vol = 0, u32_out_curr = 0; //下个周期金额 精度0.0001元,输出电压 精度0.1V,输出电流 精度0.1A
U32_T u32_power = 0,u32_energy = 0; //充电功率,下周期充电电量
S_REAL_FEE_DATA *s_real_fee_data = &s_metering_ctrl.s_real_fee_data[s_metering_ctrl.s_char_data[u8_gunNo].u8_discharge_flag]; //获取当前费率信息 默认已经更新正确
GetCurrentTime(&time);
u32_out_vol = u64_get_meter_data(u8_gunNo,0,E_METER_DATA_VOL)/1000; //输出电压,精度0.1
u32_out_curr = u64_get_meter_data(u8_gunNo,0,E_METER_DATA_CUR)/1000; //输出电流,精度0.1
u32_power = u32_out_vol * u32_out_curr /10; //充电功率 精度 0.0001kw(0.01W /10 = 0.0001kw)
u32_energy = u32_power/ 3600.0 * 10;//10S充电电能,单位0.0001kwh
//下一周期金额 精度0.0001元
u32_next_cycle_money = (U32_T)(u32_energy *(s_real_fee_data->u32_money_rate + s_real_fee_data->u32_money_service) / 10000);
//METERFEE_LOG("充电功率:%d,充电电量:%d,预判金额:%d\n",u32_power,u32_energy,u32_next_cycle_money);
return u32_next_cycle_money;
}
/**
* ************************************************************************
* @brief - 存储订单操作函数
*
* @param[in] u8_flag - 0 为数据更新 1为订单结束存储
* @param[in] u8_gunNo - 枪号 0-1
*
*
* @author jiankalka
* @date 2026-01-19
* ************************************************************************
*/
static U8_T u8_meterlog_pre_trade_no_valid(const U8_T *pre)
{
if (pre == NULL || pre[0] == '\0' || pre[0] == (U8_T)0xFF) {
return 0U;
}
if (atoi((const char *)pre) <= 0) {
return 0U;
}
return 1U;
}
/** 订单结束写 Flash 前:RAM 被复位时从 EEPROM 临时记录补全 txId 等字段 */
static void v_meterlog_merge_temp_into_log(U8_T u8_gunNo, S_LOG_DATA *s_gun_log)
{
S_LOG_DATA temp;
if (s_gun_log == NULL || u8_meterlog_pre_trade_no_valid(s_gun_log->u8_preTradeNo) != 0U) {
return;
}
if (v_check_temp_chg_record_valid(u8_gunNo) != 1U) {
return;
}
if (v_read_temp_chg_record_from_eeprom(u8_gunNo, &temp, NULL) != 0U) {
return;
}
if (u8_meterlog_pre_trade_no_valid(temp.u8_preTradeNo) == 0U) {
return;
}
memcpy(s_gun_log->u8_preTradeNo, temp.u8_preTradeNo, EVS_MAX_TRADE_LEN);
if (s_gun_log->u8_user_id[0] == '\0' && temp.u8_user_id[0] != '\0') {
memcpy(s_gun_log->u8_user_id, temp.u8_user_id, sizeof(s_gun_log->u8_user_id));
}
if (temp.StartChargeTime.iYear != 0 && s_gun_log->StartChargeTime.iYear == 0) {
memcpy(&s_gun_log->StartChargeTime, &temp.StartChargeTime, sizeof(Comm_Time));
}
if (s_gun_log->u64_startKWh == 0U && temp.u64_startKWh != 0U) {
s_gun_log->u64_startKWh = temp.u64_startKWh;
}
METERFEE_LOG("[%d]Gun merge EEPROM temp txId=%s\r\n", u8_gunNo, (char *)s_gun_log->u8_preTradeNo);
}
void v_meterlog_restore_temp_from_eeprom(void)
{
U8_T gun;
for (gun = 0U; gun < GUN_MAX_CNT; gun++) {
if (v_check_temp_chg_record_valid(gun) != 1U) {
continue;
}
if (v_read_temp_chg_record_from_eeprom(gun, &s_metering_ctrl.s_gun_log[gun], NULL) != 0U) {
continue;
}
METERFEE_LOG("[%d]Gun restore order from EEPROM temp txId=%s\r\n",
gun, (char *)s_metering_ctrl.s_gun_log[gun].u8_preTradeNo);
}
}
void v_meterlog_flash_save_log(U8_T u8_flag , U8_T u8_gunNo)
{
if(u8_flag == 1) //订单结束存储
{
v_meterlog_merge_temp_into_log(u8_gunNo, &s_metering_ctrl.s_gun_log[u8_gunNo]);
// 同时保存到Flash存储(双备份)
U32_T orderIndex = u32_chg_order_save_record(&s_metering_ctrl.s_gun_log[u8_gunNo]);
s_metering_ctrl.s_gun_log[u8_gunNo].u32_index = orderIndex;
// 将订单索引添加到未结算订单列表(默认未收到平台结算)
if (orderIndex != 0)
{
u8_add_unsettled_order(orderIndex);
METERFEE_LOG("[%d]Gun(0-N) order add to unsettled, idx=%u\r\n", u8_gunNo, orderIndex);
}
// 清除EEPROM中的临时充电记录(设置为已结算状态)
v_clear_temp_chg_record_in_eeprom(u8_gunNo);
#if FATFS_ENABLE_ORDER
// 可选:同时保存到文件系统缓存
cache_add_order(&s_metering_ctrl.s_gun_log[u8_gunNo]);
METERFEE_LOG("[%d]Gun(0-N) order saved Flash/EEPROM + FS cache, add unsettled\r\n", u8_gunNo);
#else
METERFEE_LOG("[%d]Gun(0-N) order saved Flash/EEPROM, add unsettled\r\n", u8_gunNo);
#endif
}
else //订单数据更新(充电中周期更新)
{
// 更新EEPROM中的临时充电记录(设置为未结算状态)
v_save_temp_chg_record_to_eeprom(u8_gunNo, &s_metering_ctrl.s_gun_log[u8_gunNo], TEMP_CHG_FLAG_UNSETTLED);
//METERFEE_LOG("[%d]枪(0-N)充电订单数据已更新到EEPROM临时记录(未结算状态)\r\n", u8_gunNo);
}
}
/**
* ************************************************************************
* @brief - 平台结算确认接口
*
* 当平台确认订单结算后调用此函数,从未结算订单列表中移除对应的订单索引
*
* @param[in] orderIndex - 订单索引(由u32_chg_order_save_record返回)
* @return U8_T - 0-成功,1-失败(未找到该索引)
*
* @author jiankalka
* @date 2026-01-19
* ************************************************************************
*/
U8_T u8_platform_settlement_confirm(U32_T orderIndex)
{
U8_T ret = u8_remove_unsettled_order(orderIndex);
if (ret == 0)
{
METERFEE_LOG("Plat settle confirm OK, rm unsettled idx=%u\r\n", orderIndex);
}
else
{
METERFEE_LOG("Plat settle confirm fail, idx=%u NF\r\n", orderIndex);
}
return ret;
}
/**
* ************************************************************************
* @brief - 查询订单结算状态接口
*
* 查询指定订单是否已结算
*
* @param[in] orderIndex - 订单索引(由u32_chg_order_save_record返回)
* @return U8_T - 0-已结算,1-未结算
*
* @author jiankalka
* @date 2026-01-19
* ************************************************************************
*/
U8_T u8_check_order_settlement_status(U32_T orderIndex)
{
return u8_check_order_settled(orderIndex);
}
#endif
+181
View File
@@ -0,0 +1,181 @@
#ifndef METER_CALCULATE_IMPL_H
#define METER_CALCULATE_IMPL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "fault_cheak/fault_interface.h"
/*------ Exported macro(宏定义) -------------------------*/
#define METER_MAX_ELE (9999999999) //电表最大支持电量 xxxxxx.xxxx kWh
#define METER_PERIOD_MAX_ADDED_VAL (10000 * 10) //周期计算电量 最大增量 超过这个值 则认为是数据错误
#define EVS_MAX_TRADE_LEN (40 + 1) //订单流水号最大长度
#define Calculate_LOG(fmt, ...) \
MYLOG_MSG(TASK_ID_Meterfee,fmt, ##__VA_ARGS__) //任务打印
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
E_METERLOG_GET_DATA_GUN_CNT = 0, //枪数量
E_METERLOG_GET_DATA_FEE_RATE_DATA, //费率电费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
E_METERLOG_GET_DATA_FEE_SERVER_DATA, //费率服务费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元
E_METERLOG_GET_DATA_GUN_SOC, //当前soc值
E_METERLOG_GET_DATA_GUN_PUT_VOL, //当前充电电压 精度 0.1V
E_METERLOG_GET_DATA_GUN_PUT_CUR, //当前充电电流 精度 0.1A
E_METERLOG_GET_DATA_GUN_FLOW_STATE, //终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
}E_METERLOG_GET_DATA_TYPE;
//启动原因
typedef enum
{
CTR_CMD_NONE = 0, //无动作
CTR_CMD_CARD_START, //离线卡
CTR_CMD_ONCARD_START, //在线卡
CTR_CMD_BS_START, //后台启动(tcu/积成)\扫码启动
CtR_CMD_VIN_START, //vin启动
CTR_CMD_DEBUG, //调试启动
}START_REASON;
typedef enum //启动方式
{
E_SDK_STTY_APP = 10, //10:app一键启动
E_SDK_STTY_VIN, //11:即插即充
E_SDK_STTY_BLUETOOTH, //12:蓝牙离线启动
E_SDK_STTY_QR, //13:二维码启动
E_SDK_STTY_PILE, //14:桩侧扫码启动(桩自带识别器)
E_SDK_STTY_PLAT, //15:平台启动
E_SDK_STTY_BLE, //16 蓝牙启动//
E_SDK_STTY_VIN2, //17 离线 VIN 即插即充
}E_SDK_START_TYPE;
typedef enum /* 用户类型 */
{
USER_TYPE_ONLINE_CARD = 0, //在线卡
USER_TYPE_APP, //APP用户
USER_TYPE_OFFLINE_CARD, //离线卡
USER_TYPE_VIN, //VIN
USER_TYPE_WECHAT, //微信用户
USER_TYPE_ALIPAY, //支付宝
USER_TYPE_BAIDU, //百度用户
USER_TYPE_CAR_BRAND, //车牌,类似VIN
USER_TYPE_LCD, //触摸屏按键启动
USER_TYPE_UNICOM, //互联互通用户
}USER_TYPE_E;
typedef struct
{
START_REASON e_start_reason; //启动充电原因
E_SDK_START_TYPE e_sdk_startType; //启动方式 sdk订单上传使用
USER_TYPE_E e_user_type; //用户类型 --积成平台使用
U8_T u8_preTradeNo[EVS_MAX_TRADE_LEN]; //平台交易流水号:8位桩地址+2位枪号+12位时间+补0(ascii码后面补0,相当于结束符)
U8_T u8_tradeNo[EVS_MAX_TRADE_LEN]; //设备交易流水号
U8_T u8_stop_cmd[4]; //停机码 4位
U8_T u8_user_id[32]; //用户标识
U8_T u8_vinCode[18]; //车辆vin
U32_T u32_balance_begin; //启动充电前金额 0.0001元
}S_CHAR_LOG_START_DATA; //启动充电 赋值订单信息
typedef struct
{
U32_T u32_index; //订单索引,用于唯一标识和快速查找
START_REASON e_start_reason; //启动充电原因
E_FAULT_INFO e_stop_reason[NUM_5]; //停止原因
E_SDK_START_TYPE e_sdk_startType; //启动方式 sdk订单上传使用
USER_TYPE_E e_user_type; //用户类型 --积成平台使用
Comm_Time StartChargeTime; //开始充电时间
Comm_Time EndChargeTime; //停止充电时间
U8_T u8_gunNo; //充电枪编号
U8_T u8_charge_flag; //充电标志 是否进入到充电
U8_T u8_preTradeNo[EVS_MAX_TRADE_LEN]; //平台交易流水号:8位桩地址+2位枪号+12位时间+补0(ascii码后面补0,相当于结束符)
U8_T u8_tradeNo[EVS_MAX_TRADE_LEN]; //设备交易流水号
U8_T u8_startSoc; //启动时SOC
U8_T u8_endSoc; //结束时SOC
U8_T u8_stop_cmd[4]; //停机码 4位
U8_T u8_user_id[32]; //用户标识
U8_T u8_vinCode[18]; //车辆vin
U16_T u16_ChargedTime; //充电时间 min
U32_T u32_balance_begin; //启动充电前金额 0.0001元
U32_T u32_used_money; //己充金额,单位是0.0001元
U32_T u32_used_service; //己充服务费,单位是0.0001元
U64_T u64_startKWh; //电表总起示值 单位是0.0001KWh
U64_T u64_endKWh; //电表总止示值 单位是0.0001KWh
U32_T u32_used_energy; //己用电量,单位是0.0001KWh
U32_T u32_part_energy[FEE_MODEL_CNT]; //各时段使用电量,单位是0.0001KWh - 96个
}S_LOG_DATA;
typedef enum
{
E_METERLOG_SET_LOG_INFO_u32_start_reason = 0, //启动充电原因
E_METERLOG_SET_LOG_INFO_u32_sdk_startType, //启动方式 sdk订单上传使用
E_METERLOG_SET_LOG_INFO_u32_user_type, //用户类型 --积成平台使用
E_METERLOG_SET_LOG_INFO_u32_balance_begin, //启动充电前金额 0.0001元
E_METERLOG_SET_LOG_INFO_char_vinCode, //车辆vin
E_METERLOG_SET_LOG_INFO_char_user_id, //用户标识
E_METERLOG_SET_LOG_INFO_char_stop_cmd, //停机码 4位
E_METERLOG_SET_LOG_INFO_char_tradeNo, //设备交易流水号
E_METERLOG_SET_LOG_INFO_char_preTradeNo, //平台交易流水号:
}E_METERLOG_SET_LOG_INFO_TYPE;
typedef enum
{
E_METERLOG_PUT_LOG_INFO_USED_MONEY, //以充电费金额,精度0.0001元
E_METERLOG_PUT_LOG_INFO_USED_SERVICE, //以充服务费金额,精度0.0001元
E_METERLOG_PUT_LOG_INFO_USED_ENERGY, //以充电量,精度0.0001kWh
E_METERLOG_PUT_LOG_INFO_START_TYPE, //启动充电方式 START_REASON
E_METERLOG_PUT_LOG_INFO_USER_ID, //用户ID
E_METERLOG_PUT_LOG_INFO_CHAR_TIME, //充电时长 单位分钟
}E_METERLOG_PUT_LOG_INFO_TYPE;
typedef enum
{
E_METER_DATA_POWER = 0, //电表采集电量值 精度 0.0001
E_METER_DATA_VOL, //电表采集电压值 精度 0.0001
E_METER_DATA_CUR, //电表采集电流值 精度 0.0001
}E_METER_DATA_TYPE;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
U64_T u64_meterlog_get_meter_data(U8_T u8_gunNo ,U8_T u8_type);
U32_T u32_meterlog_get_sys_data(U8_T u8_gunNo , E_METERLOG_GET_DATA_TYPE e_dataType , U16_T u16_dataPara);
U64_T u64_get_meter_data(U8_T u8_gunNo ,U8_T u8_id , E_METER_DATA_TYPE e_type);
void v_meterlog_put_char_log_data(U8_T u8_gunNo,S_LOG_DATA *s_log);
void v_meterlog_set_char_log_info(U8_T u8_gunNo, E_METERLOG_SET_LOG_INFO_TYPE e_type , U32_T *u32_data, U8_T *u8_str);
void v_meterlog_set_start_char_log_info(U8_T u8_gunNo,S_CHAR_LOG_START_DATA *s_log_start_data);
void v_meterlog_set_stop_char_log_info(U8_T u8_gunNo,E_FAULT_INFO e_stop_reason);
void v_set_plat_log_flag(U8_T u8_gunNo);
void v_meterlog_set_fault(U8_T u8_gunNo, U8_T u8_flag,U8_T u8_fault_flag,U16_T u16_data1,U16_T u16_data2);
U32_T u32_meterlog_put_data(U8_T u8_gunNo,E_METERLOG_PUT_LOG_INFO_TYPE e_typeData,U8_T *u8_str , U16_T u16_len);
U32_T u32_meterlog_next_cycle_money(U8_T u8_gunNo);
void v_meterlog_flash_save_log(U8_T u8_flag , U8_T u8_gunNo);
/** 上电后从 EEPROM 临时记录恢复未完成订单到 RAM(掉电续传) */
void v_meterlog_restore_temp_from_eeprom(void);
U8_T u8_platform_settlement_confirm(U32_T orderIndex);
U8_T u8_check_order_settlement_status(U32_T orderIndex);
#ifdef __cplusplus
}
#endif
#endif /* METER_CALCULATE_IMPL_H */
@@ -0,0 +1,80 @@
# meter_calculate 模块说明
> 路径:`app/meter_calculate/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 提供电表采集与计量计费两类任务能力:`meter_coll_task` 负责电表数据采集,`meterfee_task` 负责订单计量与费用累计;
- 维护充电订单生命周期数据(开始、充电中、结束)并对外提供订单读写接口;
- 提供费率查询、周期计量、下周期金额预估能力;
- 对接故障系统,处理计量跳变、费率时间异常等故障置位/恢复;
- 对接 Flash/EEPROM 订单存储与未结算订单管理,支持掉电续传和平台结算确认。
## 2. 关键文件
- `meter_coll_task.c/.h`:电表通信采集任务与底层数据更新;
- `calculate_task.c/.h`:计量计费任务(`v_meterfee_task`)及计费控制结构;
- `meter_calculate_impl.c/.h`:计量对外接口、订单数据接口、故障与结算接口;
- `BSP/flash_file_mgr/meter_calculate_flash_impl.c/.h`:订单正式记录与临时订单 EEPROM/Flash 持久化;
- `BSP/flash_file_mgr/unsettled_order_mng.c/.h`:未结算订单索引管理。
## 3. 功能说明
### 3.1 任务与调度模型
- `v_meterfee_task()` 在系统任务表中按周期运行,流程为:
- 初始化计量控制对象 `s_metering_ctrl`
- 初始化订单存储模块 `v_chg_order_flash_init()` 与未结算模块 `v_unsettled_order_init()`
- 循环执行 `v_meterlog_action_cheak()`(计费启停状态机)和 `v_meterlog_ctrl()`(周期计费);
- 周期喂狗、日志输出与睡眠调度。
- 计量主循环按 5 秒节拍进行电量与费用更新,充电中每 60 秒刷新一次 EEPROM 临时订单。
### 3.2 计量与计费流程
- 通过 `u64_meterlog_get_meter_data()` 获取电表电量(0.0001kWh 精度);
- 首次计量记录起始电量,后续计算循环差分电量并累计到:
- 总电量 `u32_used_energy`
- 分时段电量 `u32_part_energy[period]`
- 电费 `u32_used_money`
- 服务费 `u32_used_service`
- `v_meterlog_real_fee_updata()` 根据当前时间计算 15 分钟时段索引(0~95),并更新充电/放电费率缓存;
- `u32_meterlog_next_cycle_money()` 提供下一周期金额预测(基于当前电压电流和费率)。
### 3.3 订单生命周期管理
- 启动充电:`v_meterlog_set_start_char_log_info()` 清零并写入启动订单关键信息(交易号、用户、VIN、起始电量、起始时间);
- 充电中:`v_meterlog_log_updata()` 持续更新结束时间、结束电量、SOC、已充时长;
- 停止充电:`v_meterlog_set_stop_char_log_info()` 写入停止原因并更新末态数据;
- 对外接口:
- `v_meterlog_put_char_log_data()` 读取订单完整结构;
- `v_meterlog_set_char_log_info()` 按字段更新订单;
- `u32_meterlog_put_data()` 按枚举提取关键订单字段。
### 3.4 存储与结算接口
- `v_meterlog_flash_save_log(u8_flag, u8_gunNo)`
- `u8_flag=0`:充电中更新 EEPROM 临时订单(未结算标志);
- `u8_flag=1`:结束后保存正式订单到 Flash,记录 `u32_index`,加入未结算列表,并清除临时订单。
- `u8_platform_settlement_confirm(orderIndex)`:平台确认结算后,从未结算列表移除订单;
- `u8_check_order_settlement_status(orderIndex)`:查询订单是否已结算(0 已结算,1 未结算)。
### 3.5 故障处理
- 计量相关故障由 `v_meterlog_set_fault()` 统一上报:
- `E_FAULT_17007`:电表跳变异常;
- `E_FAULT_17008`:电量长时间未更新;
- `E_FAULT_17009`:费率/时间异常。
- 计费停止后会执行对应恢复逻辑,避免故障状态长期残留。
## 4. 维护建议
- 调整计量周期、临时订单保存周期(当前 60 秒)时,需同步评估 EEPROM 擦写寿命与掉电恢复精度;
- 修改费率来源时,优先保持 `u32_meterlog_get_sys_data()` 作为统一入口,避免业务层直连全局配置;
- 若后续接入平台上送流程,可在 `v_set_plat_log_flag()` 增加上送触发逻辑并和未结算列表闭环验证;
- 建议联动验证三类场景:正常停机、异常掉电恢复、平台延迟结算。
## 5. 关联文档
- `BSP/flash_file_mgr/flash_file_mgr模块说明.md`
- 主索引:[README](../../README.md)
+853
View File
@@ -0,0 +1,853 @@
#include "meter_calculate/meter_coll_task.h"
#include "app_init/app_init.h"
#include "app_init/task_lock.h"
#include "mylog/mylog.h"
#include "publicdata/publicdata.h"
#include "wdt_task/wdt_task.h"
#include "usart.h"
#include "fault_cheak/fault_interface.h"
#define METERCOLL_TASK_ID (TASK_ID_MeterColl)
#define METERCOLL_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(METERCOLL_TASK_ID))
#define METERCOLL_LOCK_INIT() (u8_task_lock_ctrl(METERCOLL_TASK_ID, TASK_LOCK_OP_INIT))
#define METERCOLL_LOCK_TAKE() (u8_task_lock_ctrl(METERCOLL_TASK_ID, TASK_LOCK_OP_TAKE))
#define METERCOLL_LOCK_GIVE() (u8_task_lock_ctrl(METERCOLL_TASK_ID, TASK_LOCK_OP_GIVE))
#define METERCOLL_FEED_DOG() (v_wdt_setFlag(METERCOLL_TASK_ID))
#define METERCOLL_LOG(fmt, ...) MYLOG_MSG(METERCOLL_TASK_ID, (fmt), ##__VA_ARGS__)
#define D_Print_flag (0) //printf 打印标志
#define D_YaDa_Flag (1) //雅达电表 使用高精度指令不一样 其组合有功电量获取为 00 0E 00 00 XXXXX.XXX
#define D_FrameStart 0x68 /* 帧头 */
#define D_FrameEnd 0x16 /* 帧尾 */
//645协议
#define D_645_FCReadData 0x11 /* 读实时数据 */
#define D_645_FCReadDataResp 0x91 /* 读实时数据正常应答 */
//645协议标准精度与高精度度数据标识
#define DATA_645_ENERGY 0x00000000 //电量(标准)
#define DATA_645_VOL 0x00010102 //电压(标准)
#define DATA_645_CUR 0x00010202 //电流(标准)
#define DATA_645_HIGH_CUR 0x00011102 //电流(扩展)
#define DATA_645_HIGH_VOL 0x00011002 //电压(扩展帧)
#if (D_YaDa_Flag == 0)
#define DATA_645_HIGH_ENERGY 0x00006200 //电量(高精度)
#else
#define DATA_645_HIGH_ENERGY 0x00006000 //电量(高精度) 使用正向有功电量获取
#endif
#define DATE_645_DATA_NUM (6) //645协议标准内容数量
static const Meter_Data_Item st_MeterDataSheet[DATE_645_DATA_NUM] =
{
//数据标识 字节数 系数
{DATA_645_ENERGY, 4, 100, }, //组合电量 (标准) XXXXXX.XX kWh
{DATA_645_VOL, 2, 10, }, //电压 (标准) XXX.X V
{DATA_645_CUR, 3, 1000, }, //电流(标准)XXX.XXX A
{DATA_645_HIGH_ENERGY, 5, 10000, }, //组合电量(扩展帧)xxxxxx.xxxx kWh
{DATA_645_HIGH_VOL, 3, 100, }, //电压(扩展帧) xxxx.xx V
{DATA_645_HIGH_CUR, 4, 10000, }, //电流(扩展帧) xxxx.xxxx A
};
S_METER_CTRL_DATA_DATA s_memter_ctrl; //电表控制任务初始化
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
const U8_T u8_commPort[GUN_MAX_CNT] = {E_USART_6,E_USART_5}; //双枪对应通信口
#elif SYSTEM_TYPE == SYSTEM_TYPE_CCU621_M
const U8_T u8_commPort[GUN_MAX_CNT] = {E_USART_3,E_USART_4}; //双枪对应通信口
#else
const U8_T u8_commPort[GUN_MAX_CNT] = {E_USART_3,E_USART_4}; //双枪对应通信口
#endif
/****************************************************************************系统集成改动部分-开始******************************************************/
#if 1 //接口函数
/**
* ************************************************************************
* @brief - 电表数据发送接口函数 发送485数据
*
* @param[in] u8_gunNo - 枪号 0-1 函数内部自行匹配串口号
* @param[in] u8_sendBuf - 发送数据地址
* @param[in] u16_len - 发送数据长度
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static void v_com_meterSend(U8_T u8_gunNo , U8_T *u8_sendBuf , U16_T u16_len)
{
if(u8_gunNo >= METER_COMM_MAX_CNT) return;
u16_usart_send(u8_commPort[u8_gunNo],u8_sendBuf, u16_len);
#if D_Print_flag //调试打印
if(u8_gunNo == D_Print_flag - 1) {
printf("[%d] GUN meter send,len is [%d]\r\n",u8_gunNo , u16_len);
printf_hex("--",u8_sendBuf,u16_len);
}
#endif
}
/**
* ************************************************************************
* @brief - 电表数据接收接口函数 接收485数据
*
* @param[in] u8_gunNo - 枪号 0-1 函数内部自行匹配串口号
* @param[in] u8_recvBuf - 接收数据地址
* @param[in] u16_len - 预期接收数据长度
*
* @return U16_T - 已接收数据长度
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static U16_T u16_com_meterRecv(U8_T u8_gunNo , U8_T *u8_recvBuf ,U16_T u16_len)
{
U16_T recv_len = 0;
if(u8_gunNo >= METER_COMM_MAX_CNT) return 0;
recv_len = u16_usart_recv(u8_commPort[u8_gunNo],u8_recvBuf,u16_len);
#if D_Print_flag //调试打印
if(u8_gunNo == D_Print_flag - 1) {
if(recv_len == 0) return 0;
printf("[%d] GUN meter recv,len is [%d]\r\n",u8_gunNo , recv_len);
printf_hex("--",u8_recvBuf,recv_len);
}
#endif
return recv_len;
}
#endif
#if 1 //对外数据提供 --
/**2
* ************************************************************************
* @brief - 外部获取电表数据
*
* @param[in] u8_gunNo - 抢号 0-1
* @param[in] u8_id - 该枪的电表号 默认0为直流表 1为交流表
* @param[in] u8_type - 数据类型 0电量 1电压 2电流
*
* @return U64_T - 返回值 电压、电流、电量 -精度都为0.0001 4位小数
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
U64_T u64_get_meter_data(U8_T u8_gunNo ,U8_T u8_id , E_METER_DATA_TYPE e_type)
{
U64_T u64_ret = 0;
S_METER_DATA *s_meter_data =NULL;
//数据合法性判断
if(u8_gunNo >= GUN_MAX_CNT || u8_id >= METER_COMM_MAX_CNT || e_type >= METER_COMM_TYPE_CNT)
return u64_ret;
METERCOLL_LOCK_TAKE(); //数据上锁
s_meter_data = &s_memter_ctrl.s_meters[u8_gunNo][u8_id]; //获取电表数据地址
switch (e_type)
{
case E_METER_DATA_POWER:
u64_ret = s_meter_data->u64_energy;
break;
case E_METER_DATA_VOL:
u64_ret = s_meter_data->u32_voltage;
break;
case E_METER_DATA_CUR:
u64_ret = s_meter_data->u32_current;
break;
default:
break;
}
METERCOLL_LOCK_GIVE(); //数据解锁
return u64_ret;
}
/**
* ************************************************************************
* @brief - 获取电表号
*
* @param[in] u8_gunNo - 枪号 0-N
* @param[in] u8_id - 该枪的电表号 默认0为直流表 1为交流表
*
* @return U8_T - 0未获取 1获取
*
* @author jiankalka
* @date 2025-11-27
* ************************************************************************
*/
U8_T u8_get_meter_id_str(U8_T u8_gunNo ,U8_T u8_id ,U8_T *u8_getData , U8_T u8_len)
{
if(u8_gunNo >= GUN_MAX_CNT || u8_id >= METER_COMM_MAX_CNT)
return 0;
memset(u8_getData, 0, u8_len);
strncpy((char *)u8_getData , (char *)&s_memter_ctrl.s_meters[u8_gunNo][u8_id].u8_meterNumber[0] , u8_len);
return 1;
}
#endif
#if 1 //外部数据获取
/**
* ************************************************************************
* @brief - 获取电表地址
*
* @param[in] s_memter_data 电表数据
*
*
* @author jiankalka
* @date 2025-08-06
* ************************************************************************
*/
static void v_meter_get_para(S_METER_CTRL_DATA_DATA *s_memter_data)
{
U8_T i, k, cnt;
S_MEMET_PARA *s_para = &g_t_share_data.t_sys_fix_cfg.s_memter;
U8_T gun_cnt = g_t_share_data.t_sys_fix_cfg.u8_gunCnt;
if (s_memter_data == NULL) {
return;
}
if ((gun_cnt == 0U) || (gun_cnt > GUN_MAX_CNT)) {
gun_cnt = GUN_MAX_CNT;
}
s_memter_data->u8_gunCnt = gun_cnt; // 枪数量
for (i = 0; i < s_memter_data->u8_gunCnt; i++) {
cnt = 0;
for (k = 0; k < s_para->u8_meter_num; k++) { //遍历所有电表
if (s_para->s_data[k].u8_inGun == (U8_T)(i + 1U)) { //参数设置电表 为本枪 则保存数据
if (cnt >= METER_COMM_MAX_CNT) {
break; // 单枪电表位已满
}
s_memter_data->s_meters[i][cnt].u8_enFlag = 1; //使能标志 0不使能 1使能 用于区分该位置是否存在电表
s_memter_data->s_meters[i][cnt].u8_isAC = s_para->s_data[k].u8_isAC; // 是否为交流电表标识 (1:交流 0:直流)
s_memter_data->s_meters[i][cnt].u8_highPrec = s_para->s_data[k].u8_highPrec; //是否支持高精度 0 不支持 1支持
memcpy((char *)&s_memter_data->s_meters[i][cnt].u8_meterNumber[0],
(char *)&s_para->s_data[k].u8_meterAddr[0],
6);
cnt++;
}
}
}
}
/**
* ************************************************************************
* @brief - 设置电表通信超时状态 用于外部触发故障告警
*
* @param[in] u8_gunNo - 枪号 0-1
* @param[in] u8_id - 该枪电表序号 默认0直流表 1交流表
* @param[in] u8_flag - 故障动作 0通信恢复 1 通信超时故障
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static void v_meter_set_comm_timeout(U8_T u8_gunNo , U8_T u8_id , U8_T u8_flag)
{
if(u8_flag == 1)
{
v_set_fault_data((E_FAULT_SITE)(u8_gunNo + E_GUN), E_FAULT, E_FAULT_13071, 0, u8_id, 0); //电表通讯故障
}
else
v_set_fault_data((E_FAULT_SITE)(u8_gunNo + E_GUN), E_RES, E_FAULT_13071, 0, u8_id, 0); //电表通讯故障
return;
}
#endif
/****************************************************************************系统集成改动部分-结束******************************************************/
/****************************************************************************内部逻辑-开始******************************************************/
#if 1 //发送控制
/*************************************************************
函数名称: u8_645_SendFrame(U8_T u8_frm_type, MeterAddress meteraddr, U32_T datesign, U8_T* u8_sendBuf)
函数功能: 打包组帧发送645协议命令帧
输入参数: u8_frm_type 控制码 meteraddr 电表地址 datesign 数据标识 u8_sendBuf 需要发送的数据
输出参数:
**************************************************************/
static void v_645_SendFrame(S_METER_FRAME *s_meterFrame) //组帧发送
{
U8_T u8_cnt = 0;
U8_T CheckSum;
U8_T u8_sendBuf[NUM_64] = {0};
u8_sendBuf[u8_cnt++] = D_FrameStart; //帧起始域
memcpy(&u8_sendBuf[u8_cnt], &s_meterFrame->address[0], 6);
u8_cnt += 6;
u8_sendBuf[u8_cnt++] = D_FrameStart; //帧起始域
u8_sendBuf[u8_cnt++] = s_meterFrame->control_code; //控制码 0x11 读数据
u8_sendBuf[u8_cnt++] = 0x04; //数据长度
// 数据标识(拆分为高字节和低字节)
u8_sendBuf[u8_cnt++] = ((s_meterFrame->u32_datesign >> 24) & 0xFF) + 0x33;
u8_sendBuf[u8_cnt++] = ((s_meterFrame->u32_datesign >> 16) & 0xFF) + 0x33;
u8_sendBuf[u8_cnt++] = ((s_meterFrame->u32_datesign >> 8) & 0xFF) + 0x33;
u8_sendBuf[u8_cnt++] = ((s_meterFrame->u32_datesign) & 0xFF) + 0x33;
/* 打包校验和与帧尾 */
CheckSum = u16_crc_checksum(u8_sendBuf, u8_cnt);
u8_sendBuf[u8_cnt++] = CheckSum;
u8_sendBuf[u8_cnt++] = D_FrameEnd;
v_com_meterSend(s_meterFrame->u8_gunNo,u8_sendBuf, u8_cnt);
}
/**
* ************************************************************************
* @brief - 电表数据采集发送组帧函数
*
* @param[in] u8_gunNo - 枪地址索引号 0-1
* @param[in] u8_type - 0 电量 1电压 2电流
* @param[in] s_meter_data - 电表数据
*
*
* @author jiankalka
* @date 2025-08-06
* ************************************************************************
*/
static void v_memter_SendFrame(U8_T u8_gunNo, U8_T u8_type , S_METER_DATA *s_meter_data)
{
S_METER_FRAME s_meterFrame;
memset(&s_meterFrame , 0, sizeof(S_METER_FRAME));
s_meterFrame.u8_gunNo = u8_gunNo;
s_meterFrame.control_code = D_645_FCReadData; //控制码 0x11 读数据
memcpy(&s_meterFrame.address[0], s_meter_data->u8_meterNumber, 6);
if(u8_type % 3 == 0){ //电量
if(s_meter_data->u8_highPrec == 1) //高精度
s_meterFrame.u32_datesign = DATA_645_HIGH_ENERGY;
else
s_meterFrame.u32_datesign = DATA_645_ENERGY;
}
else if(u8_type % 3 == 1){ //电压
if(s_meter_data->u8_highPrec == 1) //高精度
s_meterFrame.u32_datesign = DATA_645_HIGH_VOL;
else
s_meterFrame.u32_datesign = DATA_645_VOL;
}
else if(u8_type % 3 == 2){ //电流
if(s_meter_data->u8_highPrec == 1) //高精度
s_meterFrame.u32_datesign = DATA_645_HIGH_CUR;
else
s_meterFrame.u32_datesign = DATA_645_CUR;
}
//printf("send ctrl , gun(%d) id(0x%08x) \n",s_meterFrame.u8_gunNo ,s_meterFrame.u32_datesign );
v_645_SendFrame(&s_meterFrame);
}
/**
* ************************************************************************
* @brief - 电表数据采集发送控制函数 用于对两路电表分别进行电压、电流、电量的数据采集发送--
*
* @param[in] u16_sleepTime - 任务睡眠时间
*
*
* @author jiankalka
* @date 2025-08-06
* ************************************************************************
*/
static void v_meter_send_ctrl(U16_T u16_sleepTime)
{
U8_T i;
S_METER_DATA *s_meter_data =NULL;
S_METER_COMM_CTRL *s_comm_ctrl = NULL;
for (i = 0; i < s_memter_ctrl.u8_gunCnt; i++) //遍历所有枪数量
{
s_comm_ctrl = &s_memter_ctrl.s_commCtrl[i]; //获取通信控制内容
s_meter_data = &s_memter_ctrl.s_meters[i][s_comm_ctrl->u8_send_type/METER_COMM_TYPE_CNT]; //获取该枪电表首地址 当send type 大于3时,则为2枪数据
if(s_meter_data->u8_enFlag == 0) //不存在电表 则不执行; 当电表2不存在时,send type从0开始执行
{
s_comm_ctrl->u8_send_type = 0;
continue;
}
if(s_comm_ctrl->u8_recv_flag == 0) //未存在 等待接收数据
{
v_memter_SendFrame(i,s_comm_ctrl->u8_send_type, s_meter_data); //控制数据发送
if(++s_comm_ctrl->u8_send_type >= METER_COMM_TYPE_CNT*2) //发送数据类型 递增
{
s_comm_ctrl->u8_send_type = 0;
}
s_comm_ctrl->u8_recv_flag = 1; //发送后等待数据接收
s_comm_ctrl->u16_recv_timeout_cnt = 0; //超时计数清空
}
else //等待接收状态 判断超时
{
s_comm_ctrl->u16_recv_timeout_cnt += u16_sleepTime;
if(s_comm_ctrl->u16_recv_timeout_cnt >= METER_RECV_TIMEOUT_MS) //数据接收超时时间 600ms ,则清空标识 重新进行发送
{
s_comm_ctrl->u8_recv_flag = 0;
s_comm_ctrl->u16_recv_timeout_cnt = 0; //超时计数清空
}
}
}
}
#endif
#if 1 //接收解析
/**
* ************************************************************************
* @brief - 电表数据内容解析 并存储; 查找接收数据对应的枪号、表号、数据内容等
*
* @param[in] s_meterFrame - 接收报文解析后的数据包-默认可用数据为正确数据
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static void v_meter_unpack(S_METER_FRAME *s_meterFrame)
{
U8_T i,id;
U64_T u64_unpackData = 0,u64_recvData=0;
S_METER_DATA *s_meter = NULL;
//printf_hex("解析成功////data",s_meterFrame->data,s_meterFrame->data_len);
//前4个字节 转换成 数据标识
s_meterFrame->u32_datesign = 0;
for (i = 0; i < NUM_4; i++)
{
s_meterFrame->u32_datesign += ((s_meterFrame->data[3-i]- 0x33) << i*8);
}
//遍历所有可解析数据标识 查询对应标识
for (id = 0; id < DATE_645_DATA_NUM; id++)
{
if(s_meterFrame->u32_datesign == st_MeterDataSheet[id].iFlagCode)
break;
}
//查找失败,则退出
if(id >= DATE_645_DATA_NUM){
// printf("find fail , 0x%08x\n",s_meterFrame->u32_datesign);
return;
}
//长度一致性检查
if(st_MeterDataSheet[id].iLenth != s_meterFrame->data_len-4){
// printf("len fail , 0x%08x\n",s_meterFrame->u32_datesign);
return;
}
//将报文数据 -0x33 BCD转int 组成整形数据
for (i = 0; i < st_MeterDataSheet[id].iLenth; i++)
{
U8_T u8_data = (s_meterFrame->data[i+4]- 0x33);
u64_recvData = (u64_recvData << 8);
u64_recvData += u8_data;
}
//将BCD码 转换成 十进制 -- u64 8字节
for (i = 0; i < st_MeterDataSheet[id].iLenth; i++)
{
// 提取当前BCD数字(4位)
U8_T u8_data = (u64_recvData >> (8 * i))&0xFF;
U8_T digit = bcd_to_dec(u8_data);
// 累积结果(每次乘以10并加上新数字)
u64_unpackData = u64_unpackData * 100 + digit;
}
//电表号赋值 -- 一路单电表时,可以不设置电表号 --当一路多电表使用时,电表号为已经设置好-因此此处仅对0地址电表进行赋值
s_meter = &s_memter_ctrl.s_meters[s_meterFrame->u8_gunNo][0];
if(s_meter->u8_meterNumber[0] == 0xAA && s_meter->u8_meterNumber[1] == 0xAA){
memcpy(&s_meter->u8_meterNumber[0] , &s_meterFrame->address[0] , 6);
//凯弦欣能电表特殊处理 //打印
if(s_meterFrame->u8_gunNo == 1)
{
if(s_meter->u8_meterNumber[0] == 0x01 &&
s_meter->u8_meterNumber[1] == 0x00 &&
s_meter->u8_meterNumber[2] == 0x00 &&
s_meter->u8_meterNumber[3] == 0x00 &&
s_meter->u8_meterNumber[4] == 0x00 &&
s_meter->u8_meterNumber[5] == 0x00
)
{
s_meter->u8_meterNumber[0] = 2;
}
}
}
//printf("u64_unpackData is %lld\n", u64_unpackData);
METERCOLL_LOCK_TAKE(); // 数据上锁
//查找电表号一致的电表 - 赋值对应内容
for (i = 0; i < METER_COMM_MAX_CNT; i++)
{
s_meter = &s_memter_ctrl.s_meters[s_meterFrame->u8_gunNo][i];
//if(strncmp((char *)s_meter->u8_meterNumber , (char *)s_meterFrame->address , 6) == 0) //电表号相等
if (memcmp(s_meter->u8_meterNumber, s_meterFrame->address, 6) == 0)
{
// printf("find ok , gun(%d) id(0x%08x) data(%lld)\n",s_meterFrame->u8_gunNo ,s_meterFrame->u32_datesign , u64_unpackData);
//存储电压、电流、电量值的精度 都是0.0001 四位小数,因此根据数据系数做统一转换
switch (st_MeterDataSheet[id].iFlagCode)
{
case DATA_645_ENERGY : //0x00000000 //电量(标准) XXXXXX.XX kWh
case DATA_645_HIGH_ENERGY : //0x00006200 //电量(高精度) xxxxxx.xxxx kWh
{
if(st_MeterDataSheet[id].iCoefficient != 0)
s_meter->u64_energy = u64_unpackData *(10000 / st_MeterDataSheet[id].iCoefficient) ; //电量值(单位:0.0001 kWh
s_meter->u16_timeout_cnt[0] = 0;
break;
}
case DATA_645_VOL : //0x00010102 //电压(标准) XXX.X V
case DATA_645_HIGH_VOL : //0x00011002 //电压(扩展帧 xxxx.xx V
{
if(st_MeterDataSheet[id].iCoefficient != 0)
s_meter->u32_voltage = u64_unpackData *(10000 / st_MeterDataSheet[id].iCoefficient) ; //电压值(单位:0.0001V
s_meter->u16_timeout_cnt[1] = 0;
break;
}
case DATA_645_CUR : //0x00010202 //电流(标准) XXX.XXX A
case DATA_645_HIGH_CUR : //0x00011102 //电流(扩展) xxxx.xxxx A
{
if(st_MeterDataSheet[id].iCoefficient != 0)
s_meter->u32_current = u64_unpackData *(10000 / st_MeterDataSheet[id].iCoefficient) ; //电流值(单位:0.0001 A
s_meter->u16_timeout_cnt[2] = 0;
break;
}
default:
break;
}
break;
}
}
METERCOLL_LOCK_GIVE(); //数据解锁
//接收标识清空
s_memter_ctrl.s_commCtrl[s_meterFrame->u8_gunNo].u8_recv_flag = 0; //数据接收标识 已清空
// if(s_meterFrame->u8_gunNo == 1)
// printf("unpack is ok , gun(%d) id(0x%08x) data(%lld)\n",s_meterFrame->u8_gunNo ,s_meterFrame->u32_datesign , u64_unpackData);
}
/**
* ************************************************************************
* @brief - 接收电表数据解帧函数 - 内部包括多包、残包等异常数据内容的处理方法
*
* @param[in] new_data - 新接收数据内容
* @param[in] new_len - 新接收数据内容长度
* @param[in] s_frame - 电表数据帧结构体指针 用于存储解析后的电表数据
*
* @return U8_T - 0解析失败 1解析成功
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static U8_T u8_meter_recvFrame(const U8_T *new_data, U8_T new_len,S_METER_FRAME *s_frame)
{
S_METER_COMM_CTRL *s_ctrl = &s_memter_ctrl.s_commCtrl[s_frame->u8_gunNo];
// 1. 检查输入有效性
if (!new_data || new_len <= 0 || !s_frame) {
return 0;
}
// 2. 添加新数据到缓冲区
int free_space = MAX_BUFFER_SIZE - s_ctrl->u8_buffer_len;
int copy_len = (new_len > free_space) ? free_space : new_len;
if (copy_len > 0) {
memcpy(s_ctrl->u8_buffer + s_ctrl->u8_buffer_len, new_data, copy_len);
s_ctrl->u8_buffer_len += copy_len;
}
// 3. 查找有效帧(最小帧长为12字节)
if (s_ctrl->u8_buffer_len < 12) {
return 0; // 数据不足
}
// 4. 尝试查找帧起始位置
int frame_start = -1;
int frame_end = -1;
for (int i = 0; i <= s_ctrl->u8_buffer_len - 12; i++) {
// 检查帧起始标识(跳过前导符)
int start_idx = i;
if (s_ctrl->u8_buffer[start_idx] != D_FrameStart) continue;
// 检查地址后的帧起始符(位于start_idx+7
if (start_idx + 7 >= s_ctrl->u8_buffer_len) continue;
if (s_ctrl->u8_buffer[start_idx + 7] != D_FrameStart) continue;
// 检查数据长度域(位于start_idx+9
if (start_idx + 9 >= s_ctrl->u8_buffer_len) continue;
// 检查帧完整长度
frame_end = start_idx + 11 + s_ctrl->u8_buffer[start_idx + 9]; // 包含结束符的位置
if (frame_end >= s_ctrl->u8_buffer_len) continue;
// 检查帧结束符(0x16
if (s_ctrl->u8_buffer[frame_end] != D_FrameEnd) continue;
frame_start = start_idx;
break;
}
// 5. 没有找到完整帧
if (frame_start < 0) {
// 缓冲区已满但无有效帧,清除部分数据
if (s_ctrl->u8_buffer_len >= MAX_BUFFER_SIZE / 2) {
// 保留最后1/4的数据
int keep_len = MAX_BUFFER_SIZE / 4;
if (s_ctrl->u8_buffer_len > keep_len) {
memmove(s_ctrl->u8_buffer, s_ctrl->u8_buffer + (s_ctrl->u8_buffer_len - keep_len), keep_len);
s_ctrl->u8_buffer_len = keep_len;
}
}
return 0;
}
// 6. 计算校验和
uint8_t calc_checksum = u16_crc_checksum(&s_ctrl->u8_buffer[frame_start] , frame_end - frame_start - 1);
if (calc_checksum != s_ctrl->u8_buffer[frame_end - 1]) {
return 0;
}
// 8. 解包帧数据
// 地址域(小端格式)
memcpy(s_frame->address, s_ctrl->u8_buffer + frame_start + 1, 6);
// 控制码
s_frame->control_code = s_ctrl->u8_buffer[frame_start + 8];
// 数据域长度和内容
s_frame->data_len = s_ctrl->u8_buffer[frame_start + 9];
s_frame->data_len = (s_frame->data_len > 0xFF) ? 0xFF : s_frame->data_len; // 安全限制
if (s_frame->data_len > 0) {
memcpy(s_frame->data, s_ctrl->u8_buffer + frame_start + 10, s_frame->data_len);
}
// // 计算完整帧长度(包括前导符)
// s_frame->data_len = (frame_end - frame_start + 1);
// 9. 移除已处理数据
int process_len = (frame_end + 1);
s_ctrl->u8_buffer_len -= process_len;
if (s_ctrl->u8_buffer_len > 0) {
memmove(s_ctrl->u8_buffer, s_ctrl->u8_buffer + process_len, s_ctrl->u8_buffer_len);
}
else
memset(s_ctrl->u8_buffer, 0 , MAX_BUFFER_SIZE);
return s_frame->data_len; // 返回处理的字节数
}
/**
* ************************************************************************
* @brief - 电表数据接收函数 遍历接收两个枪号的电表数据
*
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static void v_memter_recv_ctrl()
{
U8_T i,u8_readLen,u8_recvBuf[NUM_128];
S_METER_FRAME s_meterFrame;
for (i = 0; i < METER_COMM_MAX_CNT; i++)
{
memset(u8_recvBuf , 0 , NUM_128);
u8_readLen = u16_com_meterRecv(i,u8_recvBuf , NUM_128);
if(u8_readLen > 0)
{
memset(&s_meterFrame , 0, sizeof(S_METER_FRAME));
s_meterFrame.u8_gunNo = i;
if(u8_meter_recvFrame(u8_recvBuf , u8_readLen, &s_meterFrame ) != 0)
{
//printf("gun[%d] data_len is %d\n", i, s_meterFrame.data_len);
if(s_meterFrame.data_len >= 6) //数据有效 否则可能为错误数据 最小长度为7
{
v_meter_unpack(&s_meterFrame);
}
}
}
}
}
#endif
#if 1 //通信超时
/**
* ************************************************************************
* @brief - 通信超时检查函数 超时计数累加,接收数据时会清空计数,由此判断接受超时
*
* @param[in] u16_sleepTime - 任务执行周期 ms
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static void v_meter_timeout_cheak(U16_T u16_sleepTime)
{
U8_T i,k;
U16_T u16_max_timeout_cnt;
S_METER_DATA *s_meter_data =NULL;
for (i = 0; i < s_memter_ctrl.u8_gunCnt; i++) //遍历所有枪数量
{
for (k = 0; k < METER_COMM_MAX_CNT; k++) //遍历所有电表位置
{
s_meter_data = &s_memter_ctrl.s_meters[i][k]; //获取该枪电表首地址
if(s_meter_data->u8_enFlag == 0) //电表不存在 则不判断超时
continue;
if (s_meter_data->u16_timeout_cnt[0] >= s_meter_data->u16_timeout_cnt[1] &&
s_meter_data->u16_timeout_cnt[0] >= s_meter_data->u16_timeout_cnt[2]) {
u16_max_timeout_cnt = s_meter_data->u16_timeout_cnt[0];
} else if (s_meter_data->u16_timeout_cnt[1] >= s_meter_data->u16_timeout_cnt[0] &&
s_meter_data->u16_timeout_cnt[1] >= s_meter_data->u16_timeout_cnt[2]) {
u16_max_timeout_cnt = s_meter_data->u16_timeout_cnt[1];
} else {
u16_max_timeout_cnt = s_meter_data->u16_timeout_cnt[2];
}
if(s_meter_data->u8_comm_flag == 0) //未通信 判通信恢复
{
if(u16_max_timeout_cnt < METER_TIMEOUT_CNT)
{
s_meter_data->u8_comm_flag = 1;
v_meter_set_comm_timeout(i,k,0); //触发电表通信恢复
METERCOLL_LOG("gun[%d] id[%d], comm is ok\n", i,k);
// printf("gun[%d] id[%d], comm is ok\n", i,k);
}
}
else
{
if(u16_max_timeout_cnt < METER_TIMEOUT_CNT)
{
s_meter_data->u16_timeout_cnt[0] += u16_sleepTime;
s_meter_data->u16_timeout_cnt[1] += u16_sleepTime;
s_meter_data->u16_timeout_cnt[2] += u16_sleepTime;
}
else
{
s_meter_data->u8_comm_flag = 0;
//memset(s_meter_data, 0 ,sizeof(S_METER_DATA)); //不能全部清空 仅情况采集值即可
s_meter_data->u32_voltage =0;
s_meter_data->u32_current =0;
s_meter_data->u64_energy =0;
v_meter_set_comm_timeout(i,k,1); //触发电表通信超时故障
METERCOLL_LOG("gun[%d] id[%d], comm is fail\n", i,k);
// printf("gun[%d] id[%d], comm is fail\n", i,k);
}
}
}
}
}
#endif
/**
* ************************************************************************
* @brief - 电表数据初始化 初始化数据,并获取外部数据
*
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
static void v_memter_data_init()
{
U8_T i,k;
S_METER_DATA *s_meter_data =NULL;
memset(&s_memter_ctrl , 0 ,sizeof(S_METER_CTRL_DATA_DATA));
v_meter_get_para(&s_memter_ctrl);
for (i = 0; i < s_memter_ctrl.u8_gunCnt; i++) //遍历所有枪数量
{
for (k = 0; k < METER_COMM_MAX_CNT; k++) //遍历所有电表位置
{
s_meter_data = &s_memter_ctrl.s_meters[i][k]; //获取该枪电表首地址
if(s_meter_data->u8_enFlag == 0) //电表不存在 则不判断超时
continue;
s_meter_data->u8_comm_flag = 1;
s_meter_data->u16_timeout_cnt[0] = 0;//0xFFFF; //电表默认通信超时
s_meter_data->u16_timeout_cnt[1] = 0;
s_meter_data->u16_timeout_cnt[2] = 0;
}
}
}
/**
* ************************************************************************
* @brief - 电表数据采集函数 - 循环执行数据发生、数据接收、超时判断函数
*
* @param[in] p -
*
*
* @author jiankalka
* @date 2025-08-07
* ************************************************************************
*/
void v_Meter_Coll_task(void *argument)
{
uint16_t sleep_time_ms;
uint32_t last_send_time;
uint32_t current_time;
(void)argument;
(void)METERCOLL_LOCK_INIT();
sleep_time_ms = METERCOLL_SLEEP_TIME_MS();
last_send_time = get_current_seconds();
v_memter_data_init();
for (;;) {
METERCOLL_FEED_DOG();
current_time = get_current_seconds();
if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) {
if (METERCOLL_LOCK_TAKE() == 1U) {
METERCOLL_LOG("meter coll task is runing, sleep time is %ums", sleep_time_ms);
(void)METERCOLL_LOCK_GIVE();
}
last_send_time = current_time;
}
v_memter_recv_ctrl(); //电表数据接收
v_meter_send_ctrl(sleep_time_ms); //电表数据发生控制
v_meter_timeout_cheak(sleep_time_ms); //电表通信超时检查
mSleep(sleep_time_ms);
}
}
+93
View File
@@ -0,0 +1,93 @@
#ifndef METER_COLL_TASK_H
#define METER_COLL_TASK_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
#include "meter_calculate/meter_calculate_impl.h"
/*------ Exported macro(宏定义) -------------------------*/
#ifndef METER_MAX_CNT
#define METER_MAX_CNT (4) //电表最大数量
#endif
#ifndef METER_COMM_TYPE_CNT
#define METER_COMM_TYPE_CNT (3) //电表通信数据类型数量 3分别为电量、电压、电流
#endif
#ifndef METER_RECV_TIMEOUT_MS
#define METER_RECV_TIMEOUT_MS (600) //电表数据接收超时时间 600ms
#endif
#ifndef METER_COMM_MAX_CNT
#define METER_COMM_MAX_CNT (2) //单路通信口 电表最大数量 - 一路485口最大有几个电表
#endif
#define MAX_BUFFER_SIZE (128)
/*------ Exported struct(结构体定义) --------------------*/
// 第三级:电表控制数据
typedef struct {
U8_T u8_enFlag; //使能标志 0不使能 1使能 用于区分该位置是否存在电表
U8_T u8_isAC; // 是否为交流电表标识 (1:交流 0:直流)
U8_T u8_highPrec; //是否支持高精度 0 不支持 1支持
U8_T u8_meterNumber[6]; // 6字节电表表号 (如: 0x12,0x34,0x56,0x78,0x9A,0xBC)
U8_T u8_comm_flag; //通信标识 0未通信 1已通信
U16_T u16_timeout_cnt[3]; //通信超时计数 -- 三种数据 有一种接收超时则通信超时
U32_T u32_voltage; // 电压值(单位:0.0001V
U32_T u32_current; // 电流值(单位:0.0001A
U64_T u64_energy; // 电量值(单位:0.0001 kWh
} S_METER_DATA;
typedef struct
{
U8_T u8_send_type; //发送数据类型 0电量 1电压 2电流
U8_T u8_recv_flag; //数据接收标志 0未接收到 1已接收到
U16_T u16_recv_timeout_cnt; //接收超时计数 单位ms 最大超时时间 600ms
U8_T u8_buffer_len; // 当前缓冲区数据长度
U8_T u8_buffer[MAX_BUFFER_SIZE]; // 静态接收缓冲区
}S_METER_COMM_CTRL;
typedef struct {
U8_T u8_gunCnt; //枪数量
S_METER_COMM_CTRL s_commCtrl[GUN_MAX_CNT];
S_METER_DATA s_meters[GUN_MAX_CNT][METER_COMM_MAX_CNT]; // 电表控制数据数组(固定2个元素)
} S_METER_CTRL_DATA_DATA;
// 解包结果结构体
typedef struct {
U8_T u8_gunNo; //枪号
U8_T data_len; // 数据长度
U8_T control_code; // 控制码
U32_T u32_datesign; //数据标识
U8_T address[NUM_6]; // 电表地址(小端格式)
U8_T data[NUM_32]; // 数据域
} S_METER_FRAME;
typedef struct
{
U32_T iFlagCode; //标识编码
U16_T iLenth; //数据长度,如果为组合项,则为0
U16_T iCoefficient; //系数
} Meter_Data_Item;
void v_Meter_Coll_task(void *argument);
U8_T u8_get_meter_id_str(U8_T u8_gunNo ,U8_T u8_id ,U8_T *u8_getData , U8_T u8_len);
#ifdef __cplusplus
}
#endif
#endif
+440
View File
@@ -0,0 +1,440 @@
#include "mylog/mylog.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "usart.h"
#include "app_rtc/app_rtc.h"
#include "app_init/app_init.h"
#include "app_init/app_test.h"
#include "app_init/task_lock.h"
#include "publicdata/public_define.h"
#include "publicdata/publicdata.h"
#include "wdt_task/wdt_task.h"
#if FATFS_EN == 1
#include "app_fatfs/fatfs_init.h"
#endif
#if SD_CARD_EN
#include "sdmmc/app_sdmmc.h"
#endif
/* 暂时屏蔽 mylog UDP(保留代码,不删除) */
#ifndef MYLOG_UDP_EN
#define MYLOG_UDP_EN 1
#endif
#if (MYLOG_UDP_EN)
#include "lwip/sockets.h"
#include "lwip/inet.h"
#endif
/* mylog UDP 目标地址配置(模块内定义) */
#define MYLOG_UDP_DST_IP0 192
#define MYLOG_UDP_DST_IP1 168
#define MYLOG_UDP_DST_IP2 1
#define MYLOG_UDP_DST_IP3 238
#define MYLOG_UDP_DST_PORT 18487 /* UDP日志发送的目标端口(上位机监听端口) */
#define MYLOG_UDP_SRC_PORT 8088 /* 本机UDP发送socket绑定的源端口(抓包显示的本地端口) */
#define MYLOG_UDP_RX_PORT 8088 /* 本机UDP接收socket监听端口(用于接收并回显测试数据) */
/* 日志头格式选择:
* 1: [task] file:line func - msg
* 0: [task][sec=<当前秒>] - msg
*/
#ifndef MYLOG_PRINT_WITH_FILE_LINE_FUNC
#define MYLOG_PRINT_WITH_FILE_LINE_FUNC (0)
#endif
/* 本任务ID定义:用于统一索引任务表、日志与锁资源。 */
#define MYLOG_TASK_ID (TASK_ID_MyLog)
/* 获取 MyLog 任务睡眠时间(ms)。 */
#define MYLOG_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(MYLOG_TASK_ID))
/* MyLog 任务锁操作宏:简化锁接口调用。 */
#define MYLOG_LOCK_INIT() (u8_task_lock_ctrl(MYLOG_TASK_ID, TASK_LOCK_OP_INIT))
#define MYLOG_LOCK_TAKE() (u8_task_lock_ctrl(MYLOG_TASK_ID, TASK_LOCK_OP_TAKE))
#define MYLOG_LOCK_GIVE() (u8_task_lock_ctrl(MYLOG_TASK_ID, TASK_LOCK_OP_GIVE))
/* MyLog 任务喂狗宏:标记本任务周期正常运行。 */
#define MYLOG_FEED_DOG() (v_wdt_setFlag(MYLOG_TASK_ID))
/* MyLog 自身打印接口:对齐参考工程写法。 */
#define MYLOG_LOG(fmt, ...) MYLOG_MSG(MYLOG_TASK_ID, (fmt), ##__VA_ARGS__)
/* 任务日志开关表:默认仅 MyLog 任务允许打印,其它任务默认关闭。 */
/* 索引与 TASK_ID 一一对应,值 1 表示允许该任务打印,0 表示禁止。 */
uint8_t u8_task_log_flag[MY_TASK_NUM] =
{
[TASK_ID_EthIf] = 0U,
[TASK_ID_TCPIP] = 0U,
[TASK_ID_BmsComm] = 0U,
[TASK_ID_DataColl] = 0U,
[TASK_ID_FlowCtrl] = 1U,
[TASK_ID_FaultCheck] = 1U,
[TASK_ID_MduComm] = 0U,
[TASK_ID_SubComm] = 0U,
[TASK_ID_PlatComm] = 1U,
#if (MY_SHELL_EN)
[TASK_ID_MyShell] = 0U,
#endif
[TASK_ID_UI] = 0U,
[TASK_ID_WATCHDOG] = 0U,
[TASK_ID_MeterColl] = 0U,
[TASK_ID_Meterfee] = 0U,
[TASK_ID_MyLog] = 1U,
#if (TCP_DEBUG_EN)
[TASK_ID_TcpSer] = 1U,
#endif
[TASK_ID_EthLink] = 0U
};
/* UDP 套接字句柄:<0 表示未创建。 */
#if (MYLOG_UDP_EN)
static int g_mylog_udp_sock = -1;
/* UDP 接收套接字句柄:<0 表示未创建。 */
static int g_mylog_udp_rx_sock = -1;
/* UDP 目的地址结构体。 */
static struct sockaddr_in g_mylog_udp_addr;
#endif
/**
* @brief 初始化 mylog 的 UDP 发送通道。
*
* @details
* - 首次调用时创建 UDP socket
* - 配置固定的目的 IP 和端口;
* - 若 socket 创建失败,则保持未初始化状态,后续发送时可重试。
*/
static void v_mylog_udp_init(void)
{
#if (MYLOG_UDP_EN)
struct sockaddr_in local_addr;
if (g_mylog_udp_sock >= 0) {
return;
}
g_mylog_udp_sock = lwip_socket(AF_INET, SOCK_DGRAM, 0);
if (g_mylog_udp_sock < 0) {
return;
}
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = PP_HTONS(MYLOG_UDP_SRC_PORT);
local_addr.sin_addr.s_addr = PP_HTONL(INADDR_ANY);
if (lwip_bind(g_mylog_udp_sock, (const struct sockaddr *)&local_addr, sizeof(local_addr)) != 0) {
lwip_close(g_mylog_udp_sock);
g_mylog_udp_sock = -1;
return;
}
memset(&g_mylog_udp_addr, 0, sizeof(g_mylog_udp_addr));
g_mylog_udp_addr.sin_family = AF_INET;
g_mylog_udp_addr.sin_port = PP_HTONS(MYLOG_UDP_DST_PORT);
g_mylog_udp_addr.sin_addr.s_addr = PP_HTONL(LWIP_MAKEU32(MYLOG_UDP_DST_IP0, MYLOG_UDP_DST_IP1, MYLOG_UDP_DST_IP2, MYLOG_UDP_DST_IP3));
#else
/* UDP disabled */
#endif
}
/**
* @brief 初始化 mylog 的 UDP 接收通道。
*
* @details
* - 创建 UDP socket 并绑定到 `MYLOG_UDP_RX_PORT`
* - 设置为非阻塞模式,避免阻塞任务主循环。
*/
static void v_mylog_udp_rx_init(void)
{
#if (MYLOG_UDP_EN)
if (g_mylog_udp_rx_sock >= 0) {
return;
}
#if (MYLOG_UDP_RX_PORT == MYLOG_UDP_SRC_PORT)
if (g_mylog_udp_sock < 0) {
v_mylog_udp_init();
}
g_mylog_udp_rx_sock = g_mylog_udp_sock;
return;
#endif
#if (MYLOG_UDP_RX_PORT != MYLOG_UDP_SRC_PORT)
struct sockaddr_in local_addr;
g_mylog_udp_rx_sock = lwip_socket(AF_INET, SOCK_DGRAM, 0);
if (g_mylog_udp_rx_sock < 0) {
return;
}
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = PP_HTONS(MYLOG_UDP_RX_PORT);
local_addr.sin_addr.s_addr = PP_HTONL(INADDR_ANY);
if (lwip_bind(g_mylog_udp_rx_sock, (const struct sockaddr *)&local_addr, sizeof(local_addr)) != 0) {
lwip_close(g_mylog_udp_rx_sock);
g_mylog_udp_rx_sock = -1;
return;
}
#endif
#else
/* UDP disabled */
#endif
}
/**
* @brief 发送 UDP 日志数据。
*
* @param data 待发送数据指针。
* @param len 待发送数据长度(字节)。
*
* @details
* - 入参非法时直接返回;
* - socket 未初始化时先尝试初始化;
* - 发送失败不阻塞业务流程(静默处理)。
*/
void v_mylog_udp_send_data(const uint8_t *data, uint16_t len)
{
#if (MYLOG_UDP_EN)
if (data == NULL || len == 0U) {
return;
}
if (g_mylog_udp_sock < 0) {
v_mylog_udp_init();
}
if (g_mylog_udp_sock >= 0) {
(void)lwip_sendto(g_mylog_udp_sock,
data,
len,
0,
(const struct sockaddr *)&g_mylog_udp_addr,
sizeof(g_mylog_udp_addr));
}
#else
(void)data;
(void)len;
#endif
}
/**
* @brief 非阻塞接收 UDP 数据并原样回显给发送端。
*/
static void v_mylog_udp_recv_echo(void)
{
#if (MYLOG_UDP_EN)
uint8_t rx_buf[256];
int recv_len;
socklen_t peer_len;
struct sockaddr_in peer_addr;
if (g_mylog_udp_rx_sock < 0) {
v_mylog_udp_rx_init();
}
if (g_mylog_udp_rx_sock < 0) {
return;
}
peer_len = (socklen_t)sizeof(peer_addr);
recv_len = lwip_recvfrom(g_mylog_udp_rx_sock,
rx_buf,
sizeof(rx_buf),
MSG_DONTWAIT,
(struct sockaddr *)&peer_addr,
&peer_len);
if (recv_len > 0) {
(void)lwip_sendto(g_mylog_udp_rx_sock,
rx_buf,
(size_t)recv_len,
0,
(const struct sockaddr *)&peer_addr,
peer_len);
}
#else
/* UDP disabled */
#endif
}
/**
* @brief 统一格式化并输出日志(UDP + 串口)。
*
* @param id 任务ID(用于显示任务名)。
* @param file 文件名(建议传 BASENAME(__FILE__))。
* @param line 行号。
* @param func 函数名。
* @param fmt 可变参数格式字符串。
*
* @details
* - 通过 MYLOG_TASK_ID 对应锁串行化输出,避免多任务并发打乱日志;
* - 输出格式:`[task] file:line func - message`
* - 同时发 UDP 和串口 printf。
*/
void v_format_logMsg(int id, const char *file, int line, const char *func, const char *fmt, ...)
{
va_list args;
char log_buf[256];
int written = 0;
if (fmt == NULL) {
return;
}
if (MYLOG_LOCK_TAKE() != 1U) {
return;
}
#if (MYLOG_PRINT_WITH_FILE_LINE_FUNC)
written += snprintf(log_buf + written,
(int)sizeof(log_buf) - written,
"[%s] %s:%d %s - ",
MY_GET_TASK_NAME(id),
(file != NULL) ? file : "unknown",
line,
(func != NULL) ? func : "unknown");
#else
Comm_Time now_time;
(void)memset(&now_time, 0, sizeof(now_time));
GetCurrentTime(&now_time);
written += snprintf(log_buf + written,
(int)sizeof(log_buf) - written,
"[%s][%02u%02u%02u] - ",
MY_GET_TASK_NAME(id),
(unsigned int)now_time.ucHour,
(unsigned int)now_time.ucMin,
(unsigned int)now_time.ucSec);
#endif
if (written < (int)sizeof(log_buf)) {
va_start(args, fmt);
written += vsnprintf(log_buf + written, (int)sizeof(log_buf) - written, fmt, args);
va_end(args);
}
if (written < 0) {
(void)MYLOG_LOCK_GIVE();
return;
}
if (written >= (int)sizeof(log_buf)) {
written = (int)sizeof(log_buf) - 1;
}
// v_mylog_udp_send_data((const uint8_t *)log_buf, (uint16_t)written);
printf("%s\r\n", log_buf);
// 同时写入文件系统日志
if(
// id == TASK_ID_BMS ||
// id == TASK_ID_FlowCtrl ||
// id == TASK_ID_FaultCheck ||
id == TASK_ID_PlatComm )
{
#define LOG_BUFFER_SIZE 512
char logBuffer[LOG_BUFFER_SIZE];
memset(logBuffer , 0 , LOG_BUFFER_SIZE);
written = 0;
// written += snprintf(logBuffer + written, LOG_BUFFER_SIZE - written,
// "[%s]", // 任务名称
// MY_GET_TASK_NAME(id));
// 处理内容
va_start(args, fmt);
written += vsnprintf(logBuffer + written, LOG_BUFFER_SIZE - written, fmt, args);
va_end(args);
/* LOG_INFO("%s", logBuffer); -- FS log disabled */
}
(void)MYLOG_LOCK_GIVE();
}
/**
* ************************************************************************
* @brief - RTC时间更新,
* (由于RCT无外部时钟,内部时钟精度较低,定时20分钟使用rx8025对RTC进行校时处理)
*
*
*
* @author YZG
* @date 2025-11-04
* ************************************************************************
*/
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
void v_current_time_updata()
{
static uint32_t current_time_last = 0;
Comm_Time comm_time;
memset(&comm_time,0,sizeof(Comm_Time));
if(u32_safe_seconds_since(current_time_last) > NUM_60 *20) //运行10分钟
{
GetCurrentTime(&comm_time);
MYLOG_LOG("RTC time:%d-%d-%d %d:%d:%d \n",comm_time.iYear,comm_time.ucMonth,comm_time.ucDay,
comm_time.ucHour,comm_time.ucMin,comm_time.ucSec);
v_get_rx8025_set_rtc();
GetCurrentTime(&comm_time);
MYLOG_LOG("rx8025 time:%d-%d-%d %d:%d:%d \n",comm_time.iYear,comm_time.ucMonth,comm_time.ucDay,
comm_time.ucHour,comm_time.ucMin,comm_time.ucSec);
current_time_last = get_current_seconds();
}
}
#endif
/**
* @brief MyLog 任务主函数。
*
* @param argument 任务参数,当前未使用。
*
* @details
* - 初始化 MyLog 对应锁;
* - 按任务周期执行;
* - 在 `TASK_RUN_PRINT_TIME` 周期点输出“任务运行中”日志,便于在线确认任务活性。
*/
void v_mylog_task(void *argument)
{
uint16_t u16_sleep_time;
uint32_t last_send_time;
uint32_t current_time;
(void)argument;
/* 初始化本任务专属互斥锁,保护日志格式化与输出过程。 */
(void)MYLOG_LOCK_INIT();
/* 读取任务表中的 MyLog 周期配置,后续循环统一使用该周期。 */
u16_sleep_time = MYLOG_SLEEP_TIME_MS();
/* 记录周期打印基准时间(秒),用于 TASK_RUN_PRINT_TIME 节拍判断。 */
last_send_time = get_current_seconds();
for (;;) {
/* 每个循环先喂狗,标记 MyLog 任务仍在正常执行。 */
MYLOG_FEED_DOG();
/* 使用全局 TASK_RUN_PRINT_TIME 判断是否到达周期打印时刻。 */
current_time = get_current_seconds();
if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) {
MYLOG_LOG("mylog is runing, sleep time is %ums", u16_sleep_time);
last_send_time = current_time;
}
/* 轮询 UDP 接收并回显收到的数据(可通过 MYLOG_UDP_EN 屏蔽)。 */
v_mylog_udp_recv_echo();
#if FATFS_EN == 1
fatfs_action(); //文件系统周期执行函数
#endif
#if SD_CARD_EN
app_sdmmc_periodic();
#endif
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
v_current_time_updata(); //crt时间周期更新
#endif
/* 按任务配置周期休眠,避免忙轮询占用 CPU。 */
mSleep(u16_sleep_time);
}
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef MYLOG_H
#define MYLOG_H
#include <stdint.h>
#include <string.h>
#include "app_init/app_init.h"
/* 提取文件名(去掉路径),用于日志输出。 */
#define BASENAME(file) \
(strrchr((file), '/') ? (strrchr((file), '/') + 1) : \
(strrchr((file), '\\') ? (strrchr((file), '\\') + 1) : (file)))
/* 每个任务的日志开关:1-允许打印,0-禁止打印。 */
extern uint8_t u8_task_log_flag[MY_TASK_NUM];
void v_mylog_task(void *argument);
void v_format_logMsg(int id, const char *file, int line, const char *func, const char *fmt, ...);
void v_mylog_udp_send_data(const uint8_t *data, uint16_t len);
#define MYLOG_ID_FORCE_OCPP 0xFFU
#define MYLOG_ID_FORCE_IES 0xFEU
/* id=0xFFU 表示强制打印;其余 id 需在范围内且开关为1才打印。 */
#define MYLOG_MSG(id, fmt, ...) \
do { \
if (((id) == 0xFFU) || (((uint32_t)(id) < (uint32_t)MY_TASK_NUM) && (u8_task_log_flag[(uint32_t)(id)] == 1U))) { \
v_format_logMsg((id), BASENAME(__FILE__), __LINE__, __func__, (fmt), ##__VA_ARGS__); \
} \
} while (0)
#endif
+44
View File
@@ -0,0 +1,44 @@
# mylog 模块说明
> 路径:`app/mylog/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 提供统一日志输出入口;
- 管理日志等级、模块输出开关;
- 对接串口/网络日志通道(按工程配置启用)。
## 2. 关键文件
- `mylog.c`
- `mylog.h`
## 3. 功能说明
- 提供标准日志打印宏/函数,便于各任务统一格式输出;
- 支持运行时日志开关控制,便于现场定位问题;
- 与测试模块联动时可输出周期状态日志。
## 4. 调度与对外接口
该模块由 FreeRTOS 任务 `MyLog` 周期运行(单位:ms),周期来自 `app/app_init/app_init.c`
- `MyLog``200ms`(任务入口:`v_mylog_task(void *argument)`
对外接口(来自 `mylog.h`):
- `void v_mylog_task(void *argument)`:日志任务入口
- `void v_format_logMsg(int id, const char *file, int line, const char *func, const char *fmt, ...)`:内部格式化/输出实现
- `void v_mylog_udp_send_data(const uint8_t *data, uint16_t len)`UDP 发送接口(若工程配置启用)
- `MYLOG_MSG(id, fmt, ...)`:日志宏
- `extern uint8_t u8_task_log_flag[MY_TASK_NUM]`:运行时控制各任务日志开关(`id` 对应任务 ID
## 5. 维护建议
- 业务模块不要直接依赖底层串口细节,统一走 `mylog`
- 高频日志建议加节流,避免影响实时任务。
## 6. 关联文档
- 主索引:[README](../../README.md)
@@ -0,0 +1,184 @@
# 4G 模块 AT 指令与配置功能说明(EC200A)
本目录实现 **Quectel EC200A** 与 MCU 的 AT 交互:**蜂窝注册 → PDP → TCP/TLS 业务**,以及 **FTP(S) 远程升级****GetDiagnostics 日志上传**。下文按 **AT 流程**、**透传 / 非透传**(含 **TCP 与 FTP 分 PDP**)、**FTP 升级与日志两套状态机**、**plat_comm / OCPP 衔接**、**配置宏与排障** 说明。
---
## 目录与文件速览
| 文件 | 作用 |
|------|------|
| `ec200a_4g_cfg.h` | 透传开关、TCP/FTP **分 PDP**、PlatComm FTP 与平台间隔、**QFREAD 分片**、**QFTPGET(COM) 分片收包**(与 QFREAD 同思路的轮询等待)、**QFWRITE 分片轮询**、**UFS 单次读写块 `EC200A_FTP_UFS_RW_CHUNK_BYTES`(默认 4096)**、FTP 期间是否暂缓 **QISEND**、日志块大小、日志收尾到 **QISEND** 前延时等 |
| `ec200a_4g.h` / `ec200a_4g.c` | 初始化状态机、UART 收发、`QIOPEN`/`QSSLOPEN`、非透传 **`QISEND`/`QIRD`**(明文 TCP)与 **`QSSLSEND`/`QSSLRECV`**`wss`/`QSSLOPEN`)、软重连等 |
| `ec200a_4g_ftp.h` / `ec200a_4g_ftp.c` | **`u8_ec200a_4g_ftp_update`**(固件升级)、**`u8_ec200a_4g_ftp_log_upload`**(诊断日志)、**`v_ftp_ctrl_init`** |
上层业务(如 OCPP WebSocket)经 **`bs_connect_impl.c`** 的 **`v_bs_send` / `u16_bs_recv`** 接入 4GFTP 调度由 **`bs_public_impl.c`** 的 **`v_ftp_update_action`** 在 PlatComm 中周期调用。OCPP 侧置位 **`u8_updata_flag`**、**`u8_log_file_upload`** 等与 **`BS_ocpp_ctrl.c`** 协同(见第四节)。
---
## 一、AT 指令流程(蜂窝初始化与建链)
### 1.1 前置:`v_4g_data_init`
应用解析配置里的服务器 URL(域名、端口、APN、`ws://`/`wss://`)填入 **`S_4G_CONFIG_PARA`**,再调用 **`v_4g_data_init`**,内部 **`s8_cat_reboot(0)`** 完成模组上电/复位类动作,随后周期性调用 **`s8_ec200a_4g_init`** 推进状态机。
### 1.2 主初始化路径(`s8_ec200a_4g_init`
状态枚举 **`E_EC200A_4G_INIT_STEP`**,典型顺序:命令模式 → SIM → CCID → CFUN → CGREG → **业务 PDP `QICSGP`/`QIACT``EC200A_TCP_PDP_CTX_ID`** → 可选 TLS 配置 → **`QIOPEN`/`QSSLOPEN`** → 完成。
非透传在打开 Socket 前会 **`s8_ec200a_qisde_apply()`**`AT+QISDE`),再 **`s8_ec200a_qiopen_accumulate`** 等。
### 1.3 建链与 access_mode
- **`EC200A_TCP_QIOPEN_ACCESS_MODE`**:透传为 **2**(等 `CONNECT`),缓冲模式为 **0**(轮询 `+QIOPEN`/`+QSSLOPEN` 错误码)。
- 业务 Socket 使用 **`EC200A_TCP_PDP_CTX_ID`**(默认 **1**),与 FTP 所用 PDP **必须不同**(见 `ec200a_4g_cfg.h` 编译期校验)。
### 1.4 超时与恢复
状态机末尾根据 **`u8_out_cnt`** 累加;过久会打日志、回退 **`STEP_SWCHTO_CMD_MODE`** 并可能 **`s8_cat_reboot`**。非透传下 TCP 断连可先做 **QICLOSE+QIOPEN 软重连**,连续失败后再全量 **`v_bs_4G_data_init`**(见 `ec200a_4g_cfg.h`**`EC200A_TCP_SOFT_REOPEN_FAIL_MAX`**)。
---
## 二、透传与非透传(缓冲模式)
### 2.1 配置关系(`ec200a_4g_cfg.h` 摘要)
| 宏 | 作用 |
|----|------|
| **`EC200A_TCP_USE_TRANSPARENT_MODE`** | **1** 透传;**0** 缓冲(`QISEND`/`QIRD` |
| **`EC200A_TCP_PDP_CTX_ID` / `EC200A_FTP_PDP_CTX_ID`** | 业务 TCP 与 FTP **分 PDP**(默认 1 / 2),**禁止相等** |
| **`EC200A_FTP_TO_PLATFORM_GAP_MS`** | 每轮 PlatComm 内 **`v_ftp_update_action` 一步之后**、平台收发循环前的间隔 (ms),减轻 FTP AT 与 **QISEND** 粘连 |
| **`EC200A_FTP_QFREAD_*`** | 非透传下 **`AT+QFREAD`**(日志读 UFS)及 **`AT+QFTPGET``COM:`** 固件分片:应答在单周期内 **短 `mSleep` + 收包累加** 直至 `\r\nOK`**QFTPGET** 侧等待轮次约为 **`EC200A_FTP_QFREAD_TOTAL_WAITS` 的 12 倍**(FTP 拉网侧数据较慢,见 `s16_qftpget_com_at_slice` |
| **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** | 模组 UFS 单次 **`AT+QFREAD` / `AT+QFWRITE`** 数据负载,同时也是 **固件 `AT+QFTPGET="…","COM:",off,len` 的单片长度**(默认 **4096 = 1024×4**);须为 Flash **`PACKET_SIZE`(1024)** 的整数倍(编译期校验)。固件下载每片经 **CONNECT 负载** 解析后,按 **`upgrade_write_packet`** 与 **`QFREAD` 读 UFS 写 Flash** 相同的 **flash_base + 1024 子包** 逻辑写入 |
| **`EC200A_FTP_QFWRITE_*`** | 非透传下 **`AT+QFWRITE`**CONNECT→发负载→`\r\nOK` 跨多轮 PlatComm **短等轮询**`QFWRITE_WAIT_MS` / `QFWRITE_WAITS_PER_PLAT_ROUND` / `QFWRITE_TOTAL_WAITS`),避免单周期长时间阻塞 UART |
| **`EC200A_FTP_LOG_CHUNK_SIZE` / `EC200A_FTP_LOG_QFWRITE_MAX_BYTES`** | 默认与 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** 对齐;日志从 FATFS 读块与单次 QFWRITE 负载一致,**一步一块**、包间回到 AT 态 |
| **`EC200A_BLOCK_QISEND_DURING_4G_FTP`** | **1**(默认):`u8_updata_flag` 且 4G 模组 FTP 时 **`v_bs_send`/`ws_send` 暂缓 QISEND**QFTPPUT 大文件期间避免与 AT 抢 UART;**0** 尝试穿插(仍可能 `no prompt >`)。**勿**仅用 `u8_log_file_upload` 判暂缓(recv 早于 CallResult 置位会挡应答)。 |
| **`EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS`** | 日志上传收尾(**`QFTPCLOSE`**、`v_ftp_ctrl_init`、排空 RX)后,首帧 OCPP **QISEND** 前短延时,减轻 **`break off=0`** |
| **`EC200A_FTP_LOG_DONE_REQUEST_BS_RENEW`** | 日志上传 **`STEP_FTP_INIT_OK`**:是否 **`v_set_bs_renew`**(默认 **0**,不重连 OCPP);置 **1** 时 FTP 后软重开 TCP 并 **重登** |
| **`EC200A_TCP_APP_RW_MAX`** / **`EC200A_QIRD_AT_READ_MAX`** / **`EC200A_QISEND_AT_LEN_MAX`** | 应用单次读写与 Quectel AT 上限(1500 / 1460)钳位 |
### 2.2 平台统一收发(`bs_connect_impl.c`
**`u8_netType == 0`4G** 时:`v_bs_send` → 透传 **`v_4g_send_data`** / 非透传 **`v_ec200a_tcp_app_send`**`u16_bs_recv` 同理。
非透传 **`s8_ec200a_qisend_once`**:先发 **`AT+QISEND=id,len`**,等 **`>`**,再发二进制负载,再等 **`SEND OK`**;发送前 **`ec200a_modem_uart_rx_drain`** 减轻粘连。详见源码注释与历史版本本文档附录(若需可从版本管理拷贝为 `4G模块AT指令配置功能说明_appendix.md`)。
### 2.3 TCP 与 FTP 的关系(重点)
| 维度 | 说明 |
|------|------|
| **PDP** | 业务 TCP 与 FTP 使用 **不同 context**FTP 侧 **`QIDEACT`/`QIACT`** 只作用 **`EC200A_FTP_PDP_CTX_ID`****不拆**业务 TCP 所在 PDP。 |
| **UART** | 物理 **一根** 串口;**`QISEND`/`QIRD`/`QFTP*`/`QFREAD`/`QFWRITE`** 均为 **分时串行**。 |
| **“并存”** | 模组协议栈内 TCP 与 FTP 会话可同时存在;MCU **交替发 AT**,单时刻 UART 上只有一种事务。 |
| **PlatComm 与联网穿插** | **`plat_comm_task.c`**4G FTP 活跃时 **相邻两周期交替**——一周期只做 **联网**`v_plat_comm_network_round`:双路 `recv` + 各平台 `send_ctrl`),下一周期只做 **FTP 一步**`v_plat_comm_ftp_round``recv`**`v_ftp_update_action`** → **`EC200A_FTP_TO_PLATFORM_GAP_MS`**)。配合 **QFTPGET(COM) / QFREAD / QFWRITE 分包 + 短等**FTP 传输期间 **OCPP 仍可周期性收发包**(非透传路径)。 |
---
## 三、FTP:固件升级与日志上传(两套路径)
**`u8_ec200a_4g_ftp_update`** 与 **`u8_ec200a_4g_ftp_log_upload`** 共用全局 **`e_ec200a_ftp_init_step`** 与同一套 **`STEP_FTP_*`** 枚举,但 **跳转序列不同**;进入任一路径前通常 **`v_ftp_ctrl_init()`** 将步进复位到 **`STEP_FTP_SWCHTO_CMD_MODE`**(或由业务在 **`GetDiagnostics_res`** 等里显式调用)。
**`v_ftp_update_action``bs_public_impl.c`** 分支逻辑:
1.**`s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 1`**(且编译开启 OCPP):走 **`u8_ec200a_4g_ftp_log_upload()`**(4G);网线且未实现日志 FTP 时会清标志并上报失败。
2. **否则**:走 **`u8_ec200a_4g_ftp_update()`**(4G 固件)或 LwIP 升级状态机。
因此 **`u8_log_file_upload` 必须与 `u8_updata_flag` 配合正确**:若误清 **`u8_log_file_upload`**,会在 **`u8_updata_flag` 仍为 1** 时误入 **固件升级** 分支,出现日志里的 **`[FTP-UPG]`** 与 **`RX pkt(0)/(3)`** 等异常(**`BS_ocpp_ctrl.c`** 中已去掉在 **`u8_updata_flag==0` 时无条件清 `u8_log_file_upload`** 的逻辑)。
### 3.1 固件远程升级(`u8_ec200a_4g_ftp_update`
- **入口**PlatComm 周期调用 **`v_ftp_update_action`** → **`u8_ec200a_4g_ftp_update()`**(当 **`u8_log_file_upload==0`** 且 **`t_ftp_update_info.u8_netType==0`**)。
- **日志标签****`[FTP-UPG] step=…`**(与日志上传的 **`ftp test …`** 区分)。
- **配置****`T_FTP_UPDATE_INFO t_ftp_update_info`**(URL、主机、端口、路径、**固件文件名**、账号等)。
**远端对象路径**:由 **`s32_ftp_fill_remote_get_object()`** 将 **`u8_path` + `u8_file_name`** 拼成 **`s_ftp_remote_get_obj`**(如 `/home/ftpuser/ftpfile/firmware/CCU601E.bin`),供 **`QFTPSIZE`** 与 **`QFTPGET`** 使用,**避免仅 CWD 到绝对路径失败时无法取文件**。
**`QFTPCWD`**:路径过长则跳过;先 **`AT+QFTPCWD="<path>"`**,失败再试 **去掉前导 `/`**;仍失败则 **打日志并返回成功步进(1**,后续依赖 **全路径 `QFTPSIZE` / `QFTPGET`**
**与源码一致的步骤顺序(摘要)**
| 顺序 | 枚举步 | 行为要点 |
|------|--------|----------|
| 1 | `STEP_FTP_SWCHTO_CMD_MODE` | **`s8_work_mode_cmd`**:退出透传、**`AT`** |
| 23 | `SET_CGREG` / `QUERY_CGREG` | PS 注册 |
| 45 | `SET_QIDEACT` / `SET_QIACT` | 仅 **FTP PDP**:去激活 → **`QICSGP`+`QIACT`** |
| 610 | `FTP_CFG_*` | **`QFTPCFG`**contextid、account、filetype、transmode、rsptimeout |
| 11 | `QFTPOPEN` | 登录 FTP 服务器 |
| 12 | `QFTPCWD` | 切换远程目录(失败时仍可继续,见上) |
| 13 | `GET_QFTPSIZE` | **`QFTPSIZE`** 对 **`s_ftp_remote_get_obj`** 取远程大小 → **`u32_file_size`**;成功则按 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** 计算 **`u16_packSize` / `u16_packNum`****`u16_packIndex=0`** |
| 14 | `QFDEL` | 清模组 **UFS** 旧文件(与日志/杂项共用空间,仍建议执行) |
| 15 | `GET_QFTPGET` | 循环 **`AT+QFTPGET="<remote>","COM:",offset,len`****非透传** 用 **`s16_qftpget_com_at_slice`**(与 **`s16_qfread_at_slice`** 同思路等 **OK**);**透传** 用 **`s16_4g_Send_Recv_Ftp_Get`**。首片前 **`v_upgrade_header_data(0)`****`parse_4g_ftp_response`** 取 CONNECT 与 **OK** 间二进制,按与 **`s8_ec200a_qfread`** 相同的 **`flash_base` + 1024 子包** 调 **`upgrade_write_packet`**。**返回 0** 表示尚有分片;**1** 表示全部写完 |
| 16 | `STEP_FTP_SAVE` / `INIT_OK` | **`v_upgrade_header_data(1)`** + **`v_sys_reboot()`** |
**已不再作为升级主路径****`QFLST``QFOPEN``QFSEEK``QFREAD`(从 UFS 整包下载后再读)**;源码中相关 **`case`** 仍保留,供其它场景或对照,**正常 OCPP 固件升级不会走入**。
**超时**:全局 **`u8_out_cnt`** 超过约 **3 分钟****`u8_updata_flag`**、打 **`FTP TO, exit upg`****`QUERY_CGREG`** 另有 **120 次** 强制跳转。
### 3.2 GetDiagnostics 日志上传(`u8_ec200a_4g_ftp_log_upload`
- **入口****`u8_log_file_upload==1`** 时由 **`v_ftp_update_action`** 调用。
- **日志标签****`ftp test <步号>`**。
- **典型流程**:命令模式 → **UFS `QFDEL`** → 本地日志信息(**`s8_get_log_info`**:按 **`EC200A_FTP_LOG_CHUNK_SIZE`** 等收紧 **`u16_packSize`**)→ **`QFOPEN`** 在 UFS 上覆盖打开日志文件 → **`STEP_FTP_FTP_WRITE_LOG_UFS`****`QFSEEK` + `QFWRITE`** 从 FATFS **按块(默认 4096** 写入模组 UFS**`QFWRITE` 在单周期内仅短等轮询**,未收齐 **CONNECT/OK****`s8_ec200a_write_log_data` 返回进行中**,下一 FTP 半周期续传(**不采用整段 `QFUPL` 透传写文件`**,避免长时间占 UART)→ **`QFLST`** →(与升级类似的)**CGREG / FTP PDP `QIDEACT`/`QIACT`** → **`QFTPCFG` 链** → **`QFTPOPEN`/`QFTPCWD`** → **`QFTPPUT`** 上传到远端 → **`AT+QFTPCLOSE`****`s8_ec200a_qftpclose`**)→ **`STEP_FTP_INIT_OK`** 等待若干周期 → 清 **`u8_updata_flag`**、**`u8_log_file_upload`**、**`v_ftp_ctrl_init`**,并在非透传下 **排空 RX + `EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS` 延时 + 再 drain**,减轻紧接着的 **Uploaded** / **StatusNotification** 出现 **`break off=0`**。
**`EC200A_FTP_LOG_CHUNK_SIZE` / `EC200A_FTP_LOG_QFWRITE_MAX_BYTES`**:默认与 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** 一致;控制从 FATFS 单次读取与单次 **QFWRITE** 负载。注意 **读缓冲 + 过滤缓冲** 的 RAM 占用(过滤后长度 ≤ 当块读长)。
### 3.3 与「误走升级分支」的鉴别
| 现象 | 含义 |
|------|------|
| 日志 **`[FTP-UPG]`** + **`QFTPGET(COM)` / `step``GET_QFTPGET`** | 正在跑 **`u8_ec200a_4g_ftp_update`** |
| 日志 **`ftp test`** | 正在跑 **`u8_ec200a_4g_ftp_log_upload`** |
| GetDiagnostics 后出现 **`[FTP-UPG]`** 且 **`RX pkt(0)/(3)`** 卡进度 | 多为 **`u8_log_file_upload` 被清 0** 仍 **`u8_updata_flag==1`**,已按上节在 OCPP 侧修复 |
---
## 四、与 plat_comm、OCPP、互斥策略的衔接
### 4.1 `plat_comm_task.c`
- **`u8_ftp_transfer_active`**:任一路 **`u8_updata_flag!=0`**;若开启 OCPP,在 **4G****`u8_log_file_upload==1`** 时,即使 **`u8_updata_flag` 仍为 0**GetDiagnostics **CallResult 尚未置位 `updata_flag`** 的窗口),也视为 **FTP 活跃**,以便调度 **`v_ftp_update_action`** 跑日志状态机。
- **`ftp4g`****`u8_ftp_transfer_active && t_ftp_update_info.u8_netType==0`**。为真时:使用 **静态相位 `s_plat_comm_net_ftp_phase`** —— **奇偶半周期交替**:一半周期只跑 **`v_plat_comm_network_round`**(联网收发),另一半周期只跑 **`v_plat_comm_ftp_round`**(开头双路 **`v_bs_recv_data`** → **`v_ftp_update_action` 一步** → **`EC200A_FTP_TO_PLATFORM_GAP_MS`**)。这样 **FTP 与 OCPP 不在同一 PlatComm 周期内叠执行**,与 **`QFTPGET(COM)`/`QFREAD`/`QFWRITE` 分包 + 短等** 叠加后,**FTP 传输过程中仍可稳定进行联网数据收发**。
- **网线 FTP****`u8_ftp_transfer_active && netType!=0`** 时独占整周期 **`continue`**(与 4G 交替策略不同)。
### 4.2 `u8_bs_ftp_ocpp_strict_mux``plat_comm_task.h` / `v_plat_data_init`
- **默认 0**:不在 FTP 期间强行拦截 OCPP **`v_bs_send`**;大文件上传请依赖 **`EC200A_BLOCK_QISEND_DURING_4G_FTP`(默认已 1)** 暂缓 QISEND,否则易 **`no prompt >`**。若仍要业务帧与 FTP 硬穿插可 **`#define EC200A_BLOCK_QISEND_DURING_4G_FTP 0`** 并拉大 **`EC200A_FTP_TO_PLATFORM_GAP_MS`**,或运行时置 **`u8_bs_ftp_ocpp_strict_mux=1`**。
- **为 1****`bs_connect_impl.c`** 在 **模组 FTP 传输请求有效** 且目标为 OCPP 通道时 **return** 掉发送;**`bs_public_impl.c`** 在 OCPP **4G TX busy** 时暂缓 **`v_ftp_update_action`**。与 **GetDiagnostics****「先 CallResult / Uploading,再置 `u8_updata_flag`」** 顺序配合使用(见 **`BS_ocpp_ctrl.c`**)。
### 4.3 OCPP 要点(文件在 `ocpp/`,此处仅列衔接)
- **GetDiagnostics****`GetDiagnostics_mag`** 解析 URL 并置 **`u8_log_file_upload`****`GetDiagnostics_res`** 准备 **`fileName`**、**`v_ftp_ctrl_init`****不在组帧前**置 **`u8_updata_flag`**,避免与 strict mux 组合导致 **CallResult 发不出**。**`send_mag`** 在 **CallResult `ws_send` 成功** 后置 **`u8_diag_notify_pending=Uploading`****`ocpp_send_mag_ctrl`** 在 **Uploading****`send_mag` 之后** 再置 **`u8_updata_flag=1`**。与 **4G 分包 FTP、PlatComm 半周期交替** 的衔接说明见 **`ocpp/OCPP功能模块说明.md`** **第 9.1 节**
- **`v_ocpp_private_logic`**:不再在 **`u8_updata_flag==0`** 时无条件 **`u8_log_file_upload=0`**,避免误切 **固件升级** 分支。
### 4.4 透传模式下的 FTP
透传(**`EC200A_TCP_USE_TRANSPARENT_MODE==1`**)下,**日志写 UFS 的 `QFOPEN`/`QFSEEK`/`QFWRITE` 路径不参与编译**(相关步进会失败或超时退出);**固件升级** 仍使用 **`QFTPGET``COM:`** 分片 + **`s16_4g_Send_Recv_Ftp_Get`**,与缓冲模式相比 UART 占用特征不同,需单独测试。
---
## 五、配置与排障建议
| 问题倾向 | 建议 |
|-----------|------|
| **`4G QISEND: no prompt >` / `break off=0`** | 保持 **`EC200A_BLOCK_QISEND_DURING_4G_FTP=1`**(默认);增大 **`EC200A_FTP_TO_PLATFORM_GAP_MS`**;确认 **日志已走 `QFOPEN+QFSEEK+QFWRITE` 分包**(勿再依赖长时 **`QFUPL`** 透传);固件已走 **`QFTPGET(COM)`** 分片;可调 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`**(须为 1024 倍数)、**`EC200A_FTP_QFREAD_*`**(同时影响 QFTPGET 片内等待上限);或 **`u8_bs_ftp_ocpp_strict_mux=1`** |
| **`QSSLOPEN` 成功但 `AT+QISEND` 立刻 ERROR** | **`wss` 须用 `AT+QSSLSEND` / `AT+QSSLRECV`**,不能对 SSL socket 发 **`QISEND`/`QIRD`**(固件 `ec200a_4g.c` 已按 `use_tls` 分支) |
| **`QFTPCWD` 失败但需升级** | 当前实现会在 CWD 失败后仍继续;若仍 **QFTPSIZE/QFTPGET** 失败,核对 **FTP 上实际路径****`s_ftp_remote_get_obj`** 拼接是否一致(含前导 `/` |
| **日志上传结束后首包 OCPP 失败** | 确认 **`QFTPCLOSE`**;调大 **`EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS`**;若确认 TCP 已死且可接受重登,再设 **`EC200A_FTP_LOG_DONE_REQUEST_BS_RENEW=1`** |
| **GetDiagnostics 走成 `[FTP-UPG]`** | 确认 **`u8_log_file_upload`** 未被异常清除;**`v_ftp_update_action`** 是否进入 **`u8_log_file_upload` 分支** |
| **双 PDP** | 修改 context 时同时覆盖 **TCP/FTP** 两宏且保持 **不等** |
| **QIRD ERROR** | 检查 **`want`** 是否超 **1500**(已由宏钳位) |
---
## 六、自检清单(FTP 升级相关)
- [ ] **`EC200A_TCP_PDP_CTX_ID``EC200A_FTP_PDP_CTX_ID`**
- [ ] 固件升级日志为 **`[FTP-UPG]`****`QFTPSIZE`** 使用 **远端全路径****`QFTPGET(COM)`** 分片日志 **`pkt i/N`****全部片完成后** 直接进入 **`STEP_FTP_SAVE`**(不经 **UFS QFLST/QFREAD** 主路径)
- [ ] 日志上传为 **`ftp test`**,写 UFS 阶段为 **`QFOPEN` + 循环 `QFSEEK`/`QFWRITE`(块长默认 4096)**,上传结束有 **`QFTPCLOSE`** 与收尾 **drain + 延时**
- [ ] PlatComm**4G FTP** 时周期头 **recv + FTP + gap**,且 **每平台 send 后 recv**
- [ ] OCPP**CallResult → Uploading → `u8_updata_flag`** 顺序;**不误清 `u8_log_file_upload`**
---
*路径:`CCU601E_D/app/plat_comm/4g_module/4G模块AT指令配置功能说明.md`*
*若需保留旧版「2.5 QISEND/QIRD 字节级长说明」,可从版本管理历史中拷贝为 `4G模块AT指令配置功能说明_appendix.md`。*
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
/**
* @file bs_wh_gm5.h
* @brief WH-GM5 4G 模组 AT 驱动
*
* 负责模组初始化、APN/Socket 配置、信号查询及串口通信。
* 通信串口:USART6COM_PORT_BS)。
*/
#ifndef _BS_WH_GM5_H_
#define _BS_WH_GM5_H_
#include "publicdata/type.h"
#include "main.h"
#include "publicdata/publicdata.h"
#include "usart.h"
#include "plat_comm/plat_comm_task.h"
#define COM_PORT_BS E_USART_6 //4G 模组 WH-GM5 通信串口
typedef enum
{
STEP_SWCHTO_CMD_MODE = 0, //切换到指令模式
STEP_MODE_NET_ON, //设置工作模式为网络透传模式(默认net)
STEP_UAT_ON, //使能串口AT指令(默认ON)
STEP_UAT_SET_PACK_TIME,
STEP_SET_PWD, //设置指令密码(默认usr.cn)
STEP_SET_APN, //设置APN
STEP_SET_SDPEN, //设置套接字分发
STEP_SCKA_ON, //设置socketA使能(默认ON)
STEP_SET_SCKA_PARA, //设置socketA参数
STEP_SCKB_ON, //设置socketB使能(默认ON)
STEP_SCKB_OFF, //设置socketB关闭(OFF)
STEP_SET_SCKB_PARA, //设置socketB参数
STEP_HEART_OFF, //设置心跳包不使能
STEP_SAVE, //保存当前设置并重启
STEP_INIT_OK, //设置完成
}E_WH_GM5_INIT_STEP;
typedef struct
{
U8_T u8_Sim_Type; //APN类型(0:移动,1:联通,2:电信)
U8_T u8_socketB_en; //socketB使能 默认套接字分发功能使能 - 使能分发时,组帧解帧需要添加帧头;不使能直接透传模式,将数据上送平台,但无法区分多平台
U8_T *socketA_IP; //域名或者IP+端口号 例如 www.baidu.com:1234
U8_T *socketB_IP; //is NULL 则不存在socket
U8_T *apn_str;
U16_T socketA_port; //端口号
U16_T socketB_port;
U8_T use_tls; //是否启用模块内置 TLS0=TCP1=TLSwss
}S_4G_CONFIG_PARA;
typedef struct
{
U8_T u8_out_cnt; //超时计数
E_WH_GM5_INIT_STEP e_wh_gm5_init_step; //无线通信模块初始化步骤标志
U8_T u8_Sim_Card[NUM_20]; //SIM卡号
U8_T u8_4g_Version[NUM_20]; //4g版本
S_4G_CONFIG_PARA s_4g_para;
}S_4G_CTRL_DATA;
//结构体1 -- 用于内部逻辑控制和数据存储
//结构体2 -- 用于联网参数存储-获取
S8_T s8_UAT_check_signal_quality(void);
S8_T s8_check_number(U8_T *data); //查询SIM卡号
S8_T s8_check_socket_LinkState(U8_T u8_id);
S8_T s8_WH_GM5_init(void);
U8_T u8_UAT_CheckConnState(void); //通过串口AT指令(密码ieszt)查询模块网络连接状态
void v_wh_gm5_data_init(S_4G_CONFIG_PARA *s_4g_para);
void v_4g_uart_cache_clear(void);
U8_T v_GM5_get_sim(U8_T *data); /**< 获取 SIM 卡号,有数据返回 1 */
#endif
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
#ifndef EC200A_4G_H
#define EC200A_4G_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "plat_comm/4g_module/ec200a_4g_cfg.h"
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
STEP_SWCHTO_CMD_MODE = 0, //切换到指令模式
STEP_QUERY_SIM_STATE, //查询SIM卡状态
STEP_QUERY_CCID, //查询sim卡号
STEP_SET_CFUN, //设置功能模式
STEP_SET_CGREG, //设置PS域 网络注册状态
STEP_QUERY_CGREG, //查询PS域 网络注册状态
STEP_SET_QICSGP_APN, //设置TCP场景参数 设置APN
STEP_SET_CGQMIN, //设置服务质量参数(最低可接受的)
STEP_SET_CGQREQ, //设置服务质量参数(请求的)
STEP_QUERY_CSQ, //查询信号强度
STEP_SET_QIACT, //激活PDP场景
STEP_SET_SSL_CFG, //TLS 配置(QSSLCFG 等,仅在需要 TLS 时执行)
STEP_SET_SOCKET_AND_OPEN, //设置socket 参数并打开(TCP 或 TLS
STEP_SAVE, //保存当前设置并重启
STEP_INIT_OK, //设置完成
STEP_INIT_DEBUG, // 调试
}E_EC200A_4G_INIT_STEP;
typedef struct
{
U8_T u8_Sim_Type; //APN类型(0:移动,1:联通,2:电信)
U8_T u8_socketB_en; //socketB使能 默认套接字分发功能使能 - 使能分发时,组帧解帧需要添加帧头;不使能直接透传模式,将数据上送平台,但无法区分多平台
U8_T *socketA_IP; //域名或者IP+端口号 例如 www.baidu.com:1234
U8_T *socketB_IP; //is NULL 则不存在socket
U8_T *apn_str;
U16_T socketA_port; //端口号
U16_T socketB_port;
U8_T use_tls; //是否启用模块内置 TLS0=TCP1=TLSwss
}S_4G_CONFIG_PARA;
typedef struct
{
U8_T u8_out_cnt; //超时计数
U8_T u8_csq; //信号强度
E_EC200A_4G_INIT_STEP e_4g_init_step; //无线通信模块初始化步骤标志
U8_T u8_Sim_Card[NUM_20]; //SIM卡号
U8_T u8_4g_Version[NUM_20]; //4g版本
U32_T u32_outtime; //超时计数 单位秒
U8_T u8_tcp_socket_id; // 非透传:QISEND/QIRD 使用的 connectID(明文 TCP=0TLS 与 QSSLOPEN 的 clientID 一致)
S_4G_CONFIG_PARA s_4g_para;
}S_4G_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
S8_T s8_ec200a_4g_init(void);
void v_4g_data_init(S_4G_CONFIG_PARA *s_4g_para);
void v_4g_send_data(U8_T u8_id, U8_T *u8_sendBuf , U16_T u16_len);
U16_T u16_4g_recv_data(U8_T *u8_recvBuf ,U16_T u16_len);
/** 排空模组 RX FIFO(丢弃);FTP / QISEND 等共用 UART 去粘连 */
void v_4g_uart_rx_discard_fifo(void);
#if !EC200A_TCP_USE_TRANSPARENT_MODE
/** 非透传:经 AT+QISEND 发送 TCP/TLS 负载(按 EC200A_TCP_APP_RW_MAX 分包;详见 ec200a_4g.c */
void v_ec200a_tcp_app_send(U8_T u8_id, U8_T *u8_sendBuf, U16_T u16_len);
/** 非透传:AT+QIRD 读模组 Socket 缓冲(单次至多 EC200A_TCP_APP_RW_MAX;大帧需上层循环) */
U16_T u16_ec200a_tcp_app_recv(U8_T *u8_recvBuf, U16_T u16_len);
/** 模组已在 STEP_INIT_OK 且 PDP 正常时,仅 AT+QICLOSE/QSSLCLOSE + QIOPEN/QSSLOPEN 重建业务 Socket(不拆 PDP */
S8_T s8_ec200a_tcp_socket_close(void);
S8_T s8_ec200a_tcp_socket_soft_reopen(void);
#endif
U8_T u8_ec200a_modem_tcp_stack_ok(void);
U8_T u8_get_sim_card(U8_T *u8_data ,U8_T buf_len);
U8_T u8_UAT_check_signal_quality(void);
void v_4g_log_data_send_data(U8_T *u8_sendBuf , U16_T u16_len);
S8_T s8_ec200a_qiact(void);
/** 按 context_id 配置/激活/去激活 PDP,供 FTP 与业务 TCP 分场景(见 ec200a_4g_cfg.h */
S8_T s8_ec200a_qicsgp_ctx(U8_T context_id);
S8_T s8_ec200a_qiact_ctx(U8_T context_id);
S8_T s8_ec200a_qideact_ctx(U8_T context_id);
#ifdef __cplusplus
}
#endif
#endif /* EC200A_4G_H */
+172
View File
@@ -0,0 +1,172 @@
#ifndef EC200A_4G_CFG_H
#define EC200A_4G_CFG_H
#include "usart.h"
/**
* @file ec200a_4g_cfg.h
* @brief EC200A 模组:串口选择、TCP/FTP/TLS 场景、FTP 轮询与 UFS 块长、非透传 TCP 分包与缓冲派生。
* 可在包含本头或 `ec200a_4g.h` 之前用 `#define 宏名 值` 覆盖下列默认值。
*/
/* ========== 模组 UART 与调试(`ec200a_4g.c` 发送/排空等共用) ========== */
/** 1:在 `v_4g_send_data` / `u16_4g_recv_data` 等路径打印调试信息;0:关闭。开启时 printf 须走调试口,勿与模组口 COM_PORT_BS 共用 TX。 */
#ifndef TEST_BS_PRINTF_FLAG
#define TEST_BS_PRINTF_FLAG (0)
#endif
/** 与 Quectel 模组通信的 USART 逻辑编号(见 `usart.h` 中 `E_USART_ID`,默认 UART12 接 4G)。 */
#ifndef COM_PORT_BS
#define COM_PORT_BS E_USART_6
#endif
/** `v_4g_send_data` 第一形参占位 ID;当前实现未区分多路,固定 0xFF 即可。 */
#ifndef ID_4G
#define ID_4G (0xFFU)
#endif
/* -------------------------------------------------------------------------- */
/* 1) 透传 / QIOPEN 访问模式 */
/* -------------------------------------------------------------------------- */
/** Socket 数据路径:1 = UART 透传数据态;0 = 缓冲/命令模式(AT+QISEND / AT+QIRD)。 */
#ifndef EC200A_TCP_USE_TRANSPARENT_MODE
#define EC200A_TCP_USE_TRANSPARENT_MODE (0)
#endif
/** AT+QIOPEN / AT+QSSLOPEN 最后一参「access_mode」:透传为 2,缓冲模式为 0(随 `EC200A_TCP_USE_TRANSPARENT_MODE` 自动选择)。 */
#if EC200A_TCP_USE_TRANSPARENT_MODE
#define EC200A_TCP_QIOPEN_ACCESS_MODE (2)
#else
#define EC200A_TCP_QIOPEN_ACCESS_MODE (0)
#endif
/* -------------------------------------------------------------------------- */
/* 2) PDP:业务 TCP vs FTPTLS 与业务 TCP 同 PDP */
/* -------------------------------------------------------------------------- */
/** 业务 TCPQIOPEN/QSSLOPEN、模组 QIACT)使用的 PDP context ID。 */
#ifndef EC200A_TCP_PDP_CTX_ID
#define EC200A_TCP_PDP_CTX_ID (1U)
#endif
/** FTPQFTPCFG、QICSGP、QIACT、QIDEACT)使用的 PDP context ID;须与 `EC200A_TCP_PDP_CTX_ID` 不同,避免 FTP 去激活时拆掉业务 TCP。 */
#ifndef EC200A_FTP_PDP_CTX_ID
#define EC200A_FTP_PDP_CTX_ID (2U)
#endif
/* 编译期约束:TCP 与 FTP 不得共用同一 PDP,否则 FTP 侧 QIDEACT/重配会拆掉业务 TCP。 */
#if (EC200A_TCP_PDP_CTX_ID) == (EC200A_FTP_PDP_CTX_ID)
#error EC200A_FTP_PDP_CTX_ID must not equal EC200A_TCP_PDP_CTX_ID
#endif
/** TLSQSSL*)使用的 PDP 与业务 TCP 相同,与 `EC200A_TCP_PDP_CTX_ID` 保持一致。 */
#define EC200A_SSL_PDP_CTX_ID EC200A_TCP_PDP_CTX_ID
/** AT+QSSLCFG 等命令中的 SSL context 编号(模组侧 ctx)。 */
#ifndef EC200A_SSL_CTX_ID
#define EC200A_SSL_CTX_ID (1)
#endif
/** AT+QSSLOPEN 使用的 clientID / 套接字索引(与明文 TCP 的 connectID 区分)。 */
#ifndef EC200A_SSL_CLIENT_ID
#define EC200A_SSL_CLIENT_ID (4)
#endif
/* -------------------------------------------------------------------------- */
/* 3) FTPAT+QFREAD / AT+QFWRITE 轮询(单次 sleep 与总等待次数) */
/* -------------------------------------------------------------------------- */
/** QFREAD/QFWRITE 收 UART 时,每轮循环内单次 `mSleep` 间隔(毫秒)。 */
#ifndef EC200A_FTP_UART_SLICE_WAIT_MS
#define EC200A_FTP_UART_SLICE_WAIT_MS (25U)
#endif
/** 单次 `s16_qfread_at_slice` 调用内,最多执行「sleep + 收包」的次数;超时仍未见应答则本次读失败。 */
#ifndef EC200A_FTP_QFREAD_TOTAL_WAITS
#define EC200A_FTP_QFREAD_TOTAL_WAITS (80U)
#endif
/** 单次 `s8_ec200a_qfwrite_log_bin` 调用内,等待 CONNECT/OK 等阶段的最多次数;超出则本次写失败。 */
#ifndef EC200A_FTP_QFWRITE_TOTAL_WAITS
#define EC200A_FTP_QFWRITE_TOTAL_WAITS (200U)
#endif
/* -------------------------------------------------------------------------- */
/* 4) GetDiagnostics / UFSQFUPL 与单次读写块长 */
/* -------------------------------------------------------------------------- */
/** `AT+QFUPL` 第三参数:UART 侧无数据输入超时(秒),手册范围 1~65535;分片发送时须大于单 PlatComm 周期可能间隔。 */
#ifndef EC200A_FTP_QFUPL_AT_TIMEOUT_SEC
#define EC200A_FTP_QFUPL_AT_TIMEOUT_SEC (120U)
#endif
/** QFUPL 数据态下,单次向模组 UART 写入负载的上限(字节);0 表示按整块发送的旧策略(由上层决定)。 */
#ifndef EC200A_FTP_QFUPL_UART_SLICE_MAX
#define EC200A_FTP_QFUPL_UART_SLICE_MAX (512U)
#endif
/** 模组 UFS 单次 `AT+QFREAD` / `AT+QFWRITE` 数据负载(字节)。FTP 升级 QFREAD 缓冲、日志 FAT 读块与 QFWRITE 上限均以此为界;须为 Flash `PACKET_SIZE` 的倍数且 ≤65535。 */
#ifndef EC200A_FTP_UFS_RW_CHUNK_BYTES
#define EC200A_FTP_UFS_RW_CHUNK_BYTES (4096U)
#endif
#if !EC200A_TCP_USE_TRANSPARENT_MODE
/* -------------------------------------------------------------------------- */
/* 5) 非透传 TCP:软重连、QISDE、应用分包、模组 AT 上限与接收环形区派生尺寸 */
/* -------------------------------------------------------------------------- */
/** 连续「仅 QICLOSE+QIOPEN 软重连」失败达到此次数后,才走全量 `v_bs_4G_data_init` 类驻网恢复。 */
#ifndef EC200A_TCP_SOFT_REOPEN_FAIL_MAX
#define EC200A_TCP_SOFT_REOPEN_FAIL_MAX (5U)
#endif
/** AT+QISDE1 将 QISEND 的 TCP 负载回显到模组 UART(调试用);0 关闭(初始化通常下发 AT+QISDE=0)。 */
#ifndef EC200A_QISDE_ECHO
#define EC200A_QISDE_ECHO (0U)
#endif
/** 应用层单次 TCP 读/写意向上限(字节);`ec200a_4g.c` 内会再钳位到 `EC200A_QIRD_AT_READ_MAX` / `EC200A_QISEND_AT_LEN_MAX`。 */
#ifndef EC200A_TCP_APP_RW_MAX
#define EC200A_TCP_APP_RW_MAX (2048U)
#endif
/** AT+QIRD 第二参数「欲读长度」上限(Quectel 文档常见上限 1500);超过模组可能直接 ERROR。 */
#ifndef EC200A_QIRD_AT_READ_MAX
#define EC200A_QIRD_AT_READ_MAX (1500U)
#endif
/** AT+QISEND 单次发送负载上限(字节,Quectel 常见上限 1460)。 */
#ifndef EC200A_QISEND_AT_LEN_MAX
#define EC200A_QISEND_AT_LEN_MAX (1460U)
#endif
/** 读到 +QIRD: 0(缓冲无数据)后额外延时(毫秒),减轻空轮询;0 表示不延时。 */
#ifndef EC200A_QIRD_EMPTY_POLL_DELAY_MS
#define EC200A_QIRD_EMPTY_POLL_DELAY_MS (0U)
#endif
/** 存放 AT+QIRD 应答及 +QIRD: 前缀等 URC 的缓冲上限:`EC200A_TCP_APP_RW_MAX` + 固定余量(字节)。 */
#define EC200A_QIRD_RSP_MAX ((EC200A_TCP_APP_RW_MAX) + 384U)
/** 非透传下 QIOPEN/QSSLOPEN 应答在单任务内拼接用的累计缓冲大小(字节)。 */
#define EC200A_QIOPEN_ACCUM_SZ (640U)
#if EC200A_QISDE_ECHO
/** QISDE=1 时:等待 `>` / SEND OK 的环形接收区容量(字节),需容纳回显的应用数据。 */
#define EC200A_QISEND_WAIT_RB_SZ ((EC200A_TCP_APP_RW_MAX) * 2U + 512U)
/** QISDE=1 时:`memmove` 收缩接收区时保留的尾部字节数,避免截断半条 URC。 */
#define EC200A_QISEND_RB_TAIL_KEEP ((EC200A_TCP_APP_RW_MAX) / 2U + 256U)
#else
/** QISDE=0 时:等待 QISEND 提示与应答的接收缓冲容量(字节)。 */
#define EC200A_QISEND_WAIT_RB_SZ (896U)
/** QISDE=0 时:接收区收缩时保留的尾部字节数(字节)。 */
#define EC200A_QISEND_RB_TAIL_KEEP (384U)
#endif
#endif /* !EC200A_TCP_USE_TRANSPARENT_MODE */
#endif /* EC200A_4G_CFG_H */
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
#ifndef EC200A_4G_FTP_H
#define EC200A_4G_FTP_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "publicdata/type.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
U8_T u8_ec200a_4g_ftp_update();
U8_T u8_ec200a_4g_ftp_log_upload();
void v_ftp_ctrl_init();
#ifdef __cplusplus
}
#endif
#endif /* EC200A_4G_FTP_H */
@@ -0,0 +1,365 @@
# IES 积成平台通信移植方案(CCU601_A → CCU601E_D
本文档基于 **`CCU601_A_20240601\app\platform`** 中积成智通(IESZT)协议实现,对照 **`CCU601E_D\app\plat_comm`** 现有联网框架(`plat_comm_task` + `bs_connect_impl` + `bs_public_impl`),给出在 E 工程中**新增 IES 平台连接**的分阶段实现方案。
---
## 0. 备份说明
已在本机创建工程目录备份(不含 `MDK-ARM\output` 编译产物,以缩短拷贝时间):
| 路径 |
|------|
| `d:\Desktop\NewProject\cursor\EuropeanStandard\CCU601E_D_backup_20260519` |
如需完整含编译输出的备份,可在资源管理器中自行复制整个 `CCU601E_D` 目录。
---
## 1. 源工程(CCU601_AIES 模块概览
### 1.1 文件与职责
| 文件 | 行级规模(约) | 职责 |
|------|----------------|------|
| **`BS_ies_ctrl.h`** | 协议常量 + `S_IES_CTRL_DATA` | 帧类型码 `NT_*`、数据对象、对外 API |
| **`BS_ies_ctrl.c`** | ~560 行 | 回调注册、TCP/4G 建链、登录、收包解帧、私有超时逻辑 |
| **`BS_ies_interface.h/c`** | ~1800+ 行 | **组帧/解帧**:登录、心跳、费率、实时数据、账单、远程启停、对时、FTP 升级、二维码等 |
| **`BS_Task.c/h`** | ~1150 行 | **平台任务总线**:周期发送、双枪调度、`bs_register_callback``Task_BS` 主循环 |
| **`FtpUpdate.c`** | FTP 升级 | 与 `NT_UPDATE_AUTO(0x90)` 配合(A 侧旧 FTP 流程) |
| **`BS_WH_GM5.c`** | 4G 模组 | A 工程 **WH-GM5** 透传/AT**E 工程为 EC200A,不可直接搬** |
### 1.2 协议特征(与 OCPP 差异)
积成协议为 **二进制定长头 + 变长数据域**,非 JSON/WebSocket。
```
帧起始 0x68 | 长度 2B | 序号 1B | 设备地址 16B ASCII | 加密 1B | 数据对象 1B | 帧类型 1B | 数据域 N | 校验 1B
```
- **协议版本**`NT_PTL_VERSION = 37`V3.5
- **帧类型码**:登录 `0x01/0x02`、心跳 `0x03/0x04`、充电实时 `0x11/0x13`、账单 `0x47/0x48`、远程启停 `0x44~0x46`、对时 `0x76`、计费 `0x7D/0x7E`、FTP `0x90` 等(见 `BS_ies_ctrl.h`
- **校验**:序号域至数据域末字节 **累加和**`u8_crc_checkSum`
- **粘包**`v_bs_unpack_handle_ieszt``0x68` 搜头、长度域算整帧长
### 1.3 A 侧任务模型
```mermaid
flowchart TB
subgraph Task_BS
TCP[v_bs_tcp_connect]
LOG[v_ies_logo_on / v_bs_register_req]
SEND[v_bs_send_ctrl]
RECV[v_ies_recv_data → unpack]
PRIV[v_ies_private_crel]
end
subgraph callbacks
IF[BS_ies_interface 组帧]
end
SEND -->|E_BS_CALLBACK_CMD| IF
IF -->|com_read / eth_send| LINK[4G WH_GM5 / 以太网 socket]
```
- **发送**`BS_Task.c``v_bs_send_ctrl(bs_flag)` 按周期/标志调用 `v_bs_callback(cmd, gun)`IES 在 `v_bs_ies_register_callback()` 中注册具体组帧函数。
- **接收**`v_ies_recv_data()``v_bs_unpack_handle_ieszt()``v_bs_rx_cmd_hanle()``u8_recvBuf[22]` 帧类型分发。
- **登录成功**`v_bs_register_ack()` 内置 `u8_register_flag = 1`,并置 `bs_ctrl_data[].u8_log_on_flag`(与 E 的 `u8_log_on_flag` 概念一致)。
---
## 2. 目标工程(CCU601E_D)现状
### 2.1 已有挂钩(未实现)
| 位置 | 内容 |
|------|------|
| **`public_define.h`** | `BS_IES_EN` 默认 **0** |
| **`plat_comm_task.c`** | `MasterFuncArray` 已预留 **`"ieszt"`** 槽位及 7 个回调函数名 |
| **`plat_comm_task.h`** | `#include "plat_comm/ieszt/bs_ies_ctrl.h"``S_BS_IES_CTRL_DATA s_ies_data` |
| **目录** | **`plat_comm/ieszt/` 尚不存在**(编译开启 `BS_IES_EN` 会缺文件) |
E 侧 **`MasterFuncPtl`** 比 A 多一项 **`v_bs_recv_unpack`**(OCPP 未用;IES 可将解帧放在 `v_ies_recv_data` 内,**`v_ies_recv_unpack` 可留空**)。
### 2.2 E 侧应复用的基础设施(禁止重复造轮子)
| 模块 | 用途 |
|------|------|
| **`plat_comm_task.c`** | `v_Plat_Comm` 主循环、FTP/联网分时、`v_set_bs_renew` |
| **`bs_connect_impl.c`** | **`u8_tcp_connect`**、**`v_bs_send`**、**`u16_bs_recv`**4G EC200A + 网口 LwIP/TLS |
| **`bs_public_impl.c`** | 枪状态、**`S_LOG_DATA`** 订单、启停 **`v_bs_charge_action_ctrl`**、未结/掉电 **`s8_plat_get_unsettled_order`** |
| **`meter_calculate`** | 计量订单、EEPROM 临时单、Flash 未结索引 |
| **`flowctrl`** | `u8_bs_get_flow_state`E 已有,替代 A 的 `e_get_flow_rt_state` |
| **`v_ftp_update_action`** | OCPP/平台 FTP 升级;IES `0x90` 需映射到 **`t_ftp_update_info`** + `u8_updata_flag` |
### 2.3 A → E 关键差异对照
| 维度 | CCU601_A | CCU601E_D |
|------|----------|-----------|
| RTOS / 任务 | `Task_BS` + `GetCurSecond` | `v_Plat_Comm` + `get_current_seconds` |
| 4G | WH-GM5、`com_read(COM_PORT_BS)` | **EC200A**`v_ec200a_tcp_app_send/recv` |
| 网口 | `eth_connect` / `CreatSocket` 直连 | **`lwip_tcp_connect`** + `v_bs_eth_close_socket` |
| 订单结构 | **`CHARGE_LOG_T`** | **`S_LOG_DATA`** |
| 双平台控制块 | **`bs_ctrl_data[MAX_BS_NUM]`** | **`s_bs_task_ctrl.s_bs_ctrl[]`** + 可选 **`s_ies_data`** |
| 平台协议 | SDK + IES 二选一或双开 | **OCPP** + 可选 **IES**`MAX_BS_NUM=2` |
| 日志 | `logMsg_bs` | **`Plat_Comm_LOG` / `MYLOG_MSG`** |
---
## 3. 目标架构(移植后)
```mermaid
flowchart TB
subgraph PlatComm
PC[v_Plat_Comm]
M[MasterFuncArray slot: ieszt]
end
subgraph ieszt
CTRL[bs_ies_ctrl.c]
IF[bs_ies_interface.c]
SEND[v_ies_send_ctrl]
end
subgraph impl
CONN[bs_connect_impl]
PUB[bs_public_impl]
end
PC --> M
M --> CTRL
SEND --> IF
IF --> CONN
CTRL --> PUB
CONN --> EC200A[4G EC200A]
CONN --> LWIP[LwIP TCP/TLS]
```
**原则**
1. **IES 只实现「积成二进制协议」**TCP/4G/网口/TLS **一律走 `bs_connect_impl`**
2. **业务数据只通过 `bs_public_impl` + `meter_calculate`** 读写,不在 IES 模块内直接碰 flow 内部结构。
3. **周期发送**可移植 A 的 `E_BS_CALLBACK_CMD` 模型,但计时器挂到 **`s_bs_task_ctrl.s_bs_ctrl[ies_id]`** 或独立 **`S_BS_IES_CTRL_DATA`**,由 **`v_ies_send_ctrl`** 在已登录分支执行(与 OCPP 的 `v_ocpp_send_ctrl` 同级)。
---
## 4. 建议目录与文件清单
**`CCU601E_D/app/plat_comm/ieszt/`**(运行时说明见 **`ieszt/IES积成平台功能说明.md`**,与 **`平台联网任务功能说明.md`** 联动)新建:
| 文件 | 来源 | 说明 |
|------|------|------|
| **`bs_ies_ctrl.h`** | 自 A `BS_ies_ctrl.h` 改编 | 帧类型码、`S_BS_IES_CTRL_DATA`(合并原 `S_IES_CTRL_DATA` + 必要定时器) |
| **`bs_ies_ctrl.c`** | 自 A `BS_ies_ctrl.c` 重构 | 去掉 WH_GM5/CreatSocket;改用 `u8_tcp_connect(id)``v_bs_send``u16_bs_recv` |
| **`bs_ies_interface.h`** | 自 A `BS_ies_interface.h` | 组帧/解帧 API |
| **`bs_ies_interface.c`** | 自 A `BS_ies_interface.c` | 大文件;发送改为 `v_bs_send`;桩号从 `u8_get_str_data(E_STR_PILE_NO)` 获取 |
| **`bs_ies_send.c`**(可选) | 自 A `BS_Task.c` 抽取 | `v_ies_send_ctrl``v_ies_register_callback`、双枪周期逻辑,减小单文件体积 |
| **`bs_ies_adapter.c/h`**(建议新建) | 新写 | **`S_LOG_DATA` ↔ 账单组帧字段**、停止原因 **E_FAULT_* → IESZT_REASON** |
**不迁入 E 工程(或仅参考)**
- `BS_WH_GM5.c/h` → 使用现有 **`ec200a_4g.c`**
- `BS_sdk_*` → 与 IES 无关
- A 版 `FtpUpdate.c` 中与 OCPP 重复部分 → 统一 **`v_ftp_update_action`** + `ec200a_4g_ftp` / `lwip_ftp_upgrade`
---
## 5. 接口映射(MasterFuncPtl 七件套)
| 回调 | 实现要点 |
|------|----------|
| **`v_ies_init_data(U8_T id)`** | `id` 写入 `s_ies_data.u8_bs_flag``v_ies_register_callback()`;清 `s_ies_data`**不要**调用 `v_bs_4G_data_init`(由 `u8_tcp_connect` 统一处理);帧序号 `u16_msgSerialNumber=0` |
| **`v_ies_tcp_connect(void)`** | 返回 **`u8_tcp_connect(s_ies_data.u8_bs_flag)`** 结果;删除 A 中 `v_WH_GM5_init`、独立 `CreatSocket` |
| **`v_ies_logo_on(void)`** | 周期发 **`v_bs_register_req()`**(约 20s);**`v_bs_register_ack`** 内置置位后返回 1;登录超时 6 次 → **`v_set_bs_renew(id)`** + 仅重置 IES 私有数据(**勿全量 `v_ocpp_init_data`** |
| **`v_ies_recv_data(void)`** | `u16_bs_recv(id, buf, len)`;若 `u8_updata_flag` 且 4G FTP 活跃则可直接 return(与 A 一致) |
| **`v_ies_recv_unpack(id, buf, len)`** | **建议空实现**;解帧已在 `v_ies_recv_data` 完成。若未来 UART 分包由外部注入,再迁入 `v_bs_unpack_handle_ieszt` |
| **`v_ies_send_ctrl(void)`** | 移植 A **`v_bs_send_ctrl(ies_flag)`**:登录后心跳、费率、空闲/充电实时、表底、告警、账单、即插即充、离线单等 |
| **`v_ies_private_logic(U8_T id)`** | 心跳无应答计数、登录态维护、**`v_cheak_state_change` 类逻辑**(改调 E 的 `v_bs_log_txFlag_*`)、离线订单 **`s8_plat_get_unsettled_order` 路径**(可选与 OCPP 互斥) |
---
## 6. 数据与业务适配层(`bs_ies_adapter`
### 6.1 订单 / 账单(`NT_BILL_UPLOAD 0x47`
A`v_get_log_data` / `v_gunTmpChgRcd_rd` + `CHARGE_LOG_T``v_bs_bill_pack`
E 建议:
```c
S_LOG_DATA log;
u8_plat_get_real_order(&log, gun) s8_plat_get_unsettled_order(&log, 0);
v_ies_bill_pack_from_log(gun, &log);
```
- **`u8_preTradeNo`**:IES 账单字段与 A 一致处直接 `memcpy`;无平台流水号时用设备流水号或占位规则与运营确认。
- **停充原因**`log.e_stop_reason[0]`**`v_ies_map_stop_reason_to_ieszt()`** 转为 `IESZT_REASON_E`A 已在 `BS_Task.c` 有大表,可迁到 adapter)。
### 6.2 远程启停(`0x44` / `0x46`
A`v_bs_rmt_start/stop` 内调 flow/UI。
E
```c
S_BS_CHAR_CTRL ctrl;
memset(&ctrl, 0, sizeof(ctrl));
ctrl.u16_ctrl_res = E_NORMAL_1002; /* 或映射平台原因 */
v_bs_charge_action_ctrl(gun, E_CHAR_CTRL_STOP, &ctrl);
```
启机需组 **`S_CHAR_LOG_START_DATA`** 后 `E_CHAR_CTRL_START`,与 OCPP RemoteStart 路径对齐,避免双协议冲突(见 §8)。
### 6.3 对时(`NT_CLK_SYNC_REQ 0x76`
A:写 RTC。
E**`v_bs_set_time(&Comm_Time)`**`bs_public_impl` 已有)。
### 6.4 计费模型(`0x05/0x06/0x7D/0x7E`
A:写费率 Flash/共享区。
E:对接 **`meter_calculate` / 费率存储**`u32_meterlog_get_sys_data(E_METERLOG_GET_DATA_FEE_*)` 或现有配置接口),需与产品确认 IES 费率 PDU 与 E 侧 96 时段模型是否一致。
### 6.5 实时数据(`0x11` / `0x13`
字段来源改为:
- 电压电流 SOC`u32_bs_get_sys_data` / `u64_bs_get_sys_data`
- 枪状态:`u8_bs_get_flow_state``E_BS_GET_SYS_DATA_GUN_STATE`
- 电量:`E_BS_GET_SYS_DATA_MEMTER_ELECT`
### 6.6 FTP 升级(`NT_UPDATE_AUTO 0x90`
解析平台下发的 FTP 参数 → 填充 **`t_ftp_update_info`** → 置对应槽位 **`u8_updata_flag=1`** → 由现有 **`v_ftp_update_action`** 执行(4G/网口分支已有)。
---
## 7. 配置与编译集成
### 7.1 宏与工程配置
```c
/* public_define.h */
#define BS_IES_EN (1) /* 使能积成平台 */
#define BS_OCPP_EN (1) /* 可与 OCPP 双平台:槽位 0=OCPP1=IES,由 PLAT_EN 决定 */
```
- Keil / CMake:将 **`plat_comm/ieszt/*.c`** 加入 **PlatComm** 分组。
- 确认 **`plat_comm_task.h`** 中 `S_BS_IES_CTRL_DATA``bs_ies_ctrl.h` 定义一致(或 typedef 别名)。
### 7.2 系统参数(与 A 对齐项)
| 配置项 | A 来源 | E 建议 |
|--------|--------|--------|
| 主站 IP/端口 | `u8_remote_ip` / `u16_remote_port` | 继续用 **`t_sys_fix_cfg`**IES **不用** WebSocket URL,可用 **纯 `tcp://ip:port`** 或单独 **IES 服务器配置项**(若与 OCPP 域名不同,需在配置表增加字段,避免与 `u8_serverA_domain_name` 混用) |
| 桩号 16 字节 | `u8_pile_serial_num` | `u8_get_str_data(E_STR_PILE_NO)` |
| 平台使能 | `bs_type[]` 初始化 | `E_BS_GET_SYS_DATA_PLAT_EN` per 槽位 |
| 联网方式 | `e_network_type` | `E_BS_GET_SYS_DATA_NET_TYPE` |
### 7.3 `MasterFuncArray` 注册示例
与现有 `plat_comm_task.c` 一致,确保 **`BS_IES_EN`** 时槽位与 **`PLAT_EN`** 配置对应,例如:
- 槽位 0OCPP`BS_OCPP_EN`
- 槽位 1IES`BS_IES_EN`
双平台同时使能时,**两路独立 `u8_TCP_connect_flag` / `u8_log_on_flag`**,互不覆盖。
---
## 8. 双平台共存策略(OCPP + IES
| 风险 | 策略 |
|------|------|
| 同一枪被 OCPP RemoteStart 与 IES `0x44` 同时控制 | 产品层约定:**仅一个平台为主**;或 `PLAT_EN` 互斥。代码层在 `v_bs_rmt_start` 中判断 **`u8_bs_get_flow_state==0`** 且 OCPP 未在充。 |
| 离线订单双路上送 | E 未结索引统一 **`unsettled_order_mng`**IES 掉电单走 **`s8_plat_get_unsettled_order`** 时与 OCPP **互斥处理**(同一单不两协议各 Stop 一次)。 |
| 4G UART 占用 | OCPP 与 IES **共用 `bs_connect_impl`** 时,**不能两槽位同时 `u8_netType==0` 各建一条 Socket**,除非运营确认为双连接;通常 **一机一路 4G 一个 TCP****双平台只选其一或网口+4G 分流**。 |
| FTP 与 IES 收包 | 保持 **`plat_comm_task` FTP/联网分时**IES **`v_ies_recv_data`** 在 FTP 周期不解析 IES 帧(与 A 相同 guard)。 |
---
## 9. 分阶段实施计划
### 阶段 1:骨架可编译(1~2 天)
- [ ] 创建 `plat_comm/ieszt/`,迁入 **`bs_ies_ctrl.h`**、精简 **`bs_ies_ctrl.c`**`init/tcp_connect/logo_on/recv/private` 空壳 + `u8_tcp_connect`
- [ ] **`BS_IES_EN=1`** 编译通过;`v_ies_recv_unpack` 空实现
- [ ] 槽位 **`bs_enable=1`** 时能进 `v_Plat_Comm` 建链日志
### 阶段 2:链路 + 登录心跳(2~3 天)
- [ ] 迁入 **`bs_ies_interface.c`** 中:`u8_bs_pack_frm_head``v_bs_register_req/ack``v_bs_heart/ack``u8_crc_checkSum`
- [ ] 收包粘包 **`v_bs_unpack_handle_ieszt`**
- [ ] 实机验证:**Login 0x02**、**Heartbeat 0x04**、平台对时(若有)
### 阶段 3:运营关键帧(3~5 天)
- [ ] **`v_bs_rtd_free` / `v_bs_rtd_chging`** + `v_ies_send_ctrl` 周期表(对齐 A:心跳 20s、充电 30s、空闲 180s 等)
- [ ] **`v_bs_bill_pack`** + adapter 从 **`S_LOG_DATA`**
- [ ] **`v_bs_rmt_start/stop`** → `v_bs_charge_action_ctrl`
- [ ] **`v_bs_bill_upload_ack`** → `v_bs_platform_settlement_confirm` / 清 EEPROM 临时单
### 阶段 4:增强与对齐(按需)
- [ ] 计费模型 `0x7D/0x7E`、二维码 `0x08/0x0A`、故障 `0x15`、表底 `0x31`
- [ ] FTP `0x90``t_ftp_update_info`
- [ ] 掉电未结:与 **`meter_calculate` EEPROM 恢复**、**`s8_plat_get_unsettled_order`** 联调
- [ ] 双枪 `u8_bs_twoGnu_send` 逻辑迁入
### 阶段 5:测试与文档
- [ ] 用例:4G / 网线、单枪/双枪、远程启停、断网重连、充电中离线、掉电上电补单
- [ ] 更新 **`平台联网任务功能说明.md`** 增加 IES 章节
---
## 10. 接收发送伪代码(E 风格)
```c
void v_ies_recv_data(void)
{
U8_T id = s_ies_data.u8_bs_flag;
U8_T buf[512];
U16_T n;
if (s_bs_task_ctrl.s_bs_ctrl[id].u8_updata_flag != 0U
&& s_bs_task_ctrl.s_bs_ctrl[id].u8_netType == 0U) {
return;
}
n = u16_bs_recv(id, buf, sizeof(buf));
if (n > 0U) {
v_bs_unpack_handle_ieszt(buf, (S16_T)n);
}
}
static void v_ies_send_frame(U8_T *frm, U16_T len)
{
v_bs_send(s_ies_data.u8_bs_flag, frm, len);
}
```
---
## 11. 测试验收标准(摘要)
| 编号 | 项 | 通过标准 |
|------|-----|----------|
| T1 | TCP | `PlatComm` 打印 `tcp connect`,平台侧可见连接 |
| T2 | 登录 | 收到 `0x02``u8_log_on_flag=1`,桩号与配置一致 |
| T3 | 心跳 | 周期 `0x03`,平台 `0x04`,断链后 `v_set_bs_renew` 可恢复 |
| T4 | 充电实时 | 充电中 `0x11`、空闲 `0x13` 周期上送,数据与电表一致 |
| T5 | 账单 | 停充后 `0x47`,平台 `0x48` 后本地未结清除 |
| T6 | 远程停 | 平台 `0x46` 后枪停止,原因码正确 |
| T7 | 掉电 | 有 txId 的订单恢复后账单 **transactionId 非 0**(与 OCPP 掉电修复一致) |
---
## 12. 关联文档
| 文档 | 路径 |
|------|------|
| E 联网任务总览 | `plat_comm/平台联网任务功能说明.md` |
| OCPP 模块 | `plat_comm/ocpp/OCPP功能模块说明.md` |
| 4G EC200A | `plat_comm/4g_module/4G模块AT指令配置功能说明.md` |
| 源 IES 协议头 | `CCU601_A_20240601/app/platform/BS_ies_ctrl.h` |
---
*文档版本:2026-05-19;随 `plat_comm/ieszt` 落地情况更新勾选清单。*
+14
View File
@@ -0,0 +1,14 @@
# gwsdk — 国网平台 SDK(预留)
**当前未参与 CCU621 编译。**
- `public_define.h``BS_GWSDK_EN = 0`
- `GD32H759.uvprojx` 未添加本目录下 `.c` 文件
- 依赖第三方头:`infra_defs.h``protocol.h` 等(PCU606 侧 SDK 包)
启用前需:
1. 将 SDK 库与头文件路径加入 Keil Include
2.`bs_gwsdk_ctrl.c``bs_gwsdk_interface.c` 加入工程
3.`gwsdk_interface.c``ccu_comm/ccu_interface.h` 改为 `plat_comm/impl/bs_local_data.h`
4. 设置 `BS_GWSDK_EN=1`,并与 `BS_IES_EN` 互斥或按产品配置二选一
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
#ifndef BS_GWSDK_CTRL_H
#define BS_GWSDK_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/type.h"
#if (BS_GWSDK_EN)
/*------ Exported macro(宏定义) -------------------------*/
#define GWSDK_TIMERCNT_NUM (3 + 5*GUN_MAX_CNT) //计时计数 -每一个需主动上送的报文总数
/*------ Exported struct(结构体定义) --------------------*/
typedef enum{
SDK_TIMER_REQUSET = 0, //校时
SDK_TIMER_FEE_REQ, //费率请求
SDK_TIMER_DC_DEV_INFO, //直流设备属性上报
SDK_TIMER_REAL_WORK_GUN, //工作实时状态 数量跟随枪数量 - 包含工作状态和空闲状态两种
SDK_TIMER_TRADE_INFO = SDK_TIMER_REAL_WORK_GUN + GUN_MAX_CNT, //订单信息上送 数量跟随枪数量
SDK_TIMER_PROPERTY_BMS = SDK_TIMER_TRADE_INFO + GUN_MAX_CNT, //BMS 属性周期上送
SDK_TIMER_METER_INFO = SDK_TIMER_PROPERTY_BMS + GUN_MAX_CNT, //电表低值上送
SDK_TIMER_FAULT_INFO = SDK_TIMER_METER_INFO + GUN_MAX_CNT, //故障信息上报
}SDK_SEND_TIMER;
typedef struct
{
U8_T u8_bs_flag; //处于双平台架构中的哪个 用于映射控制结构体的地址
S8_T gw_connet_flag; //联网标识
U8_T u8_bs_state_type; //维护状态
U8_T u8_bs_reboot_flag; //平台重启标志 - 要求收到重启命令时,需回复后再重启
U8_T u8_bs_gun2_notup_cnt; //2枪订单未上传计时
U8_T txFlag_synTime; //是否已校时标识 0否 1是
U8_T txFlag_askFeeMode; //请求费率
U8_T txFlag_firwareInfo; //固件信息上送标志
U8_T txFlag_versInfo; //版本信息上送标志
U8_T txFlag_cfgInfoUpdate; //配置信息更新请求
U8_T txFlag_devMduInfoEvt; //设备组件信息上报
U8_T u8_Meter_ack[2]; //电表召测标志
U8_T u8_sim_id[20]; //sim卡号
U16_T u16_work_info_period; //工作实时状态 上送周期
U16_T u16_free_info_period; //空闲实时状态 上送周期
U16_T ChargeCnt; //充电序号计次
U16_T OperateCnt; //操作序号计次 生成订单流水号时使用
struct
{
U8_T u8_real_state_cnt; //充电实时状态 上送计数
U16_T u16_charge_cnt; //充电次数
U32_T u32_charge_time; //充电时间
}s_gwsdk_gun[GUN_MAX_CNT];
struct
{
U16_T u16_timerCnt; //周期发送计数
U16_T u16_timeNUM ; //计数周期值 单位秒
}s_timerCnt[GWSDK_TIMERCNT_NUM];
}S_GWSDK_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_sdk_init_data(U8_T bs_flag);
U8_T v_sdk_tcp_connect(void);
U8_T v_sdk_logo_on(void);
void v_sdk_recv_data(void);
void v_sdk_send_ctrl(void);
void v_sdk_private_crel(U8_T u8_flag);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_GWSDK_CTRL_H */
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
#ifndef BS_GWSDK_INTERFACE_H
#define BS_GWSDK_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#if (BS_GWSDK_EN)
#include "infra_defs.h"
#include "protocol_data_def.h"
#include "interface.h"
#include "protocol.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void gw_get_firmware_data(evs_event_firmware_info *fireware_info ); //获取固件信息
void gw_get_ver_data(evs_event_ver_info *ver_info); //获取版本信息
void gw_get_devInfo_data(evs_event_devmdu_info* devInfo_data); //获取设备组件信息
void gw_get_alarm_data(evs_event_alarm *event_alarm ,U8_T u8_gunNo); //获取故障告警信息
void gw_getPilePropertyData(evs_property_dcPile* dcProperty); //获取直流桩设备属性
void gw_get_gunlink_data(evs_event_pile_stutus_change *gunlink_state,U8_T u8_gunNo); //获取枪连接状态
void gw_get_stopResult_info(U8_T gunNo,evs_event_stopCharge* event_stopResult); //枪停止结果上送
void gw_get_startResult_info(U8_T gunNo,evs_event_startResult* event_startResult) ;//枪启动结果上送
void gw_get_DC_meter_data(evs_property_meter* meterData,U8_T u8_gunNo); //获取直流电表底值数据
void gw_get_dc_BMS_Data(evs_property_BMS* BMSData ,U8_T u8_gunNo); //获取直流桩BMS属性;
void gw_get_log_info(U8_T flag ,U8_T gunNo,evs_event_tradeInfo* tradeInfo_data);
void gw_get_Auth_Data(evs_event_startCharge* AuthData ,U8_T u8_gunNo); //获取即插即充所需数据
void gw_get_dc_nonWork(evs_property_dc_nonWork* gunData,U8_T u8_gunNo); //获取直流非充电中状态数据
void gw_get_dc_work(evs_property_dc_work* gunData,U8_T u8_gunNo); //获取直流充电中状态数据
U8_T u8_feeModle_save(evs_service_issue_feeModel *param);
U8_T u8_gwsdk_start_charge_ctrl(evs_service_startCharge *param);
U8_T u8_gwsdk_stop_chaege_ctrl(evs_service_stopCharge *param);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_GWSDK_INTERFACE_H */
@@ -0,0 +1,200 @@
# IES 积成平台功能说明(CCU601E_D / plat_comm/ieszt
本文档描述 **积成智通 IESZT** 二进制协议在 E 工程中的落地方案、与 **PlatComm 联网任务** 的挂接关系,以及 **`S_BS_IES_CTRL_DATA`** 各字段含义。
上层任务状态机、双平台槽位、FTP 分时等见 **[《平台联网任务功能说明.md》](../平台联网任务功能说明.md)**。
---
## 1. 模块定位
| 项目 | 说明 |
|------|------|
| **目录** | `app/plat_comm/ieszt/` |
| **编译宏** | **`BS_IES_EN`**`public_define.h` |
| **PlatComm 槽位** | `MasterFuncArray` 名称 **`"ieszt"`**`plat_comm_task.c`);与 OCPP 并存时通常为槽位 **0**OCPP 为槽位 **1**(以 `PLAT_EN` 配置为准) |
| **全局数据** | **`s_bs_task_ctrl.s_ies_data`**`plat_comm_task.h`,类型 **`S_BS_IES_CTRL_DATA`** |
| **传输层** | 与 OCPP 共用 **`bs_connect_impl`**`u8_tcp_connect` / `v_bs_send` / `u16_bs_recv`**明文 TCP**,无 WebSocket |
| **当前阶段** | **登录/心跳** + **校时** + **费率** + **二维码 0x08/0x0A** |
---
## 2. 源文件与功能分区
**`BS_ocpp_ctrl.c`** 相同,`.c` 内用 **`#if 1 //公共` / `#if 1 //发送` / `#if 1 //接收` / `#if 1 //外部控制使用`** 折叠分区(便于 Keil 大纲跳转与整段屏蔽调试)。
| 文件 | `#if 1` 分区 | 内容 |
|------|----------------|------|
| **`bs_ies_ctrl.c`** | 公共 | `v_ies_timer_add``u8_ies_timer_ctrl`、定时器初值、`v_ies_reset_ctx``v_ies_on_login_ok` |
| | 发送 | `v_ies_logo_on``v_ies_send_ctrl` |
| | 接收 | `v_ies_recv_data``v_ies_recv_unpack` |
| | 外部控制使用 | `v_ies_init_data``v_ies_tcp_connect``v_ies_private_logic` |
| **`impl/bs_public_impl.c`** | — | **`u8_get_str_data` / `u8_set_str_data`**、**`u32_bs_get_sys_data` / `u8_set_sys_data`**(含 `E_STR_QR_DISPLAY``E_BS_GET_SYS_DATA_UNIX_TIMESTAMP` |
| **`bs_ies_interface.c`** | 公共 | 组帧头、CRC、`v_ies_finish_frame_and_send` |
| | 发送 | `v_ies_register_req``v_ies_heart_req``v_ies_fee_req``u8_ies_send_ack` |
| | 接收 | 登录/心跳/校时/费率/二维码;`v_ies_unpack_handle`(经 `bs_public_impl` 读写系统) |
| **`bs_ies_ctrl.h`** | — | 协议常量、`IES_SEND_TIMER``S_BS_IES_CTRL_DATA`、对外 API |
| **`bs_ies_interface.h`** | — | 报文层对外声明 |
移植路线图(阶段 3+)见 **`../IES积成平台移植方案.md`**。
---
## 3. 与 PlatComm 的挂接(对照联网任务文档 §4)
### 3.1 七回调映射
| PlatComm 回调 | IES 实现 | 说明 |
|---------------|----------|------|
| **`v_bs_data_init`** | `v_ies_init_data` | 清零 **`s_ies_data`**,写入 **`u8_bs_flag`** |
| **`v_bs_tcp_connect`** | `v_ies_tcp_connect` | 建链前清 **`u8_register_flag`**;内部 **`u8_tcp_connect(id)`** |
| **`v_bs_logon_connect`** | `v_ies_logo_on` | 周期发 **0x01****`u8_register_flag==1`** 时返回 1,置槽位 **`u8_log_on_flag`** |
| **`v_bs_recv_data`** | `v_ies_recv_data` | **`u16_bs_recv`** → **`v_ies_unpack_handle`** |
| **`v_bs_recv_unpack`** | `v_ies_recv_unpack` | 空实现(4G 外部分包预留) |
| **`v_bs_send_ctrl`** | `v_ies_send_ctrl` | 已登录后周期发 **0x03**、**0x05**(未获费率时) |
| **`v_bs_private_logic`** | `v_ies_private_logic` | **`u8_heart_outTime_cnt`** 超限 → **`v_set_bs_renew`** |
调度顺序与 **`v_plat_comm_network_round`** 一致,见 **[平台联网任务功能说明.md §4.3](../平台联网任务功能说明.md)**。
### 3.2 与 `S_BS_CTRL_DATA` 的分工
| 数据 | 位置 | 用途 |
|------|------|------|
| TCP/登录标志 | **`s_bs_ctrl[i].u8_TCP_connect_flag`** / **`u8_log_on_flag`** | PlatComm 通用状态机 |
| IES 协议态 | **`s_ies_data`** | 登录应答、心跳计数、**`s_timerCnt[]`** 周期定时、序列号等 |
| 槽位号 | **`s_ies_data.u8_bs_flag`** | 与 **`i`**、`E_STR_PILE_NO` 第三参数、`v_bs_send(id)` 一致 |
---
## 4. 运行态结构 `S_BS_IES_CTRL_DATA`
所有 IES 私有计时与计数集中在 **`s_bs_task_ctrl.s_ies_data`**`v_ies_reset_ctx` / `memset` 时一并清零)。
| 字段 | 类型 | 含义 |
|------|------|------|
| **`u8_bs_flag`** | U8 | PlatComm 槽位 **0/1**;对应 **`E_BS_GET_SYS_DATA_PLAT_EN`** 与桩编号 **`E_STR_PILE_NO`** 的 `u8_dataPara` |
| **`u8_register_out_cnt`** | U8 | 已发 **0x01**、仍未收到 **0x02** 的累计次数;超过 **`BS_IES_LOGIN_RETRY_MAX`** 触发 **`v_set_bs_renew`** |
| **`u8_heart_outTime_cnt`** | U8 | 已发 **0x03**、仍未收到 **0x04** 的连续次数;由 **`v_ies_heart_req`** 递增、**`v_ies_heart_ack`** 清零 |
| **`u8_register_flag`** | U8 | **1** = 平台登录成功(**0x02** 且结果字节为 0);为 1 时 **`v_ies_logo_on`** 返回 1 |
| **`u8_syn_time_flag`** | U8 | **1** = 已完成平台校时(登录成功或收到 **0x76** |
| **`u8_ask_fee_flag`** | U8 | **1** = 已获取/下发费率,不再周期发 **0x05** |
| **`u16_msgSerialNumber`** | U16 | 自发帧序列号(组帧头时 `s16_serialNum<=0` 自增) |
| **`ulong_restart_timer`** | ULONG | 最近一次 TCP 建链成功时刻(`get_current_seconds` |
| **`s_timerCnt[]`** | 数组 | 与 OCPP **`OCPP_DATA_CTRL.s_timerCnt`** 同结构:`u16_timerCnt` 每秒 +1`u16_timeNUM` 为周期(秒) |
| **`ack_ctrl[]`** | 指针数组 | 平台下行需应答帧的异步队列(**`IES_DATA_RECV_ACK`**,同 OCPP **`ack_ctrl`**);`v_ies_send_ctrl` 每轮上送一条 |
### 4.1 `IES_SEND_TIMER` 与默认周期(`v_ies_init_timer_period`
| 枚举 | 默认 `u16_timeNUM`(秒) | 说明 |
|------|-------------------------|------|
| **`IES_TIMER_LOGIN`** | `BS_IES_LOGIN_INTERVAL_S`20 | `v_ies_logo_on` 发 0x01 |
| **`IES_TIMER_HEART`** | `BS_IES_HEART_INTERVAL_S`20 | `v_ies_send_ctrl` 发 0x03 |
| **`IES_TIMER_FEE_MODULE`** | 30 | 登录后周期发 **0x05** 直至 **0x06****0x7D** 写入费率 |
| **`IES_TIMER_FREE_DATA_GUN1/2`** | 180 | 空闲实时 0x13 |
| **`IES_TIMER_CHG_DATA_GUN1/2`** | 30 | 充电实时 0x15 |
| **`IES_TIMER_METER_GUN1/2`** | 3600 | 电表 |
| **`IES_TIMER_TRADE_GUN1`** | 10 | 账单 |
内部接口(`bs_ies_ctrl.c`,与 OCPP 同名语义):**`v_ies_timer_add()`** 每秒递增;**`u8_ies_timer_ctrl(cmd, GET_FLAG/CLEAR_FALG)`** 查询到时或清零。
---
## 5. 登录与心跳流程
```text
[TCP 未连接]
v_ies_tcp_connect → u8_tcp_connect
(清 u8_register_flagCLEAR_FALG LOGIN/HEART 定时器)
[TCP 已连接,未登录]
v_ies_logo_on → v_ies_timer_add
IES_TIMER_LOGIN 到时 → v_ies_register_req
收到 0x02 且 [BODY]==0 → v_ies_on_login_ok(含首包心跳定时到点)
超时次数 > BS_IES_LOGIN_RETRY_MAX → v_set_bs_renew + v_ies_reset_ctx
[已登录,u8_log_on_flag==1]
v_ies_send_ctrl → v_ies_timer_add
IES_TIMER_HEART 到时 → v_ies_heart_req
u8_ask_fee_flag==0 且 IES_TIMER_FEE_MODULE 到时 → v_ies_fee_req
v_ies_recv_data → 0x04 → v_ies_heart_ack
→ 0x76 → v_ies_set_clk_syncv_bs_set_timeack 挂入 ack_ctrl
→ 0x7D → 解析费率 → v_bs_save_fee_data0x7E 挂入 ack_ctrl
v_ies_send_ctrl → ack_ctrl 非空 → u8_ies_send_ack → v_ies_free_ack_data
v_ies_private_logic → v_ies_timer_add
u8_heart_outTime_cnt > BS_IES_HEART_MISS_MAX → v_set_bs_renew
```
---
## 6. 0x68 帧与已实现命令
帧域下标见 **`bs_ies_ctrl.h`** 中 **`IES_FRM_OFF_*`**(与 A 工程 `u8_recvBuf[21/22/23]` 一致)。
| 类型码 | 方向 | 函数 |
|--------|------|------|
| **0x01** | 上行 | `v_ies_register_req` |
| **0x02** | 下行 | `v_ies_register_ack` |
| **0x03** | 上行 | `v_ies_heart_req` |
| **0x04** | 下行 | `v_ies_heart_ack` |
| **0x05** | 上行 | `v_ies_fee_req` |
| **0x06** | 下行 | `v_ies_fee_module_ack``v_bs_save_fee_data` |
| **0x07** | 上行 | `u8_ies_send_ack` 二维码下发应答 |
| **0x08** | 下行 | `v_ies_qr_dis``QRDisplay[]` + Flash |
| **0x09** | 上行 | `u8_ies_send_ack` 二维码召唤应答 |
| **0x0A** | 下行 | `v_ies_qr_call` |
| **0x75** | 上行 | `v_ies_clk_sync_ack` |
| **0x76** | 下行 | `v_ies_set_clk_sync``v_bs_set_time` |
| **0x7D** | 下行 | `v_ies_fee_cfg_req` |
| **0x7E** | 上行 | `v_ies_fee_cfg_ack` |
| 其它 | — | `v_ies_rx_cmd_handle` 打日志占位 |
---
## 7. 配置与联调要点
| 配置项 | 接口 | 说明 |
|--------|------|------|
| 平台使能 | **`E_BS_GET_SYS_DATA_PLAT_EN`** | 槽位 0/1 与 **`MasterFuncArray`** 下标一致 |
| 联网方式 | **`E_BS_GET_SYS_DATA_NET_TYPE`** | 0=4G1=网口;IES 与 OCPP 共用同一套 connect |
| 桩编号 | **`E_STR_PILE_NO`** + **`u8_bs_flag`** | 双平台时平台 A/B 各一套桩号 |
| 枪数 / 故障 | **`E_BS_GET_SYS_DATA_GUN_NUM`**、**`NOW_FAULT_ALARM_NUM`** | 登录帧、心跳帧组帧 |
**双平台 + 单 4G TCP**:产品与 **`PLAT_EN`** 需约定主平台;详见移植方案「风险」章节。
**FTP 升级中**`v_ies_recv_data`**`u8_updata_flag!=0` 且 4G** 时跳过收包(与 OCPP 策略一致,见联网任务文档 §4.4)。
---
## 8. 日志
| 宏 | 定义位置 | 说明 |
|----|----------|------|
| **`IES_PRINT_FLAG`** | `plat_comm_task.h` | 值 **0xFE**,与 **`OCPP_PRINT_FLAG`(0xFF)** 同类:不受 `u8_task_log_flag` 门控时默认输出 |
| **`IES_PRINTF_LOG`** | `bs_ies_ctrl.h` | `MYLOG_MSG(IES_PRINT_FLAG, …)`;**ieszt 目录内请用此宏,勿用 `Plat_Comm_LOG`** |
| 日志 | 含义 |
|------|------|
| `IES: init slot=` | `v_ies_init_data` |
| `IES: login req pile=` | 发出 0x01 |
| `IES: login ok` / `login fail` | 0x02 解析结果 |
| `IES: heartbeat tx cnt=` | 发出 0x03(含未应答计数) |
| `IES: heartbeat ack` | 收到 0x04 |
| `IES: clk sync` / `clk sync ack` | 0x76 校时 / 0x75 应答 |
| `IES: fee module req` / `fee model saved` | 0x05 请求 / 0x06·0x7D 落盘 |
| `IES: login timeout, renew` / `heartbeat miss, renew` | 触发 **`v_set_bs_renew`** |
---
## 9. 关联文档
| 文档 | 内容 |
|------|------|
| **[平台联网任务功能说明.md](../平台联网任务功能说明.md)** | PlatComm 任务、双槽状态机、FTP 分时、**`v_set_bs_renew`** |
| **[IES积成平台移植方案.md](../IES积成平台移植方案.md)** | A→E 分阶段移植清单 |
| **`4g_module/4G模块AT指令配置功能说明.md`** | 4G TCP/FTP 与 PlatComm 分时 |
| **`impl/bs_public_impl.h`** | **`E_STR_*`** / **`E_BS_GET_SYS_DATA_*`** 枚举 |
---
*文档版本:与 `plat_comm/ieszt` 源码同步;结构体字段变更时请同步更新 §4。*
+43
View File
@@ -0,0 +1,43 @@
# ieszt — 积成 IES 运营平台协议
规约依据:积成充电桩网络通信协议(V3.5 / V3.7 / V3.8,由 `BS_IES_TYPE` 选择)
## 编译开关
`public_define.h` 中设置:
```c
#define BS_IES_EN (1) // 使能积成 IES 运营协议(CCU621 默认)
```
协议版本在 `bs_ies_ctrl.h` 中配置:
```c
#define BS_IES_TYPE (2) // 1=山高3.82=积成3.7(默认),3=积成3.5
```
可与 `BS_IESYW_EN` 并存:平台 A 走 IES 运营(本目录),平台 B 走远程运维(`iesyw/`),共用 4G 模组 socketA/B 分发。
## 文件
| 文件 | 说明 |
| --- | --- |
| `bs_ies_ctrl.c/h` | 任务回调、登录/心跳、周期上送调度、接收解帧 |
| `bs_ies_interface.c/h` | 帧格式(0x68 起始)、帧类型码、组包/解包 |
## 主要帧类型
| 类型 | 说明 |
| --- | --- |
| 0x01/0x02 | 登录/应答 |
| 0x03/0x04 | 心跳/应答 |
| 0x05/0x06 | 计费模型请求/应答 |
| 0x07~0x0A | 二维码下发/召唤 |
| 0x11/0x13 | 充电中/非充电实时数据 |
| 0x15 | 故障告警 |
| 0x31 | 表底值上送 |
| 0x41~0x48 | 启停/账单结算 |
| 0x76 | 对时 |
| 0x7D/0x7E | 费率相关 |
详细说明见 [plat_comm模块说明.md](../plat_comm模块说明.md)。
+709
View File
@@ -0,0 +1,709 @@
#include "plat_comm/ieszt/bs_ies_ctrl.h"
#include "plat_comm/ieszt/bs_ies_interface.h"
#include "plat_comm/impl/bs_connect_impl.h"
#include "plat_comm/impl/bs_public_impl.h"
#if WH_GM5_EN
#include "plat_comm/4g_module/bs_wh_gm5.h"
#endif
#if (BS_IES_EN)
#define BS_TIME_1S 10 //1秒 发送周期1秒计数
S_BS_IES_CTRL_DATA *private_ies_data;
#endif
#if (BS_IES_EN) //内部控制函数
void v_ies_send(U8_T *u8_sendBuf, U32_T sendLen)
{
u8_sendBuf[sendLen] = u16_crc_checksum(&u8_sendBuf[3], sendLen-3); //和校验
sendLen ++;
//更新帧长度域
u8_sendBuf[1] = (sendLen-4)>>8;
u8_sendBuf[2] = sendLen-4;
v_bs_send(private_ies_data->id,u8_sendBuf,sendLen);
}
//发送周期计数累加 1秒加一
static void v_ies_timer_add()
{
U8_T i;
static ULONG time_old = 0;
ULONG time_ing = get_current_seconds(); //获取当前秒数
if(time_old != time_ing)
{
time_old = time_ing;
for (i = 0; i < IES_TIMERCNT_NUM; i++)
{
if(private_ies_data->s_timerCnt[i].u16_timerCnt < private_ies_data->s_timerCnt[i].u16_timeNUM)
{
private_ies_data->s_timerCnt[i].u16_timerCnt ++;
}
}
}
}
//获取发送周期计数到时标志
static U8_T u8_ies_timer_ctrl(IES_SEND_TIMER cmd ,FLAG_CTLR flag)
{
U8_T ret = 0;
if(flag == CLEAR_FALG) //清空对应计时计数
{
private_ies_data->s_timerCnt[(U8_T)cmd].u16_timerCnt = 0;
ret = 1;
}
else if(private_ies_data->s_timerCnt[(U8_T)cmd].u16_timeNUM != 0 &&
private_ies_data->s_timerCnt[(U8_T)cmd].u16_timerCnt >= private_ies_data->s_timerCnt[(U8_T)cmd].u16_timeNUM)
{
private_ies_data->s_timerCnt[(U8_T)cmd].u16_timerCnt = 0;
ret = 1;
}
return ret;
}
#endif
#if (BS_IES_EN) //发送相关
/**
* ************************************************************************
* @brief - 故障信息变化检查
*
*
* @return U8_T - 0未变化 1变化
*
* @author jiankalka
* @date 2025-09-20
* ************************************************************************
*/
static U8_T u8_cheak_fault()
{
static U8_T u8_old_fault_num=0;
U8_T u8_fault_num,ret = 0;
u8_fault_num = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM,0) +
u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM,1); //获取故障 告警数量
if(u8_fault_num != u8_old_fault_num)
{
u8_old_fault_num = u8_fault_num;
ret = 1;
}
return ret;
}
/**
* ************************************************************************
* @brief - 抢连接变化检查
*
* @param[in] u8_gunNo - 抢号 0-N
*
* @return U8_T - 0未变化 1已经变化
*
* @author jiankalka
* @date 2025-09-20
* ************************************************************************
*/
static U8_T u8_cheak_gunlink(U8_T u8_gunNo)
{
static U8_T u8_old_gunLink[GUN_MAX_CNT] = {0};
U8_T u8_gunLink = u8_ies_get_gunLink_state(u8_gunNo); //获取枪连接状态 0未连接 1已连接
//u8_gunLink = (u8_gunLink == 0 ? 0 : 1);
if(u8_gunLink != u8_old_gunLink[u8_gunNo])
{
//printf("%d枪变位,原状态:%d,现状态:%d\n",u8_gunNo,u8_old_gunLink[u8_gunNo],u8_gunLink);
u8_old_gunLink[u8_gunNo] = u8_gunLink;
return 1;
}
return 0;
}
/**
* ************************************************************************
* @brief - 抢状态变化检查
*
* @param[in] u8_gunNo - 抢号 0-N
*
* @return U8_T - 0未变化 1已经变化
*
* @author jiankalka
* @date 2025-09-20
* ************************************************************************
*/
static U8_T u8_cheak_gunState(U8_T u8_gunNo)
{
static U8_T u8_old_gunState[GUN_MAX_CNT] = {0};
U8_T u8_gunState = u8_ies_get_gun_state(u8_gunNo); //获取枪状态 0未连接 1已连接
//u8_gunLink = (u8_gunLink == 0 ? 0 : 1);
if(u8_gunState != u8_old_gunState[u8_gunNo])
{
//printf("%d枪变位,原状态:%d,现状态:%d\n",u8_gunNo,u8_old_gunState[u8_gunNo],u8_gunState);
u8_old_gunState[u8_gunNo] = u8_gunState;
return 1;
}
return 0;
}
//发送控制
static void v_ies_send_mag_ctrl()
{
S_BS_CTRL_DATA *s_bs_ctrl = &s_bs_task_ctrl.s_bs_ctrl[private_ies_data->id]; //获取控制数据
U8_T i,u8_data; //临时变量 使用前注意赋值
U8_T u8_gunCnt = s_bs_task_ctrl.u8_gunCnt;
U8_T u8_get_flow_state[GUN_MAX_CNT];
static U8_T fee_flag = 0; //连接平台后直接请求一次计费模型标志
//检查心跳周期是否到位
if(u8_ies_timer_ctrl(IES_TIMER_Heartbeat,GET_FLAG) == 1)
{
v_ies_heart();
return;
}
//电表底值上送
for(i=0;i<u8_gunCnt;i++)
{
if(u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_MeterValues_GUN + i), GET_FLAG) == 1)
{
v_ies_meter_data(i);
return;
}
}
//主动上送
//桩主动请求充电
for(i=0;i<u8_gunCnt;i++)
{
if(private_ies_data->s_ctrl_flag[i].u8_charge_req == 1)
{
v_ies_start_req(i);
return;
}
}
//请求费率
if(fee_flag == 0 ||(private_ies_data->txFlag_askFeeMode == 0 &&
u8_ies_timer_ctrl(IES_TIMER_FEE_REQ,GET_FLAG) == 1))
{
v_ies_fee_req();
fee_flag = 1;
return;
}
//订单上送
for(i=0; i<u8_gunCnt; i++)
{
u8_data = 0;
if(s_bs_ctrl->s_gundata[i].txFlag_tradUpload == 1)
{
if( s_bs_ctrl->s_gundata[i].u8_tradUpload_cnt < 6) //充电中 只发6次
u8_data = 1;
}
else
s_bs_ctrl->s_gundata[i].u8_tradUpload_cnt = 0;
if(u8_data == 1 && u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_TRADE_INFO + i), GET_FLAG) == 1)
{
v_ies_bill_pack(i);
s_bs_ctrl->s_gundata[i].u8_tradUpload_cnt ++;
return;
}
}
//故障状态上送-有故障变化时上送,无故障变化时,3分钟上送一次
u8_data = u8_cheak_fault();
if(u8_data == 1 || u8_ies_timer_ctrl(IES_TIMER_FAULT_GUN ,GET_FLAG) == 1)
{
u8_ies_timer_ctrl(IES_TIMER_FAULT_GUN ,CLEAR_FALG); //清空计数
v_ies_fault_V35(i);
return;
}
//充电/空闲实时信息上报
for(i=0; i<u8_gunCnt; i++)
{
u8_get_flow_state[i] = u8_bs_get_flow_state(i); //获取终端状态 0 空闲 1开始充电 2充电中
if(u8_get_flow_state[i] == 2) //充电中
{
//当状态由变化时 前3帧按10秒发送--u8_real_state_cnt 0x00-0x0F 充电中计数 0x10-0x1F 非充电中计数
private_ies_data->s_ies_gun[i].u8_real_state_cnt &= 0x0F;
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 3)
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = 10; //前3帧 10秒周期上送
}
else
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = private_ies_data->u16_work_info_period; //上送周期等于充电数据上送周期设置值
}
if(u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_REAL_WORK_GUN + i), GET_FLAG) == 1)
{
// printf("%d枪,充电中,u8_real_state_cnt=%02x\n",i,private_ies_data->s_ies_gun[i].u8_real_state_cnt);
v_ies_rtd_chging(i);
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 3)
{
private_ies_data->s_ies_gun[i].u8_real_state_cnt ++;
}
return;
}
}
else
{
//当状态由变化时 前3帧按10秒发送--u8_real_state_cnt 0x00-0x0F 充电中计数 0x10-0x1F 非充电中计数
private_ies_data->s_ies_gun[i].u8_real_state_cnt &= 0xF0; //清除低位计数值
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 0x30)
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = 3; //前3帧 3秒周期上送
}
else
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = private_ies_data->u16_free_info_period; //上送周期等于充电数据上送周期设置值
}
if(u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_REAL_WORK_GUN + i), GET_FLAG) == 1 //|| u8_cheak_gunlink(i)
|| u8_cheak_gunState(i)) //延时到时间上送、枪状态改变上送
{
//printf("%d枪,非充电中,u8_real_state_cnt=%02x\n",i,private_ies_data->s_ies_gun[i].u8_real_state_cnt);
v_ies_rtd_free(i);
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 0x30)
{
private_ies_data->s_ies_gun[i].u8_real_state_cnt += 0x10;
}
return;
}
}
}
}
#endif
#if (BS_IES_EN) //接收
static void v_bs_rx_cmd_hanle(U8_T *u8_recvBuf) //具体报文解帧
{
U8_T u8_cmd = u8_recvBuf[22];
switch(u8_cmd)
{
case NT_LOGIN_ACK: //0x02 //登录注册应答
{
if(private_ies_data->u8_register_flag == 0)
{
v_ies_register_ack(u8_recvBuf);
}
break;
}
case NT_TICK_ACK: //0x04 //心跳应答
{
v_ies_heart_ack();
break;
}
case NT_FEE_MODULE_ACK: //0x06 //计费模型应答
{
v_ies_fee_ack(u8_recvBuf);
break;
}
case NT_MDU_DATA_ACK: //0x17 //充电桩整流模块数据应答
{
break;
}
case NT_START_CHG_ACK: //0x42 //平台确认桩主动申请启动充电
{
v_ies_rx_start(u8_recvBuf);
break;
}
case NT_REMOTE_START_REQ: //0x44 //平台远程控制启机
{
if(private_ies_data->txFlag_askFeeMode == 1) //未收到计费模型,不启动充电
{
Plat_Comm_LOG("private_ies_data->txFlag_askFeeMode=%d\n",private_ies_data->txFlag_askFeeMode);
v_ies_rmt_start(u8_recvBuf);
}
break;
}
case NT_REMOTE_STOP_REQ: //0x46 //平台远程停机
{
v_ies_rmt_stop(u8_recvBuf);
break;
}
case NT_BILL_UPLOAD_ACK: //0x48 //平台结算账单确认
{
v_ies_bill_upload_ack(u8_recvBuf);
break;
}
case NT_USERACNTINFO_ACK: //0x4A //平台回复查询用户账单信息(未用)
{
//v_bs_user_acntInfo_ack(u8_recvBuf);
break;
}
case NT_CARD_BILL_UPLOAD_ACK: //0x4C //平台刷卡结算订单应答
{
//v_bs_bill_card_ack(u8_recvBuf);
break;
}
case NT_WORK_PARA_CFG_REQ: //0x72 //平台设置工作参数
{
//v_bs_set_wkPara(u8_recvBuf);
break;
}
case NT_WORK_PARA_QUERY_REQ: //0x74 //平台查询工作参数
{
//v_bs_query_wkPara(u8_recvBuf);
break;
}
case NT_CLK_SYNC_REQ: //0x76 //平台对时
{
v_ies_set_clk_sync(u8_recvBuf);
break;
}
case NT_REMOTE_OPRT_REQ: //0x78 //平台远程操作
{
//v_bs_rmt_oprt(u8_recvBuf);
break;
}
case NT_FEE_CFG_REQ: //0x7D //平台设置计费模型
{
v_ies_set_fee(u8_recvBuf);
break;
}
case NT_UPDATE_AUTO: //0x90 //平台下发FTP升级命令
{
//v_ies_upgrade_data_auto(u8_recvBuf);
break;
}
case NT_QR_REQ_CALL: //0x0A 平台召唤二维码
{
v_ies_qr_call(u8_recvBuf);
break;
}
case NT_QR_REQ_DIS: //0x08 平台下发二维码
{
Plat_Comm_LOG("Receive platform QR code command\n");
// printf("收到平台下发二维码命令\n");
// for(int i=0;i < 100;i++)
// {
// printf("%.2x ",u8_recvBuf[i]);
// }
// printf("\n");
v_ies_qr_dis(u8_recvBuf);
break;
}
default:
break;
}
}
// static void v_bs_unpack_handle_ieszt(U8_T *u8_recvBuf, U16_T u16_rcvLen) //解帧函数
// {
// U8_T u8_crc;
// U16_T u16_rcvByteNum = 0; //解粘包指针
// U16_T u16_oneFrmLen = 0; //一完整帧理论总长度
// U16_T u16_dataLen = 0; //数据域长度
// U8_T upackBuf[BS_RECV_BUF_LEN] = {0}; //待解析帧缓存
// while(u16_rcvLen >= 23) //帧头长度=23
// {
// if(u8_recvBuf[u16_rcvByteNum] != NT_HEADER) //找帧头
// {
// u16_rcvByteNum++;
// u16_rcvLen--;
// continue;
// }
// u16_dataLen = (U16_T)((u8_recvBuf[u16_rcvByteNum+1]<<8) + u8_recvBuf[u16_rcvByteNum+2]);
// if(u16_dataLen >= 256)
// return;
// u16_oneFrmLen = u16_dataLen + 4;
// if(u16_oneFrmLen <= u16_rcvLen) //构成完整帧
// {
// memcpy(&upackBuf[0], &u8_recvBuf[u16_rcvByteNum], u16_oneFrmLen);
// //和校验
// u8_crc = u16_crc_checksum(&upackBuf[3], u16_oneFrmLen-4);
// if(u8_crc == upackBuf[u16_oneFrmLen-1])
// {
// v_bs_rx_cmd_hanle(upackBuf); //和校验通过,解析数据
// }
// u16_rcvByteNum += u16_oneFrmLen;
// u16_rcvLen -= u16_oneFrmLen;
// }
// else
// return;
// }
// }
static void v_bs_unpack_handle_ieszt(U8_T *u8_recvBuf, U16_T u16_rcvLen) //解帧函数
{
U8_T u8_crc;
U16_T u16_rcvByteNum = 0; //解粘包指针
U16_T u16_oneFrmLen = 0; //一完整帧理论总长度
U16_T u16_dataLen = 0; //数据域长度
U8_T upackBuf[BS_RECV_BUF_LEN] = {0}; //待解析帧缓存
static U8_T u8_save_buf[BS_RECV_BUF_LEN*2] = {0};
static U16_T u16_save_len = 0;
// 检查缓冲区是否溢出
if(u16_rcvLen + u16_save_len >= BS_RECV_BUF_LEN*2)
{
// 移除前四分之一数据以腾出空间
U16_T remove_len = u16_save_len / 4; // 计算要移除的长度
U16_T remain_len = u16_save_len - remove_len; // 剩余数据长度
if(remain_len > 0) {
// 将后面的数据向前移动,覆盖前四分之一
memmove(u8_save_buf, u8_save_buf + remove_len, remain_len);
}
u16_save_len = remain_len; // 更新保存的数据长度
}
// 将接收到的数据添加到缓存区中
if(u16_rcvLen > 0) {
memcpy(&u8_save_buf[u16_save_len], u8_recvBuf, u16_rcvLen);
u16_save_len += u16_rcvLen;
}
// 使用静态缓冲区数据进行解析
U16_T process_len = u16_save_len; // 当前要处理的数据长度
u16_rcvByteNum = 0; // 重置解析指针
while(process_len >= 23) //帧头长度=23
{
if(u8_save_buf[u16_rcvByteNum] != NT_HEADER) //找帧头
{
u16_rcvByteNum++;
process_len--;
continue;
}
u16_dataLen = (U16_T)((u8_save_buf[u16_rcvByteNum+1]<<8) + u8_save_buf[u16_rcvByteNum+2]);
if(u16_dataLen >= 256)
{
u16_rcvByteNum++;
process_len--;
continue; // 数据长度异常,跳过这个帧头
}
u16_oneFrmLen = u16_dataLen + 4;
if(u16_oneFrmLen <= process_len) //构成完整帧
{
memcpy(&upackBuf[0], &u8_save_buf[u16_rcvByteNum], u16_oneFrmLen);
//和校验
u8_crc = u16_crc_checksum(&upackBuf[3], u16_oneFrmLen-4);
if(u8_crc == upackBuf[u16_oneFrmLen-1])
{
v_bs_rx_cmd_hanle(upackBuf); //和校验通过,解析数据
}
u16_rcvByteNum += u16_oneFrmLen;
process_len -= u16_oneFrmLen;
}
else
break; // 数据不完整,等待下次接收更多数据
}
// 将处理完的数据从静态缓冲区中移除
if(u16_rcvByteNum > 0) // 有处理完的数据需要移除
{
// 计算剩余未处理数据的长度
U16_T remaining_len = u16_save_len - u16_rcvByteNum;
if(remaining_len > 0)
{
// 将未处理的数据向前移动,覆盖已处理的数据
memmove(u8_save_buf, u8_save_buf + u16_rcvByteNum, remaining_len);
}
// 更新静态缓冲区的数据长度
u16_save_len = remaining_len;
// 清空移动后空出的部分(可选,为了调试清晰)
if(remaining_len < BS_RECV_BUF_LEN*2) {
memset(u8_save_buf + remaining_len, 0, BS_RECV_BUF_LEN*2 - remaining_len);
}
}
}
#endif
#if (BS_IES_EN) //外部控制使用
/****************************************************
*函数名称: v_ies_init_data()
*功能说明: 程序运行前 参数初始化
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
void v_ies_init_data(U8_T bs_flag)
{
U8_T i;
private_ies_data = &s_bs_task_ctrl.s_ies_data; //指针数据初始化
memset(private_ies_data,0,sizeof(S_BS_IES_CTRL_DATA));
private_ies_data->id = bs_flag; //平台位置
// if (bs_flag == 0U) {
// s_bs_task_ctrl.s_bs_ctrl[bs_flag].u8_netType =
// (u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NET_TYPE,bs_flag) == 1U) ? 0U : 1U;
// } else {
// s_bs_task_ctrl.s_bs_ctrl[bs_flag].u8_netType =
// (u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NET_TYPE,bs_flag) == 1U) ? 0U : 1U;
// }
s_bs_task_ctrl.s_bs_ctrl[bs_flag].fd = -1;
s_bs_task_ctrl.s_bs_ctrl[bs_flag].ulong_restart_timer = get_current_seconds();
if(s_bs_task_ctrl.s_bs_ctrl[bs_flag].u8_netType == 0) //4G
{
v_bs_4G_data_init();
}
//周期发送控制初始化
private_ies_data->u16_work_info_period = 30; //工作实时状态 上送周期
private_ies_data->u16_free_info_period = 120; //空闲实时状态 上送周期
//周期发送控制初始化
private_ies_data->s_timerCnt[IES_TIMER_Register].u16_timeNUM = 20; //秒 登录帧上送周期
private_ies_data->s_timerCnt[IES_TIMER_Register].u16_timerCnt = 20; //确保立马执行登录帧
private_ies_data->s_timerCnt[IES_TIMER_Heartbeat].u16_timeNUM = 15; //秒 心跳帧上送周期
private_ies_data->s_timerCnt[IES_TIMER_FEE_REQ].u16_timeNUM = 60; //秒 费率请求
private_ies_data->s_timerCnt[IES_TIMER_FAULT_GUN].u16_timeNUM = 60*3; //秒 故障信息上报
for ( i = 0; i < GUN_MAX_CNT; i++)
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = 30; //秒 工作实时状态
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timerCnt = 0xFFFF;
private_ies_data->s_timerCnt[IES_TIMER_TRADE_INFO + i].u16_timeNUM = 30; //秒 订单信息上送
private_ies_data->s_timerCnt[IES_TIMER_MeterValues_GUN + i].u16_timeNUM = 60*30; //秒 电表低值上送
}
}
/****************************************************
*函数名称: v_ies_tcp_connect()
*功能说明: TCP连接功能实现 - 配置4G模块
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
U8_T v_ies_tcp_connect(void)
{
return u8_tcp_connect(private_ies_data->id);
}
/****************************************************
*函数名称: v_ies_logo_on()
*功能说明: 平台桩信息登录 -
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
U8_T v_ies_logo_on(void)
{
v_ies_timer_add(); //计时计数+1
//检查登录周期是否到位
if(u8_ies_timer_ctrl(IES_TIMER_Register,GET_FLAG) == 1)
{
v_ies_register_req(); //登录帧
Plat_Comm_LOG("Send login platform packet, u8_register_flag:%d\n",private_ies_data->u8_register_flag);
if(private_ies_data->u8_register_out_cnt++ > 6) //两分钟登录帧未连接成功
{
v_set_bs_renew(private_ies_data->id); //设置主函数 联网重连
v_ies_init_data(private_ies_data->id); //设置 子函数参数重置 重走初始化过程
Plat_Comm_LOG("Login frame send timeout\n");
}
}
if(private_ies_data->u8_register_flag == 1)
{
private_ies_data->u8_register_out_cnt = 0;
return 1;
}
else
return 0;
}
/****************************************************
*函数名称: v_ies_recv_data()
*功能说明: 数据接收处理 - 控制任务周期调用 使用网口或者4G非分包功能时使用,需内部实现从串口获取数据
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ies_recv_data(void)
{
S16_T s16_recvLen = 0;
U8_T u8_recvBuf[BS_RECV_BUF_LEN] = {0};
s16_recvLen = u16_bs_recv(private_ies_data->id,u8_recvBuf,BS_RECV_BUF_LEN);
if(s16_recvLen > 0)
{
v_bs_unpack_handle_ieszt(&u8_recvBuf[0], s16_recvLen);
}
}
/**
* ************************************************************************
* @brief - 控制任务接收解析完数据后调用 4G 模块多包接收时使用-当4G使用多socket数据接收时,串口数据获取和分包功能由外部实现,函数传入参数为本平台的接收数据
*
* @param[in] id - 平台地址
* @param[in] recvBuf - 接收数据
* @param[in] u16_len - 接收数据长度
*
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
void v_ies_recv_unpack(U8_T id, U8_T *recvBuf,U16_T u16_len)
{
v_bs_unpack_handle_ieszt(recvBuf,u16_len);
}
/****************************************************
*函数名称: v_ies_send_ctrl()
*功能说明: 发送控制函数 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ies_send_ctrl(void)
{
v_ies_timer_add(); //计时计数+1
v_ies_send_mag_ctrl(); //发送控制
}
/****************************************************
*函数名称: v_ies_private_logic()
*功能说明: 私有协议逻辑控制 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ies_private_logic(U8_T bs_flag)
{
if(private_ies_data->u8_heart_outTime_cnt > 3) //发送三次心跳 未回复 则进行重连
{
v_set_bs_renew(private_ies_data->id); //设置主函数 联网重连
v_ies_init_data(private_ies_data->id); //设置 子函数参数重置 重走初始化过程
//轻质化群充测试过程中发现与平台连接超时后不重连
//检查发现iotx_linkkit_ctx_t结构体中is_opened,is_connected未复位,原因未知,为确保现场使用,此地暂时强制复位
//v_connected_resset();
Plat_Comm_LOG("Timeout reconnect\n");
}
}
#endif
+100
View File
@@ -0,0 +1,100 @@
/**
* @file bs_ies_ctrl.h
* @brief 积成 IES 平台协议控制层
*
* 定义 IES 协议私有数据结构、周期上送计时器及任务回调声明。
* 协议版本由 BS_IES_TYPE 选择(默认积成 3.7)。
*/
#ifndef BS_IES_CTRL_H
#define BS_IES_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#if (BS_IES_EN)
/*------ Exported macro(宏定义) -------------------------*/
#define IES_TIMERCNT_NUM (4 + 3*GUN_MAX_CNT) //计时计数
#define IES_ACK_STRUCT_NUM 5 //应答结构体数量
/*************************************
* 宏名称: BS_IES_TYPE
* 宏说明: 积成平台协议版本
* 设置说明:未定义:报错
* 1 山高平台 3.8 2积成 3.7 3积成 3.5
*
**************************************/
#define BS_IES_TYPE 2
/*------ Exported struct(结构体定义) --------------------*/
typedef enum{
IES_TIMER_Register = 0, //登录帧 0
IES_TIMER_Heartbeat, //心跳 1
IES_TIMER_FEE_REQ, //费率请求 2
IES_TIMER_FAULT_GUN, //充电故障告警上送3
IES_TIMER_REAL_WORK_GUN, //工作实时状态 数量跟随枪数量 - 包含工作状态和空闲状态两种 4
//IES_TIMER_FAULT_GUN = IES_TIMER_REAL_WORK_GUN+GUN_MAX_CNT, //充电故障告警上送, 4+20 24
IES_TIMER_MeterValues_GUN = IES_TIMER_REAL_WORK_GUN+GUN_MAX_CNT, //表底值上送, 25+20 45
IES_TIMER_TRADE_INFO = IES_TIMER_MeterValues_GUN+GUN_MAX_CNT, //订单信息上送 数量跟随枪数量 46+20 66
}IES_SEND_TIMER;
typedef struct
{
U8_T id; //平台位置 平台1还是平台2
U8_T u8_register_out_cnt; //登录帧超时计数
U8_T u8_heart_outTime_cnt; //心跳超时计时
U8_T u8_register_flag; //是否已注册/是否联网.0否1是
U8_T txFlag_askFeeMode; //请求费率
U8_T u8_time_flag; //校时标志
U8_T u8_heart_outTime; //心跳超时计时--
U16_T u16_msgSerialNumber; //帧序号
U16_T u16_work_info_period; //工作实时状态 上送周期
U16_T u16_free_info_period; //空闲实时状态 上送周期
struct
{
U8_T u8_MeterValues_type; //数据上送标志 0无动作 1空闲数据上送 2充电中数据上送 3交易开始数据上送 4交易停止数据上送
U8_T u8_charge_req; //充电请求标志 0 不请求充电 1 请求充电
}s_ctrl_flag[GUN_MAX_CNT]; //数据控制标志
struct
{
U8_T u8_real_state_cnt; //充电实时状态 上送计数
U16_T u16_charge_cnt; //充电次数
U32_T u32_charge_time; //充电时间
}s_ies_gun[GUN_MAX_CNT];
struct
{
U16_T u16_timerCnt; //周期发送计数
U16_T u16_timeNUM ; //计数周期值
}s_timerCnt[IES_TIMERCNT_NUM];
}S_BS_IES_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_ies_init_data(U8_T bs_flag);
U8_T v_ies_tcp_connect(void);
U8_T v_ies_logo_on(void);
void v_ies_recv_data(void);
void v_ies_send_ctrl(void);
void v_ies_private_logic(U8_T bs_flag);
void v_ies_recv_unpack(U8_T id, U8_T *recvBuf,U16_T u16_len);
void v_ies_send(U8_T *u8_sendBuf, U32_T sendLen);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_IES_CTRL_H */
File diff suppressed because it is too large Load Diff
+336
View File
@@ -0,0 +1,336 @@
/**
* @file bs_ies_interface.h
* @brief 积成 IES 平台协议报文层
*
* 帧格式、帧类型码定义及组包/解包函数声明。
* 帧头 0x68,设备地址 16 字节 ASCII 桩编号。
*/
#ifndef BS_IES_INTERFACE_H
#define BS_IES_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#if (BS_IES_EN)
/*------ Exported macro(宏定义) -------------------------*/
/************积成通信协议帧结构*********************
* 帧起始域 1字节 0x68
* 帧长度域 2字节 从序列号域到帧数据域,高字节在前,低字节在后
* 序列号域 1字节 数据包发送顺序号,从0开始,应答帧则同询问包
* 设备地址 16字节 ASCII
* 加密方式 1字节 0不加密,1AES加密,登录帧和确认帧加密方式为0
* 数据对象 1字节 高2位:0直流1交流2交直流;低6位标识枪号,0代表充电机本体
* 帧类型码 1字节
* 帧数据域 N字节
* 校验和 1字节 序列号域到帧数据域
**************************************************/
#define NT_PTL_VERSION 37 //通信协议版本V3.5
#define NT_HEADER 0x68 //帧头
//数据对象
#if BS_TYPE_JIMO_EN //即墨平台使能,则桩类型为液冷充电桩。否则为直流桩
#define NT_DC_PILE 3 //直流桩 //#define NT_AC_PILE 1 //交流桩 //直流桩 0积成平台 3即墨平台
#else
#define NT_DC_PILE 0 //直流桩 //#define NT_AC_PILE 1 //交流桩 //直流桩 0积成平台 3即墨平台
#endif
#define NT_0B_MASK_H 0xC0 //高2位掩码
#define NT_0B_MASK_L 0x3F //低6位掩码
#define NT_ID_PILE 0 //充电桩本体
#define NT_ID_GUN1 1 //1号充电枪
#define NT_ID_GUN2 2 //2号充电枪
/************帧类型码宏定义************/
//登录心跳帧类型码数据定义
#define NT_LOGIN_REQ 0x01 //登录请求
#define NT_LOGIN_ACK 0x02 //登录应答
#define NT_TICK_REQ 0x03 //心跳请求
#define NT_TICK_ACK 0x04 //心跳应答
#define NT_FEE_MODULE_REQ 0x05 //计费模型请求
#define NT_FEE_MODULE_ACK 0x06 //计费模型应答
#define NT_QR_REQ_DIS_ACK 0x07 //二维码下发应答
#define NT_QR_REQ_DIS 0x08 //二维码下发
#define NT_QR_REQ_CALL_ACK 0x09 //二维码召唤应答
#define NT_QR_REQ_CALL 0x0A //二维码召唤帧
//实时数据帧类型码数据定义
#define NT_GUN_DATA_CHGING 0x11 //直流充电枪充电中实时数据
#define NT_GUN_DATA_FREE 0x13 //直流充电枪非充电中实时数据
#define NT_FAULT_DATA 0x15 //直流充电桩故障告警数据上传
#define NT_MDU_DATA_REQ 0x16 //运营平台整流模块数据读取 rectifier module整流模块
#define NT_MDU_DATA_ACK 0x17 //充电桩整流模块数据应答
//表底值上送
#define NET_METER_DATA 0x31 //表底值上送
//运营交互帧类型码数据定义
#define NT_START_CHG_REQ 0x41 //充电桩主动申请启动充电
#define NT_START_CHG_ACK 0x42 //平台确认启动充电
#define NT_REMOTE_START_ACK 0x43 //充电桩回复远程启机
#define NT_REMOTE_START_REQ 0x44 //平台远程控制启机
#define NT_REMOTE_STOP_ACK 0x45 //充电桩远程停机命令回复
#define NT_REMOTE_STOP_REQ 0x46 //平台远程停机
#define NT_BILL_UPLOAD 0x47 //充电结算帐单上传
#define NT_BILL_UPLOAD_ACK 0x48 //平台结算账单确认
#define NT_USERACNTINFO_REQ 0x49 //充电桩主动申请查询用户账户信息(未用)
#define NT_USERACNTINFO_ACK 0x4A //平台回复查询用户账单信息(未用)
#define NT_CARD_BILL_UPLOAD 0x4B //充电桩刷卡结算订单上传
#define NT_CARD_BILL_UPLOAD_ACK 0x4C //平台刷卡结算订单应答
//运营平台设置帧类型码数据定义
#define NT_WORK_PARA_CFG_ACK 0x71 //充电桩应答工作参数设置
#define NT_WORK_PARA_CFG_REQ 0x72 //平台设置工作参数
#define NT_WORK_PARA_QUERY_ACK 0x73 //充电桩应答工作参数查询
#define NT_WORK_PARA_QUERY_REQ 0x74 //平台查询工作参数
#define NT_CLK_SYNC_ACK 0x75 //充电桩应答平台对时
#define NT_CLK_SYNC_REQ 0x76 //平台对时
#define NT_REMOTE_OPRT_ACK 0x77 //充电桩应答平台远程操作
#define NT_REMOTE_OPRT_REQ 0x78 //平台远程操作
#define NT_LCD_SHOW_ACK 0x79 //充电桩应答平台触发液晶屏显示(用户电话)
#define NT_LCD_SHOW_REQ 0x7A //平台触发液晶屏显示内容
#define NT_UNLOCK_CARD_REQ 0x7B //充电桩请求解锁卡
#define NT_UNLOCK_CARD_ACK 0x7C //平台应答解锁卡请求
#define NT_FEE_CFG_REQ 0x7D //平台设置计费模型
#define NT_FEE_CFG_ACK 0x7E //充电桩应答模型设置
//远程升级数据定义
#define NT_UPGRADE_RQ 0x80 //平台发送远程升级指令
#define NT_UPGRADE_DATA_REQ 0x81 //桩向平台请求文件内容(多次执行,直至文件传输完毕)
#define NT_UPGRADE_DATA_ACK 0x82 //平台应答文件内容请求
#define NT_UPDATE_AUTO 0x90 //平台FTP升级请求
#define NT_UPDATE_AUTO_ACK 0x93 //充电桩主动上传FTP文件传输结果
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
typedef enum /* 积成智通后台通信协议-订单上送-启机失败/停机原因 */
{
IESZT_NORMAL=0, //0正常
IESZT_STARTING_FAULT, //1启动中有报警
IESZT_INSU_FAIL, //2绝缘检测失败
IESZT_DY_SWCH_FAIL, //3控制辅源电压检测切换失败
IESZT_DY_FAIL, //4投辅源失败
IESZT_STARTING_BATVOLT_FAIL = 6, //6启动中,检测电池电压失败
IESZT_KM_ON_FAIL, //7合直流输出开关失败
IESZT_MDU_STAT_FAIL, //8模块启动失败
IESZT_CHGING_FAULT, //9充电中有报警
IESZT_CHGING_GUN_UNCNCT, //10充电中,检测到枪未连接到车上
IESZT_CHGING_DY_OFF, //11充电中未检测到辅源
IESZT_CHGING_BATVOLT_FAIL, //12充电中未检测到电池电压
IESZT_CARD, //13刷卡停机
IESZT_NO_BALANCE, //14余额不足
IESZT_BMS_ABNMAL, //15充电中BMS异常停机
IESZT_BMS_FULL, //16充电中BMS充满停机
IESZT_STARTING_UNDERPOWER, //17启动中,辅助桩功率不足
IESZT_BMS_DMD_VOLT_OVER, //18充电中,BMS需求电压越限
IESZT_CURR_OVER, //19充电中,过流
IESZT_GUN_TEMP_OVER, //20充电中,枪温过温
IESZT_BMS_TO, //21 BMS通信超时
IESZT_BMS_COMM_INTRPUT= 24, //24 BMS通信中断
IESZT_LH_KM_OFF, //25 充电中,联合开关未合
IESZT_LL_KM_ON, //26 充电中,边界联络开关未分
IESZT_APP_STOP, //27 APP停止充电
IESZT_K1K2_OFF, //28 K1/K2闭合失败
IESZT_LOCK_GUN_FAIL = 30, //30 锁枪失败
IESZT_OUT_VOLT_LOW = 33, //33 启机时,输出电压不达标
IESZT_INSU_TO, //34 绝缘检测超时
IESZT_BSM_STOP, //35 bsm终止
IESZT_TO= 37, //37 超时重试
IESZT_RELIEF_ON = 42, //42 启机过程中泄放继电器非法合
IESZT_MONEY_OK, //43 达到设定金额
IESZT_TIME_OK, //44 达到设定时长
IESZT_POWER_OK, //45 达到设定电量
IESZT_URGENCY = 50, //50 急停
IESZT_ACS, //51 门禁
IESZT_WATER, //52 水浸
IESZT_FS, //53 防雷
IESZT_OVER_TEMP, //54 环温过温
IESZT_UNDER_TEMP, //55 环温过低
IESZT_CARdREADER_COMM, //56 读卡器通信故障
IESZT_LCD_COMM, //57 LCD通信故障
IESZT_KM_ERRACT, //58 母联直流接触器拒动/误动
IESZT_KM_ADHESION, //59 母联直流接触器粘连
IESZT_WHM_COMM, //60 电表通信故障
IESZT_FUSE, //61 直流熔断器故障
IESZT_GUN_KM_ADHESION, //62 #号枪对应直流输出接触器黏连故障 - 空闲,应断开,但检测到为闭合
IESZT_MDU_COMM_DOWN, //63 所有模块通信中断
IESZT_PRE_CHGOT, //64 充电机预充超时
IESZT_bms_BHM_OT, //65 BHM报文接收超时
IESZT_BMS_BRM_OT, //66 BRM报文接收超时
IESZT_BMS_BRM_ERR, //67 BRM报文数据异常 - 报文数据长度错误,版本协议错误
IESZT_BMS_BCP_OT, //68 BCP报文接收超时
IESZT_BMS_BCP_ERR, //69 BCP报文数据异常 - 报文数据长度错误,电池当前电压越限
IESZT_BMS_BRO_00_OT, //70 BRO报文(0x00)接收超时
IESZT_BMS_BRO_AA_OT, //71 BRO报文(0xAA)接收超时
IESZT_BMS_BCS_OT, //72 BCS报文接收超时
IESZT_BMS_VOLTPARA_MISMTCH, //73 电池最高允许充电电压小于充电机最小输出电压
IESZT_BATV_OVER10, //74 绝缘检测前直流输出接触器外侧电压>=10V
IESZT_BATV_BMSV_OVER5, //75 启动充电前直流输出接触器外侧电压与通信报文电池电压相差》=-5%
IESZT_OUTV_BATV_1TO10, //76 预充电时输出电压不在电池电压-(1V~10V)范围内
IESZT_VIN_ERR, //77 vin清单使能,当前车vin不在清单内
IESZT_CHGCABINET_COMM_DOWN, //78 充电柜通信中断
IESZT_BMS_BCL_OT, //79 电池充电需求报文BCL接收超时
IESZT_BMS_BAT_BCS_OT, //80 电池充电总状态报文BCS接收超时
IESZT_BMS_BSM_OT, //81 动力蓄电池状态信息BSM接收超时
IESZT_BMS_BSM_OVERU, //82 BSM - 单体动力蓄电池电压过高
IESZT_BMS_BSM_BELOWU, //83 BSM - 单体动力蓄电池电压过低
IESZT_BMS_BSM_OVERSOC, //84 BSM - 整车动力蓄电池荷电状态SOC过高
IESZT_BMS_BSM_BELOWSOC, //85 BSM - 整车动力蓄电池荷电状态SOC过低
IESZT_BMS_BSM_OVERI, //86 BSM - 动力蓄电池充电过电流
IESZT_BMS_BSM_OVERT, //87 BSM - 动力蓄电池温度过高
IESZT_BMS_BSM_INSUERR, //88 BSM - 动力蓄电池绝缘状态异常
IESZT_BMS_BSM_CONNERR, //89 BSM - 动力蓄电池组输出连接器连接状态异常
IESZT_BMS_BSM_FORBIT, //90 BSM - 禁止充电
IESZT_BMS_BST_OT, //91 BST报文接收超时
IESZT_BMS_BST_U_OK, //92 BST - 达到总电压设定值
IESZT_BMS_BST_SNGUOK, //93 BST - 达到单体电压设定值
IESZT_BMS_BST_INSUERR, //94 BST - 绝缘故障
IESZT_BMS_BST_CONN_OVERT, //95 BST - 输出连接器过温
IESZT_BMS_BST_CELL_OVERT, //96 BST - BMS元件过温/输出连接器过温
IESZT_BMS_BST_CONN_ERR, //97 BST - 充电连接器故障
IESZT_BMS_BST_BAT_OVERT, //98 BST - 电池组温度过高
IESZT_BMS_BST_HVRELAY_ERR, //99 BST - 高压继电器故障
IESZT_BMS_BST_2U_ERR, //100 BST - 检测点2电压检测故障
IESZT_BMS_BST_ELSE, //101 BST - 其他故障
IESZT_BMS_BST_OVERI, //102 BST - 电流超过需求值
IESZT_BMS_BST_U_ABNORML, //103 BST - 电压异常
IESZT_BMS_BEM, //104 BEM - 错误报文
IESZT_OUTPUT_VOLT_OVER, //105 直流输出过压
IESZT_OUTPUT_VOLT_LOW, //106 直流输出不达标
IESZT_GUNLOCK_ERR, //107 枪锁状态错误
IESZT_SMOKE_ERR, //108 烟感告警
} IESZT_REASON_E;
typedef enum //星星充电-订单上送-启机失败/停机原因
{
STAR_PILE_STOP = 0, //0 桩主动停止
STAR_URGENCY, //1 急停按键被按下
STAR_WHM_COMM, //2 电表通信故障
STAR_CARdREADER_COMM, //3 读卡器通信故障
STAR_PE_FAULT, //4 接地故障
STAR_MDU_COMM_DOWN, //5 电源模块通信故障
STAR_ALL_MDU_COMM_DOWN, //6 电源模块全部通信故障
STAR_OVER_TEMP, //7 过温故障
STAR_ELE_LEAKAGE, //8 漏电故障
STAR_METER_DATA_ABNOEMAL, //9 电表数据异常停止充电
SATR_ACS, //10 舱门打开停止充电
STAR_URGENCY_PRESS, //11 急停按键被按下(直流专用)
STAR_FCI = 17, //17 FCI故障-部分模块通讯故障(用作开关模块故障)
STAR_INVALID_CURR, //18 无有效电流停止(交流充满,车载故障等)
STAR_LOW_BLANCE, //19 余额不足停止
STAR_GUNLINK_INTERUPTED, //20 充电连接器断开停止
STAR_CARD_STOP, //21 刷卡停止
STAR_BMS_STOP, //22 BMS正常停止(直流充满)
STAR_APP_STOP, //23 手机APP或者微信停止
STAR_SCREEN_STOP, //24 触控屏手动停止
STAR_BMS_CONN_ERR, //25 BMS通信故障
STAR_DC_ELECT_SHORT, //26 充电机输出短路
STAR_MDU_HARD_ERR, //27 模块硬件故障
STAR_OUTPUT_OVER_CURR, //28 充电机输出过流
STAR_PILE_OVER_TEMP, //29 充电机过温保护
STAR_OUTPUT_OVER_VOL, //30 充电机输出过压
STAR_INPUT_OVER_VOL, //31 充电机输入过压
STAR_INPUT_LOW_VOL, //32 充电机输入欠压
STAR_BLEED_ERR, //33 泄放电阻放电故障(未能在规定时间电压降至60V以下)
STAR_BEM_ALARM, //34 BEM报警
STAR_INSUAL_ERR, //35 充电机绝缘检测故障
STAR_GUNLINK_ERR, //36 充电枪未正确连接
STAR_BATT_REVERSE, //37 电池反接故障
STAR_BATT_ABNORMAL, //38 电池异常
STAR_BSD_OVERTIME, //39 BSD超时
STAR_BST_OVERTIME, //40 BST超时
STAR_BCL_OVERTIME, //41 BCL超时
STAR_BRO_OVERTIME, //42 BRO超时
STAR_BCP_OVERTIME, //43 BCP超时
STAR_BRM_OVERTIME, //44 BRM超时
STAR_BMS_OTHER_ERR, //45 BMS其他故障
STAR_BMS_VOL_ABNORMAL, //46 BMS检测电压异常
STAR_BMS_CURR_OVER, //47 BMS检测电流过大
STAR_BMS_BATT_OVER_TEMP, //48 BMS电池组过温
STAR_BMS_COMPOENT_OVER_TEMP,//49 BMS元件过温
STAR_BMS_CONNECTOR_OVER_TEMP,//50 BMS输出连接器过温故障
STAR_BMS_INSUAL_ERR, //51 Bms绝缘故障
STAR_CAR_STOP, //52 车载停止
STAR_SWITCH_CHARGE_WAIT, //53 切换充等待中(用作避雷器故障)
STAR_PLAT_CONN_ERR, //54 与后台通信故障
STAR_PLAT_STOP, //55 后台停止
STAR_TIME_OK, //56 达到设置充电时长停机
STAR_POWER_OK, //57 达到设置充电电量停机
STAR_MONEY_OK, //58 达到设置充电金额停止
STAR_PILE_TERMAL_CONN_ERR, //59 主副板通信故障
STAR_POWER_OUTAGE_STOP, //60 掉电停止
STAR_K3K4_STICK, //61 辅助电源粘连
STAR_K1K2_STICK, //62 输出继电器粘连
STAR_BATTV_OVER10, //63 (启动前)继电器外侧电压大于10v停止充电
STAR_PILE_BHM_MISMATCH, //64 桩输出能力不匹配(BHM)停止充电
STAR_NEED_OVER_MAXVOL=66, //66 需求电压或充电电压大于最高允许充电电压停止充电
STAR_BRO_MAJOR_FAILURE, //67 BRO重大故障停止充电
STAR_PERCHARGE_VOL_MISMATCH,//68 预充电压不匹配停止充电
STAR_SMOKE_ERR, //69 烟雾报警(停止用)
STAR_CAR_MATCH_FAILED, //70 车辆匹配失败
STAR_SOC_REACH_THRESHOLD, //71 SOC达到阀值
STAR_CARD_UNREGISTERED, //100 卡号未注册
STAR_TIME_FORMAT_ABNORMAL, //101 时间格式异常
}STAR_REASON_E;
/*------ Exported function prototypes (函数声明) -------*/
void v_ies_register_req(void);
void v_ies_heart(void);
void v_ies_fee_req(void);
void v_ies_rtd_free(U8_T gunNo); //空闲实时数据 0x13, 变位上送,120s
void v_ies_rtd_chging(U8_T gunNo); //充电中实时数据 0x11 变化加间隔上送,30s
void v_ies_meter_data(U8_T u8_gunNo); //表底值 0x31,30分钟上送一次
void v_ies_fault_V35(U8_T u8_gunNo); //故障告警数据 0x15, V3.5协议故障不分枪
void v_ies_bill_pack(U8_T u8_gunNo); //组上送账单
void v_ies_start_req(U8_T u8_gunNo); //主动启动充电请求 0x41
void v_ies_set_clk_sync_ack(void);
void v_ies_set_fee_ack(void);
void v_ies_upgrade_data_auto_ack(void);
void v_ies_rmt_start_ack(U8_T u8_gunNo, U8_T serialNum ,U8_T flag); //远程启动应答 0x43, 应答帧序号跟询问帧需保持一致
void v_ies_rmt_stop_ack(U8_T u8_gunNo, U8_T serialNum); //远程停机应答 0x45
void v_ies_rmt_oprt_ack(U8_T *u8_opr_cmd, U8_T opr_result); //远程操作应答 0x77
void v_ies_heart_ack(void);
void v_ies_register_ack(U8_T *u8_recvBuf); //登陆帧应答
void v_ies_fee_ack(U8_T *u8_recvBuf); //计费模型应答帧
void v_ies_set_clk_sync(U8_T *u8_recvBuf); //对时
void v_ies_set_fee(U8_T *u8_recvBuf); //计费模型设置
void v_ies_upgrade_data_auto(U8_T *u8_recvBuf); //平台应答文件内容请求
void v_ies_start_req(U8_T gunNo); //主动启动充电请求 0x41
void v_ies_rx_start(U8_T *u8_recvBuf); //运营确认启动 0x42
void v_ies_bill_upload_ack(U8_T *u8_recvBuf); //结算账单应答
void v_ies_rmt_start(U8_T *u8_recvBuf); //远程启动
void v_ies_rmt_stop(U8_T *u8_recvBuf); //远程停机
void v_ies_rmt_oprt(U8_T *u8_recvBuf) ; //远程操作
void v_ies_qr_call(U8_T *u8_recvBuf) ; //二维码召唤
void v_ies_qr_dis(U8_T *u8_recvBuf) ; //二维码下发
U8_T u8_ies_get_gunLink_state(U8_T u8_gunNo);
U8_T u8_ies_get_gun_state(U8_T u8_gunNo);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_IES_INTERFACE_H */
+472
View File
@@ -0,0 +1,472 @@
/**
* @file bs_connect_impl.c
* @brief 平台链路层实现:TCP/4G 连接、收发、4G 双 socket 分发
*
* - 联网参数从 t_sys_fix_cfg.s_net_cfg 读取
* - 4G 模式经 WH-GM5 模组,支持 socketA/B 分发
* - 以太网分支(u8_netType!=0)预留待补全
*/
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g.h"
#include "plat_comm/4g_module/ec200a_4g_cfg.h"
#endif
#if WH_GM5_EN
#include "plat_comm/4g_module/bs_wh_gm5.h"
#endif
#include "publicdata/publicdata.h"
#include "plat_comm/plat_comm_task.h"
#include "plat_comm/lwip_module/lwip_module.h"
#define BS_PRINTF_EN 1 //平台收发数据打印
ws_url_t url_info;
/**
* @brief 网口 TCP:关闭指定平台通道 socket
*/
void v_bs_eth_close_socket(U8_T u8_id)
{
if (s_bs_task_ctrl.s_bs_ctrl[u8_id].fd >= 0) {
lwip_tcp_close((int)s_bs_task_ctrl.s_bs_ctrl[u8_id].fd);
s_bs_task_ctrl.s_bs_ctrl[u8_id].fd = (S32_T)-1;
s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_TCP_connect_flag = 0;
}
}
#if 1 //联网参数配置
/**
* ************************************************************************
* @brief - 4G模块联网参数获取
*
* @param[in] s_4g_para - 4G联网参数结构体
*
*
* @author jiankalka
* @date 2025-09-01
* ************************************************************************
*/
#if WH_GM5_EN
void v_get_4G_config_para(S_4G_CONFIG_PARA *s_4g_para)
{
SYS_FIX_CFG_T *t_sys_fix_cfg = &g_t_share_data.t_sys_fix_cfg;
memset(s_4g_para , 0 ,sizeof(S_4G_CONFIG_PARA));
//s_4g_para->socketA_IP = testIP;
s_4g_para->socketA_IP = t_sys_fix_cfg->s_net_cfg.u8_serverA_domain_name; //
s_4g_para->socketB_IP = t_sys_fix_cfg->s_net_cfg.u8_serverB_domain_name;
s_4g_para->u8_Sim_Type = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_sim_anp_type;
s_4g_para->u8_socketB_en = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_platB_en;
}
#endif /* WH_GM5_EN */
/**
* ************************************************************************
* @brief - 4G模块参数初始化
*
*
*
* @author jiankalka
* @date 2025-09-19
* ************************************************************************
*/
void v_bs_4G_data_init(void)
{
#if EC200A_4G_EN
{
S_4G_CONFIG_PARA s_4g_para;
SYS_FIX_CFG_T *t_sys_fix_cfg = &g_t_share_data.t_sys_fix_cfg;
memset(&s_4g_para, 0, sizeof(S_4G_CONFIG_PARA));
s_4g_para.socketA_IP = t_sys_fix_cfg->s_net_cfg.u8_serverA_domain_name;
s_4g_para.socketB_IP = t_sys_fix_cfg->s_net_cfg.u8_serverB_domain_name;
s_4g_para.u8_Sim_Type = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_sim_anp_type;
s_4g_para.u8_socketB_en = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_platB_en;
v_4g_data_init(&s_4g_para);
}
#endif /* EC200A_4G_EN */
#if WH_GM5_EN
{
S_4G_CONFIG_PARA s_4g_para;
v_get_4G_config_para(&s_4g_para);
v_wh_gm5_data_init(&s_4g_para);
}
#endif
}
/**
* ************************************************************************
* @brief - TCP 联网测试函数
*
*
* @return U8_T - 0未连接TCP 1已连接
*
* @author jiankalka
* @date 2025-08-29
* ************************************************************************
*/
U8_T u8_tcp_connect(U8_T u8_id)
{
U8_T u8_ret = 0;
if(s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_netType == 0) //4G联网
{
#if WH_GM5_EN
// 无线模块WH_GM5初始化
if(s8_WH_GM5_init() == 0) //如果4g配置成功
{
if(s8_check_socket_LinkState(u8_id) >= 0) //查询网路连接状态
{
mSleep(100);
v_4g_uart_cache_clear(); //4G模块配置完毕 清除串口缓存区中的数据
return 1;
}
}
if(u8_ret == 0 && u32_safe_seconds_since(s_bs_task_ctrl.s_bs_ctrl[u8_id].ulong_restart_timer) > NUM_120) //120分钟 没连上 则重新连接
{
v_bs_4G_data_init(); //4G模块控制数据 初始化
s_bs_task_ctrl.s_bs_ctrl[u8_id].ulong_restart_timer = get_current_seconds();
}
#endif
#if EC200A_4G_EN
if(s8_ec200a_4g_init() == 0)
{
u8_ret = 1;
}
#endif
}
else //网线连接
{
}
return u8_ret;
}
#endif
#if WH_GM5_EN // WH-GM5 4G收发接口
/**
* ************************************************************************
* @brief - 4G模块多端口数据接收、用于多socket分端口接收、需要传入两平台接收处理函数接口
*
* @param[in] socketA_unPack - socketA 接收处理函数,不存在时赋值为NULL
* @param[in] socketB_unPack - socketB 接收处理函数,不存在时赋值为NULL
*
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
void v_4g_wh5_data_divide_recv(RecvUnpackHandler socketA_unPack, RecvUnpackHandler socketB_unPack)
{
// 静态缓冲区用于保存残包
static U8_T u8_recv_saveBuf[BS_RECV_BUF_LEN];
static U16_T u16_recv_saveLen = 0;
// 1. 数据合并:将新数据与静态缓冲区的残包合并
U16_T u16_totalLen = 0; // 总数据长度(静态缓冲区+新数据)
U16_T u16_rcvByteNum = 0; // 当前解析位置
U16_T u16_oneFrmLen = 0; // 完整帧长度
U16_T u16_dataLen = 0; // 数据域长度
U8_T frameType = 0; // 帧类型
U8_T flags = 0; // 标志位
U8_T* payloadStart = NULL; // 有效载荷起始位置
U16_T payloadLen = 0; // 有效载荷长度
U8_T u8_recvBuf[BS_RECV_BUF_LEN] = {0}; //接收数据缓存区
// 0. 获取串口数据
U16_T u16_recvLen = u16_usart_recv(COM_PORT_BS, u8_recvBuf, BS_RECV_BUF_LEN);
// 如果没有接收到数据,直接返回
if(u16_recvLen == 0) {
return;
}
// 计算新数据需要复制的长度(避免缓冲区溢出)
U16_T actualCopyLen = u16_recvLen;
if (u16_recv_saveLen + u16_recvLen > BS_RECV_BUF_LEN) {
// 缓冲区溢出处理:保留新数据,丢弃最旧的数据
U16_T overflowBytes = (u16_recv_saveLen + u16_recvLen) - BS_RECV_BUF_LEN;
// 移动现有数据,为新数据腾出空间
memmove(u8_recv_saveBuf, &u8_recv_saveBuf[overflowBytes],
u16_recv_saveLen - overflowBytes);
u16_recv_saveLen -= overflowBytes;
// 更新实际可复制长度
actualCopyLen = BS_RECV_BUF_LEN - u16_recv_saveLen;
}
// 追加新数据到静态缓冲区末尾
memcpy(u8_recv_saveBuf + u16_recv_saveLen, u8_recvBuf, actualCopyLen);
u16_recv_saveLen += actualCopyLen;
// 设置总数据长度
u16_totalLen = u16_recv_saveLen;
// 2. 主解析循环 - 处理完整帧
while (u16_totalLen >= 8)
{
// 帧头检查
if (u8_recv_saveBuf[u16_rcvByteNum] != 0xAA ||
u8_recv_saveBuf[u16_rcvByteNum+1] != 0xFD ||
u8_recv_saveBuf[u16_rcvByteNum+2] != 0x55)
{
// 帧头不匹配,移动搜索位置
u16_rcvByteNum++;
u16_totalLen--;
continue;
}
// 获取数据长度(大端格式)
u16_dataLen = (U16_T)((u8_recv_saveBuf[u16_rcvByteNum+3] << 8) |
u8_recv_saveBuf[u16_rcvByteNum+4]);
// 计算完整帧长度 = 头部(3) + 长度字段(2) + 元数据(3) + 数据长度
u16_oneFrmLen = 6 + u16_dataLen;
// 检查帧完整性
if (u16_oneFrmLen > u16_totalLen) {
// 残包处理:帧不完整,等待更多数据
break;
}
// 提取关键字段
frameType = u8_recv_saveBuf[u16_rcvByteNum + 5]; // 类型字段(0x61/0x62)
flags = u8_recv_saveBuf[u16_rcvByteNum + 6]; // 标志位
// 安全获取有效载荷(跳过帧头、长度字段和类型字段)
payloadStart = &u8_recv_saveBuf[u16_rcvByteNum + 7];
payloadLen = u16_dataLen - 2; // 减去类型和标志位
// 确保有效载`` 荷在缓冲区范围内
if ((payloadStart + payloadLen) > (u8_recv_saveBuf + BS_RECV_BUF_LEN)) {
// 缓冲区越界错误处理:重置缓冲区
u16_recv_saveLen = 0;
return;
}
// 处理网络返回数据
if ((flags & 0x80) == 0) {
if (frameType == 0x61 && socketA_unPack != NULL) {
// SOCKETA数据
socketA_unPack(0, payloadStart, payloadLen);
}
else if (frameType == 0x62 && socketB_unPack != NULL) {
// SOCKETB数据
socketB_unPack(1, payloadStart, payloadLen);
}
//测试打印
#if BS_PRINTF_EN
printf("--------------plat[%d] recv\r\n",frameType - 0x60);
for (int i = 0; i < u16_recvLen; i++)
{
printf("%02x ",u8_recvBuf[i]);
}
printf("\n---------------\r\n");
#endif
}
// 移动位置指针(处理完整帧)
u16_rcvByteNum += u16_oneFrmLen;
u16_totalLen -= u16_oneFrmLen;
}
// 3. 残包处理:保存剩余数据到静态缓冲区
if (u16_totalLen > 0) {
// 移动剩余数据到静态缓冲区开头
if (u16_rcvByteNum > 0) {
memmove(u8_recv_saveBuf, &u8_recv_saveBuf[u16_rcvByteNum], u16_totalLen);
}
u16_recv_saveLen = u16_totalLen;
} else {
// 没有剩余数据,重置静态缓冲区
u16_recv_saveLen = 0;
}
return;
}
/**
* ************************************************************************
* @brief - 4G 模块发送数据接口
*
* @param[in] u8_id - 发送接口ID 0代表不使能分发功能,直接透传发送 1代表像socketA发送数据 2代表像socketB发送数据
* @param[in] u8_sendBuf - 发送数据数组指针
* @param[in] u16_len - 发送数据长度
*
*
* @author jiankalka
* @date 2025-09-01
* ************************************************************************
*/
void v_4g_wh5_data_send(U8_T u8_id,U8_T *u8_sendBuf,U16_T u16_len)
{
U16_T len,u16_checkSum;
U8_T send_buf[10]={0};
if(u8_id == 0) //不使用套接字分发 直接发送数据
u16_usart_send(COM_PORT_BS, u8_sendBuf, u16_len);
else
{
//套接字分发组帧头
send_buf[0] = 0x55;
send_buf[1] = 0xFD;
send_buf[2] = 0xAA;
len = u16_len + 2;
send_buf[3] = (len >> 8) & 0xFF;
send_buf[4] = len & 0xFF;
send_buf[5] = 0x60 + u8_id; //SOCKETA为0x61,SOCKETB为0x62
send_buf[6] = 0;
//发送帧头
u16_usart_send(COM_PORT_BS, send_buf, 7);
//发送数据
u16_usart_send(COM_PORT_BS, u8_sendBuf, u16_len);
//计算校验合 发送数据+SOCKET序号
u16_checkSum = u16_crc_checksum(u8_sendBuf, u16_len);
u16_checkSum = u16_checkSum + send_buf[5];
send_buf[0] = u16_checkSum&0xFF;
u16_usart_send(COM_PORT_BS, send_buf, 1);
}
}
/**
* ************************************************************************
* @brief - 4G模块数据接收 接收原始数据 不进行分包解析
*
* @param[in] u8_recvBuf - 接收数据缓存数组
* @param[in] u16_len - 接受数组长度
*
* @return U16_T -
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
U16_T u16_4g_wh5_data_recv(U8_T *u8_recvBuf,U16_T u16_len)
{
U16_T u16_recvLen;
memset(u8_recvBuf , 0 , u16_len);
u16_recvLen = u16_usart_recv(COM_PORT_BS, u8_recvBuf, u16_len);
return u16_recvLen;
}
#endif
#if 1 //公共s收发函数接口
/**
* ************************************************************************
* @brief - 公共发送接口
*
* @param[in] u8_id - 平台位置 0-1
* @param[in] u8_sendBuf - 发送数据
* @param[in] u16_len - 发送数据长度
*
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
void v_bs_send(U8_T u8_id,U8_T *u8_sendBuf,U16_T u16_len)
{
if(s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_netType == 0) //4G联网
{
#if WH_GM5_EN
if(s_bs_task_ctrl.u8_4g_dispense_en == 1) u8_id+=1; //分发使能 序号加一
v_4g_wh5_data_send(u8_id,u8_sendBuf,u16_len);
#endif
#if EC200A_4G_EN
if(s_bs_task_ctrl.u8_4g_dispense_en == 1) u8_id+=1;
#if EC200A_TCP_USE_TRANSPARENT_MODE
v_4g_send_data(u8_id,u8_sendBuf,u16_len);
#else
v_ec200a_tcp_app_send(u8_id,u8_sendBuf,u16_len);
#endif
#endif
}
else
{
}
mSleep(Plat_Send_Sleep);
s_bs_task_ctrl.u8_sendCnt++;
#if BS_PRINTF_EN
printf("\n--------------bs send\r\n");
for (int i = 0; i < u16_len; i++)
{
printf("%c",u8_sendBuf[i]);
}
printf("\r\n");
for (int i = 0; i < u16_len; i++)
{
printf("%02x ",u8_sendBuf[i]);
}
printf("\n---------------\r\n");
#endif
}
/**
* ************************************************************************
* @brief - 公共接收函数
*
* @param[in] u8_id - 平台位置 0-1
* @param[in] u8_recvBuf - 接收数据缓存地址
* @param[in] u16_len - 最大接收长度
*
* @return U16_T -
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
U16_T u16_bs_recv(U8_T u8_id,U8_T *u8_recvBuf,U16_T u16_len)
{
U16_T u16_recvLen = 0;
if(s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_netType == 0) //4G联网
{
#if WH_GM5_EN
u16_recvLen = u16_usart_recv(COM_PORT_BS, u8_recvBuf, u16_len);
#endif
#if EC200A_4G_EN
u16_recvLen = u16_ec200a_tcp_app_recv(u8_recvBuf, u16_len);
#endif
}
else //以太网
{
// if(bs_task_ctrl_data.s_bs_ctrl[id].fd >= 0)
// u16_recvLen = eth_recv((int)bs_task_ctrl_data.s_bs_ctrl[id].fd, (char *)recvBuf, BS_RECV_BUF_LEN, 1);
}
#if BS_PRINTF_EN
if(u16_recvLen == 0) return u16_recvLen;
printf("\n--------------bs recv\r\n");
for (int i = 0; i < u16_recvLen; i++)
{
printf("%c",u8_recvBuf[i]);
}
printf("\r\n");
for (int i = 0; i < u16_recvLen; i++)
{
printf("%02x ",u8_recvBuf[i]);
}
printf("\n---------------\r\n");
#endif
return u16_recvLen;
}
#endif
+53
View File
@@ -0,0 +1,53 @@
#ifndef BS_CONNECT_IMPL_H
#define BS_CONNECT_IMPL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
#include "plat_comm/plat_comm_task.h"
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g.h"
#endif
#if WH_GM5_EN
#include "plat_comm/4g_module/bs_wh_gm5.h"
#endif
/*------ Exported macro(宏定义) -------------------------*/
/** 4G 套接字分发接收回调:id=平台序号,buf/len=有效载荷 */
typedef void (*RecvUnpackHandler)(U8_T id, U8_T *buf, U16_T len);
/*------ Exported struct(结构体定义) --------------------*/
typedef struct {
char host[NUM_64];
int port;
char path[NUM_64];
char apn_str[NUM_32];
int is_secure; /* 0=ws, 1=wss */
} ws_url_t;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_bs_4G_data_init(void);
U8_T u8_tcp_connect(U8_T u8_id);
/** 网口:关闭指定通道 socket 并清零 fd/u8_TCP_connect_flag,重连前必须调用,避免 socket 泄漏导致“再连也连不上” */
void v_bs_eth_close_socket(U8_T u8_id);
void v_bs_send(U8_T u8_id,U8_T *u8_sendBuf,U16_T u16_len);
U16_T u16_bs_recv(U8_T u8_id,U8_T *u8_recvBuf,U16_T u16_len);
void v_4g_wh5_data_divide_recv(RecvUnpackHandler socketA_unPack, RecvUnpackHandler socketB_unPack);
int parse_websocket_url_simple(const char* url, ws_url_t* result) ;
#ifdef __cplusplus
}
#endif
#endif /* BS_CONNECT_IMPL_H */
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
#ifndef BS_PUBLIC_IMPL_H
#define BS_PUBLIC_IMPL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
#include "fault_cheak/fault_interface.h"
#include "app_rtc/app_rtc.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "flowctrl/flow_interface.h"
/*------ Exported macro(宏定义) -------------------------*/
#define BS_BUF_SIZE_16 16
#define BS_BUF_SIZE_32 32
#define BS_BUF_SIZE_64 64
#define BS_BUF_SIZE_128 128
#define BS_BUF_SIZE_256 256
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
GUN_NULL = 0, //无
GUN_1 = 0, //1枪
GUN_2, //2枪
}E_GUN_NO;
typedef enum{
GET_FLAG=0, //获取数据
SET_FALG, //设置标志
CLEAR_FALG, //清空标志
}FLAG_CTLR;
typedef union {
U8_T u8_data;
S8_T s8_data;
U16_T u16_data;
S16_T s16_data;
U32_T u32_data;
F32_T f32_data;
U64_T u64_data;
}TYPE_DATA;
typedef enum
{
//字符串数据类型 0x7000--0x8000
E_STR_TYPE = 0,
E_STR_PILE_NO, //桩编号-16
E_STR_SOFTWARE_VERSION, //软件版本号
E_STR_HARDWARE_VERSION, //硬件版本号
E_STR_LOG_preTradeNo, //订单流水号 type=0,平台流水号;type=1,设备流水号
E_STR_LOG_user_id, //订单用户标识
E_STR_LOG_vin, //VIN 车辆信息
E_STR_SDK_PARA_product_key, //国网sdk 产品密钥
E_STR_SDK_PARA_device_name, //国网sdk 资产吗
E_STR_SDK_PARA_device_secret, //国网sdk 设备秘钥
E_STR_SDK_PARA_device_reg_code, //国网sdk 注册码
E_STR_SDK_PARA_device_uid, //国网sdk 出厂编码
E_STR_SDK_PARA_firmware_version, //国网sdk 固件版本信息
E_STR_SDK_SIM_ID, //sim卡号
E_STR_METER_ADDR, //获取电表号 0直流电表 1交流电表
E_STR_FEE_ID, //计费模型ID
E_STR_QR_DISPLAY, //枪二维码 u8_gunNo=0/1 对应枪A/B
}E_GET_SYS_STR;
typedef struct
{
U8_T u8_init_flag; //初始化标志
U8_T u8_netType; //网络类型 04G 1网线
U8_T u8_update_server_addr[32]; //升级服务器IP
U16_T u16_update_server_port; //升级服务器端口号
U8_T u8_user[20]; //用户名,字符串,不足尾部补0
U8_T u8_pwd[20]; //密码,字符串,不足尾部补0
U8_T u8_path[64]; //路径名,字符串,不足尾部补0
U8_T u8_file_name[32]; //文件名,字符串,不足尾部补0
U8_T u8_bs_ack; //平台回复报文
U8_T u8_update_result; //升级结果
U32_T u32_file_size; //文件大小
U16_T u16_packSize; //单包数据大小
U16_T u16_packNum; //包数量
U16_T u16_packIndex; //获取包索引号
U32_T u32_file_fd; //文件句柄 EC200A 4G模块使用
}T_FTP_UPDATE_INFO;
/***************************
变量功能:定义变量类型枚举,内部调用时使用统一函数接口获取不同的数据内容
*********************************/
typedef enum
{
//桩信息
E_BS_GET_SYS_DATA_GUN_NUM = 0, //枪数量
E_BS_GET_SYS_DATA_NET_TYPE, //联网类型 0:4G 1:网线
E_BS_GET_SYS_DATA_PLAT_EN, //平台使能 u8_gunNo=0平台A 1平台B0不使能 1使能
E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM, //type=0当前故障数量/type=1当前告警数量
E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_DATA, //获取故障内容 参数 0-N 获取故障位置数量
E_BS_GET_SYS_DATA_4G_signal_quality, //4G信号强度
E_BS_GET_SYS_DATA_RATED_POWER, //额定功率 单位w
E_BS_GET_SYS_DATA_PILE_PUT_PARA, //充电桩输出参数 单位0.1V/0.1A 0最大电压 1最小电压 2最大电流 3最小电流
//枪信息
E_BS_GET_SYS_DATA_GUN_FLOW_STATE, //终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
E_BS_GET_SYS_DATA_GUN_STATE, //枪状态信息 type=0,枪连接状态;
//type=1,电子锁状态;
//type=2,枪空座归位状态;
//type=3,正母线直流输出接触器状态
//type=4,负母线直流输出接触器状态
E_BS_GET_SYS_DATA_GUN_TEMP_POS, //充电枪正极温度 单位0.1度 0-100最小为0
E_BS_GET_SYS_DATA_GUN_TEMP_NEG, //充电枪负极温度 单位0.1度 0-100最小为0
E_BS_GET_SYS_DATA_MEMTER_ELECT, //电表电量 0.001kWh
E_BS_GET_SYS_DATA_PILE_TEMP, //环境温度 精确到小数点后1位,-50℃偏差
E_BS_GET_SYS_DATA_METER_CUR, //电表电流 单位0.1A
E_BS_GET_SYS_DATA_METER_VOL, //电表电压 单位0.1V
//订单信息
E_BS_GET_SYS_DATA_LOG_ENERGY, //订单电量 0.001kWh 三位小数精度 type=0,已充电量;type=1,电表起始值,type=2,电表终止值
E_BS_GET_SYS_DATA_LOG_STOP_REASON, //充电订单停止原因
E_BS_GET_SYS_DATA_LOG_charge_flag, //订单充电标志 0未充电 1已经充电
E_BS_GET_SYS_DATA_SOC_DATA, //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
E_BS_GET_SYS_DATA_LOG_CHARGE_TIME, //充电时间 min
//bms信息
E_BS_GET_SYS_DATA_BMS_CELL_MAX_VOL, //最高单体动力蓄电池电压
E_BS_GET_SYS_DATA_BMS_NEED_VOL, //电压需求 单位0.1V
E_BS_GET_SYS_DATA_BMS_NEED_CUR, //电流需求 单位0.1A
E_BS_GET_SYS_DATA_BMS_CHARGE_VOL, //BMS充电电压测量值
E_BS_GET_SYS_DATA_BMS_CHARGE_CUR, //BMS充电电流测量值
E_BS_GET_SYS_DATA_BMS_BATT_CHARGE_MAX_VOL, //最高允许充电总电压
E_BS_GET_SYS_DATA_BMS_CELL_CHARGE_MAX_VOL, //单体动力蓄电池最高允许充电电压
E_BS_GET_SYS_DATA_BMS_BATT_CHARGE_MAX_CUR, //最高允许充电电流
E_BS_GET_SYS_DATA_BMS_BATT_RATED_VOL, //整车动力蓄电池系统额定总电压
E_BS_GET_SYS_DATA_BMS_BATT_VOL, //整车动力蓄当前电池总电压
E_BS_GET_SYS_DATA_BMS_BATT_RATED_C10, //整车动力蓄电池系统额定容量
E_BS_GET_SYS_DATA_BMS_BATT_RATED_ENERY, //动力蓄电池标称总能量
E_BS_GET_SYS_DATA_BMS_BATT_ID, //电池编号信息 0最高单体动力蓄电池电压所在编号 1最高动力蓄电池温度检测点编号 2最低动力蓄电池温度检测点编号
E_BS_GET_SYS_DATA_BMS_TEMP, //单位 0.1度0-100最小为0 0 电池最高温度 1最低温度
E_BS_GET_SYS_DATA_BMS_REMAIN_TIME, //剩余充电时间 min
E_BS_GET_SYS_DATA_BMS_BATT_MAX_TEMP, //最高动力蓄电池温度
E_BS_GET_SYS_DATA_BMS_BATT_MIN_TEMP, //最低动力蓄电池温度
E_BS_GET_SYS_DATA_BMS_BATT_ALLOW_MAX_TEMP, //最高允许动力蓄电池温度
E_BS_GET_SYS_DATA_UNIX_TIMESTAMP, //当前 RTC Unix 时间戳(秒)
} E_GET_SYS_DATA;
typedef struct
{
U16_T u16_ctrl_res; //控制原因 启动时为启动原因 停止时为停止原因;启动原因0 无动作 1离线卡 2在线卡 3后台启动(tcu/积成)\扫码启动 4vin启动 5调试启动
E_CHRG_LIMIT_MOD e_start_mode; //启动充电限制方式 0 不做限制的充电(默认) 1 限制金额 2 限制电量 electricity 3 限制SOC 4 限制充电时长 5 限制功率
U16_T u16_limit; //限制参数 默认精度 0.01
U32_T u32_balance_begin; //启动充电前金额 0.0001元
USER_TYPE_E e_user_type; //用户类型
U8_T u8_user_id[NUM_32]; //用户标识符
U8_T u8_tradeNo[EVS_MAX_TRADE_LEN]; //设备流水号
U8_T u8_preTradeNo[EVS_MAX_TRADE_LEN]; //平台订单流水号
}S_BS_CHAR_CTRL;
typedef enum
{
E_CHAR_CTRL_START = 0, //启动充电
E_CHAR_CHANGE_LOG, //修改数据
E_CHAR_CTRL_STOP, //停止充电
}E_CTRL_TYPE;
/*------ Exported variables(变量定义) -------------------*/
extern T_FTP_UPDATE_INFO t_ftp_update_info;
/*------ Exported function prototypes (函数声明) -------*/
U8_T u8_get_str_data(U8_T u8_gunNo,E_GET_SYS_STR e_dataType,U8_T u8_dataPara,char *u8_getData,U8_T u8_len);
/** 写字符串类系统参数;u8_len 为有效字节数(帧体可非 '\0' 结尾);返回 1 成功 0 失败 */
U8_T u8_set_str_data(U8_T u8_gunNo, E_GET_SYS_STR e_dataType, U8_T u8_dataPara,
const char *pu8_data, U8_T u8_len);
U32_T u32_bs_get_sys_data(U8_T u8_gunNo,E_GET_SYS_DATA e_datatype,U8_T u8_dataPara);
/** 写数值类系统参数;返回 1 成功 0 失败(如 E_BS_GET_SYS_DATA_UNIX_TIMESTAMP 校时) */
U8_T u8_set_sys_data(U8_T u8_gunNo, E_GET_SYS_DATA e_datatype, U32_T u32_val, U8_T u8_dataPara);
U64_T u64_bs_get_sys_data(U8_T u8_gunNo,E_GET_SYS_DATA e_datatype);
U8_T u8_bs_get_flow_state(U8_T u8_gunNo);
void v_bs_set_time(Comm_Time *currtime);
void v_bs_save_fee_data(U8_T u8_flag, S_FEE_MODEL *s_fee_model);
void v_bs_log_txFlag_tradUpload(U8_T u8_gunNo);
void v_bs_log_txFlag_autoStart(U8_T u8_gunNo); //设置充电鉴权上送标志;
void v_bs_charge_action_ctrl(U8_T u8_gunNo,E_CTRL_TYPE e_type, S_BS_CHAR_CTRL *s_ctrl_data);
void v_remote_gunLock_ctrl(U8_T u8_gunNo,U8_T u8_type);
void v_remote_reboot(U8_T type) ;
void v_cheak_state_change(void);
void v_ftp_update_action(void);
U8_T u8_bs_get_txFlag_autoRwsult(U8_T u8_gunNo) ; //对外获取鉴权结果
U8_T u8_bs_get_log_on_flag();
U8_T u8_card_info_add(char *card_id);
void v_card_info_clear();
void v_bs_get_gun_log_data(U8_T u8_gunNo , S_LOG_DATA *s_log);
void v_bs_gwsdk_set_para(U8_T u8_type, S_SDK_DATA *s_sdk_data);
void v_bs_logUploadOk_updateInfo(U16_T u16_cmplte_bill_index);
void v_bs_get_fee_part_data(U8_T u8_gunNo,U8_T flag,S_LOG_DATA *t_log,U32_T *u32_part_energy,F32_T *f32_rate,F32_T *f32_sevice);
S8_T s8_plat_get_unsettled_order( S_LOG_DATA *s_log,U8_T u8_type);
void v_bs_platform_settlement_confirm(U8_T u8_gunNo);
void v_ocpp_offline_local_authentication_check(U8_T u8_gunNo);
U8_T u8_plat_get_real_order(S_LOG_DATA *s_log,U8_T u8_gunNo);
void v_get_ocpp_cfg_info(char *mag , U16_T u16_len);
U8_T u8_set_ocpp_cfg_info(const char *key,const char *data);
void v_ocpp_get_real_log_file_name(char *fileName,int len);
U8_T u8_bs_get_update_flag();
U8_T u8_bs_get_gun_disable_flag(U8_T u8_gunNo);
void v_ocpp_set_gun_flow_real_state_init(U8_T u8_gunNo);
/** 设置二维码更新标志(CCU621 暂未实现) */
void v_ccu_set_qrcode_flag(U8_T u8_gunNo, U8_T flag);
#ifdef __cplusplus
}
#endif
#endif /* BS_PUBLIC_IMPL_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/**
* @file lwip_ftp_upgrade.h
* @brief 基于 LwIP socket 的 FTP 固件升级状态机(网线远程升级)
*
* 说明:
* - 参考 ec200a_4g_ftp.c 的“状态机 + 周期调用”风格;
* - 通过 LwIP socket 实现 FTP 控制/数据连接,按 1024 字节分包写入升级区域;
* - 每次调用只推进一小步,不阻塞 v_Plat_Comm 线程。
*/
#ifndef LWIP_FTP_UPGRADE_H
#define LWIP_FTP_UPGRADE_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 初始化 LwIP FTP 升级状态机
*
* 说明:
* - 直接读取全局 t_ftp_update_info 中的 FTP 参数(server/port/user/pass/path/file_name);
* - 清空内部静态状态结构体,关闭残留 socket,准备进入 CONNECT 步骤。
*/
void lwip_ftp_upgrade_init_from_tftp(void);
/**
* @brief 周期推进 LwIP FTP 升级状态机(单步)
*
* 返回值:
* - 0:升级进行中(下次调用继续推进);
* - 1:升级成功完成(文件全部下载且升级头标记为 NEED_UP);
* - <0:升级失败(FTP/网络/Flash 错误等,具体原因在日志中打印)。
*/
int lwip_ftp_upgrade_step(void);
#ifdef __cplusplus
}
#endif
#endif /* LWIP_FTP_UPGRADE_H */
@@ -0,0 +1,215 @@
# LwIP FTP 升级功能说明(最终版)
## 1. 设计目标
-**不依赖 4G 模块 AT 指令** 的情况下,通过 **以太网 (LwIP socket)** 实现 FTP 固件下载。
- 复用已有的 `flash_upgrade.h` 升级区管理:
- `upgrade_erase_program_area()` 擦除升级区域;
- `upgrade_write_packet()``PACKET_SIZE`1024 字节)分包写入;
- `upgrade_success_update_header()` 更新升级头(`UPGRADE_FLAG_IDLE / UPGRADE_FLAG_NEED_UP`)。
- 与 4G FTP 升级整体思路保持一致,但实现形式为:
**非阻塞状态机 + 周期调用 `v_ftp_update_action()`**
## 2. 模块文件结构
位于 `app/plat_comm/lwip_module/`
- `lwip_module.[ch]`
- 对 LwIP socket 做简单封装:`lwip_resolve_host / lwip_set_nonblock / lwip_tcp_send / lwip_tcp_recv / lwip_tcp_close` 等。
- `lwip_ftp_upgrade.[ch]`
- **网线 FTP 程序远程升级的完整状态机实现**,仅保留这一对文件:
- 控制连接(21 端口);
- `USER/PASS` 登录;
- `TYPE I` 二进制模式;
- `CWD`(可选目录切换);
- `SIZE` 查询文件大小;
- `PASV` 被动模式解析数据通道;
- `RETR` 下载文件并按 1024 字节分包写 Flash
- `QUIT` 结束会话并写入 NEED_UP 升级头。
- 对外只暴露两个接口:
- `void lwip_ftp_upgrade_init_from_tftp(void);`
- `int lwip_ftp_upgrade_step(void);`
> 说明:旧版的 `lwip_ftp_fw_upgrade_api.[ch]`、`lwip_ftp_upgrade_flash_adapter.[ch]`、`lwip_ftp_fw_upgrade_sm.[ch]` 等文件已删除,不再使用。
## 3. 参数来源与触发方式
### 3.1 参数结构:`T_FTP_UPDATE_INFO`
`bs_public_impl.h` 中定义,关键字段如下:
- `u8_update_server_addr`FTP 服务器域名 / IP
- `u16_update_server_port`FTP 端口(通常 21);
- `u8_user / u8_pwd`:用户名、密码;
- `u8_path`:远程目录(可为空);
- `u8_file_name`:文件名;
- `u8_netType`:网络类型(`0=4G``1=网线`);
- `u32_file_size`:文件大小(由 FTP SIZE 结果回填);
- `u8_update_result`:升级结果(0=成功,1=失败)。
这些字段由 OCPP `UpdateFirmware` 命令解析函数 `parse_ftp_url()` 填充。
### 3.2 触发流程
1. OCPP 平台下发 `UpdateFirmware`URL 形如:
`ftp://user:pass@host:port/path/file.bin`
→ 解析到全局 `t_ftp_update_info`
2. 上层把对应平台的 `u8_updata_flag` 置位。
3. `plat_comm_task.c` 中的 `v_Plat_Comm()` 周期调用 `v_ftp_update_action()`
4. `v_ftp_update_action()` 内部根据:
- `s_bs_task_ctrl.ocpp_data.u8_log_file_upload``1=日志上传,0=程序升级`
- `t_ftp_update_info.u8_netType``0=4G`(走 4G 模块),`1=网线`(走 LwIP FTP)。
## 4. 程序升级流程(网线 / LwIP)
当满足:
- `s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 0`(程序升级);
- `t_ftp_update_info.u8_netType != 0`(网线/LwIP);
`v_ftp_update_action()` 中流程为:
```c
if (t_ftp_update_info.u8_init_flag == 0)
{
if (t_ftp_update_info.u8_netType == 0) {
v_ftp_ctrl_init(); // 4G 模块初始化
} else {
lwip_ftp_upgrade_init_from_tftp(); // 网线 FTP 状态机初始化
}
t_ftp_update_info.u8_init_flag = 1;
}
if (t_ftp_update_info.u8_netType == 0) {
u8_ec200a_4g_ftp_update(); // 4G FTP 升级
} else {
int ret = lwip_ftp_upgrade_step(); // LwIP FTP 升级状态机单步
if (ret == 1) {
// 升级成功
s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0;
t_ftp_update_info.u8_update_result = 0;
} else if (ret < 0) {
// 升级失败
s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0;
t_ftp_update_info.u8_update_result = 1;
}
}
```
### 4.1 状态机步骤(`E_LWIP_FTP_STEP`
`lwip_ftp_upgrade.c` 内部定义的状态机大致流程:
1. `LWIP_FTP_STEP_CONNECT`
- 建立 FTP 控制连接(21 端口),并设置为非阻塞。
2. `LWIP_FTP_STEP_WAIT_WELCOME`
- 等待 `220` 欢迎语。
3. `LWIP_FTP_STEP_SEND_USER / WAIT_USER`
- 发送 `USER xxx`,等待响应;
- 若返回 `331`,继续发送 `PASS`;否则直接进入 `TYPE`
4. `LWIP_FTP_STEP_SEND_PASS / WAIT_PASS`
- 发送 `PASS xxx`,等待登录结果。
5. `LWIP_FTP_STEP_SEND_TYPE / WAIT_TYPE`
- 发送 `TYPE I`,切换为二进制模式。
6. `LWIP_FTP_STEP_SEND_CWD / WAIT_CWD`(可选)
- 如果 `u8_path` 非空,发送 `CWD <path>` 切目录。
7. `LWIP_FTP_STEP_SEND_SIZE / WAIT_SIZE`
- 发送 `SIZE <file>`,并从 `213` 响应中解析文件大小;
- 回填到 `s_ctx.file_size``t_ftp_update_info.u32_file_size`
8. `LWIP_FTP_STEP_PREPARE_FLASH`
- 调用 `upgrade_erase_program_area()` 擦除升级区;
- 构造 `upgrade_header_t`,写入 `UPGRADE_FLAG_IDLE / file_size / packet_count` 等信息。
9. `LWIP_FTP_STEP_SEND_PASV / WAIT_PASV`
- 发送 `PASV`,解析 `227` 响应得到数据通道 IP/端口。
10. `LWIP_FTP_STEP_CONNECT_DATA`
-`data_ip / data_port` 建立数据连接 socket。
11. `LWIP_FTP_STEP_SEND_RETR / WAIT_RETR`
- 发送 `RETR <file>`,等待 `150/125` 等开始传输响应。
12. `LWIP_FTP_STEP_RECV_DATA`
- 循环接收数据,按 1024 字节分包写入 Flash(见 4.2)。
13. `LWIP_FTP_STEP_SEND_QUIT`
- 发送 `QUIT`,关闭控制连接;
- 写入 `UPGRADE_FLAG_NEED_UP` 头部,标记升级就绪。
14. `LWIP_FTP_STEP_DONE / FAIL`
- `DONE`:整个流程成功,`lwip_ftp_upgrade_step()` 返回 `1`
- `FAIL`:任意步骤出错,返回 `<0`
### 4.2 分包写 Flash 与进度打印
- 内部使用一个 **动态申请的** 1024 字节缓存 `packet_buf`(不是全局大数组):
-`lwip_ftp_upgrade_init_from_tftp()` 中第一次调用 `pvPortMalloc(PACKET_SIZE)`(FreeRTOS 堆),后续重复使用;
- 失败会直接打印 `"pvPortMalloc packet_buf fail"` 并进入 `FAIL`
- 数据通道每次 `lwip_tcp_recv()` 得到的数据都会写入 `packet_buf`
- 每累计满 1024 字节调用一次 `upgrade_write_packet(packet_index, packet_buf, len)`
- 处理最后不足一包的数据时,也会单独调用一次。
- **接收进度打印**
- 每次收到数据后,都会根据 `downloaded / file_size` 计算百分比:
```c
Plat_Comm_LOG("lwip ftp: recv %lu/%lu (%u%%)\r\n", ...);
```
- 方便在串口日志中实时观察下载进度。
### 4.3 控制通道响应等待(最长 1 秒)
- 所有 FTP 控制命令的响应(`220 / 331 / 230 / 213 / 227 / 150 ...`)统一通过:
```c
static int ftp_read_reply_block(int sock, uint32_t timeout_ms,
int *out_code, char *out_msg, size_t out_msg_sz);
```
- 关键特性:
- **内部使用局部缓冲区**
- `char rx_buf[FTP_CTRL_RX_BUF_SIZE];`
- `char line[FTP_LINE_MAX];`
- 不再在全局结构体中定义大数组。
- 每次调用最长阻塞 `timeout_ms` 毫秒(本工程中统一传入 `1000`,即 1 秒):
- 在 1 秒内尽可能收满一条完整响应行;
- 超时、对端关闭或错误均返回非 0,状态机进入 `FAIL`。
- 满足需求:
- **“发送命令后可最长等待 1 秒接收数据,这样可以保证大部分交互在一个周期内完成”**。
## 5. 日志上传(4G / 预留 LwIP
- 当 `s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 1` 时,`v_ftp_update_action()` 走“日志上传”分支:
- 当前仅在 `t_ftp_update_info.u8_netType == 0`4G)时调用 `u8_ec200a_4g_ftp_log_upload()`
- 网线场景下会打印提示,尚未提供 LwIP 日志上传实现。
后续如需支持 **LwIP FTP 日志上传**,建议在 `lwip_ftp_upgrade.[ch]` 基础上扩展 `STOR/APPE` 等命令。
## 6. 注意事项
- **非阻塞总框架 + 局部 1 秒等待**:
- 整体采用状态机 + 周期调用的方式集成在 `v_Plat_Comm` 中,不新建线程;
- 单条 FTP 命令的响应等待时间 **最多 1 秒**,保证大部分交互能在一次 `v_ftp_update_action()` 周期内完成。
- **内存使用**
- 不再在全局结构体中定义大数组:
- 控制通道解析使用的缓冲区全部为函数内局部变量;
- 数据分包缓冲 `packet_buf[PACKET_SIZE]` 采用 FreeRTOS `pvPortMalloc` 动态申请,仅 1KB。
- **文件大小依赖 SIZE**
- 必须保证 FTP 服务器支持 `SIZE` 命令,否则无法写入正确的 `file_size`,升级头将不可靠。
- **错误处理与重试**
- 任意步骤出错,`lwip_ftp_upgrade_step()` 返回 `<0`
- `v_ftp_update_action()` 会清除 `u8_updata_flag` 并设置 `u8_update_result = 1`
- 上层可根据日志与结果决定是否重新触发升级。
## 7. 与 4G FTP 的对比
- **4G FTP**
- 文件:`ec200a_4g_ftp.c`
- 通过 AT 命令驱动 4G 模块内置 FTP 客户端;
- 状态机在 `u8_ec200a_4g_ftp_update()` 内实现。
- **LwIP FTP(网线)**
- 文件:`lwip_module.c` + `lwip_ftp_upgrade.c`
- 使用 LwIP socket 直接实现 FTP 协议;
- 与 4G 共用 `flash_upgrade` 升级区与头部结构;
- 集成方式完全嵌入 `v_ftp_update_action()`,不影响 `v_Plat_Comm` 线程的主循环。
+146
View File
@@ -0,0 +1,146 @@
#include "plat_comm/lwip_module/lwip_module.h"
#include "lwip/sockets.h" /* LwIP socket APIsocket/connect/send/recv/close */
#include "lwip/netdb.h" /* 域名解析 lwip_gethostbyname */
#include <string.h>
#include <errno.h>
/**
* ************************************************************************
* @brief 将主机名或 IP 字符串解析为 in_addr
* @param[in] host - 服务器地址,可为 "192.168.1.1" 或 "www.xxx.com"
* @param[out] out_addr - 解析得到的 IPv4 地址
* @return 0 成功,-1 失败(非 IP 且 DNS 解析失败)
* ************************************************************************
*/
int lwip_resolve_host(const char *host, struct in_addr *out_addr)
{
if (host == NULL || out_addr == NULL)
return -1;
/* 先按 IP 字符串解析(如 "134.17.16.109" */
unsigned long addr = inet_addr(host);
if (addr != INADDR_NONE) {
out_addr->s_addr = addr;
return 0;
}
/* 非 IP 则按域名解析(依赖 LwIP DNS,仅 LWIP_DNS=1 时 netdb.h 才声明 lwip_gethostbyname */
#if LWIP_DNS
{
struct hostent *he = lwip_gethostbyname(host);
if (he != NULL && he->h_addr_list[0] != NULL) {
memcpy(&out_addr->s_addr, he->h_addr_list[0], (size_t)he->h_length);
return 0;
}
}
#endif
return -1;
}
/**
* ************************************************************************
* @brief 设置 socket 为非阻塞模式
* @param[in] sock - socket 描述符
* @return 0 成功,-1 失败
* ************************************************************************
*/
int lwip_set_nonblock(int sock)
{
int flags = lwip_fcntl(sock, F_GETFL, 0);
if (flags >= 0) {
if (lwip_fcntl(sock, F_SETFL, flags | O_NONBLOCK) >= 0)
return 0;
}
return -1;
}
/**
* ************************************************************************
* @brief 创建 TCP socket 并连接到指定地址
* @param[in] host - 服务器地址(IP 或域名)
* @param[in] port - 服务器端口
* @return >=0 socket 描述符,<0 失败
* ************************************************************************
*/
int lwip_tcp_connect(const char *host, uint16_t port)
{
struct sockaddr_in server_addr;
struct in_addr ip_addr;
int sock;
if (host == NULL || host[0] == '\0' || port == 0)
return -1;
/* 创建 TCP 流式 socket */
sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return -1;
/* 解析服务器 hostIP 或域名)为 in_addr */
if (lwip_resolve_host(host, &ip_addr) != 0) {
lwip_close(sock);
return -1;
}
/* 填充服务器地址并发起连接 */
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = ip_addr.s_addr;
server_addr.sin_port = htons(port);
if (lwip_connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
lwip_close(sock);
return -1;
}
/* 设为非阻塞,避免 recv 阻塞任务 */
if (lwip_set_nonblock(sock) != 0) {
lwip_close(sock);
return -1;
}
return sock;
}
/**
* ************************************************************************
* @brief 关闭 socket
* @param[in] sock - socket 描述符
* ************************************************************************
*/
void lwip_tcp_close(int sock)
{
if (sock >= 0)
lwip_close(sock);
}
/**
* ************************************************************************
* @brief 发送数据
* @param[in] sock - socket 描述符
* @param[in] buf - 发送缓冲区
* @param[in] len - 发送长度
* @return >0 实际发送字节数,<=0 失败或对端关闭
* ************************************************************************
*/
int lwip_tcp_send(int sock, const void *buf, size_t len)
{
if (sock < 0 || buf == NULL || len == 0)
return -1;
return lwip_send(sock, buf, len, 0);
}
/**
* ************************************************************************
* @brief 接收数据(非阻塞)
* @param[in] sock - socket 描述符
* @param[out] buf - 接收缓冲区
* @param[in] len - 缓冲区长度
* @return >0 实际接收字节数,0 对端关闭,<0 错误(errno 需检查)
* ************************************************************************
*/
int lwip_tcp_recv(int sock, void *buf, size_t len)
{
if (sock < 0 || buf == NULL || len == 0)
return -1;
return lwip_recv(sock, buf, len, 0);
}
+82
View File
@@ -0,0 +1,82 @@
#ifndef LWIP_MODULE_H
#define LWIP_MODULE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include <stdint.h>
#include <stddef.h> /* size_t */
#include "lwip/sockets.h" /* LwIP socket API 和结构体定义 */
#include "lwip/inet.h" /* LwIP inet_addr 和 INADDR_NONE 等宏 */
/* LwIP socket 相关宏定义(部分环境在 lwip/sockets.h 中未定义时在此兜底) */
#ifndef F_GETFL
#define F_GETFL 3 /* fcntl 获取文件描述符标志 */
#endif
#ifndef F_SETFL
#define F_SETFL 4 /* fcntl 设置文件描述符标志 */
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0x0004 /* 非阻塞模式,recv 无数据时立即返回不阻塞任务 */
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK 11 /* 非阻塞 recv 无数据时的 errno */
#endif
#ifndef EAGAIN
#define EAGAIN 11 /* 同上,与 EWOULDBLOCK 等价 */
#endif
/**
* @brief 将主机名或 IP 字符串解析为 in_addr
* @param[in] host 服务器地址,可为 "192.168.1.1" 或 "www.xxx.com"
* @param[out] out_addr 解析得到的 IPv4 地址
* @return 0 成功,-1 失败(非 IP 且 DNS 解析失败)
*/
int lwip_resolve_host(const char *host, struct in_addr *out_addr);
/**
* @brief 创建 TCP socket 并连接到指定地址
* @param[in] host 服务器地址(IP 或域名)
* @param[in] port 服务器端口
* @return >=0 socket 描述符,<0 失败
*/
int lwip_tcp_connect(const char *host, uint16_t port);
/**
* @brief 关闭 socket
* @param[in] sock socket 描述符
*/
void lwip_tcp_close(int sock);
/**
* @brief 发送数据
* @param[in] sock socket 描述符
* @param[in] buf 发送缓冲区
* @param[in] len 发送长度
* @return >0 实际发送字节数,<=0 失败或对端关闭
*/
int lwip_tcp_send(int sock, const void *buf, size_t len);
/**
* @brief 接收数据(非阻塞)
* @param[in] sock socket 描述符
* @param[out] buf 接收缓冲区
* @param[in] len 缓冲区长度
* @return >0 实际接收字节数,0 对端关闭,<0 错误(errno 需检查)
*/
int lwip_tcp_recv(int sock, void *buf, size_t len);
/**
* @brief 设置 socket 为非阻塞模式
* @param[in] sock socket 描述符
* @return 0 成功,-1 失败
*/
int lwip_set_nonblock(int sock);
#ifdef __cplusplus
}
#endif
#endif /* LWIP_MODULE_H */
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
#ifndef _BS_OCPP_CTRL_H_
#define _BS_OCPP_CTRL_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#if BS_OCPP_EN //OCPP平台使能
#include "bs_ocpp_json_data.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "publicdata/publicdata.h"
#include "main.h"
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#define OCPP_TIMERCNT_NUM 12 //ocpp 计时计数
#define OCPP_ACK_STRUCT_NUM 5 //ocpp 应答结构体数量
#define OCPP_MALLOC pvPortMalloc
#define OCPP_FREE vPortFree
typedef enum{
OCPP_TIMER_BootNotification = 0, //登录帧
OCPP_TIMER_Heartbeat, //心跳帧
OCPP_TIMER_StatusNotification_GUNA, //A枪 状态上传
OCPP_TIMER_StatusNotification_GUNB, //B枪 状态上传
OCPP_TIMER_MeterValues_GUNA, //A枪电表数据上传
OCPP_TIMER_MeterValues_GUNB, //B枪电表数据上传
OCPP_TIMER_StopTransaction_GUNA, //A枪停止充电数据上传
OCPP_TIMER_StopTransaction_GUNB, //B枪停止充电数据上传
OCPP_TIMER_Authorize_GUNA, //A枪鉴权查询上传
OCPP_TIMER_Authorize_GUNB, //B枪鉴权查询上传
}OCPP_SEND_TIMER;
typedef enum{
OCPP_STATE_CHANGE_FLAG = 0, //状态变位标志
OCPP_TRADE_UPLOAD_FLAG, //交易订单上传标志
OCPP_START_RESULT_FLAG, //启动结果上送标志
OCPP_STOP_RESULT_FLAG, //停止结果上送标志
OCPP_VIN_START_FLAG, //VIN鉴权请求上送标志
OCPP_VIN_RESULT, //VIN鉴权结果
OCPP_BILL_INDEX, //上送订单索引号
}OCPP_CHARGE_FLAG;
/** DiagnosticsStatusNotification 待上送状态:外部/FTP 仅赋值;ocpp_send_mag_ctrl 监听非 0 则发送并清 0 */
typedef enum {
OCPP_DIAG_NOTIFY_PENDING_NONE = 0,
OCPP_DIAG_NOTIFY_PENDING_UPLOADING,
OCPP_DIAG_NOTIFY_PENDING_UPLOADED,
OCPP_DIAG_NOTIFY_PENDING_UPLOAD_FAILED,
} OCPP_DIAG_NOTIFY_PENDING_E;
/** FirmwareStatusNotification 待上送状态:与 Diagnostics 同套路(u8_*_notify_pending + ocpp_send_mag_ctrl */
typedef enum {
OCPP_FW_NOTIFY_PENDING_NONE = 0,
OCPP_FW_NOTIFY_PENDING_DOWNLOADING,
OCPP_FW_NOTIFY_PENDING_DOWNLOADED,
OCPP_FW_NOTIFY_PENDING_INSTALLING,
OCPP_FW_NOTIFY_PENDING_INSTALLED,
OCPP_FW_NOTIFY_PENDING_DOWNLOAD_FAILED,
OCPP_FW_NOTIFY_PENDING_INSTALLATION_FAILED,
} OCPP_FW_NOTIFY_PENDING_E;
typedef struct
{
U8_T flag; //存储标志
U8_T check_count; //查询次数
U8_T gunNo; //抢号
U16_T u16_cnt; /* 存储计数(回绕安全:v_clear_save_cmd 基于 max-min 差值检测回绕)*/
OCPP_CMD cmd; //命令值
char key[64]; //随机数密钥
}OCPP_DATA_SAVE;
typedef struct
{
OCPP_CMD cmd; //命令值
U8_T gunNo; //枪号
U8_T type; //应答数据类型 不同帧自定义
U8_T timer_cnt; //计时计数 用于超时判定
char ack_key[64]; //回复相同秘钥
char ack_data[32]; //应答数据内容 不同帧自定义
}OCPP_DATA_RECV_ACK;
/** 未结补单:先 Authorize 再 StartTransaction 的鉴权阶段 */
typedef enum {
OCPP_UNSETTLED_AUTH_NONE = 0,
OCPP_UNSETTLED_AUTH_WAIT,
OCPP_UNSETTLED_AUTH_OK
} OCPP_UNSETTLED_AUTH_PHASE_E;
typedef struct
{
U8_T id; //平台id 0-1
U8_T u8_register_flag; //登录标志
U8_T u8_ping_ack; //ping包回复标志
U8_T u8_unsettled_order_flag; //离线订单上送检查
U8_T u8_reboot_flag; //重启标志 0无动作 1平台下发重启 2本地重启
U8_T u8_log_file_upload; //日志文件上传标志
U8_T u8_diag_notify_pending; //见 OCPP_DIAG_NOTIFY_PENDING_E,仅赋值触发;发送在 ocpp_send_mag_ctrl
U8_T u8_fw_notify_pending; //见 OCPP_FW_NOTIFY_PENDING_E,与 u8_diag_notify_pending 并列、同发送机制
U8_T u8_fw_reboot_pending; //Installed 已成功上送后再 v_sys_reboot(避免未上报即复位)
U8_T u8_fw_installing_fsn_sent; //FSN(Installing) 已 sm>0 发出;STEP_FTP_SAVE 写头/复位前须为 1
S_LOG_DATA *s_unsettled_order_data;
U8_T u8_unsettled_auth_phase; // 未结补单:Authorize→StartTransaction 流水线阶段,取值见 OCPP_UNSETTLED_AUTH_PHASE_E
U32_T u32_unsettled_last_auth_s; // 未结补单:上次发出 Authorize 的秒时间戳(get_current_seconds),用于发送节流
U32_T u32_unsettled_last_stx_s; // 未结补单:上次发出 StartTransaction 的秒时间戳(get_current_seconds),用于发送节流
struct
{
U8_T u8_MeterValues_type; //数据上送标志 0无动作 1空闲数据上送 2充电中数据上送 3交易开始数据上送 4交易停止数据上送
//平台召测数据 "BootNotification","DiagnosticsStatusNotification","FirmwareStatusNotification","Heartbeat","MeterValues","StatusNotification"
U8_T u8_get_Trigger_BootNotification;
U8_T u8_get_Trigger_DiagnosticsStatusNotification;
U8_T u8_get_Trigger_FirmwareStatusNotification;
U8_T u8_get_Trigger_Heartbeat;
U8_T u8_get_Trigger_MeterValues;
U8_T u8_get_Trigger_StatusNotification;
U8_T u8_aut_cnt; //鉴权次数
U8_T u8_StatusNotification_send_cnt; //状态上传次数(保留,周期上送已取消)
U8_T u8_need_status_after_login; // BootNotification Accepted/Pending 后每枪需上送一次 StatusNotification
U8_T s_u8_mv_ws_fail_snap_req; /** MeterValues ws 发送失败时置位,下一周期强制写一条离线 MV */
int s_ocpp_live_stop_tx_id; /** 在线已上送 StopTransaction 的 transactionId(按枪),-1 表示无 */
}s_ctrl_flag[GUN_MAX_CNT]; //数据控制标志
struct
{
U16_T u16_timerCnt; //周期发送计数
U16_T u16_timeNUM ; //计数周期值
}s_timerCnt[OCPP_TIMERCNT_NUM];
U16_T u16_ocpp_ping_timer; //ping包周期计数
U32_T u32_timer; //计时计数
ULONG ulong_timer; //接收计时
OCPP_DATA_RECV_ACK *ack_ctrl[OCPP_ACK_STRUCT_NUM];
}OCPP_DATA_CTRL;
int recv_mags(char* recv_mag);
/****************************************************
* v_ocpp_init_data()
* OCPP `v_ocpp_mv_offline_init` OCPP_DATA_CTRLFlash 4G
*
* TCP diag/fw pending memset
****************************************************/
void v_ocpp_init_data(U8_T bs_flag);
/** 仅联网恢复:释放应答、可选 v_bs_4G_data_init、fd=-1、ulong_restart_timer;不执行 memset(OCPP_DATA_CTRL) */
void v_ocpp_init_data_ex(U8_T bs_flag, U8_T reset_4g_modem);
/****************************************************
* v_ocpp_tcp_connect()
* TCP连接功能实现 - 4G模块
*
*
* 0 1
****************************************************/
U8_T v_ocpp_tcp_connect();
/****************************************************
* v_ocpp_logo_on()
* -
*
*
* 0 1
****************************************************/
U8_T v_ocpp_logo_on();
/****************************************************
* v_ocpp_recv_data()
* -
*
*
*
****************************************************/
void v_ocpp_recv_data();
/****************************************************
* v_ocpp_send_ctrl()
* sdk私有协议逻辑控制 -
*
*
*
****************************************************/
void v_ocpp_send_ctrl();
/****************************************************
* v_ocpp_private_logic()
* sdk私有协议逻辑控制 -
*
*
*
****************************************************/
void v_ocpp_private_logic(U8_T bs_flag);
/** 仅置 u8_diag_notify_pendingUploaded / UploadFailed);实际上送由 ocpp_send_mag_ctrl 监听该变量完成 */
void v_ocpp_set_diag_upload_notify_pending(U8_T pending);
/** 置 u8_fw_notify_pending;合法值见 OCPP_FW_NOTIFY_PENDING_E;实际上送由 ocpp_send_mag_ctrl */
void v_ocpp_set_fw_notify_pending(U8_T pending);
/** FSN(Installing) 是否已由 ocpp_send_mag_ctrl 成功发出(供 ec200a STEP_FTP_SAVE 等待) */
U8_T u8_ocpp_fw_installing_fsn_sent(void);
void v_ocpp_clr_fw_installing_fsn_sent(void);
/** Installed 上送成功后需复位:置位,由 ocpp_send_mag_ctrl 在 FSN(Installed) 发出后调用 v_sys_reboot(旧流程;FTP 升级现改 EEPROM+先复位) */
void v_ocpp_set_fw_reboot_after_installed(U8_T pending);
/** FTP 写升级头后:FRAM 置位,复位;BootNotification Accepted/Pending 后再发 FSN Installed */
void v_ocpp_fw_eeprom_set_installed_after_boot(void);
/** 在线会话已上送 StopTransaction(如 Remote)后登记 txId,避免 EEPROM 掉电单重复 StopTx PowerLoss */
void v_ocpp_mark_live_stop_tx_done(U8_T gun_idx, int transaction_id);
/** 未结/掉电补单是否应跳过(同枪同 txId 已在线停充上送) */
U8_T u8_ocpp_skip_unsettled_stop_for_live_tx(S_LOG_DATA *log);
/** 未结单与当前枪正在进行的在线充电为同一会话(勿 StopTx / 勿抢 session MV 补发) */
U8_T u8_ocpp_unsettled_is_live_charging_session(S_LOG_DATA *log);
/** 未结订单:preTradeNo 是否仍为占位/空(须先 Authorize→StartTransaction */
U8_T u8_ocpp_is_pre_trade_no_pending(const U8_T *p);
/** 补发一条离线缓存的 MeterValues(与 send_mag 主动帧格式一致) */
int32_t s32_ocpp_send_meter_values_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv);
#endif
#endif
@@ -0,0 +1,764 @@
/**
* @file BS_ocpp_mv_offline_bridge.c
* @brief OCPP 线 MeterValues /+
*
* @details ****\n
* 1. **线**TCP CSMS SPI Flash \n
* FRAM \n
* 2. **线** **Authorize StartTransaction** **transactionId** \n
* `StopTransaction` Flash **** 线 MeterValues\n
* 3. ** MV**使 **** **** `StartChargeTime``Comm_Time`\n
* `xDate2Seconds` **32 ** `u32_charge_start_sec` **** FRAM API 0\n
* `S_LOG_DATA` 线 `v_meterlog_put_char_log_data` ****\n
* `u8_ocpp_mv_offline_map_get` MV `StopTransaction`\n
*
* @details ****\n
* 1. ****线\n
* 2. ****`S_OCPP_MV_OFFLINE_MV` `BS_ocpp_ctrl.c` `u8_get_ocpp_measurand_data` `s32_ocpp_send_meter_values_replay` JSON\n
* 3. **** `s_ocpp_mv_bridge_ctx` \n
* `v_ocpp_mv_offline_init` **`v_ocpp_init_data`**`BS_ocpp_ctrl.c` FRAM `v_ocpp_mv_offline_bridge_private_logic` Flash init\n
* `v_ocpp_mv_offline_unsettled_abort()` `S_LOG_DATA` \n
* 4. ****`get_current_seconds()` ****`public_func.c`/**OCPP Flash `u32_timestamp_unix`** **`GetCurrentTime` + `xDate2Seconds`**线 `MeterValues_mag` `StartChargeTime` epoch
*/
#include "BS_ocpp_mv_offline_bridge.h"
#if BS_OCPP_EN
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "BS_ocpp_ctrl.h"
#include "publicdata/public_define.h"
#include "publicdata/publicdata.h"
#include "plat_comm/impl/bs_public_impl.h"
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#include "plat_comm/plat_comm_task.h"
#include "BS_ocpp_configuration.h"
#include "app_rtc/app_rtc.h"
#include "bs_ocpp_json_data.h"
#define OCPP_OFFLINE_LOG(fmt, ...) \
MYLOG_MSG(OCPP_PRINT_FLAG,fmt, ##__VA_ARGS__) //任务打印
extern int32_t s32_ocpp_send_meter_values_replay(int gunNo, int transactionId,
const S_OCPP_MV_OFFLINE_MV *mv);
/* 与 BS_ocpp_ctrl.c 中 `v_ocpp_timestamp_to_str` 口径一致(离线 MV 时间戳由 unix 秒经本文件内 helper 转换) */
static void v_mv_offline_timestamp_to_str(Comm_Time *ctTime, char *time_str)
{
Comm_Time time;
if (ctTime == NULL) {
GetCurrentTime(&time);
ctTime = &time;
}
#if (OCPP_TIMESTAMP_UTC_OUTPUT)
else {
memcpy(&time, ctTime, sizeof(Comm_Time));
ctTime = &time;
}
v_adjust_time_with_timezone((Comm_Time *)ctTime, -8, 0);
(void)snprintf(time_str, (size_t)OCPP_DATE_LEN, "%04u-%02u-%02uT%02u:%02u:%02uZ",
(unsigned)ctTime->iYear, (unsigned)ctTime->ucMonth, (unsigned)ctTime->ucDay,
(unsigned)ctTime->ucHour, (unsigned)ctTime->ucMin, (unsigned)ctTime->ucSec);
#else
(void)snprintf(time_str, (size_t)OCPP_DATE_LEN, "%04u-%02u-%02uT%02u:%02u:%02u+08:00",
(unsigned)ctTime->iYear, (unsigned)ctTime->ucMonth, (unsigned)ctTime->ucDay,
(unsigned)ctTime->ucHour, (unsigned)ctTime->ucMin, (unsigned)ctTime->ucSec);
#endif
}
static void v_mv_offline_timestamp_unix_to_str(U32_T unix_sec, char *time_str)
{
Comm_Time ct;
xSeconds2Date((unsigned long)unix_sec, &ct);
v_mv_offline_timestamp_to_str(&ct, time_str);
}
/** 将离线 32B 快照填到单个 SampledValue(与 `u8_get_ocpp_measurand_data` 口径一致) */
static U8_T u8_offline_mv_to_sampled(SampledValue *sv, OCPP_MEASURAND_ID e_type, const S_OCPP_MV_OFFLINE_MV *mv)
{
char data_str[24] = {0};
if (sv == NULL || mv == NULL) {
return 0u;
}
switch (e_type) {
case ENUM_EnergyActiveImportRegister:
(void)snprintf(data_str, sizeof(data_str), "%lu", (unsigned long)mv->u32_meter_import_wh);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_EnergyActiveImportRegister, strlen(OCPP_STR_EnergyActiveImportRegister));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_Wh, strlen(OCPP_STR_Wh));
return 1u;
case ENUM_EnergyActiveImportInterval:
(void)snprintf(data_str, sizeof(data_str), "%lu", (unsigned long)mv->u32_energy_interval_wh);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_EnergyActiveImportInterval, strlen(OCPP_STR_EnergyActiveImportInterval));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_Wh, strlen(OCPP_STR_Wh));
return 1u;
case ENUM_PowerActiveImport:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_power_active_w);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_PowerActiveImport, strlen(OCPP_STR_PowerActiveImport));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_W, strlen(OCPP_STR_W));
return 1u;
case ENUM_PowerOffered:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_power_offered_01kw * 100.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_PowerOffered, strlen(OCPP_STR_PowerOffered));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_W, strlen(OCPP_STR_W));
return 1u;
case ENUM_CurrentImport:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_current_import_a_x100 / 100.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_CurrentImport, strlen(OCPP_STR_CurrentImport));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_A, strlen(OCPP_STR_A));
return 1u;
case ENUM_CurrentOffered:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_current_offered_a_x100 / 100.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_CurrentOffered, strlen(OCPP_STR_CurrentOffered));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_A, strlen(OCPP_STR_A));
return 1u;
case ENUM_Voltage:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_voltage_v_x10 / 10.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_Voltage, strlen(OCPP_STR_Voltage));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_V, strlen(OCPP_STR_V));
return 1u;
case ENUM_SoC:
(void)snprintf(data_str, sizeof(data_str), "%u", (unsigned)mv->u8_soc);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_SOC, strlen(OCPP_STR_SOC));
memcpy(sv->location, OCPP_STR_EV, strlen(OCPP_STR_EV));
memcpy(sv->unit, OCPP_STR_Percent, strlen(OCPP_STR_Percent));
if (mv->u8_soc_data_type == 1u) {
memcpy(sv->context, OCPP_STR_TransactionBegin, strlen(OCPP_STR_TransactionBegin));
} else if (mv->u8_soc_data_type == 2u) {
memcpy(sv->context, OCPP_STR_TransactionEnd, strlen(OCPP_STR_TransactionEnd));
} else {
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
}
return 1u;
default:
return 0u;
}
}
cJSON *MeterValues_mag_offline_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv)
{
MeterValuesRequest json_data;
char time_str[30] = {0};
U8_T i;
U8_T u8_data_num = 0;
if (mv == NULL) {
return NULL;
}
(void)memset(&json_data, 0, sizeof(json_data));
json_data.connectorId = gunNo + 1;
json_data.transactionId = transactionId;
v_mv_offline_timestamp_unix_to_str(mv->u32_timestamp_unix, time_str);
(void)memcpy(json_data.meterValue[0].timestamp, time_str, strlen(time_str));
for (i = 0; i < (U8_T)ENUM_measurand_Num; i++) {
if ((s_ocpp_flash_cfg_data.s_ocpp_config_data.u8_MeterValuesMeasurandNeed[i] & E_CFG_MVMN_MeterValuesSampledData)
== E_CFG_MVMN_MeterValuesSampledData) {
if (u8_offline_mv_to_sampled(&json_data.meterValue[0].sampledValue[u8_data_num], (OCPP_MEASURAND_ID)i, mv) != 0u) {
if (++u8_data_num >= MeterValuesRequest_SV_NUM) {
break;
}
}
}
}
if (u8_data_num == 0u) {
return NULL;
}
return j_get_ocpp_json(OCPP_MeterValues, &json_data);
}
/**
* @brief 线 MV RAM
* @details \n
* - `last_mv_save_sec[]`**** `MeterValueSampleInterval` \n
* - `unsettled` MV `s_unsettled_order_data` abort
*/
/** 离线 MV 顺序补发状态(未结补单 / 在线会话内补发共用) */
typedef struct {
U8_T active; /**< 非 0:正处于某笔单的补发区间 `[seq_next, seq_end]` 内 */
U8_T gun; /**< 当前补发枪号 */
U32_T u32_charge_start_sec; /**< `xDate2Seconds(StartChargeTime)`,与 FRAM `u32_tx_sn_lo` 一致 */
int i32_txid; /**< 本笔 OCPP transactionId(会话补发在读订单失败时沿用) */
U32_T seq_start;
U32_T seq_next;
U32_T seq_end;
} S_OCPP_MV_REPLAY_CTX;
typedef struct {
U32_T last_mv_save_sec[GUN_MAX_CNT]; /**< 每枪上次成功落盘时的 `get_current_seconds()`(单调秒),仅用于采样周期节流 */
S_OCPP_MV_REPLAY_CTX unsettled; /**< 未结订单 StopTx 前补发 */
S_OCPP_MV_REPLAY_CTX session; /**< 在线起充、中途离线再上线:用已有 transactionId 补发 */
} S_OCPP_MV_OFFLINE_BRIDGE_CTX;
/** 模块唯一静态上下文;静态存储期整体零初始化 */
static S_OCPP_MV_OFFLINE_BRIDGE_CTX s_ocpp_mv_bridge_ctx;
/** 每枪当前是否已为本笔充电完成起充准备(flow 离开充电后清零) */
static U8_T s_u8_mv_charge_session_armed[GUN_MAX_CNT];
/** 上一周期 BMS flow,用于停充边沿补写一条 MV */
static U8_T s_u8_mv_prev_bs_flow[GUN_MAX_CNT];
/** map full 日志节流(单调秒) */
static U32_T s_u32_mv_map_full_log_sec[GUN_MAX_CNT];
/**
* @fn v_ocpp_mv_charge_start_sec_from_time
* @brief FRAM **** 32 `xDate2Seconds`
* @param[in] ct `S_LOG_DATA.StartChargeTime` `log`
* @param[out] out_sec `xDate2Seconds(ct)` 0
* @details `*out_sec` `S_OCPP_MV_OFFLINE_MAP_ROW.u8_gun_no` \n
* `u8_ocpp_mv_offline_mv_save` / `map_get` 0 FRAM `u32_tx_sn_hi` 64 \n
* `ct` 112 131
*/
static void v_ocpp_mv_charge_start_sec_from_time(const Comm_Time *ct, U32_T *out_sec)
{
unsigned int sec;
if (out_sec != NULL) {
*out_sec = 0u;
}
if (ct == NULL || out_sec == NULL) {
return;
}
if (ct->iYear == 0u && ct->ucMonth == 0u && ct->ucDay == 0u) {
return;
}
if (ct->ucMonth == 0u || ct->ucMonth > 12u) {
return;
}
if (ct->ucDay == 0u || ct->ucDay > 31u) {
return;
}
sec = xDate2Seconds((Comm_Time *)ct);
*out_sec = (U32_T)sec;
}
/**
* @fn u8_ocpp_mv_fill_snapshot
* @brief Flash `S_OCPP_MV_OFFLINE_MV`
* @param[in] gun `0..GUN_MAX_CNT-1` `u8_ocpp_mv_offline_mv_save` `u8_gun_no`
* @param[out] out
* @return 1 0
* @details `u8_get_ocpp_measurand_data` \n
* - `u32_timestamp_unix``GetCurrentTime` + `xDate2Seconds`线 `MeterValues_mag` `v_mv_offline_timestamp_unix_to_str` ISO8601**** `get_current_seconds()`\n
* - `u32_meter_import_wh` Wh`E_BS_GET_SYS_DATA_MEMTER_ELECT`/10 U32\n
* - `u32_energy_interval_wh` Wh `E_BS_GET_SYS_DATA_LOG_ENERGY` type=1\n
* - `u16_voltage_v_x10` / `METER_VOL``METER_CUR` 0.01A 0.1A×10\n
* - `u16_power_active_w` `u16_voltage_v_x10`0.1V `u16_current_import_a_x100`0.01A`W = vol×cur / 1000` 65535\n
* - `u16_power_offered_01kw` / `u16_current_offered_a_x100` 0.1kW \n
* - SOC`E_BS_GET_SYS_DATA_SOC_DATA` `u8_soc_data_type` 0
*/
static U8_T u8_ocpp_mv_fill_snapshot(U8_T gun, S_OCPP_MV_OFFLINE_MV *out)
{
U64_T u64_wh; /**< 电表累计能量 Wh(64 位中间量,避免乘除溢出) */
U32_T start_wh; /**< 事务起始电表累计 Wh,来自日志能量 type=1 */
U64_T delta; /**< 事务段内增量 Wh = 当前累计 − 起始 */
Comm_Time now_ct; /**< 当前 RTC 日历(东八区),与 `BS_ocpp_ctrl.c` 中 `v_ocpp_timestamp_to_str(NULL,…)` 一致 */
if (out == NULL || gun >= GUN_MAX_CNT) {
return 0u;
}
(void)memset(out, 0, sizeof(*out)); //清空快照
out->u32_magic = OCPP_MV_OFFLINE_FLASH_MAGIC; //魔数
out->u16_version = (U16_T)OCPP_MV_OFFLINE_FLASH_VER; //版本
GetCurrentTime(&now_ct);
out->u32_timestamp_unix = (U32_T)xDate2Seconds(&now_ct); /* 日历 epoch 秒,非 get_current_seconds 上电秒 */
u64_wh = u64_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_MEMTER_ELECT) / 10u; //电表累计能量 Wh
out->u32_meter_import_wh = (U32_T)((u64_wh > 0xFFFFFFFFULL) ? 0xFFFFFFFFu : u64_wh); //电表累计能量 Wh 截断
start_wh = u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_LOG_ENERGY, 1u); //事务起始电表累计 Wh
delta = (out->u32_meter_import_wh > start_wh) ? ((U64_T)out->u32_meter_import_wh - (U64_T)start_wh) : 0u; //事务段内增量 Wh
out->u32_energy_interval_wh = (U32_T)((delta > 0xFFFFFFFFULL) ? 0xFFFFFFFFu : delta); //事务段内增量 Wh 截断
out->u16_voltage_v_x10 = u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_METER_VOL, 0u); //输出电压 0.1V
out->u16_current_import_a_x100 = (U16_T)(u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_METER_CUR, 0u) * 10u); //输出电流 0.01A
{
U32_T p_w; /**< `W = (0.1V×值)×(0.01A×值) = u16_voltage_v_x10 * u16_current_import_a_x100 / 1000` */
p_w = (U32_T)out->u16_voltage_v_x10 * (U32_T)out->u16_current_import_a_x100 / 1000u;
if (p_w > 65535u) {
p_w = 65535u;
}
out->u16_power_active_w = (U16_T)p_w;
}
out->u16_power_offered_01kw = (U16_T)u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_RATED_POWER, 0u); //额定功率 0.1kW
out->u16_current_offered_a_x100 = (U16_T)u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_PILE_PUT_PARA, 1u); //最大输出电流 0.01A
out->u8_soc = (U8_T)u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_SOC_DATA, 0u); //SOC%
out->u8_soc_data_type = 0u; //SOC 上下文:0 当前 / 1 Transaction.Begin / 2 Transaction.End
out->u8_connector_id = (U8_T)(gun + 1u); //连接器号
out->u8_evse_id = gun; //EVSE/枪逻辑号
return 1u;
}
/**
* @fn v_ocpp_mv_offline_bridge_private_logic
* @brief 线 MV
*/
static U8_T u8_mv_pre_trade_no_pending(const U8_T *p)
{
if (p == NULL || p[0] == '\0' || p[0] == (U8_T)0xFF) {
return 1u;
}
return 0u;
}
/**
* @brief Flash flow沿
* @return 1 0
*/
static U8_T u8_ocpp_mv_offline_persist_snapshot(U8_T gun, U32_T *seq_out)
{
S_LOG_DATA s_log;
U32_T charge_start_sec;
S_OCPP_MV_OFFLINE_MV mv;
U32_T seq_local;
U8_T sav_ret;
if (gun >= (U8_T)GUN_MAX_CNT) {
return 0u;
}
if (seq_out == NULL) {
seq_out = &seq_local;
}
(void)memset(&s_log, 0, sizeof(s_log));
v_meterlog_put_char_log_data(gun, &s_log);
v_ocpp_mv_charge_start_sec_from_time(&s_log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_ocpp_mv_fill_snapshot(gun, &mv) == 0u) {
return 0u;
}
sav_ret = u8_ocpp_mv_offline_mv_save(gun, charge_start_sec, 0u, &mv, seq_out);
if (sav_ret == 0u) {
s_ocpp_mv_bridge_ctx.last_mv_save_sec[gun] = (U32_T)get_current_seconds();
return 1u;
}
return 0u;
}
void v_ocpp_mv_offline_on_charge_start(U8_T gun)
{
if (gun >= (U8_T)GUN_MAX_CNT) {
return;
}
if (s_ocpp_mv_bridge_ctx.session.active != 0u
&& s_ocpp_mv_bridge_ctx.session.gun == gun) {
(void)memset(&s_ocpp_mv_bridge_ctx.session, 0, sizeof(s_ocpp_mv_bridge_ctx.session));
}
/* 勿清 FRAM 映射:同枪连续多笔离线充(A→B→A)须各自保留 StartChargeTime 键,上线后逐笔补 MV */
s_ocpp_mv_bridge_ctx.last_mv_save_sec[gun] = 0u;
s_u32_mv_map_full_log_sec[gun] = 0u;
s_u8_mv_charge_session_armed[gun] = 1u;
OCPP_OFFLINE_LOG("MV offline: charge start gun=%u (keep pending maps)\r\n", (unsigned)gun);
}
void v_ocpp_mv_offline_bridge_private_logic(U8_T bs_id)
{
U8_T i; /**< 枪循环变量 `0..GUN_MAX_CNT-1` */
U16_T interval_s; /**< 采样周期(秒),来自 OCPP 配置 `MeterValueSampleInterval` */
U32_T now; /**< `get_current_seconds()`:上电起单调秒,仅用于与 `MeterValueSampleInterval` 比较节流 */
(void)bs_id;
interval_s = (U16_T)60; /* 配置为 0 或未加载时的保守默认,避免除零或过频写 Flash */
if (s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval != 0u) {
interval_s = s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval;
}
now = (U32_T)get_current_seconds();
for (i = 0u; i < (U8_T)GUN_MAX_CNT; i++) {
S_LOG_DATA s_log; /**< 当前枪订单日志快照,含 `StartChargeTime` */
U32_T charge_start_sec; /**< `xDate2Seconds(StartChargeTime)`,与 FRAM `u32_tx_sn_lo` 一致 */
S_OCPP_MV_OFFLINE_MV mv; /**< 单条待写入 Flash 的 32B 负载 + 帧头由底层封装 */
U32_T seq_out; /**< 本笔分配到的该枪单调序号(调试用,此处未回传上层) */
U8_T sav_ret; /**< `u8_ocpp_mv_offline_mv_save` 返回值:0 成功,2 映射满,3 Flash,4 FRAM5 序号不连续等 */
{
U8_T bs_flow = u8_bs_get_flow_state(i);
/* 停充边沿:补写结束时刻 MV,避免最后一采样周期未落盘 */
if (s_u8_mv_prev_bs_flow[i] == 2u && bs_flow != 2u) {
U32_T seq_end = 0u;
if (u8_ocpp_mv_offline_persist_snapshot(i, &seq_end) != 0u) {
OCPP_OFFLINE_LOG("MV offline: stop snap gun=%u seq=%lu\r\n",
(unsigned)i, (unsigned long)seq_end);
}
}
s_u8_mv_prev_bs_flow[i] = bs_flow;
if (bs_flow != 2u) { //枪未充电 跳过
s_u8_mv_charge_session_armed[i] = 0u;
continue;
}
}
if (s_u8_mv_charge_session_armed[i] == 0u) {
U32_T resume_key = 0u;
U32_T resume_seq = 0u;
U16_T resume_cnt = 0u;
(void)memset(&s_log, 0, sizeof(s_log));
v_meterlog_put_char_log_data(i, &s_log);
v_ocpp_mv_charge_start_sec_from_time(&s_log.StartChargeTime, &resume_key);
if (resume_key != 0u
&& u8_ocpp_mv_offline_map_get(i, resume_key, 0u, &resume_seq, &resume_cnt) == 0u
&& resume_cnt > 0u) {
/* 链路抖动/TCP 重连:同一笔充电勿清 map,避免补发从 seq 头反复上送 */
s_u8_mv_charge_session_armed[i] = 1u;
OCPP_OFFLINE_LOG("MV offline: gun=%u link resume, keep map (%u条) key=0x%08lX\r\n",
(unsigned)i, (unsigned)resume_cnt, (unsigned long)resume_key);
} else {
v_ocpp_mv_offline_on_charge_start(i);
}
}
if (interval_s != 0u && s_ocpp_mv_bridge_ctx.last_mv_save_sec[i] != 0u
&& (now - s_ocpp_mv_bridge_ctx.last_mv_save_sec[i]) < (U32_T)interval_s) { //时间间隔未到 跳过
continue;
}
(void)memset(&s_log, 0, sizeof(s_log));
v_meterlog_put_char_log_data(i, &s_log); //获取订单日志
v_ocpp_mv_charge_start_sec_from_time(&s_log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) { /* 开始充电时间无效或未写入,无法建立稳定键 */
continue;
}
if (u8_ocpp_mv_fill_snapshot(i, &mv) == 0u) { //填充快照失败 跳过
continue;
}
sav_ret = u8_ocpp_mv_offline_mv_save(i, charge_start_sec, 0u, &mv, &seq_out);
if (sav_ret == 0u) {
s_ocpp_mv_bridge_ctx.last_mv_save_sec[i] = now;
OCPP_OFFLINE_LOG("MV offline: write ok gun=%u seq=%lu key_sec=0x%08lX unix_ts=%lu Wh=%lu Ival=%lu\r\n",
(unsigned)i,
(unsigned long)seq_out,
(unsigned long)charge_start_sec,
(unsigned long)mv.u32_timestamp_unix,
(unsigned long)mv.u32_meter_import_wh,
(unsigned long)mv.u32_energy_interval_wh);
} else if (sav_ret == 2u) {
U32_T now_log = (U32_T)get_current_seconds();
if (s_u32_mv_map_full_log_sec[i] == 0u
|| (now_log - s_u32_mv_map_full_log_sec[i]) >= 30u) {
s_u32_mv_map_full_log_sec[i] = now_log;
OCPP_OFFLINE_LOG("MV offline: map full gun=%u (FRAM rows=%u)\r\n",
(unsigned)i, (unsigned)OCPP_MV_OFFLINE_MAP_ROWS);
}
} else if (sav_ret != 5u) {
OCPP_OFFLINE_LOG("MV offline: save err=%u gun=%u\r\n", (unsigned)sav_ret, (unsigned)i);
}
}
}
void v_ocpp_mv_offline_snap_gun_now(U8_T gun)
{
U32_T seq_out = 0u;
if (gun >= (U8_T)GUN_MAX_CNT) {
return;
}
if (u8_bs_get_flow_state(gun) != 2u) {
return;
}
if (s_u8_mv_charge_session_armed[gun] == 0u) {
v_ocpp_mv_offline_on_charge_start(gun);
}
if (u8_ocpp_mv_offline_persist_snapshot(gun, &seq_out) != 0u) {
OCPP_OFFLINE_LOG("MV offline: ws-fail snap gun=%u seq=%lu\r\n",
(unsigned)gun, (unsigned long)seq_out);
}
}
/**
* @fn v_ocpp_mv_offline_unsettled_abort
* @brief `s_ocpp_mv_bridge_ctx.unsettled`
*/
void v_ocpp_mv_offline_unsettled_abort(void)
{
(void)memset(&s_ocpp_mv_bridge_ctx.unsettled, 0, sizeof(s_ocpp_mv_bridge_ctx.unsettled));
}
U8_T u8_ocpp_mv_offline_map_pending_for_log(const S_LOG_DATA *log)
{
U32_T charge_start_sec;
U32_T seq_start;
U16_T count;
U8_T gun;
if (log == NULL) {
return 0u;
}
gun = log->u8_gunNo;
if (gun >= (U8_T)GUN_MAX_CNT) {
gun = 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log->StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_ocpp_mv_offline_map_get(gun, charge_start_sec, 0u, &seq_start, &count) != 0u
|| count == 0u) {
return 0u;
}
return 1u;
}
void v_ocpp_mv_offline_session_replay_abort(void)
{
(void)memset(&s_ocpp_mv_bridge_ctx.session, 0, sizeof(s_ocpp_mv_bridge_ctx.session));
}
/**
* @brief MeterValues
* @return 1 0 /
*/
static U8_T u8_ocpp_mv_replay_step_ctx(S_OCPP_MV_REPLAY_CTX *rp, U8_T gun, U32_T charge_start_sec, int txid,
const char *tag)
{
U32_T seq_start;
U16_T count;
S_OCPP_MV_OFFLINE_MV mv;
if (rp == NULL || charge_start_sec == 0u || txid < 0) {
return 0u;
}
if (rp->active == 0u) {
if (u8_ocpp_mv_offline_map_get(gun, charge_start_sec, 0u, &seq_start, &count) != 0u || count == 0u) {
return 0u;
}
rp->active = 1u;
rp->gun = gun;
rp->u32_charge_start_sec = charge_start_sec;
rp->i32_txid = txid;
rp->seq_start = seq_start;
rp->seq_next = seq_start;
rp->seq_end = seq_start + (U32_T)count - 1u;
OCPP_OFFLINE_LOG("MV offline: %s queue gun=%u txId=%d key_sec=0x%08lX seq %lu-%lu (%u条)\r\n",
tag, (unsigned)gun, txid,
(unsigned long)charge_start_sec,
(unsigned long)seq_start,
(unsigned long)rp->seq_end,
(unsigned)count);
} else if (rp->gun != gun || rp->u32_charge_start_sec != charge_start_sec) {
(void)memset(rp, 0, sizeof(*rp));
return 0u;
}
if (rp->seq_next > rp->seq_end) {
(void)u8_ocpp_mv_offline_map_remove(rp->gun, rp->u32_charge_start_sec, 0u);
(void)memset(rp, 0, sizeof(*rp));
return 0u;
}
if (u8_ocpp_mv_offline_mv_read_by_seq(rp->gun, rp->seq_next, &mv) != 0u) {
OCPP_OFFLINE_LOG("MV offline: %s read fail gun=%u seq=%lu slot=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)rp->seq_next,
(unsigned long)(rp->seq_next % (U32_T)OCPP_MV_OFFLINE_POOL_MAX_PER_GUN));
rp->seq_next++;
return 1u;
}
if (u8_ocpp_mv_offline_mv_is_valid(&mv) == 0u) {
OCPP_OFFLINE_LOG("MV offline: %s read skip invalid gun=%u seq=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)rp->seq_next);
rp->seq_next++;
return 1u;
}
if (s32_ocpp_send_meter_values_replay((int)rp->gun, txid, &mv) <= 0) {
OCPP_OFFLINE_LOG("MV offline: %s send fail gun=%u seq=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)rp->seq_next);
return 1u;
}
{
U32_T total = rp->seq_end - rp->seq_start + 1u;
U32_T done;
rp->seq_next++;
done = rp->seq_next - rp->seq_start;
OCPP_OFFLINE_LOG("MV offline: %s send ok gun=%u %lu/%lu seq=%lu ts=%lu Wh=%lu\r\n",
tag, (unsigned)rp->gun,
(unsigned long)done, (unsigned long)total,
(unsigned long)(rp->seq_next - 1u),
(unsigned long)mv.u32_timestamp_unix,
(unsigned long)mv.u32_meter_import_wh);
if (rp->seq_next > rp->seq_end) {
OCPP_OFFLINE_LOG("MV offline: %s replay done gun=%u total=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)total);
(void)u8_ocpp_mv_offline_map_remove(rp->gun, rp->u32_charge_start_sec, 0u);
(void)memset(rp, 0, sizeof(*rp));
return 0u;
}
}
return 1u;
}
U8_T u8_ocpp_mv_offline_session_replay_pending(U8_T gun)
{
S_LOG_DATA log;
U32_T charge_start_sec;
U32_T seq_start;
U16_T count;
if (gun >= (U8_T)GUN_MAX_CNT) {
return 0u;
}
if (s_ocpp_mv_bridge_ctx.session.active != 0u) {
return (gun == s_ocpp_mv_bridge_ctx.session.gun) ? 1u : 0u;
}
if (s_ocpp_mv_bridge_ctx.unsettled.active != 0u) {
return 0u;
}
(void)memset(&log, 0, sizeof(log));
if (u8_plat_get_real_order(&log, gun) == 0u) {
return 0u;
}
if (u8_mv_pre_trade_no_pending(log.u8_preTradeNo) != 0u) {
return 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_ocpp_mv_offline_map_get(gun, charge_start_sec, 0u, &seq_start, &count) != 0u || count == 0u) {
return 0u;
}
return 1u;
}
U8_T u8_ocpp_mv_offline_session_replay_step(U8_T gun)
{
S_LOG_DATA log;
U32_T charge_start_sec;
U8_T flow;
int txid;
if (gun >= (U8_T)GUN_MAX_CNT) {
return 0u;
}
if (s_ocpp_mv_bridge_ctx.unsettled.active != 0u) {
return 0u;
}
/* 已在补发:仅在本枪推进;其它枪轮询项直接跳过,避免 gun0 空订单 pending 占满发送队列(session_0013 */
if (s_ocpp_mv_bridge_ctx.session.active != 0u) {
if (gun != s_ocpp_mv_bridge_ctx.session.gun) {
return 0u;
}
txid = s_ocpp_mv_bridge_ctx.session.i32_txid;
(void)memset(&log, 0, sizeof(log));
if (u8_plat_get_real_order(&log, gun) != 0u) {
int live_tx = atoi((const char *)log.u8_preTradeNo);
if (live_tx > 0) {
s_ocpp_mv_bridge_ctx.session.i32_txid = live_tx;
txid = live_tx;
}
}
return u8_ocpp_mv_replay_step_ctx(&s_ocpp_mv_bridge_ctx.session,
s_ocpp_mv_bridge_ctx.session.gun,
s_ocpp_mv_bridge_ctx.session.u32_charge_start_sec,
txid, "session");
}
(void)memset(&log, 0, sizeof(log));
if (u8_plat_get_real_order(&log, gun) == 0u) {
return 0u;
}
if (u8_mv_pre_trade_no_pending(log.u8_preTradeNo) != 0u) {
return 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
txid = atoi((const char *)log.u8_preTradeNo);
flow = u8_bs_get_flow_state(gun);
if (flow != 2u && flow != 9u && flow != 10u) {
return 0u;
}
return u8_ocpp_mv_replay_step_ctx(&s_ocpp_mv_bridge_ctx.session, gun, charge_start_sec, txid, "session");
}
/**
* @fn u8_ocpp_mv_offline_unsettled_step
* @brief WS MeterValues
*/
U8_T u8_ocpp_mv_offline_unsettled_step(S_LOG_DATA *log)
{
U32_T charge_start_sec;
U8_T gun;
int txid;
if (log == NULL) {
return 0u;
}
if (u8_ocpp_unsettled_is_live_charging_session(log) != 0u) {
return 0u;
}
gun = log->u8_gunNo;
if (gun >= GUN_MAX_CNT) {
gun = 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log->StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_mv_pre_trade_no_pending(log->u8_preTradeNo) != 0u) {
return 0u;
}
txid = atoi((const char *)log->u8_preTradeNo);
if (txid < 0) {
return 0u;
}
if (u8_ocpp_mv_offline_map_pending_for_log(log) == 0u) {
OCPP_OFFLINE_LOG("MV offline: unsettled no map gun=%u key_sec=0x%08lX txId=%d\r\n",
(unsigned)gun, (unsigned long)charge_start_sec, txid);
return 0u;
}
if (s_ocpp_mv_bridge_ctx.session.active != 0u
&& s_ocpp_mv_bridge_ctx.session.gun == gun) {
/* 同枪在线会话 MV 补发中:勿 abort session 走未结 StopTxsession_0012 */
if (u8_ocpp_unsettled_is_live_charging_session(log) != 0u) {
return 0u;
}
v_ocpp_mv_offline_session_replay_abort();
}
return u8_ocpp_mv_replay_step_ctx(&s_ocpp_mv_bridge_ctx.unsettled, gun, charge_start_sec, txid, "unsettled");
}
#endif /* BS_OCPP_EN */
@@ -0,0 +1,92 @@
/**
* @file BS_ocpp_mv_offline_bridge.h
* @brief OCPP 线 MeterValues `ocpp_mv_offline_flash_impl`SPI Flash + FRAM
*
* @details
* - ****TCP CSMS FlashFRAM **** `StartChargeTime` `xDate2Seconds` **32 ** Flash `u32_tx_sn_lo` 0 FRAM `u32_tx_sn_hi` 64 线\n
* - **FRAM/SPI ** `BS_ocpp_ctrl.c` **`v_ocpp_init_data`** `v_ocpp_mv_offline_init()` EEPROM OCPP FRAM \n
* - **** `transactionId` `u8_ocpp_mv_offline_unsettled_step` MeterValues\n
* `BS_ocpp_ctrl` `StopTransaction`\n
* - **线**Remote 线 线 线 ** transactionId** `u8_ocpp_mv_offline_session_replay_step` 线 MV\n
* 线 MV / `StopTransaction`\n
* - `BS_OCPP_EN` API OCPP
*/
#ifndef BS_OCPP_MV_OFFLINE_BRIDGE_H
#define BS_OCPP_MV_OFFLINE_BRIDGE_H
#include "publicdata/type.h"
#include "meter_calculate/meter_calculate_impl.h"
#if BS_OCPP_EN
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#include "bs_ocpp_json_data.h"
/**
* @fn MeterValues_mag_offline_replay
* @brief 线 MeterValues PDU线 `MeterValues_mag` `j_get_ocpp_json` /
*/
cJSON *MeterValues_mag_offline_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv);
/**
* @fn v_ocpp_mv_offline_bridge_private_logic
* @brief 线 MV
* @param[in] bs_id / OCPP id `v_ocpp_private_logic(U8_T bs_flag)` 使
* @details
* - `v_ocpp_private_logic` **TCP ** **** \n
* - `s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval` 0 60s\n
* - `u8_bs_get_flow_state()==2` **** `StartChargeTime` \n
* `xDate2Seconds(StartChargeTime)` ** U32**`u8_ocpp_mv_offline_mv_save` 0\n
* @note 线 `send_mag(..., OCPP_MeterValues, ...)` Flash `ocpp_mv_offline_flash_impl.h`
*/
void v_ocpp_mv_offline_bridge_private_logic(U8_T bs_id);
/** 在线 MeterValues 发送失败时立即落盘一条离线 MV(忽略采样间隔节流) */
void v_ocpp_mv_offline_snap_gun_now(U8_T gun);
/** 该枪进入新一笔充电会话时调用:清 FRAM 映射槽、复位落盘节流 */
void v_ocpp_mv_offline_on_charge_start(U8_T gun);
/**
* @fn v_ocpp_mv_offline_unsettled_abort
* @brief 线 MV
* @details
* - `v_safe_free_unsettled_order_data` `S_LOG_DATA` \n
* - Flash FRAM
*/
void v_ocpp_mv_offline_unsettled_abort(void);
/** 未结订单在 Flash 中是否有待补发的离线 MeterValues(按 StartChargeTime 映射键) */
U8_T u8_ocpp_mv_offline_map_pending_for_log(const S_LOG_DATA *log);
/**
* @fn u8_ocpp_mv_offline_unsettled_step
* @brief transactionId 线 MeterValues
* @param[in] log `private_ocpp_data->s_unsettled_order_data`
* @return
* - **1** MV/ WebSocket \n
* **** `StopTransaction`**** `free` \n
* - **0**Flash/FRAM `map_remove`/\n
* `StopTransaction`
* @details
* - `log->StartChargeTime` `xDate2Seconds` **U32** `u8_ocpp_mv_offline_mv_save` `u32_tx_sn_lo` \n
* - JSON `transactionId`使 `atoi(log->u8_preTradeNo)`** 0** 0xFF/\n
* - **1 ** WS
*/
U8_T u8_ocpp_mv_offline_unsettled_step(S_LOG_DATA *log);
/** 中止「在线会话内」离线 MV 补发状态机(新一笔充电开始时会自动调用) */
void v_ocpp_mv_offline_session_replay_abort(void);
/** 当前枪是否存在待补发的离线 MV(已有 txId,FRAM 映射非空) */
U8_T u8_ocpp_mv_offline_session_replay_pending(U8_T gun);
/**
* @brief 线 `preTradeNo` transactionId Flash MV
* @return 1 0 StopTx / MV
*/
U8_T u8_ocpp_mv_offline_session_replay_step(U8_T gun);
#endif /* BS_OCPP_EN */
#endif /* BS_OCPP_MV_OFFLINE_BRIDGE_H */
@@ -0,0 +1,363 @@
# OCPP 功能模块说明(CCU601E_D
本文档描述 **`CCU601E_D\app\plat_comm\ocpp`** 目录内各文件职责、主要数据结构及与其它模块的关系,便于维护与二次开发。全文基于工程源码归纳;协议细节以 **OCPP 1.6-j** 为准。
---
## 1. 模块定位与编译开关
| 项目 | 说明 |
|------|------|
| **协议** | OCPP **1.6**,典型传输为 **WebSocket**(子协议 `ocpp1.6`)。 |
| **宏开关** | 全局 **`BS_OCPP_EN`**(通常为 `Public_define.h` 等处):为 **0** 时本目录多数代码不参与编译。 |
| **依赖** | **cJSON**JSON 组帧/解析)、FreeRTOS 内存 **`pvPortMalloc` / `vPortFree`**(见 `BS_ocpp_ctrl.h``OCPP_MALLOC`)、平台任务 **`plat_comm`**(TCP/4G、充电订单接口)、计量/F **`meter_calculate`**、Flash 配置等。 |
---
## 2. 目录文件一览与职责
| 文件名 | 类型 | 职责摘要 |
|--------|------|----------|
| **BS_ocpp_ctrl.h** | 头文件 | OCPP 运行时控制结构体、定时器枚举、充电侧标志位枚举;对外 API:`v_ocpp_init_data``v_ocpp_tcp_connect``v_ocpp_logo_on``v_ocpp_recv_data``v_ocpp_send_ctrl``v_ocpp_private_logic``recv_mags` 等。 |
| **BS_ocpp_ctrl.c** | 实现 | **核心控制器**WebSocket 收发衔接、`send_mag` 统一发包、`ocpp_send_mag_ctrl` 周期/事件触发(Boot、Heartbeat、Status、MeterValues、Start/Stop、Authorize);平台下行解析(RemoteStart/Stop、ChangeConfiguration、GetConfiguration、Reset、Unlock、UpdateFirmware、SendLocalList、GetDiagnostics 等);**离线/未结算订单** `s8_unsettled_order_chaek`;各 Action 的 **CallResult** 处理函数(`*_res` / `*_unpack`)。**离线周期 MeterValues 的落盘与补发状态机**委托 **`BS_ocpp_mv_offline_bridge`****补发单条 PDU** 由 **`s32_ocpp_send_meter_values_replay`** 调 **`MeterValues_mag_offline_replay`**(实现位于桥接模块)。源码内用 **`#if 1 // …`** 做大段分区(见 **§4.0**)。 |
| **BS_ocpp_mv_offline_bridge.h** | 头文件 | **`BS_OCPP_EN`** 内)声明 **`MeterValues_mag_offline_replay`**、`v_ocpp_mv_offline_bridge_private_logic``v_ocpp_mv_offline_unsettled_abort``u8_ocpp_mv_offline_unsettled_step`;依赖 Flash 侧 **`ocpp_mv_offline_flash_impl.h`** 与 **`bs_ocpp_json_data.h`**。 |
| **BS_ocpp_mv_offline_bridge.c** | 实现 | **离线 MeterValues 桥接**:TCP 未连接或已连接未登录时按周期把测量快照写入 SPI Flash 池(与 **`u8_get_ocpp_measurand_data`** 口径对齐);FRAM 映射键为 **订单开始充电时间**经 **`xDate2Seconds`** 的 32 位秒值 + 枪号;联网成功且未结单已取得平台 **transactionId** 后,在 **StopTransaction** 前按序 **`u8_ocpp_mv_offline_unsettled_step`** 补发;**`MeterValues_mag_offline_replay`** 按 **`u8_MeterValuesMeasurandNeed`** 从快照填 **SampledValue****`j_get_ocpp_json(OCPP_MeterValues, …)`**。**`v_ocpp_mv_offline_init`** 在 **`v_ocpp_init_data`** 中调用。详情见桥接文件头注释与 **`flash_file_mgr/ocpp_mv_offline_flash_impl.*`**。 |
| **bs_ocpp_json_data.h** | 头文件 | **OCPP_CMD** 枚举(桩→平台 / 平台→桩)、各 PDU 的 **Request/Response** 结构体、`GetConfiguration`/`MeterValues` 等缓冲区长度宏;供组帧与解析共用。 |
| **bs_ocpp_json_data.c** | 实现 | **字符串常量表**:命令名、`StatusNotification` 的 errorCode/statusCode、`StopTransaction.reason`、RemoteStart 应答等;**发送侧**`BootNotification_json``StartTransaction_json``MeterValues_json`**cJSON 组帧****接收侧**`get_ocpp_data` / `ocpp_*_unpack` 解析平台下行 payload**通用**`j_get_ocpp_json``s_get_ocpp_cmd_str`、枚举转字符串等。 |
| **bs_ocpp_str.h** | 头文件 | OCPP 协议中出现的 **字面量**measurand、context、unit、`Accepted`/`Blocked`、以及标准 **Configuration Key** 名称宏 `KEY_*`),避免魔法字符串散落。 |
| **bs_ocpp_configuration.h** | 头文件 | **连接器相位**枚举、**MeterValues 测量项**枚举 `OCPP_MEASURAND_ID`(顺序固定,与配置数组下标绑定);Flash 配置结构 **`S_OCPP_FLASH_CFG_DATA`** / **`OCPP_CONFIG`** 声明;测量项英文串表 **`OCPP_measurand_str`** 外部声明。 |
| **bs_ocpp_configuration.c** | 实现 | **`s_ocpp_flash_cfg_data`**:默认与 Flash 持久化的 OCPP 配置(Heartbeat、MeterValues 采样项、`SupportedFeatureProfiles` 等);**GetConfiguration** / **ChangeConfiguration** 读写逻辑;与 **`spi_Flash`/`flash_external_data`** 协同落地配置;测量项字符串数组 **`OCPP_measurand_str`** 定义。 |
| **bs_websocket.h** | 头文件 | WebSocket **帧类型**(文本/PING/PONG/断开);**`ws_send`**、**`websocket_build`**、**`ocpp_recv`** 声明;可选 **`OCPP_WS_HTTP_AUTHORIZATION_EN`**HTTP Basic)。 |
| **bs_websocket.c** | 实现 | **RFC6455** 握手(含 Sec-WebSocket-Accept、可选 Basic)、帧封装/解析;与 **`bs_connect_impl`** URL/TCP 衔接;收包后递交给 **`recv_mags`**(见 `BS_ocpp_ctrl.c`)。 |
---
## 3. 核心数据结构(`BS_ocpp_ctrl.h`
### 3.1 `OCPP_SEND_TIMER`
周期性上报节拍:**BootNotification**、**Heartbeat**、各枪 **StatusNotification**、**MeterValues**、**StopTransaction** 定时、**Authorize** 定时等。具体周期值在运行时由配置或初始化写入 **`s_timerCnt[]`**。
### 3.2 `OCPP_CHARGE_FLAG`
与充电流程协同的软件标志(落在 **`plat_comm_task`** 的枪数据中,通过 **`u8_charge_flag_ctrl`** 访问),例如:
- **`OCPP_STATE_CHANGE_FLAG`**:连接器状态变化需上报;
- **`OCPP_TRADE_UPLOAD_FLAG`**:订单结束待上送 **StopTransaction**
- **`OCPP_START_RESULT_FLAG`** / **`OCPP_STOP_RESULT_FLAG`**:启动/停止结果触发;
- **`OCPP_VIN_START_FLAG`**:即插即充等场景的鉴权请求节拍。
### 3.3 `OCPP_DATA_CTRL`
OCPP 私有全局状态(指针 **`private_ocpp_data`**,初始化见 **`v_ocpp_init_data`**),主要包括:
- **登录与链路**`ulong_timer`、超时重连等;
- **离线/未结订单**`u8_unsettled_order_flag``s_unsettled_order_data`(动态分配的 **`S_LOG_DATA`**);补单流水线 **`u8_unsettled_auth_phase`**`OCPP_UNSETTLED_AUTH_PHASE_E`:先 **Authorize****StartTransaction**)、**`u32_unsettled_last_auth_s` / `u32_unsettled_last_stx_s`****Authorize** 节流;**StartTransaction** 在本单鉴权通过后仅发一次,用非零时间戳标记已发,避免平台多笔 transaction);
- **每枪控制**`s_ctrl_flag[]`MeterValues 召测、Trigger、鉴权计数等);
- **定时器数组**`s_timerCnt[OCPP_TIMERCNT_NUM]`
- **异步应答队列**`ack_ctrl[]`(平台多条指令并发时的应答上下文)。
### 3.4 `OCPP_DATA_SAVE`
待发 CALL 与应答匹配的 **`messageId`/`key`** 缓存(`save_json_cmd` / `get_json_cmd`),用于解析 **`CALLRESULT`** / **`CALLERROR`** 时找到对应枪号与命令类型。
---
## 4. `BS_ocpp_ctrl.c` 功能分层说明
### 4.0 源码分区(`#if 1` 标记)
为方便在单文件内浏览,`BS_ocpp_ctrl.c` 用大段 **`#if 1 // 注释`** 划分逻辑归属(**非**编译裁剪,恒为 1):
| 分区注释 | 主要内容 |
|----------|----------|
| **`//公共`** | 随机数、待发 **messageId** 队列(`save_json_cmd` / `get_json_cmd`)、发送节拍累加(`v_ocpp_timer_add``u8_ocpp_timer_ctrl`)、**`v_ocpp_timestamp_to_str`**、应答块分配/释放、**`parse_ftp_url`** 与 FTP URL 自测、时间差工具函数;**供 FTP / 其它模块写 OCPP 侧“待发 Notify 状态”** 的 **`v_ocpp_set_diag_upload_notify_pending``v_ocpp_set_fw_notify_pending``v_ocpp_set_fw_reboot_after_installed`**(写入 **`s_bs_task_ctrl.ocpp_data`**,与 **`DiagnosticsStatusNotification_mag` / `FirmwareStatusNotification_mag`**、**`send_mag`** 配合)。**不再**在控制器内保留已迁出的 **unix 秒→ISO8601** 辅助(补发时间戳在桥接文件内实现)。 |
| **`//数据获取`** | 枪/订单标志 **`u8_charge_flag_ctrl`**、桩状态、重启与交易上送标记、**`u8_get_ocpp_measurand_data`**、**`u8_get_ocpp_transaction_data`**、**GetConfiguration** 键填充、**StatusNotification** 变化检测与 Faulted 判定等。 |
| **`//发送`** | 各 **`BootNotification_mag``*_mag`** 组 **`cJSON` body**(含 **MeterValues** 在线路径)、**GetConfiguration** 应答体、**`DiagnosticsStatusNotification_mag``FirmwareStatusNotification_mag`**、**`send_mag`**、**`s32_ocpp_send_meter_values_replay`**(**离线快照→JSON** 调用桥接层 **`MeterValues_mag_offline_replay`**)、**`ocpp_send_mag_ctrl`**;部分静态函数名含 **`_res`** 实为 **对外发送的 CallResult payload 组装**,仍属发送段。 |
| **`//接收`** | 平台下行 **CALL** 解析(**`*_mag` unpack**、`ocpp_analysis_data`)、**CALLRESULT** 分发(**`*_res`**)、**`recv_mags`**。 |
| **`未结订单与充电鉴权`** | **`u8_ocpp_is_pre_trade_no_pending`**、未结内存释放 **`v_safe_free_unsettled_order_data`**(内含 **`v_ocpp_mv_offline_unsettled_abort`**)、**`u8_unsettled_pretrade_pipeline`**、**`s8_unsettled_order_chaek`**、充电中鉴权检查 **`v_ocpp_private_charge_authentication_cheak`**。 |
| **`//外部控制使用`** | **`v_ocpp_init_data` / `v_ocpp_init_data_ex`**(含 **`v_ocpp_mv_offline_init`**)、RX 超时与重连门槛 **`u8_ocpp_block_rx_timeout_relink`**、**`v_ocpp_tcp_connect``v_ocpp_logo_on``v_ocpp_recv_data``v_ocpp_send_ctrl``v_ocpp_private_logic`**。 |
**内层** **`MeterValues_mag`** 中另有 **`#if 1 //根据枚举 MeterValues 测量项…`**,仅区分「按配置枚举上送」与「简化固定字段」两套实现,不单独对应上表大段。
### 4.1 发送路径
1. 业务或定时器置位标志 → **`ocpp_send_mag_ctrl()`** 轮询;
2. **`send_mag(gunNo, cmd, ack_data, type)`**
- 构造 **[2, messageId, Action, payload]**(主动)或 **[3, …]**(应答);
- `switch(cmd)` 调用各 **`*_mag`** 生成 **`cJSON` body**(实际多在 **`BS_ocpp_ctrl.c`** 内静态函数,如 `BootNotification_mag``StartTransaction_mag`,内部再调 **`bs_ocpp_json_data.c`** 的 json 辅助);**在线 MeterValues** 走 **`MeterValues_mag`**;**离线 Flash 中缓存的一条快照补发** 走 **`s32_ocpp_send_meter_values_replay`**,其 body 由 **`MeterValues_mag_offline_replay`****`BS_ocpp_mv_offline_bridge.c`**)生成,与在线共用 **`j_get_ocpp_json`** 与 **`u8_MeterValuesMeasurandNeed`**
- 文本输出经 **`ws_send`** 发到 CSMS。
**`type` 参数**:同一 Action 不同数据源时使用,例如 **StartTransaction / StopTransaction / Authorize****实时枪数据(0****未结算内存订单 `s_unsettled_order_data`1**
### 4.2 接收路径
1. **`ocpp_recv()`**`bs_websocket.c`)收到文本帧 → **`recv_mags()`**
2. 解析 JSON:区分 **CALL / CALLRESULT / CALLERROR**
3. **CALL**:根据 **Action** 字符串映射 **`OCPP_CMD`** → **`unpack`** 填充 **`OCPP_DATA_RECV_ACK`**,必要时 **`send_mag` 返回 CallResult**
4. **CALLRESULT****`get_json_cmd`** 匹配 messageId → 调用对应 **`StartTransaction_res``Authorize_res`** 等,更新本地订单/流水号/鉴权状态。
### 4.3 典型已实现的平台指令(下行)
包括但不限于:**RemoteStartTransaction**、**RemoteStopTransaction**、**UnlockConnector**、**Reset**、**ChangeAvailability**、**ChangeConfiguration**、**GetConfiguration**、**TriggerMessage**、**SendLocalList**、**GetLocalListVersion**、**ClearCache**、**UpdateFirmware**、**GetDiagnostics**(工程内可能对 diagnostics 使用自定义 Action 名,以源码为准)。
### 4.4 离线 / 未结算订单(概要)
- **`s8_unsettled_order_chaek()`**:联网且登录后周期性调用;从 **`s8_plat_get_unsettled_order`****`bs_public_impl.c`**)拉取 **Flash 未结列表**或 **EEPROM 掉电临时订单**,挂到 **`s_unsettled_order_data`**(一次一单)。
- **已有平台流水号****`u8_preTradeNo`** 有效:非空且非 **`0xFF`** 占位,判定见 **`u8_ocpp_is_pre_trade_no_pending`**):直接 **`StopTransaction(type=1)`** 上送结束侧订单信息(meterStop、timestamp、reason、transactionData 等),随后释放内存上下文。
- **尚无流水号**(空或 **`0xFF`** 占位):**`Authorize(type=1)`**idTag 取订单 **`u8_user_id`**)→ 仅 **`idTagInfo.status == Accepted`** 后 **`StartTransaction(type=1)`** 申请 **transactionId****`StartTransaction_res`** 在桩空闲且未结副本存在时把 **transactionId** 写入 **`u8_preTradeNo`** → 下一周期走 **StopTransaction(type=1)**。**补充**:若本笔在离线阶段已通过 **`BS_ocpp_mv_offline_bridge`** 将周期 **MeterValues** 写入 Flash,则在 **已取得平台 `transactionId`** 后、发 **StopTransaction** 前,由 **`u8_ocpp_mv_offline_unsettled_step`** 按序号逐条补发(帧格式与在线 **`send_mag`/`MeterValues`** 一致),补完或映射无效后再结束事务。
- **拒绝与单次 StartTransaction****StartTransaction.conf** 对 Blocked/Invalid 等未结补单**立即** **`v_safe_free_unsettled_order_data`**(不重试 **StartTransaction**,避免平台侧多笔订单);**Authorize** 非 Accepted 同样丢弃内存。**Authorize** 带秒级节流(**`u32_unsettled_last_auth_s`**);鉴权通过后 **StartTransaction** 仅尝试发送一次(成功发出后 **`u32_unsettled_last_stx_s` 非零** 即不再发;链路 **`ws_send` 失败** 未置位时可下一周期再试一次发送)。
**详细分场景(Flash / EEPROM、与平台报文顺序)见第 10~12 节。**
详细边界条件见代码注释及同类需求文档。
---
## 5. `bs_ocpp_json_data.c` 功能分层说明
| 类别 | 内容 |
|------|------|
| **命令表** | `ocpp_cmd_str[][]`**`OCPP_CMD`** 枚举对齐,用于日志与解析。 |
| **枚举串** | errorCode、status、reason、UnlockConnector 状态等,供 **`s_get_ocpp_enum_data_str`** 使用。 |
| **桩 → 平台** | `*_json()`:把结构体填进 **cJSON**。 |
| **平台 → 桩** | `ocpp_*_unpack()`:从 **cJSON** 填结构体,供业务判断。 |
| **入口封装** | **`j_get_ocpp_json`**:按 cmd 选择对应 json 构造函数;**`get_ocpp_data`**:解析 CallResult。 |
---
## 6. `bs_ocpp_configuration.c` 功能说明
- 维护全局 **`s_ocpp_flash_cfg_data`**(含 **`OCPP_CONFIG`**):对标 **OCPP 标准配置键**,如 HeartbeatInterval、MeterValuesSampledData、AuthorizeRemoteTxRequests、StopTransactionOnInvalidId 等;
- **ChangeConfiguration**:校验、写入 RAM + Flash;部分键需解析 CSV(测量项列表)、相位字符串等;
- **GetConfiguration**:按 key 列表返回 **readonly/value**;未知 key 记入 **unknownKey**
- **`OCPP_measurand_str[]`**:与 **`OCPP_MEASURAND_ID`** 一一对应,供 MeterValues/StopTxn 数据项映射。
---
## 7. `bs_websocket.c` 功能说明
- 完成 **TLS/TCP 之上的 WebSocket** 字节流处理(依工程 **`websocket_build`** 与连接模块集成方式而定);
- **握手**HTTP Upgrade → Sec-WebSocket-Accept(内置 SHA1/Base64 相关逻辑);
- **心跳**:处理 **Ping/Pong**,业务层亦可配合应用层 Heartbeat;
- **数据**:文本帧交给 **`recv_mags`**。
---
## 8. `bs_ocpp_str.h` 功能说明
- **协议常量**measurand、location、unit、`Sample.Periodic``Transaction.Begin/End` 等;
- **授权/配置结果**`Accepted``Blocked``Invalid` 等;
- **配置键名**`KEY_HEARTBEAT_INTERVAL``KEY_METER_VALUES_SAMPLED_DATA` 等(与 **`bs_ocpp_configuration`** 协同)。
避免与 **`bs_ocpp_json_data.h`** 中结构体字段混淆:**str.h** 侧重 **字面量与 Key 名****json_data.h** 侧重 **PDU 结构体与枚举**
---
## 9. 与其它目录的典型调用关系(示意)
```mermaid
flowchart LR
subgraph plat_comm_task
T[v_ocpp_send_ctrl / v_ocpp_recv_data]
end
subgraph ocpp
C[BS_ocpp_ctrl.c]
M[BS_ocpp_mv_offline_bridge.c]
J[bs_ocpp_json_data.c]
W[bs_websocket.c]
CFG[bs_ocpp_configuration.c]
end
subgraph impl
P[bs_public_impl.c]
end
subgraph flash
F[ocpp_mv_offline_flash_impl]
end
T --> C
C --> M
C --> J
C --> W
C --> CFG
C --> P
M --> F
M --> J
```
- **`plat_comm_task`**:周期性调用 **`v_ocpp_private_logic`** / **`v_ocpp_send_ctrl`**,驱动 **`ocpp_send_mag_ctrl`**;未联网或未登录分支末尾可调用 **`v_ocpp_mv_offline_bridge_private_logic`** 做离线 MV 周期采样落盘;
- **`bs_public_impl`**:枪状态、订单 **`S_LOG_DATA`**、未结算订单 **`s8_plat_get_unsettled_order`**、结算确认等;**`v_ftp_update_action`** 在 4G 下根据 **`u8_log_file_upload` / `u8_updata_flag`** 调度 **`ec200a_4g_ftp.c`** 中日志或固件 FTP 状态机;
- **`meter_calculate`**:订单写 Flash、EEPROM 临时订单、未结算索引链表(见 **`unsettled_order_mng`**)。
### 9.1 GetDiagnostics、4G FTP 与 PlatComm 调度(与 `4g_module` 衔接)
**GetDiagnostics** 由 **`BS_ocpp_ctrl.c`** 解析平台下发的 **location**FTP URL、账号等),置 **`s_bs_task_ctrl.ocpp_data.u8_log_file_upload`**,并在 **`GetDiagnostics_res`** 中准备文件名、调用 **`v_ftp_ctrl_init()`** 复位 FTP 步进。**须保证「先发出 CallResult / Uploading,再置 `u8_updata_flag`」**,否则与 **`EC200A_BLOCK_QISEND_DURING_4G_FTP`**、**`u8_bs_ftp_ocpp_strict_mux`** 组合时可能挡掉应答(详见 **`4g_module/4G模块AT指令配置功能说明.md`** 第四节)。
**4G + 非透传** 时,日志上传在 **`ec200a_4g_ftp.c`** 中已改为:**`QFOPEN` + `QFSEEK` + `QFWRITE`** 按 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`(默认 4096** 分包写模组 UFS**`QFWRITE` 在单周期内仅短等轮询**,未完成则下一 **FTP 半周期** 续传。**`plat_comm_task.c`** 在 FTP 活跃时对 4G 采用 **相邻 PlatComm 周期「仅联网 / 仅 FTP 一步」交替**,与上述分包叠加后,**FTP 传输期间 OCPP 仍可周期性完成 `QISEND`/`QIRD` 与 WebSocket 业务**。
**`v_ocpp_private_logic`**:不得在 **`u8_updata_flag==0`** 时无条件清除 **`u8_log_file_upload`**,否则会误入固件升级分支(日志出现 **`[FTP-UPG]`** 等异常);该约束与 **`4g_module/4G模块AT指令配置功能说明.md`** 第三节一致。
配置与排障(**`EC200A_FTP_*`**、**`EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS`** 等)以 **`ec200a_4g_cfg.h`** 与 **`4G模块AT指令配置功能说明.md`** 为准。
---
## 10. 离线未结算订单处理流程(Flash 列表)
本节描述:**充电正常结束或结算链路中断后,订单已写入 Flash,且索引已进入 EEPROM「未结算列表」**,待 CSMS 上线后通过 OCPP 补单:**本地尚无有效 transactionId(空或 0xFF 占位)时,先 Authorize(idTag),再 StartTransaction 获取流水号并写入 `u8_preTradeNo`,再 StopTransaction 上送订单结束信息****若 `u8_preTradeNo` 已为有效流水号,则直接 StopTransaction**,不再重复鉴权与 StartTransaction。
### 10.1 数据从哪里来(产生侧)
| 环节 | 位置 | 说明 |
|------|------|------|
| 订单落盘 | `meter_calculate_impl.c`**`v_meterlog_flash_save_log(1, gunNo)`** | 订单结束时 **`u32_chg_order_save_record`** 写 Flash,得到有效 **`orderIndex`u32_index**。 |
| 进入未结队列 | 同上 | **`u8_add_unsettled_order(orderIndex)`**`BSP/flash_file_mgr/unsettled_order_mng.c`)把该索引写入 **EEPROM 未结算管理区**,表示「尚未收到平台对本单的结算确认」。 |
| 典型字段 | **`S_LOG_DATA`** | 含 **`u8_user_id`**、起止电量与时间、**`u8_preTradeNo`**(从未拿到 **transactionId** 时可能为空;离线刷卡等场景可能为 **`0xFF`** 首字节占位,**`u8_ocpp_is_pre_trade_no_pending`** 视为须先 **Authorize + StartTransaction**)、**`u8_gunNo`** 等。 |
充电过程中周期性刷新 EEPROM 临时区的逻辑见 **第 11 节**;正常完整结束订单后,会 **清除该枪 EEPROM 临时记录**`v_clear_temp_chg_record_in_eeprom`),并依赖 **Flash 未结列表** 做后续上送。
### 10.2 读单出口(`bs_public_impl.c`
**`S8_T s8_plat_get_unsettled_order(S_LOG_DATA *s_log, U8_T u8_type)`** 在 **已处理完掉电临时单** 之后,走 Flash 未结逻辑:
1. **`u16_get_unsettled_order_count()`** 为 0 则直接返回 0(无单)。
2. **`u8_type == 1`** 时只返回数量,不读内容。
3. 否则 **`v_get_unsettled_order_indexes`** 取 **队首** 索引 **`currentIndex`**。
4. **`v_read_chg_order_record(s_log, currentIndex)`** 从 Flash 读出完整 **`S_LOG_DATA`**。
5. **`u8_platform_settlement_confirm(currentIndex)`**:按当前实现,在 **判断 readResult 之前** 即从未结列表中移除该索引(若 Flash 读失败,存在「索引已删、数据未读出」风险,属实现细节,维护时需留意)。
返回值:**>0** 表示仍有未结条数(含本条);**0** 无单;**-1** 读单失败。
### 10.3 OCPP 何时启动补单(`BS_ocpp_ctrl.c`
前置条件(**`v_ocpp_private_logic`** 内 **已联网且已登录 CSMS**):
- **`u8_unsettled_order_flag == 0`**(本开机周期尚未标记「未结检查完毕」);
- **`ulong_timer - u32_timer > 10`**(与注释「约 30 秒」对应的节流,实际以 `ulong_timer` 单位为准),更新 **`u32_timer`** 后调用 **`s8_unsettled_order_chaek()`**。
断网或未登录时 **`u8_unsettled_order_flag` 被置 0**,不会跑未结上送;避免无链路发 OCPP。
### 10.4 OCPP 状态机:`s8_unsettled_order_chaek`
内存载体:**`private_ocpp_data->s_unsettled_order_data`**(堆上 **`S_LOG_DATA`**,一次只处理一单)。分支判定统一使用 **`u8_ocpp_is_pre_trade_no_pending(u8_preTradeNo)`**(空串或首字节 **`0xFF`** 视为「尚无有效流水号」)。
**首次取单**`s_unsettled_order_data == NULL`):
1. **`OCPP_MALLOC`** + **`memset`****`u8_unsettled_auth_phase = NONE`****`u32_unsettled_last_auth_s` / `u32_unsettled_last_stx_s` 清零**
2. **`s8_plat_get_unsettled_order(..., 0)`** 填入内容;
3. **`u8_user_id` 为空** → **`v_safe_free_unsettled_order_data()`**,返回;
4. **尚无流水号**`u8_ocpp_is_pre_trade_no_pending == 1`):调用 **`u8_unsettled_pretrade_pipeline`**
- **`OCPP_UNSETTLED_AUTH_NONE`**:节流后 **`send_mag(gunIdx, OCPP_Authorize, NULL, 1)`****`Authorize_mag`** 从 **`s_unsettled_order_data->u8_user_id`** 取 idTag;成功后 **`u8_unsettled_auth_phase = WAIT`**
- **`WAIT`**:等待 **`Authorize_res`**;仅 **Accepted** 时置 **`OK`**,否则释放订单;
- **`OK`**:本单在 **`u32_unsettled_last_stx_s == 0`** 时仅 **`send_mag(0, OCPP_StartTransaction, NULL, 1)` 一次**(成功后置非零时间戳,避免重复 **StartTransaction** 导致平台多笔订单);**`StartTransaction_mag`** 用未结副本组 **StartTransaction.req****`connectorId = u8_gunNo+1`**、历史 **meterStart / timestamp / idTag**)。
5. **已有流水号**`pending == 0`):**`send_mag(0, OCPP_StopTransaction, NULL, 1)`** → **`v_safe_free_unsettled_order_data()`**。
**同一单未释放时**(内存中仍持有上一笔未结单):
- 仍按 **步骤 4 / 5** 区分;**`u8_cnt`(静态)** 仅用于 **已有流水号****StopTransaction** 重试计数;无流水号路径由 **`u8_unsettled_pretrade_pipeline`** 管理(**StartTransaction** 每单最多成功发出一次)。
### 10.5 平台应答:Authorize 与 StartTransaction
- **`Authorize_res`****`u8_unsettled_auth_phase == WAIT`**):仅 **`idTagInfo.status == Accepted`** 时置 **`OCPP_UNSETTLED_AUTH_OK`**,否则 **`v_safe_free_unsettled_order_data()`**。解析失败时 **`WAIT` 回退为 `NONE`** 以便重试鉴权。
- **`StartTransaction_res`**:若 **`idTagInfo.status`** 为 **Blocked / Invalid / Expired / ConcurrentTx**,未结补单路径**不得**把 **`transactionId`** 写入 **`preTradeNo`**,并**立即**释放内存(**不重试 StartTransaction**)。
**桩空闲****`u8_get_pile_state(gunNo)==0`**)且 **`s_unsettled_order_data` 非空** 且非上述拒绝态:将 **`data.transactionId`** 写入 **`u8_preTradeNo`****`u8_unsettled_auth_phase = NONE`**,下一周期上送 **StopTransaction**
注意:**StopTransaction** 主动发包 **`gunNo` 常为 0**,但 PDU 内 **connectorId / 计量** 依赖 **`s_unsettled_order_data->u8_gunNo`****Authorize** 主动发包使用 **订单枪号 `gunIdx`**,以便 **CALLRESULT****`ack_data->gunNo`** 一致。
### 10.6 StopTransactiontype=1
**`StopTransaction_mag(..., type==1)`** 使用 **`s_unsettled_order_data`****transactionId**、**idTag**、结束时间、**meterStop**、**reason**(内部故障码映射到 **`StopTransactionRequest.reason`**,如 Local / PowerLoss 等)。
---
## 11. 掉电(异常断电)订单处理流程(EEPROM 临时记录)
本节描述:**充电过程中异常断电**,来不及走完整「订单结束写 Flash + 未结索引」路径时,依赖 **每枪独立 EEPROM 槽位** 保存的 **最后一次充电快照**,上电后优先恢复并走 **同一套 OCPP 补单**
### 11.1 数据何时写入 EEPROM(充电中刷新)
| 调用 | 说明 |
|------|------|
| **`v_meterlog_flash_save_log(0, gunNo)`** | **订单未结束**,周期性把当前 **`s_metering_ctrl.s_gun_log[gunNo]`** 写入 EEPROM **`v_save_temp_chg_record_to_eeprom(..., TEMP_CHG_FLAG_UNSETTLED)`**`meter_calculate_flash_impl.c`)。 |
每枪地址互不干扰(如枪 A / 枪 B 不同 EEPROM 基址)。
### 11.2 正常结束订单时对 EEPROM 的处理
**`v_meterlog_flash_save_log(1, gunNo)`**(订单结束分支):写 Flash 订单、**加入未结索引链** 后,执行 **`v_clear_temp_chg_record_in_eeprom(gunNo)`**,避免「已正规入库」仍被当成掉电单重复上传。
### 11.3 上电读单优先级(`s8_plat_get_unsettled_order` 前半段)
**`gunNo = 0 .. GUN_MAX_CNT-1`** 依次:
1. **`v_check_temp_chg_record_valid(gunNo)`**:标志为 **`TEMP_CHG_FLAG_UNSETTLED` 或 SETTLED 类有效值** 则认为有条目;
2. **`v_read_temp_chg_record_from_eeprom(gunNo, s_log, NULL)`** 读出 **`S_LOG_DATA`**
3. 强制 **`s_log->e_stop_reason[0] = E_FAULT_13073`**(异常掉电),用于 **StopTransaction** 映射为 **`PowerLoss`** 等;
4. **`v_clear_temp_chg_record_in_eeprom(gunNo)`**:读完即清标志(下次不再重复读);
5. **return 1**,表示有一笔待 OCPP 处理的未结算数据。
因此:**掉电单总是先于 Flash 未结队列被取出**;若 EEPROM 与 Flash 同时存在数据,**优先消耗掉电副本**。
### 11.4 与 OCPP 的衔接
掉电单读出后 **`S_LOG_DATA`** 同样挂在 **`s_unsettled_order_data`** 上,后续与 **第 10.410.6 节** 完全一致:
- **无有效流水号****Authorize(type=1)****Accepted****StartTransaction(type=1)** → 应答写入 **`preTradeNo`** → **StopTransaction(type=1)**
- **读单时已有流水号**(少见)→ 直接 **StopTransaction(type=1)**
- 本实现 **StopTransaction** 发送后即 **`v_safe_free_unsettled_order_data()`**(与 **CALLRESULT** 无硬耦合)。
### 11.5 流程示意图(合并视角)
```mermaid
sequenceDiagram
participant MC as meter_calculate
participant EEPROM as EEPROM临时区
participant Flash as Flash订单+未结索引
participant IMPL as s8_plat_get_unsettled_order
participant OCPP as s8_unsettled_order_chaek
Note over MC,EEPROM: 充电中周期
MC->>EEPROM: v_save_temp_chg_record(UNSETTLED)
Note over MC,Flash: 正常结束
MC->>Flash: 写订单 + u8_add_unsettled_order
MC->>EEPROM: clear 临时记录
Note over EEPROM,OCPP: 上电/联网后
EEPROM-->>IMPL: 若有有效临时记录优先读出并 clear
Flash-->>IMPL: 否则读未结队列首条
IMPL->>OCPP: 填充 s_unsettled_order_data
OCPP->>OCPP: 无txId→Auth(1)→StartTx(1)→写preTradeNo→StopTx(1)
```
---
## 12. 两类订单的差异小结与维护注意
| 对比项 | Flash 离线未结订单 | EEPROM 掉电临时订单 |
|--------|-------------------|---------------------|
| **典型成因** | 已写完订单但 CSMS 未确认结算 / **StopTransaction** 未成功 | 充电中断电,仅有最后一次周期快照 |
| **持久化位置** | Flash 订单文件 + EEPROM **未结索引列表** | EEPROM **按枪临时块** |
| **读取顺序** | 在掉电分支 **之后** | **优先**for 循环枪号顺序) |
| **停止原因** | 以 **`S_LOG_DATA` 内原有故障码** 映射 | 读出后强制 **`E_FAULT_13073`**(映射 **PowerLoss** |
| **OCPP 后续** | 相同:**Authorize(1) → StartTx(1) → 填 preTradeNo → StopTx(1)**(有流水号则仅 **StopTx(1)** | 相同 |
**实现层面已知耦合点**(便于排查问题):
1. **未结补单****`StopTransaction` / `StartTransaction`** 常用 **`send_mag(0, …)`**PDU 内 **connectorId / 计量** 依赖 **`s_unsettled_order_data->u8_gunNo`****`Authorize(type=1)`** 使用 **`send_mag(gunIdx, …)`**(订单枪号),以便 **CALLRESULT****`ack_data->gunNo`** 一致。需确认 sampled 数据是否混枪。
2. **Flash 读单**后立即 **`u8_platform_settlement_confirm`**:若 Flash 读失败,可能导致索引已删。
3. **`StartTransaction_res`** 未结算分支已对 **Blocked/Invalid 等** 拒绝 **`preTradeNo`** 写入;**Authorize** 非 **Accepted** 会丢弃内存上下文。
4. **`StopTransaction` 成功后立即 `free`**,与 **`StopTransaction_res`** 无联动;若需严格「应答后再删单」,需架构层增强。
---
## 13. 维护建议
1. **新增 OCPP Action**:在 **`OCPP_CMD`**、**`ocpp_cmd_str`**、**`send_mag` switch**、**接收分发**、**json 组帧/解析** 五处同步扩展。
2. **新增 Configuration Key**:在 **`bs_ocpp_str.h`**、`OCPP_CONFIG` 结构体、**Get/Change** 分支中一并添加,并处理 Flash 默认值与长度。
3. **测量项顺序****`OCPP_MEASURAND_ID` 顺序不可随意插入**,否则配置位图与 **`OCPP_measurand_str`** 错位。
4. **GetDiagnostics / 4G 日志 FTP**:与 **`plat_comm` / `4g_module`** 强相关;**CallResult 与 `u8_updata_flag` 顺序**、**`u8_log_file_upload` 勿误清**、**PlatComm 联网与 FTP 半周期交替**、**`QFWRITE`/`QFREAD` 分包** 等见 **第 9.1 节****`app/plat_comm/4g_module/4G模块AT指令配置功能说明.md`**。
5. **补单链路变更**:同步核对 **第 1012 节****`bs_public_impl.c` / `meter_calculate_impl.c` / `unsettled_order_mng`** 的一致性。
6. **离线 MeterValues**:改动落盘键(**StartChargeTime** / 枪号)、FRAM 映射、快照字段或与在线 measurand 口径时,需同时核对 **`BS_ocpp_mv_offline_bridge.c`**、**`ocpp_mv_offline_flash_impl.*`** 与 **`u8_get_ocpp_measurand_data`**;释放未结单内存前须 **`v_ocpp_mv_offline_unsettled_abort`**(见 **`v_safe_free_unsettled_order_data`**)。
---
*文档版本:2026-05 起补充 **离线 MV 桥接****`BS_ocpp_ctrl.c` 分区说明**;与当前 CCU601E_D `app/plat_comm/ocpp` 目录一致,若后续增减文件请同步更新第二节表格。*
File diff suppressed because it is too large Load Diff
+395
View File
@@ -0,0 +1,395 @@
#ifndef _BS_OCPP_CONFIG_H_
#define _BS_OCPP_CONFIG_H_
#include "bs_ocpp_ctrl.h"
#if BS_OCPP_EN //配置参数 分可选、必选
//枚举 每个连接器相对于其电表(若无电表则相对于电网连接)的相序
typedef enum
{
ENUM_NotApplicable, // 不适用(单相交流/直流充电桩,非三相场景)
ENUM_Unknown, // 未知(设备未检测到相序,需人工排查接线)
ENUM_RST, // 标准参考相序(L1-R、L2-S、L3-T,默认推荐配置)
ENUM_RTS, // 反向参考相序(L1-R、L2-T、L3-S,相序反转需修正)
ENUM_SRT, // 反向240度旋转(L1-S、L2-R、L3-T,特殊场景接线)
ENUM_STR, // 标准120度旋转(L1-S、L2-T、L3-R,特殊场景接线)
ENUM_TRS, // 标准240度旋转(L1-T、L2-R、L3-S,特殊场景接线)
ENUM_TSR // 反向120度旋转(L1-T、L2-S、L3-R,特殊场景接线)
} OCPP_CONNECTOR_PHASE;
//枚举MeterValues测量项 顺序固定!不可变更! (定义顺序关联了配置参数结构体里相关数组的下标)
typedef enum
{
// ------------------- 能量累计值(Register- 文档7.31核心能量类 -------------------
ENUM_EnergyActiveExportRegister, // 有功电能输出累计值(Wh)- 设备向电网输出的总有功电能累计
ENUM_EnergyActiveImportRegister, // 有功电能输入累计值(Wh)- 电网向设备输入的总有功电能累计
ENUM_EnergyReactiveExportRegister, // 无功电能输出累计值(VARh)- 设备向电网输出的总无功电能累计
ENUM_EnergyReactiveImportRegister, // 无功电能输入累计值(VARh)- 电网向设备输入的总无功电能累计
// ------------------- 能量时段值(Interval- 文档7.31时段能量类 -------------------
ENUM_EnergyActiveExportInterval, // 有功电能输出时段值(Wh)- 单个时钟对齐时段内输出的有功电能
ENUM_EnergyActiveImportInterval, // 有功电能输入时段值(Wh)- 单个时钟对齐时段内输入的有功电能
ENUM_EnergyReactiveExportInterval, // 无功电能输出时段值(VARh)- 单个时钟对齐时段内输出的无功电能
ENUM_EnergyReactiveImportInterval, // 无功电能输入时段值(VARh)- 单个时钟对齐时段内输入的无功电能
// ------------------- 功率类 - 文档7.31功率计量类 -------------------
ENUM_PowerActiveExport, // 有功功率输出(W)- 设备向电网输出的实时有功功率
ENUM_PowerActiveImport, // 有功功率输入(W)- 电网向设备输入的实时有功功率
ENUM_PowerOffered, // 提供的功率(W)- 充电桩可向EV提供的最大可用有功功率
ENUM_PowerReactiveExport, // 无功功率输出(VAR)- 设备向电网输出的实时无功功率
ENUM_PowerReactiveImport, // 无功功率输入(VAR)- 电网向设备输入的实时无功功率
ENUM_PowerFactor, // 功率因数(无单位,0~1)- 实时有功功率与视在功率的比值
// ------------------- 电流类 - 文档7.31电流计量类 -------------------
ENUM_CurrentImport, // 输入电流(A)- 电网向设备输入的实时电流
ENUM_CurrentExport, // 输出电流(A)- 设备向电网输出的实时电流
ENUM_CurrentOffered, // 提供的电流(A)- 充电桩可向EV提供的最大可用电流
// ------------------- 电气参数类 - 文档7.31基础电气参数 -------------------
ENUM_Voltage, // 电压(V)- 充电桩输出端的实时线电压
ENUM_Frequency, // 电网频率(Hz)- 接入电网的实时频率(50/60Hz)
// ------------------- 物理/状态量类 - 文档7.31非电气计量项 -------------------
ENUM_Temperature, // 温度(℃)- 充电桩/EV电池的实时温度(文档标注为℃/℉,默认℃)
ENUM_SoC, // 电池剩余电量(%- EV电池的State of Charge0~100%
ENUM_RPM, // 转速(转/分钟)- 冷却风扇/泵等设备的实时转速
// 统计最大数量标志,用于定义配置参数中数组空间的大小
ENUM_measurand_Num,
} OCPP_MEASURAND_ID;
/*
//MeterValues测量项结构体
typedef struct
{
OCPP_MEASURAND_ID enum_measurandID; //枚举变量
const char* str_data; //对应字符串内容
}OCPP_MEASURAND_INFO;
*/
/*
// MeterValues测量项 结构体数组(枚举值 ↔ 标准字符串 映射表,只读不可修改。顺序对应枚举顺序,固定不可变更)
const OCPP_MEASURAND_INFO OCPP_measurand_info[] =
*/
// MeterValues测量项 串数组(标准字符串 只读不可修改。顺序对应枚举顺序,固定不可变更)
extern const char *OCPP_measurand_str[];
// 配置参数 结构体 前半部分必选 后半部分可选
typedef struct
{
// ------------------- 必选boolean类型(U8_T -------------------
U8_T u8_AuthorizeRemoteTxRequests; // 远程启动交易是否需要预授权
U8_T u8_LocalAuthorizeOffline; // 离线时是否允许本地授权启动交易
U8_T u8_LocalPreAuthorize; // 在线时是否无需等待中央系统授权即可本地启动交易
U8_T u8_StopTransactionOnEVSideDisconnect; // EV端断开时是否自动停止交易
U8_T u8_StopTransactionOnInvalidId; // 收到无效授权状态时是否停止当前交易
U8_T u8_UnlockConnectorOnEVSideDisconnect; // EV端断开时是否解锁充电点侧连接器
U8_T u8_LocalAuthListEnabled; // 是否启用本地授权列表
// ------------------- 必选integer类型(U16_T -------------------
U16_T u16_ClockAlignedDataInterval; // 时钟对齐数据间隔(秒)
U16_T u16_ConnectionTimeOut; // 准备状态超时自动取消交易时间(秒)
U16_T u16_GetConfigurationMaxKeys; // 单个GetConfiguration请求最大键数量
U16_T u16_HeartbeatInterval; // 心跳间隔(秒)
U16_T u16_MeterValueSampleInterval; // 计量数据采样间隔(秒)
U16_T u16_NumberOfConnectors; // 充电点物理连接器数量
U16_T u16_ResetRetries; // 充电点复位失败后重试次数
U16_T u16_TransactionMessageAttempts; // 交易消息提交失败后重试次数
U16_T u16_TransactionMessageRetryInterval; // 交易消息重试间隔(秒)
U16_T u16_LocalAuthListMaxLength; // 本地授权列表最大存储条目数
U16_T u16_SendLocalListMaxLength; // 单个SendLocalList请求最大发送条目数
U16_T u16_ChargeProfileMaxStackLevel; // 充电配置最大堆叠层级
U16_T u16_ChargingScheduleMaxPeriods; // 单个充电计划最大时段数
U16_T u16_MaxChargingProfilesInstalled; // 同时可安装的充电配置最大数量
// ------------------- 必选CSL类型 -------------------------------
OCPP_CONNECTOR_PHASE enum_ConnectorPhaseRotation[3]; //各枪电表的连接相序
/*合并 MeterValuesAlignedData、
MeterValuesSampledData
StopTxnAlignedData
StopTxnSampledData
MeterValues测量项在以上数据单元是否需要
bit 0 MeterValuesAlignedData是否需要
bit 1 MeterValuesSampledData是否需要
bit 2 StopTxnAlignedData是否需要
bit 3 StopTxnSampledData是否需要
*/
U8_T u8_MeterValuesMeasurandNeed[ENUM_measurand_Num];
/*支持的功能配置文件列表
bit 0 Core
bit 1 FirmwareManagement
bit 2 LocalAuthListManagement
bit 3 Reservation
bit 4 SmartCharging
bit 5 RemoteTrigger
*/
U8_T u8_SupportedFeatureProfiles;
/*充电调度支持的物理量列表
bit 0 'Current'
bit 1 'Power'
*/
U8_T ChargingScheduleAllowedChargingRateUnit;
// ------------------- 可选boolean类型(U8_T0=false,非0=true -------------------
U8_T u8_AllowOfflineTxForUnknownId; // 是否允许未知ID用户离线启动交易
U8_T u8_AuthorizationCacheEnabled; // 是否启用本地授权缓存
U8_T u8_ReserveConnectorZeroSupported; // 是否支持对0号连接器进行预约
U8_T u8_ConnectorSwitch3to1PhaseSupported; // 是否支持交易期间三相切单相充电
// ------------------- 可选integer类型(U16_T,单位见注释) -------------------
U16_T u16_BlinkRepeat; // 充电点指示灯闪烁次数
U16_T u16_ConnectorPhaseRotationMaxLength; // 相位旋转配置最大条目数(补充:可选核心项)
U16_T u16_LightIntensity; // 充电点灯光亮度(百分比)
U16_T u16_MaxEnergyOnInvalidId; // 无效ID用户最大允许充电能量(Wh)
U16_T u16_MeterValuesAlignedDataMaxLength; // 时钟对齐计量项列表最大长度(可选)
U16_T u16_MeterValuesSampledDataMaxLength; // 采样计量项列表最大长度(可选)
U16_T u16_MinimumStatusDuration; // 状态稳定后发送通知的最短持续时间(秒)
U16_T u16_StopTxnAlignedDataMaxLength; // 交易停止时对齐计量项最大长度(可选)
U16_T u16_StopTxnSampledDataMaxLength; // 交易停止时采样计量项最大长度(可选)
U16_T u16_SupportedFeatureProfilesMaxLength; // 支持功能配置文件列表最大长度(可选)
U16_T u16_WebSocketPingInterval; // WebSocket Ping间隔(秒,0表示禁用)
//------------------私有配置------------------------
char QRCodeConnector1[NUM_64];
char QRCodeConnector2[NUM_64];
} OCPP_CONFIG;
typedef struct
{
S8_T s8_ChangeAvailability[GUN_MAX_CNT]; //更改枪可用性 <0 不可用 >=0 可用 ||>0时可扩充枪预约状态
int card_ver; //卡号版本
char chargePointVendor[OCPP_CiString20]; //供应商信息 可改
}S_OCPP_CHARGE_CFG_DATA;
typedef struct
{
Comm_Time time;
OCPP_CONFIG s_ocpp_config_data; //数据内容
S_OCPP_CHARGE_CFG_DATA s_ocpp_charge_data; //充电相关数据
}S_OCPP_FLASH_CFG_DATA;
extern S_OCPP_FLASH_CFG_DATA s_ocpp_flash_cfg_data;
#endif
#if BS_OCPP_EN //OCPP可配置参数
/*
U8 timeNUm
1
2
3
4 使switch
typedef struct
{
1
2 U8_T
3\ /
4 char*;
}METERV_MAG;
const METERV_MAG met[] =
{
{1a },
{2.},
}
0 1
U8_T u8_action(type =0 get 1 set ,meiju ,char *v)
meiju eMMC_HIGH_VOLTAGE_RANGE(const char *mag )
{
}
*/
//保存unknown的Key串
extern char *unknown_tmpbuf[GetConfiguration_NUM];
//OCPP可配置参数枚举
typedef enum
{
ENUM_NULL = 0,
//Core Profile
ENUM_ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID, /* 可选 允许未知用户ID的离线交易 (布尔: 0/1) */
ENUM_AUTHORIZATION_CACHE_ENABLED, /* 可选 启用本地授权缓存 (布尔: 0/1) */
ENUM_AUTHORIZE_REMOTE_TX_REQUESTS, /* 必选 允许远程启动充电事务 (布尔: 0/1) */
ENUM_BLINK_REPEAT, /* 可选 充电桩指示灯闪烁次数 (整数: 次数) */
ENUM_CLOCK_ALIGNED_DATA_INTERVAL, /* 必选 时钟对齐的数据上报间隔 (整数: 秒) */
ENUM_CONNECTION_TIME_OUT, /* 必选 TCP连接超时时间 (整数: 秒) */
ENUM_CONNECTOR_PHASE_ROTATION, /* 必选 连接器相位旋转配置 (字符串: L1/L2/L3顺序) */
ENUM_CONNECTOR_PHASE_ROTATION_MAX_LENGTH, /* 可选 相位旋转配置最大长度 (整数: 字节数) */
ENUM_GET_CONFIGURATION_MAX_KEYS, /* 必选 GetConfiguration请求最大返回Key数 (整数: 数量) */
ENUM_HEARTBEAT_INTERVAL, /* 必选 心跳间隔 (整数: 秒) */
ENUM_LIGHT_INTENSITY, /* 可选 充电桩灯光亮度 (整数: 百分比) */
ENUM_LOCAL_AUTHORIZE_OFFLINE, /* 必选 本地离线授权模式 (布尔: 0/1) */
ENUM_LOCAL_PRE_AUTHORIZE, /* 必选 本地预授权模式 (布尔: 0/1) */
ENUM_MAX_ENERGY_ON_INVALID_ID, /* 可选 无效ID用户的最大允许充电能量 (整数: Wh) */
ENUM_METER_VALUES_ALIGNED_DATA, /* 必选 对齐数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
ENUM_METER_VALUES_ALIGNED_DATA_MAX_LENGTH, /* 可选 对齐数据列表最大长度 (整数: 字符数) */
ENUM_METER_VALUES_SAMPLED_DATA, /* 必选 采样数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
ENUM_METER_VALUES_SAMPLED_DATA_MAX_LENGTH, /* 可选 采样数据列表最大长度 (整数: 字符数) */
ENUM_METER_VALUE_SAMPLE_INTERVAL, /* 必选 采样间隔 (整数: 秒) */
ENUM_MINIMUM_STATUS_DURATION, /* 可选 状态最小持续时间 (整数: 秒) */
ENUM_NUMBER_OF_CONNECTORS, /* 必选 充电桩连接器数量 (整数: 数量) */
ENUM_RESET_RETRIES, /* 必选 复位重试次数 (整数: 次数) */
ENUM_STOP_TRANSACTION_ON_EV_SIDE_DISCONNECT, /* 必选 EV端断开时停止事务 (布尔: 0/1) */
ENUM_STOP_TRANSACTION_ON_INVALID_ID, /* 必选 无效ID时停止事务 (布尔: 0/1) */
ENUM_STOP_TXN_ALIGNED_DATA, /* 必选 事务停止时的对齐数据项 (字符串: 逗号分隔的measurand值) */
ENUM_STOP_TXN_ALIGNED_DATA_MAX_LENGTH, /* 可选 停止事务对齐数据最大长度 (整数: 字符数) */
ENUM_STOP_TXN_SAMPLED_DATA, /* 必选 停止事务时的采样数据项 (字符串: 逗号分隔的measurand值) */
ENUM_STOP_TXN_SAMPLED_DATA_MAX_LENGTH, /* 可选 停止事务采样数据最大长度 (整数: 字符数) */
ENUM_SUPPORTED_FEATURE_PROFILES, /* 必选 支持的功能配置文件 (字符串: 逗号分隔的FeatureProfile值) */
ENUM_SUPPORTED_FEATURE_PROFILES_MAX_LENGTH, /* 可选 功能配置文件列表最大长度 (整数: 字符数) */
ENUM_TRANSACTION_MESSAGE_ATTEMPTS, /* 必选 事务消息重试次数 (整数: 次数) */
ENUM_TRANSACTION_MESSAGE_RETRY_INTERVAL, /* 必选 事务消息重试间隔 (整数: 秒) */
ENUM_UNLOCK_CONNECTOR_ON_EV_SIDE_DISCONNECT, /* 必选 EV端断开时解锁连接器 (布尔: 0/1) */
ENUM_WEB_SOCKET_PING_INTERVAL, /* 可选 WebSocket Ping间隔 (整数: 秒) */
//Local Auth List Management Profile
ENUM_LOCAL_AUTH_LIST_ENABLED, /* 必选 启用本地授权列表 (布尔: 0/1) */
ENUM_LOCAL_AUTH_LIST_MAX_LENGTH, /* 必选 本地授权列表的最大条目数 (整数) */
ENUM_SEND_LOCAL_LIST_MAX_LENGTH, /* 必选 发送本地列表的最大条目数 (整数) */
//Reservation Profile
ENUM_RESERVE_CONNECTOR_ZERO_SUPPORTED, /* 可选 是否支持预留连接器0 (布尔: 0/1) */
//Smart Charging Profile
ENUM_CHARGE_PROFILE_MAX_STACK_LEVEL, /* 必选 充电配置最大堆叠层级 (整数) */
ENUM_CHARGING_SCHEDULE_ALLOWED_RATE_UNIT, /* 必选 充电速率允许单位 (字符串: "A"或"W") */
ENUM_CHARGING_SCHEDULE_MAX_PERIODS, /* 必选 充电计划最大时段数 (整数) */
ENUM_CONNECTOR_SWITCH_3TO1_PHASE_SUPPORTED, /* 可选 是否支持三相切单相充电 (布尔: 0/1) */
ENUM_MAX_CHARGING_PROFILES_INSTALLED, /* 必选 最大可安装充电配置数 (整数) */
//私有配置
ENUM_QRCODE_TEXT_CONNECTOR1, /* 枪1二维码 */
ENUM_QRCODE_TEXT_CONNECTOR2, /* 枪2二维码 */
//num-end OCPP配置参数的总数量,作为在根据字符串寻找对应枚举变量时的上限;未来添加新变量时需在此位置前添加;
ENUM_OCPP_CONFIG,
}OCPP_CONFIG_KEY_ID;
//OCPP可配置参数是否支持
typedef enum
{
ENUM_SUPPORT_NOT = 0,
ENUM_SUPPORT_YES,
}OCPP_CONFIG_SUPPORT;
//OCPP可配置参数数据类型
typedef enum
{
ENUM_TYPE_INT = 0,
ENUM_TYPE_CSL,
}OCPP_CONFIG_TYPE;
//OCPP修改配置参数返回操作结果
typedef enum
{
ENUM_Rejected = 0,
ENUM_Accepted,
ENUM_Required,
ENUM_NotSupported,
}OCPP_SETCONFIG_RET;
//OCPP获取配置参数返回只读结果
typedef enum
{
ENUM_RnW = 0,
ENUM_R_Only,
}OCPP_GETCONFIG_RET;
//OCPP可配置参数结构体
typedef struct
{
OCPP_CONFIG_KEY_ID u8_keyID; //1,枚举变量
OCPP_CONFIG_SUPPORT u8_support; //2,支持标志 0:不支持,1:支持。
OCPP_CONFIG_TYPE u8_type; //3,数据类型 1:integer2:CSL。
const char* str_data; //4、对应字符串内容 char*;
}OCPP_CONFIG_INFO;
//OCPP可配置参数操作类型
typedef enum
{
ENUM_TYPE_GET = 0,
ENUM_TYPE_SET,
}OCPP_CONFIG_OPERATE;
//OCPP可配置参数结构体数组
extern const OCPP_CONFIG_INFO OCPP_config_info[];
/*******************************************************************************
* : u8_find_OCPP_configID
* : OCPP配置参数的枚举变量
* : *mag OCPP配置参数的键的字符串
* : OCPP_CONFIG_KEY_ID的对应枚举量ENUM_NULL则未找到该键
* :
******************************************************************************/
OCPP_CONFIG_KEY_ID u8_find_OCPP_configID(const char *mag);
/*******************************************************************************
* : find_OCPP_configMag
* :
* : keyID
* *mag
* u16_len key串最大长度
* : -
* :
******************************************************************************/
void find_OCPP_configMag(OCPP_CONFIG_KEY_ID keyID, char *mag, U16_T u16_len);
/*******************************************************************************
* : u8_check_OCPP_configSupport
* :
* : keyID
* :
* :
******************************************************************************/
OCPP_CONFIG_SUPPORT u8_check_OCPP_configSupport(OCPP_CONFIG_KEY_ID keyID);
/*******************************************************************************
* : u8_OCPP_config_operate
* : OCPP配置参数进行读或写
* : operate_type ENUM_TYPE_GET--ENUM_TYPE_GET--
* *key_mag
* *value_mag
* u16_len
* : 0-Rejected 1-Accepted 2-RebootRequired 3-NotSupported
* 0-readonly-false 1-readonly-ture
* :
******************************************************************************/
U8_T u8_OCPP_config_operate(OCPP_CONFIG_OPERATE operate_type, const char *key_mag, char *value_mag , U16_T u16_len);
/****************************************************
* v_ocpp_init_config_data()
*
*
*
*
****************************************************/
void v_ocpp_init_config_data();
void v_flash_save_ocpp_cfg_data(void);
#endif
#endif
File diff suppressed because it is too large Load Diff
+528
View File
@@ -0,0 +1,528 @@
#ifndef _BS_OCPP_JSON_DATA_H_
#define _BS_OCPP_JSON_DATA_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#include "bs_ocpp_str.h"
#if BS_OCPP_EN //OCPP平台使能
#include "cJSON.h"
#include <string.h>
#define OCPP_LOGMSG_DEBUG_EN 1 //打印标志
#define OCPP_PRINTF_DEBUG_EN 1 //printf打印标志
#define MeterValuesRequest_MV_NUM 1 //电表数据组数量
#define MeterValuesRequest_SV_NUM 8 //数据内容数量
#define GetConfiguration_NUM 20 //平台获取数据最大数量
#define StopTransactionRequest_TD_NUM 1 //停止充电 数据组数量
#define StopTransactionRequest_SV_NUM 5 //停止充电 数据内容数量
#define E_CFG_MVMN_MeterValuesAlignedData (0x01)
#define E_CFG_MVMN_MeterValuesSampledData (0x02)
#define E_CFG_MVMN_StopTxnAlignedData (0x04)
#define E_CFG_MVMN_StopTxnSampledData (0x08)
#define OCPP_DATE_LEN (30) //OCPP时间格式 数据长度 ISO 8601 格式。
#define OCPP_CiString20 (20+1)
#define OCPP_CiString30 (30+1)
#define OCPP_CiString25 (25+1)
#define OCPP_CiString50 (50+1)
#define OCPP_CiString100 (100+1)
#define OCPP_CiString255 (255+1)
#define OCPP_CiString300 (300+1)
#define OCPP_CiString500 (500+1)
typedef enum{
OCPP_NULL = 0,
OCPP_BootNotification, //引导通知 桩->平台
OCPP_StatusNotification, //状态通知 桩->平台
OCPP_Heartbeat, //心跳 桩->平台
OCPP_Authorize, //授权 桩->平台
OCPP_MeterValues, //采样信息上传 桩->平台
OCPP_StartTransaction, //开始交易 桩->平台
OCPP_StopTransaction, //停止交易 桩->平台
OCPP_RemoteStartTransaction,//平台下发启动充电 平台->桩
OCPP_RemoteStopTransaction, //平台下发停止充电 平台->桩
OCPP_UnlockConnector, //解枪锁 平台->桩
OCPP_UpdateFirmware, //固件升级 平台->桩
OCPP_Reset, //重启 平台->桩
OCPP_CancelReservation, //取消已经预定的充电桩 平台->桩
OCPP_ChangeAvailability, //设置枪可用状态 平台->桩
OCPP_ChangeConfiguration, //更改参数配置 平台->桩
OCPP_TriggerMessage, //平台下发数据召测 平台->桩
OCPP_GetConfiguration, //平台获取参数配置 平台->桩
OCPP_ClearCache, //清楚桩内缓存 一般为白名单 平台->桩
OCPP_SendLocalList, //平台下发本地缓存列表
OCPP_GetLocalListVersion, //获取卡号版本
OCPP_GetDiagnostics, //获取诊断日志文件
OCPP_DiagnosticsStatusNotification, /* 诊断日志上传状态 桩->平台 */
OCPP_FirmwareStatusNotification, /* 固件升级状态 桩->平台 */
}OCPP_CMD;
typedef enum{
OCPP_StatusNotification_errorCode = 0, //StatusNotification errorCode
OCPP_StatusNotification_statusCode,
OCPP_RemoteStartTransactionResponse_statusCode,
OCPP_RemoteStopTransactionResponse_statusCode,
OCPP_StopTransactionRequest_reason,
OCPP_UnlockConnectorResponse_stateCode,
OCPP_DiagnosticsStatusNotification_status, /* 与 OCPP_DIAG_NOTIFY_PENDING_E 下标对齐:0 Idle … */
OCPP_FirmwareStatusNotification_status, /* 与 OCPP_FW_NOTIFY_PENDING_E 下标对齐:0 Idle … */
}OCPP_ENUM_DATA_TYPE;
//状态错误信息
typedef enum{
OCPP_ERRORCODE_ConnectorLockFailure, //连接器锁定故障
OCPP_ERRORCODE_EVCommunicationError, //EV通讯故障
OCPP_ERRORCODE_GroundFailure, //接地故障
OCPP_ERRORCODE_HighTemperature, //高温
OCPP_ERRORCODE_InternalError, //内部错误
OCPP_ERRORCODE_LocalListConflict, //本地列表冲突
OCPP_ERRORCODE_NoError, //无错误
OCPP_ERRORCODE_OtherError, //其他错误
OCPP_ERRORCODE_OverCurrentFailure, //过电流故障
OCPP_ERRORCODE_PowerMeterFailure, //功率表故障
OCPP_ERRORCODE_PowerSwitchFailure, //电源开关故障
OCPP_ERRORCODE_ReaderFailure, //读卡器故障
OCPP_ERRORCODE_ResetFailure, //重置失败
OCPP_ERRORCODE_UnderVoltage, //电压过低
OCPP_ERRORCODE_OverVoltage, //电压过高
OCPP_ERRORCODE_WeakSignal, //信号弱
}OCPP_STATUSNOTIFICATION_ERRORCODE;
//状态信息
typedef enum{
OCPP_STATUSCODE_Available, //空闲
OCPP_STATUSCODE_Preparing, //准备
OCPP_STATUSCODE_Charging, //充电
OCPP_STATUSCODE_SuspendedEVSE, //暂停EVSE
OCPP_STATUSCODE_SuspendedEV, //暂停EV
OCPP_STATUSCODE_Finishing, //完成
OCPP_STATUSCODE_Reserved, //预留
OCPP_STATUSCODE_Unavailable, //不空闲
OCPP_STATUSCODE_Faulted, //故障
}OCPP_STATUSNOTIFICATION_STATUSCODE;
typedef struct
{
//必选
char chargePointVendor[OCPP_CiString20]; //"type": "string","maxLength": 20
char chargePointModel[OCPP_CiString20]; //"type": "string","maxLength": 20
//可选
char chargePointSerialNumber[OCPP_CiString25]; //"type": "string","maxLength": 25
char chargeBoxSerialNumber[OCPP_CiString25]; //"type": "string","maxLength": 25
char firmwareVersion[OCPP_CiString20]; //"type": "string","maxLength": 50
char iccid[OCPP_CiString30]; //"type": "string","maxLength": 20
char imsi[OCPP_CiString30]; //"type": "string","maxLength": 20
char meterType[OCPP_CiString25]; //"type": "string","maxLength": 25
char meterSerialNumber[OCPP_CiString25]; //"type": "string","maxLength": 25
}BootNotificationRequest;
typedef struct
{
//必选
char status[OCPP_CiString20]; //"type": "string","enum": ["Accepted","Pending","Rejected"]
char currentTime[OCPP_DATE_LEN]; //"type": "string","format": "date-time" 2022-09-09T16:40:00+04:00
int interval; //"type": "integer"
//可选
}BootNotificationResponse;
typedef struct
{
//必选
int connectorId; //连接器id
char errorCode[OCPP_CiString30]; //枚举 ocpp_errorCode 当前的错误状态代码。应当表示充电桩或连接器当前的错误情况。
/*"ConnectorLockFailure", 充电枪锁止机构故障
"EVCommunicationError",
"GroundFailure",
"HighTemperature",
"InternalError",
"LocalListConflict",
"NoError",
"OtherError",
"OverCurrentFailure",
"PowerMeterFailure",
"PowerSwitchFailure",
"ReaderFailure",
"ResetFailure",
"UnderVoltage",
"OverVoltage",
"WeakSignal" */
char status[OCPP_CiString20]; //枚举 statusCode 连接器的当前状态。
/*"Available", 可用
"Preparing",
"Charging",
"SuspendedEVSE",
"SuspendedEV",
"Finishing",
"Reserved",
"Unavailable",
"Faulted" */
//可选
char info[OCPP_CiString30]; //与错误相关的其他自由格式的信息
char timestamp[OCPP_DATE_LEN]; //日期时间 状态变化发生的时间戳
char vendorId[OCPP_CiString50]; //制造商标识。
char vendorErrorCode[OCPP_CiString50]; //制造商特定的错误代码。
}StatusNotificationRequest;
typedef struct
{
char value[OCPP_CiString20]; //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
char context[OCPP_CiString20]; //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)
/*"Interruption.Begin", 中断开始 标识充电过程被主动中断的起始时刻(如用户手动停止、系统故障触发)。
"Interruption.End",
"Sample.Clock",
"Sample.Periodic", 15
"Transaction.Begin",
"Transaction.End",
"Trigger", triggerMessage 使
"Other"
*/
char format[OCPP_CiString20]; //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。/*"Raw" 明文,"SignedData" 签名加密*/
char measurand[OCPP_CiString50]; //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register (导入的有效能量)。
/*"Energy.Active.Export.Register", 累计放电电量(电表读数)Wh 或 kWh
"Energy.Active.Import.Register", Wh kWh
"Energy.Reactive.Export.Register", (: kvarh/varh)
"Energy.Reactive.Import.Register", (: kvarh/varh)
"Energy.Active.Export.Interval", (: kWh/Wh)
"Energy.Active.Import.Interval", (: kWh/Wh)
"Energy.Reactive.Export.Interval", (: kvarh/varh)
"Energy.Reactive.Import.Interval", (: kvarh/varh)
"Power.Active.Export", W
"Power.Active.Import", W
"Power.Offered", (: W/kW)
"Power.Reactive.Export", (: var/kvar)
"Power.Reactive.Import", (: var/kvar)
"Power.Factor", (, : -1.0~1.0)
"Current.Import",
"Current.Export",
"Current.Offered", (: A)
"Voltage", 线
"Frequency",
"Temperature",
"SoC", State of Charge
"RPM" (: RPM)*/
char phase[OCPP_CiString20]; //可选 可选字段,表示电力相位,例如 L1, L2, L3。
/*"L1",
"L2",
"L3",
"N",
"L1-N",
"L2-N",
"L3-N",
"L1-L2",
"L2-L3",
"L3-L1"*/
char location[OCPP_CiString20]; //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
/*"Cable",电缆
"EV",
"Inlet",
"Outlet",
"Body" */
char unit[OCPP_CiString20]; //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
/*"Wh",
"kWh",
"varh",
"kvarh",
"W",
"kW",
"VA",
"kVA",
"var",
"kvar",
"A",
"V",
"K",
"Celcius",
"Celsius",
"Fahrenheit",
"Percent" */
}SampledValue;
typedef struct
{
int connectorId; //必须 表示充电连接器的唯一标识符。
int transactionId; //可选 当前充电事务的唯一标识符。这是可选字段,只有在当前有正在进行的充电事务时才有值。
struct
{
char timestamp[OCPP_DATE_LEN]; //必须 记录取样数据的时间,ISO 8601 格式。
SampledValue sampledValue[MeterValuesRequest_SV_NUM]; //必须 最大同时上送3种数据 代表一个或多个取样值,每个取样值包含以下字段:
}meterValue[MeterValuesRequest_MV_NUM]; //必须 这是一个数组,包含一组计量值。每个计量值包含一个时间戳和对应的一组取样值。
}MeterValuesRequest;
typedef struct
{
int connectorId; //可选 指示发起交易的连接器ID,即充电桩的具体插口编号。
char idTag[OCPP_CiString20]; //必选 标识电动汽车用户或车主的唯一ID标识,用于识别用户。。
struct
{
int chargingProfileId; //必选 充电配置的唯一标识符
int transactionId; //可选 关联的事务ID,用于将此配置与特定充电事务关联
int stackLevel; //必选 配置优先级,数值越高优先级越高
char chargingProfilePurpose[OCPP_CiString25]; //必选 定义配置的用途
/* "ChargePointMaxProfile", 充电桩最大功率配置
"TxDefaultProfile",
"TxProfile" */
char chargingProfileKind[OCPP_CiString20]; //必选 定义配置的时间类型
/* "Absolute", 绝对时间配置
"Recurring",
"Relative" */
char recurrencyKind[OCPP_CiString20]; //可选 仅当chargingProfileKind为"Recurring"时有效
/* "Daily",每天
"Weekly" */
char validFrom[OCPP_DATE_LEN]; //可选 "date-time"配置生效的开始时间。如果不存在,则配置文件在充电桩收到后立即有效。当ChargingProfilePurpose是TxProfile时,请勿使用。
char validTo[OCPP_DATE_LEN]; //可选 "date-time"配置失效的结束时间。如果不存在,则该配置文件在被另一个 配置文件替换之前一直有效。当ChargingProfilePurpose是TxProfile时,请勿使用。
struct
{
int duration; //可选 持续时间
char startSchedule[OCPP_DATE_LEN]; //可选 "date-time" 充电计划开始时间
char chargingRateUnit[OCPP_CiString20]; //必选 充电速率单位
/* "A",
"W"*/
struct
{
int startPeriod; //必选 时段开始时间(相对于充电计划开始时间的秒数)
float limit; //必选 该时段的最大充电速率
int numberPhases; //可选 使用的相数(1-3)
}chargingSchedulePeriod[3]; //必选 充电计划时段数组
float minChargingRate; //可选 最小充电速率
}chargingSchedule; //必选 充电计划配置
}chargingProfile; //可选 充电配置参数,用于指定充电行为。
}RemoteStartTransactionRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted"接受,"Rejected"拒绝
}RemoteStartTransactionResponse;
typedef struct
{
int transactionId; //当前充电事务的唯一标识符。
}RemoteStopTransactionRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted"接受,"Rejected"拒绝
}RemoteStopTransactionResponse;
typedef struct
{
int connectorId; //必选 指示发起交易的连接器ID,即充电桩的具体插口编号。
int meterStart; //必选 交易开始时的电表读数,这通常是充电开始时的初始能量值。 Wh
int reservationId; //可选 如果交易基于事先的预定进行,此字段包含预定的ID。
char idTag[OCPP_CiString20]; //必选 标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
char timestamp[OCPP_DATE_LEN]; //必选 "date-time" 交易开始的时间戳,表示交易开始的准确时间。
}StartTransactionRequest;
typedef struct
{
int transactionId; //必选 中央系统生成的唯一交易ID,用于后续的交易管理和追踪。
struct
{
char status[OCPP_CiString20]; //必选 指示idTag的当前状态,例如是否被接受、是否过期等。Accepted、Blocked、Expired、Invalid、ConcurrentTx
char expiryDate[OCPP_DATE_LEN]; //可选 idTag有效期的截止日期
char parentIdTag[OCPP_CiString20]; //可选 如果存在关联账户,这是父idTag。
}idTagInfo; //必选 包含对idTag的验证和状态信息,其中包括:
}StartTransactionResponse;
typedef struct
{
int meterStop; //必选 交易结束时的电表读数,这通常是充电结束时的最终能量值 Wh
int transactionId; //必选 要停止的充电事务的唯一标识符
char idTag[OCPP_CiString20]; //可选 标识电动汽车用户或车主的唯一ID标识。
char timestamp[OCPP_DATE_LEN]; //必选 交易结束的时间戳,表示交易停止的准确时间
char reason[OCPP_CiString20]; //可选 指示停止充电的原因,这可以包括:正常结束、紧急停止等情况。 Local、Remote、EmergencyStop、EVDisconnected、HardReset、SoftReset、deAuthorized、Expired、EVConnectTimeout、Other
struct
{
char timestamp[OCPP_DATE_LEN]; //必选
SampledValue sampledValue[StopTransactionRequest_SV_NUM]; //必选
}transactionData[StopTransactionRequest_TD_NUM]; //可选 包含与充电交易相关的更详细的数据,如中途的数据采集点
}StopTransactionRequest;
typedef struct
{
struct
{
char status[OCPP_CiString20]; //必选 指示idTag的当前状态,例如是否被接受、是否过期等。Accepted、Blocked、Expired、Invalid、ConcurrentTx
char expiryDate[OCPP_DATE_LEN]; //可选 idTag有效期的截止日期。
char parentIdTag[OCPP_CiString20]; //可选 果存在关联账户,这是父idTag。
}idTagInfo;
}StopTransactionResponse;
typedef struct
{
int connectorId;
}UnlockConnectorRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Unlocked","UnlockFailed","NotSupported"
}UnlockConnectorResponse;
typedef struct
{
int retries; //可选
int retryInterval; //可选
char retrieveDate[OCPP_DATE_LEN]; //必选
char location[OCPP_CiString255]; //必选
}UpdateFirmwareRequest;
typedef struct
{
int test;
}UpdateFirmwareResponse;
typedef struct
{
char type[OCPP_CiString20]; // "Hard","Soft"
}ResetRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected"
}ResetResponse;
typedef struct
{
char idTag[OCPP_CiString20];
}AuthorizeRequest;
typedef struct
{
struct
{
char expiryDate[OCPP_DATE_LEN]; //可选
char parentIdTag[OCPP_CiString20]; //可选
char status[OCPP_CiString20]; //必选
/*"Accepted", 请求已被接受
"Blocked",
"Expired",
"Invalid",
"ConcurrentTx" /*/
}idTagInfo;
}AuthorizeResponse;
typedef struct
{
int reservationId;
}CancelReservationRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected"
}CancelReservationResponse;
typedef struct
{
int connectorId;
char type[OCPP_CiString20]; //"Inoperative","Operative"
}ChangeAvailabilityRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected",Scheduled"
}ChangeAvailabilityResponse;
typedef struct
{
char key[OCPP_CiString50];
char value[OCPP_CiString300]; //
}ChangeConfigurationRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected","RebootRequired","NotSupported"
}ChangeConfigurationResponse;
typedef struct
{
int connectorId; //可选
char requestedMessage[OCPP_CiString50]; //必选 "BootNotification","DiagnosticsStatusNotification","FirmwareStatusNotification","Heartbeat","MeterValues","StatusNotification"
}TriggerMessageRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected","NotImplemented"
}TriggerMessageResponse;
typedef struct
{
char key[GetConfiguration_NUM][OCPP_CiString50]; //array
}GetConfigurationRequest;
typedef struct
{
char unknownKey[GetConfiguration_NUM][OCPP_CiString50]; //array
struct
{
int readonly; //必选
char key[OCPP_CiString50]; //必选
char value[OCPP_CiString100]; //可选
}configurationKey[GetConfiguration_NUM];
}GetConfigurationResponse;
typedef struct
{
int res;
}ClearCacheRequest;
typedef struct
{
char status[OCPP_CiString20];
}ClearCacheResponse;
typedef struct
{
char idTag[OCPP_CiString30];
struct
{
char expiryDate[OCPP_DATE_LEN];
char parentIdTag[OCPP_CiString20];
char status[OCPP_CiString20];
}idTagInfo;
}LocalAuthorizationList;
typedef struct
{
int listVersion;
char updateType[OCPP_CiString30];
int List_num;
LocalAuthorizationList *list;
}SendLocalListRequest;
typedef struct
{
char status[OCPP_CiString20];
}SendLocalListResponse;
typedef struct
{
int listVersion;
}GetLocalListVersionResponse;
typedef struct
{
int retries; //可选
int retryInterval; //可选
char location[OCPP_CiString255]; //必须
char startTime[OCPP_DATE_LEN]; //可选
char stopTime[OCPP_DATE_LEN]; //可选
}GetDiagnosticsRequest;
typedef struct
{
char fileName[OCPP_CiString50];
}GetDiagnosticsResponse;
typedef struct {
char status[OCPP_CiString20];
} DiagnosticsStatusNotificationRequest;
/** 与 DiagnosticsStatusNotification 载荷相同,仅 Action 名不同 */
typedef DiagnosticsStatusNotificationRequest FirmwareStatusNotificationRequest;
cJSON* j_get_ocpp_json(OCPP_CMD cmd , void *data);
int get_ocpp_data(OCPP_CMD cmd , void *data ,cJSON *root);
OCPP_CMD e_get_recv_json_cmd(char* cmd_str);
const char *s_get_ocpp_cmd_str(OCPP_CMD cmd);
const char *s_get_ocpp_enum_data_str(OCPP_ENUM_DATA_TYPE type ,int cmd);
#endif
#endif
+150
View File
@@ -0,0 +1,150 @@
#ifndef _BS_OCPP_STR_H_
#define _BS_OCPP_STR_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#if BS_OCPP_EN //OCPP平台使能
#define OCPP_STR_SamplePeriodic "Sample.Periodic" //周期上送 Periodic delivery
#define OCPP_STR_TransactionBegin "Transaction.Begin" //交易开始
#define OCPP_STR_TransactionEnd "Transaction.End" //交易停止
#define STR_TRUE "TRUE"
#define STR_true "true"
#define STR_1 "1"
#define STR_FALSE "FALSE"
#define STR_false "false"
#define STR_0 "0"
/*
#define OCPP_STR_EnergyActiveImportRegister "Energy.Active.Import.Register" //累计充电电量 单位kWh
#define OCPP_STR_PowerActiveImport "Power.Active.Import" //充电功率 单位W
#define OCPP_STR_CurrentImport "Current.Import" //充电电流 单位A
#define OCPP_STR_Voltage "Voltage" //电压 单位V
#define OCPP_STR_SOC "SoC" //电池电量状态(State of Charge
#define OCPP_STR_Temperature "Temperature" //温度(桩内)
*/
#define OCPP_STR_EnergyActiveExportRegister "Energy.Active.Export.Register" // 有功电能输出累计值(Wh
#define OCPP_STR_EnergyActiveImportRegister "Energy.Active.Import.Register" // 有功电能输入累计值(Wh
#define OCPP_STR_EnergyReactiveExportRegister "Energy.Reactive.Export.Register" // 无功电能输出累计值(VARh
#define OCPP_STR_EnergyReactiveImportRegister "Energy.Reactive.Import.Register" // 无功电能输入累计值(VARh
#define OCPP_STR_EnergyActiveExportInterval "Energy.Active.Export.Interval" // 有功电能输出时段值(Wh
#define OCPP_STR_EnergyActiveImportInterval "Energy.Active.Import.Interval" // 有功电能输入时段值(Wh
#define OCPP_STR_EnergyReactiveExportInterval "Energy.Reactive.Export.Interval" // 无功电能输出时段值(VARh
#define OCPP_STR_EnergyReactiveImportInterval "Energy.Reactive.Import.Interval" // 无功电能输入时段值(VARh
#define OCPP_STR_PowerActiveExport "Power.Active.Export" // 有功功率输出(W
#define OCPP_STR_PowerActiveImport "Power.Active.Import" // 有功功率输入(W
#define OCPP_STR_PowerOffered "Power.Offered" // 充电桩可提供的最大有功功率(W)
#define OCPP_STR_PowerReactiveExport "Power.Reactive.Export" // 无功功率输出(VAR
#define OCPP_STR_PowerReactiveImport "Power.Reactive.Import" // 无功功率输入(VAR
#define OCPP_STR_PowerFactor "Power.Factor" // 功率因数(无单位,0~1
#define OCPP_STR_CurrentImport "Current.Import" // 输入电流(A
#define OCPP_STR_CurrentExport "Current.Export" // 输出电流(A
#define OCPP_STR_CurrentOffered "Current.Offered" // 充电桩可提供的最大电流(A
#define OCPP_STR_Voltage "Voltage" // 实时线电压(V
#define OCPP_STR_Frequency "Frequency" // 电网实时频率(Hz
#define OCPP_STR_Temperature "Temperature" // 设备/电池实时温度(℃)
#define OCPP_STR_SOC "SoC" // 电池剩余电量(%
#define OCPP_STR_RPM "RPM" // 风扇转速(转/分钟)
#define OCPP_STR_CurrentRequest "Current.Request" // 请求电流
#define OCPP_STR_VoltageRequest "Voltage.Request" // 请求电压值
#define OCPP_STR_Raw "Raw" //明文(不加密)
#define OCPP_STR_Body "Body" //整桩
#define OCPP_STR_EV "EV" //整桩
#define OCPP_STR_Outlet "Outlet" //出口
#define OCPP_STR_L1 "L1" //
#define OCPP_STR_L2 "L2" //
#define OCPP_STR_L3 "L3" //
#define OCPP_STR_Wh "Wh"
#define OCPP_STR_kWh "kWh" //
#define OCPP_STR_W "W" //
#define OCPP_STR_V "V" //
#define OCPP_STR_A "A" //
#define OCPP_STR_Celsius "Celsius" // 摄氏度
#define OCPP_STR_Percent "Percent" //百分比*/
#define OCPP_STR_Accepted "Accepted" //接受
#define OCPP_STR_Rejected "Rejected" //拒绝
#define OCPP_STR_RebootRequired "RebootRequired" //需要重启
#define OCPP_STR_NotSupported "NotSupported" //不支持
#define OCPP_STR_Blocked "Blocked" //阻塞
#define OCPP_STR_Expired "Expired" //过期
#define OCPP_STR_Invalid "Invalid" //无效
#define OCPP_STR_ConcurrentTx "ConcurrentTx" //并发事务
// --------------------------------------------------------
// 核心配置参数(数据类型与功能说明)
// --------------------------------------------------------
#define KEY_ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID "AllowOfflineTxForUnknownId" /* 可选 允许未知用户ID的离线交易 (布尔: 0/1) */
#define KEY_AUTHORIZATION_CACHE_ENABLED "AuthorizationCacheEnabled" /* 可选 启用本地授权缓存 (布尔: 0/1) */
#define KEY_AUTHORIZE_REMOTE_TX_REQUESTS "AuthorizeRemoteTxRequests" /* 必选 允许远程启动充电事务 (布尔: 0/1) */
#define KEY_BLINK_REPEAT "BlinkRepeat" /* 可选 充电桩指示灯闪烁次数 (整数: 次数) */
#define KEY_CLOCK_ALIGNED_DATA_INTERVAL "ClockAlignedDataInterval" /* 必选 时钟对齐的数据上报间隔 (整数: 秒) */
#define KEY_CONNECTION_TIMEOUT "ConnectionTimeOut" /* 必选 TCP连接超时时间 (整数: 秒) */
#define KEY_CONNECTOR_PHASE_ROTATION "ConnectorPhaseRotation" /* 必选 连接器相位旋转配置 (字符串: L1/L2/L3顺序) */
#define KEY_CONNECTOR_PHASE_ROTATION_MAX_LENGTH "ConnectorPhaseRotationMaxLength" /* 可选 相位旋转配置最大长度 (整数: 字节数) */
#define KEY_GET_CONFIGURATION_MAX_KEYS "GetConfigurationMaxKeys" /* 必选 GetConfiguration请求最大返回Key数 (整数: 数量) */
#define KEY_HEARTBEAT_INTERVAL "HeartbeatInterval" /* 必选 心跳间隔 (整数: 秒) */
#define KEY_LIGHT_INTENSITY "LightIntensity" /* 可选 充电桩灯光亮度 (整数: 百分比) */
#define KEY_LOCAL_AUTHORIZE_OFFLINE "LocalAuthorizeOffline" /* 必选 本地离线授权模式 (布尔: 0/1) */
#define KEY_LOCAL_PRE_AUTHORIZE "LocalPreAuthorize" /* 必选 本地预授权模式 (布尔: 0/1) */
#define KEY_MAX_ENERGY_ON_INVALID_ID "MaxEnergyOnInvalidId" /* 可选 无效ID用户的最大允许充电能量 (整数: Wh) */
#define KEY_METER_VALUES_ALIGNED_DATA "MeterValuesAlignedData" /* 必选 对齐数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
#define KEY_METER_VALUES_ALIGNED_DATA_MAX_LENGTH "MeterValuesAlignedDataMaxLength" /* 可选 对齐数据列表最大长度 (整数: 字符数) */
#define KEY_METER_VALUES_SAMPLED_DATA "MeterValuesSampledData" /* 必选 采样数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
#define KEY_METER_VALUES_SAMPLED_DATA_MAX_LENGTH "MeterValuesSampledDataMaxLength" /* 可选 采样数据列表最大长度 (整数: 字符数) */
#define KEY_METER_VALUE_SAMPLE_INTERVAL "MeterValueSampleInterval" /* 必选 采样间隔 (整数: 秒) */
#define KEY_MINIMUM_STATUS_DURATION "MinimumStatusDuration" /* 可选 状态最小持续时间 (整数: 秒) */
#define KEY_NUMBER_OF_CONNECTORS "NumberOfConnectors" /* 必选 充电桩连接器数量 (整数: 数量) */
#define KEY_RESET_RETRIES "ResetRetries" /* 必选 复位重试次数 (整数: 次数) */
#define KEY_STOP_TXN_ON_EV_SIDE_DISCONNECT "StopTransactionOnEVSideDisconnect" /* 必选 EV端断开时停止事务 (布尔: 0/1) */
#define KEY_STOP_TXN_ON_INVALID_ID "StopTransactionOnInvalidId" /* 必选 无效ID时停止事务 (布尔: 0/1) */
#define KEY_STOP_TXN_ALIGNED_DATA "StopTxnAlignedData" /* 必选 事务停止时的对齐数据项 (字符串: 逗号分隔的measurand值) */
#define KEY_STOP_TXN_ALIGNED_DATA_MAX_LENGTH "StopTxnAlignedDataMaxLength" /* 可选 停止事务对齐数据最大长度 (整数: 字符数) */
#define KEY_STOP_TXN_SAMPLED_DATA "StopTxnSampledData" /* 必选 停止事务时的采样数据项 (字符串: 逗号分隔的measurand值) */
#define KEY_STOP_TXN_SAMPLED_DATA_MAX_LENGTH "StopTxnSampledDataMaxLength" /* 可选 停止事务采样数据最大长度 (整数: 字符数) */
#define KEY_SUPPORTED_FEATURE_PROFILES "SupportedFeatureProfiles" /* 必选 支持的功能配置文件 (字符串: 逗号分隔的FeatureProfile值) */
#define KEY_SUPPORTED_FEATURE_PROFILES_MAX_LENGTH "SupportedFeatureProfilesMaxLength" /* 可选 功能配置文件列表最大长度 (整数: 字符数) */
#define KEY_TRANSACTION_MESSAGE_ATTEMPTS "TransactionMessageAttempts" /* 必选 事务消息重试次数 (整数: 次数) */
#define KEY_TRANSACTION_MESSAGE_RETRY_INTERVAL "TransactionMessageRetryInterval" /* 必选 事务消息重试间隔 (整数: 秒) */
#define KEY_UNLOCK_CONNECTOR_ON_EV_DISCONNECT "UnlockConnectorOnEVSideDisconnect" /* 必选 EV端断开时解锁连接器 (布尔: 0/1) */
#define KEY_WEBSOCKET_PING_INTERVAL "WebSocketPingInterval" /* 可选 WebSocket Ping间隔 (整数: 秒) */
// --------------------------------------------------------
// 本地授权与充电配置管理
// --------------------------------------------------------
#define KEY_LOCAL_AUTH_LIST_ENABLED "LocalAuthListEnabled" /* 必选 启用本地授权列表 (布尔: 0/1) */
#define KEY_LOCAL_AUTH_LIST_MAX_LENGTH "LocalAuthListMaxLength" /* 必选 本地授权列表的最大条目数 (整数) */
#define KEY_SEND_LOCAL_LIST_MAX_LENGTH "SendLocalListMaxLength" /* 必选 发送本地列表的最大条目数 (整数) */
// --------------------------------------------------------
// 连接器与充电行为控制
// --------------------------------------------------------
#define KEY_RESERVE_CONNECTOR_ZERO_SUPPORTED "ReserveConnectorZeroSupported" /* 可选 是否支持预留连接器0 (布尔: 0/1) */
// --------------------------------------------------------
// 充电计划与配置限制
// --------------------------------------------------------
#define KEY_CHARGE_PROFILE_MAX_STACK_LEVEL "ChargeProfileMaxStackLevel" /* 必选 充电配置最大堆叠层级 (整数) */
#define KEY_CHARGING_SCHEDULE_ALLOWED_RATE_UNIT "ChargingScheduleAllowedChargingRateUnit" /* 必选 充电速率允许单位 (字符串: "A"或"W") */
#define KEY_CHARGING_SCHEDULE_MAX_PERIODS "ChargingScheduleMaxPeriods" /* 必选 充电计划最大时段数 (整数) */
#define KEY_CONNECTOR_SWITCH_3TO1_PHASE_SUPPORTED "ConnectorSwitch3to1PhaseSupported" /* 可选 是否支持三相切单相充电 (布尔: 0/1) */
#define KEY_MAX_CHARGING_PROFILES_INSTALLED "MaxChargingProfilesInstalled" /* 必选 最大可安装充电配置数 (整数) */
// --------------------------------------------------------
// 私有配置
// --------------------------------------------------------
#define KEY_QRCODE_TEXT_CONNECTOR1 "QRCodeTextOfConnector-1" /* 枪1二维码 */
#define KEY_QRCODE_TEXT_CONNECTOR2 "QRCodeTextOfConnector-2" /* 枪2二维码 */
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More