PDO 输入输出
通过 Slave 类的零拷贝方法直接访问 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_t | uint8_t |
| AsInt16() / AsInt16(v) | int16_t | int16_t |
| AsUInt16() / AsUInt16(v) | uint16_t | uint16_t |
| AsInt32() / AsInt32(v) | int32_t | int32_t |
| AsUInt32() / AsUInt32(v) | uint32_t | uint32_t |
| AsFloat() / AsFloat(v) | float | float |
| AsDouble() / AsDouble(v) | double | double |
| GetBit(idx) / SetBit(idx, v) | bool | bool |
输入 PDO 为只读 -- 对输入数据调用写入方法会抛出异常。
PDODataItem 支持的数据类型:
| 数据类型 | 大小 | 读取方法 | 写入方法 |
|---|---|---|---|
| uint8_t | 1B | Content() | Content(v) |
| int16_t | 2B | AsInt16() | AsInt16(v) |
| uint16_t | 2B | AsUInt16() | AsUInt16(v) |
| int32_t | 4B | AsInt32() | AsInt32(v) |
| uint32_t | 4B | AsUInt32() | AsUInt32(v) |
| int64_t | 8B | AsInt64() | AsInt64(v) |
| uint64_t | 8B | AsUInt64() | AsUInt64(v) |
| float | 4B | AsFloat() | AsFloat(v) |
| double | 8B | AsDouble() | AsDouble(v) |
| bool (bit) | 1bit | GetBit(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;
}