跳到主要内容

PDO 输入输出

通过 darra::SlavePDO 命名空间的自由函数访问过程映像;元素视图由 darra::PDOArrayInstance 提供,按需自行构造。​

PDO 就是过程映像里的一段数据,主站每周期自动收发。映射成结构体后,读写成员就是直接读写过程映像本身 —— 0 拷贝:不用拷贝、不用刷新,读到的永远是当前周期,写下的下个周期发出。

使用案例​

日常使用​

① 映射到结构体(0 拷贝,持续用):进 OP 后映射一次,拿到钉在过程映像上的引用,之后当变量读写。

#include "ethercat.hpp"
#include <cstdio>

#pragma pack(push, 1) // 必须: 结构体逐字节对齐 PDO 映射
struct InPdo { uint16_t statusWord; int32_t actualPosition; };
struct OutPdo { uint16_t controlWord; int32_t targetPosition; };
#pragma pack(pop)

int main() {
dll_t dll;
darra::EtherCATMaster master(dll);
master.SetENI("C:\\EtherCAT\\MyProject.deni"); // DENI 一行加载
if (!master.Build() || !master.SetState(darra::EcState::OP)) return 1;

const uint16_t mi = master.MasterNumber();
const uint16_t si = 1; // 从站索引, 1-based

const InPdo& in = darra::SlavePDO::InputsMapping<InPdo>(dll, mi, si);
OutPdo& out = darra::SlavePDO::OutputsMapping<OutPdo>(dll, mi, si);

out.controlWord = 0x000F; // 写: 下个周期发出
out.targetPosition = 10000;
std::printf("status=0x%04X pos=%d\n",
static_cast<unsigned>(in.statusWord), in.actualPosition);
}

② 单次读取(一次性读):不要结构体,只取一份当前值。

// 单点: 按字节偏移取一个类型化值, 失败返回 std::nullopt
if (auto sw = darra::SlavePDO::ReadUInt16(dll, mi, si, 0))
std::printf("status=0x%04X\n", static_cast<unsigned>(*sw));
if (auto name = darra::SlavePDO::ReadString(dll, mi, si, 6, 16))
std::printf("name=%s\n", name->c_str());

// 整帧: 一份完整输入帧快照, 同一 generation, 拷出来不会跟着总线变
std::vector<uint8_t> frame;
if (darra::SlavePDO::CopyInputFrame(dll, mi, si, frame) && frame.size() >= 2)
std::printf("快照 %zu 字节, status=0x%04X\n", frame.size(),
static_cast<unsigned>(frame[0] | (frame[1] << 8)));

周期变化​

映射取一次就够,同一份映射放进你自己的周期循环直接用:

while (running) {
out.controlWord = (in.statusWord & 0x0004) ? static_cast<uint16_t>(0x000F)
: static_cast<uint16_t>(0x0006);
out.targetPosition = in.actualPosition;
WaitNextTick(); // 你自己的节奏, 例如 1 ms
}

主站每周期搬运过程数据:循环里读到的永远是当前周期、写下的下个周期发出,不需要任何额外调用,也不要每拍重新映射(主站重建 / 重新进 OP 后引用才作废,重进 OP 时重取一次即可)。

新鲜度模型​

读接口分两类,混用前必须知道差别:

  • live 引用:InputsMapping<T>() / OutputsMapping<T>() / InputsSliceMapping<T>() 返回的引用直接指向过程映像,没有"读一次"的动作。
  • 快照:元素访问器、Read* 系列每次都重新取一份当前方向的完整 PDO 帧再按偏移解析。
    • Write* 里的局部写系列每次都取一份完整帧、改完再版本化提交:
      • WriteInt8 … WriteDouble、WriteString、元素访问器 setter、PatchOutputFrame() / MutateOutputFrame()
    • WriteOutputFrame() 则是无条件整帧替换。

因此元素访问器与 Read* 是"一次调用 = 一份新的完整快照",同一个表达式里的两次调用可能落在两个不同的总线周期上:

// 两次调用 = 两份独立快照, 不保证同拍
int32_t delta = darra::SlavePDO::ReadInt32(dll, mi, si, 6).value_or(0)
- darra::SlavePDO::ReadInt32(dll, mi, si, 2).value_or(0);

