PDO 输入输出
通过 slave.pdo() 访问过程映像。
PDO 就是过程映像里的一段数据,内核每周期自动收发。两条路径并存:结构体映射把过程映像当结构体读写(0 拷贝,持续控制用);元素视图按字节偏移取单个元素(每次调用取一份当前快照,一次性读用)。
邮箱通信(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,否则报PdoFailedinput_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 = LSBvalue(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、Real64value(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 数据类型表位宽向上取整(Integer243 字节、Integer40/Unsigned405 字节、Integer48/Unsigned48/TimeOfDay/TimeDifference6 字节、Integer56/Unsigned567 字节、位串 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/Unsigned405 字节,Integer48/Unsigned48/TimeOfDay/TimeDifference6 字节,Integer56/Unsigned567 字节);变长(字符串 /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— 小端解析结果;帧不可用或偏移越界时折叠为0Result<()>— 写成功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 析构:自动停止
}