BR_YKC 充电桩网关
基于 STM32H743 + AIR724 4G模块 的充电桩网关方案,对接云快充平台,支持通过4G TCP和以太网UDP双通道通信。
目录
硬件架构
┌──────────────────────────────────────────────┐
│ STM32H743 │
│ │
│ UART1 ─── 4G模块(AIR724) ──→ 云快充平台 (TCP)│
│ UART3 ─── RS485 │
│ ETH ──── LAN (UDP, 桩1~6 + 上位机) │
│ UART6 ─── 调试串口(printf) │
└──────────────────────────────────────────────┘
| 外设 | 用途 |
|---|---|
| UART1 (115200) | 与AIR724 4G模块通信 |
| UART3 | RS485接口 |
| ETH (LWIP) | 以太网,桩1~6 UDP通信 + 上位机 |
| UART6 | printf调试输出 |
软件架构
Os/ 任务/队列/信号量/定时器管理
├─ os_task.c 5个任务的创建
├─ os_queue.c 3个消息队列
├─ os_timer.c 软件定时器
└─ os_semaphore.c 信号量/互斥量
App/ 应用层任务
├─ task_air724 4G下行数据解析任务
├─ task_udp UDP收发 + 解析任务
├─ task_ykc 云快充核心状态机任务
└─ task_sys 系统任务(心跳指示、喂狗)
Driver/ 驱动层
├─ drv_air724 AIR724命令收发 + 响应解析
├─ drv_usart 串口收发(含互斥锁)
├─ drv_flash 通用Flash读写框架
└─ flash_config 应用层Flash配置
Network/ 网络层
├─ udp_manager UDP报文发送
└─ udp_router UDP路由分发
Protocol/ 协议层
├─ Point/ 南向(充电桩JSON)
├─ Host Computer/ 上位机JSON通信
└─ Ykc/ 北向(云快充字节协议)
├─ charger_to_server 上行帧
├─ server_to_charger 下行帧
├─ server_common 公共(组包/解包/CRC)
└─ ykc_router 帧类型路由分发
Global/ 全局数据
├─ g_dcpile 桩/枪 结构体 + 管理器
├─ g_init 全局初始化
├─ g_runtime 运行时统计
└─ board_config 板级配置(IP/端口)
4G/ 4G模块Lua固件
├─ core/main.lua 入口:初始化+串口+网络
├─ core/cmd.lua 命令解析+分发
└─ core/linksocket.lua 多路TCP socket管理
通信拓扑
云快充服务器(TCP)
↑
│ 4G
AIR724模块
↑ UART1
┌─────────┴──────────┐
│ STM32H743 网关 │
│ (协议转换器) │
└─────────┬──────────┘
UDP │ 以太网
┌─────────────┼─────────────┐
│ │ │
桩1~6 上位机 RS485设备
(10.12.19.101~106) (107) (触摸屏等)
数据路径:
- 云快充 → 桩:云快充TCP → 4G → UART1 → STM32解析 → 填充数据到桩结构体
- 桩上报 → 云快充:桩 → UDP → STM32 → 打包Ykc帧 → UART1 → 4G → TCP → 云快充
- 上位机查询:上位机 → UDP → STM32 → 查询桩数据 → 回复
三层状态机
task_ykc.c 实现了 桩层 + 枪层 两层非阻塞时间片状态机:
桩层初始化(桩0~2通用,只做一次)
step 0 ── 等待桩UDP上电 → 连接4G通道(i+1)
↓ (socket_connected[i] == true)
step 1 ── 发送云快充登录(0x01)
↓ (is_online == true)
step 2 ── 发送计费模型验证(0x05) + 请求(0x09)
↓ (get_model == true)
step 3 ── 进入枪层运行态
枪层运行(每把枪独立)
gun_step 0 ── 每5秒上传实时数据(0x13) + 主动上报到上位机
如有账单(is_get_bill==true),上传交易记录(0x3B)
非阻塞机制
所有 osDelay() 已被替换为时间戳轮询:
now = HAL_GetTick();
if (now < pile->step_tick) // 时间没到,立即跳过,不阻塞
continue;
// 执行动作
pile->step_tick = now + 5000; // 设定5秒后的下次超时
6个桩每轮循环只需微秒级完成,互不阻塞。
通信协议
南向协议(网关 ↔ 桩)
- 传输:以太网 UDP
- 端口:Server=6001, Stake=6001, App=6002
- 桩IP:10.12.19.101 ~ 106
- 格式:JSON
路由表 udp_router.c:
| cmd | 桩ID来源 | 处理函数 | 说明 |
|---|---|---|---|
online |
桩1~6 | point_callback_power_on |
上电,设置 is_udp_online |
heartbeat |
桩1~6 | point_callback_heartbeat |
心跳 |
start charging |
桩1~6 | point_callback_start_charging |
开始充电反馈 |
end charging |
桩1~6 | point_callback_end_charging |
停止充电反馈 |
realtime data |
桩1~6 | point_callback_realtime_data |
实时数据 |
settlement bill |
桩1~6 | point_callback_settlement_bill |
结算账单 |
proactive end charging |
桩1~6 | point_callback_proactive_end_charging |
桩主动停止 |
charge process real |
桩1~6 | point_callback_charge_process |
BMS需求 |
bms info real |
桩1~6 | point_callback_bms_info |
BMS信息 |
桩未上电拦截:除 online 外,所有桩指令都会检查 is_udp_online,未上电的桩指令将被静默丢弃。
上位机协议(网关 ↔ 上位机APP)
- 传输:以太网 UDP(同一通道)
- 端口:Server=6001, App=6002
- 上位机ID:7
- 格式:JSON,含
request_id+success
| cmd | 功能 | 处理函数 | 回复字段 |
|---|---|---|---|
server_login |
登录 | host_computer_on_login |
success |
server_get_status |
获取所有桩状态 | host_computer_on_get_status |
piles[].serial/is_online/guns[].status |
server_reboot |
重启网关 | host_computer_on_reboot |
无(立即重启) |
gateway_get_info |
查询网关信息 | host_computer_on_get_gw_info |
device_id, software_ver, hardware_ver, uptime |
gateway_get_4g_status |
查询4G状态 | host_computer_on_get_4g_status |
iccid, net_status, signal, isp |
gateway_get_cloud_config |
查询云配置 | host_computer_on_get_cloud_config |
host, port |
gateway_set_cloud_config |
设置云配置 | host_computer_on_set_cloud_config |
host, port(触发重连) |
gateway_get_net_config |
查询网络配置 | host_computer_on_get_net_config |
ip, mask, gateway, dns |
gateway_set_net_config |
设置网络配置 | host_computer_on_set_net_config |
success(重启生效) |
server_get_pile_info |
查询单个桩信息 | host_computer_on_get_pile_info |
serial, type, gun_num, protocol_ver, software_ver, sim |
北向协议(网关 ↔ 云快充)
- 传输:4G TCP
- 数据格式:字节流,含CRC16校验
- 封装:
55 AA 01 socketId len [ykc_frame] AA 55
YKC帧格式
┌────────┬──────┬────────┬─────────┬────────┬──────────┬──────┐
│ 0x68 │ len │ serial │encrypt │type │ data │ crc │
│ (起始) │(1字节)│ (2字节)│ (1字节) │(1字节) │ (n字节) │2字节│
└────────┴──────┴────────┴─────────┴────────┴──────────┴──────┘
帧类型(上行 → 云快充)
| type | 函数 | 说明 |
|---|---|---|
| 0x01 | charger_to_server_0X01 |
登录认证 |
| 0x03 | charger_to_server_0X03 |
心跳 |
| 0x05 | charger_to_server_0X05 |
计费模型验证 |
| 0x09 | charger_to_server_0X09 |
计费模型请求 |
| 0x13 | charger_to_server_0X13 |
实时监测数据 |
| 0x19 | charger_to_server_0X19 |
充电结束 |
| 0x33 | charger_to_server_0X33 |
远程启动回复 |
| 0x35 | charger_to_server_0X35 |
远程停机回复 |
| 0x3B | charger_to_server_0X3B |
交易记录 |
| 0x57 | charger_to_server_0x57 |
计费模型应答 |
帧类型(下行 ← 云快充)
| type | 处理函数 | 说明 |
|---|---|---|
| 0x02 | on_cmd_frame_type_0X02 |
登录认证应答 |
| 0x04 | on_cmd_frame_type_0X04 |
心跳应答 |
| 0x06 | on_cmd_frame_type_0X06 |
计费模型验证应答 |
| 0x0A | on_cmd_frame_type_0X0A |
获取计费模型 |
| 0x58 | on_cmd_frame_type_0X58 |
计费模型设置 |
| 0x34 | on_cmd_frame_type_0X34 |
平台启动充电 |
| 0x36 | on_cmd_frame_type_0X36 |
平台控制停止充电 |
| 0x40 | on_cmd_frame_type_0X40 |
交易记录确认 |
4G链路协议(STM32 ↔ AIR724)
AIR724模块内部运行Lua脚本 cmd.lua + linksocket.lua:
55 AA mainCmd subCmd [payload] AA 55
| mainCmd | subCmd | 功能 |
|---|---|---|
| 0x01 | socketId | 数据发送到指定Socket |
| 0x02 | - | 系统重启 |
| 0x03 | 0x01/0x02 | 连接/断开Socket |
| 0x04 | 0x00 | 设置服务器地址 |
| 0x05 | 0x01~0x03 | 查询ICCID/IMSI/IMEI |
| 0x06 | 0x00 | 查询信号强度 |
| 0x07 | 0x00 | 查询链路状态 |
上行响应(4G → STM32):
| mainCmd | subCmd | 含义 |
|---|---|---|
| 0x83 | 0x01 | TCP已连接 |
| 0x83 | 0x02 | TCP已断开 |
| 0x83 | 0x03 | TCP连接失败 |
| 0x84 | 0x01~0x03 | SIM信息(ICCID/IMSI/IMEI) |
| 0x85 | - | 信号强度 |
| 0x86 | - | 链路状态 |
上位机 C# 客户端
C# 上位机客户端项目位于 C:\Users\Administrator\PycharmProjects\PythonProject\C#\YKC,基于 .NET Framework 4.7.2 + WPF 框架开发,通过 UDP 与网关通信。
项目结构
YKC/
├── App.xaml(.cs) 程序入口
├── MainWindow.xaml(.cs) 主窗口(导航框架)
├── LoginWindow.xaml(.cs) 登录窗口
├── Config.cs 配置参数 + 指令字典
├── UdpClient.cs UDP通信客户端(同步/异步)
├── PilesDataStore.cs 充电桩历史数据存储(含图表数据)
│
├── Pages/
│ ├── DashboardPage.xaml(.cs) 总览页面
│ ├── RealtimePage.xaml(.cs) 实时监控(含OxyPlot图表)
│ ├── ChargerPage.xaml(.cs) 充电桩管理
│ └── GatewayPage.xaml(.cs) 网关管理
│
├── bin/Debug/
│ ├── OxyPlot.dll 波形图库
│ ├── OxyPlot.Wpf.dll
│ ├── OxyPlot.Wpf.Shared.dll
│ └── Newtonsoft.Json.dll JSON库
│
└── Properties/ 项目属性
页面功能
1. 登录页面 (LoginWindow)
- 用户名/密码认证
- 发送
server_login→ 网关返回success: true/false - 默认账号:
admin/123456
2. 总览页面 (DashboardPage)
- 连接状态:显示网关在线/离线
- 在线/充电统计:在线桩数、充电中枪数
- 表格列表:每桩的序列号、在线状态、每枪状态
- 每 5秒 自动轮询
server_get_status
| 枪状态 | 含义 |
|---|---|
| 0 | 离线 |
| 1 | 故障 |
| 2 | 空闲 |
| 3 | 充电中 |
3. 实时监控页面 (RealtimePage)
- 选择桩号(1
N) + 枪号(1N) - 实时显示:电流(A)、电压(V)、功率(kW)、电量(kWh)、总金额、SOC、累计时间、剩余时间
- OxyPlot 实时曲线:
- 电流/电压/功率 折线图(含时间轴,最多300个点)
- 电费/服务费/总金额 柱状图
- 每 2秒 自动轮询
report_data主动上报数据(来自PilesDataStore历史记录)
4. 充电桩管理页面 (ChargerPage)
查询桩信息:
- 按桩索引查询
- 回复显示:序列号、类型(直流/交流)、枪数、协议版本、软件版本、SIM
设置桩ID:
- 输入桩索引 + 14位HEX序列号
重启网关:
- 发送
server_reboot(确认弹窗)
5. 网关管理页面 (GatewayPage)
网关信息卡片:
- 设备ID、软件版本、硬件版本、运行时长
- 调用
gateway_get_info
4G状态卡片:
- ICCID、网络状态、信号强度(dBm)、运营商
- 调用
gateway_get_4g_status
云平台配置卡片:
- 查询(
gateway_get_cloud_config) / 保存(gateway_set_cloud_config) - 保存时验证:IP格式或域名格式,端口 1~65535
网络配置卡片:
- IP/掩码/网关/DNS 查询 + 设置
- 调用
gateway_get_net_config/gateway_set_net_config - 支持IP地址格式校验
通信机制
UDP通信封装在 UdpClient.cs:
消息格式:
{
"id": 7, // 固定ID=7(上位机)
"cmd": "server_login",
"request_id": "server_login_1700000000000",
...其他参数...
}
回复格式:
{
"request_id": "server_login_...",
"success": true,
...其他数据...
}
两种发送模式:
| 方法 | 说明 |
|---|---|
Send(payload, callback) |
异步发送,通过回调接收回复 |
SendSync(payload, timeoutSec?) |
同步发送,阻塞等待回复或超时(默认4秒) |
超时机制:异步请求启动定时器,超时后自动回调 { success: false, error: "timeout" }
主动上报接收:OnActiveReport 事件监听 cmd == "report_data" / "pile_metrics" / "real_time_data",由 PilesDataStore 订阅存储。
监听端口:Config.LocalPort = 6002,目标网关 Config.TargetIp:Config.TargetPort(默认 10.12.19.100:6001)
数据存储
PilesDataStore.cs 单例模式管理:
- 实时数据:
GetLatest(pile, gun)→ 最新一条JObject - 图表数据:
GetChartData(pile, gun)→GunData对象,内含最多300个历史点的电流/电压/功率/电量/金额等序列 - 历史清理:超过300个点自动移除旧数据
配置参数
TargetIp = "10.12.19.100"; // 网关IP
TargetPort = 6001; // 网关UDP端口
LocalPort = 6002; // 本机监听端口
UdpTimeout = 3; // 超时时间(秒)
指令字典(与网关 udp_router.c 路由表对应):
| 常量 | cmd | 对应网关处理函数 |
|---|---|---|
LOGIN |
server_login |
host_computer_on_login |
GET_STATUS |
server_get_status |
host_computer_on_get_status |
REBOOT |
server_reboot |
host_computer_on_reboot |
GET_PILE_INFO |
server_get_pile_info |
host_computer_on_get_pile_info |
GET_GW_INFO |
gateway_get_info |
host_computer_on_get_gw_info |
GET_4G_STATUS |
gateway_get_4g_status |
host_computer_on_get_4g_status |
GET_CLOUD_CONFIG |
gateway_get_cloud_config |
host_computer_on_get_cloud_config |
SET_CLOUD_CONFIG |
gateway_set_cloud_config |
host_computer_on_set_cloud_config |
GET_NET_CONFIG |
gateway_get_net_config |
host_computer_on_get_net_config |
SET_NET_CONFIG |
gateway_set_net_config |
host_computer_on_set_net_config |
SET_PILE_ID |
server_set_pile_id |
待开发 |
REPORT_DATA |
report_data |
主动上报(南向) |
配置指南
修改桩编码(序列号)
修改 g_dcpile.c 中的 piles_serial 数组,每个桩编码为7字节十六进制:
/*充电桩序列号*/
const uint8_t piles_serial[6][7] = {
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x45}, // 桩1
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x44}, // 桩2
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x43}, // 桩3
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x42}, // 桩4
{0x32, 0x01, 0x06, 0x01, 0x11, 0x15, 0x58}, // 桩5
{0x32, 0x01, 0x06, 0x01, 0x11, 0x15, 0x54}, // 桩6
};
init_chargers()中通过memcpy(ctx->charger_serial, piles_serial[i], CHARGER_SERIAL_LENGTH)加载。
序列号转换为字符串显示时表现为14位HEX码,例如 32010601169244。
修改桩数量
两步:
① 修改 g_dcpile.h 中的宏:
#define MAX_CHARGER_COUNT 2 // ← 改为实际桩数量
② 确保 g_dcpile.c 中的 piles_serial 数组有对应数量的序列号。
同时需要修改 udp_router.c 中 ROUTE_TABLE 的路由数量(如果新增桩路由),以及 task_ykc.c 中状态机的循环 for (int i = 0; i < g_charger_manager.charger_count; i++) 已自动适配。
桩1~6的UDP IP地址在 udp_manager.c 中硬编码:
static ip4_addr_t s_point_ip[6] = { IPADDR4_INIT_BYTES(10, 12, 19, 101), /* 桩1 */ IPADDR4_INIT_BYTES(10, 12, 19, 102), /* 桩2 */ ... };如果桩数量变化,需要同步更新此数组。
修改每桩枪数
修改 g_dcpile.h:
#define MAX_GUN_PER_CHARGER 2 // ← 改为实际枪数
状态机中的枪循环已自动适配:
for (int g = 0; g < MAX_GUN_PER_CHARGER; g++)
配置云快充服务器地址
默认在 linksocket.lua 中:
local server_config = {
ip = "129.211.170.245",
port = 9002
}
可通过上位机指令 gateway_set_cloud_config 动态修改 s_cloud_host / s_cloud_port,修改后调用 drv_air724_set_server 触发AIR724重新连接。
Flash组件使用说明
Flash存储组件分为两层:
框架层 drv_flash
提供通用Flash读写管理,支持按帧类型存取、覆写模式、多帧管理。
帧格式
┌──────┬──────┬──────┬──────┬──────────┬──────┐
│ HEAD │ size_low │size_high│type│ DATA │ TAIL │
│(0xA5)│ (2字节 小端) │(1字节)│(n字节) │(0x5A)│
└──────┴──────┴──────┴──────┴──────────┴──────┘
核心API
| 函数 | 说明 |
|---|---|
flash_manage_init(flash_manage_t *fm) |
初始化:遍历Flash重建可读帧表 |
flash_manage_read(fm, *pbuf, frame_type) |
读取指定类型的帧数据 |
flash_manage_write(fm, *pbuf, size, frame_type) |
写入指定类型的帧数据 |
flash_manage_t 配置
typedef struct {
uint8_t align_num; // 地址对齐数(STM32H7一般为32)
flash_write_read_mode_t flash_write_mode; // 写模式
flash_write_read_mode_t flash_read_mode; // 读模式
uint32_t flash_start_address; // 管理起始地址(按扇区对齐)
uint32_t manage_sector_num; // 管理的扇区数
uint32_t sector_size; // 扇区大小(H743=128KB)
void (*read_flash)(...); // 读函数指针
void (*write_flash)(...); // 写函数指针
uint8_t (*erase_sector)(...); // 擦除函数指针
} flash_manage_t;
应用层 flash_config
flash_config.h / flash_config.c
基于框架层的封装,提供开箱即用的API:
// 单例,地址=0x08180000,1个Sector(128KB),32字节对齐
void stm_flash_init(void);
// 读取,传入缓冲区 + 帧类型,成功返回1
int8_t stm_flash_read(uint8_t *pbuf, uint8_t frame_type);
// 写入,传入数据 + 大小 + 帧类型,成功返回1
int8_t stm_flash_write(uint8_t *pbuf, uint16_t size, uint8_t frame_type);
注意:STM32H743 Bank2 的 Sector 擦除规则:
- 起始地址
0x08100000,每Sector = 128KB- 写入必须 32字节对齐,不满32字节补
0xFFflash_config.c使用0x08180000(Bank2的第4个Sector)
使用示例
#include "flash_config.h"
// 初始化(在main中调用一次)
stm_flash_init();
// 写入配置数据
uint8_t config_data[] = {0x01, 0x02, 0x03, 0x04};
stm_flash_write(config_data, sizeof(config_data), 0x01); // 帧类型1
// 读取配置数据
uint8_t read_buf[64] = {0};
if (stm_flash_read(read_buf, 0x01) == 1) {
// 读取成功,read_buf中为数据
} else {
// 无数据
}
写模式:
| 模式 | 值 | 说明 |
|---|---|---|
FLASH_NO_OVERWRITING_MODE |
0x00 | 只追加,不覆盖旧帧 |
FLASH_OVERWRITING_MODE |
0x01 | 将旧帧头置为无效,写新帧(默认) |
读模式:
| 模式 | 值 | 说明 |
|---|---|---|
FLASH_NO_CLEAR_MODE |
0x02 | 读取后不清除(默认) |
FLASH_CLEAR_MODE |
0x03 | 读取后将帧标记为无效 |
任务列表
| 任务 | 函数 | 栈大小 | 优先级 | 说明 |
|---|---|---|---|---|
| SysTask | sys_task_function |
128 | Low | 指示灯、蜂鸣器、喂狗 |
| Air724RecvTask | air724_recv_task_function |
1024 | AboveNormal | 解析4G下行数据 |
| UDPRecvTask | udp_recv_task_function |
1536 | High | UDP接收入队 |
| UDPParseTask | udp_parse_task_function |
2048 | AboveNormal | UDP数据解析+路由分发 |
| YkcTask | ykc_task_function |
1024 | AboveNormal | 云快充状态机+数据上报 |
消息队列
| 队列 | 长度 | 消息大小 | 用途 |
|---|---|---|---|
| Air724_Message_Queue | 20 | 512 | 4G接收数据 |
| RS485_Message_Queue | 5 | 256 | RS485接收数据 |
| UDP_Message_Queue | 20 | sizeof(UdpMsg_t) | UDP接收数据 |
日志格式
[YKC Router] frame=0x02 │ stake=1 │ 登录认证应答 ← 4G下行路由
[ UDP 接收路由 ] 指令: realtime data │ 桩ID: 1 │ 实时数据 ← 南向UDP
[ UDP 路由拦截 ] 桩ID: 2 未上电,忽略指令: heartbeat ← 上电过滤
[ 北向 ] 对电桩 1 发送登录认证 ← 4G上行
[ 北向 ] 对电桩 1 枪 1 上传交易记录(0x3B),电量:0.00,金额:0.00 ← 4G上行
[ 4G ] 通道 1 连接成功 ← 4G链路通知
[ 上位机 ] get_status 回复已发送 ← 上位机回复