需要多字段一致时,任选一条:

① 映射引用按值拷一份(0 拷贝映射,拷出来的值不再跟着总线变):

const InPdo& live = darra::SlavePDO::InputsMapping<InPdo>(dll, mi, si);
InPdo snapshot = live; // 各字段来自同一次拷贝
int32_t pos = snapshot.actualPosition;

② 整帧快照:一次取一份完整帧,再按偏移自行解析。

std::vector<uint8_t> frame;
if (darra::SlavePDO::CopyInputFrame(dll, mi, si, frame) && frame.size() >= 2)
std::printf("status=0x%04X\n", // 帧内字段来自同一份快照
static_cast<unsigned>(frame[0] | (frame[1] << 8)));

③ 自备缓冲整帧读:ReadInputDirect() 一次填满调用方缓冲,缓冲不足时不复制半帧。

uint8_t buf[64];
int n = darra::SlavePDO::ReadInputDirect(dll, mi, si, buf, sizeof(buf)); // 0 = 失败
std::printf("读到 %d 字节\n", n);
0 拷贝路径

0 拷贝路径是这一族:

  • InputsMapping<T>()、OutputsMapping<T>()、InputsSliceMapping<T>()、OutputsSliceMapping<T>()
  • InputsSliceMapping(offset, size) / OutputsSliceMapping(offset, size)(返回 live 字节切片)
  • InputDataPointer()、OutputDataPointer()

元素访问器与 Read* 每次都取整帧快照。

Write* 里的局部写走版本化提交、WriteOutputFrame() 是无条件整帧替换 —— 都不是 0 拷贝路径。

结构体映射(零拷贝)​

映射入口把 live 过程映像 overlay 成引用(不是快照):映射一次,后面一直读同一个引用。

dll 取 master.Dll(),mi 取 master.MasterNumber(),si 为 1-based 从站索引。

失败 fail-closed 抛 std::runtime_error:

  • 映像不可用抛 "PDO 输入映像不可用" / "PDO 输出映像不可用"。
  • sizeof(T) 超尺寸抛 "结构体大于输入 PDO" / "结构体大于输出 PDO"。
结构体契约
  • 必须 #pragma pack(push, 1)(或 #pragma pack(1)),字段顺序与 PDO 映射逐字节一致;漏写 pack 一样能编译,只是带对齐填充、字段全部错位。
  • sizeof(T) 不得大于该方向字节数(slave.Ibytes() / slave.Obytes()),否则抛错;不要求完全相等,小于合法。
  • T 必须 trivially copyable 且 standard layout,这是编译期 static_assert;含虚函数 / 引用成员 / 非平凡拷贝的类型 overlay 到过程映像必错。
  • 输入侧只读 —— 写它静默无效;要写过程数据请用输出方向的映射。
  • 映射在主站当前一代过程映像内一直有效;主站重建 / 重新进 OP 后必须重新映射。

InputsMapping<T>()​

namespace darra::SlavePDO {

template<typename T>
const T& InputsMapping(dll_t& dll, uint16_t mi, uint16_t si);

}

把输入过程映像(TxPDO)映射成只读结构体引用,映射一次后读字段即读当前周期值。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)

返回值:

  • const T& — 钉在过程映像上的只读引用

示例:

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

const InPdo& in = darra::SlavePDO::InputsMapping<InPdo>(dll, mi, 1);
uint16_t sw = in.statusWord; // 当前周期值

OutputsMapping<T>()​

namespace darra::SlavePDO {

template<typename T>
T& OutputsMapping(dll_t& dll, uint16_t mi, uint16_t si);

}

把输出过程映像(RxPDO)映射成结构体引用,改字段即写回过程映像。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)

返回值:

  • T& — 可写引用,钉在过程映像上;改字段后下个周期发往从站

示例:

#pragma pack(push, 1)
struct OutPdo { uint16_t controlWord; int32_t targetPosition; };
#pragma pack(pop)

OutPdo& out = darra::SlavePDO::OutputsMapping<OutPdo>(dll, mi, 1);
out.controlWord = 0x000F; // 下个周期发出

