第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等级、遗嘱消息、保留消息这些高级特性。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。

记住一句话:好的架构不是设计出来的,是改出来的。别怕重构,怕的是不敢重构。