跳到主要内容

PDO 输入输出

通过 GetIO 取过程映像指针,通过 PDOArrayFromFrame / PDOArrayItem 取过程数据元素视图。​

PDO 就是过程映像里的一段数据,内核每周期自动收发。

热路径是把 GetIO 拿到的指针 reinterpret 成 packed 结构体 overlay —— 0 拷贝:

  • 读写成员就是直接读写过程映像本身,不用拷贝、不用刷新。
  • 读到的永远是当前周期,写下的下个周期发出。

需要按字节偏移取单个定宽标量时,用元素视图(ec_pdo_array_t / ec_pdo_data_item_t + PDOArrayItem + PDOItemRead* / PDOItemWrite*)。

PDO 周期与邮箱的关系

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

过程数据元素视图契约
  • 是视图,不是拷贝:视图只持 frame / size / offset / is_input 四个字段,自己不存任何数据字节 —— 元素就是过程映像里那一个字节的句柄。稳态零分配,调用方通常放栈上。
  • 视图不持有帧:这族函数不复制、不持有、不释放帧,帧的寿命与线程安全由调用方负责。live 切片在主站释放 / 换代后立即失效,禁止跨代缓存。
  • 失败语义:元素访问器一律返回 FALSE 且不改写出参 —— 不折叠成 0、不静默按 1 字节读、不报半个结果。
  • 写侧只认输出:只有输出侧(RxPDO)窗口可写;对输入侧视图调用写访问器一律返回 FALSE(输入区由主站每周期覆盖,写它静默无效)。
  • 索引越界与宽度越界分开报:PDOArrayItem 的字节索引越界与"元素起点处放不下该类型的宽度"是两种失败,便于定位是选错字节还是选错类型。

使用案例​

日常使用​

① 结构体映射(0 拷贝,持续用):进 OP 后 GetIO 取一次过程映像指针,reinterpret 成 packed 结构体指针,之后当变量读写。

uint16_t m = EcInitFromFile("C:\\EtherCAT\\MyProject.json");  /* JSON 一步初始化(缺省进 OP)*/

#pragma pack(push, 1)
typedef struct {
uint16_t status_word;
int32_t actual_position;
} in_pdo_t;

typedef struct {
uint16_t control_word;
int32_t target_position;
} out_pdo_t;
#pragma pack(pop)

int32_t out_size = 0, in_size = 0;
uint8_t *out_ptr = NULL, *in_ptr = NULL;
if (!ec_pdo_io_is_ok(GetIO(m, 1, &out_size, &out_ptr, &in_size, &in_ptr)))
return 1; /* 未进 OP 或过程映像不可用 */
if ((size_t)in_size < sizeof(in_pdo_t) || (size_t)out_size < sizeof(out_pdo_t))
return 1; /* 结构体比 IO 区大:会踩到别的从站 */

in_pdo_t *in = (in_pdo_t *)in_ptr; /* 钉在过程映像上(0 拷贝) */
out_pdo_t *out = (out_pdo_t *)out_ptr;

out->control_word = 0x000F; /* 写:下个周期发出 */
out->target_position = in->actual_position + 1000; /* 读:当前周期 */

② 元素视图(按偏移读一个定宽值):先取一份输入帧快照,再在快照上建数组视图、取元素视图。

uint8_t  frame[64];
uint32_t copied = 0, required = 0;
uint8_t st = CopySlaveInputPdo(m, 1, frame, (uint32_t)sizeof frame, &copied, &required);

if (ec_pdo_io_is_ok(st)) {
ec_pdo_array_t arr;
ec_pdo_data_item_t item;
uint16_t sw = 0;

if (PDOArrayFromFrame(frame, (int32_t)copied, TRUE, &arr) /* TRUE = 输入侧:只读 */
&& PDOArrayItem(&arr, 0, &item) /* 偏移 0 处的元素视图 */
&& PDOItemReadUInt16(&item, &sw)) /* 小端 2 字节 */
printf("status=0x%04X\n", sw);
} else if (ec_pdo_io_is_buffer_too_small(st)) {
printf("输入帧 %u 字节,缓冲区扩容后重试\n", required);
}

③ 元素视图写输出:输出侧不能对 live 切片做局部读改写,走版本化事务(Copy → patch → Commit)。

uint8_t  out_frame[64];                                 /* 缓冲不足时按 required 给出的完整帧长扩容 */
uint32_t copied = 0, required = 0;
uint64_t ticket = 0, deadline = 0, token = 0; /* 首次 Begin:票据 / 截止时间传 0 由 Core 签发 */
uint8_t st = CopySlaveOutputPdoVersioned(m, 1, out_frame, (uint32_t)sizeof out_frame,
&copied, &required, &ticket, &deadline, &token);
if (ec_pdo_io_is_ok(st)) {
ec_pdo_array_t arr;
ec_pdo_data_item_t item;

if (PDOArrayFromFrame(out_frame, (int32_t)copied, FALSE, &arr) /* FALSE = 输出侧:可写 */
&& PDOArrayItem(&arr, 2, &item)) {
if (!PDOItemWriteInt16(&item, 1500)) /* patch:只改帧缓冲,还没上总线 */
printf("patch 被拒:该元素放不下 2 字节\n");
}

uint64_t current = 0;
uint32_t written = 0, need = 0;
if (!ec_pdo_io_is_ok(CommitSlaveOutputPdoVersioned(m, 1, out_frame, copied, token,
ticket, deadline,
&current, &written, &need)))
printf("提交失败:written=%u required=%u\n", written, need);
}

