液冷储能系统NCCU 软件系统架构分析文档
一、工程概述
本工程是一套运行于嵌入式 Linux 平台(AM335x ARM Cortex-A8,IMMU2/IMMU3 硬件平台)的储能充电站控制系统(NCCU,Network Communication Control Unit),采用纯 C语言编写,面向工业实时控制场景,支持多种工业通信协议,兼顾高可靠性与高可扩展性。
二、顶层目录结构与职责划分
项目根目录
├── main/ # 程序入口与系统主控
├── publib/ # 公共基础库(线程、队列、互斥、日志、数据库)
├── include/ # 全局头文件(数据模型、基础类型、第三方库)
│ ├── base_defines/ # 基础类型与工具宏
│ ├── data_model/ # 核心数据模型定义(CHARGER/DEVICE/SAMPLER/REPORTER等)
│ └── 3rd_party/ # 第三方库(SQLite、curl、MQTT、IEC61850等)
├── comm_ports/ # 通信端口硬件抽象层(HAL),以动态库形式存在
├── data_interfaces/ # 数据访问接口层(DAI),Reporter 访问数据的统一入口
├── moduals/ # 核心业务模块
│ ├── data_processor/ # 数据处理器(DPR):系统核心总线
│ ├── samplers/ # 采样器插件(设备数据采集)
│ └── reporters/ # 报告器插件(对外协议上报)
├── config/ # 系统配置(SQLite格式 System.cfg)
├── extensions/ # 驱动内核模块与系统扩展
├── utilities/ # 辅助工具(日志查看、系统监控、USB升级等)
└── shells/ # 系统启动/停止/运维脚本
三、整体软件架构
3.1 架构模式
本系统采用分层 + 插件化的事件驱动架构,整体可分为五个主要层次:
┌─────────────────────────────────────────────────────────────────┐ │ 外部协议层(Reporter 层) │ │ OCPP1.6 | Modbus TCP | IEC104 | IEC101 | IEC61850 | MQTT | │ │ WebSocket | HTTP/REST | GUI(Qt) | 云平台 | 运维后台 ... │ ├─────────────────────────────────────────────────────────────────┤ │ 数据访问接口层(DAI 层) │ │ DAI_Get / DAI_Set / DAI_Query / DAI_Signal / DAI_EvHandler │ ├─────────────────────────────────────────────────────────────────┤ │ 数据处理器层(DPR 层) │ │ 参数更新 / 报警判定 / 事件分发 / 逻辑计算 / 历史数据持久化 │ ├─────────────────────────────────────────────────────────────────┤ │ 设备采样层(Sampler 层) │ │ Charger | KWH Meter | System | CCU | BMS | PV | AEMS | ... │ ├─────────────────────────────────────────────────────────────────┤ │ 通信端口硬件抽象层(CommPort HAL 层) │ │ 串口 Serial | TCP/IP | UDP | WebSocket | CAN | HTTP/FTP | IPC │ └─────────────────────────────────────────────────────────────────┘ ▲ 公共基础库(publib) iThread | iQueue | iMutex | iLog | iDataBase | iNew
四、核心模块深度分析
4.1 主控模块(main/)
主控模块是整个系统的启动器与调度中心,入口为 main/main.c。
4.1.1 启动流程(main.c)
int main(int argc, char *argv[]) {
// Step 1: 命令行参数处理(-pid, -version)
Main_ProcessCmdLine(argc, argv);
// Step 2: 内存池初始化
MemoryPool_Init();
// Step 3: 等待 SQLite 数据库文件锁释放(防止多进程冲突)
Lock_SqlDB_FileLocker(1000);
// Step 4: 初始化 SQLite(序列化模式,支持多线程共享同一 handle)
sqlite3_config(SQLITE_CONFIG_SERIALIZED);
sqlite3_open_v2(szDBFile, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);
// Step 5: 创建核心数据结构 CHARGER,挂载事件队列
Create_Charger(db);
// Step 6: 打开硬件看门狗
WatchDog_Open();
// Step 7: 安装信号处理器
Install_SigHandlers();
// Step 8: 设置 longjmp 跳出点(用于异常快速退出)
int nExitCode = setjmp(s_JumpBufFaultError);
// Step 9: 初始化线程管理器
RunThread_ManagerInit(ThreadManager_EventHandler);
// Step 10: 加载所有业务模块(DPR → DAI → CommPorts → Samplers → Reporters)
Main_LoadModuals();
// Step 11: 进入主事件循环
while(!s_pChargeThis->bExit)
{
Do_Proc_HeartBeat(); // 进程心跳
Queue_Get(hqEvent, &ev_arg, FALSE, 100); // 取事件
dataprocessor.DoEvent(&ev_arg); // 分发事件
}
}
关键设计要点:
使用 setjmp/longjmp 实现从任意深度调用栈中安全退出,避免层层返回的复杂性
主循环以 事件队列(hqEvent) 为驱动,每 100ms 轮询一次
使用 SQLite 文件锁 防止多进程同时操作数据库
4.1.2 模块加载顺序(Main_LoadModuals)
static BOOL Main_LoadModuals(void) {
Init_CreateDataProcessor(); // 1. 数据处理器(核心总线)
dataprocessor.LoadConfigAndData(); // 2. 从 SQLite 加载配置
Init_LoadDAI(); // 3. 数据访问接口
Init_Load_AllCommPorts(); // 4. 通信端口(动态库)
Set_SqliteDB_2RunTime_Mode(); // 5. 切换数据库到运行时模式
Init_Load_AllSamplers(); // 6. 采样器(动态库)
Init_Load_AllReporters(); // 7. 报告器(动态库)
}
加载顺序体现了清晰的依赖关系:数据层先于通信层,通信层先于业务层。
4.1.3 看门狗(watch_dog.c)
//打开硬件看门狗设备
s_hWatchDog = open("/dev/wd", O_RDWR);
ioctl(s_hWatchDog, IOCTL_SET_TIMER, 60); // 60秒超时
ioctl(s_hWatchDog, IOCTL_EN_WD);
// 喂狗(由 ThreadManager 每次心跳时调用)
void WatchDog_Feed(void) {
ioctl(s_hWatchDog, IOCTL_FEED_WD);
}
同时支持新旧两代 Linux Kernel 的 ioctl 接口(新 WDOG_MAJOR=236,旧 WDOG_MAJOR=241)。
4.1.4 信号处理(sig_handler.c)
4.2 核心数据模型(include/data_model/)
数据模型是整个架构的数据契约,所有模块共享同一套类型定义。
4.2.1 顶层数据结构:CHARGER
typedef struct tagCharger {
CHARGER_INFO info; // 软件版本、设备模型、参数定义、报警定义
CLUSTER_DEVICE devices; // 设备列表(含参数、报警实例)
CLUSTER_SAMPLER samplers; // 采样器列表
CLUSTER_REPORTER reporters; // 报告器列表
CLUSTER_COMMPORT commports; // 通信端口列表
BOOL bExit; // 全局退出标志(所有线程监听此变量)
int nExitCode; // 退出原因码
HANDLE hRunDB; // 运行时 SQLite 数据库句柄
HANDLE hqEvent; // 主事件队列(MAX=96条)
BOOL bIsInitOK; // 初始化完成标志
DATA_PROCESSOR dataprocessor; // 数据处理器(函数指针集合)
DAI dai; // 数据访问接口(函数指针集合)
} CHARGER;
CHARGER 是整个系统的"大脑",贯穿所有模块,通过指针在各层传递。
4.2.2 设备模型层次
CHARGER_INFO
└── CLUSTER_DEVICE_MODEL_INFO 设备模型定义
└── CLUSTER_STATUS_PARAM_INFO 状态参数定义(含显示表达式、范围、枚举)
└── CLUSTER_CONTROL_PARAM_INFO 控制参数定义(含控制条件表达式)
└── CLUSTER_SETTING_PARAM_INFO 设置参数定义(含默认值、同步策略)
└── CLUSTER_ALARM_INFO 报警定义(含触发/屏蔽表达式)
CLUSTER_DEVICE (devices)
└── DEVICE
├── DEVICE_INFO 设备标识(ID、名称、所属模型、所属采样器)
├── CLUSTER_PARAMETER params_Status 运行时状态参数值
├── CLUSTER_PARAMETER params_Control 运行时控制参数值
├── CLUSTER_PARAMETER params_Setting 运行时设置参数值
└── CLUSTER_ALARM alarms 运行时报警状态
4.2.3 参数值(PARAMETER)
typedef struct tagA_Param {
PARAM_BASIC_INFO* pParamInfo; // 指向参数定义(静态配置)
HANDLE hDevice; // 所属设备
VAR_VALUE currValue; // 当前值
BOOL bIsValueInvalid; // 当前值是否有效
VAR_VALUE lastValue; // 上一次值(用于变化检测)
time_t tmCurrValueRefreshed; // 最后更新时间
time_t tmLastValueChanged; // 最后变化时间
} PARAMETER;
4.3 公共基础库(publib/)
publib 是整个系统的"地基",编译为 libpub.so 动态库,供所有模块链接使用。
4.3.1 线程管理(iThread.c)
系统实现了一套 带心跳监控的线程管理框架:
线程管理器内部结构
typedef struct tagThreadManager {
int nThreadEntries; // 当前分配的线程槽数(动态扩展,步长32)
RUN_THREAD_ENTRY *pThreadEntries; // 线程条目数组
int volatile nRunningThreads; // 当前运行中的线程数
RUN_THREAD_EVENT_HANDLER pfnEventHandler; // 事件处理回调(main 注册)
pthread_mutex_t hSyncLock; // 保护线程表的互斥锁
} RUN_THREAD_MANAGER;
每个线程条目
typedef struct tagThreaEntry {
DWORD dwThreadId; // 高16位为魔术字,低16位为索引
char szThreadName[16];
pthread_t hSystemThread;
DWORD volatile dwThreadHeartbeat; // 心跳计数器
int volatile nStatus; // RUN_THREAD_IS_RUNNING / TO_QUIT / IS_INVALID
} RUN_THREAD_ENTRY;
心跳机制:
每个业务线程必须在循环中调用 RUN_THREAD_HEARTBEAT()
ThreadManager 每 500ms 检查一次所有线程心跳
若某线程 60 秒无心跳响应,触发 THREAD_EVENT_NO_RESPONSE 回调
主控接收到回调后设置 bExit=TRUE,触发系统重启
main.c 中的线程事件处理器
static DWORD ThreadManager_EventHandler(DWORD dwThreadEvent, HANDLE hThread, const char *pszThreadName)
{
WatchDog_Feed(); // 每次心跳时喂狗
if(THREAD_EVENT_NO_RESPONSE == dwThreadEvent) {
s_pChargeThis->bExit = TRUE; s_pChargeThis->nExitCode = SYS_EXIT_BY_THREAD;
}
return THREAD_CONTINUE_RUN;
}
4.3.2 线程安全队列(iQueue.c)
typedef struct tagQueue {
int nSize; // 单个元素大小
int nCapacity; // 队列容量
char *pBuffer; // 环形缓冲区
pthread_mutex_t hSyncLock;// 互斥锁
int nCount; // 当前元素数 int nHead; // 头指针
int nTail; // 尾指针
HANDLE hCountSem; // 信号量(用于阻塞等待)
} QUEUE;
队列支持:
阻塞模式(bBlock=TRUE,带超时等待)
非阻塞模式(bBlock=FALSE,立即返回)
可配置的自动扩容(nIncStep > 0)
4.3.3 互斥锁与信号量(iMutex.c)
对 pthread_mutex_t 和 POSIX 信号量进行封装,提供带超时的 Mutex_Lock,避免死锁。
4.4 通信端口硬件抽象层(comm_ports/)
4.4.1 HAL 接口定义(comm_port.h)
所有通信端口驱动必须导出统一的 6 个函数接口:
动态库必须导出的符号名称
#define SYM_HAL_COMMOPEN "HAL_CommOpen"
#define SYM_HAL_COMMACCEPT "HAL_CommAccept"
#define SYM_HAL_COMMREAD "HAL_CommRead"
#define SYM_HAL_COMMWRITE "HAL_CommWrite"
#define SYM_HAL_COMMCONTROL "HAL_CommControl"
#define SYM_HAL_COMMCLOSE "HAL_CommClose"
通信端口对象(函数指针集合)
typedef struct tagCommPort {
COMMPORT_INFO info;
COMM_OPEN_PROC Open;
COMM_CLOSE_PROC Close;
COMM_ACCEPT_PROC Accept;
COMM_READ_PROC Read;
COMM_WRITE_PROC Write;
COMM_CONTROL_PROC Control;
HANDLE hLib; // dlopen 句柄
} COMMPORT;
4.4.2 动态加载机制(comm_loader.c)
static BOOL Load_ACommPort(COMMPORT* pCommPort) {
// 通过 dlopen 加载 .so 文件,路径由配置数据库决定
pCommPort->hLib = LoadDynamicLibrary( MakeFullFileName(szFullLibPath, getenv("NCCU_APP_PATH"), "comm_port", pCommPort->info.szPortDriver),
ITEM_OF(pszSymName),
pszSymName, // 需要查找的符号名数组
pfnProc, // 将函数地址写入 COMMPORT 对应成员
TRUE // 要求所有符号必须存在
);
}
4.4.3 支持的通信端口类型
4.5 采样器层(moduals/samplers/)
采样器负责从物理设备采集数据并回写到数据处理器,每个采样器运行在独立线程中。
4.5.1 采样器数据模型(sampler_model.h)
typedef struct tagSampler {
SAMPLER_INFO info; // ID、名称、驱动库名、采样周期(ms)
COMMPORT *pUsedCommPort; // 使用的通信端口
HANDLE hSamplerLib; // dlopen 动态库句柄
HANDLE hSampleThread; // 采样线程句柄
HANDLE hDPR; // 数据处理器句柄(用于 Get/Set)
void *pDataHeap; // 采样器私有数据堆
// 动态加载的函数指针(插件接口)
SAMPLER_INIT_PROC Init; // 初始化
SAMPLER_SAMPLE_PROC Sample; // 采样主函数
SAMPLER_CONTROL_PROC Control; // 控制执行
SAMPLER_NOTIFY_PROC Notify; // 接收 DPR 通知
HANDLE hqControlCommand; // 控制命令队列(异步接收来自 DPR 的控制命令)
BYTE emWorkStatus; // 工作状态(工作中/断线/不工作)
double tmLastSampled; // 最后采样时间
BOOL bJustControlled; // 刚执行了控制命令,需立即重采样
BOOL bExit; // 线程退出标志
} SAMPLER;
4.5.2 采样器线程主循环(sampler_loader.c)
static DWORD __Thread_ASampler(void* pArgs) {
SAMPLER* pSampler = (SAMPLER*)pArgs;
while(!pSampler->bExit) { RUN_THREAD_HEARTBEAT(); // 向 ThreadManager 汇报心跳
// 1. 优先处理控制命令队列(最多连续处理6条)
while(Queue_Get(pSampler->hqControlCommand, &cmd, FALSE, 100) == OK)
{
pSampler->Control(hSamplerSelf, PARAM_TYPE_CONTROL,
cmd.data.nDevId, cmd.data.nParamId,
&cmd.data.varValue, cmd.data.nValueSize, INFINITE);
// 控制成功后,回写结果到 DPR
pDPR->Set(pSampler, SETTYPE_RAW_BACK_CTRL, &cmd.data, sizeof(INTERSET_DATA));
}
// 2. 按采样周期触发采样
if(IsTimeToSample(pSampler, GetCurrentTime()))
{
pSampler->Sample(hSamplerSelf); // 调用插件的 Sample 函数
}
}
}
4.5.3 已实现的采样器插件
4.6 数据处理器层(moduals/data_processor/dpr/)
数据处理器(DPR)是系统的中央总线与大脑,负责:
配置加载:从 SQLite 读取所有设备模型、参数定义、报警定义
数据管理:维护所有设备参数的实时值(线程安全)
事件分发:向注册了感兴趣事件的所有模块广播事件
报警判定:基于表达式引擎计算报警触发/屏蔽条件
历史数据持久化:将关键数据写入 SQLite
4.6.1 DPR 接口(dataprocessor_model.h)
typedef struct tagDataProc {
DATAPROC_INFO info;
HANDLE hThread;
BOOL bExit;
// 供 main 调用的接口
LOADCFG_PROC LoadConfigAndData; // 从 DB 加载所有配置
STARTSTOPDATA_PROC DoDataProcessing; // 启动数据处理线程
PROCESSEVENT_PROC DoEvent; // 处理来自主队列的事件
// 供 Sampler 调用的接口
INTER_GET Get; // 采样器读取参数值
INTER_SET Set; // 采样器推送采样结果
CALCULATE_EXPRESSION CalculateExp; // 计算逻辑表达式
REGISTER_EVHAND Register_EvHandler; // 注册事件处理器
} DATA_PROCESSOR;
4.6.2 内部数据流向(Set 接口的类型枚举)
typedef enum tagInternalSetType {
SETTYPE_RAW_SP, // 采样器推送原始状态参数值
SETTYPE_LOGIC_SP, // 逻辑计算出的状态参数值
SETTYPE_INTERN_SETT, // 设置参数同步到其他采样器
SETTYPE_RAW_ST, // 设置参数本地刷新(不同步)
SETTYPE_INTERN_CTRL, // 控制参数分发到对应采样器
SETTYPE_SENDMSG, // 发送消息到 Reporter/DPR
SETTYPE_RAW_BACK_CTRL, // 控制执行结果回写
SETTYPE_RAW_BACK_SETT, // 设置执行结果回写
SETTYPE_RESTORE_SETTINGS_FROMDB, // 从 SD 卡恢复配置
SETTYPE_CLEAR_FACTOY_DEBUG_DATA, // 清除工厂调试数据
} INTERSET_TYPE;
4.7 数据访问接口层(data_interfaces/DAI)
DAI 是 Reporter 访问系统数据的标准接口,将 DPR 的内部实现与 Reporter 完全解耦。
4.7.1 DAI 接口集合(dai_model.h)
typedef struct tagDAI {
DAI_GET Get; // 读取设备信息、参数值、报警状态
DAI_SET Set; // 设置参数(控制命令/设置参数)
DAI_QUERY Query; // 历史数据查询(充电记录、报警、事件)
DAI_SIGNAL Signal; // 向主事件队列投递事件
DAI_REGISTER_EVHAND Regsiter_EvHandler; // 注册事件监听器
} DAI;
4.7.2 DAI_GET 支持的查询类型
typedef enum tagDAIType {
DAITYPE_DEV_TOTAL_R, // 设备总数
DAITYPE_DEV_INFO_R, // 设备信息(按索引)
DAITYPE_PARAM_TOTAL_R, // 参数总数(状态/控制/设置)
DAITYPE_PARAM_INFO_R, // 参数定义信息
DAITYPE_ALARM_TOTAL_R, // 报警总数
DAITYPE_ALARM_INFO_R, // 报警定义信息
DAITYPE_STAT_PARAMVALUE_R, // 读取实时状态参数值
DAITYPE_SETT_PARAMVALUE_RW, // 读写设置参数
DAITYPE_CTRL_PARAMVALUE_RW, // 写控制参数(发控制命令)
DAITYPE_ALARMSTAT_R, // 读取报警状态
DAITYPE_GETLOCALLIST_VERSION, // OCPP 本地授权列表版本
DAITYPE_COMMPORT_R, // 读取通信端口信息
DAITYPE_VIRTUAL_PARAMVALUE_W, // Reporter 写入虚拟参数
DAITYPE_COMM_DATA_DUMP, // 通信数据转储
...
} DAI_TYPE;
4.8 报告器层(moduals/reporters/)
报告器负责实现对外协议,向上级系统、后台或用户界面上报数据和处理指令。每个报告器运行在独立线程,通过 DAI 接口访问数据,通过事件机制感知系统状态变化。
4.8.1 报告器数据模型(reporter_model.h)
typedef struct tagReporter {
REPORTER_INFO info; // ID、名称、驱动库名、配置字符串
void *pDataHeap; // 报告器私有数据
HANDLE hSamplerLib; // 动态库句柄
COMMPORT *pUsedCommPort; // 使用的通信端口
HANDLE hThread; // 报告器主线程
BOOL bExit; // 退出标志
HANDLE hqEvent; // 事件队列(接收 DPR 的事件推送)
DAI *pDAI; // 数据访问接口指针
INIT_PROC Init; // 初始化函数(启动线程)
} REPORTER;
4.8.2 已实现的报告器插件
4.9 事件驱动机制
事件机制是各层间解耦通信的核心手段。
4.9.1 事件类型(event_model.h)
typedef enum tagEvent_Type {
EVENT_TYPE_ALARM, // 报警触发/消除
EVENT_TYPE_CFG_CHANGED, // 配置变更
EVENT_TYPE_CHARGING, // 充电状态变化
EVENT_TYPE_USER_AUTHORIZE, // 用户授权
EVENT_TYPE_SYSTEM_EXIT, // 系统退出
EVENT_TYPE_COMM_FAIL, // 通信失败
EVENT_TYPE_CHARGEREC_UPDATE, // 充电记录更新
EVENT_TYPE_FIRMWARE_UPDATE, // 固件升级状态
EVENT_TYPE_DIAGNOSTIC_UPLOAD, // 诊断日志上传
EVENT_TYPE_METERVAL_UPDATE_IN_CHARGING,// 充电过程中电表值更新
EVENT_TYPE_BMSRUNDATA_UPDATE, // BMS 运行数据更新
EVENT_TYPE_HEALTHDATA_UPDATE, // 健康数据更新
EVENT_TYPE_REFRESH_ALARM, // 刷新报警
...
} _EVENT_TYPE;
4.9.2 事件对象(EVENT_ARG)
typedef struct tagEventArg {
_EVENT_TYPE emEvType; // 事件类型
_EVENT_MSG emMsgType; // 消息阶段(BEGIN/STATUS_CHANGED/END/RESTORE)
UINT nEvDataSize; // 数据大小
void *pEvData; // 事件数据指针
BOOL bHighPriority; // 高优先级(插队到队头)
BOOL bBlockMode; // 阻塞模式(等待处理完成)
HANDLE hSender; // 发送者句柄
EVENT_RECEIPT pfnReceipt; // 处理完成回调
} EVENT_ARG;
4.9.3 事件流向
Sampler (数据变化/报警触发)
│ DPR->Set(SETTYPE_SENDMSG)
▼
DPR 检测到状态变化 → 生成 EVENT_ARG
│ Queue_Put(s_pChargeThis->hqEvent)
▼
主事件循环 (main.c) 取出事件
│ dataprocessor.DoEvent(&ev_arg)
▼
DPR 广播给所有已注册的 EVENT_HANDLER
│ 遍历 EVENT_HANDLER_REGISTER 列表
├──▶ Reporter A 的 hqEvent (OCPP 关心 CHARGING 事件)
├──▶ Reporter B 的 hqEvent (IEC104 关心 ALARM 事件)
└──▶ Reporter C 的 hqEvent (GUI 关心所有事件)
4.10 配置数据库(SQLite)
系统配置采用 SQLite 数据库文件(config/System.cfg),核心表结构如下:
SQLite 运行策略:
启动阶段用 序列化模式(SQLITE_CONFIG_SERIALIZED)安全加载配置
切换到运行时后调用 Set_SqliteDB_2RunTime_Mode(),优化为高并发模式
通过 文件锁 防止多进程冲突(系统升级时其他进程可能操作数据库)
五、系统可靠性设计
5.1 多重故障保护机制
硬件看门狗(WDT)
│ 每次 ThreadManager 心跳时喂狗(60秒内必须喂一次)
│ ThreadManager 心跳监控(每 60 秒检查所有线程)
│ 每个业务线程循环调用 RUN_THREAD_HEARTBEAT()
│
信号处理(SIGSEGV/SIGILL/SIGBUS 等) → 记录日志 → longjmp 退出 → WDT 超时 → 系统 reboot
进程心跳(Do_Proc_HeartBeat) → 通知 sys_hand 进程,app 进程仍在存活 → sys_hand 若检测 app 消失,可触发 SIGUSR1 信号通知
5.2 优雅退出机制
longjmp 机制:从任意深度快速返回主函数
void Main_Exit(int nExitCode) {
// 通知所有 Sampler/Reporter 退出
for(n = 0; n < nTotal; n++) pSampler->bExit = TRUE;
WatchDog_Close(FALSE); // 不禁用看门狗(让 WDT 触发重启)
longjmp(s_JumpBufFaultError, nExitCode); // 跳回 main 的 setjmp 点
}
// 在 setjmp 接收点:
int nExitCode = setjmp(s_JumpBufFaultError);
if(nExitCode != 0) {
RunThread_ManagerExit(10000, TRUE); // 等待最多 10 秒让所有线程退出
_exit(nExitCode);
}
六、构建系统
6.1 构建体系(Makefile + include.mk)
交叉编译工具链
GCC_PREFIX = arm-linux-gnueabihf- # ARM 目标平台
CC = ccache $(GCC_PREFIX)gcc # 使用 ccache 加速编译
构建目标:
- libpub.so 公共基础库
- libcomm_xxx.so 各通信端口驱动(8种)
- sampler_xxx.so 各采样器插件(12种)
- reporter_xxx.so 各报告器插件(15种)
- nccu_app 主程序可执行文件
平台差异
ifeq ($(NCCU_CROSS_PLATFORM), arm)
CFLAGS += -D_HAS_FLASH_MEM=1 # 启用看门狗、闪存支持
endif
ifeq ($(MU_TYPE), mu3)
CFLAGS += -DIMMU3 # IMMU3 硬件平台差异
endif
6.2 第三方依赖
七、架构优缺点总结
优点
不足与值得关注的点
八、架构图总览
┌────────────────────────────────────────────────────────────────────────┐ │ NCCU 软件系统架构 │ │ │ │ ┌──────────────────────── 硬件平台 ─────────────────────────────┐ │ │ │ AM335x ARM Cortex-A8 | IMMU2 / IMMU3 | Linux 内核 │ │ │ │ 硬件看门狗 /dev/wd | CAN Bus | RS485 | 触摸屏 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌── main/ 主控层 ────────────────────────────────────────────────┐ │ │ │ main.c: 启动序列 | 事件主循环 | longjmp 退出机制 │ │ │ │ sig_handler.c: 信号处理 watch_dog.c: 硬件看门狗控制 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ 统一使用
●P│ ┌── publib/ 基础库层(libpub.so)────────────────────────────────┐ │ │ │ iThread: 线程管理+心跳 iQueue: 线程安全队列 iMutex: 互斥锁 │ │ │ │ iLog/iLog4c: 日志框架 iDataBase: SQLite封装 iNew: 内存池 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌── include/data_model/ 数据模型层(共享类型定义)─────────────────┐ │ │ │ CHARGER → DEVICE → PARAMETER(状态/控制/设置) → ALARM │ │ │ │ DATA_PROCESSOR(函数指针集) │ DAI(函数指针集) │ EVENT_ARG │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ ┌──────────────┬───────────────────┬──────────────────┐ │ │ ┌──▼──────────┐ ┌─▼──────────────┐ ┌──▼────────────────┐│ │ │ │ DPR(核心 │ │ Sampler 层 │ │ Reporter 层 ││ │ │ │ 数据总线) │ │ 采样器插件 │ │ 报告器插件 ││ │ │ │ 参数管理 │ │ charger.so │ │ ocpp.so ││ │ │ │ 事件分发 │ │ kwhmeter.so │ │ modbus.so ││ │ │ │ 报警判定 │ │ system.so │ │ iec104.so ││ │ │ │ 历史持久化 │ │ ccu.so ... │ │ mqtt.so ... ││ │ │ └──────────┘ └────────┬───────┘ └────────┬──────────┘│ │ │ ▲ DPR.Get/Set │ │ DAI.Get/Set/Query │ │ ┌──┴─────────── data_interfaces/DAI 层 ──┘ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ ┌── comm_ports/ 通信端口 HAL 层(动态库)────────────────────────┐ │ │ │ serial.so │ tcpip.so │ udp.so │ websocket.so │ │ │ │ httpftp.so │ can.so │ ipc.so │ websocket_ex.so │ │ │ └──────────────────────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────┘
九、关键设计模式总结
┌────────────┬────────────────────────────────────────────────────────────────────────────────────┐ │ 设计模式 │ 在系统中的体现 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 插件模式 │ Sampler/Reporter/CommPort 均以 .so 形式存在,dlopen 动态加载,主程序不依赖具体实现 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ HAL 抽象 │ CommPort 统一 6 个函数接口(Open/Accept/Read/Write/Control/Close),屏蔽硬件差异 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 观察者模式 │ Reporter 通过 Register_EvHandler 向 DPR 注册感兴趣的事件类型,DPR 广播时精准投递 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 命令模式 │ 控制命令封装为 INTER_QUEUE_CMD 投入采样器命令队列,异步执行 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 门面模式 │ DAI 作为统一门面,Reporter 无需了解 DPR 内部实现,仅调用 Get/Set/Query/Signal │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 策略模式 │ DATA_PROCESSOR 和 DAI 均为函数指针结构体,可替换不同实现策略 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 心跳守卫 │ ThreadManager + WDT 构成双重"死锁/卡死"守卫,确保系统自愈 │ └────────────┴────────────────────────────────────────────────────────────────────────────────────┘
以上即为本工程的完整软件架构分析文档。整个系统体现了工业嵌入式软件的典型设计哲学:以稳定性为第一优先级,以可扩展性为第 二优先级,通过插件化和接口抽象实现协议的灵活扩展,同时通过多重硬件与软件守卫机制保障 7×24 小时不间断运行。