跳到主要内容

PDO 输入输出

通过 Slave 类的零拷贝方法直接访问 PDO 数据。

PDO 周期与邮箱的关系

邮箱通信自动附带在 PDO 周期中,不占用额外帧。邮箱响应延迟约 2 个 PDO 周期。

推荐

高性能场景使用 零拷贝指针映射InputDataPointer()/OutputDataPointer()),快速原型使用 ReadInputDirect/WriteOutputDirect

零拷贝指针映射

通过 Slave 类获取 IOmap 中的原始指针:

#pragma pack(push, 1)
struct ServoInput {
uint16_t statusWord;
int32_t actualPosition;
int32_t actualVelocity;
};

struct ServoOutput {
uint16_t controlWord;
int32_t targetPosition;
};
#pragma pack(pop)

auto& slave = master.GetSlave(1);
auto* input = reinterpret_cast<ServoInput*>(slave.InputDataPointer());
auto* output = reinterpret_cast<ServoOutput*>(slave.OutputDataPointer());

if (input && output) {
printf("位置: %d\n", input->actualPosition);
output->controlWord = 0x000F;
output->targetPosition = 100000;
}

直接读写

ReadInputDirect()

int ReadInputDirect(uint8_t* buffer, int bufferSize) const;

读取输入数据到缓冲区。返回实际读取的字节数。

WriteOutputDirect()

int WriteOutputDirect(const uint8_t* buffer, int bufferSize) const;

写入输出数据从缓冲区。返回实际写入的字节数。

示例:

auto& slave = master.GetSlave(1);

// 读取输入
uint8_t in_buf[64];
int in_bytes = slave.ReadInputDirect(in_buf, sizeof(in_buf));
printf("读取 %d 字节输入数据\n", in_bytes);

// 写入输出
uint8_t out_buf[] = {0x0F, 0x00, 0xA0, 0x86, 0x01, 0x00};
int out_bytes = slave.WriteOutputDirect(out_buf, sizeof(out_buf));
printf("写入 %d 字节输出数据\n", out_bytes);

字节数组访问

Inputs() / Outputs()

std::vector<uint8_t> Inputs() const;
std::vector<uint8_t> Outputs() const;
void SetOutputs(const std::vector<uint8_t>& data);

直接读写 PDO 字节数组。每次访问从 IOmap 读取最新数据。写入时数据长度必须与 Obytes() 匹配。

示例:

auto& slave = master.GetSlave(1);

// 读取输入字节数组
auto inputs = slave.Inputs();
if (inputs.size() >= 2) {
uint16_t sw = inputs[0] | (inputs[1] << 8);
printf("StatusWord: 0x%04X\n", sw);
}

// 写入输出字节数组
std::vector<uint8_t> outputs = {0x0F, 0x00, 0x00, 0x00, 0x00, 0x00};
slave.SetOutputs(outputs);

IO 大小属性

auto& slave = master.GetSlave(1);

// 输入/输出字节数
uint32_t obytes = slave.Obytes();
uint32_t ibytes = slave.Ibytes();
printf("输出 %u 字节, 输入 %u 字节\n", obytes, ibytes);

PDODataItem 类型化访问

PDODataItem 类提供对单个 PDO 数据项的类型化访问:

auto& slave = master.GetSlave(1);

// 通过 SlavePdoAccess 获取数组实例
darra::ethercat::SlavePdoAccess pdo(dll, master.MasterNumber(), 1);
auto inputArray = pdo.InputArray();
auto outputArray = pdo.OutputArray();

// 按字节索引访问
uint16_t sw = inputArray[0].AsUInt16(); // 读取状态字
int32_t pos = inputArray[2].AsInt32(); // 读取位置

// 写入输出
outputArray[0].AsUInt16(0x000F); // 写入控制字
outputArray[2].AsInt32(100000); // 写入目标位置

// 位操作
bool bit0 = inputArray[0].GetBit(0);
outputArray[0].SetBit(4, true);

// 浮点数
float temp = inputArray[6].AsFloat();
outputArray[6].AsFloat(25.5f);

PDODataItem 类型化方法:

