Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions src/master.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@
}

impl Master {
/// Open an EtherCAT master device, that is `/dev/EtherCAT{idx}`,
/// and check if the version of the kernel module matches the expected version.
///
/// This call is the equivalent of `ecrt_open_master(idx)` in the C API.
/// It does NOT reserve the master instance. Use `reserve()` for that.
///
/// # Arguments
///
/// * `idx` - The index of the master to open. The first master is `idx = 0`, the second `idx = 1`, etc.
/// * `access` - The access level for the master (MasterAccess::ReadOnly or MasterAccess::ReadWrite).
///
/// # Returns
///
/// * `Result<Self>` - A result containing the master instance if successful, or an error.
pub fn open(idx: MasterIdx, access: MasterAccess) -> Result<Self> {
let devpath = format!("/dev/EtherCAT{}", idx);
log::debug!("Open EtherCAT Master {}", devpath);
Expand All @@ -64,24 +78,66 @@
Ok(master)
}

/// Determine how many EtherCAT masters are available in the system.
/// For example, if only /dev/EtherCAT0 exists, this returns 1.
///
/// # Returns
///
/// * `Result<usize>` - A result containing the number of available masters if successful, or an error.
pub fn master_count() -> Result<usize> {
let master = Self::open(0, MasterAccess::ReadOnly)?;
let mut module_info = ec::ec_ioctl_module_t::default();
ioctl!(master, ec::ioctl::MODULE, &mut module_info)?;
Ok(module_info.master_count as usize)
}

/// Reserve an EtherCAT master for realtime use.
/// You need to call this before using any domain functions or PDOs
///
/// This is the equivalent of `ecrt_master_reserve()` in the C API.
///
/// # Returns
///
/// * `Result<()>` - A result indicating success or failure.
pub fn reserve(&self) -> Result<()> {
log::debug!("Reserve EtherCAT Master");
ioctl!(self, ec::ioctl::REQUEST)?;
Ok(())
}

/// Create a new process data domain for the given master.
/// You must `reserve()` the master before calling this function.
/// You must create at least one domain before calling `ecrt_master_activate()`.
///
/// The resulting domain index can be using in Master::domain() to create a `Domain` instance.
///
/// Refer to the [EtherLab EtherCAT master documentation](https://docs.etherlab.org/ethercat/1.6/pdf/ethercat_doc.pdf)
/// for more information on how domains are intended to be used in an application.
///
/// This is the equivalent of `ecrt_master_create_domain()` in the C API.
///
/// # Returns
///
/// * `Result<Self>` - A result with the domain index if successful, or an error.
pub fn create_domain(&self) -> Result<DomainIdx> {
Ok((ioctl!(self, ec::ioctl::CREATE_DOMAIN)? as usize).into())
}

/// Create a `Domain` object from a given domain index
/// This does NOT create a new domain in the master, use `master.create_domain()` for that.
///
/// You MUST pass a valid domain index previously created with `master.create_domain()`.
/// If the domain index is invalid, this function will still return a `Domain` object,
/// but subsequent calls with said `Domain` object will fail.
///
/// # Arguments
///
/// * `idx` - The index of the domain, i.e. the value returned by `master.create_domain()`.
///
/// # Returns
///
/// * `Domain` - A `Domain` object representing the specified domain index.
pub const fn domain(&self, idx: DomainIdx) -> Domain {

Check failure on line 140 in src/master.rs

View workflow job for this annotation

GitHub Actions / build (nightly)

hiding a lifetime that's elided elsewhere is confusing

Check failure on line 140 in src/master.rs

View workflow job for this annotation

GitHub Actions / build (stable)

hiding a lifetime that's elided elsewhere is confusing
Domain::new(idx, self)
}

Expand All @@ -108,6 +164,22 @@
})
}

/// Activate a previously reserved EtherCAT master.
/// You MUST reserve the master using `master.reserve()` before calling this function.
/// You MUST create at least one domain using `master.create_domain()` before calling this function.
///
/// This is the equivalent of `ecrt_master_activate()` in the C API.
///
/// Calling this function indicates to the kernel module that the master configuration
/// phase is finished and the realtime operation can begin.
///
/// After calling this function, you are responsible for cyclically calling `master.send()` and `master.receive()`,
/// with the appropriate timing for your application.
///
/// # Returns
///
/// * `Result<()>` - A result indicating success or failure.
///
pub fn activate(&mut self) -> Result<()> {
log::debug!("Activate EtherCAT Master");
let mut data = ec::ec_ioctl_master_activate_t::default();
Expand Down Expand Up @@ -135,6 +207,11 @@
ioctl!(self, ec::ioctl::SET_SEND_INTERVAL, &interval_us).map(|_| ())
}

