跳到主要内容

PDO 输入输出

通过 slave.pdo() 访问过程映像。​

PDO 就是过程映像里的一段数据,内核每周期自动收发。两条路径并存:结构体映射把过程映像当结构体读写(0 拷贝,持续控制用);元素视图按字节偏移取单个元素(每次调用取一份当前快照,一次性读用)。

PDO 周期与邮箱的关系

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

句柄与快照契约
  • 元素视图是 Copy 轻量句柄,自身不存任何数据字节:pdo.in_items().get(i) 只是把「从站 + 偏移 i」包成句柄,取它不读总线。
  • 一次访问器调用 = 一份快照:content() / as_int16() / get_bit() 每次都会复制一份该方向的完整帧再按偏移解析。
  • 同一个表达式里的两次调用可能落在两个 PDO 周期上。
  • 要多个字段同一拍,用一次 pdo.try_inputs() 取整帧再从这一份上解析。
  • in_items()(TxPDO,从站→主站)只读。
  • 写它的元素报 DarraError::PdoFailed(宽类型写出的 write_from_base_data() 例外,固定返回 false)。
  • 写过程数据用 out_items()(RxPDO)。
  • 多字节一律小端(EtherCAT 线序),本 SDK 不做任何字节颠倒。
  • 句柄借住主站:pdo 存活期间主站不能再被 set_state() / stop() 这类 &mut self 调用借用;drop(master) 之后再取用是编译错误,不是悬空引用。
  • 元素访问器不是 0 拷贝路径(每次调用复制完整帧);0 拷贝入口逐条列在下面「零拷贝入口」一节。

零拷贝入口​

下面这些入口把过程映像钉在调用方手里(引用 / 切片 / 裸指针),调用本身不复制帧;本节之外的入口每次调用都复制。

  • inputs_mapping<T>() / outputs_mapping<T>() — &T / &mut T,要求 align_of::<T>() == 1,否则报 PdoFailed
  • input_slice() / output_slice() — 整段 &[u8] / &mut [u8]
  • inputs_slice_mapping(offset, size) / outputs_slice_mapping(offset, size) — 区间子切片
  • inputs_mapping_info() / outputs_mapping_info() — (*mut u8, usize),指针 + 本方向映像字节数
  • get_input_data_pointer() / get_output_data_pointer() — *mut u8 裸指针
  • input_pdo_struct<T>() — Option<*const T> 裸指针(只读,对齐不过返回 None)
  • output_pdo_struct<T>() — Option<*mut T> 裸指针(可写,对齐不过返回 None)
裸指针入口的对齐与方向

input_pdo_struct<T>() / output_pdo_struct<T>() 与 inputs_mapping<T>() / outputs_mapping<T>() 共用同一道对齐门:align_of::<T>() != 1 一律拒绝(裸指针入口返回 None、引用入口报 PdoFailed), 所以 T 必须是 #[repr(C, packed)],否则字段访问是未对齐访问。

方向由入口本身决定,不再靠 is_input 布尔量开关:输入入口只交回 *const T (写它没有意义 —— 主站每周期覆盖输入映像),要写过程映像请用 output_pdo_struct<T>() 或 outputs_mapping<T>()。

名字带 info / pointer 的四个入口给的也是裸指针,它们不查对齐, 过程映像换代(重进 OP)之后指向的地址不再有效。

read_input_data_direct() / write_output_data_direct() 不是 0 拷贝入口:前者先取一份完整帧快照, 再把区间拷进调用方的缓冲区;后者是版本化读改写(整帧快照 → 编码 → 提交),下个周期发出。

使用案例​

日常使用​

① 结构体映射(0 拷贝,持续用):进 OP 后映射一次,拿到钉在过程映像上的引用,之后当变量读写。

use darra_ethercat::{EcState, EtherCATMaster};

#[repr(C, packed)] // packed 必须:对齐必须是 1
struct InPdo { status_word: u16, actual_position: i32 }

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

let mut master = EtherCATMaster::builder()
.set_eni(r"C:\EtherCAT\MyProject.deni") // DENI 一行加载
.build()?
.master;
master.set_state(EcState::Operational)?; // 进 OP
master.start();

let pdo = master.slave(1).pdo(); // 句柄借住 master

// 映射一次:引用钉在过程映像上(0 拷贝)。unsafe 只包住这两处调用。
let (inputs, outputs) = unsafe {
(pdo.inputs_mapping::<InPdo>()?, pdo.outputs_mapping::<OutPdo>()?)
};

