PDO 输入输出
通过 slave.io() 获取原始指针, 或使用 slave.read_input_*() / slave.write_output_*() 类型化访问。
邮箱通信自动附带在 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;
}
}