周期变化​

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

while (running) {
out->control_word = (in->status_word & 0x0004) ? 0x000F : 0x0006;
out->target_position = in->actual_position;
wait_next_tick(); /* 你自己的节奏,例如 1 ms */
}

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

元素视图反过来 —— 它绑的是你交给它的那段窗口:快照帧不会自己变,要新的一拍必须重新取帧;live 切片指向的就是过程映像本身,值跟着总线变,不必每拍重取,但释放 / 换代后必须重取。

过程映像访问​

GetIO()​

uint8_t GetIO(uint16_t master_index, uint16_t slave_index,
int32_t* out_byte_size, uint8_t** out_byte,
int32_t* in_byte_size, uint8_t** in_byte);

取从站的 live IOmap 指针:out_byte 是输出过程映像(写进去即下发),in_byte 是输入过程映像(总线刷新即更新)。这是 0 拷贝热路径的入口,配合 packed 结构体 overlay 使用。

参数:

  • master_index (uint16_t) — 主站索引,1-based
  • slave_index (uint16_t) — 从站索引,1-based
  • out_byte_size (int32_t*) — 出参,接收输出过程映像字节数
  • out_byte (uint8_t**) — 出参,接收输出过程映像基址
  • in_byte_size (int32_t*) — 出参,接收输入过程映像字节数
  • in_byte (uint8_t**) — 出参,接收输入过程映像基址

返回值:

  • uint8_t — native 状态,成功是 0x00;一律用 ec_pdo_io_is_ok() 判定,不要拿 EC_PDO_IO_* 名去比

说明:

  • 失败(状态非成功或 shell 未就绪导致指针为 NULL)时绝不要解引用 out_byte / in_byte。
  • 拿到指针后按 *_byte_size 核对结构体大小再 overlay。
  • 指针钉在 IOmap 上,主站释放 / 换代后失效。
  • 输入区只读:in_byte 指向的 I 区由主站每周期从从站覆盖,写它静默无效(下一周期即被冲掉)。只读消费(解码 / 判断 / 转发)用 GetIORO,出参是 const uint8_t**,类型上封住写口;写过程数据只走输出侧(out_byte / PDOSliceOutputPtr / WriteSlaveOutputPdo / 版本化事务)。

示例:

int32_t out_size = 0, in_size = 0;
uint8_t *out_ptr = NULL, *in_ptr = NULL;
if (ec_pdo_io_is_ok(GetIO(m, 1, &out_size, &out_ptr, &in_size, &in_ptr))
&& out_ptr && in_ptr && in_size >= 2 && out_size >= 1) {
uint16_t sw = (uint16_t)(in_ptr[0] | (in_ptr[1] << 8)); /* 读输入:当前周期,小端 */
out_ptr[0] = 0x0F; /* 写输出:下个周期发出 */
printf("sw=0x%04X\n", sw);
}

ec_pdo_io_is_ok() / ec_pdo_io_is_buffer_too_small()​

BOOL ec_pdo_io_is_ok(uint8_t status);
BOOL ec_pdo_io_is_buffer_too_small(uint8_t status);

过程数据状态码的唯一正确判据。整帧函数与 GetIO 的成功码是同一个 native 码 0x00;两个助手只是额外接受合同码(1 / 2),判定一律只走它们。

参数:

  • status (uint8_t) — GetIO 或整帧函数的返回值

返回值:

  • BOOL — ec_pdo_io_is_ok:native 成功码 0x00 或合同码 1 都判成功;ec_pdo_io_is_buffer_too_small:native 0xFD 或合同码 2 都判缓冲不足

说明:

  • 缓冲不足时不会复制半帧,required_size 给出完整帧长度,按它扩容后重试即可。
  • 运行期交回来的只有 native 码:成功 0x00,失败类 0xFA..0xFF。1..6 是 ec_pdo_io_status_t(master/core.h)的合同取值,不要拿 EC_PDO_IO_* 名去比原始 status —— EC_PDO_IO_OK 判不到 native 成功,而 EC_PDO_IO_FAILED 又与 native 成功同值 0。
  • 其它状态(长度不匹配、版本冲突、generation 已失效、IOmap 正忙)都是失败。

示例:

uint32_t copied = 0, required = 0;
uint8_t st = CopySlaveInputPdo(m, 1, frame, (uint32_t)sizeof frame, &copied, &required);
if (ec_pdo_io_is_ok(st))
printf("这一拍 %u 字节\n", copied);
else if (ec_pdo_io_is_buffer_too_small(st))
printf("需要 %u 字节的缓冲\n", required);

PDOSliceInputPtr()​

BOOL PDOSliceInputPtr(uint16_t master_index, uint16_t slave_index,
int32_t offset, int32_t size,
const uint8_t** slice);

在 live 输入过程映像的字节偏移 offset 处取 size 字节的只读切片视图(0 拷贝)。

