第3章:充电桩软件架构:嵌入式软件分层与云平台通信模块设计
大家好,我是你们的老朋友。今天我们来聊聊充电桩的软件架构。
说实话,很多刚入行的朋友问我:“充电桩不就是个开关吗?软件能有多复杂?”
嗯,如果你这么想,那可就大错特错了。一个合格的充电桩,它的软件架构其实相当讲究。我个人习惯把嵌入式软件分成三层:驱动层、协议层、应用层。每一层各司其职,就像军队里的不同兵种。
3.1 嵌入式软件分层设计
为什么要分层?说白了,就是为了解耦。你想想看,如果所有代码都揉在一起,换一个硬件平台,或者升级一个通信协议,那简直就是灾难。我在项目中遇到过好几次,就是因为早期架构没分好,后期改一个继电器控制逻辑,结果把整个通信模块都带崩了。
3.1.1 驱动层(Driver Layer)
驱动层是最底层的,直接跟硬件打交道。它负责控制MCU的GPIO、ADC、SPI、UART等外设。
举个例子,充电桩里的继电器控制、电能计量芯片的读数、LCD屏幕的显示,这些都属于驱动层的活。
核心职责:
- 硬件抽象:把具体的寄存器操作封装成函数接口
- 中断处理:处理来自硬件的中断请求
- 时序控制:确保硬件操作的时序满足要求
我曾经踩过一个坑:某款充电桩的电能计量芯片,它的SPI读取时序要求CS片选信号必须在时钟上升沿之前拉低至少100ns。驱动层如果没处理好这个时序,读出来的电量数据就是乱码。嗯,这里要注意,驱动层的代码一定要经过严格的硬件测试。
3.1.2 协议层(Protocol Layer)
协议层在中间,它负责把驱动层的数据“翻译”成有意义的协议报文。
对于充电桩来说,常见的协议包括:
- OCPP协议:充电桩与云平台之间的通信协议
- CAN协议:与BMS(电池管理系统)通信
- Modbus协议:与电能表、漏电保护器等设备通信
我个人习惯在协议层做两件事:一是协议解析,二是协议封装。比如收到云平台下发的“开始充电”指令,协议层要解析出指令类型、充电参数,然后封装成应用层能识别的数据结构。
避坑指南:我曾经在协议层犯过一个低级错误——没有做超时重传机制。结果有一次网络波动,云平台下发的指令丢了,充电桩就一直傻等着。从那以后,我所有的协议层代码都会加上超时处理和重传逻辑。
3.1.3 应用层(Application Layer)
应用层是最高层,它负责业务逻辑。比如:
- 充电流程控制(待机、插枪、鉴权、充电、结束)
- 计费逻辑(按电量计费、按时间计费)
- 故障诊断与处理(过流、过压、漏电保护)
应用层不关心底层硬件是怎么实现的,也不关心协议报文长什么样。它只关心业务状态机怎么跳转。举个例子,当应用层收到“充电枪已插入”的信号,它就会从“待机状态”切换到“插枪状态”,然后通知协议层去请求鉴权。
分层的好处:
- 驱动层换了MCU,应用层代码不用改
- 协议层从OCPP 1.6升级到OCPP 2.0,应用层几乎不受影响
- 应用层增加新的充电模式,驱动层和协议层只需提供接口支持
3.2 云平台通信模块设计
接下来我们重点聊聊云平台通信模块。这是充电桩的“神经中枢”,负责把桩端数据上传到云端,同时接收云端的控制指令。
3.2.1 通信模块的整体架构
我设计的通信模块通常包含以下几个组件:
| 组件名称 | 功能描述 |
|---|---|
| 连接管理器 | 负责MQTT连接的建立、保活、重连 |
| 消息路由器 | 根据Topic分发消息到对应的处理函数 |
| 数据序列化器 | 将结构体数据转换成JSON或Protobuf格式 |
| 心跳管理器 | 定时发送心跳包,检测连接状态 |
| 命令队列 | 缓存待发送的命令,防止网络拥塞 |
你想想看,如果这些功能都揉在一个文件里,那维护起来得多痛苦?我见过一个项目,通信模块就一个main.c,里面塞了2000多行代码,后来想加个断线重连功能,改了一个月还没改完。
3.2.2 MQTT主题设计
MQTT的主题设计是通信模块的核心。我个人习惯采用分层级的主题结构:
// 设备上报主题
ocpp/chargepoint/{device_id}/status
ocpp/chargepoint/{device_id}/meter
ocpp/chargepoint/{device_id}/event
// 云端下发主题
ocpp/cloud/{device_id}/command
ocpp/cloud/{device_id}/config
ocpp/cloud/{device_id}/ota
为什么要这样设计?因为MQTT支持通配符订阅。云端可以订阅 ocpp/chargepoint/+/status 来接收所有充电桩的状态上报,而单个充电桩只需要订阅自己的专属主题即可。
注意:主题层级不要太深,一般不超过4级。太深的主题会影响MQTT Broker的匹配性能。我曾经见过有人设计了8级主题,结果高并发时Broker直接崩溃了。
3.2.3 数据上报策略
数据上报不是越频繁越好。我建议采用“事件驱动+定时上报”的组合策略:
- 事件驱动:当充电状态发生变化时(如开始充电、结束充电、故障发生),立即上报
- 定时上报:电量数据、电压电流等遥测数据,每30秒或1分钟上报一次
- 批量上报:对于日志类数据,可以缓存起来,每5分钟批量上报一次
这样做的好处是:既保证了实时性,又不会因为频繁上报而浪费流量和带宽。我在一个项目中,客户要求每100ms上报一次电压数据,结果一个月下来,4G流量费比充电桩本身还贵。后来改成1秒上报一次,流量费降了90%,而且对业务没有任何影响。
3.2.4 断线重连机制
充电桩经常部署在地下停车场、偏远郊区,网络环境很不稳定。所以断线重连机制是通信模块的“保命符”。
我常用的重连策略是“指数退避”:
// 伪代码示例
int retry_interval = 1; // 初始重连间隔1秒
int max_interval = 300; // 最大重连间隔5分钟
while (is_connected == false) {
if (mqtt_connect() == SUCCESS) {
break;
}
sleep(retry_interval);
retry_interval = min(retry_interval * 2, max_interval);
}
为什么要用指数退避?因为如果所有充电桩同时断线,又同时尝试重连,那服务器瞬间就会被冲垮。指数退避可以让重连请求分散开,减轻服务器压力。
个人经验:我曾经遇到过一个极端情况——充电桩在充电过程中突然断网,重连后又断网,反复循环。后来我在重连逻辑里加了一个“充电状态保持”机制:如果断网时正在充电,重连后优先恢复充电状态,而不是重新开始鉴权流程。这样用户体验就好多了。
3.3 小结
好了,这一章的内容就到这里。我们讲了嵌入式软件的三层架构:驱动层管硬件、协议层管通信、应用层管业务。还讲了云平台通信模块的设计要点,包括组件划分、主题设计、上报策略和重连机制。
下一章,我们会深入MQTT协议的细节,聊聊QoS等级、遗嘱消息、保留消息这些高级特性。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。
记住一句话:好的架构不是设计出来的,是改出来的。别怕重构,怕的是不敢重构。