InputsSliceMapping<T>() / OutputsSliceMapping<T>()​

namespace darra::SlavePDO {

template<typename T>
const T& InputsSliceMapping(dll_t& dll, uint16_t mi, uint16_t si, int offset);

template<typename T>
T& OutputsSliceMapping(dll_t& dll, uint16_t mi, uint16_t si, int offset);

}

从过程映像的指定字节偏移处 overlay 结构体引用,供模块化设备按模块切段使用。改输出方向的字段即写回过程映像。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • offset (int) — 相对本从站该方向过程映像起始的字节偏移

返回值:

  • const T& / T& — 钉在该偏移处的引用(0 拷贝)

说明: 映像不可用抛 std::runtime_error;offset < 0 或 offset + sizeof(T) 越界抛 std::out_of_range。

示例:

#pragma pack(push, 1)
struct Module1In { uint16_t status; };
struct Module2In { int32_t position; };
#pragma pack(pop)

// 模块 1 从偏移 0 起, 模块 2 从偏移 8 起
const Module1In& m1 = darra::SlavePDO::InputsSliceMapping<Module1In>(dll, mi, 1, 0);
const Module2In& m2 = darra::SlavePDO::InputsSliceMapping<Module2In>(dll, mi, 1, 8);

InputsSliceMapping(int offset, int size) / OutputsSliceMapping(int offset, int size)​

namespace darra::SlavePDO {

std::pair<const uint8_t*, int> InputsSliceMapping(dll_t& dll, uint16_t mi,
uint16_t si, int offset, int size);

std::pair<uint8_t*, int> OutputsSliceMapping(dll_t& dll, uint16_t mi,
uint16_t si, int offset, int size);

}

按字节取 live 过程映像切片,用于结构体放不下的变长字段。

返回 {nullptr, 0} 表示越界(offset < 0、size <= 0、offset + size 超出本方向字节数)或映像不可用,绝不返回映像外指针。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • offset (int) — 字节偏移
  • size (int) — 切片字节数

返回值:

  • std::pair<const uint8_t*, int> / std::pair<uint8_t*, int> — (指针, 字节数);输入方向只读,输出方向改写即写回过程映像

示例:

auto outSlice = darra::SlavePDO::OutputsSliceMapping(dll, mi, 1, 4, 8);
if (outSlice.first)
outSlice.first[0] = 0x01; // 写回过程映像, 下个周期发出

InputDataPointer() / OutputDataPointer()​

namespace darra::SlavePDO {

void* InputDataPointer(dll_t& dll, uint16_t mi, uint16_t si);
void* OutputDataPointer(dll_t& dll, uint16_t mi, uint16_t si);

}

取 live 过程映像的裸指针(TxPDO / RxPDO)。失败返回 nullptr;指针在主站运行期间有效。输入方向只读 —— 写它静默无效。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)

返回值:

  • void* — 过程映像起点指针;nullptr = 取不到

示例:

auto* inBytes  = static_cast<const uint8_t*>(darra::SlavePDO::InputDataPointer(dll, mi, 1));
auto* outBytes = static_cast<uint8_t*>(darra::SlavePDO::OutputDataPointer(dll, mi, 1));

InputDataSize() / OutputDataSize()​

namespace darra::SlavePDO {

int InputDataSize(dll_t& dll, uint16_t mi, uint16_t si);
int OutputDataSize(dll_t& dll, uint16_t mi, uint16_t si);

}

本方向过程映像的权威帧长(0 缓冲 size probe 查询,不复制数据),用于映射前核对结构体大小、以及给元素视图确定长度。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)

返回值:

  • int — 字节数;探针失败或未进 OP 时为 0

示例:

const int inLen = darra::SlavePDO::InputDataSize(dll, mi, 1);
if (inLen < static_cast<int>(sizeof(InPdo)))
std::printf("结构体大于输入 PDO\n");

直接访问(元素视图)​

不映射结构体也能按字节偏移读写单个元素。数组视图是 darra::PDOArrayInstance,由调用方用 dll_t、主站编号、从站索引与本方向字节数直接构造。

另一条等价路线(C 元素函数)

