跳到主要内容

PDO 输入输出

通过 slave.io() 获取原始指针, 或使用 slave.read_input_*() / slave.write_output_*() 类型化访问。

PDO 周期与邮箱的关系

邮箱通信自动附带在 PDO 周期中,不占用额外帧。邮箱响应延迟约 2-3 个 PDO 周期。

推荐

高性能场景使用 原始指针访问 (slave.io()),快速原型使用 类型化读写 (read_input_* / write_output_*)。

原始指针访问(零拷贝)

io()

pub fn io(&self) -> Result<(i32, *mut u8, i32, *mut u8)>

获取 IO 映射指针和大小,返回 (输出字节数, 输出指针, 输入字节数, 输入指针)。

示例:

let slave = master.slave(1);
let (out_size, out_ptr, in_size, in_ptr) = slave.io()?;

// 使用 IOmap 守卫确保线程安全
{
let _guard = master.iomap_guard();

// 读取输入 (unsafe 原始指针操作)
let status_word = unsafe { *(in_ptr as *const u16) };
let actual_position = unsafe { *((in_ptr.add(2)) as *const i32) };

// 写入输出
unsafe {
*(out_ptr as *mut u16) = 0x000F; // ControlWord
*((out_ptr.add(2)) as *mut i32) = 10000; // TargetPosition
}
}
注意

原始指针在 IOmap 重新映射后会失效,仅在 PDO 循环中使用。

字节数组访问

直接通过 Slave 实例按偏移量和类型读写 PDO 数据。读取方法直接返回值, 写入方法 直接写入 (无返回, 失败被吞掉时建议改用 slave.io() 自行检查指针)。

read_input_u8() / write_output_u8()

pub fn read_input_u8(&self, offset: u32) -> u8
pub fn write_output_u8(&self, offset: u32, value: u8)

读取 / 写入 PDO 中的单字节 (无符号), 适合数字 IO 模块的位组合输入或开关量输出。

let bits = slave.read_input_u8(0);
slave.write_output_u8(0, bits | 0b0000_0001);

read_input_i8() / write_output_i8()

pub fn read_input_i8(&self, offset: u32) -> i8
pub fn write_output_i8(&self, offset: u32, value: i8)

读取 / 写入 PDO 中的有符号字节, 用于温度补偿值或小范围带符号设定值。

read_input_u16() / write_output_u16()

pub fn read_input_u16(&self, offset: u32) -> u16
pub fn write_output_u16(&self, offset: u32, value: u16)

读取 / 写入 PDO 中的 u16, 典型用于 StatusWord / ControlWord (CiA 402)。

let status: u16 = slave.read_input_u16(0);   // StatusWord
slave.write_output_u16(0, 0x000F); // ControlWord

read_input_i16() / write_output_i16()

pub fn read_input_i16(&self, offset: u32) -> i16
pub fn write_output_i16(&self, offset: u32, value: i16)

读取 / 写入 PDO 中的 i16, 典型用于 TargetTorque、模拟量带符号读数等。

read_input_u32() / write_output_u32()

pub fn read_input_u32(&self, offset: u32) -> u32
pub fn write_output_u32(&self, offset: u32, value: u32)

读取 / 写入 PDO 中的 u32, 典型用于错误掩码、计数器等无符号整型。

read_input_i32() / write_output_i32()

pub fn read_input_i32(&self, offset: u32) -> i32
pub fn write_output_i32(&self, offset: u32, value: i32)

读取 / 写入 PDO 中的 i32, 典型用于 ActualPosition / TargetPosition (CiA 402)。

let pos: i32 = slave.read_input_i32(2);
slave.write_output_i32(2, 100000);

read_input_f32() / write_output_f32()

pub fn read_input_f32(&self, offset: u32) -> f32
pub fn write_output_f32(&self, offset: u32, value: f32)

读取 / 写入 PDO 中的 f32, 用于温度、压力等浮点过程值。

读取与写入的特点
  • 读取方法直接返回值 (例如 -> u16)
  • 写入方法直接写入 IOmap, 无返回值; 偏移越界会被忽略 (调用前请确认 slave.input_bytes() / slave.output_bytes())

直接字节数组访问

通过 SlavePdo::new(master_index, slave_index) 构造一个独立的 PDO 访问句柄, 提供字节数组 / 直接读写 / 零拷贝切片 / 结构体绑定能力.

use ethercat::SlavePdo;

let pdo = SlavePdo::new(master.index(), 1);

inputs() / outputs()

pub fn inputs(&self) -> Vec<u8>
pub fn outputs(&self) -> Vec<u8>

获取 PDO 输入 / 输出数据的拷贝。每次调用从 IOmap 读取最新数据。

let input_data = pdo.inputs();
let output_data = pdo.outputs();

write_outputs()

pub fn write_outputs(&self, data: &[u8])

写入输出 PDO 数据 (整体覆盖)。

pdo.write_outputs(&[0x0F, 0x00, 0x00, 0x00]);

read_input_data_direct() / write_output_data_direct()

pub fn read_input_data_direct(&self, buffer: &mut [u8], offset: usize, length: usize) -> usize
pub fn write_output_data_direct(&self, data: &[u8], offset: usize, length: usize) -> usize

直接从 IOmap 读取 / 写入数据, 支持指定偏移和长度.

参数:

  • buffer / data — 目标 / 源缓冲区
  • offset (usize) — 偏移
  • length (usize) — 要读 / 写的字节数 (0 = 全部)

返回值:

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

示例:

let mut input_buffer = vec![0u8; slave.ibytes() as usize];
let bytes_read = pdo.read_input_data_direct(&mut input_buffer, 0, 0);

let output_data = [0x0Fu8, 0x00, 0x00, 0x00];
let bytes_written = pdo.write_output_data_direct(&output_data, 0, 4);