let pos = inputs.actual_position; // 读字段 = 读当前周期
let sw = inputs.status_word;
outputs.control_word = 0x000F; // 写字段 = 下个周期发出
outputs.target_position = pos + 1000;
println!("status=0x{sw:04X} pos={pos}");

② 元素视图(一次性读):不写结构体,只按字节偏移取元素。

let in_items = pdo.in_items();                      // TxPDO:只读
let sw: u16 = in_items.get(0)?.as_uint16()?; // 偏移 0 起 2 字节
let pos: i32 = in_items.get(2)?.as_int32()?; // 偏移 2 起 4 字节
let ready = in_items.get(6)?.as_bool()?; // 只有最低位为 1 才是 true

let out_items = pdo.out_items(); // RxPDO:可读写
out_items.get(0)?.set_uint16(0x000F)?; // 写:下个周期发出

周期变化​

映射取一次就够,同一份映射放进你自己的周期循环直接用:

loop {
outputs.control_word = if inputs.status_word & 0x0004 != 0 { 0x000F } else { 0x0006 };
outputs.target_position = inputs.actual_position;
std::thread::sleep(std::time::Duration::from_millis(1)); // 你自己的节奏
}

内核每周期搬运过程数据:循环里读到的永远是当前周期、写下的下个周期发出,不需要任何额外调用,也不要每圈重新映射。stop() 或 IOmap 换代后旧引用才作废,重进 OP 后重取一次即可。

结构体映射(零拷贝)​

inputs_mapping<T>()​

把输入过程映像(TxPDO)映射成钉在内核 IOmap 上的只读结构体引用 —— 本 SDK 的 0 拷贝路径之一。

pub unsafe fn inputs_mapping<'a, T>(&'a self) -> Result<&'a T>

返回值:

  • Result<&'a T> — 钉在过程映像上的只读引用(0 拷贝,不是快照);借用期绑定在句柄上,句柄又借住主站,故引用既出不了句柄作用域,也活不过主站

说明:

  • unsafe 契约:T 必须是 #[repr(C, packed)],字段顺序与从站 TxPDO 映射逐字节一致。
  • 对齐不是 1(漏写 packed)报 DarraError::PdoFailed。
  • 过程映像不可用(未进 OP / 未映射)报 PdoFailed。
  • size_of::<T>() 大于输入映像字节数(pdo.in_items().length())报 PdoFailed。
  • 映射结构体只能放定宽字段(整数、浮点、[u8; N])—— String / Vec / 引用会把指针叠在过程数据上,对齐检查拦不住。
  • packed 字段不能直接取引用:println!("{}", inputs.actual_position) 编译报错。
  • 先 let v = inputs.actual_position; 拷到局部变量再用。

示例:

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

let pdo = master.slave(1).pdo();
let inputs: &InPdo = unsafe { pdo.inputs_mapping::<InPdo>()? };
let sw = inputs.status_word; // 当前周期值

outputs_mapping<T>()​

把输出过程映像(RxPDO)映射成可写结构体引用,改字段即写过程映像。

pub unsafe fn outputs_mapping<'a, T>(&'a self) -> Result<&'a mut T>

返回值:

  • Result<&'a mut T> — 可写引用,钉在过程映像上(0 拷贝);赋值后下个周期发往从站

说明:

  • 布局与尺寸校验同 inputs_mapping<T>()。
  • 同一映像同一时刻只允许存在一个可写引用 / 可写切片:
    • outputs_mapping() / output_slice() / outputs_slice_mapping() 三者之间不能同时取两个。
    • 同一句柄取两次、或两个句柄各取一个都是别名 &mut,属未定义行为。
    • 请用作用域把它们串行化。
  • unsafe 契约同样要求布局与从站 RxPDO 逐字节一致。

示例:

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

let outputs: &mut OutPdo = unsafe { pdo.outputs_mapping::<OutPdo>()? };
outputs.target_position = 10000; // 下个周期发出
outputs.control_word = 0x000F;

input_slice() / output_slice()​

零拷贝整段:直接取钉在 IOmap 上的字节切片,不经过结构体。

pub unsafe fn input_slice<'a>(&'a self) -> &'a [u8]          // TxPDO 整段,只读
pub unsafe fn output_slice<'a>(&'a self) -> &'a mut [u8] // RxPDO 整段,可写