/// Send queued data frames to the EtherCAT slaves.
/// You MUST call this function cyclically, after
/// `master.activate()` has been called.
///
/// This is the similar of `ecrt_master_send()` in the C API.
pub fn send(&mut self) -> Result<usize> {
let mut sent = 0;
ioctl!(self, ec::ioctl::SEND, &mut sent as *mut _ as c_ulong)?;
Expand All @@ -149,6 +226,20 @@
ioctl!(self, ec::ioctl::RESET).map(|_| ())
}

// Query the current state of the EtherCAT master.
// The resulting structure contains the following information:
// .slaves_responding: Number of slaves currently responding to the master.
// .al_states: Sum of the AL states of all slaves.
// .link_up: True if AT LEAST ONE EtherCAT link is up
//
// This function is the equivalent of `ecrt_master_state()` in the C API.
//
// If you need to query the state of a specific device (i.e. primary or backup device),
// use `master.link_state()` instead.
// If you don't have a backup device, both functions return the same result.
//
// # Returns
// * `Result<MasterState>` - A result containing the current state of the EtherCAT master.
pub fn state(&self) -> Result<MasterState> {
let mut data = ec::ec_master_state_t::default();
ioctl!(self, ec::ioctl::MASTER_STATE, &mut data)?;
Expand All @@ -159,6 +250,25 @@
})
}

/// Query the state of a specific EtherCAT master link.
/// A "link" is either the primary EtherCAT interface or a backup interface.
/// The resulting MasterState structure contains the following information:
/// .slaves_responding: Number of slaves currently responding to the master on this link (=device)
/// .al_states: Sum of the AL states of all slaves on this link (=device)
/// .link_up: True if the Ethernet link is up for this link (=device)
///
/// This function is the equivalent of `ecrt_master_link_state()` in the C API.
///
/// If you don't care about the state of specific interfaces,
/// (such as if you don't have a backup device configured),
/// use `Master::state()` instead.
///
/// # Arguments
///
/// * `dev_idx` - 0 for the primary device, 1 for the first backup device, etc.
///
/// # Returns
/// * `Result<MasterState>` - A result containing the state of the specified link.
pub fn link_state(&self, dev_idx: u32) -> Result<MasterState> {
let mut state = ec::ec_master_link_state_t::default();
let mut data = ec::ec_ioctl_link_state_t {
Expand Down Expand Up @@ -265,7 +375,7 @@
})
}

pub fn configure_slave(&mut self, addr: SlaveAddr, expected: SlaveId) -> Result<SlaveConfig> {

Check failure on line 378 in src/master.rs

View workflow job for this annotation

GitHub Actions / build (nightly)

hiding a lifetime that's elided elsewhere is confusing

Check failure on line 378 in src/master.rs

View workflow job for this annotation

GitHub Actions / build (stable)

hiding a lifetime that's elided elsewhere is confusing
log::debug!("Configure slave {:?}", addr);
let mut data = ec::ec_ioctl_config_t::default();
let (alias, pos) = addr.as_pair();
Expand Down Expand Up @@ -343,6 +453,19 @@
})
}

// Download a value to a given SDO on a slave.
// Essentially, this allows writing a SDO value to a slave.
//
// This is a *blocking* call. It may not be used in realtime contexts,
// since it will block the master until the SDO download is complete.
//
// This is equivalent to the C API function `ecrt_master_sdo_download()`.
//
// # Arguments
// * `position` - The position of the slave in the EtherCAT ring (0 = first slave)
// * `sdo_idx` - The index and subindex of the SDO to download.
// * `complete_access` - Whether to use complete access for the SDO download (only used in Synapticon branch)
// * `data` - A data buffer containing the value to download to the SDO. Must implement `data.data_ptr()`.
pub fn sdo_download<T>(
&mut self,
position: SlavePos,
Expand Down Expand Up @@ -371,6 +494,20 @@
ioctl!(self, ec::ioctl::SLAVE_SDO_DOWNLOAD, &mut data).map(|_| ())
}

// Make a slave upload the value of a given SDO to the master.
// Essentially, this allows reading a SDO value from a slave.
// This function may be called before activating the master.
//
// This is a *blocking* call. It may not be used in realtime contexts,
// since it will block the master until the SDO upload is complete.
//
// Calling this is equivalent to the C API function `ecrt_master_sdo_upload()`.
//
// # Arguments
// * `position` - The position of the slave in the EtherCAT ring (0 = first slave)
// * `sdo_idx` - The index and subindex of the SDO to upload.
// * `complete_access` - Whether to use complete access for the SDO upload (only used in Synapticon branch)
// * `target` - A mutable reference to the buffer where the uploaded data will be stored. This buffer must be large enough to hold the SDO data.
pub fn sdo_upload<'t>(
&self,
position: SlavePos,
Expand Down Expand Up @@ -471,6 +608,13 @@
})
}