参数:

  • master_index (uint16_t) — 主站索引,1-based
  • slave_index (uint16_t) — 从站索引,1-based
  • offset (int32_t) — 相对该从站输入过程映像起点的字节偏移
  • size (int32_t) — 切片字节数,必须 > 0
  • slice (const uint8_t**) — 出参,接收切片基址

返回值:

  • BOOL — 成功 TRUE
    • 越界(offset < 0、size <= 0、offset + size 超出该方向映像)或映像不可用返回 FALSE,且 *slice 置 NULL —— 绝不返回指向映像外的指针

说明: 返回的是 const 视图:输入区由主站每周期从从站覆盖,写它静默无效,要写过程数据走输出侧。

用元素视图解 live 输入切片

PDOSliceInputPtr 给的是 const uint8_t*。

要在它上面建元素视图需要显式去掉 const,并且只能用于读取 —— 视图 is_input = TRUE 会让所有写访问器返回 FALSE,只读约束仍由 SDK 强制。

示例:

const uint8_t* slice = NULL;
if (PDOSliceInputPtr(m, 1, 0, 4, &slice)) {
ec_pdo_array_t arr;
ec_pdo_data_item_t item;
uint32_t value = 0;

if (PDOArrayFromFrame((uint8_t *)(uintptr_t)slice, 4, TRUE, &arr)
&& PDOArrayItem(&arr, 0, &item)
&& PDOItemReadUInt32(&item, &value))
printf("value=%u\n", value); /* 每次访问都读当前映像 */
}

PDOSliceOutputPtr()​

BOOL PDOSliceOutputPtr(uint16_t master_index, uint16_t slave_index,
int32_t offset, int32_t size,
uint8_t** slice);

在 live 输出过程映像的字节偏移 offset 处取 size 字节切片视图,改写即写回。越界判据与 PDOSliceInputPtr 相同。

参数:

  • master_index (uint16_t) — 主站索引,1-based
  • slave_index (uint16_t) — 从站索引,1-based
  • offset (int32_t) — 相对该从站输出过程映像起点的字节偏移
  • size (int32_t) — 切片字节数,必须 > 0
  • slice (uint8_t**) — 出参,接收切片基址

返回值:

  • BOOL — 成功 TRUE;越界或映像不可用返回 FALSE,且 *slice 置 NULL

说明: 这是一个活的写窗口,适合整段 overwrite。禁止对 live 输出切片做局部读改写(RMW) —— 切片内的部分覆盖 / 位操作必须走版本化事务。

示例:

uint8_t* out_slice = NULL;
if (PDOSliceOutputPtr(m, 1, 0, 2, &out_slice)) {
ec_pdo_array_t arr;
ec_pdo_data_item_t item;

if (PDOArrayFromFrame(out_slice, 2, FALSE, &arr)
&& PDOArrayItem(&arr, 0, &item)
&& PDOItemWriteUInt16(&item, 0x000F))
printf("已写入输出切片\n");
}

帧来源与新鲜度​

元素视图绑在调用方持有的一段过程映像字节窗口上。窗口有三种来源,元素访问器对三者一视同仁 —— 它只认窗口,不认来源:

  • 快照帧(推荐):CopySlaveInputPdo / CopySlaveOutputPdo 拷出的整帧。这是 generation-scoped 的一致读:一次成功调用给出的整帧自洽,缓冲不足时不会复制半帧。
  • 版本化输出帧:CopySlaveOutputPdoVersioned 取的帧,patch 之后必须由 CommitSlaveOutputPdoVersioned 提交,中间那一段只在你的缓冲里,没上总线。
  • live 切片:PDOSliceInputPtr / PDOSliceOutputPtr 指向的过程映像本身,0 拷贝,读到的是"访问那一刻"的值。

新鲜度由窗口决定,不由访问器决定。 SDK 不替你取快照:一次访问器调用只是从绑定窗口上取一次值。

  • 视图绑在快照帧上时,视图生命期内所有读都是同一帧 —— 多字段天然一致,但它不会自己变,要新的一拍必须重新取帧。
  • 视图绑在 live 切片上时,每次访问器调用都直接读当前映像,因此同一个表达式里的两次访问可能落在两个不同的总线周期(例如把两个元素的读结果写进一个表达式,两次读之间总线可能已经刷新了一轮)。

需要一拍之内多字段互相自洽的视图,就用 CopySlaveInputPdo 取整帧快照,在这块缓冲上建数组视图;或者用 GetIO 的 packed 结构体 overlay,一次读完整个方向。

元素访问器不是 0 拷贝热路径。

0 拷贝读过程映像的入口是 GetIO(packed 结构体 overlay)与 PDOSliceInputPtr / PDOSliceOutputPtr(按偏移取切片)。

元素访问器是"按偏移解一个定宽标量"的工具:它本身不复制字节,但也不替你保证一致,读的就是你交给它的那段窗口。

需要对 live 映像做安全的整体视图,用 overlay,不要靠元素访问器逐个字段拼。

整帧快照与版本化事务​

CopySlaveInputPdo() / CopySlaveOutputPdo()​

uint8_t CopySlaveInputPdo(uint16_t master_index, uint16_t slave_index,
uint8_t* buffer, uint32_t buffer_size,
uint32_t* copied_size, uint32_t* required_size);
uint8_t CopySlaveOutputPdo(uint16_t master_index, uint16_t slave_index,
uint8_t* buffer, uint32_t buffer_size,
uint32_t* copied_size, uint32_t* required_size);

