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 darra_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) {
// #[repr(C, packed)] 字段不能直接在 println! 取引用, 先拷贝到局部变量
let pos = (*input_ptr).actual_position;
println!("位置: {}", pos);
}
if let Some(output_ptr) = pdo.bind_pdo_struct::<ServoOutput>(false) {
// 写 packed 字段用 ptr::addr_of_mut! + write_unaligned, 避免未对齐引用
std::ptr::addr_of_mut!((*output_ptr).control_word).write_unaligned(0x000F);
std::ptr::addr_of_mut!((*output_ptr).target_position).write_unaligned(10000);
}
}
#[repr(C, packed)] 结构体的字段可能未对齐,不能直接取引用(println!("{}", (*p).field) 会触发
unaligned_references 报错)。读取时先 let v = (*p).field; 拷贝到局部变量;写入时用
std::ptr::addr_of_mut!((*p).field).write_unaligned(v)。
模块级零拷贝指针:
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 统计: 主站级 PDO 帧频 / 抖动 / 周期 / 错误等统计请用 master.diagnostics_info()(见 主站诊断)。例如 master.diagnostics_info().avg_cycle_time_us()、packet_loss_rate()、error_cnt()。
PDO 监控 (PdoMonitor): 按字节范围监视 PDO 输入变化, 周期性检查并报告变化。
use darra_ethercat::PdoMonitor;
let mut monitor = PdoMonitor::new(master.index());
monitor.watch(1, "statusword", 0, 2); // 监视从站1偏移0的2字节
// PDO 回调中检查变化 (PdoChange 字段: watch_name / slave_index / old_data / new_data)
let changes = monitor.check();
for change in &changes {
println!("从站 {} '{}' 变化: {:?} -> {:?}",
change.slave_index, change.watch_name, change.old_data, change.new_data);
}
完整示例:
use darra_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) {
// packed 字段先拷贝到局部变量再用
let pos = (*ptr).actual_position;
println!("位置: {}", pos);
}
}
// ===== 原始指针(高性能) =====
{
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;
}
}