evc_ii.c
evc_ii_control,evc_ii.c:1716)4.4 充停状态机(evc_ii_update_charge_control_state,evc_ii.c:1100)4.5 总可用功率(evc_ii_calculated_total_power,evc_ii.c:906)4.6 多桩功率分配(evc_ii_distribute_power,evc_ii.c:1295)4.7 单桩功率落地4.8 通讯丢失与运行数据清理4.9 加密协商流程5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
midware/evc/evc_ii.c所属模块:midware/evc(二代交流充电桩 SMILE-G3 系列通信中间件) 代码行数:2212
1. 文件职责
本文件是 EMS 主控对二代交流充电桩(EVC-II)的完整驱动实现,支持最多 4 桩并机、单相/三相、即插即用、刷卡启停、云平台远程充停与 AES 加密通讯,通过 USART3(RS485)以 Modbus-RTU 主站方式轮询。
数据流:evc_ii_get_sys_data()(evc_ii.c:396)抓取电表/g_t_logic_info/logic_read_*() 快照 → evc_ii_calculated_total_power()(evc_ii.c:906)按策略算出 EV 子系统总可用功率 → evc_ii_distribute_power()(evc_ii.c:1295)在多桩间做"启动功率预留 + 动态功率均分" → evc_ii_parallel_calculate_single_station_power()(evc_ii.c:1530)落到单桩设定功率 → send_cmd_evc_ii()(evc_ii.c:710)以 Modbus 0x10 下发。
驱动方:midware/interface/interface.c:355 在二代桩使能且非升级状态下每 100ms 调用 evc_ii_control()(evc_ii.c:1716),内部自行分频出 1s 轮询与 10s/N 下发节拍。
本文件与一代实现 evc.c 互斥使用(同一 485 口、同一收发缓存),由 interface.c:347-357 依据 get_evc_enable_flag() 二选一。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
s_evc_ii_send_buf / s_evc_ii_recv_buf | static 指针 | 直接指向 g_evc_send_buf/g_evc_recv_buf,与一代/升级共享 256B 缓冲(evc_ii.c:28-29) |
g_t_evc_ii_single_config[4] | 全局数组(EVC_II_SINGLE_CONFIG_T) | 4 桩设置表;本文件会改写 u8_ev_phase(evc_ii.c:1499/1511) |
g_t_evc_ii_run_info[4] | 全局数组(EVC_II_RUN_INFO_T) | 4 桩运行数据,本文件读写核心 |
s_t_evc_ii_sys_data | static(EVC_II_SYS_DATA_T) | 系统侧输入快照(evc_ii.c:34) |
s_t_evc_ii_control_para | static(EVC_II_CONTROL_PARA_T) | 整体策略 u8_evc_plot、整体最大电流、A/B/C 相与总可充功率(evc_ii.c:35) |
s_evc_ii_rtc_datetime | static(struct RTC_DateTime) | RTC 副本,用于三段充电时间判定(evc_ii.c:36) |
SYSTEM_DATETIME | extern | 系统全局时间(evc_ii.c:38) |
l_u8_recv_data[90] | static 局部数组 | AES 解密输出缓冲,固定 90 字节(evc_ii.c:109) |
l_u8_enc_data[256] | 局部数组 | AES 加密输出缓冲,位于任务栈(evc_ii.c:2177) |
aesInfo[4] | extern(common/aes.h:53) | 4 桩各自的 AES 上下文,密钥槽与桩地址 id 一一对应 |
PUBLIC_KEY | 宏(common/aes.h:13) | 与桩协商私钥用的公开密钥 "R$66tK~Dh09&Mg#5#172DTA%&97^%10" |
aes_key | extern(common/aes 模块) | 私钥解密后的临时存放区(evc_ii.c:493) |
MAX_EVC_II_NUM | 宏(evc_ii.h:14) | 4,全部数组维度与循环上限 |
EVC_REMOTE_ALLOW / EVC_REMOTE_DENY | 宏(evc_ii.h:22-23) | remotcontrol 语义:0 允许 / 1 禁止 |
NOMINAL_VOLTAGE_V | 宏(evc_ii.h:25) | 2300(0.1V 单位) |
EVC_II_MANUAL_SETTING / EVC_II_TIMING_CHARGE / EVC_II_PLUG_PLAY | 枚举(evc_ii.h:68) | u8_charge_tactics 三种控制方式,是刷卡/定时状态机的顶层分支 |
EVC_II_SLOW / EVC_II_NORMAL / EVC_II_QUICK / EVC_II_SPECIFIED_POWER / EVC_II_NONE | 枚举(evc_ii.h:43) | 充电策略,evc_ii_calculated_total_power 与单桩限流的分支键 |
EVC_II_CHARGE_NORMAL / EVC_II_CONDITION_CHARGE / EVC_II_NO_CONDITION_CHARGE | 枚举(evc_ii.h:52) | 条件充电延时状态机取值 |
EVC_II_EMS_START / EVC_II_CARD_START / EVC_II_PLUG_PLAY_START | 枚举(evc_ii.h:28) | evc_ii_state_u.BIT.startmode 的三种启动来源 |
g_system_function_info.u_system_function1.bit.u8_evc_gun_lock | extern 位域 | 枪锁状态上报标志,由 gun_lock_flag 折算(data/system_config/system_config.h:202) |
g_t_logic_info.u8_14a_enwg_active | extern | 14a/ENWG 是否真正生效,生效时 EV 总额度受 get_enwg_evc_power_limit() 二次限幅(logic/logic.h:602、evc_ii.c:1821) |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
evc_ii_master_send_cmd | static(evc_ii.c:69) | 组 Modbus-RTU 帧;若该桩加密标记为真则整体 AES 加密并重新附加 CRC,再经 uart3_send_data 发出 |
evc_ii_master_recv_data | static(evc_ii.c:102) | 500ms 窗口接收;先校验外层 CRC,加密桩再解密并二次校验 CRC |
evc_ii_charge_time | static(evc_ii.c:193) | 三段充电时间段的分钟刻度判定,输出 u8_time1/2/3_flag(0 段内 / 1 段外 / 2 无效) |
evc_ii_get_evc_data | static(evc_ii.c:329) | 按 u8_charge_tactics 决定当前生效的充电策略与最大电流,并设置 chg_mode/plug_play_ctrl |
evc_ii_get_sys_data | static(evc_ii.c:396) | 抓取系统快照 + 逐桩刷新策略,并把最后一个可充桩的策略提升为整体策略 |
get_evc_ii_pravite_key | 全局(evc_ii.c:470) | 读 0x0300 起 16 寄存器,用 PUBLIC_KEY 解密出桩私钥存入 aesInfo[id],置加密标记 |
get_evc_ii_id_info | static(evc_ii.c:537) | 读 0x0100 起 38 寄存器:SN、启动/最大功率、相数、软/硬件版本,并按相数与 SN 推断型号 |
get_evc_ii_run_info | static(evc_ii.c:609) | 读 0x9100 起 19 寄存器:三相电压电流、总功率、电量、故障字、CP 电压/占空比、状态字 |
send_cmd_evc_ii | static(evc_ii.c:710) | 写 0x0100 起 11 寄存器:总/分相设定功率、控制字、设定电流、模式控制字 |
evc_ii_set_power_total_limit | static(evc_ii.c:811) | 设定功率的上下限裁剪:负值归 0,总值 ≤ 单相最大 × 相数,分相值 ≤ 单相最大 |
set_evc_ii_energy | static(evc_ii.c:863) | 写 0x00A2 起 2 寄存器为 0,清指定桩电量 |
evc_ii_calculated_total_power | static(evc_ii.c:906) | 计算系统侧 EV 总可用功率,含电池充放限值裁剪与四种策略分支 |
evc_ii_get_max_allowed_power | static(evc_ii.c:1085) | 单桩功率硬上限 = min(硬件单相最大×相数, V×最大电流×相数/100) |
evc_ii_update_charge_control_state | static(evc_ii.c:1100) | 单桩充停状态机:云指令消费、刷卡启停两阶段确认、定时充停、remotcontrol 最终裁决 |
evc_ii_want_charge | static(evc_ii.c:1272) | 判断单桩"真实充电意愿"(已连接、未充满、未被禁止) |
evc_ii_distribute_power | static(evc_ii.c:1295) | 多桩功率分配核心:候选筛选 → 稳定充电优先排序 → 启动功率预留 → 动态功率均分与二次填充 |
evc_ii_apply_system_power_limit | static(evc_ii.c:1469) | 系统级硬件限幅:按并机限流标志取 16A 或 32A 对应的 3 相总功率 |
evc_ii_prepare_power_limit | static(evc_ii.c:1486) | 单桩预处理:电流上限裁剪、相数缺省填充、电压异常强制单相、设置 phasecontrol 与 gun_lock_ctrl |
evc_ii_parallel_calculate_single_station_power | static(evc_ii.c:1530) | 并机模式下按 EMS 硬约束写单桩设定功率(不做本地状态机) |
evc_ii_calculate_single_station_power | static(evc_ii.c:1586) | 单桩独立模式下的完整功率计算,含条件充电延时状态机 |
evc_ii_control | 全局(evc_ii.c:1716) | 周期调度总入口:轮询读、状态机两轮迭代、总功率与分配、轮询下发、通讯丢失判定 |
get_evc_ii_enable_flag | 全局(evc_ii.c:1969) | 任一桩使能则返回 TRUE;未使能桩的运行表被 memset 清空 |
get_evc_ii_chargemodel | 全局(evc_ii.c:1995) | 按 charge_en 与 startmode 折算上报云端的充电模式 |
get_evc_ii_type | 全局(evc_ii.c:2033) | 按已识别型号字符串返回型号,默认返回三相型号 |
get_evc_ii_power_total | 全局(evc_ii.c:2056) | 4 桩 s32_evc_ii_power_total 求和 |
get_evc_ii_energy_total | 全局(evc_ii.c:2074) | 4 桩 u32_evc_ii_energy_total 求和 |
get_evc_ii_gun_lock_flag | 全局(evc_ii.c:2091) | 4 桩枪锁状态各取 2bit 压入 1 字节 |
get_evc_ii_gun_lock_ctrl | 全局(evc_ii.c:2106) | 4 桩枪锁使能各取 1bit 压入 1 字节 |
clear_evc_ii_energy | 全局(evc_ii.c:2120) | 按 id 置清电量标记(0 = 全部桩,>4 = 全部清除并返回) |
set_evc_ii_charge_flag | 全局(evc_ii.c:2151) | 写入指定桩的云充停指令并置 cloud_charge_cmd_pending |
evc_ii_aes_encrypt | 全局(evc_ii.c:2173) | AES 加密 + 4 字节明文头(addr / 长度高 / 长度低 / 补齐+2)封装 |
4. 核心逻辑解析
4.1 通信协议帧格式(含加密分支)
明文帧(evc_ii_master_send_cmd,evc_ii.c:69-93)
+--------+--------+------------------+--------------+--------------+
| addr | fun | data[len] | CRC16 低字节 | CRC16 高字节 |
| id+1 | 0x03/10| | | |
+--------+--------+------------------+--------------+--------------+
CRC 由 GetCrcCode_Modbus() 生成、低字节先发(evc_ii.c:78-80)。
加密帧(u8_evc_ii_encryption_flag == TRUE 时,evc_ii.c:82-90)
先在原缓冲上补齐到 32 字节整数倍(evc_ii.c:2178-2185,补 0xFF),AES 加密得到密文,再拼装:
+--------+-------------+-------------+-------------+-------------------+
| addr | len_hi | len_lo | pad+2 | ciphertext |
| id+1 | (恒为 0) | (明文帧长-2)| 补齐字节数+2 | 32 字节对齐的密文 |
+--------+-------------+-------------+-------------+-------------------+
外层再追加一次 CRC16-Modbus(evc_ii.c:86-88)。接收侧(evc_ii.c:151-169)先校验外层 CRC,再按 (buf[1]<<8|buf[2]) + buf[3] 求得密文长度解密,最后对明文再校验一次 CRC——双重 CRC 保证加密隧道内的完整性。
命令一览
| 命令 | 功能码 | 起始寄存器 | 寄存器数 | 数据长度 | 响应判据 |
|---|---|---|---|---|---|
get_evc_ii_pravite_key | 0x03 | 0x0300 | 0x0010(16) | 4 | buf[2] == 32(evc_ii.c:488-491) |
get_evc_ii_id_info | 0x03 | 0x0100 | 0x0026(38) | 4 | buf[2] == 0x26*2(76)(evc_ii.c:550-553) |
get_evc_ii_run_info | 0x03 | 0x9100 | 0x0013(19) | 4 | buf[2] == 0x13*2(38)(evc_ii.c:622-625) |
send_cmd_evc_ii | 0x10 | 0x0100 | 0x000B(11) | 5+22 | 回显 01 00 00 0B 四字节(evc_ii.c:786-792) |
set_evc_ii_energy | 0x10 | 0x00A2 | 0x0002 | 5+4 | 回显起址与寄存器数(evc_ii.c:882-888) |
接收超时:evc_ii_master_recv_data 的超时窗口硬编码 500ms(evc_ii.c:115),失败时 *len = 0;buf[0] == 0(地址字节为 0 的无效帧)直接 return 且不回写 *len(evc_ii.c:142-145),依赖调用方在声明处初始化为 0。
4.2 寄存器映射
身份信息(0x0100 起 38 寄存器 → 76 字节,evc_ii.c:555-580)
| 偏移 | 长度 | 字段 |
|---|---|---|
[3..34] | 32B | u8_evc_ii_sn 序列号 |
[51..52] | 2B | u32_evc_ii_start_power_total 启动功率 |
[53..54] | 2B | u8_evc_ii_phase 相位数 |
[55..64] | 10B | 软件版本 |
[65..74] | 10B | 硬件版本 |
[75..78] | 4B | u32_evc_ii_max_power_total 最大功率(大端) |
型号不直接上报,由相数 + SN 第 12–14 字符推断(evc_ii.c:559-573):相数 3 → SMILE-G3-EVCT11/S;相数 1 且 SN 含 "134" → SMILE-G3-EVCS7/S;含 "138" → SMILE-G3-EVCS7-E/S。判定用 memcmp(...) == FALSE(即返回 0 表示相等)。
运行信息(0x9100 起 19 寄存器 → 38 字节,evc_ii.c:627-659):与一代完全相同的偏移布局(A/B/C 相电压电流、总功率 [7..10]、原始电量 [11..14]、故障字 [25][26][23][24] 拼装、CP 电压 [31..32]、占空比 [33..34]、状态字 [35..36]、可清电量 [37..40])。额外在 evc_ii.c:652-656 把 gun_lock_flag != 0 折算为 s_u8_gun_lock_fun 并或入 g_system_function_info.u_system_function1.bit.u8_evc_gun_lock,仅在状态变化时写入(避免反复或运算后永不清零)。
控制下发(0x0100 起 11 寄存器 → 22 字节,evc_ii.c:726-764)
| 寄存器 | data 下标 | 内容 |
|---|---|---|
| 0x0100–0x0101 | [5..8] | s32_evc_ii_set_power_total(大端 4B) |
| 0x0102–0x0103 | [9..12] | s32_evc_ii_set_power_a |
| 0x0104–0x0105 | [13..16] | s32_evc_ii_set_power_b |
| 0x0106–0x0107 | [17..20] | s32_evc_ii_set_power_c |
| 0x0108 | [21..22] | evc_ii_set_u.u16_all(remotcontrol + phasecontrol) |
| 0x0109 | [23..24] | s16_evc_ii_set_current 设定电流 |
| 0x010A | [25..26] | evc_ii_mode_contrl_u(chg_mode / plug_play_ctrl / gun_lock_ctrl) |
下发前在 send_cmd_evc_ii 内先根据 remotcontrol 生成充停指令(evc_ii.c:715-724):remotcontrol == 0 → u8_send_control_start_cmd_flag = 1 且 u8_charge_flag = EVC_II_START_CHARGE;否则 = 0 且 EVC_II_STOP_CHARGE。随后按功率反算设定电流(evc_ii.c:733-735):
s16_evc_ii_set_current = P_set_total * 10 / phasecontrol / (V_meter_a / 10)
4.3 主调度时序(evc_ii_control,evc_ii.c:1716)
由 interface.c:355 每 100ms 调用一次,内部四段:
1) 统计 l_u8_evc_cnt = 使能桩数 (1735-1741)
2) 清电量:逐桩置 0,set_evc_ii_energy() 最多重试 3 次 (1744-1757)
3) 每 1s(s_u32_recv_tick):
├─ 轮询 1 桩(s_u8_poll_id 轮转) (1763-1791)
│ 未取私钥 → get_evc_ii_pravite_key(i)
│ 已取私钥且 comm_flag == FALSE → get_evc_ii_id_info(i)
│ 已取私钥且 comm_flag == TRUE → get_evc_ii_run_info(i)
│ 未使能桩 → memset 运行表 + 刷新 comm_tick
├─ evc_ii_get_sys_data() (1794)
├─ i) 两轮遍历所有使能桩执行 evc_ii_update_charge_control_state (1797-1806)
├─ evc_ii_calculated_total_power() (1809)
├─ 若 l_u8_evc_cnt > 1:
│ total_available = calc_total + get_evc_ii_power_total() (1813)
│ evc_ii_apply_system_power_limit() (1818)
│ 14a 生效时二次限幅到 get_enwg_evc_power_limit() (1821-1826)
│ want_charge[] ← evc_ii_want_charge(i) (1829-1838)
│ evc_ii_distribute_power(...) (1841)
└─ 逐桩落功率:cnt>1 → parallel_calculate(EMS 硬约束) (1845-1860)
cnt==1 → calculate_single_station_power(本地)
4) 下发档(每 10000/l_u8_evc_cnt ms):
轮询 1 桩(s_u8_send_id 轮转),刷新 tags 后 send_cmd_evc_ii(i) (1884-1916)
5) 通讯丢失判定:≥20s 未刷新 comm_tick → 清运行数据 + b_comm_failure (1923-1950)
"两轮迭代状态机"(evc_ii.c:1797)是为了让刷卡/定时的两阶段确认标志(card_start/card_stop/timer_stop_flag)在同一周期内收敛:第一轮置位,第二轮依据更新后的 u8_send_control_start_cmd_flag 清位或反置。
多桩轮询的时间代价:1s 档每次只服务 1 桩(evc_ii.c:1763-1791 内 break),4 桩时每桩 4s 才读一次;下发档周期 10000/桩数,4 桩时每桩 10s 一次。通讯丢失门限为 20s(evc_ii.c:1927),比一代的 10s 宽,是为了容纳轮询分频。
4.4 充停状态机(evc_ii_update_charge_control_state,evc_ii.c:1100)
默认判禁:函数第一句即 remotcontrol = EVC_REMOTE_DENY(evc_ii.c:1103),后续只有满足特定条件才会改为 0(允许)。这是"默认安全"的设计。
云指令消费(evc_ii.c:1106-1110):若 cloud_charge_cmd_pending 为真,把 cloud_charge_cmd 写入 u8_charge_flag 并清 pending。
大前提:evc_ii_state_u.BIT.charge_en == TRUE(枪已连接)。否则把四分相功率、总功率清零,u8_charge_flag = EVC_II_DEFAULT_CHARGE,timer_stop_flag = 0(evc_ii.c:1254-1262)。
进入子状态机的前提(evc_ii.c:1114-1119):
u8_charge_flag != EVC_II_DEFAULT_CHARGE(0)
或 startmode == CARD_START(2)
或 startmode == PLUG_PLAY_START(3)
或 u8_time1_flag == 0 或 u8_time2_flag == 0 或 u8_time3_flag == 0
满足后先置 remotcontrol = 0,再按下列分支逐条裁决:
| 控制方式 | 触发条件(摘要) | 动作 | ||
|---|---|---|---|---|
| MANUAL_SETTING | startmode==2 && cardflag==1 && u8_charge_flag!=2 && card_start==0 | 刷卡开始:u8_send_control_start_cmd_flag==1 时置 card_start=1, card_stop=0(1122-1133) | ||
| MANUAL_SETTING | startmode==2 && cardflag==1 && u8_charge_flag==1 && card_stop==0 | 刷卡开始但 EMS 已停:remotcontrol=1;确认后清 card_start/card_stop(1134-1146) | ||
| MANUAL_SETTING | startmode==0 && cardflag==2 && u8_charge_flag==2 && card_stop==1 | 刷卡停止、EMS 要开:remotcontrol=0,确认后清标志(1147-1159) | ||
| MANUAL_SETTING | `startmode==0 && cardflag==2 && u8_charge_flag==2 && (card_stop==0 \ | \ | card_start==1)` | 刷卡停止:remotcontrol=1,确认后置 card_stop=1(1160-1172) |
| TIMING_CHARGE | 三段标志全部 != 0(全在段外) | 定时停充:remotcontrol=1、u8_charge_flag=1、清各标志(1173-1183) | ||
| TIMING_CHARGE | 任一标志 == 0 且 timer_stop_flag==0 | 定时启动:remotcontrol=0,确认后 timer_stop_flag=1(1184-1197) | ||
| TIMING_CHARGE | 段内有刷卡/EMS 停止意图且 timer_stop_flag==1 | remotcontrol=1,确认后 card_stop=1、timer_stop_flag=2(1198-1215) | ||
| TIMING_CHARGE | 段内且 timer_stop_flag==2 | 维持停充:remotcontrol=1、u8_charge_flag=1(1216-1226) | ||
| MANUAL_SETTING | u8_charge_flag==1 && startmode!=3 | EMS 停止:remotcontrol=1(1227-1233) | ||
| 任意 | meter1_lost == TRUE | 空分支:保持默认禁充(1234-1237) |
timer_stop_flag 是 0→1→2 的三态机(0 未启动、1 已启动、2 段内被停),与 card_start/card_stop 一起构成"指令在途"缓存,避免同一物理动作被反复下发。
4.5 总可用功率(evc_ii_calculated_total_power,evc_ii.c:906)
单相剩余可充功率(evc_ii.c:922-924,与一代同式):
P_phaseX = V_meterX * u16_max_consume_cur * 8 / 1000 + P_meterX
其中 l_u16_max_consume_cur 取自 g_t_evc_config_table.u16_max_consume_cur(一代/二代共用的整体配置表,evc_ii.c:914),即一/二代桩共享"入户最大电流"配置。
电池限值裁剪(evc_ii.c:943-985)与一代同构但更严谨:削峰模式的判据为 u8_bat_soc <= u8_peakshaving_discharg_cut_soc(evc_ii.c:958),比 evc.c:666 的写法正确。
策略分支(switch (s_t_evc_ii_control_para.u8_evc_plot),evc_ii.c:991):SLOW / NORMAL / QUICK / SPECIFIED_POWER 的公式与一代 evc_calculated_total_power 逐字对应(见 evc.c 文档 4.4 节),仅数据源从 s_t_sys_data 换成 s_t_evc_ii_sys_data、以及 l_u8_sys_plot 来自"整体策略"而非全局配置。
4.6 多桩功率分配(evc_ii_distribute_power,evc_ii.c:1295)
这是二代相对一代最重要的新增算法,输入 total_available_power、输出每桩硬上限 p_limit_power[] 与允许标志 p_enable[]。
步骤 1:收集信息(evc_ii.c:1314-1331)
- 未使能桩
max_power = start_power = 0; max_power[i] = evc_ii_get_max_allowed_power(i);start_power[i] = u32_evc_ii_start_power_single_phase * phasecontrol * V_meter_a / NOMINAL_VOLTAGE_V;charging_flag[i] = (实际功率 > 200W)→ 视为"稳定充电中"。
步骤 2–3:初始化与候选筛选(evc_ii.c:1334-1350):p_limit_power[] = 0、p_enable[] = FALSE;want_charge[i] 为真的桩进入 candidates[];无候选直接返回(退回全停)。
步骤 4:排序(evc_ii.c:1353-1379)——冒泡式两两比较,排序键为二元:
charging_flag为真者优先(保证已经充上的桩不被挤掉);- 同状态时按
start_power升序(先满足启动功率小的桩)。
步骤 5–6:启动功率预留机制(evc_ii.c:1382-1410)
if (total_available >= Σ start_power) → 全部桩可启动,reserved = Σ start_power
else 逐个累加 start_power,直到超出可用功率为止 → actual_started = 能负担的桩数
if (actual_started == 0) return; → 一台都启动不了则全停
步骤 7–9:功率分配(evc_ii.c:1413-1461)
dynamic_power = total_available - reserved_power (扣除启动预留后的可自由分配功率)
先给每桩 p_limit_power = start_power (保证启动门槛)
若 dynamic_power > 0:
per_alloc = dynamic_power / actual_started
remainder = dynamic_power % actual_started
逐桩 add = per_alloc + (remainder > 0 ? 1 : 0),并按 max_power[i] - p_limit_power[i] 裁剪
裁剪后仍有剩余(dynamic_power > 0)→ 第二轮按剩余容量补给
算法的工程意义:充电桩启动需要达到 start_power 门槛,若把总功率平均分成 N 份,每份都可能低于门槛导致所有桩都充不上。因此先按门槛"预留",再分配余量,并且优先保证已充上的桩——避免多桩之间反复启停震荡。
4.7 单桩功率落地
并机模式(evc_ii_parallel_calculate_single_station_power,evc_ii.c:1530)
evc_ii_prepare_power_limit(id, &max_cur)做电流/相数/枪锁预处理;- 停止条件:
remotcontrol == DENY或ems_enable == FALSE或ems_limit_power <= 0→ 四分相与总功率清零返回(evc_ii.c:1539-1548); - 计算本地理想分相功率(
V * I / 100 + P_avail_phase,evc_ii.c:1551-1559)——注释明确写"保留,后期优化",实际未参与最终值; l_s32_final_power = ems_limit_power;仅当策略为SPECIFIED_POWER时再按V * max_cur * phasecontrol / 100限流并+= phasecontrol * 50(evc_ii.c:1564-1573);evc_ii_set_power_total_limit(id)做最终裁剪。
单桩模式(evc_ii_calculate_single_station_power,evc_ii.c:1586)
与一代 evc_calculate_single_station_power 同构:计算启动功率 P_start = start_power_single_phase * phasecontrol * V_meter_a / 2300(此处写死 2300,未用 NOMINAL_VOLTAGE_V),正常充电时 P_set = max(P_actual, P_start) + P_avail,并运行条件充电延时状态机(evc_ii.c:1659-1689):
| 状态 | 条件 | 动作 |
|---|---|---|
| 任意 | P_set + 500 < P_start | 刷新 charge_dealy_tick,状态 = NO_CONDITION_CHARGE;若 u8_smart_mode == TRUE 则强制相数降为 1(evc_ii.c:1665-1666) |
CHARGE_NORMAL 或(CONDITION_CHARGE 且 < 3min) | P_start - 500 <= P_set < P_start | P_set += 500,状态 = CONDITION_CHARGE |
CONDITION_CHARGE 且 ≥ 3min | 同上 | 刷新 tick,状态 = NO_CONDITION_CHARGE |
P_set >= P_start | — | 刷新 tick,状态 = CHARGE_NORMAL |
u8_smart_mode 分支是二代独有的"降低启动门槛"策略:功率不足时用单相充电以提高成功率。
公共预处理(evc_ii_prepare_power_limit,evc_ii.c:1486)
max_evc_cur = (u8_evc_ii_phase == 3) ? 160 : 320 // 三相桩每相 16A,单相桩 32A
if (max_cur == 0 || max_cur > max_evc_cur) max_cur = max_evc_cur
if (single_config[id].u8_ev_phase == 0) u8_ev_phase = u8_evc_ii_phase // 缺省填充
if (三相电压不全部 > 1000 且 A 相 > 1000) u8_ev_phase = 1 // 电压异常强制单相
phasecontrol = u8_ev_phase
gun_lock_ctrl = (u8_gun_lock_ctrl == TRUE) ? 2 : 1
4.8 通讯丢失与运行数据清理
evc_ii.c:1923-1950:逐桩判断 (OSTimeGet() - u32_evc_ii_comm_tick) >= 20s:
- 首次进入时保存
u8_charge_flag与charge_en; memset(&u8_evc_ii_phase+1, 0, (u8*)&u32_evc_ii_comm_tick - &u8_evc_ii_phase - 1)(evc_ii.c:1934)一次清掉中间所有运行字段;- 写回保存的两项;置
evc_ii_fault_u.bit.b_comm_failure = TRUE(该标志进入故障日志,见fault_warn_log.c:1176)。
未使能桩在 evc_ii.c:1947 与 evc_ii.c:1981 被 memset 清零后把 comm_tick 刷新为当前值,因此不会被误判为通讯故障(evc_ii.c:1952-1958 再把其 b_comm_failure 强制清 0)。
4.9 加密协商流程
首次轮询(get_prvite_key_flag == FALSE)
get_evc_ii_pravite_key(id)
├─ memset(&aesInfo[id].key, 0, 32); memcpy(key, PUBLIC_KEY, 32); AES_data_Init(id); AESInit()
├─ 发 0x03 读 0x0300×16
├─ 收到 32 字节 → AESDecrypt(PUBLIC_KEY 上下文) → aes_key → 回填 aesInfo[id].key → AESInit()
│ → u8_evc_ii_encryption_flag = TRUE, get_prvite_key_flag = TRUE, comm_tick 刷新
└─ 收到但内容不符 → encryption_flag = FALSE, get_prvite_key_flag = TRUE(降级为明文)
get_prvite_key_flag == TRUE 后永久不再重试(evc_ii.c:1768-1770)
升级流程也会在进入时调 get_evc_ii_pravite_key(i),最多重试 3 次(evc_upgrade.c:466-473),成功则 u8_evc_ii_encryption_flag[i] = TRUE 让升级帧同样走加密(evc_upgrade.c:65-74)。
5. 对外接口
本文件导出的函数(供谁调用)
| 导出函数 | 调用方 |
|---|---|
evc_ii_control | midware/interface/interface.c:355 |
get_evc_ii_enable_flag | interface.c:331 |
get_evc_ii_power_total | data/resume_data/flash/flash_data_store.c:673、logic/logic.c:165、net/ems.c:540、本文件 evc_ii_control |
get_evc_ii_energy_total | flash_data_store.c:1037、midware/interface/modbus/modbus.c:937/948、net/ems.c:764 |
get_evc_ii_chargemodel | modbus.c:960/1191/1422/1653、net/ems.c:1045 |
get_evc_ii_type | net/ems.c:3019/3100、net/server.c:3309/3710 |
get_evc_ii_gun_lock_flag | net/ems.c:1458 |
get_evc_ii_gun_lock_ctrl | net/ems.c:1979 |
clear_evc_ii_energy | net/server.c:1226 |
set_evc_ii_charge_flag | net/server.c:1776 |
get_evc_ii_pravite_key | evc_upgrade.c:468 |
evc_ii_aes_encrypt | evc_upgrade.c:67 |
本文件导出的全局变量
g_t_evc_ii_single_config[4]、g_t_evc_ii_run_info[4](evc_ii.h:371-372);被 system_config.c:861 初始化、被 fault_warn_log.c:1147-1187 读故障字、被 evc_upgrade.c:449/715 读写。
本文件调用的外部函数/变量
| 来源模块 | 符号 |
|---|---|
hardware/usart3 | uart3_clear_buffer、uart3_send_data、uart3_is_recv_async_data、uart3_get_data_len、uart3_read_data_async |
common/crc | GetCrcCode_Modbus |
common/aes | aesInfo[4]、PUBLIC_KEY、aes_key、AES_data_Init、AESInit、AESDecrypt、AESEncrypt |
logic/logic.c | logic_read_sequencecontrol_mode、logic_read_charger_cut_soc、logic_read_ups_reserve_soc、logic_read_feedin_cut_soc、logic_read_allow_discharge_cut_soc、logic_read_peakshving_discharge_cut_soc、get_enwg_evc_power_limit、g_t_logic_info |
midware/meter | meter_read_voltage、meter_read_actpower、GRID_METER、PV_METER、PHASE_A/B/C/T |
data/system_config | g_t_evc_config_table(一代配置表,读入户电流)、g_system_function_info、SYSTEM_DATETIME |
net/ems.c | Set_Setup_Config_Extend1_Status |
| uCOS-II | OSTimeGet |
| C 库 | memcpy、memset、memcmp、printf、abs |
6. 依赖关系
- 依赖:
evc_ii.h、evc.h(复用收发缓存与EVC_BUF_LEN)、evc_download.h、usart3.h、crc.h、system_aux.h、logic.h、meter.h、ems.h、aes.h、system_config.h(evc_ii.c:13-23)。 - 被依赖:
midware/interface/interface.c、midware/interface/modbus/modbus.c、data/system_config/system_config.c、data/fault_warn_log/fault_warn_log.c、data/resume_data/flash/flash_data_store.c、logic/logic.c、net/ems.c、net/server.c、midware/evc/evc_upgrade.c。
7. 备注与风险点
- AES 解密写入
l_u8_recv_data[90]可能越界(高危):evc_ii.c:109声明static u8 l_u8_recv_data[90],而evc_ii.c:154以((buf[1]<<8|buf[2]) + buf[3])作为长度调用AESDecrypt。以身份信息响应为例:明文帧1+1+1+76+2 = 81字节,补齐到 32 整数倍需 +15 字节,故密文长度= (81-2) + (15+2) = 96 > 90。即加密桩首次读取 ID 信息时,解密会写出 96 字节,覆盖该静态数组之后的 6 字节内存。运行信息响应(43B→64B)与私钥响应(37B→64B)不越界,问题只在 76 字节的身份响应上暴露。 evc_ii_aes_encrypt的长度字段高字节恒为 0(evc_ii.c:2190):(u8)((len-2) & 0xff) >>8先把结果截断为u8再右移 8 位,恒得 0。接收侧evc_ii.c:153的buf[1]<<8 | buf[2] + 2又因+优先级高于|而实际等于(buf[1]<<8) | (buf[2]+2),两处不规范的写法恰好互相抵消而能工作,但任何一侧"修正"都会破坏兼容性。evc_ii_aes_encrypt就地加解密:evc_ii.c:84把s_evc_ii_send_buf同时作为源与目标传入。当前实现先用局部l_u8_enc_data[256]承接密文再拷贝,因此安全;但l_u8_enc_data[256]是任务栈上的 256 字节局部数组(evc_ii.c:2177),需确认 uCOS-II Modbus/RTC 任务的栈空间足够,否则栈溢出难以定位。- 接收缓冲无地址范围校验:
evc_ii.c:151、evc_ii.c:137(在evc_upgrade.c)用buf[0]-1直接索引g_t_evc_ii_run_info[](长度 4)与u8_evc_ii_encryption_flag[](长度 4)。若因噪声导致 CRC 偶然匹配且地址字节 > 4,将越界读写。 - 除零风险:
evc_ii.c:579-580直接u32_evc_ii_start_power_total / u8_evc_ii_phase,桩上报相数 0 时结果错误;evc_ii.c:733-735的设定电流除以phasecontrol与(V_meter_a/10),若phasecontrol == 0(配置相数为 0 且桩相数也为 0)或电表电压为 0,同样除零。evc_ii_prepare_power_limit的相数填充保护发生在单桩功率计算之前(evc_ii.c:1497-1500),但send_cmd_evc_ii的电流反算在之后,时序上受保护;ID 信息解析处无保护。 evc_ii_master_recv_data的*len回写不一致:evc_ii.c:142-145在buf[0] == 0时直接return而不写*len;调用方靠"声明即初始化 0"侥幸正确。若将来有人复用变量而不重置,会读到上一帧长度。- 每 100ms 的冗余
memset:get_evc_ii_enable_flag()(evc_ii.c:1981)在未使能时清空运行表,而interface.c:331每轮循环都会调用它。未使能桩的运行表被高频清零,虽无害但浪费 CPU;同时若某桩刚被禁用,其故障/统计信息会立即丢失。 - 策略来源不确定:
s_t_evc_ii_control_para.u8_evc_plot被"最后一个charge_en == TRUE的桩"覆盖(evc_ii.c:432-435),多桩策略不一致时整体策略取决于遍历顺序,evc_ii_want_charge与evc_ii_distribute_power都依赖它。 evc_ii_calculate_single_station_power在并机时不执行(evc_ii.c:1856仅在l_u8_evc_cnt == 1调用),意味着u8_smart_mode的"功率不足降为单相"策略、以及单桩版条件充电延时状态机在 2 桩及以上时完全不生效。两套功率路径的行为差异较大(并机路径没有P_actual < 150死区判断、也没有chargingfull时+200的启动补偿),同一款桩在 1 桩/2 桩场景下体验会不一致。evc_ii_want_charge不含"已充满可再充"判断:chargingfull == TRUE直接返回 FALSE(evc_ii.c:1277),但在evc_ii_parallel_calculate_single_station_power里并没有对应的+200补偿逻辑(该补偿只在单桩路径evc_ii.c:1615-1618存在),多桩场景下充满的车可能无法重新启动。- 未使用的函数形参:
evc_ii_get_evc_data的id、evc_ii_set_power_total_limit之外多个函数存在"仅取功率不取相位"的不对称处理,evc_ii.c:1566的注释("14a EV 总额度已在 evc_ii_distribute_power 入口处对 total_available 限幅,此处不再按桩重复限幅")与紧随其后仍在按桩限流的代码(evc_ii.c:1567-1572)存在语义冲突,属版本迭代遗留。 evc_ii_distribute_power的排序是 O(n²) 冒泡且未稳定:candidates[]在交换时只换 id,charging_flag相同时按start_power升序;由于比较的是start_power而非"剩余容量",且排序在预留计算之前完成,可能出现"启动功率小的桩先启动、但之后拿不到足够余量"的情况——这是设计取舍而非缺陷,但值得在调参时注意。- 魔法数字密集:
200W(稳定充电判定 / 充满启动补偿,evc_ii.c:1329/1617)、500W(条件充电压差与补偿,evc_ii.c:1661/1674)、150W(功率死区,evc_ii.c:1632)、3*60*1000(3 分钟宽限,evc_ii.c:1672/1678)、2*60*1000(2 分钟不下调窗口,evc_ii.c:1036)、160/320A(evc_ii.c:1488)、230*16*3/230*32*3(evc_ii.c:1474/1476)、*8/1000(evc_ii.c:922)、92/100(evc_ii.c:1000/1003/1035)、2300(evc_ii.c:1608,同文件已定义NOMINAL_VOLTAGE_V却未使用),全部为裸常量无宏定义。 - 注释与代码不一致:
evc_ii.c:1566声称"此处不再按桩重复限幅"但下面立刻限幅;evc_ii.c:1855注释"先更新充电控制状态(与多桩保持一致的状态机)"但该状态机已在evc_ii.c:1803的循环里执行过,属重复描述。 - 拼写与命名遗留:
charge_dealy_tick(应为 delay,evc_ii.c:1663等)、get_evc_ii_pravite_key(应为 private,evc_ii.c:470)、u8_evc_ii_pravite_key[32](evc_ii.h:311)全工程沿用,属历史命名。