要实现 openPOWERLINK C 语言读取 PLC 数据 + MQTT 发送,核心逻辑是:用 C 语言调用 openPOWERLINK 协议栈读取 PLC 的 PDO/SDO 数据,再通过 MQTT 客户端库(如 Paho MQTT C)将数据发布到 MQTT Broker。以下是完整可落地的方案,包含环境搭建、代码开发、调试步骤,适配 Windows/Linux 系统。
一、核心前提(必满足)
1. 硬件要求
- 目标 PLC:支持 Powerlink 协议(贝加莱 X20、倍福 CX 等),已配置为「从站(CN)」,完成 PDO/SDO 映射(记录变量的「OD 索引 + 子索引 + 数据类型」);
- 通信连接:C 程序运行设备(Powerlink 主站)与 PLC 直连(CAT5e 以上网线)或接入 Powerlink 兼容交换机;
- 网卡:推荐支持硬件时间戳的网卡(如 Intel I210),保障 Powerlink 实时性。
2. 软件依赖
| 依赖项 | 作用 | 下载 / 安装方式 |
|---|---|---|
| openPOWERLINK 协议栈 | Powerlink 主站通信核心(C 语言) | 官网:https://openpowerlink.sourceforge.io/ 或 Git 克隆:git clone https://git.code.sf.net/p/openpowerlink/code |
| Paho MQTT C 库 | MQTT 客户端(C 语言,轻量稳定) | Git 克隆:git clone https://github.com/eclipse/paho.mqtt.c.git |
| 编译工具 | 编译协议栈、MQTT 库、应用程序 | Windows:MinGW-w64;Linux:gcc/make;CMake(跨平台构建工具) |
| MQTT Broker | 接收 MQTT 消息(如 EMQ X、Mosquitto、阿里云 IoT 等) | 本地部署:Mosquitto(sudo apt install mosquitto);云端:阿里云 IoT / 华为云 IoT |
| 辅助工具 | 调试 MQTT 消息 | MQTTX(客户端工具,验证消息是否发布成功) |
二、第一步:搭建基础环境(编译依赖库)
需先编译 openPOWERLINK 协议栈 和 Paho MQTT C 库 ,生成静态库 / 动态库,供应用程序调用。以下以 Linux(Ubuntu 20.04) 为例(Windows 流程类似,仅编译命令调整)。
1. 安装系统依赖
bash
运行
sudo apt update && sudo apt install -y gcc make cmake libpcap-dev pkg-config
libpcap-dev:openPOWERLINK 依赖(网络抓包);pkg-config:辅助查找 MQTT 库路径。
2. 编译 Paho MQTT C 库
bash
运行
# 1. 克隆源码
git clone https://github.com/eclipse/paho.mqtt.c.git
cd paho.mqtt.c
# 2. 创建编译目录
mkdir build && cd build
# 3. CMake配置(生成静态库+动态库)
cmake .. -DCMAKE_BUILD_TYPE=Release -DPAHO_BUILD_STATIC=ON -DPAHO_BUILD_SHARED=ON
# 4. 编译并安装(安装到系统目录/usr/local)
make -j4 && sudo make install
# 5. 刷新库缓存(让系统识别新安装的MQTT库)
sudo ldconfig
编译成功后,生成:
- 静态库:
/usr/local/lib/libpaho-mqtt3c.a(C 语言同步客户端); - 动态库:
/usr/local/lib/libpaho-mqtt3c.so; - 头文件:
/usr/local/include/mqtt/(包含MQTTClient.h等)。
3. 编译 openPOWERLINK 协议栈
bash
运行
# 1. 克隆源码(若已下载可跳过)
git clone https://git.code.sf.net/p/openpowerlink/code openpowerlink-code
cd openpowerlink-code
# 2. 创建编译目录
mkdir build && cd build
# 3. CMake配置(启用Powerlink主站模式+静态库)
cmake .. -DCMAKE_BUILD_TYPE=Release \
-DOPLK_BUILD_STACK=ON \
-DOPLK_BUILD_APPS=OFF \
-DOPLK_TARGET_PLATFORM=linux \
-DOPLK_NETIF=linux \
-DOPLK_BUILD_STATIC_LIB=ON \
-DOPLK_MN_MODE=ON # 运行为主站(读取PLC数据)
# 4. 编译(生成静态库)
make -j4
编译成功后,核心文件在 build/lib/Release/:
- 静态库:
libpowerlink.a; - 头文件:源码根目录
include/(如oplk/oplk.h、oplk/sdo.h、oplk/pdo.h)。
三、第二步:C 语言核心代码(Powerlink 读取 + MQTT 发送)
代码分为 3 个模块:Powerlink初始化与数据读取、MQTT连接与消息发布、主逻辑调度。需将 openPOWERLINK 的头文件目录和库文件路径配置到编译命令中。
完整代码(powerlink_mqtt.c)
c
运行
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
// 1. openPOWERLINK头文件(需替换为你的openPOWERLINK源码include路径)
#include "../../openpowerlink-code/include/oplk/oplk.h"
#include "../../openpowerlink-code/include/oplk/sdo.h"
#include "../../openpowerlink-code/include/oplk/pdo.h"
// 2. Paho MQTT头文件(系统安装路径)
#include <mqtt/MQTTClient.h>
// -------------------------- 配置参数(必须根据实际场景修改)--------------------------
// Powerlink配置
#define PLC_NODE_ID 0x01 // 目标PLC的Node ID(1-239,唯一)
#define MAIN_STATION_ID 0xEF // Powerlink主站Node ID(默认239=0xEF)
#define NET_IF_NAME "eth0" // 主站网卡名称(Linux:ifconfig查询;Windows:NPF GUID)
// 变量配置(从PLC的EDS文件/厂商工具获取)
#define VAR_OD_INDEX 0x0E253A4E // 变量的OD索引(十六进制)
#define VAR_OD_SUBINDEX 0x002E // 变量的子索引(十六进制)
#define VAR_DATA_LEN 2 // 变量长度(字节):UINT16=2,INT32=4,FLOAT=4
#define VAR_DATA_TYPE "UINT16" // 变量数据类型(用于JSON格式化)
// MQTT配置
#define MQTT_BROKER "tcp://localhost:1883" // MQTT Broker地址(本地Mosquitto默认)
#define MQTT_CLIENT_ID "powerlink_plc_reader" // MQTT客户端ID(唯一)
#define MQTT_TOPIC "plc/powerlink/data" // 发布消息的主题
#define MQTT_QOS 1 // QoS等级(0=最多一次,1=至少一次,2=恰好一次)
#define MQTT_USERNAME "" // MQTT用户名(无则留空)
#define MQTT_PASSWORD "" // MQTT密码(无则留空)
#define PUBLISH_INTERVAL 1000 // 发布间隔(毫秒,PDO实时读取可设为10)
// --------------------------------------------------------------------------------
// 全局变量(控制程序退出)
static volatile int g_run_program = 1;
// 信号处理函数(捕获Ctrl+C,优雅退出)
void signal_handler(int sig) {
if (sig == SIGINT) {
printf("\n收到退出信号,正在关闭程序...\n");
g_run_program = 0;
}
}
// -------------------------- MQTT相关函数 --------------------------
/**
* @brief 初始化MQTT客户端,建立与Broker的连接
* @return MQTTClient句柄(NULL表示失败)
*/
MQTTClient init_mqtt_client() {
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
int ret;
// 1. 创建MQTT客户端
ret = MQTTClient_create(&client, MQTT_BROKER, MQTT_CLIENT_ID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
if (ret != MQTTCLIENT_SUCCESS) {
printf("MQTT客户端创建失败,错误码: %d\n", ret);
return NULL;
}
// 2. 配置连接参数
conn_opts.keepAliveInterval = 60; // 心跳间隔(秒)
conn_opts.cleansession = 1; // 清除会话(断开后重新连接不恢复历史消息)
if (strlen(MQTT_USERNAME) > 0) {
conn_opts.username = MQTT_USERNAME;
conn_opts.password = MQTT_PASSWORD;
}
// 3. 连接MQTT Broker
printf("正在连接MQTT Broker: %s...\n", MQTT_BROKER);
ret = MQTTClient_connect(client, &conn_opts);
if (ret != MQTTCLIENT_SUCCESS) {
printf("MQTT连接失败,错误码: %d\n", ret);
MQTTClient_destroy(&client);
return NULL;
}
printf("MQTT连接成功!\n");
return client;
}
/**
* @brief 发布消息到MQTT Broker
* @param client MQTTClient句柄
* @param payload 消息内容(字符串)
* @return 0=成功,非0=失败
*/
int publish_mqtt_message(MQTTClient client, const char* payload) {
MQTTClient_message pub_msg = MQTTClient_message_initializer;
int ret;
pub_msg.payload = (void*)payload;
pub_msg.payloadlen = strlen(payload);
pub_msg.qos = MQTT_QOS;
pub_msg.retained = 0; // 不保留消息(新订阅者看不到历史消息)
ret = MQTTClient_publishMessage(client, MQTT_TOPIC, &pub_msg, NULL);
if (ret != MQTTCLIENT_SUCCESS) {
printf("MQTT消息发布失败,错误码: %d\n", ret);
return ret;
}
printf("[MQTT发布] 主题: %s, 消息: %s\n", MQTT_TOPIC, payload);
return 0;
}
// -------------------------- Powerlink相关函数 --------------------------
/**
* @brief 初始化Powerlink主站
* @return 0=成功,非0=失败(参考openPOWERLINK错误码)
*/
int init_powerlink() {
tOplkError ret;
// 1. 初始化Powerlink栈(主站模式)
printf("正在初始化Powerlink主站...\n");
ret = oplk_init(NET_IF_NAME, MAIN_STATION_ID, OPLK_MN_MODE);
if (ret != kErrorOk) {
printf("Powerlink初始化失败,错误码: %d\n", ret);
return ret;
}
// 2. 启动Powerlink栈
printf("启动Powerlink栈...\n");
ret = oplk_start();
if (ret != kErrorOk) {
printf("Powerlink栈启动失败,错误码: %d\n", ret);
oplk_shutdown();
return ret;
}
// 3. 等待主站与PLC从站建立连接(3秒超时)
printf("等待连接PLC(Node ID: %d)...\n", PLC_NODE_ID);
sleep(3);
// 验证从站是否在线
tOplkNodeInfo nodeInfo;
ret = oplk_enumNodes(&nodeInfo, NULL);
if (ret == kErrorOk && nodeInfo.nodeId == PLC_NODE_ID && nodeInfo.state == kNmtStateOperational) {
printf("Powerlink连接成功!PLC已进入运行状态。\n");
return 0;
} else {
printf("Powerlink连接PLC失败,PLC可能未在线或配置错误。\n");
oplk_stop();
oplk_shutdown();
return -1;
}
}
/**
* @brief 通过SDO读取PLC数据(非实时,适用于参数读取)
* @param data_buf 接收数据的缓冲区
* @param buf_len 缓冲区长度(需与VAR_DATA_LEN一致)
* @return 0=成功,非0=失败
*/
int read_powerlink_sdo(uint8_t* data_buf, uint32_t buf_len) {
tOplkError ret;
if (buf_len != VAR_DATA_LEN) {
printf("SDO读取:缓冲区长度不匹配!\n");
return -1;
}
// 调用openPOWERLINK的SDO读取接口(阻塞模式)
ret = oplk_sdoClientDownload(
PLC_NODE_ID, // 目标PLC的Node ID
VAR_OD_INDEX, // OD索引
VAR_OD_SUBINDEX, // 子索引
data_buf, // 接收缓冲区
buf_len, // 数据长度
kSdoClientBlocking // 阻塞模式(直到读取完成)
);
if (ret != kErrorOk) {
printf("SDO读取失败,错误码: %d\n", ret);
return ret;
}
return 0;
}
/**
* @brief PDO接收回调函数(实时数据触发,由Powerlink栈异步调用)
* @param nodeId 发送数据的PLC Node ID
* @param pData 接收的数据缓冲区
* @param dataLen 数据长度
*/
void plc_pdo_receive_callback(uint16_t nodeId, uint8_t* pData, uint32_t dataLen) {
if (nodeId != PLC_NODE_ID || dataLen != VAR_DATA_LEN) {
return; // 忽略非目标PLC或长度不匹配的数据
}
// 解析数据(根据VAR_DATA_TYPE调整)
uint16_t value = 0;
if (strcmp(VAR_DATA_TYPE, "UINT16") == 0) {
value = (pData[0] << 8) | pData[1]; // 大端模式(需与PLC一致,不一致则交换字节)
} else if (strcmp(VAR_DATA_TYPE, "INT16") == 0) {
value = (int16_t)((pData[0] << 8) | pData[1]);
}
// 格式化MQTT消息(JSON格式,便于上位机解析)
char mqtt_payload[128];
snprintf(mqtt_payload, sizeof(mqtt_payload),
"{\"node_id\":%d,\"od_index\":\"0x%08X\",\"subindex\":\"0x%02X\",\"data_type\":\"%s\",\"value\":%d,\"timestamp\":%ld}",
PLC_NODE_ID, VAR_OD_INDEX, VAR_OD_SUBINDEX, VAR_DATA_TYPE, value, time(NULL));
// 发布MQTT消息(注意:回调函数中避免阻塞,若MQTT发布失败可记录日志)
MQTTClient client = *(MQTTClient*)oplk_getUserContext(); // 从Powerlink上下文获取MQTT句柄
if (client != NULL) {
publish_mqtt_message(client, mqtt_payload);
}
}
// -------------------------- 主函数 --------------------------
int main() {
MQTTClient mqtt_client = NULL;
uint8_t sdo_data[VAR_DATA_LEN] = {0};
int ret = 0;
// 1. 注册信号处理(捕获Ctrl+C)
signal(SIGINT, signal_handler);
// 2. 初始化MQTT客户端
mqtt_client = init_mqtt_client();
if (mqtt_client == NULL) {
ret = -1;
goto exit_program;
}
// 3. 初始化Powerlink主站
// 将MQTT句柄存入Powerlink用户上下文(供PDO回调函数使用)
oplk_setUserContext(&mqtt_client);
ret = init_powerlink();
if (ret != 0) {
goto exit_program;
}
// 4. 注册PDO接收回调(实时读取用)
ret = oplk_registerPdoRxCallback(plc_pdo_receive_callback);
if (ret != kErrorOk) {
printf("PDO回调注册失败,错误码: %d\n", ret);
goto exit_program;
}
printf("PDO回调注册成功,开始接收实时数据...\n");
// 5. 主循环(根据配置选择SDO或PDO读取)
while (g_run_program) {
// 方式1:SDO读取(非实时,按间隔发布)
/*
ret = read_powerlink_sdo(sdo_data, VAR_DATA_LEN);
if (ret == 0) {
uint16_t sdo_value = (sdo_data[0] << 8) | sdo_data[1];
char payload[128];
snprintf(payload, sizeof(payload),
"{\"type\":\"SDO\",\"node_id\":%d,\"value\":%d,\"timestamp\":%ld}",
PLC_NODE_ID, sdo_value, time(NULL));
publish_mqtt_message(mqtt_client, payload);
}
usleep(PUBLISH_INTERVAL * 1000); // 毫秒转微秒
*/
// 方式2:PDO读取(实时,回调函数自动发布,主循环仅保持程序运行)
usleep(10000); // 10ms延时,降低CPU占用
}
exit_program:
// 6. 资源释放(顺序:先关闭MQTT,再关闭Powerlink)
if (mqtt_client != NULL) {
MQTTClient_disconnect(mqtt_client, 1000); // 1秒内断开连接
MQTTClient_destroy(&mqtt_client);
printf("MQTT客户端已关闭\n");
}
oplk_stop();
oplk_shutdown();
printf("Powerlink栈已关闭\n");
return ret;
}
四、第三步:编译与运行
1. 编译命令(Linux)
需指定 openPOWERLINK 的头文件路径、库文件路径,以及链接 MQTT 库和 Powerlink 库。假设:
- openPOWERLINK 源码目录:
../openpowerlink-code; - 应用程序代码:
powerlink_mqtt.c; - 生成可执行文件:
powerlink_mqtt。
编译命令:
bash
运行
gcc powerlink_mqtt.c -o powerlink_mqtt \
-I../openpowerlink-code/include \ # openPOWERLINK头文件路径
-L../openpowerlink-code/build/lib/Release \ # openPOWERLINK库文件路径
-lpowerlink \ # 链接openPOWERLINK静态库
-lpaho-mqtt3c \ # 链接Paho MQTT C库
-lpcap \ # 链接libpcap库(Powerlink依赖)
-pthread # 线程支持(Powerlink和MQTT均需)
2. 运行前准备
-
启动 MQTT Broker(本地 Mosquitto): bash
运行
sudo systemctl start mosquitto # 启动服务 sudo systemctl enable mosquitto # 设置开机自启 -
验证 MQTT Broker 是否可用:用 MQTTX 连接
tcp://localhost:1883,订阅主题plc/powerlink/data。 -
确认 PLC 配置:PLC 已设为 Powerlink 从站(Node ID 与代码一致),变量已映射到 TxPDO(PDO 方式)或启用 SDO 访问(SDO 方式)。
3. 运行程序
bash
运行
# 赋予执行权限
chmod +x powerlink_mqtt
# 运行程序
./powerlink_mqtt
4. 预期输出
plaintext
正在连接MQTT Broker: tcp://localhost:1883...
MQTT连接成功!
正在初始化Powerlink主站...
启动Powerlink栈...
等待连接PLC(Node ID: 1)...
Powerlink连接成功!PLC已进入运行状态。
PDO回调注册成功,开始接收实时数据...
[MQTT发布] 主题: plc/powerlink/data, 消息: {"node_id":1,"od_index":"0x0E253A4E","subindex":"0x002E","data_type":"UINT16","value":1234,"timestamp":1730000000}
[MQTT发布] 主题: plc/powerlink/data, 消息: {"node_id":1,"od_index":"0x0E253A4E","subindex":"0x002E","data_type":"UINT16","value":1236,"timestamp":1730000001}
...
五、关键配置与避坑指南
1. 核心参数修改(必须适配你的场景)
PLC_NODE_ID:PLC 的 Powerlink Node ID(从 PLC 配置工具中获取,唯一);VAR_OD_INDEX/VAR_OD_SUBINDEX:变量的索引和子索引(从 EDS 文件中提取);VAR_DATA_LEN/VAR_DATA_TYPE:变量长度和类型(如FLOAT对应 4 字节,需修改数据解析逻辑);MQTT_BROKER:若用云端 Broker(如阿里云 IoT),格式为tcp://xxx.xxx.xxx.xxx:1883,需填写用户名 / 密码。
2. 数据解析注意事项
-
字节序:PLC 的 PDO/SDO 数据默认多为「大端模式」(高位字节在前),代码中
UINT16解析用(pData[0] << 8) | pData[1];若 PLC 是小端模式,需改为(pData[1] << 8) | pData[0]; -
浮点数解析:若变量是
FLOAT(4 字节),用memcpy转换:c
运行
float float_value; memcpy(&float_value, pData, 4);
3. 常见错误排查
| 错误现象 | 排查方向 |
|---|---|
| Powerlink 初始化失败 | 1. 网卡名称错误(NET_IF_NAME);2. 网卡被占用(如 Wireshark);3. openPOWERLINK 库编译错误;4. 未安装libpcap-dev |
| 无法连接 PLC | 1. Node ID 冲突;2. PLC 未设为从站;3. PLC 未重启生效配置;4. 网线 / 交换机不兼容 |
| SDO 读取失败 | 1. 索引 / 子索引错误;2. 变量未启用 SDO 访问;3. 数据长度不匹配 |
| PDO 无回调数据 | 1. 变量未映射到 PLC 的 TxPDO;2. PDO 映射的索引 / 子索引与代码不一致;3. 实时同步失败(换 Powerlink 交换机) |
| MQTT 连接失败 | 1. Broker 地址 / 端口错误;2. 用户名 / 密码错误;3. 防火墙拦截 1883 端口(云端需开放端口) |
4. 实时性优化
- 优先用 PDO 读取(周期可达 1ms 级),SDO 仅用于非实时参数;
- 减少 MQTT 发布频率(如 10ms / 次),避免网络拥堵;
- 选择支持硬件时间戳的网卡,编译 openPOWERLINK 时启用
-DOPLK_USE_HW_TIMESTAMP=ON。
六、Windows 系统适配说明
- 编译依赖:安装 MinGW-w64、WinPcap(Powerlink 依赖)、CMake;
- 编译 openPOWERLINK:将
OPLK_TARGET_PLATFORM改为windows,OPLK_NETIF改为winpcap; - 编译 Paho MQTT:用 CMake 生成 MinGW Makefiles,编译后安装;
- 网卡名称:Windows 下
NET_IF_NAME需填网卡的 NPF GUID(通过 Wireshark 查询); - 编译命令:调整头文件 / 库文件路径为 Windows 格式(如
-I"C:\openpowerlink-code\include")。
总结
通过「openPOWERLINK C 协议栈 + Paho MQTT C 库」,可实现 PLC 数据的实时读取与 MQTT 发布,适用于工业物联网(IIoT)场景。核心是:正确配置 Powerlink 主从站参数、确保索引 / 子索引匹配、处理好数据解析与 MQTT 连接稳定性。
若需对接云端 IoT 平台(如阿里云、华为云),只需修改 MQTT Broker 地址和认证信息;若需读取多个变量,可扩展 PDO 映射表和数据解析逻辑。