返回值:

  • &'a [u8] / &'a mut [u8] — 钉在过程映像上的整段切片;过程映像不可用时是空切片

说明: output_slice() 与 outputs_mapping() 同一份独占契约:同一映像同一时刻只允许一个可写切片 / 映射。

示例:

use darra_ethercat::slave::{read_le_i32, read_le_u16};

let frame: &[u8] = unsafe { pdo.input_slice() }; // 不拷贝,直接是过程映像
let sw = read_le_u16(frame, 0);
let pos = read_le_i32(frame, 2);
println!("status=0x{sw:04X} pos={pos}");

inputs_slice_mapping(offset, size) / outputs_slice_mapping(offset, size)​

按字节区间取零拷贝子切片,供多模块设备按段切分。

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

参数:

  • offset (usize) — 相对本从站本方向过程映像起始的字节偏移
  • size (usize) — 子切片的字节数

返回值:

  • &'a [u8] / &'a mut [u8] — 该区间的切片;offset + size 越界时返回空切片(不 panic)

说明: 可写子切片与 output_slice() 共享同一份独占契约。

示例:

// MDP 设备:模块 1 从偏移 0 开始,模块 2 从偏移 8 开始
let m1: &[u8] = unsafe { pdo.inputs_slice_mapping(0, 8) };
let m2: &mut [u8] = unsafe { pdo.outputs_slice_mapping(8, 8) };

直接访问(不映射也能用)​

下面这组调用与结构体映射彼此独立:不映射可以直接用,映射了也常并用(结构体放不下的字段用它解)。

in_items() / out_items()​

取本方向过程映像的按字节索引容器。

pub fn in_items(&self)  -> PdoArrayInstance<'m>   // TxPDO(从站→主站):只读
pub fn out_items(&self) -> PdoArrayInstance<'m> // RxPDO(主站→从站):可读可写

返回值:

  • PdoArrayInstance<'m> — 按字节索引的过程映像视图;length() 是本方向过程映像字节数,未进 OP / 未映射时为 0(此时任何索引都越界)

说明: 容器与元素视图都是 Copy 轻量句柄,不存数据字节。返回容器不返回 Result:拿不到长度就是 0。'm 与句柄同源,容器出不了主站作用域。

相关结构:

pub struct PdoArrayInstance<'m> { /* 从站 + 方向 + 长度;不存数据字节 */ }

impl<'m> PdoArrayInstance<'m> {
pub fn length(&self) -> usize; // 本方向过程映像字节数
pub fn is_input(&self) -> bool; // true = TxPDO(只读)
pub fn get(&self, index: usize) -> Result<PdoDataItem<'m>>; // 取第 index 字节的元素视图
pub fn set(&self, index: usize, value: u8) -> Result<()>; // 提交第 index 字节(仅 Out)
}

示例:

let in_items = pdo.in_items();
println!("输入映像 {} 字节", in_items.length());

let item = in_items.get(2)?; // 取视图不读总线
let pos = item.as_int32()?; // 到这里才取一份快照并按偏移解析

get(index)​

取第 index 字节处的元素视图 —— 它只持「从站 + 偏移 + 方向 + 边界」四要素,自身不存任何数据字节。

pub fn get(&self, index: usize) -> Result<PdoDataItem<'m>>

参数:

  • index (usize) — 相对本方向过程映像起始的字节偏移

返回值:

  • Result<PdoDataItem<'m>> — 元素视图(Copy 轻量句柄,零堆分配)

说明:

  • index 落在 [0, length()) 之外报 DarraError::InvalidParameter。
  • 取视图不读总线;读取发生在访问器调用时(每次一份快照),写入发生在 set_* 调用时(每次一次版本化提交,下个周期发出)。
  • 访问器里偏移 + 宽度越出本方向过程映像同样报 InvalidParameter;写 in_items() 方向的元素报 PdoFailed。

相关结构:

impl<'m> PdoDataItem<'m> {
pub fn offset(&self) -> usize; // 本元素在本方向过程映像里的字节偏移
pub fn is_input(&self) -> bool; // true = TxPDO(只读)

pub fn content(&self) -> Result<u8>; // 1 字节原始值(无符号 8 位)
pub fn set_content(&self, value: u8) -> Result<()>;
pub fn as_int8(&self) -> Result<i8>; // 有符号 8 位
pub fn set_int8(&self, value: i8) -> Result<()>;
pub fn as_uint8(&self) -> Result<u8>; // 与 content() 同一字节、同一宽度
pub fn set_uint8(&self, value: u8) -> Result<()>; // 与 set_content() 同义
pub fn as_int16(&self) -> Result<i16>; // 以下多字节均为小端
pub fn set_int16(&self, value: i16) -> Result<()>;
pub fn as_uint16(&self) -> Result<u16>;
pub fn set_uint16(&self, value: u16) -> Result<()>;
pub fn as_int32(&self) -> Result<i32>;
pub fn set_int32(&self, value: i32) -> Result<()>;
pub fn as_uint32(&self) -> Result<u32>;
pub fn set_uint32(&self, value: u32) -> Result<()>;
pub fn as_int64(&self) -> Result<i64>;
pub fn set_int64(&self, value: i64) -> Result<()>;
pub fn as_uint64(&self) -> Result<u64>;
pub fn set_uint64(&self, value: u64) -> Result<()>;
pub fn as_float(&self) -> Result<f32>; // IEEE754 单精度(小端线序)
pub fn set_float(&self, value: f32) -> Result<()>;
pub fn as_double(&self) -> Result<f64>; // IEEE754 双精度(小端线序)
pub fn set_double(&self, value: f64) -> Result<()>;
pub fn as_bool(&self) -> Result<bool>; // bit0 语义:只有最低位为 1 才是 true
pub fn set_bool(&self, value: bool) -> Result<()>; // true → 0x01、false → 0x00(整字节)

pub fn get_bit(&self, bit_index: u8) -> Result<bool>; // 0-7,0 = LSB
pub fn set_bit(&self, bit_index: u8, value: bool) -> Result<()>; // 仅 Out
}

as_bool() 只认最低位:字节 0x01 是 true,0x02 与 0x80 都读 false。

本 SDK 没有"非 0 即真"的第二个入口。要按整字节 0 / 非 0 判真假,读 content() 自行比较。

set_bool() 写的是整字节 0x01 / 0x00,不是位。

多字节一律小端(EtherCAT 线序):as_int16() / as_uint16() / … / as_double() 都是直接小端重解释,本 SDK 不做任何字节颠倒。

方向:只有 out_items() 的元素可写;in_items() 由主站每周期覆盖,写它报 DarraError::PdoFailed。

示例:

let in_items = pdo.in_items();
let sw = in_items.get(0)?.as_uint16()?; // 读:当前周期
let raw = in_items.get(0)?.content()?; // 同一个字节的原始值
let flag = in_items.get(3)?.as_bool()?; // bit0 语义:0x02 / 0x80 都是 false

let out_items = pdo.out_items();
out_items.get(2)?.set_int16(123)?; // 写:版本化提交,下个周期发出
out_items.set(0, 0x11)?; // 等价于 out_items.get(0)?.set_content(0x11)?

get_bit(bit_index) / set_bit(bit_index, value)​

按位读写元素偏移处的那个字节。

pub fn get_bit(&self, bit_index: u8) -> Result<bool>
pub fn set_bit(&self, bit_index: u8, value: bool) -> Result<()>

参数:

  • bit_index (u8) — 位下标 0-7,0 = LSB
  • value (bool) — 置位 / 清位

返回值:

  • bool — 该位当前值
  • () — 置位成功(版本化提交,下个周期发出)

说明:

  • 位访问器是按字节的,不是按宽度:bit_index 只寻址元素偏移处的那一个字节,与这个元素按什么宽度读无关。
    • get(2)?.get_bit(0)? 测的始终是字节 2 的最低位,即便你打算把同一个偏移当 as_int32() 读 4 字节。
  • u8 已排除负值,8..=255 报 DarraError::InvalidParameter。
  • set_bit() 是位域读改写:只覆盖目标位,同一字节的其余位保持原值。
  • 对 in_items() 方向的元素调用 set_bit() 报 PdoFailed(get_bit() 两个方向都可读)。

示例:

let out_items = pdo.out_items();
out_items.get(0)?.set_bit(0, true)?; // 只置 bit0,同字节其余位不动
out_items.get(0)?.set_bit(7, false)?; // 只清 bit7

let lsb = pdo.in_items().get(3)?.get_bit(0)?; // 0 = LSB

as_value(data_type) / write_value(data_type, value)​