把一整个方向的 PDO 帧拷进你自己的缓冲,得到一份同一代、自洽的快照。

参数:

  • master_index (uint16_t) — 主站索引,1-based
  • slave_index (uint16_t) — 从站索引,1-based
  • buffer (uint8_t*) — 调用方的接收缓冲
  • buffer_size (uint32_t) — 缓冲字节数
  • copied_size (uint32_t*) — 出参,实际拷贝的字节数
  • required_size (uint32_t*) — 出参,完整帧长度(缓冲不足时按它扩容)

返回值:

  • uint8_t — 状态码,用 ec_pdo_io_is_ok() / ec_pdo_io_is_buffer_too_small() 判定

说明: 缓冲不足时不复制任何字节,required_size 给出权威长度。快照是你的内存,拷出来之后不跟着总线变,可以慢慢解析、跨线程传阅;这是"要一致的多字段视图"时的首选帧来源。

示例:

uint8_t  frame[128];
uint32_t copied = 0, required = 0;
if (ec_pdo_io_is_ok(CopySlaveInputPdo(m, 1, frame, (uint32_t)sizeof frame, &copied, &required))) {
ec_pdo_array_t arr;
if (PDOArrayFromFrame(frame, (int32_t)copied, TRUE, &arr))
printf("这一拍 %d 字节的视图就绪\n", PDOArrayLength(&arr));
}

WriteSlaveOutputPdo()​

uint8_t WriteSlaveOutputPdo(uint16_t master_index, uint16_t slave_index,
const uint8_t* data, uint32_t size,
uint32_t* written_size, uint32_t* required_size);

用手里的完整 RxPDO 帧整帧精确替换该从站的输出过程映像(单一实时 owner 的整帧写入口)。

参数:

  • master_index (uint16_t) — 主站索引,1-based
  • slave_index (uint16_t) — 从站索引,1-based
  • data (const uint8_t*) — 要写入的完整输出帧
  • size (uint32_t) — 帧字节数,必须等于当前完整帧长度
  • written_size (uint32_t*) — 出参,实际写入的字节数
  • required_size (uint32_t*) — 出参,当前的完整帧长度

返回值:

  • uint8_t — 状态码,用 ec_pdo_io_is_ok() 判定;size 与当前完整帧长度不匹配时返回长度不匹配状态且不写任何字节

说明: 定长整帧替换,不做读改写 —— 只改其中几个字段时走版本化事务。

示例:

uint8_t  frame[64];                        /* 完整输出帧,长度须与当前帧一致 */
uint32_t written = 0, required = 0;
if (!ec_pdo_io_is_ok(WriteSlaveOutputPdo(m, 1, frame, (uint32_t)sizeof frame,
&written, &required)))
printf("写入被拒:written=%u required=%u\n", written, required);

CopySlaveOutputPdoVersioned() / CommitSlaveOutputPdoVersioned()​

uint8_t CopySlaveOutputPdoVersioned(uint16_t master_index, uint16_t slave_index,
uint8_t* buffer, uint32_t buffer_size,
uint32_t* copied_size, uint32_t* required_size,
uint64_t* transaction_ticket,
uint64_t* native_deadline_us,
uint64_t* version_token);
uint8_t CommitSlaveOutputPdoVersioned(uint16_t master_index, uint16_t slave_index,
const uint8_t* data, uint32_t size,
uint64_t expected_token,
uint64_t transaction_ticket,
uint64_t native_deadline_us,
uint64_t* current_token,
uint32_t* written_size,
uint32_t* required_size);

乐观并发的 RxPDO 事务:Begin 取一份输出帧并带回版本令牌,在本地缓冲上 patch,再 Commit 提交。这是局部 / 类型化 / 位 / 批量读改写唯一的合法入口。

参数:

  • master_index (uint16_t) — 主站索引,1-based
  • slave_index (uint16_t) — 从站索引,1-based
  • buffer (uint8_t*) / data (const uint8_t*) — Begin 的接收缓冲 / Commit 要提交的帧
  • buffer_size (uint32_t) / size (uint32_t) — 缓冲字节数 / 提交帧长度
  • copied_size (uint32_t*) / written_size (uint32_t*) — 出参,实际拷贝 / 实际写入字节数
  • required_size (uint32_t*) — 出参,完整帧长度
  • transaction_ticket (uint64_t*) — 出参(Begin)/ 传入(Commit),Core 签发的不透明事务票据
  • native_deadline_us (uint64_t*) — 出参(Begin)/ 传入(Commit),Core 单调时钟的绝对截止值
  • version_token (uint64_t*) — 出参(Begin)/ 经 expected_token 传入(Commit)
    • 高 32 位是 context generation,低 32 位是该从站输出 revision,两者都不为 0
  • current_token (uint64_t*) — 出参,权威的当前版本令牌

返回值:

  • uint8_t — 状态码,用 ec_pdo_io_is_ok() 判定;冲突 / 已失效 / 长度不匹配时 written_size 为 0 并返回权威的 current_token / required_size

