EMS 知识库
首页 / knowledge/代码解析/midware/evc

evc_ii.c

源文件路径: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_bufstatic 指针直接指向 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_datastatic(EVC_II_SYS_DATA_T)系统侧输入快照(evc_ii.c:34)
s_t_evc_ii_control_parastatic(EVC_II_CONTROL_PARA_T)整体策略 u8_evc_plot、整体最大电流、A/B/C 相与总可充功率(evc_ii.c:35)
s_evc_ii_rtc_datetimestatic(struct RTC_DateTime)RTC 副本,用于三段充电时间判定(evc_ii.c:36)
SYSTEM_DATETIMEextern系统全局时间(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_keyextern(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_lockextern 位域枪锁状态上报标志,由 gun_lock_flag 折算(data/system_config/system_config.h:202)
g_t_logic_info.u8_14a_enwg_activeextern14a/ENWG 是否真正生效,生效时 EV 总额度受 get_enwg_evc_power_limit() 二次限幅(logic/logic.h:602、evc_ii.c:1821)

3. 函数清单

函数名作用域说明
evc_ii_master_send_cmdstatic(evc_ii.c:69)组 Modbus-RTU 帧;若该桩加密标记为真则整体 AES 加密并重新附加 CRC,再经 uart3_send_data 发出
evc_ii_master_recv_datastatic(evc_ii.c:102)500ms 窗口接收;先校验外层 CRC,加密桩再解密并二次校验 CRC
evc_ii_charge_timestatic(evc_ii.c:193)三段充电时间段的分钟刻度判定,输出 u8_time1/2/3_flag(0 段内 / 1 段外 / 2 无效)
evc_ii_get_evc_datastatic(evc_ii.c:329)按 u8_charge_tactics 决定当前生效的充电策略与最大电流,并设置 chg_mode/plug_play_ctrl
evc_ii_get_sys_datastatic(evc_ii.c:396)抓取系统快照 + 逐桩刷新策略,并把最后一个可充桩的策略提升为整体策略
get_evc_ii_pravite_key全局(evc_ii.c:470)读 0x0300 起 16 寄存器,用 PUBLIC_KEY 解密出桩私钥存入 aesInfo[id],置加密标记
get_evc_ii_id_infostatic(evc_ii.c:537)读 0x0100 起 38 寄存器:SN、启动/最大功率、相数、软/硬件版本,并按相数与 SN 推断型号
get_evc_ii_run_infostatic(evc_ii.c:609)读 0x9100 起 19 寄存器:三相电压电流、总功率、电量、故障字、CP 电压/占空比、状态字
send_cmd_evc_iistatic(evc_ii.c:710)写 0x0100 起 11 寄存器:总/分相设定功率、控制字、设定电流、模式控制字
evc_ii_set_power_total_limitstatic(evc_ii.c:811)设定功率的上下限裁剪:负值归 0,总值 ≤ 单相最大 × 相数,分相值 ≤ 单相最大
set_evc_ii_energystatic(evc_ii.c:863)写 0x00A2 起 2 寄存器为 0,清指定桩电量
evc_ii_calculated_total_powerstatic(evc_ii.c:906)计算系统侧 EV 总可用功率,含电池充放限值裁剪与四种策略分支
evc_ii_get_max_allowed_powerstatic(evc_ii.c:1085)单桩功率硬上限 = min(硬件单相最大×相数, V×最大电流×相数/100)
evc_ii_update_charge_control_statestatic(evc_ii.c:1100)单桩充停状态机:云指令消费、刷卡启停两阶段确认、定时充停、remotcontrol 最终裁决
evc_ii_want_chargestatic(evc_ii.c:1272)判断单桩"真实充电意愿"(已连接、未充满、未被禁止)
evc_ii_distribute_powerstatic(evc_ii.c:1295)多桩功率分配核心:候选筛选 → 稳定充电优先排序 → 启动功率预留 → 动态功率均分与二次填充
evc_ii_apply_system_power_limitstatic(evc_ii.c:1469)系统级硬件限幅:按并机限流标志取 16A 或 32A 对应的 3 相总功率
evc_ii_prepare_power_limitstatic(evc_ii.c:1486)单桩预处理:电流上限裁剪、相数缺省填充、电压异常强制单相、设置 phasecontrol 与 gun_lock_ctrl
evc_ii_parallel_calculate_single_station_powerstatic(evc_ii.c:1530)并机模式下按 EMS 硬约束写单桩设定功率(不做本地状态机)
evc_ii_calculate_single_station_powerstatic(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_key0x030x03000x0010(16)4buf[2] == 32(evc_ii.c:488-491)
get_evc_ii_id_info0x030x01000x0026(38)4buf[2] == 0x26*2(76)(evc_ii.c:550-553)
get_evc_ii_run_info0x030x91000x0013(19)4buf[2] == 0x13*2(38)(evc_ii.c:622-625)
send_cmd_evc_ii0x100x01000x000B(11)5+22回显 01 00 00 0B 四字节(evc_ii.c:786-792)
set_evc_ii_energy0x100x00A20x00025+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]32Bu8_evc_ii_sn 序列号
[51..52]2Bu32_evc_ii_start_power_total 启动功率
[53..54]2Bu8_evc_ii_phase 相位数
[55..64]10B软件版本
[65..74]10B硬件版本
[75..78]4Bu32_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_SETTINGstartmode==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_SETTINGstartmode==2 && cardflag==1 && u8_charge_flag==1 && card_stop==0刷卡开始但 EMS 已停:remotcontrol=1;确认后清 card_start/card_stop(1134-1146)
MANUAL_SETTINGstartmode==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==1remotcontrol=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_SETTINGu8_charge_flag==1 && startmode!=3EMS 停止: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)

步骤 2–3:初始化与候选筛选(evc_ii.c:1334-1350):p_limit_power[] = 0、p_enable[] = FALSE;want_charge[i] 为真的桩进入 candidates[];无候选直接返回(退回全停)。

步骤 4:排序(evc_ii.c:1353-1379)——冒泡式两两比较,排序键为二元:

  1. charging_flag 为真者优先(保证已经充上的桩不被挤掉);
  2. 同状态时按 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)

  1. evc_ii_prepare_power_limit(id, &max_cur) 做电流/相数/枪锁预处理;
  2. 停止条件:remotcontrol == DENY 或 ems_enable == FALSE 或 ems_limit_power <= 0 → 四分相与总功率清零返回(evc_ii.c:1539-1548);
  3. 计算本地理想分相功率(V * I / 100 + P_avail_phase,evc_ii.c:1551-1559)——注释明确写"保留,后期优化",实际未参与最终值;
  4. l_s32_final_power = ems_limit_power;仅当策略为 SPECIFIED_POWER 时再按 V * max_cur * phasecontrol / 100 限流并 += phasecontrol * 50(evc_ii.c:1564-1573);
  5. 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_startP_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:

  1. 首次进入时保存 u8_charge_flag 与 charge_en;
  2. memset(&u8_evc_ii_phase+1, 0, (u8*)&u32_evc_ii_comm_tick - &u8_evc_ii_phase - 1)(evc_ii.c:1934)一次清掉中间所有运行字段;
  3. 写回保存的两项;置 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_controlmidware/interface/interface.c:355
