Libnodave S7 通信库:架构设计与实现解析
一、项目概述
Libnodave 是一个开源的自由通信库,专门用于通过 MPI 适配器或以太网 CP(通信处理器)与 西门子 S7 系列 PLC(S7-200/300/400) 进行通信。库本身由纯 C 编写,具有良好的跨平台特性,可以从 Visual Basic、C#、Delphi、Python、Java、LabVIEW 等语言中调用。
支持的物理通信方式包括:
| 连接方式 | 协议 | 说明 |
|---|---|---|
| RS-232 串口 | MPI / PPI | 通过 MPI 适配器 |
| 以太网 | ISO over TCP | 最常用的 TCP/IP 方式 |
| IBH/MHJ-NetLink | MPI_IBH / PPI_IBH | 网关设备 |
| CP 243 | ISO over TCP (243) | S7-200 专用 |
| S7online | S7online | 通过 s7onlinx.dll |
二、文件结构
libnodave/
├── nodave.h # 头文件:数据结构定义、协议常量、API 声明
├── nodave.c # 核心实现:所有协议的业务逻辑
├── nodavesimple.h # 简化版头文件(只暴露常用 API)
├── log2.h / log2.c # 调试日志宏实现
├── openSocket.c # 跨平台 socket 封装(TCP/串口)
├── openS7online.c # S7online DLL 封装
├── PLC.h # PLC 侧私有结构体
├── PLC.c # PLC 端通信实现
├── Benchmark.c # 性能测试程序
├── testISO_TCP.c # ISO over TCP 完整示例程序
├── testS7online.c # S7online 方式示例
├── setport.c / ... # 各种辅助工具
└── *.h / *.c # 各类协议专用实现文件
三、核心数据结构
3.1 操作系统串口抽象
c
// nodave.h
typedef struct dost {
HANDLE rfd; // Windows: 文件句柄
int rfd; // Linux: 文件描述符
int wfd; // 读写 fd(串口模式下合一)
} _daveOSserialType;
统一了 Linux/Windows 的串口/网络句柄,接口层根据平台选择不同实现。
3.2 接口结构 daveInterface
c
// nodave.h
typedef struct {
char name[32]; // 接口名称
_daveOSserialType fd; // OS 层句柄
int localMPI; // 本地 MPI 地址
int protocol; // 协议类型
int speed; // 通信速率
int timeout; // 超时(微秒)
// 函数指针:协议实现注入
int (*ifread)(daveInterface*, char*, int);
int (*ifwrite)(daveInterface*, char*, int);
int (*getResponse)(daveConnection*);
int (*exchange)(daveConnection*, PDU*);
// ... 更多协议方法
} daveInterface;
设计模式:策略模式 ---
daveNewInterface()根据protocol参数,动态绑定不同的函数指针集,实现协议解耦。同一个daveInterface结构可承载 MPI、TCP、PPI 等完全不同的通信逻辑。
3.3 连接结构 daveConnection
c
// nodave.h
typedef struct {
daveInterface* iface; // 所属接口
int MPIAdr; // PLC MPI 地址
int rack; // 机架号
int slot; // 槽号
int maxPDUlength; // 最大 PDU 长度(协商后确定)
int connectionNumber; // 连接编号
int connectionNumber2; // 扩展编号
int PDUstartO, PDUstartI; // PDU 在报文中的偏移
// 缓冲区
uc msgOut[MAX_DP_BUFFERLEN];
uc msgIn [MAX_DP_BUFFERLEN];
} daveConnection;
3.4 PDU(协议数据单元)
c
// nodave.h
typedef struct {
uc* header; // 指向 msgOut/msgIn 中的 PDU 头
uc* param; // 参数区指针
uc* data; // 数据区指针
int hlen; // 头部长度
int plen; // 参数长度
int dlen; // 数据长度
int udlen; // 用户数据长度
uc* udata; // 用户数据指针
} PDU;
3.5 错误码常量体系
nodave.c 中定义了完整的 S7 错误码映射表(daveStrerror 函数),覆盖:
- 传输层错误(超时、缓冲区不足、PDU 损坏)
- 访问错误(地址越界、数据类型不支持、对象不存在)
- CPU 状态错误(已在 RUN/STOP、功能未授权等)
四、协议体系架构
Libnodave 支持 7 大协议族,通过函数指针注入实现完全解耦:
#mermaid-svg-njGb1SpMwVTDWUFN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-njGb1SpMwVTDWUFN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-njGb1SpMwVTDWUFN .error-icon{fill:#552222;}#mermaid-svg-njGb1SpMwVTDWUFN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-njGb1SpMwVTDWUFN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-njGb1SpMwVTDWUFN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-njGb1SpMwVTDWUFN .marker.cross{stroke:#333333;}#mermaid-svg-njGb1SpMwVTDWUFN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-njGb1SpMwVTDWUFN p{margin:0;}#mermaid-svg-njGb1SpMwVTDWUFN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-njGb1SpMwVTDWUFN .cluster-label text{fill:#333;}#mermaid-svg-njGb1SpMwVTDWUFN .cluster-label span{color:#333;}#mermaid-svg-njGb1SpMwVTDWUFN .cluster-label span p{background-color:transparent;}#mermaid-svg-njGb1SpMwVTDWUFN .label text,#mermaid-svg-njGb1SpMwVTDWUFN span{fill:#333;color:#333;}#mermaid-svg-njGb1SpMwVTDWUFN .node rect,#mermaid-svg-njGb1SpMwVTDWUFN .node circle,#mermaid-svg-njGb1SpMwVTDWUFN .node ellipse,#mermaid-svg-njGb1SpMwVTDWUFN .node polygon,#mermaid-svg-njGb1SpMwVTDWUFN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-njGb1SpMwVTDWUFN .rough-node .label text,#mermaid-svg-njGb1SpMwVTDWUFN .node .label text,#mermaid-svg-njGb1SpMwVTDWUFN .image-shape .label,#mermaid-svg-njGb1SpMwVTDWUFN .icon-shape .label{text-anchor:middle;}#mermaid-svg-njGb1SpMwVTDWUFN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-njGb1SpMwVTDWUFN .rough-node .label,#mermaid-svg-njGb1SpMwVTDWUFN .node .label,#mermaid-svg-njGb1SpMwVTDWUFN .image-shape .label,#mermaid-svg-njGb1SpMwVTDWUFN .icon-shape .label{text-align:center;}#mermaid-svg-njGb1SpMwVTDWUFN .node.clickable{cursor:pointer;}#mermaid-svg-njGb1SpMwVTDWUFN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-njGb1SpMwVTDWUFN .arrowheadPath{fill:#333333;}#mermaid-svg-njGb1SpMwVTDWUFN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-njGb1SpMwVTDWUFN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-njGb1SpMwVTDWUFN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-njGb1SpMwVTDWUFN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-njGb1SpMwVTDWUFN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-njGb1SpMwVTDWUFN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-njGb1SpMwVTDWUFN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-njGb1SpMwVTDWUFN .cluster text{fill:#333;}#mermaid-svg-njGb1SpMwVTDWUFN .cluster span{color:#333;}#mermaid-svg-njGb1SpMwVTDWUFN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-njGb1SpMwVTDWUFN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-njGb1SpMwVTDWUFN rect.text{fill:none;stroke-width:0;}#mermaid-svg-njGb1SpMwVTDWUFN .icon-shape,#mermaid-svg-njGb1SpMwVTDWUFN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-njGb1SpMwVTDWUFN .icon-shape p,#mermaid-svg-njGb1SpMwVTDWUFN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-njGb1SpMwVTDWUFN .icon-shape .label rect,#mermaid-svg-njGb1SpMwVTDWUFN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-njGb1SpMwVTDWUFN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-njGb1SpMwVTDWUFN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-njGb1SpMwVTDWUFN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 协议族
daveInterface 协议注入层
daveInterface
ifread/ifwrite
exchange()
getResponse()
connectPLC()
disconnectPLC()
MPI v1/v2/v3/v4
PPI
ISO over TCP
CP243 ISO over TCP
IBH NetLink MPI/PPI
S7online DLL
AS511 S5协议
UserTransport
4.1 ISO over TCP(最重要的协议)
S7-300/400 系列 PLC 通过以太网模块(CP343/CP443)使用 ISO over TCP 通信:
S7 PLC (CP343/443) Libnodave 应用层 S7 PLC (CP343/443) Libnodave 应用层 #mermaid-svg-lxkUi4u9JsPH7vSb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lxkUi4u9JsPH7vSb .error-icon{fill:#552222;}#mermaid-svg-lxkUi4u9JsPH7vSb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lxkUi4u9JsPH7vSb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lxkUi4u9JsPH7vSb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lxkUi4u9JsPH7vSb .marker.cross{stroke:#333333;}#mermaid-svg-lxkUi4u9JsPH7vSb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lxkUi4u9JsPH7vSb p{margin:0;}#mermaid-svg-lxkUi4u9JsPH7vSb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lxkUi4u9JsPH7vSb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-lxkUi4u9JsPH7vSb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-lxkUi4u9JsPH7vSb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-lxkUi4u9JsPH7vSb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-lxkUi4u9JsPH7vSb .sequenceNumber{fill:white;}#mermaid-svg-lxkUi4u9JsPH7vSb #sequencenumber{fill:#333;}#mermaid-svg-lxkUi4u9JsPH7vSb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-lxkUi4u9JsPH7vSb .messageText{fill:#333;stroke:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lxkUi4u9JsPH7vSb .labelText,#mermaid-svg-lxkUi4u9JsPH7vSb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .loopText,#mermaid-svg-lxkUi4u9JsPH7vSb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-lxkUi4u9JsPH7vSb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-lxkUi4u9JsPH7vSb .noteText,#mermaid-svg-lxkUi4u9JsPH7vSb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-lxkUi4u9JsPH7vSb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lxkUi4u9JsPH7vSb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lxkUi4u9JsPH7vSb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lxkUi4u9JsPH7vSb .actorPopupMenu{position:absolute;}#mermaid-svg-lxkUi4u9JsPH7vSb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-lxkUi4u9JsPH7vSb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lxkUi4u9JsPH7vSb .actor-man circle,#mermaid-svg-lxkUi4u9JsPH7vSb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-lxkUi4u9JsPH7vSb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 连接建立阶段 数据交换阶段 openSocket(port, IP)TCP 三次握手TCP ConnectedISO Connection Request (COTP)ISO Connection ConfirmS7 Establish Connection (COTP)S7 ACK + PDU Length NegotiationdaveReadBytes(dc, area, db, start, len)S7 Read Request PDUS7 Read Response PDU原始字节流daveGetU32/daveGetFloat 等解析函数
4.2 MPI 协议族(3种变体)
MPI(Multi Point Interface)是西门子 PLC 之间的高速并行通信协议,Libnodave 实现了4种变体:
| 变体 | 宏定义 | 特点 |
|---|---|---|
| MPI v1 | daveProtoMPI |
经典 MPI,含 STX |
| MPI v2 | daveProtoMPI2 |
Andrew's 版本,无 STX |
| MPI v3 | daveProtoMPI3 |
Step7 版本,含 CRC |
| MPI v4 | daveProtoMPI4 |
Andrew's 版本,含 STX |
MPI v3 的 CRC 算法 :ccrc() 函数实现了特殊的 CRC-16 多项式校验,预先计算了不同长度的初始值表(startTab[]),这在代码中已注释掉但保留了历史痕迹。
4.3 PPI 协议(S7-200)
PPI(Point-to-Point Interface)是 S7-200 的专用协议,结构简单但与 MPI 有细微差异:
c
// nodave.h 中的偏移差异
case daveProtoPPI:
dc->PDUstartO=3; // 报文头偏移不同
dc->PDUstartI=7;
break;
五、核心 API 设计
5.1 连接管理
#mermaid-svg-ZrqKOLBuqxiAuGNg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZrqKOLBuqxiAuGNg .error-icon{fill:#552222;}#mermaid-svg-ZrqKOLBuqxiAuGNg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZrqKOLBuqxiAuGNg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .marker.cross{stroke:#333333;}#mermaid-svg-ZrqKOLBuqxiAuGNg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZrqKOLBuqxiAuGNg p{margin:0;}#mermaid-svg-ZrqKOLBuqxiAuGNg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .cluster-label text{fill:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .cluster-label span{color:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .cluster-label span p{background-color:transparent;}#mermaid-svg-ZrqKOLBuqxiAuGNg .label text,#mermaid-svg-ZrqKOLBuqxiAuGNg span{fill:#333;color:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .node rect,#mermaid-svg-ZrqKOLBuqxiAuGNg .node circle,#mermaid-svg-ZrqKOLBuqxiAuGNg .node ellipse,#mermaid-svg-ZrqKOLBuqxiAuGNg .node polygon,#mermaid-svg-ZrqKOLBuqxiAuGNg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .rough-node .label text,#mermaid-svg-ZrqKOLBuqxiAuGNg .node .label text,#mermaid-svg-ZrqKOLBuqxiAuGNg .image-shape .label,#mermaid-svg-ZrqKOLBuqxiAuGNg .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZrqKOLBuqxiAuGNg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .rough-node .label,#mermaid-svg-ZrqKOLBuqxiAuGNg .node .label,#mermaid-svg-ZrqKOLBuqxiAuGNg .image-shape .label,#mermaid-svg-ZrqKOLBuqxiAuGNg .icon-shape .label{text-align:center;}#mermaid-svg-ZrqKOLBuqxiAuGNg .node.clickable{cursor:pointer;}#mermaid-svg-ZrqKOLBuqxiAuGNg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .arrowheadPath{fill:#333333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZrqKOLBuqxiAuGNg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZrqKOLBuqxiAuGNg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZrqKOLBuqxiAuGNg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZrqKOLBuqxiAuGNg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .cluster text{fill:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg .cluster span{color:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZrqKOLBuqxiAuGNg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZrqKOLBuqxiAuGNg rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZrqKOLBuqxiAuGNg .icon-shape,#mermaid-svg-ZrqKOLBuqxiAuGNg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZrqKOLBuqxiAuGNg .icon-shape p,#mermaid-svg-ZrqKOLBuqxiAuGNg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZrqKOLBuqxiAuGNg .icon-shape .label rect,#mermaid-svg-ZrqKOLBuqxiAuGNg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZrqKOLBuqxiAuGNg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZrqKOLBuqxiAuGNg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZrqKOLBuqxiAuGNg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} daveNewInterface()
daveInterface 结构
daveNewConnection()
daveConnection 结构
daveConnectPLC()
连接就绪
数据交换
daveDisconnectPLC()
daveDisconnectAdapter()
5.2 数据读写 API
#mermaid-svg-JZUHBhPc2XenAqNw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JZUHBhPc2XenAqNw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JZUHBhPc2XenAqNw .error-icon{fill:#552222;}#mermaid-svg-JZUHBhPc2XenAqNw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JZUHBhPc2XenAqNw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JZUHBhPc2XenAqNw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JZUHBhPc2XenAqNw .marker.cross{stroke:#333333;}#mermaid-svg-JZUHBhPc2XenAqNw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JZUHBhPc2XenAqNw p{margin:0;}#mermaid-svg-JZUHBhPc2XenAqNw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JZUHBhPc2XenAqNw .cluster-label text{fill:#333;}#mermaid-svg-JZUHBhPc2XenAqNw .cluster-label span{color:#333;}#mermaid-svg-JZUHBhPc2XenAqNw .cluster-label span p{background-color:transparent;}#mermaid-svg-JZUHBhPc2XenAqNw .label text,#mermaid-svg-JZUHBhPc2XenAqNw span{fill:#333;color:#333;}#mermaid-svg-JZUHBhPc2XenAqNw .node rect,#mermaid-svg-JZUHBhPc2XenAqNw .node circle,#mermaid-svg-JZUHBhPc2XenAqNw .node ellipse,#mermaid-svg-JZUHBhPc2XenAqNw .node polygon,#mermaid-svg-JZUHBhPc2XenAqNw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JZUHBhPc2XenAqNw .rough-node .label text,#mermaid-svg-JZUHBhPc2XenAqNw .node .label text,#mermaid-svg-JZUHBhPc2XenAqNw .image-shape .label,#mermaid-svg-JZUHBhPc2XenAqNw .icon-shape .label{text-anchor:middle;}#mermaid-svg-JZUHBhPc2XenAqNw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JZUHBhPc2XenAqNw .rough-node .label,#mermaid-svg-JZUHBhPc2XenAqNw .node .label,#mermaid-svg-JZUHBhPc2XenAqNw .image-shape .label,#mermaid-svg-JZUHBhPc2XenAqNw .icon-shape .label{text-align:center;}#mermaid-svg-JZUHBhPc2XenAqNw .node.clickable{cursor:pointer;}#mermaid-svg-JZUHBhPc2XenAqNw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JZUHBhPc2XenAqNw .arrowheadPath{fill:#333333;}#mermaid-svg-JZUHBhPc2XenAqNw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JZUHBhPc2XenAqNw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JZUHBhPc2XenAqNw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JZUHBhPc2XenAqNw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JZUHBhPc2XenAqNw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JZUHBhPc2XenAqNw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JZUHBhPc2XenAqNw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JZUHBhPc2XenAqNw .cluster text{fill:#333;}#mermaid-svg-JZUHBhPc2XenAqNw .cluster span{color:#333;}#mermaid-svg-JZUHBhPc2XenAqNw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JZUHBhPc2XenAqNw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JZUHBhPc2XenAqNw rect.text{fill:none;stroke-width:0;}#mermaid-svg-JZUHBhPc2XenAqNw .icon-shape,#mermaid-svg-JZUHBhPc2XenAqNw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JZUHBhPc2XenAqNw .icon-shape p,#mermaid-svg-JZUHBhPc2XenAqNw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JZUHBhPc2XenAqNw .icon-shape .label rect,#mermaid-svg-JZUHBhPc2XenAqNw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JZUHBhPc2XenAqNw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JZUHBhPc2XenAqNw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JZUHBhPc2XenAqNw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 写操作
davePrepareWriteRequest()
daveAddVarToWriteRequest()
_daveExchange()
_daveSetupReceivedPDU()
_daveTestWriteResult()
写入完成
读操作
davePrepareReadRequest()
daveAddVarToReadRequest()
_daveExchange()
_daveSetupReceivedPDU()
_daveTestReadResult()
解析: daveGetU8/U16/U32/Float...
5.3 批量化读写
c
// 批量读取多个变量(一次请求,多次响应)
davePrepareReadRequest(dc, &p);
daveAddVarToReadRequest(&p, daveInputs, 0, 0, 1); // 读输入
daveAddVarToReadRequest(&p, daveFlags, 0, 0, 4); // 读标志位
daveAddVarToReadRequest(&p, daveDB, 6, 20, 2); // 读数据块
res = daveExecReadRequest(dc, &p, &rs); // 执行
// 通过 daveUseResult 逐个提取结果
daveUseResult(dc, &rs, 0); // 第1个结果
daveGetU8(dc); // 解析
六、PDU 结构详解
S7 通信的核心是 PDU(Protocol Data Unit),分为三个区域:
#mermaid-svg-4fcRNdqciXNfTyvX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4fcRNdqciXNfTyvX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4fcRNdqciXNfTyvX .error-icon{fill:#552222;}#mermaid-svg-4fcRNdqciXNfTyvX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4fcRNdqciXNfTyvX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4fcRNdqciXNfTyvX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4fcRNdqciXNfTyvX .marker.cross{stroke:#333333;}#mermaid-svg-4fcRNdqciXNfTyvX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4fcRNdqciXNfTyvX p{margin:0;}#mermaid-svg-4fcRNdqciXNfTyvX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4fcRNdqciXNfTyvX .cluster-label text{fill:#333;}#mermaid-svg-4fcRNdqciXNfTyvX .cluster-label span{color:#333;}#mermaid-svg-4fcRNdqciXNfTyvX .cluster-label span p{background-color:transparent;}#mermaid-svg-4fcRNdqciXNfTyvX .label text,#mermaid-svg-4fcRNdqciXNfTyvX span{fill:#333;color:#333;}#mermaid-svg-4fcRNdqciXNfTyvX .node rect,#mermaid-svg-4fcRNdqciXNfTyvX .node circle,#mermaid-svg-4fcRNdqciXNfTyvX .node ellipse,#mermaid-svg-4fcRNdqciXNfTyvX .node polygon,#mermaid-svg-4fcRNdqciXNfTyvX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4fcRNdqciXNfTyvX .rough-node .label text,#mermaid-svg-4fcRNdqciXNfTyvX .node .label text,#mermaid-svg-4fcRNdqciXNfTyvX .image-shape .label,#mermaid-svg-4fcRNdqciXNfTyvX .icon-shape .label{text-anchor:middle;}#mermaid-svg-4fcRNdqciXNfTyvX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4fcRNdqciXNfTyvX .rough-node .label,#mermaid-svg-4fcRNdqciXNfTyvX .node .label,#mermaid-svg-4fcRNdqciXNfTyvX .image-shape .label,#mermaid-svg-4fcRNdqciXNfTyvX .icon-shape .label{text-align:center;}#mermaid-svg-4fcRNdqciXNfTyvX .node.clickable{cursor:pointer;}#mermaid-svg-4fcRNdqciXNfTyvX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4fcRNdqciXNfTyvX .arrowheadPath{fill:#333333;}#mermaid-svg-4fcRNdqciXNfTyvX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4fcRNdqciXNfTyvX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4fcRNdqciXNfTyvX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4fcRNdqciXNfTyvX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4fcRNdqciXNfTyvX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4fcRNdqciXNfTyvX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4fcRNdqciXNfTyvX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4fcRNdqciXNfTyvX .cluster text{fill:#333;}#mermaid-svg-4fcRNdqciXNfTyvX .cluster span{color:#333;}#mermaid-svg-4fcRNdqciXNfTyvX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4fcRNdqciXNfTyvX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4fcRNdqciXNfTyvX rect.text{fill:none;stroke-width:0;}#mermaid-svg-4fcRNdqciXNfTyvX .icon-shape,#mermaid-svg-4fcRNdqciXNfTyvX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4fcRNdqciXNfTyvX .icon-shape p,#mermaid-svg-4fcRNdqciXNfTyvX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4fcRNdqciXNfTyvX .icon-shape .label rect,#mermaid-svg-4fcRNdqciXNfTyvX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4fcRNdqciXNfTyvX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4fcRNdqciXNfTyvX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4fcRNdqciXNfTyvX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} S7 PDU
Header
10-12 bytes
Parameter
可变长度
Data
可变长度
6.1 PDU Header(10-12字节)
c
// nodave.h 中的结构
typedef struct {
uc P; // 协议 ID(固定 0x32)
uc type; // 1=Request, 2=Response, 3=ACK
uc[] reserved;
uc[] plen; // 参数长度(高字节在前)
uc[] dlen; // 数据长度(高字节在前)
} PDUHeader;
6.2 PDU 参数(Read 示例)
0x04 ← daveFuncRead
0x00 ← 变量数量
[变长区域] ← 每次 daveAddVarToReadRequest 添加 12 字节
- 0x12, 0x0a, 0x10
- 数据类型(1=bit/2=byte/4=word)
- 长度(字节)
- DB 编号
- 区域代码(daveDB/daveFlags/daveInputs...)
- 起始地址
七、S7 地址区域体系
c
// nodave.h
#define daveSysInfo 0x81 // 系统信息(S7-200)
#define daveSysFlags 0x82 // 系统标志
#define daveAnaIn 0x83 // 模拟输入
#define daveAnaOut 0x84 // 模拟输出
#define daveP 0x80 // 外设 I/O
#define daveInputs 0x81 // 数字输入
#define daveOutputs 0x82 // 数字输出
#define daveFlags 0x83 // 标志位(M)
#define daveDB 0x84 // 数据块(DB)
#define daveDI 0x85 // 实例数据块
#define daveLocal 0x86 // 局部数据
#define daveV 0x87 // V 存储区(S7-200)
#define daveCounter 0x1C // S7 计数器
#define daveTimer 0x1D // S7 定时器
#define daveCounter200 0x00 // IEC 计数器
#define daveTimer200 0x00 // IEC 定时器
八、字节序处理
S7 使用 big-endian(高字节在前),Libnodave 在 x86(little-endian)平台需要转换:
c
// nodave.c 中的字节序交换函数
short DECL2 daveSwapIed_16(short ff) {
// B0 B1 → B1 B0
}
int DECL2 daveSwapIed_32(int ff) {
// B0 B1 B2 B3 → B3 B2 B1 B0
}
浮点数的特殊处理 :S7 和 x86 的 float 字节序不同,toPLCfloat() 和 daveToPLCfloat() 同时处理字节序反转。
九、调试系统
c
// nodave.h 中的调试标志位
int daveDebug = 0;
// 0x01 byte level data
// 0x02 packets
// 0x04 init adapter
// 0x08 connect
// 0x10 exchange
// 0x20 PDU detail
// 0x40 error messages
// 0x80 special characters
// ...
// 使用方法
daveSetDebug(daveDebugAll); // 打印所有调试信息
daveSetDebug(daveDebugPrintErrors); // 只打印错误
十、完整使用流程
#mermaid-svg-SAvy38QZnkZD3Sp0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SAvy38QZnkZD3Sp0 .error-icon{fill:#552222;}#mermaid-svg-SAvy38QZnkZD3Sp0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SAvy38QZnkZD3Sp0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .marker.cross{stroke:#333333;}#mermaid-svg-SAvy38QZnkZD3Sp0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SAvy38QZnkZD3Sp0 p{margin:0;}#mermaid-svg-SAvy38QZnkZD3Sp0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .cluster-label text{fill:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .cluster-label span{color:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .cluster-label span p{background-color:transparent;}#mermaid-svg-SAvy38QZnkZD3Sp0 .label text,#mermaid-svg-SAvy38QZnkZD3Sp0 span{fill:#333;color:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .node rect,#mermaid-svg-SAvy38QZnkZD3Sp0 .node circle,#mermaid-svg-SAvy38QZnkZD3Sp0 .node ellipse,#mermaid-svg-SAvy38QZnkZD3Sp0 .node polygon,#mermaid-svg-SAvy38QZnkZD3Sp0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .rough-node .label text,#mermaid-svg-SAvy38QZnkZD3Sp0 .node .label text,#mermaid-svg-SAvy38QZnkZD3Sp0 .image-shape .label,#mermaid-svg-SAvy38QZnkZD3Sp0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-SAvy38QZnkZD3Sp0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .rough-node .label,#mermaid-svg-SAvy38QZnkZD3Sp0 .node .label,#mermaid-svg-SAvy38QZnkZD3Sp0 .image-shape .label,#mermaid-svg-SAvy38QZnkZD3Sp0 .icon-shape .label{text-align:center;}#mermaid-svg-SAvy38QZnkZD3Sp0 .node.clickable{cursor:pointer;}#mermaid-svg-SAvy38QZnkZD3Sp0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .arrowheadPath{fill:#333333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SAvy38QZnkZD3Sp0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SAvy38QZnkZD3Sp0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SAvy38QZnkZD3Sp0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SAvy38QZnkZD3Sp0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .cluster text{fill:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 .cluster span{color:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SAvy38QZnkZD3Sp0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SAvy38QZnkZD3Sp0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-SAvy38QZnkZD3Sp0 .icon-shape,#mermaid-svg-SAvy38QZnkZD3Sp0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SAvy38QZnkZD3Sp0 .icon-shape p,#mermaid-svg-SAvy38QZnkZD3Sp0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SAvy38QZnkZD3Sp0 .icon-shape .label rect,#mermaid-svg-SAvy38QZnkZD3Sp0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SAvy38QZnkZD3Sp0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SAvy38QZnkZD3Sp0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SAvy38QZnkZD3Sp0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
- openSocket(102, IP)
- daveNewInterface()
- daveNewConnection()
- daveConnectPLC()
连接成功?
错误处理 - PDU 长度协商
- 循环数据交换
读/写操作 - daveDisconnectPLC()
- closeSocket()
十一、关键设计思想总结
| 设计模式 | 在 Libnodave 中的体现 |
|---|---|
| 策略模式 | daveInterface 中的函数指针,协议可替换 |
| 适配器模式 | _daveOSserialType 统一不同 OS 的句柄 |
| 工厂模式 | daveNewInterface() 根据协议类型创建实例 |
| 门面模式 | daveReadBytes() 等高层 API 封装底层 PDU 细节 |
| 模板方法 | _daveExchange() 统一通信流程,具体协议有差异 |
十二、测试程序示例(ISO over TCP)
c
// testISO_TCP.c 核心流程
#include "nodavesimple.h"
#include "openSocket.h"
int main(int argc, char **argv) {
_daveOSserialType fds;
daveInterface *di;
daveConnection *dc;
// 1. 建立 TCP 连接
fds.rfd = openSocket(102, argv[1]); // 端口 102 是 S7 默认
fds.wfd = fds.rfd;
// 2. 初始化接口
di = daveNewInterface(fds, "IF1", 0, daveProtoISOTCP, daveSpeed187k);
daveSetTimeout(di, 5000000); // 5 秒超时
// 3. 创建连接(rack=0, slot=2 是 S7-300 默认)
dc = daveNewConnection(di, 2, 0, 2);
// 4. 连接 PLC
if (daveConnectPLC(dc) == 0) {
printf("Connected!\n");
// 5. 读取 DB1 前 64 字节
res = daveReadBytes(dc, daveDB, 1, 0, 64, NULL);
if (res == 0) {
a = daveGetU16(dc);
d = daveGetFloat(dc);
}
// 6. 批量读
davePrepareReadRequest(dc, &p);
daveAddVarToReadRequest(&p, daveFlags, 0, 0, 4);
daveExecReadRequest(dc, &p, &rs);
daveUseResult(dc, &rs, 0);
}
daveDisconnectPLC(dc);
closeSocket(fds.rfd);
}
结语
Libnodave 是一个设计精良的工业通信库,其核心价值在于:
- 协议抽象层 --- 函数指针注入使 8 种协议共用一套接口
- 跨平台实现 --- Linux/Windows/嵌入式系统统一 API
- PDU 粒度控制 --- 支持单变量读写和批量多变量读写
- 完善的错误体系 --- 从传输层到应用层错误码全覆盖
- 多语言绑定友好 --- 纯 C ABI,任何语言均可调用
对于需要与西门子 PLC 集成的工业自动化应用,Libnodave 至今仍是免费开源领域最成熟的选择之一。