方法读取类型写入类型
Content() / Content(v)uint8_tuint8_t
AsInt16() / AsInt16(v)int16_tint16_t
AsUInt16() / AsUInt16(v)uint16_tuint16_t
AsInt32() / AsInt32(v)int32_tint32_t
AsUInt32() / AsUInt32(v)uint32_tuint32_t
AsFloat() / AsFloat(v)floatfloat
AsDouble() / AsDouble(v)doubledouble
GetBit(idx) / SetBit(idx, v)boolbool
注意

输入 PDO 为只读 -- 对输入数据调用写入方法会抛出异常。

PDODataItem 支持的数据类型:

数据类型大小读取方法写入方法
uint8_t1BContent()Content(v)
int16_t2BAsInt16()AsInt16(v)
uint16_t2BAsUInt16()AsUInt16(v)
int32_t4BAsInt32()AsInt32(v)
uint32_t4BAsUInt32()AsUInt32(v)
int64_t8BAsInt64()AsInt64(v)
uint64_t8BAsUInt64()AsUInt64(v)
float4BAsFloat()AsFloat(v)
double8BAsDouble()AsDouble(v)
bool (bit)1bitGetBit(idx)SetBit(idx, v)

Span 零拷贝访问

darra::ethercat::SlavePdoAccess pdo(dll, master.MasterNumber(), 1);

// 获取输入/输出的原始指针和大小
auto [inPtr, inSize] = pdo.InputSpan();
auto [outPtr, outSize] = pdo.OutputSpan();

// 切片访问(偏移+大小)
auto [slicePtr, sliceSize] = pdo.InputsSliceMapping(2, 4); // 从偏移2读取4字节

切片映射(MDP 模块化设备)

InputsSliceMapping / OutputsSliceMapping 用于 MDP 模块化设备,每个模块在 IOmap 中有不同偏移:

#pragma pack(push, 1)
struct Module1Input { uint8_t di_data[2]; };
struct Module2Input { int16_t ai_ch0; int16_t ai_ch1; };
#pragma pack(pop)

darra::ethercat::SlavePdoAccess pdo(dll, master.MasterNumber(), 1);

// 模块 1 从偏移 0 开始
auto [mod1Ptr, mod1Size] = pdo.InputsSliceMapping(0, sizeof(Module1Input));
auto* mod1 = reinterpret_cast<Module1Input*>(mod1Ptr);

// 模块 2 从偏移 2 开始
auto [mod2Ptr, mod2Size] = pdo.InputsSliceMapping(2, sizeof(Module2Input));
auto* mod2 = reinterpret_cast<Module2Input*>(mod2Ptr);
if (mod2) {
printf("AI 通道 0: %d, 通道 1: %d\n", mod2->ai_ch0, mod2->ai_ch1);
}

结构体绑定

SlavePdoAccess::BindPdoStruct<T>() 可以直接将 PDO 数据区映射为用户结构体指针:

#pragma pack(push, 1)
struct MyInputPdo { uint16_t status; int32_t position; };
struct MyOutputPdo { uint16_t control; int32_t target; };
#pragma pack(pop)

darra::ethercat::SlavePdoAccess pdo(dll, master.MasterNumber(), 1);

// 绑定输入 PDO 到结构体
auto* in = pdo.BindPdoStruct<MyInputPdo>(true); // true=输入
if (in) printf("位置: %d\n", in->position);

// 绑定输出 PDO 到结构体
auto* out = pdo.BindPdoStruct<MyOutputPdo>(false); // false=输出
if (out) {
out->control = 0x000F;
out->target = 100000;
}
注意

绑定的结构体类型必须是 POD 类型(无虚函数、无构造函数),且大小不超过对应 PDO 数据区。

PDO 管理器 (主站级)

PDOManager 类提供主站级的批量 PDO 操作、监控统计、IOmap 锁控制和组分频管理。

darra::PDOManager pdo_mgr(dll, master.MasterNumber());

BatchRead() / BatchWrite()

uint32_t BatchRead(uint16_t* slaveIndices, uint32_t count,
void** buffers, uint32_t* bufferSizes, uint32_t* bytesRead);
uint32_t BatchWrite(uint16_t* slaveIndices, uint32_t count,
const void** buffers, uint32_t* bufferSizes);

一次系统调用对多个从站的 PDO 数据做批量读 / 写, 比逐个 ReadInputDirect 更高效。

返回值:

  • uint32_t — 实际成功的从站数

StartMonitoring() / StopMonitoring()

void StartMonitoring(int intervalMs = 100);
void StopMonitoring();