不想建 darra::PDOArrayInstance,也可以用随包的 include/slave/pdo.h 那套 C 元素函数, 在 C++ 里照样能编:PDOArrayFromFrame() 建视图 → PDOArrayItem() 按偏移取元素 → PDOItemReadInt16() / PDOItemWriteInt16() 之类读写值。

std::vector<uint8_t> frame;
darra::SlavePDO::CopyInputFrame(dll, mi, si, frame); // 一拍输入快照

ec_pdo_array_t arr;
PDOArrayFromFrame(frame.data(), (int32_t)frame.size(), TRUE, &arr); // TRUE = 输入侧

ec_pdo_data_item_t it;
int16_t iw = 0;
if (PDOArrayItem(&arr, 2, &it)) PDOItemReadInt16(&it, &iw); // 偏移 2 起 2 字节

函数声明与尺寸约定见随包的 include/slave/pdo.h。

构造方式

元素视图不挂在从站对象上,每个方向各建一个 darra::PDOArrayInstance:长度取该方向的 darra::SlavePDO::InputDataSize(dll, mi, si) / darra::SlavePDO::OutputDataSize(dll, mi, si),is_input 取 true / false 决定方向。

darra::ethercat::SlavePdoAccess 是本页这族自由函数外面套的旧封装(Inputs() / Outputs() / SetOutputs() / InputArray() / OutputArray()),只为兼容既有代码保留,新代码不需要它;其 WriteOutputDataDirect() 已标记废弃且恒返回 0,永久 fail-closed。

PDOArrayInstance​

namespace darra {

class PDOArrayInstance {
public:
PDOArrayInstance(dll_t& dll, uint16_t mi, uint16_t si,
int length, bool is_input);

PDODataItem operator[](int index) const; // 按字节偏移取元素视图
int Length() const; // 本方向过程映像字节数
};

}

按字节偏移索引过程数据元素:index 相对本方向过程映像起点,越界抛 std::out_of_range("索引超出范围")。元素按偏移封装成 PDODataItem,按值返回、不产生堆分配。

另一个构造重载 PDOArrayInstance(uint8_t* data_ptr, int length, bool is_input) 接受调用方自己的缓冲基址,元素直接对该内存按偏移读写,不走快照与版本化提交。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • length (int) — 本方向过程映像字节数,来自 InputDataSize() / OutputDataSize()
  • is_input (bool) — true 为输入方向(TxPDO,只读),false 为输出方向(RxPDO)

相关结构:

class PDODataItem {                        // 元素是视图不是拷贝: 只存基址/索引/偏移/方向
public:
// 字节
uint8_t Content() const; void Content(uint8_t value);

// 定宽标量: 取值用无参重载, 写值用同名单参重载, 多字节一律小端 (本 SDK 不做任何字节颠倒)
int8_t AsInt8() const; void AsInt8(int8_t value);
uint8_t AsUInt8() const; void AsUInt8(uint8_t value); // 与 Content 同义同字节
int16_t AsInt16() const; void AsInt16(int16_t value);
uint16_t AsUInt16() const; void AsUInt16(uint16_t value);
int32_t AsInt32() const; void AsInt32(int32_t value);
uint32_t AsUInt32() const; void AsUInt32(uint32_t value);
int64_t AsInt64() const; void AsInt64(int64_t value);
uint64_t AsUInt64() const; void AsUInt64(uint64_t value);
float AsFloat() const; void AsFloat(float value);
double AsDouble() const; void AsDouble(double value);

// 布尔: bit0 语义
bool AsBool() const; void AsBool(bool value); // 写: 整字节 0 / 1

// 位: 索引 0-7, 0 = LSB
bool GetBit(int bitIndex) const;
void SetBit(int bitIndex, bool value); // 同字节其余位不动

// 按映射类型
PDOValue AsValue(EcDataType type) const;
void WriteValue(EcDataType type, const PDOValue& value);

// 宽类型出口 (位串 / 变长类型)
BaseData AsBaseData(EcDataType type) const;
bool WriteFromBaseData(const BaseData& data);

operator uint8_t() const; // 隐式转换, 等同 Content()
};

示例:

darra::PDOArrayInstance in (dll, mi, si,                 // 输入元素视图 (TxPDO)
darra::SlavePDO::InputDataSize(dll, mi, si), true);
darra::PDOArrayInstance out(dll, mi, si, // 输出元素视图 (RxPDO)
darra::SlavePDO::OutputDataSize(dll, mi, si), false);

if (in.Length() >= 4) {
int32_t pos = in[2].AsInt32(); // 读: 偏移 2 起 4 字节
std::printf("pos=%d\n", pos);
}
out[0].AsInt16(123); // 写: 版本化局部提交, 下个周期发出
uint8_t raw = in[3]; // 隐式转换: 等同 in[3].Content()

AsBool()​

bool AsBool() const;
void AsBool(bool value);

按整字节读写真假值,bit0 语义:只有最低位为 1 才是 true。

返回值:

  • bool — 整字节的最低位的值(0 → false,1 → true)

说明:

  • 非规范字节 0x02 与 0x80 都读 false —— 不是"非 0 即真"。
  • 本 SDK 只提供这一个布尔口径,要按整字节 0 / 非 0 判真假请读 Content() 自行比较。
  • 写方向把整字节写成 1 或 0(不是改单个位);要按位读写请用 GetBit() / SetBit()。

示例:

bool flag = in[3].AsBool();        // 只有最低位为 1 才是 true
uint8_t b = in[3].Content(); // 要按整字节 0 / 非 0 判真假时读原始字节
out[3].AsBool(true); // 写: 整个字节 = 0x01

GetBit() / SetBit()​

bool GetBit(int bitIndex) const;
void SetBit(int bitIndex, bool value);

读写元素所在的那一个字节里的一位。

参数:

  • bitIndex (int) — 位索引,取值 0-7,0 是最低位(LSB)
  • value (bool) — 要写入的位值

返回值:

  • bool — 该位的值

说明:

  • 位索引只用元素偏移处的那一个字节,与元素宽度无关:
    • AsInt16 / AsInt32 元素上取 0-7 仍然只访问起始字节,不会跨到后续字节。
  • 位索引不在 0-7 抛 std::out_of_range("位索引必须在 0-7 之间")。
  • SetBit 在输入方向抛 std::runtime_error("不能写入输入 PDO");写入过程中同字节其余位不动,提交失败抛 std::runtime_error。
  • 按字节偏移(而不是元素偏移)的等价入口是:
    • darra::SlavePDO::GetBit(dll, mi, si, byteOffset, bitOffset)
    • SetBit(dll, mi, si, byteOffset, bitOffset, value)

示例:

bool b0 = in[3].GetBit(0);         // 偏移 3 那个字节的 bit0
out[3].SetBit(0, true); // 只置 bit0, 同字节其余位不动

AsValue() / WriteValue()​

PDOValue AsValue(EcDataType type) const;
void WriteValue(EcDataType type, const PDOValue& value);

按 EtherCAT 数据类型读写标量:读按 type 从当前位置取值,写按 type 把值换算后落到当前位置。

参数:

  • type (EcDataType) — 该位置的映射类型(来自 ESI/ENI 的 PDO 映射条目)
  • value (const PDOValue&) — 要写入的值,按 type 换算

返回值:

  • PDOValue — 与 type 对应的标量

说明:

  • 白名单恰好 11 个定宽类型:BOOLEAN、INTEGER8/16/32/64、UNSIGNED8/16/32/64、REAL32、REAL64。
  • 位串(BIT1..BIT8)、INTEGER24/40/48/56、字符串、DOMAIN 一律抛 std::out_of_range —— 不猜宽度、不静默按 1 字节读。
  • WriteValue 的值按目标类型换算(浮点→整数是就近取整),超出目标类型范围抛 std::out_of_range。
  • BOOLEAN 收 bool 原样,其余按 0 / 非 0 判真假,再落 AsBool 的整字节 bit0 语义。

相关结构:

using PDOValue = std::variant<bool, int8_t, uint8_t, int16_t, uint16_t,
int32_t, uint32_t, int64_t, uint64_t,
float, double>;

示例:

darra::PDOValue v = in[2].AsValue(darra::EcDataType::INTEGER32);
if (auto p = std::get_if<int32_t>(&v))
std::printf("pos=%d\n", *p);

