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 拷贝路径是这一族:
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、主站编号、从站索引与本方向字节数直接构造。
不想建 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::nulloptbool— 写是否成功
说明:
- 读方向按
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
}