说明:

  • 第一次 Begin 把 transaction_ticket 与 native_deadline_us 都传 0,Core 同时签发非零票据并设置 now + 5000 us 的绝对截止时间。
  • 同一笔读改写的尺寸查询、重新 Begin、失败重试与 Commit 必须原样复用这两个值,禁止清零续期或伪造。
  • 只有 Core 可以裁决已产生副作用的 Commit 是否超时;Commit 返回成功后不得再由调用方本地时钟改判失败。
写输出的正确入口
  • 整帧精确替换(单一实时 owner 已持有完整权威 RxPDO)→ WriteSlaveOutputPdo;size 与当前完整帧长度不匹配时返回长度不匹配状态且不写任何字节。
  • 局部 / 类型化 / 位 / 批量读改写 → 本节这对版本化事务。
  • WriteSlaveOutput / WriteSlaveOutputByte / PDOWriteDirect / PDOBatchWrite 都是退场入口:
    • 固定 fail-closed,不写任何输出。
    • 前两个与真入口 WriteSlaveOutputPdo 只差词尾。
  • 读侧孪生 PDOReadDirect / PDOBatchRead 是活入口:只把调用方缓冲填成一整帧输入快照,不写任何输出,不在退场之列。

示例:

uint64_t ticket = 0, deadline = 0, token = 0;
uint64_t current = 0;
uint32_t copied = 0, required = 0, written = 0, need = 0;

if (ec_pdo_io_is_ok(CopySlaveOutputPdoVersioned(m, 1, frame, (uint32_t)sizeof frame,
&copied, &required, &ticket, &deadline, &token))) {
/* 在 frame 上按元素视图 patch(见"元素视图"一节)... */

if (!ec_pdo_io_is_ok(CommitSlaveOutputPdoVersioned(m, 1, frame, copied, token,
ticket, deadline,
&current, &written, &need)))
printf("提交失败:current=0x%llX written=%u required=%u\n",
(unsigned long long)current, written, need);
}

元素视图​

PDOArrayFromFrame()​

BOOL PDOArrayFromFrame(uint8_t* frame, int32_t size, BOOL is_input, ec_pdo_array_t* array);

在调用方持有的过程映像字节窗口上建立数组视图。本函数不复制、不持有、不释放帧。

参数:

  • frame (uint8_t*) — 窗口基址(快照帧缓冲或 live 切片),调用方持有
  • size (int32_t) — 窗口可用字节数 = 本方向过程映像字节数;未进 OP / 未映射传 0,此时任何索引都越界
  • is_input (BOOL) — TRUE = 输入侧(TxPDO,只读);FALSE = 输出侧(RxPDO,可写)
  • array (ec_pdo_array_t*) — 出参,接收数组视图

返回值:

  • BOOL — 成功 TRUE;frame / array 为 NULL 或 size < 0 返回 FALSE

示例:

ec_pdo_array_t arr;
if (PDOArrayFromFrame(frame, (int32_t)copied, TRUE, &arr))
printf("输入过程映像 %d 字节\n", PDOArrayLength(&arr));

相关结构:

typedef struct {
uint8_t* frame; /* 窗口基址 (调用方持有; 不复制、不释放) */
int32_t size; /* 窗口可用字节数 = 本方向过程映像字节数; 未进 OP = 0 */
BOOL is_input; /* TRUE = 输入侧 (TxPDO, 只读) / FALSE = 输出侧 (RxPDO) */
} ec_pdo_array_t;

PDOArrayLength()​

int32_t PDOArrayLength(const ec_pdo_array_t* array);

本方向过程映像的字节数。

参数:

  • array (const ec_pdo_array_t*) — 数组视图

返回值:

  • int32_t — 字节数;array 为 NULL、或未进 OP / 未映射时为 0

示例:

for (int32_t i = 0; i < PDOArrayLength(&arr); i++) {
ec_pdo_data_item_t item;
if (PDOArrayItem(&arr, i, &item))
printf("偏移 %d 可访问\n", i);
}

PDOArrayItem()​

BOOL PDOArrayItem(const ec_pdo_array_t* array, int32_t index, ec_pdo_data_item_t* item);

按字节偏移取元素视图,index 相对本方向过程映像起点、从 0 起。元素视图只持句柄,不持数据字节。

参数:

  • array (const ec_pdo_array_t*) — 数组视图
  • index (int32_t) — 字节偏移,取值范围 [0, PDOArrayLength(array))
  • item (ec_pdo_data_item_t*) — 出参,接收元素视图

返回值:

  • BOOL — 成功 TRUE;array / item 为 NULL、窗口基址为 NULL、或 index 越界返回 FALSE

说明: 这里拒绝的是"字节偏移越界"。取到元素视图后,访问器还会再判一次"该宽度在这个元素起点放不下",两种失败刻意分开报。

示例:

ec_pdo_data_item_t item;
if (PDOArrayItem(&arr, 6, &item))
printf("偏移 6 的元素视图就绪\n");

相关结构:

typedef struct {
uint8_t* frame; /* 窗口基址 (同 ec_pdo_array_t) */
int32_t size; /* 窗口可用字节数 */
int32_t offset; /* 元素字节偏移 (相对 frame) */
BOOL is_input; /* TRUE = 输入侧 (TxPDO, 只读) */
} ec_pdo_data_item_t;

元素访问器​

已删除的旧入口

偏移式单标量接口(PdoRead* / PdoWrite* / PDOReadInput* / PDOWriteOutput*)的头内包装已从本 SDK 删除。

