跳到主要内容

PDO 输入输出

通过 slave.PDO 访问。

PDO 周期与邮箱的关系

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

推荐

高性能场景使用 结构体映射(零拷贝),快速原型使用 索引访问

结构体映射(零拷贝)

InputsMapping<T>()

public ref T InputsMapping<T>() where T : struct

将输入 PDO 数据映射为结构体引用(零拷贝,约 5-10 纳秒)。

说明: 结构体大小必须与 Ibytes 完全匹配。返回的引用直接映射到过程数据。

示例:

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

ref ServoInput input = ref slave.PDO.InputsMapping<ServoInput>();
Console.WriteLine($"当前位置: {input.ActualPosition}");

InputsMapping<T>(onChanged)

public InputPdoInstance<T> InputsMapping<T>(Action<PdoStructChangedEventArgs<T>> onChanged) where T : struct
public InputPdoInstance<T> InputsMapping<T>(Action onChanged) where T : struct

将输入 PDO 数据映射为结构体实例,支持数据变化回调。

返回值: InputPdoInstance<T> — 通过 .Value 获取当前输入 PDO 结构体引用(零拷贝)。

回调参数:

public sealed class PdoStructChangedEventArgs<T> : EventArgs where T : struct
{
public ushort SlaveIndex { get; } // 从站索引
public bool IsInput { get; } // 是否为输入PDO
public T Previous { get; } // 变化前的值
public T Current { get; } // 变化后的值
public DateTime Timestamp { get; } // 时间戳
}

示例:

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

var inputInstance = slave.PDO.InputsMapping<ServoInput>(e =>
{
Console.WriteLine($"位置变化: {e.Previous.ActualPosition} -> {e.Current.ActualPosition}");
});

ref ServoInput input = ref inputInstance.Value;

InputsSliceMapping<T>(int offset)

public ref T InputsSliceMapping<T>(int offset) where T : struct

将输入 PDO 数据的指定偏移位置映射为结构体引用。用于 MDP 模块化设备,每个模块在 IOmap 中有不同偏移。

参数:

  • offset (int) — 相对于从站输入起始位置的字节偏移

示例:

// MDP 设备: 模块1 从偏移 0 开始,模块2 从偏移 8 开始
ref var mod1 = ref slave.PDO.InputsSliceMapping<Module1Input>(0);
ref var mod2 = ref slave.PDO.InputsSliceMapping<Module2Input>(8);

InputsSliceMapping<T>(int offset, onChanged)

public InputPdoInstance<T> InputsSliceMapping<T>(int offset, Action<PdoStructChangedEventArgs<T>> onChanged) where T : struct
public InputPdoInstance<T> InputsSliceMapping<T>(int offset, Action onChanged) where T : struct

将输入 PDO 指定偏移位置映射为结构体实例,支持数据变化回调。用于 MDP 模块化设备场景下监控特定模块的数据变化。

参数:

  • offset (int) — 相对于从站输入起始位置的字节偏移
  • onChanged — 数据变化回调

示例:

// MDP 设备: 监控模块2(偏移 8 字节)的输入数据变化
var mod2Instance = slave.PDO.InputsSliceMapping<Module2Input>(8, e =>
{
Console.WriteLine($"模块2 数据变化: {e.Previous} -> {e.Current}");
});

ref Module2Input mod2 = ref mod2Instance.Value;

OutputsMapping<T>()

public ref T OutputsMapping<T>() where T : struct

将输出 PDO 数据映射为结构体引用(零拷贝)。结构体大小必须与 Obytes 匹配。

示例:

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

ref ServoOutput output = ref slave.PDO.OutputsMapping<ServoOutput>();
output.ControlWord = 0x000F;
output.TargetPosition = 10000;

OutputsSliceMapping<T>(int offset)

public ref T OutputsSliceMapping<T>(int offset) where T : struct

将输出 PDO 数据的指定偏移位置映射为结构体引用。用于 MDP 模块化设备。

参数:

  • offset (int) — 相对于从站输出起始位置的字节偏移

BindPdoStruct<T>(...)

public PdoStructBinding<T> BindPdoStruct<T>(
PDOManager manager, bool isInput,
int offset = 0, Func<T, T, bool>? comparer = null) where T : struct

绑定 PDO 结构体变化事件(基于 PDOManager 的数据变化通知)。

