-
Notifications
You must be signed in to change notification settings - Fork 2
EL6021
Driver: devices/beckhoff_modules/el6021.rs
io trait: io/serial_interface.rs (SerialInterfaceDevice)
The EL6021 is a one-channel serial interface terminal. Bytes you want to transmit go out in the RxPDO, and received bytes come back in the TxPDO. Each handshake moves at most 22 bytes, and the terminal and the master acknowledge each other with toggle bits. Because each step of the handshake needs a cycle to reach the terminal and another for the answer to come back, the driver's API is a set of non-blocking calls that you make once per cycle.
| Struct | EL6021 |
| Identities |
EL6021_IDENTITY_A…_D (product 0x17853052; revisions 0x150000, 0x140000, 0x160000, 0x100000) |
| Config |
EL6021Configuration, applied with ConfigurableDevice::write_config
|
| Ports | one (EL6021Port::SI1). The port argument of every trait method is ignored. |
Needs input_post_process/output_pre_process
|
No, both are no-ops |
Only the Standard 22 Byte MDP 600 layout is implemented. It is a 24-byte PDO in each direction:
| Byte | TxPDO 0x1A02 (terminal → master): Standard22ByteMdp600Input
|
RxPDO 0x1602 (master → terminal): Standard22ByteMdp600Output
|
|---|---|---|
| 0 |
status: bit 0 transmit_accepted, 1 receive_request, 2 init_accepted, 3 buffer_full, 4 parity_error, 5 framing_error, 6 overrun_error
|
control: bit 0 transmit_request, 1 received_acepted (spelled this way in the code), 2 init_request
|
| 1 |
length: number of valid bytes in data
|
length: number of bytes to send |
| 2–23 | data: [u8; 22] |
data: [u8; 22] |
EL6021PdoPreset also lists the Legacy and other Standard layouts, but their txpdo_assignment() and
rxpdo_assignment() hit todo!(). Choosing any preset other than Standard22ByteMdp600 panics,
both in write_config and when you construct the driver from that config.
EL6021Configuration::default() returns:
| Field | CoE | Default | Written by write_config? |
|---|---|---|---|
rts_enabled |
0x8000:01 | true |
no, the field is never written |
xon_off_supported_rx |
0x8000:03 | false |
yes |
xon_on_supported_tx |
0x8000:02 | false |
yes |
fifo_continuous_send_enabled |
0x8000:04 | false |
yes |
enable_transfer_rate_optimization |
0x8000:05 | true |
yes |
half_duplex_enabled |
0x8000:06 | true |
yes |
point_to_point_connection_enabled |
0x8000:07 | false |
yes |
baud_rate |
0x8000:11 | EL6021Baudrate::B19200 |
yes |
data_frame |
0x8000:15 | SerialEncoding::Coding8E1 |
yes |
rx_buffer_full_notification |
0x8000:1A | 0x0360 |
yes |
pdo_assignment |
0x1C12 / 0x1C13 | Standard22ByteMdp600 |
yes |
Some field doc comments in the source give different defaults (9600 baud, 8N1, half duplex off). The
table above is what Default actually returns.
write_config stores the config in the driver and rebuilds txpdo and rxpdo from
pdo_assignment. get_baudrate() and get_serial_encoding() return the stored values. You can use
them to work out timing, for example SerialEncoding::total_bits() gives the bits on the wire per byte.
Like every CoE write, this has to happen in PreOp.
All three operations are handshakes between the control bits you set and the status bits the terminal
answers with. Each call to the driver moves the handshake at most one step, and nothing reaches the
terminal until you write the outputs. So call the driver once per cycle, between input() and
output().
Call it every cycle until it returns true. Don't send or receive before then.
cycle init_request (ours) init_accepted (terminal) driver action returns
1 0 → 1 0 request init false
… 1 0 wait for terminal false
n 1 → 0 1 terminal accepted, drop request false
… 0 1 wait for terminal to drop accepted false
m 0 0 remember receive toggle state true
The driver sets an internal initialized flag at the first step. That flag is never cleared, so to run
the initialization again you need a new EL6021 instance.
-
With a non-empty
bytes(at most 22): the driver copies the bytes and length into the RxPDO and togglestransmit_request. It returnsOk(true), which means the bytes were queued, not that they were sent. Ifbytesis longer than 22, it returns an error. -
With an empty
bytes: this polls for completion. It returnsOk(true)once the terminal'stransmit_acceptedequals ourtransmit_request, which means the last frame was taken.
The driver does not stop you from queuing a new frame before the previous one has been accepted.
If you do, the pending data is overwritten and the toggle flips back, so the terminal may never see
the first frame. Always wait for the empty-message poll to return true before sending again.
Messages longer than 22 bytes have to be split up by the caller.
The terminal toggles receive_request whenever it puts new bytes in the TxPDO. has_messages()
returns true while that toggle is different from the last value the driver acknowledged.
read_message() does three things:
- It returns the first
lengthbytes. - It records the new toggle state.
- It toggles
received_acepted, which tells the terminal it can deliver the next chunk.
A message longer than 22 bytes arrives as several chunks over several cycles, so it's up to you to reassemble it (for example until a terminator byte or an expected length). With transfer-rate optimisation on (the default), the terminal sends a chunk after a pause of about 2 byte times, or when the PDO is full.
If the terminal ever toggles receive_request with length == 0, read_message() returns None
without acknowledging. has_messages() then stays true from that point on.
use bitvec::{order::Lsb0, slice::BitSlice};
use ethercat_hal::coe::ConfigurableDevice;
use ethercat_hal::devices::{EthercatDevice, NewEthercatDevice};
use ethercat_hal::devices::beckhoff_modules::el6021::{
EL6021, EL6021Baudrate, EL6021Configuration, EL6021_PRODUCT_ID,
};
use ethercat_hal::io::serial_interface::{SerialEncoding, SerialInterfaceDevice};
// PreOp: configure
let mut el6021 = EL6021::new();
let sd = subdevices.iter().find(|sd| sd.product_id == EL6021_PRODUCT_ID).unwrap();
let config = EL6021Configuration {
baud_rate: EL6021Baudrate::B9600,
data_frame: SerialEncoding::Coding8N1, // one of the accepted pairs, see above
..EL6021Configuration::default()
};
el6021.write_config(channel.clone(), sd.device_address, &config)?;
// ... request Op, wait for check_all_op(), re-read the subdevice list (offsets) ...
let mut ready = false;
let mut send_pending = false;
let mut rx_buffer: Vec<u8> = Vec::new();
loop {
if let Some(inputs) = handle.get_inputs() {
el6021.input(BitSlice::<u8, Lsb0>::from_slice(&inputs[sd.start_tx..sd.end_tx]))?;
}
if !ready {
ready = el6021.serial_interface_initialize(0);
} else {
// receive
if let Some(chunk) = el6021.serial_interface_read_message(0) {
rx_buffer.extend_from_slice(&chunk);
}
// send one frame at a time
if send_pending {
if el6021.serial_interface_write_message(0, vec![])? {
send_pending = false; // previous frame accepted
}
} else {
el6021.serial_interface_write_message(0, b"PING\r\n".to_vec())?;
send_pending = true;
}
}
if let Some(outputs) = handle.write_outputs() {
el6021.output(BitSlice::<u8, Lsb0>::from_slice_mut(&mut outputs[sd.start_rx..sd.end_rx]))?;
handle.send_outputs();
}
}buffer_full, parity_error, framing_error and overrun_error are decoded, but no trait method
returns them. Read them directly:
if let Some(pdo) = &el6021.txpdo.com_tx_pdo_map_22_byte {
if pdo.status.parity_error || pdo.status.framing_error { /* line settings wrong? */ }
}cd ethercat_hal && cargo test el6021 runs the encoding and decoding tests for the 22-byte input and
output PDO objects.
EtherCAT HAL
EtherCAT Devices
XTREM
Other crates