按 EtherCAT 数据类型读写当前位置的值(小端)。

pub fn as_value(&self, data_type: EcDataType) -> Result<EcValue>
pub fn write_value(&self, data_type: EcDataType, value: &EcValue) -> Result<()>

参数:

  • data_type (EcDataType) — 该位置的映射类型;白名单恰好 11 种:Boolean、Integer8/16/32/64、Unsigned8/16/32/64、Real32、Real64
  • value (EcValue) — 要写入的值

返回值:

  • EcValue — 读出的值(Bool / I8 / U8 / I16 / U16 / I32 / U32 / I64 / U64 / F32 / F64)
  • () — 写入成功

说明:

  • 白名单外的类型(位串 Bit1..Bit8、Integer24/40/48/56、Unsigned24/40/48/56、TimeOfDay / TimeDifference、字符串、Domain)报 DarraError::InvalidParameter。
    • 不猜宽度、不静默按 1 字节读。
  • 白名单外的类型请走 as_base_data():宽度 = native 数据类型表位宽向上取整(Integer24 3 字节、Integer40 / Unsigned40 5 字节、Integer48 / Unsigned48 / TimeOfDay / TimeDifference 6 字节、Integer56 / Unsigned56 7 字节、位串 1 字节)。
  • 写入时值按标量转换(浮点四舍六入五取偶取整;布尔目标接受 EcValue::Bool 或任意整数 / 浮点,0 = false、非 0 = true,再落 bit0 整字节语义)。
  • EcValue::String / EcValue::Bytes 是显式的非标量变体,一律报错 —— 本 SDK 不提供"把字符串当数字写进 PDO"的隐式路径。
  • 仅 out_items() 的元素可写。

示例:

use darra_ethercat::{EcDataType, EcValue};

let out_items = pdo.out_items();
out_items.get(4)?.write_value(EcDataType::Real32, &EcValue::F32(3.5))?;

let v: EcValue = pdo.in_items().get(4)?.as_value(EcDataType::Real32)?;
if let EcValue::F32(temp) = v {
println!("温度 {temp}");
}

// 位串没有定宽标量对标:报错而不是按 1 字节猜
if let Err(e) = pdo.in_items().get(0)?.as_value(EcDataType::Bit3) {
eprintln!("不支持的数据类型: {e}");
}

as_base_data(data_type) / write_from_base_data(data)​

宽类型出口:as_value() / write_value() 只覆盖 11 种定宽标量,位串、变长字段用这一对进出。

pub fn as_base_data(&self, data_type: EcDataType) -> Result<BaseData>
pub fn write_from_base_data(&self, data: &BaseData) -> bool

参数:

  • data_type (EcDataType) — 该位置的映射类型;字节宽度 = native 数据类型表位宽向上取整(Integer40 / Unsigned40 5 字节,Integer48 / Unsigned48 / TimeOfDay / TimeDifference 6 字节,Integer56 / Unsigned56 7 字节);变长(字符串 / Domain)与 native 表未定义的类型按 1 字节取
  • data (BaseData) — 要写回的整段原始字节

返回值:

  • BaseData — 按 data_type 宽度从当前位置拷出的原始字节包装
  • bool — 是否写成功;失败返回 false(不抛),in_items() 方向恒返回 false

说明:

  • 取字节的宽度与 BaseData 容器的宽度出自同一处:定宽类型下二者必然相等,变长类型下容器接受任意长度。所以"取出来的字节写不进容器"这类失败不会发生。
  • 值形态:TimeOfDay / TimeDifference 与 Integer40/48/56 给 EcValue::I64,Unsigned40/48/56 给 EcValue::U64,位串给 EcValue::Bool(bit0 语义);变长类型给 EcValue::String / EcValue::Bytes。
  • TimeOfDay / TimeDifference 是 6 字节 48 bit:低 32 位是自午夜起的毫秒、高 16 位是自 1984-01-01 起的天数(小端),按无符号 48 位读;容器里 read() 给的是 EcValue::I64,承载的就是这个打包值。
  • as_base_data() 在偏移 + 宽度越出本方向过程映像时报 DarraError::InvalidParameter,快照不可用报 DarraError::PdoFailed。
  • write_from_base_data() 走输出方向的版本化提交,下个周期发出。
  • 任何失败都以 false 返回,不给调用方添一条异常分支。

示例:

use darra_ethercat::EcDataType;