out[0].WriteValue(darra::EcDataType::INTEGER16, static_cast<int16_t>(-100));

AsBaseData() / WriteFromBaseData()​

BaseData AsBaseData(EcDataType type) const;
bool WriteFromBaseData(const BaseData& data);

定宽 11 类型之外的位串 / 变长类型出口:按原始字节进出一个 BaseData 包装。

参数:

  • type (EcDataType) — 该位置的映射类型;字节宽度取自 native 数据类型表(ValueCodec::TypeBitSize 向上取整到字节),给不出宽度的类型按 1 字节处理
  • data (const BaseData&) — 要写回的整段原始字节

返回值:

  • BaseData — 按 type 宽度从当前位置拷出的原始字节包装
  • bool — 是否写成功;失败返回 false(不抛),输入方向固定返回 false

说明:

  • AsBaseData 越界抛 std::out_of_range。
  • 帧快照取不到抛 std::runtime_error("PDO 快照不可用")。
  • 数据类型表给不出合法宽度抛 std::invalid_argument。
  • WriteFromBaseData 走输出方向的版本化提交,任何失败都以 false 返回。

示例:

darra::ethercat::BaseData wide = in[0].AsBaseData(darra::EcDataType::BIT3);   // 位串
bool ok = out[0].WriteFromBaseData(wide); // 失败返回 false

整帧快照与类型化单点​

CopyInputFrame() / CopyOutputFrame()​

namespace darra::SlavePDO {

bool CopyInputFrame(dll_t& dll, uint16_t mi, uint16_t si,
std::vector<uint8_t>& frame);
bool CopyOutputFrame(dll_t& dll, uint16_t mi, uint16_t si,
std::vector<uint8_t>& frame);

}

复制当前 generation 的一份完整 PDO 帧到调用方容器,是本 SDK 的整帧快照入口。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • frame (std::vector<uint8_t>&) — 输出参数;失败时被清空,绝不留下旧帧或半帧

返回值:

  • bool — 是否成功;帧长超过 64 KiB 上限或 Core 返回状态自相矛盾时为 false

示例:

std::vector<uint8_t> frame;
if (darra::SlavePDO::CopyInputFrame(dll, mi, 1, frame))
std::printf("%zu 字节\n", frame.size());

ReadInputDirect()​

namespace darra::SlavePDO {

int ReadInputDirect(dll_t& dll, uint16_t mi, uint16_t si,
uint8_t* buffer, int bufferSize);

}

一次把完整输入帧读进调用方缓冲;缓冲不足时不复制半帧。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • buffer (uint8_t*) — 目标缓冲
  • bufferSize (int) — 缓冲容量(字节)

返回值:

  • int — 实际读到的字节数;任何失败(未进 OP、缓冲不足、指针为空)返回 0

示例:

uint8_t buf[64];
int n = darra::SlavePDO::ReadInputDirect(dll, mi, 1, buf, sizeof(buf));

WriteOutputFrame() / PatchOutputFrame() / MutateOutputFrame()​

namespace darra::SlavePDO {

bool WriteOutputFrame(dll_t& dll, uint16_t mi, uint16_t si,
const uint8_t* data, uint32_t size);

bool PatchOutputFrame(dll_t& dll, uint16_t mi, uint16_t si,
size_t offset, const void* data, size_t size);

template<typename Mutator>
bool MutateOutputFrame(dll_t& dll, uint16_t mi, uint16_t si, Mutator&& mutator);

}
  • WriteOutputFrame 无条件替换完整 RxPDO,只供单一实时 owner 使用,不得用于局部写。
  • PatchOutputFrame / MutateOutputFrame 走版本化局部提交:取快照 → 改字节 → 按令牌提交整帧。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • data (const uint8_t* / const void*) — 要写入的字节
  • size (uint32_t / size_t) — 字节数
  • offset (size_t) — 相对输出过程映像起点的字节偏移
  • mutator (Mutator&&) — 接收待提交帧的 std::vector<uint8_t>&,返回 false 表示放弃本次提交;必须可重复执行且不产生外部副作用

返回值:

  • bool — 是否成功提交,失败返回 false

