一、工程概述本工程是一套运行于嵌入式 Linux 平台AM335x ARM Cortex-A8IMMU2/IMMU3 硬件平台的储能充电站控制系统NCCUNetwork 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/ # 数据访问接口层DAIReporter 访问数据的统一入口├── 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.cint 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_LoadModualsstatic 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_MAJOR236旧 WDOG_MAJOR241。4.1.4 信号处理sig_handler.c4.2 核心数据模型include/data_model/数据模型是整个架构的数据契约所有模块共享同一套类型定义。4.2.1 顶层数据结构CHARGERtypedef 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; // 主事件队列MAX96条 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 参数值PARAMETERtypedef 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; // 当前分配的线程槽数动态扩展步长32RUN_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 回调主控接收到回调后设置 bExitTRUE触发系统重启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.ctypedef struct tagQueue {int nSize; // 单个元素大小int nCapacity; // 队列容量char *pBuffer; // 环形缓冲区pthread_mutex_t hSyncLock;// 互斥锁int nCount; // 当前元素数 int nHead; // 头指针int nTail; // 尾指针HANDLE hCountSem; // 信号量用于阻塞等待} QUEUE;队列支持阻塞模式bBlockTRUE带超时等待非阻塞模式bBlockFALSE立即返回可配置的自动扩容nIncStep 04.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.cstatic 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.htypedef 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.cstatic 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 读取所有设备模型、参数定义、报警定义数据管理维护所有设备参数的实时值线程安全事件分发向注册了感兴趣事件的所有模块广播事件报警判定基于表达式引擎计算报警触发/屏蔽条件历史数据持久化将关键数据写入 SQLite4.6.1 DPR 接口dataprocessor_model.htypedef 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/DAIDAI 是 Reporter 访问系统数据的标准接口将 DPR 的内部实现与 Reporter 完全解耦。4.7.1 DAI 接口集合dai_model.htypedef 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.htypedef 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.htypedef 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_ARGtypedef 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_MEM1 # 启用看门狗、闪存支持endififeq ($(MU_TYPE), mu3)CFLAGS -DIMMU3 # IMMU3 硬件平台差异endif6.2 第三方依赖七、架构优缺点总结优点不足与值得关注的点八、架构图总览九、关键设计模式总结以上即为本工程的完整软件架构分析文档。整个系统体现了工业嵌入式软件的典型设计哲学以稳定性为第一优先级以可扩展性为第 二优先级通过插件化和接口抽象实现协议的灵活扩展同时通过多重硬件与软件守卫机制保障 7×24 小时不间断运行。