参数:

  • manager — PDOManager 实例
  • isInput — true=输入(TxPDO), false=输出(RxPDO)
  • offset — 结构体在 IOmap 中的偏移字节
  • comparer — 可选的自定义比较器

字节数组访问

Inputs / Outputs

public byte[] Inputs { get; }
public byte[] Outputs { get; set; }

直接读写 PDO 字节数组。每次访问从 IOmap 读取最新数据。写入 Outputs 时数据长度必须与 Obytes 匹配。

In / Out(索引访问)

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

基于数组的 PDO 实例,支持按索引访问。

示例:

var value = slave.PDO.In[0].Content;    // 读取第一个输入 PDO
slave.PDO.Out[0].Content = 0xFF; // 写入第一个输出 PDO

直接字节数组访问

提示

以下方法通过 slave.PDO 的底层从站对象提供,适用于需要精细控制偏移和长度的场景。

ReadInputDataDirect() / WriteOutputDataDirect()

public int ReadInputDataDirect(byte[] buffer, int offset = 0, int count = 0)
public int WriteOutputDataDirect(byte[] buffer, int offset = 0, int count = 0)

直接从 IOmap 读取输入数据 / 写入输出数据,支持指定缓冲区偏移和字节数。

参数:

  • buffer (byte[]) — 目标/源缓冲区
  • offset (int) — 缓冲区偏移(默认 0)
  • count (int) — 要读/写的字节数(0 = 全部)

返回值:

  • int — 实际读取/写入的字节数

示例:

byte[] inputBuffer = new byte[slave.Ibytes];
int bytesRead = slave.ReadInputDataDirect(inputBuffer);

byte[] outputData = new byte[] { 0x0F, 0x00, 0x00, 0x00 };
int bytesWritten = slave.WriteOutputDataDirect(outputData);

ReadInputDataPooled() / ReturnPooledBuffer()

public (byte[] buffer, int length) ReadInputDataPooled()
public static void ReturnPooledBuffer(byte[] buffer)

使用 ArrayPool 读取输入数据,减少 GC 压力。调用者必须在使用完毕后归还缓冲区。

返回值:

  • buffer (byte[]) — 从 ArrayPool 租借的缓冲区(可能大于实际数据)
  • length (int) — 实际数据长度

示例:

var (buffer, length) = slave.ReadInputDataPooled();
try
{
// 使用 buffer[0..length]
ushort statusWord = BitConverter.ToUInt16(buffer, 0);
}
finally
{
Slave.ReturnPooledBuffer(buffer);
}

原始指针访问

注意

以下 API 直接操作内存指针,仅适用于极致性能场景。指针在重新初始化后会失效,请确保仅在 PDO 循环期间使用。

GetInputDataPointer() / GetOutputDataPointer()

public IntPtr GetInputDataPointer()
public IntPtr GetOutputDataPointer()

获取输入/输出数据的直接指针(零拷贝访问)。失败时返回 IntPtr.Zero

示例:

IntPtr inputPtr = slave.GetInputDataPointer();
if (inputPtr != IntPtr.Zero)
{
unsafe
{
ushort statusWord = *(ushort*)inputPtr;
}
}

GetInputDataSpan() / GetOutputDataSpan()

public Span<byte> GetInputDataSpan()
public Span<byte> GetOutputDataSpan()

获取输入/输出数据的 Span<byte>(零拷贝访问)。失败时返回 Span<byte>.Empty

备注

仅适用于 .NET Core 3.0+ 和 .NET 5.0+。

示例:

Span<byte> inputSpan = slave.GetInputDataSpan();
if (!inputSpan.IsEmpty)
{
ushort statusWord = BitConverter.ToUInt16(inputSpan.Slice(0, 2));
}

ReadInputDataToSpan() / WriteOutputDataFromSpan()

public int ReadInputDataToSpan(Span<byte> destination)
public int WriteOutputDataFromSpan(ReadOnlySpan<byte> source)

Span 方式读取输入数据 / 写入输出数据(零拷贝)。

备注

仅适用于 .NET Core 3.0+ 和 .NET 5.0+。

参数:

  • destination / source — 目标/源 Span

返回值:

  • int — 实际读取/写入的字节数

示例:

Span<byte> buffer = stackalloc byte[64];
int bytesRead = slave.ReadInputDataToSpan(buffer);

ReadOnlySpan<byte> output = stackalloc byte[] { 0x0F, 0x00 };
int bytesWritten = slave.WriteOutputDataFromSpan(output);