PDO 输入输出
通过 slave.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);