说明:

  • 局部提交在冲突 / 忙时从一次新的取快照重来,总共最多三轮,且共享同一个 5 ms 预算。
  • 预算耗尽或任何一次提交失败都返回 false。
  • WriteOutputFrame 只能由该从站输出的唯一实时 owner 调用,不要在整体替换之外拿它实现局部语义。
    • 局部写请走 PatchOutputFrame / MutateOutputFrame 或元素访问器。

示例:

const uint16_t cw = 0x000F;
darra::SlavePDO::PatchOutputFrame(dll, mi, 1, 0, &cw, sizeof(cw)); // 偏移 0 起 2 字节

darra::SlavePDO::MutateOutputFrame(dll, mi, 1, [](std::vector<uint8_t>& frame) {
if (frame.size() < 2) return false;
frame[1] |= 0x01;
return true;
});

ReadInputValue<T>() / ReadOutputValue<T>()​

namespace darra::SlavePDO {

template<typename T>
std::optional<T> ReadInputValue(dll_t& dll, uint16_t mi, uint16_t si, int offset);

template<typename T>
std::optional<T> ReadOutputValue(dll_t& dll, uint16_t mi, uint16_t si, int offset);

}

从一份完整快照的字节偏移 offset 处读一个 T,小端解析。帧拿不到或越界一律返回 std::nullopt —— 不抛、不留半读。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • offset (int) — 相对本从站该方向过程数据区起点的字节偏移

返回值:

  • std::optional<T> — 读到的值;越界或失败为 std::nullopt

示例:

if (auto v = darra::SlavePDO::ReadInputValue<uint32_t>(dll, mi, 1, 4))
std::printf("0x%08X\n", *v);

ReadInt8() / ReadUInt16() 等定宽读族​