对应的薄壳 DarraEcat_* 导出仍在内部 ABI 里,序号不平移,不属公开入口,不要直调。

按偏移取单个值请统一走元素视图(PDOArrayItem + PDOItemRead* / PDOItemWrite*)。

PDOItemReadContent() / PDOItemWriteContent()​

BOOL PDOItemReadContent (const ec_pdo_data_item_t* item, uint8_t* value);
BOOL PDOItemWriteContent(const ec_pdo_data_item_t* item, uint8_t value);

元素第一个字节的原始值(1 字节,无符号)。PDOItemReadUInt8 / PDOItemWriteUInt8 与它同义同字节。

参数:

  • item (const ec_pdo_data_item_t*) — 元素视图
  • value (uint8_t* / uint8_t) — 读:出参,失败时不改写;写:要写入的字节

返回值:

  • BOOL — 成功 TRUE;value 为 NULL、偏移越界、或输入侧视图(写)返回 FALSE

示例:

uint8_t raw = 0;
if (PDOItemReadContent(&item, &raw))
printf("byte=0x%02X\n", raw);
if (!PDOItemWriteContent(&out_item, 0x0F)) /* 整字节覆盖,仅输出侧 */
printf("写入被拒:输入侧视图或偏移越界\n");

类型化读取(PDOItemRead*())​

BOOL PDOItemReadInt8  (const ec_pdo_data_item_t* item, int8_t*   value);
BOOL PDOItemReadUInt8 (const ec_pdo_data_item_t* item, uint8_t* value);
BOOL PDOItemReadInt16 (const ec_pdo_data_item_t* item, int16_t* value);
BOOL PDOItemReadUInt16(const ec_pdo_data_item_t* item, uint16_t* value);
BOOL PDOItemReadInt32 (const ec_pdo_data_item_t* item, int32_t* value);
BOOL PDOItemReadUInt32(const ec_pdo_data_item_t* item, uint32_t* value);
BOOL PDOItemReadInt64 (const ec_pdo_data_item_t* item, int64_t* value);
BOOL PDOItemReadUInt64(const ec_pdo_data_item_t* item, uint64_t* value);
BOOL PDOItemReadFloat (const ec_pdo_data_item_t* item, float* value);
BOOL PDOItemReadDouble(const ec_pdo_data_item_t* item, double* value);

从元素偏移起按定宽读一个标量,读侧对任何方向的视图都合法。

参数:

  • item (const ec_pdo_data_item_t*) — 元素视图
  • value (int8_t* … double*) — 出参,接收读出的值;失败时不改写

返回值:

  • BOOL — 成功 TRUE;value 为 NULL、元素窗口放不下该宽度、或偏移越界返回 FALSE

说明: 多字节一律小端(EtherCAT 线序),本 SDK 不做任何字节颠倒。PDOItemReadInt8 按 8 bit 做二补数符号扩展;浮点是 IEEE754。

示例:

int16_t  pos = 0;
uint16_t sw = 0;
if (PDOItemReadInt16(&item, &pos))
printf("pos=%d\n", pos); /* 元素偏移起 2 字节,小端 */
if (!PDOItemReadUInt16(&item, &sw))
printf("该元素放不下 2 字节\n"); /* 例如已是最后一个字节 */

类型化写入(PDOItemWrite*())​

BOOL PDOItemWriteInt8  (const ec_pdo_data_item_t* item, int8_t   value);
BOOL PDOItemWriteUInt8 (const ec_pdo_data_item_t* item, uint8_t value);
BOOL PDOItemWriteInt16 (const ec_pdo_data_item_t* item, int16_t value);
BOOL PDOItemWriteUInt16(const ec_pdo_data_item_t* item, uint16_t value);
BOOL PDOItemWriteInt32 (const ec_pdo_data_item_t* item, int32_t value);
BOOL PDOItemWriteUInt32(const ec_pdo_data_item_t* item, uint32_t value);
BOOL PDOItemWriteInt64 (const ec_pdo_data_item_t* item, int64_t value);
BOOL PDOItemWriteUInt64(const ec_pdo_data_item_t* item, uint64_t value);
BOOL PDOItemWriteFloat (const ec_pdo_data_item_t* item, float value);
BOOL PDOItemWriteDouble(const ec_pdo_data_item_t* item, double value);

把值按定宽小端写进元素所在字节。仅输出侧:输入侧视图一律返回 FALSE 且不改写任何字节。

参数:

  • item (const ec_pdo_data_item_t*) — 元素视图,必须来自输出侧窗口
  • value (int8_t … double) — 要写入的值

返回值:

  • BOOL — 成功 TRUE;输入侧视图、元素窗口放不下该宽度、或偏移越界返回 FALSE

说明: 对 live 输出切片做部分覆盖(含位操作)属于局部读改写,不允许;整段覆盖整个切片可以。其余写要落在版本化事务取出的帧缓冲上,patch 完由 CommitSlaveOutputPdoVersioned 提交。

示例:

/* arr 建在版本化事务取出的输出帧上(is_input = FALSE) */
ec_pdo_data_item_t item;
if (PDOArrayItem(&arr, 2, &item)) {
if (!PDOItemWriteInt16(&item, 1500)) /* patch 帧缓冲 */
printf("写入被拒\n");
}
/* patch 完成,随后 Commit */