let wide = pdo.in_items().get(0)?.as_base_data(EcDataType::Integer24)?; // 24 位整数:定宽标量覆盖不到
if !pdo.out_items().get(0)?.write_from_base_data(&wide) {
eprintln!("宽类型写回被拒绝"); // 失败返回 false,不抛
}

inputs() / outputs() / write_outputs(data)​

按字节访问过程映像整帧。

pub fn inputs(&self)  -> Vec<u8>                    // 输入整帧:失败折叠为空 Vec
pub fn try_inputs(&self) -> Result<Vec<u8>> // 输入整帧:失败返回 Err
pub fn outputs(&self) -> Vec<u8> // 输出整帧:失败折叠为空 Vec
pub fn try_outputs(&self) -> Result<Vec<u8>>
pub fn write_outputs(&self, data: &[u8]) -> bool // 提交完整输出帧
pub fn try_write_outputs(&self, data: &[u8]) -> Result<()>

参数:

  • data (&[u8]) — 完整输出帧;长度必须与本方向输出映像逐字节相等

返回值:

  • Vec<u8> — 快照,不是 0 拷贝:同一拍一致,拷出来不跟着总线变
  • bool / () — 是否写成功

说明:

  • 一次调用 = 一份完整帧:同一份快照上的多个字段必然同一拍,这是多字段一致口径的取法(配合 read_le_u16() 一族解析)。
  • write_outputs() 不做局部补丁:帧长与输出映像不符、或版本化提交失败都会失败。

示例:

use darra_ethercat::slave::{read_le_i32, read_le_u16};

let frame = pdo.try_inputs()?; // 一份完整输入帧快照
let sw = read_le_u16(&frame, 0); // 与下面一行必然同一拍
let pos = read_le_i32(&frame, 2);
println!("status=0x{sw:04X} pos={pos}");

let mut out = pdo.try_outputs()?; // 整帧读改写
if out.len() >= 2 {
out[0..2].copy_from_slice(&0x000F_u16.to_le_bytes());
}
if !pdo.write_outputs(&out) {
eprintln!("整帧提交失败");
}

read_le_u16() / read_le_u32() / read_le_i16() / read_le_i32()​

从一份帧快照里按字节偏移解析定宽小端值。

pub fn read_le_u16(data: &[u8], offset: usize) -> u16
pub fn read_le_u32(data: &[u8], offset: usize) -> u32
pub fn read_le_i16(data: &[u8], offset: usize) -> i16
pub fn read_le_i32(data: &[u8], offset: usize) -> i32

参数:

  • data (&[u8]) — 一份帧快照(例如 pdo.try_inputs()? 的结果)
  • offset (usize) — 帧内字节偏移

返回值:

  • u16 / u32 / i16 / i32 — 小端解析结果;偏移越界或不可表示时折叠为 0

说明:

  • 声明在 darra_ethercat::slave 下(不在 crate 根)。
  • 配合一次 try_inputs() / try_outputs() 使用,多个字段从同一份快照里解析,它们必然同一拍。
  • 这是元素视图(每次调用一份新快照)给不了的口径。

示例:

use darra_ethercat::slave::{read_le_i32, read_le_u16};

let frame = pdo.try_inputs()?; // 一次快照
let sw = read_le_u16(&frame, 0); // 同一拍的 StatusWord
let pos = read_le_i32(&frame, 2); // 同一拍的 ActualPosition
println!("status=0x{sw:04X} pos={pos}");

read_input_() / write_output_()(定宽快捷入口)​

在从站句柄 slave = master.slave(i) 上按字节偏移直接读输入 / 写输出的定宽快捷入口 —— 不写结构体、不取元素视图。

// slave = master.slave(i):读走 native 直读通道,写返回 Result
pub fn read_input_u8(&self, offset: u32) -> u8
pub fn read_input_i8(&self, offset: u32) -> i8
pub fn read_input_i16(&self, offset: u32) -> i16
pub fn read_input_u16(&self, offset: u32) -> u16
pub fn read_input_i32(&self, offset: u32) -> i32
pub fn read_input_u32(&self, offset: u32) -> u32
pub fn read_input_f32(&self, offset: u32) -> f32
pub fn write_output_u8(&self, offset: u32, value: u8) -> Result<()>
pub fn write_output_i8(&self, offset: u32, value: i8) -> Result<()>
pub fn write_output_i16(&self, offset: u32, value: i16) -> Result<()>
pub fn write_output_u16(&self, offset: u32, value: u16) -> Result<()>
pub fn write_output_i32(&self, offset: u32, value: i32) -> Result<()>
pub fn write_output_u32(&self, offset: u32, value: u32) -> Result<()>
pub fn write_output_f32(&self, offset: u32, value: f32) -> Result<()>