/// Request that a specific slave (identified by slave_pos) changes
/// its state to the specified AL state.
/// For example, request the slave to go to Operational state.
///
/// If you want to QUERY the current state of a slave, use `get_slave_info()` instead.
///
/// This is the equivalent of the C API function `ec_slave_request_state()`.
pub fn request_state(&mut self, slave_pos: SlavePos, state: AlState) -> Result<()> {
let mut data = ec::ec_ioctl_slave_state_t::default();
data.slave_position = u16::from(slave_pos);
Expand All @@ -487,16 +631,43 @@
Ok(())
}

/// When operating in distributed clock mode, set the application time
/// that will be used as reference for the next cycle.
///
/// The time is defined as nanoseconds since 2000-01-01 00:00:00 UTC.
/// Typically, you would get this time from a high-resolution system clock.
pub fn set_application_time(&mut self, app_time: u64) -> Result<()> {
ioctl!(self, ec::ioctl::APP_TIME, &app_time)?;
Ok(())
}

/// Puts a DC reference clock drift compensation EtherCAT datagram into
/// the send queue. This datagram will be sent in the next
/// `master.send()` call.
///
/// You MUST call `master.set_application_time()` before calling this function,
/// to set the desired application time for the next cycle.
/// Typically, you would call `master.set_application_time()`
/// EVERY time before calling `master.sync_reference_clock()`.
///
/// See the [Beckhoff documentation](https://infosys.beckhoff.com/english.php?content=../content/1033/ethercatsystem/2469118347.html&id=)
/// on EtherCAT distributed clocks for more details on how this process works.
///
/// See also `master.sync_slave_clocks()`.
pub fn sync_reference_clock(&mut self) -> Result<()> {
ioctl!(self, ec::ioctl::SYNC_REF)?;
Ok(())
}

/// Puts a DC clock drift compensation EtherCAT datagram into
/// the send queue. This datagram will be sent in the next
/// `master.send()` call.
///
/// A logical prerequisitie for this function is that you have already called
/// `master.sync_reference_clock()`.
///
/// See the [Beckhoff documentation](https://infosys.beckhoff.com/english.php?content=../content/1033/ethercatsystem/2469118347.html&id=)
/// on EtherCAT distributed clocks for more details on how this process works.
pub fn sync_slave_clocks(&mut self) -> Result<()> {
ioctl!(self, ec::ioctl::SYNC_SLAVES)?;
Ok(())
Expand Down Expand Up @@ -710,6 +881,16 @@
})
}

/// Configure this slave's Distributed Clocks (DC) settings
///
/// # Arguments
/// * `assign_activate` - DC assign and activate settings.
/// This is a device-specific parameter, which can be found in the
/// device's ESI XML file. If you can't find it, 0x0300 is a common choice.
/// * `sync0_cycle_time` - Cycle time for Sync0 in nanoseconds, i.e. how often a SYNC0 signal is sent.
/// * `sync0_shift_time` - Shift time for Sync0 in nanoseconds
/// * `sync1_cycle_time` - Cycle time for Sync1 in nanoseconds, see above
/// * `sync1_shift_time` - Shift time for Sync1 in nanoseconds
pub fn config_dc(
&mut self,
assign_activate: u16,
Expand Down
5 changes: 5 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

impl From<Error> for io::Error {
fn from(e: Error) -> Self {
io::Error::new(io::ErrorKind::Other, e)

Check failure on line 33 in src/types.rs

View workflow job for this annotation

GitHub Actions / build (nightly)

this can be `std::io::Error::other(_)`

Check failure on line 33 in src/types.rs

View workflow job for this annotation

GitHub Actions / build (stable)

this can be `std::io::Error::other(_)`
}
}

Expand Down Expand Up @@ -60,9 +60,14 @@
pub revision_number: u32,
pub serial_number: u32,
}

Check warning on line 63 in src/types.rs

View workflow job for this annotation

GitHub Actions / fmt

Diff in /home/runner/work/ethercat/ethercat/src/types.rs
/// An EtherCAT slave, which is specified either by absolute position in the
/// ring or by offset from a given alias.
///
/// Example for slave address by position
/// ```rust
/// let myslave_addr = SlaveAddr::ByPos(2); // third slave
/// ```
#[derive(Debug, Clone, Copy)]
pub enum SlaveAddr {
ByPos(u16),
Expand Down
Loading