get_evc_ii_enable_flaginterface.c:331
get_evc_ii_power_totaldata/resume_data/flash/flash_data_store.c:673、logic/logic.c:165、net/ems.c:540、本文件 evc_ii_control
get_evc_ii_energy_totalflash_data_store.c:1037、midware/interface/modbus/modbus.c:937/948、net/ems.c:764
get_evc_ii_chargemodelmodbus.c:960/1191/1422/1653、net/ems.c:1045
get_evc_ii_typenet/ems.c:3019/3100、net/server.c:3309/3710
get_evc_ii_gun_lock_flagnet/ems.c:1458
get_evc_ii_gun_lock_ctrlnet/ems.c:1979
clear_evc_ii_energynet/server.c:1226
set_evc_ii_charge_flagnet/server.c:1776
get_evc_ii_pravite_keyevc_upgrade.c:468
evc_ii_aes_encryptevc_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/usart3uart3_clear_buffer、uart3_send_data、uart3_is_recv_async_data、uart3_get_data_len、uart3_read_data_async
common/crcGetCrcCode_Modbus
common/aesaesInfo[4]、PUBLIC_KEY、aes_key、AES_data_Init、AESInit、AESDecrypt、AESEncrypt
logic/logic.clogic_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/metermeter_read_voltage、meter_read_actpower、GRID_METER、PV_METER、PHASE_A/B/C/T
data/system_configg_t_evc_config_table(一代配置表,读入户电流)、g_system_function_info、SYSTEM_DATETIME
net/ems.cSet_Setup_Config_Extend1_Status
uCOS-IIOSTimeGet
C 库memcpy、memset、memcmp、printf、abs

6. 依赖关系

7. 备注与风险点

  1. 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 字节的身份响应上暴露。
  2. 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),两处不规范的写法恰好互相抵消而能工作,但任何一侧"修正"都会破坏兼容性。
  3. 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 任务的栈空间足够,否则栈溢出难以定位。
  4. 接收缓冲无地址范围校验: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,将越界读写。
  5. 除零风险: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 信息解析处无保护。
  6. evc_ii_master_recv_data 的 *len 回写不一致:evc_ii.c:142-145 在 buf[0] == 0 时直接 return 而不写 *len;调用方靠"声明即初始化 0"侥幸正确。若将来有人复用变量而不重置,会读到上一帧长度。
  7. 每 100ms 的冗余 memset:get_evc_ii_enable_flag()(evc_ii.c:1981)在未使能时清空运行表,而 interface.c:331 每轮循环都会调用它。未使能桩的运行表被高频清零,虽无害但浪费 CPU;同时若某桩刚被禁用,其故障/统计信息会立即丢失。
  8. 策略来源不确定: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 都依赖它。
  9. 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 桩场景下体验会不一致。
  10. evc_ii_want_charge 不含"已充满可再充"判断:chargingfull == TRUE 直接返回 FALSE(evc_ii.c:1277),但在 evc_ii_parallel_calculate_single_station_power 里并没有对应的 +200 补偿逻辑(该补偿只在单桩路径 evc_ii.c:1615-1618 存在),多桩场景下充满的车可能无法重新启动。
  11. 未使用的函数形参: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)存在语义冲突,属版本迭代遗留。
  12. evc_ii_distribute_power 的排序是 O(n²) 冒泡且未稳定:candidates[] 在交换时只换 id,charging_flag 相同时按 start_power 升序;由于比较的是 start_power 而非"剩余容量",且排序在预留计算之前完成,可能出现"启动功率小的桩先启动、但之后拿不到足够余量"的情况——这是设计取舍而非缺陷,但值得在调参时注意。
  13. 魔法数字密集: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 却未使用),全部为裸常量无宏定义。
  14. 注释与代码不一致:evc_ii.c:1566 声称"此处不再按桩重复限幅"但下面立刻限幅;evc_ii.c:1855 注释"先更新充电控制状态(与多桩保持一致的状态机)"但该状态机已在 evc_ii.c:1803 的循环里执行过,属重复描述。
  15. 拼写与命名遗留: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)全工程沿用,属历史命名。