参数:

  • offset (u32) — 本方向过程映像内的字节偏移
  • value (u8 / i8 / i16 / u16 / i32 / u32 / f32) — 要写入的定宽值

返回值:

  • u8 / i8 / i16 / u16 / i32 / u32 / f32 — 小端解析结果;帧不可用或偏移越界时折叠为 0
  • Result<()> — 写成功 Ok(()),失败报 Err

说明:

  • 读走 native 直读通道(不经整帧拷贝):单字段轮询用它最省。
  • 读失败折叠为 0,调用方看不到原因 —— 要区分「真值 0」与「越界」请改用元素视图(报 Err)或 read_le_u16() 一族。
  • 写是局部补丁:一次调用只动目标字段;要多字段同一事务用元素视图,或整帧 try_inputs() → 改 → write_outputs()。

示例:

let slave = master.slave(1);

let sw = slave.read_input_u16(0); // 越界时折叠为 0
slave.write_output_u16(0, 0x000F)?; // 失败报 Err
let temp = slave.read_input_f32(10); // 单字段轮询:native 直读,不整帧拷贝

read_string() / write_string()(过程映像句柄)​

字符串入口在过程映像句柄 pdo = master.slave(i).pdo() 上:

pub fn read_string(&self, offset: u32, max_len: u32) -> String
pub fn write_string(&self, offset: u32, value: &str, max_len: u32) -> bool

参数:

  • offset (u32) — 本方向过程映像内的字节偏移
  • max_len (u32) — read_string() 最多读多少字节;write_string() 允许写入的字节上限

返回值:

  • String — 遇 \0 截断的字符串(按 UTF-8 解码,非 UTF-8 时按 Latin-1 逐字节解码);读不到时返回空串
  • bool — 写成功返回 true;超出 max_len 或提交失败返回 false

示例:

let pdo = master.slave(1).pdo();
let name = pdo.read_string(6, 16); // 遇 \0 截断,最多 16 字节
pdo.write_string(6, "axis-1", 16);

完整示例​

use darra_ethercat::slave::{read_le_i32, read_le_u16};
use darra_ethercat::{EcState, EtherCATMaster};

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

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

fn main() -> darra_ethercat::Result<()> {
let mut master = EtherCATMaster::builder()
.set_eni(r"C:\EtherCAT\MyProject.deni") // DENI 一行加载
.build()?
.master;
master.set_state(EcState::Operational)?; // 进 OP
master.start();

let pdo = master.slave(1).pdo(); // 句柄借住 master

// 0 拷贝:映射一次,引用钉在过程映像上(unsafe 只包住这两处调用)
let (inputs, outputs) = unsafe {
(pdo.inputs_mapping::<InPdo>()?, pdo.outputs_mapping::<OutPdo>()?)
};

// 元素视图:每次访问器调用各取一份快照,两次调用可能跨在两个 PDO 周期上
let in_items = pdo.in_items();
let sw_point = in_items.get(0)?.as_uint16()?;
let pos_point = in_items.get(2)?.as_int32()?;

// 一次快照解析多字段:下面三个值必然同一拍
let frame = pdo.try_inputs()?;
let sw = read_le_u16(&frame, 0);
let pos = read_le_i32(&frame, 2);
println!("单点 {sw_point}/{pos_point},整帧 {sw}/{pos}");

// 周期循环:映射引用与元素视图可以并用
for _ in 0..10 {
let cw = if inputs.status_word & 0x0004 != 0 { 0x000F_u16 } else { 0x0006_u16 };
outputs.control_word = cw; // 写字段:下个周期发出
outputs.target_position = pos;
pdo.out_items().get(0)?.set_bit(0, true)?; // 位级补丁:同字节其余位不动

let mapped_sw = inputs.status_word; // packed 字段先拷到局部变量再格式化
let mapped_pos = inputs.actual_position;
println!("status=0x{mapped_sw:04X} pos={mapped_pos}");
std::thread::sleep(std::time::Duration::from_millis(1));
}

Ok(()) // master 析构:自动停止
}