结构体映射(零拷贝)

注意

以下 API 直接操作内存指针 / 切片, 仅适用于极致性能场景。指针在 IOmap 重新映射后会失效, 请确保仅在 PDO 循环期间使用.

input_slice() / output_slice()

pub unsafe fn input_slice(&self) -> &[u8]
pub unsafe fn output_slice(&self) -> &mut [u8]

获取输入 / 输出数据的零拷贝切片, 直接指向 IOmap 内存.

示例:

unsafe {
let input = pdo.input_slice();
let status_word = u16::from_le_bytes([input[0], input[1]]);

let output = pdo.output_slice();
output[0..2].copy_from_slice(&0x000Fu16.to_le_bytes());
}

inputs_slice_mapping() / outputs_slice_mapping()

pub unsafe fn inputs_slice_mapping(&self, offset: usize, size: usize) -> &[u8]
pub unsafe fn outputs_slice_mapping(&self, offset: usize, size: usize) -> &mut [u8]

获取指定偏移位置的零拷贝切片映射. 用于 MDP 模块化设备, 每个模块在 IOmap 中有不同偏移.

参数:

  • offset (usize) — 相对于从站输入 / 输出起始位置的字节偏移
  • size (usize) — 切片大小 (字节)

示例:

unsafe {
let mod1_input = pdo.inputs_slice_mapping(0, 8);
let mod2_input = pdo.inputs_slice_mapping(8, 6);
}

bind_pdo_struct()

pub unsafe fn bind_pdo_struct<T>(&self, is_input: bool) -> Option<*mut T>

将 PDO 映射绑定到用户结构体指针 (零拷贝), 实现结构化访问.

参数:

  • is_input (bool) — true=输入 (TxPDO), false=输出 (RxPDO)

返回值:

  • Option<*mut T> — 成功返回结构体指针, 失败返回 None

示例:

#[repr(C, packed)]
struct ServoInput {
status_word: u16,
actual_position: i32,
actual_velocity: i32,
}

#[repr(C, packed)]
struct ServoOutput {
control_word: u16,
target_position: i32,
}

unsafe {
if let Some(input_ptr) = pdo.bind_pdo_struct::<ServoInput>(true) {
println!("位置: {}", (*input_ptr).actual_position);
}
if let Some(output_ptr) = pdo.bind_pdo_struct::<ServoOutput>(false) {
(*output_ptr).control_word = 0x000F;
(*output_ptr).target_position = 10000;
}
}

模块级零拷贝指针:

pub fn get_input_data_pointer(master_index: u16, slave_index: u16) -> *mut u8
pub fn get_output_data_pointer(master_index: u16, slave_index: u16) -> *mut u8

模块级函数, 获取从站输入 / 输出数据的直接指针, 失败时返回空指针。

PDO 管理器 (PdoManager):

pub fn new(master_index: u16) -> Self
pub fn avg_cycle_time_ns(&self) -> u64 // 平均 PDO 周期 (ns), 0=未采样
pub fn error_count(&self) -> u32 // 累计 PDO 错误数
pub fn is_valid(&self) -> bool // 至少收过一帧且 WKC 正确
pub fn changed(&self) -> bool // 自上次调用以来输入是否变化
pub fn expected_size(&self) -> u32 // 整网 PDO 预期总字节数

PdoManager 提供主站级 PDO 统计信息, 接口轻量, 适合在 PDO 回调中调用; 完整诊断 (帧频 / 抖动 / 端口错误 / 拓扑 / 冗余等) 请用 master.diagnostics_info().

use ethercat::PdoManager;

let pdo_mgr = PdoManager::new(master.index());
if !pdo_mgr.is_valid() {
eprintln!("PDO 尚未稳定, 跳过本周期");
return Ok(());
}
println!("平均周期: {} ns", pdo_mgr.avg_cycle_time_ns());
println!("错误累计: {}", pdo_mgr.error_count());
if pdo_mgr.changed() {
refresh_ui();
}

PDO 监控 (PdoMonitor): 按字节范围监视 PDO 输入变化, 周期性检查并报告变化。

use ethercat::PdoMonitor;

let mut monitor = PdoMonitor::new(master.index());
monitor.watch(1, "statusword", 0, 2); // 监视从站1偏移0的2字节

// PDO 回调中检查变化
let changes = monitor.check();
for change in &changes {
println!("从站 {} '{}' 变化: {:?} -> {:?}",
change.slave_index, change.name, change.old_value, change.new_value);
}

完整示例:

use ethercat::{EtherCATMaster, EcState, SlavePdo};

let slave = master.slave(1);

// ===== 类型化读写(推荐) =====
let sw: u16 = slave.read_input_u16(0);
println!("状态字: 0x{:04X}", sw);

slave.write_output_u16(0, 0x000F); // ControlWord
slave.write_output_i32(2, 10000); // TargetPosition

// ===== 字节数组访问 =====
let pdo = SlavePdo::new(master.index(), 1);
let input_data = pdo.inputs();
println!("输入数据: {:?}", input_data);

// ===== 直接读写 =====
let mut buf = vec![0u8; 10];
pdo.read_input_data_direct(&mut buf, 0, 0);

// ===== 零拷贝结构体绑定 =====
#[repr(C, packed)]
struct ServoInput {
status_word: u16,
actual_position: i32,
}

unsafe {
if let Some(ptr) = pdo.bind_pdo_struct::<ServoInput>(true) {
println!("位置: {}", (*ptr).actual_position);
}
}

// ===== 原始指针(高性能) =====
{
let _guard = master.iomap_guard();
let (_, out_ptr, _, in_ptr) = slave.io()?;
unsafe {
let status = *(in_ptr as *const u16);
*(out_ptr as *mut u16) = 0x000F;
}
}