跳到主要内容

PDO 输入输出

通过 slave.CopyInputPdo / slave.WriteOutputPdo 访问。

PDO 周期与邮箱的关系

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

应用层不是零拷贝指针

C# 应用层当前主路径是 generation 整帧复制:读走 CopyInputPdo,写走 WriteOutputPdo。调用方预分配完整帧缓冲区,SDK 把这一代过程数据拷进/拷出托管缓冲。

内核 SHM 零拷贝只存在于驱动内部(IOmap / GlobalIO),不向应用层暴露指针。不要再把 InputsMapping<T>()GetIOGetInputDataPointer / live Span 当成可用主路径——这些入口已退场(Obsolete,返回空或抛错),也不是第二套 API。

主路径:generation 整帧

CopyInputPdo()

public PdoIoStatus CopyInputPdo(
Span<byte> destination, out int copiedSize, out int requiredSize)

把当前 generation 的完整 TxPDO(从站→主站)复制到调用方缓冲区。无每周期托管分配。

返回值: PdoIoStatus。成功时 copiedSize == requiredSize,且必须等于当前输入映射长度。

查询尺寸: 传入空 Span 可读 requiredSize(此时不拷数据)。

示例:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ServoInput
{
public ushort StatusWord;
public int ActualPosition;
public int ActualVelocity;
}

byte[] inputFrame = new byte[slave.InputsByteCount];
PdoIoStatus status = slave.CopyInputPdo(
inputFrame, out int copied, out int required);
if (status == PdoIoStatus.Success &&
copied == required && required == inputFrame.Length)
{
ServoInput input = MemoryMarshal.Read<ServoInput>(inputFrame);
Console.WriteLine($"当前位置: {input.ActualPosition}");
}

CopyOutputPdo()

public PdoIoStatus CopyOutputPdo(
Span<byte> destination, out int copiedSize, out int requiredSize)

把当前 generation 的完整 RxPDO 镜像(主站→从站)复制到调用方缓冲区。用于读回已下发的输出映像;局部改写请在托管缓冲上改完后,再用 WriteOutputPdo 提交完整帧。

WriteOutputPdo()

public PdoIoStatus WriteOutputPdo(
ReadOnlySpan<byte> source, out int writtenSize, out int requiredSize)

单 owner 提交完整 RxPDO。source 长度必须与当前输出映射完全相等,无静默截断。此入口不提供并发 CAS,禁止当局部/typed/batch 读改写用。

示例:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ServoOutput
{
public ushort ControlWord;
public int TargetPosition;
public int TargetVelocity;
}

var output = new ServoOutput
{
ControlWord = 0x000F,
TargetPosition = 10000,
};
byte[] outputFrame = new byte[slave.OutputsByteCount];
MemoryMarshal.Write(outputFrame, ref output);

PdoIoStatus status = slave.WriteOutputPdo(
outputFrame, out int written, out int required);
if (status != PdoIoStatus.Success ||
written != outputFrame.Length || required != outputFrame.Length)
{
// 如实处理 Busy / SizeMismatch 等,不要假定已写入
}

PdoIoStatus

public enum PdoIoStatus : byte
{
Failed = 0,
Success = 1,
BufferTooSmall = 2,
SizeMismatch = 3,
Conflict = 4,
Stale = 5,
Busy = 6,
}

Busy 表示这一代 IOmap 被实时线程瞬占,可按调用方策略有界重试同一完整帧。不要把失败粉饰成成功。

同一路径上的便捷包装

下列入口不是第二套 PDO API,内部仍走 CopyInputPdo / WriteOutputPdo 整帧。

Inputs / Outputs

public byte[] Inputs { get; }          // 每次访问分配一份完整托管快照
public byte[] Outputs { get; set; } // getter 同样是快照;setter 只接受完整帧

高频周期请预分配缓冲区,直接调 CopyInputPdo / WriteOutputPdo,避免每周期 byte[] 分配。

In / Out(索引访问)

public PDOArrayInstance In { get; }
public PDOArrayInstance Out { get; }

按字段索引读写,底层仍是 generation 整帧复制,不是 IOmap 指针。

var value = slave.PDO.In[0].Content;
slave.PDO.Out[0].Content = 0xFF;

已退场(不要再用)

旧写法现状
slave.PDO.InputsMapping<T>() / OutputsMapping<T>() 返回 ref TObsolete,抛 NotSupportedException
InputsSliceMapping<T>(offset) / OutputsSliceMapping<T>(offset) 返回 ref T同上
GetInputDataPointer() / GetOutputDataPointer()Obsolete,返回 IntPtr.Zero
GetInputDataSpan() / GetOutputDataSpan() / live InputSpan()Obsolete,返回空 Span
GetIO 指针主路径应用层已退场

需要结构体视图时:先 CopyInputPdo 到调用方缓冲区,再 MemoryMarshal.Read<T> / Write<T>。不要假设能拿到跨周期仍有效的 IOmap 指针。