启停 PDO 周期监控 (内部统计抖动、平均周期、丢帧等)。

AvgCycleTimeNs()

uint64_t AvgCycleTimeNs() const;

最近窗口内 PDO 平均周期时间 (纳秒)。

ErrorCount()

uint32_t ErrorCount() const;

PDO 错误累计计数。

状态查询 (IsValid / Changed / ExpectedSize)

bool     IsValid() const;       // PDO 数据是否有效 (至少接收到一帧且 WKC 正确)
bool Changed() const; // 自上次调用以来 PDO 数据是否发生变化, 调用即清除标志
uint32_t ExpectedSize() const; // PDO 预期数据大小 (所有从站输入 + 输出总字节数)

GetPDOStats() / ResetPDOStats()

bool GetPDOStats(uint16_t slaveIndex, PDOPerformanceStats& out) const;
void ResetPDOStats(uint16_t slaveIndex);

获取 / 重置指定从站的 PDO 性能统计。

GetPDOMapping()

std::vector<PDOMappingEntry> GetPDOMapping(uint16_t slaveIndex, uint16_t pdoIndex) const;

读取指定从站某个 PDO 索引的映射条目列表。

IOmap 互斥保护

void LockIOmap();                       // 显式锁定 IOmap, 多线程访问 PDO 缓冲区时使用
void UnlockIOmap(); // 解锁 IOmap, 推荐用 RAII 包装
void SetMutexProtection(bool enable); // 启停 PDO 内部互斥保护; 高频小数据可关闭, 默认建议开启

GetGroupCycleDivider() / SetGroupCycleDivider()

uint8_t GetGroupCycleDivider(uint8_t group) const;
bool SetGroupCycleDivider(uint8_t group, uint8_t divider);

读取 / 设置指定组的 PDO 周期分频器, 等价于 master.Divider(group) / master.Divider(group, value)

PDOReadDirect() / PDOWriteDirect()

bool PDOReadDirect(uint16_t slaveIndex, uint16_t offset,
void* buffer, uint32_t size, uint32_t* bytesRead) const;
bool PDOWriteDirect(uint16_t slaveIndex, uint16_t offset,
const void* data, uint32_t size) const;

按字节偏移直接读写指定从站的 PDO 数据。offset 相对该从站输入 / 输出区起点。

GetPDOFrameLossStats() / ResetPDOFrameLossStats()

FrameLossStats GetPDOFrameLossStats(uint8_t group = 0) const;
void ResetPDOFrameLossStats(uint8_t group = 0);

按组获取 / 重置 PDO 丢帧统计。结构定义与主站诊断侧一致, 详见 主站诊断 - 通信与性能统计

示例:

darra::PDOManager pdo_mgr(dll, master.MasterNumber());
pdo_mgr.StartMonitoring(100);

// 周期内查统计
printf("平均周期=%llu ns, 错误数=%u, 数据有效=%d\n",
pdo_mgr.AvgCycleTimeNs(), pdo_mgr.ErrorCount(), pdo_mgr.IsValid());

// 批量读多个从站
uint16_t indices[] = {1, 2, 3};
void* bufs[3] = {buf1, buf2, buf3};
uint32_t sizes[3] = {sz1, sz2, sz3};
uint32_t reads[3] = {0};
pdo_mgr.BatchRead(indices, 3, bufs, sizes, reads);

完整示例

#include "ethercat.hpp"
using namespace darra;

#pragma pack(push, 1)
struct MyInput { uint16_t sw; int32_t pos; int32_t vel; };
struct MyOutput { uint16_t cw; int32_t tp; };
#pragma pack(pop)

int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();

auto& slave = master.GetSlave(1);

master.SetState(EcState::OP);
master.Start();

// 零拷贝指针映射
auto* in = reinterpret_cast<MyInput*>(slave.InputDataPointer());
auto* out = reinterpret_cast<MyOutput*>(slave.OutputDataPointer());

if (in && out) {
out->cw = 0x000F;
out->tp = in->pos + 1000;
}

// 直接读写
uint8_t in_buf[64];
int bytes = slave.ReadInputDirect(in_buf, sizeof(in_buf));

uint8_t out_buf[] = {0x0F, 0x00, 0, 0, 0, 0};
slave.WriteOutputDirect(out_buf, sizeof(out_buf));

getchar();
return 0;
}