namespace darra::SlavePDO {

std::optional<int8_t> ReadInt8 (dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<int16_t> ReadInt16 (dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<int32_t> ReadInt32 (dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<int64_t> ReadInt64 (dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<uint8_t> ReadUInt8 (dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<uint16_t> ReadUInt16(dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<uint32_t> ReadUInt32(dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<uint64_t> ReadUInt64(dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<float> ReadFloat (dll_t& dll, uint16_t mi, uint16_t si, int offset);
std::optional<double> ReadDouble(dll_t& dll, uint16_t mi, uint16_t si, int offset);

}

ReadInputValue<T>() 的定宽包装,按类型名取一份完整输入快照再解析。参数与返回值同上,失败返回 std::nullopt。

示例:

int32_t pos = darra::SlavePDO::ReadInt32(dll, mi, 1, 2).value_or(0);     // 偏移 2 起 4 字节
uint16_t sw = darra::SlavePDO::ReadUInt16(dll, mi, 1, 0).value_or(0); // StatusWord

WriteInt8() / WriteUInt16() 等定宽写族​

namespace darra::SlavePDO {

bool WriteInt8 (dll_t& dll, uint16_t mi, uint16_t si, int offset, int8_t value);
bool WriteInt16 (dll_t& dll, uint16_t mi, uint16_t si, int offset, int16_t value);
bool WriteInt32 (dll_t& dll, uint16_t mi, uint16_t si, int offset, int32_t value);
bool WriteInt64 (dll_t& dll, uint16_t mi, uint16_t si, int offset, int64_t value);
bool WriteUInt8 (dll_t& dll, uint16_t mi, uint16_t si, int offset, uint8_t value);
bool WriteUInt16(dll_t& dll, uint16_t mi, uint16_t si, int offset, uint16_t value);
bool WriteUInt32(dll_t& dll, uint16_t mi, uint16_t si, int offset, uint32_t value);
bool WriteUInt64(dll_t& dll, uint16_t mi, uint16_t si, int offset, uint64_t value);
bool WriteFloat (dll_t& dll, uint16_t mi, uint16_t si, int offset, float value);
bool WriteDouble(dll_t& dll, uint16_t mi, uint16_t si, int offset, double value);

}

WriteOutputValue<T>() 的定宽包装,走版本化局部提交,语义与 PatchOutputFrame() 一致。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • offset (int) — 字节偏移;为负直接失败
  • value — 要写入的值

返回值:

  • bool — 是否成功提交;offset 非法或整帧提交失败返回 false

示例:

darra::SlavePDO::WriteInt32(dll, mi, 1, 2, 10000);      // 局部写, 下个周期发出
darra::SlavePDO::WriteUInt16(dll, mi, 1, 0, 0x000F);

ReadString() / WriteString()​

namespace darra::SlavePDO {

std::optional<std::string> ReadString(dll_t& dll, uint16_t mi, uint16_t si,
int offset, int maxLen);

bool WriteString(dll_t& dll, uint16_t mi, uint16_t si, int offset,
const std::string& value, int maxLen);

}

按字节偏移读写定长字符串字段;读去掉末尾的空字符,写不足部分填零。

参数:

  • dll (dll_t&) — DLL 上下文
  • mi (uint16_t) — 主站编号
  • si (uint16_t) — 从站索引(1-based)
  • offset (int) — 字节偏移
  • maxLen (int) — 字段字节长度
  • value (const std::string&) — 要写入的内容

返回值:

  • std::optional<std::string> — 读到的字符串;越界或快照不可用为 std::nullopt
  • bool — 写是否成功

说明:

  • 读方向按 offset 与 maxLen 定位字段,offset < 0、maxLen < 0 或字段超出帧长时返回 std::nullopt。
  • 返回内容去掉末尾空字符,字段内没有空字符时保留整个 maxLen 长度。
  • 写方向超出 maxLen 的部分被截断,不足部分填零。

示例:

if (auto name = darra::SlavePDO::ReadString(dll, mi, 1, 6, 16))
std::printf("name=%s\n", name->c_str());

darra::SlavePDO::WriteString(dll, mi, 1, 6, "Axis-01", 16);

完整示例​

#include "ethercat.hpp"

#include <cstdio>
#include <vector>

#pragma pack(push, 1)
struct InPdo { uint16_t statusWord; int32_t actualPosition; };
struct OutPdo { uint16_t controlWord; int32_t targetPosition; };
#pragma pack(pop)

int main() {
dll_t dll;
darra::EtherCATMaster master(dll);

master.SetENI("C:\\EtherCAT\\MyProject.deni"); // DENI 一行加载
if (!master.Build()) return 1;
if (!master.SetState(darra::EcState::OP)) return 1; // 进 OP

const uint16_t mi = master.MasterNumber();
const uint16_t si = 1; // 从站索引, 1-based

// 结构体映射: 一次映射, 之后读写成员即读写过程映像 (0 拷贝)
const InPdo& in = darra::SlavePDO::InputsMapping<InPdo>(dll, mi, si);
OutPdo& out = darra::SlavePDO::OutputsMapping<OutPdo>(dll, mi, si);

out.controlWord = 0x000F; // 写: 下个周期发出
out.targetPosition = in.actualPosition + 1000; // 读: 当前周期

// 元素视图: 按字节偏移读写单个元素
darra::PDOArrayInstance inElem(dll, mi, si,
darra::SlavePDO::InputDataSize(dll, mi, si), true);
darra::PDOArrayInstance outElem(dll, mi, si,
darra::SlavePDO::OutputDataSize(dll, mi, si), false);

if (inElem.Length() >= 6)
std::printf("pos=%d\n", inElem[2].AsInt32()); // 一份新的完整输入快照
if (outElem.Length() > 0)
outElem[0].SetBit(0, true); // 只置 bit0, 同字节其余位不动

// 类型化单点: 按字节偏移取一个值
int32_t velocity = darra::SlavePDO::ReadInt32(dll, mi, si, 6).value_or(0);

// 整帧快照: 一份完整帧, 同一 generation, 拷出来不会跟着总线变
std::vector<uint8_t> frame;
if (darra::SlavePDO::CopyInputFrame(dll, mi, si, frame) && frame.size() >= 2)
std::printf("快照 %zu 字节, status=0x%04X\n", frame.size(),
static_cast<unsigned>(frame[0] | (frame[1] << 8)));

std::printf("status=0x%04X pos=%d vel=%d\n",
static_cast<unsigned>(in.statusWord), in.actualPosition, velocity);

std::getchar();
return 0; // master 析构: 自动 Stop + Dispose
}