PDOItemReadBool() / PDOItemWriteBool()​

BOOL PDOItemReadBool (const ec_pdo_data_item_t* item, BOOL* value);
BOOL PDOItemWriteBool(const ec_pdo_data_item_t* item, BOOL value);

整字节布尔访问器,语义是 bit0:读 (byte & 0x01) != 0,写 TRUE = 0x01 / FALSE = 0x00(整字节覆盖,不是位操作)。

参数:

  • item (const ec_pdo_data_item_t*) — 元素视图
  • value (BOOL* / BOOL) — 读:出参,失败时不改写;写:要写入的布尔值

返回值:

  • BOOL — 成功 TRUE;value 为 NULL、偏移越界、或输入侧视图(写)返回 FALSE

说明:

  • 布尔访问器只认最低位:字节 0x00 读 FALSE、0x01 读 TRUE,而 0x02 与 0x80 都读 FALSE。
  • 这不是"非 0 即真",很多人第一次会踩。
  • 要按整字节 0 / 非 0 判真假,用 PDOItemReadContent 取出原始字节自行比较 —— 本 SDK 不提供第二个"非 0 即真"的布尔访问器。

示例:

BOOL enable = FALSE;
if (PDOItemReadBool(&item, &enable))
printf("bit0 = %s\n", enable ? "true" : "false"); /* 字节 0x02 / 0x80 也是 false */

if (!PDOItemWriteBool(&out_item, TRUE)) /* 整字节写 0x01 */
printf("写入被拒\n");

PDOItemReadBit() / PDOItemWriteBit()​

BOOL PDOItemReadBit (const ec_pdo_data_item_t* item, int bit_index, BOOL* value);
BOOL PDOItemWriteBit(const ec_pdo_data_item_t* item, int bit_index, BOOL value);

读 / 写本元素字节的第 bit_index 位,bit_index 取 0..7,0 = LSB。

参数:

  • item (const ec_pdo_data_item_t*) — 元素视图
  • bit_index (int) — 位号 0..7,0 = LSB
  • value (BOOL* / BOOL) — 读:出参,失败时不改写;写:要写入的位

返回值:

  • BOOL — 成功 TRUE;bit_index 不在 0..7、偏移越界、或输入侧视图(写)返回 FALSE

说明:

  • 位号是按字节而不是按元素宽度寻址:无论元素有多宽,0..7 都指向元素偏移处的那一个字节(多字节元素的第 0 字节就是小端低字节),也不会跨到下一个字节。
  • 写在输出侧,并且只覆盖目标位 —— 同字节其余位保持原值。

示例:

BOOL ready = FALSE;
if (PDOItemReadBit(&item, 0, &ready)) /* 元素字节的 bit0 */
printf("ready=%d\n", ready);

if (!PDOItemWriteBit(&out_item, 0, TRUE)) /* 只置 bit0,同字节其余位不动 */
printf("写入被拒\n");

if (!PDOItemWriteBit(&out_item, 8, TRUE)) /* 位号越界:返回 FALSE,不改任何字节 */
printf("位号必须在 0..7\n");

PDOItemReadValue() / PDOItemWriteValue()​

BOOL PDOItemReadValue (const ec_pdo_data_item_t* item,
ec_pdo_datatype_t type, ec_pdo_value_t* value);
BOOL PDOItemWriteValue(const ec_pdo_data_item_t* item,
ec_pdo_datatype_t type, const ec_pdo_value_t* value);

按 EtherCAT 数据类型读 / 写元素 —— 数据类型要到运行期才知道(例如按 ESI 映射表驱动)时的入口。

参数:

  • item (const ec_pdo_data_item_t*) — 元素视图
  • type (ec_pdo_datatype_t) — EC_DT_* 码值
  • value (ec_pdo_value_t* / const ec_pdo_value_t*) — 读:出参,按 type 取对应成员,失败时不改写;写:按 type 读对应成员

返回值:

  • BOOL — 成功 TRUE;type 不在白名单、value 为 NULL、偏移 / 宽度越界、或输入侧视图(写)返回 FALSE

说明:

  • 白名单恰好 11 个:
    • EC_DT_BOOL、INT8、INT16、INT32、INT64、UINT8
    • UINT16、UINT32、UINT64、REAL32、REAL64(多字节小端)
  • 白名单之外一律返回 FALSE:
    • EC_DT_BIT1..EC_DT_BIT8(含 EC_DT_BIT8 —— 位串与 EC_DT_UINT8 语义不同,有意不映射)
    • EC_DT_INT24 / EC_DT_INT40 / EC_DT_INT48 / EC_DT_INT56(以及对应的 EC_DT_UINT24 / EC_DT_UINT40 / EC_DT_UINT48 / EC_DT_UINT56)
    • 字符串(EC_DT_VISIBLE_STRING / EC_DT_OCTET_STRING / EC_DT_UNICODE_STRING)
    • EC_DT_TIME_OF_DAY / EC_DT_TIME_DIFFERENCE / EC_DT_DOMAIN / EC_DT_NONE
  • 不猜宽度、不静默按 1 字节读。

联合体没有运行期类型标签,成员必须与 type 匹配:读 EC_DT_INT16 就取 i16,写 EC_DT_REAL32 就填 f32。

boolean 成员读时是 bit0 语义;写时按布尔取真值,落到总线上仍是整字节 0x01 / 0x00。

示例:

ec_pdo_value_t v;
if (PDOItemReadValue(&item, EC_DT_INT32, &v))
printf("i32=%d\n", v.i32);

v.f32 = 12.5f;
if (!PDOItemWriteValue(&out_item, EC_DT_REAL32, &v))
printf("写入被拒\n");

相关结构:

typedef union {
BOOL boolean; /* EC_DT_BOOL, bit0 语义 */
int8_t i8; /* EC_DT_INT8 */
uint8_t u8; /* EC_DT_UINT8 */
int16_t i16; /* EC_DT_INT16 */
uint16_t u16; /* EC_DT_UINT16 */
int32_t i32; /* EC_DT_INT32 */
uint32_t u32; /* EC_DT_UINT32 */
int64_t i64; /* EC_DT_INT64 */
uint64_t u64; /* EC_DT_UINT64 */
float f32; /* EC_DT_REAL32 */
double f64; /* EC_DT_REAL64 */
} ec_pdo_value_t;

位串与变长类型(ec_value_*)​

位串(EC_DT_BIT1..EC_DT_BIT8)没有定宽标量访问器。直接对元素窗口调 ec_value_*,宽度取 ec_value_type_bit_size(1..8):

int bits = ec_value_type_bit_size(EC_DT_BIT3);          /* 3 */
uint64_t raw = 0;
if (ec_value_try_extract_u64(item.frame, item.size,
ec_pdo_item_bit_offset(&item), bits, &raw))
printf("bit3=%llu\n", (unsigned long long)raw);

字符串 / EC_DT_DOMAIN 是变长类型:ec_value_type_bit_size 对它们返回 -1(不是可用宽度),ec_value_* 不适用。

这类字段用 CopySlaveInputPdo 取整帧快照后按字节自行解析。

ec_pdo_item_bit_offset(&item) 给出元素起点的 bit 偏移(字节偏移 × 8);它不做 NULL 检查,item 必须先由 PDOArrayItem 取到手。

写侧用 ec_value_insert_u64 / ec_value_insert_i64(返回 EC_VALUE_OK 表示成功),位域写入是读改写,区外位保持原值。

这组是裸 codec 调用

ec_value_* 不做方向检查:它不会替你拒绝输入侧窗口。只在对输出帧缓冲(版本化事务取出的帧)写入时才用它们,且写入发生在 CommitSlaveOutputPdoVersioned 之前。

完整示例​

#include <stdio.h>
#include <stdint.h>
#include <ethercat.h>

#pragma pack(push, 1)
typedef struct { uint16_t status_word; int32_t actual_position; } in_pdo_t;
typedef struct { uint16_t control_word; int32_t target_position; } out_pdo_t;
#pragma pack(pop)

int main(void)
{
/* JSON 一步初始化:加载 + 进 OP */
uint16_t m = EcInitFromFile("C:\\EtherCAT\\MyProject.json");
if (m == 0) return 1;

/* ① 0 拷贝热路径:packed 结构体钉在过程映像上 */
int32_t out_size = 0, in_size = 0;
uint8_t *out_ptr = NULL, *in_ptr = NULL;
if (!ec_pdo_io_is_ok(GetIO(m, 1, &out_size, &out_ptr, &in_size, &in_ptr)))
return 1; /* 未进 OP 或过程映像不可用 */
if ((size_t)in_size < sizeof(in_pdo_t) || (size_t)out_size < sizeof(out_pdo_t))
return 1; /* 结构体比 IO 区大:会踩到别的从站 */

in_pdo_t *in = (in_pdo_t *)in_ptr;
out_pdo_t *out = (out_pdo_t *)out_ptr;

out->control_word = 0x000F; /* 写:下个周期发出 */
out->target_position = in->actual_position + 1000;
printf("status=0x%04X pos=%d\n", in->status_word, in->actual_position);

/* ② 元素视图:先取一拍输入快照,再按偏移读单个定宽标量 */
uint8_t frame[64];
uint32_t copied = 0, required = 0;
uint8_t st = CopySlaveInputPdo(m, 1, frame, (uint32_t)sizeof frame, &copied, &required);

if (ec_pdo_io_is_ok(st)) {
ec_pdo_array_t arr;
ec_pdo_data_item_t item;
uint16_t sw = 0;
BOOL ready = FALSE;

if (PDOArrayFromFrame(frame, (int32_t)copied, TRUE, &arr) /* 快照帧是输入侧:只读 */
&& PDOArrayItem(&arr, 0, &item)) {
if (PDOItemReadUInt16(&item, &sw)) /* 小端 2 字节 */
printf("快照 status=0x%04X\n", sw);
if (PDOItemReadBit(&item, 0, &ready)) /* 同一字节的 bit0 */
printf("ready=%d\n", ready);
}
/* 需要按偏移局部写输出时走版本化事务,见"使用案例 ③" */
} else if (ec_pdo_io_is_buffer_too_small(st)) {
printf("输入帧 %u 字节,缓冲区扩容后重试\n", required);
}

getchar();
EcClose(m); /* 一步关闭:Stop + Dispose */
return 0;
}