Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31ea497035 | ||
|
|
3a6eb9c8e5 |
No files matched your search
+290
@@ -0,0 +1,290 @@
|
||||
# EL6021 EtherCAT Serial Interface Device Driver Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The EL6021 is an EtherCAT serial interface terminal that provides RS232/RS485 communication capabilities. This driver implementation provides a comprehensive interface for configuring and communicating with the EL6021 device through the EtherCAT protocol.
|
||||
|
||||
## Device Identity
|
||||
|
||||
The EL6021 device can be identified using the following constants:
|
||||
|
||||
- **Vendor ID**: `2`
|
||||
- **Product ID**: `0x17853052`
|
||||
|
||||
### Supported Revisions
|
||||
|
||||
| Revision | Value |
|
||||
| ---------- | ---------- |
|
||||
| Revision A | `0x150000` |
|
||||
| Revision B | `0x140000` |
|
||||
| Revision C | `0x160000` |
|
||||
| Revision D | `0x100000` |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Baudrate Settings
|
||||
|
||||
The EL6021 supports the following baudrate configurations:
|
||||
|
||||
```rust
|
||||
pub enum EL6021Baudrate {
|
||||
B2400 = 4, // 2400 baud (CoE Value: 4)
|
||||
B4800 = 5, // 4800 baud (CoE Value: 5)
|
||||
B9600 = 6, // 9600 baud (CoE Value: 6)
|
||||
B19200 = 7, // 19200 baud (CoE Value: 7)
|
||||
B38400 = 8, // 38400 baud (CoE Value: 8)
|
||||
B57600 = 9, // 57600 baud (CoE Value: 9)
|
||||
B115200 = 10, // 115200 baud (CoE Value: 10)
|
||||
}
|
||||
```
|
||||
|
||||
### PDO Presets
|
||||
|
||||
The driver supports various PDO (Process Data Object) configurations:
|
||||
|
||||
```rust
|
||||
pub enum EL6021PdoPreset {
|
||||
Legacy22ByteMdp600,
|
||||
Legacy3ByteMdp600,
|
||||
Legacy5ByteMdp600,
|
||||
Standard22ByteMdp600,
|
||||
Standard50WordMdp600,
|
||||
Standard98ByteMdp600,
|
||||
}
|
||||
```
|
||||
|
||||
However currently only Standard22ByteMdp600 is implemented, which offers 22 bytes of input and output bytes
|
||||
|
||||
### Configuration Structure
|
||||
|
||||
The `EL6021Configuration` struct provides comprehensive configuration options.
|
||||
This is the default configuration to be able to communicate with the inverter.
|
||||
|
||||
```rust
|
||||
impl Default for EL6021Configuration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rts_enabled: true,
|
||||
xon_off_supported_rx: false,
|
||||
xon_on_supported_tx: false,
|
||||
enable_transfer_rate_optimization: true,
|
||||
fifo_continuous_send_enabled: false,
|
||||
half_duplex_enabled: true,
|
||||
point_to_point_connection_enabled: false,
|
||||
baud_rate: EL6021Baudrate::B19200,
|
||||
data_frame: SerialEncoding::Coding8E1,
|
||||
rx_buffer_full_notification: 0x0360,
|
||||
pdo_assignment: EL6021PdoPreset::Standard22ByteMdp600,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For communication with the inverter the EL6021 uses half-duplex mode instead of fullduplex, as the mitsubishi inverter we use communicates in half-duplex.
|
||||
The baud_rate and data_frame is set to enable communication with the inverter in a format that both are able to do so.
|
||||
|
||||
## Process Data Objects (PDOs)
|
||||
|
||||
### Standard 22-Byte MDP 600 Format
|
||||
|
||||
#### Input PDO (TxPDO) - Device to Controller
|
||||
|
||||
The input PDO contains status information and received data:
|
||||
|
||||
```rust
|
||||
pub struct Standard22ByteMdp600Input {
|
||||
pub status: EL6021Status,
|
||||
pub length: u8,
|
||||
pub data: [u8; 22],
|
||||
}
|
||||
```
|
||||
|
||||
**Status Bits:**
|
||||
|
||||
- `transmit_accepted`: Transmission was accepted by the device
|
||||
- `receive_request`: New data available (toggle bit)
|
||||
- `init_accepted`: Initialization was accepted
|
||||
- `buffer_full`: Receive buffer is full
|
||||
- `parity_error`: Parity error detected
|
||||
- `framing_error`: Framing error detected
|
||||
- `overrun_error`: Data overrun error detected
|
||||
|
||||
#### Output PDO (RxPDO) - Controller to Device
|
||||
|
||||
The output PDO contains control information and data to transmit:
|
||||
|
||||
```rust
|
||||
pub struct Standard22ByteMdp600Output {
|
||||
pub control: EL6021Control,
|
||||
pub length: u8,
|
||||
pub data: [u8; 22],
|
||||
}
|
||||
```
|
||||
|
||||
**Control Bits:**
|
||||
|
||||
- `transmit_request`: Request to transmit data (toggle bit)
|
||||
- `received_acepted`: Acknowledge received data (toggle bit)
|
||||
- `init_request`: Request device initialization
|
||||
|
||||
## The Complete Communication Flow
|
||||
|
||||
1. Sending data from controller to EL6021:
|
||||
You put your Modbus request in the output buffer
|
||||
You toggle "Transmit request"
|
||||
The EL6021 toggles "Transmit accepted" to acknowledge it received your data from the controller
|
||||
|
||||
2. EL6021 to inverter (or different serial device) communication:
|
||||
The EL6021 sends the data over the RS485 line to the inverter
|
||||
This physical transmission happens independently of the EtherCAT communication
|
||||
The inverter processes the Modbus request and sends a response
|
||||
|
||||
3. Receiving the response:
|
||||
The EL6021 receives the response from the inverter
|
||||
It toggles "Receive request" to tell your controller that data is available
|
||||
Your controller reads the data and toggles "Receive accepted
|
||||
|
||||
## Serial Interface Implementation
|
||||
|
||||
The EL6021 implements the `SerialInterfaceDevice` trait, providing the following functionality:
|
||||
|
||||
### Message Detection
|
||||
|
||||
```rust
|
||||
fn serial_interface_has_messages(&mut self, port: EL6021Port) -> bool
|
||||
```
|
||||
|
||||
Checks if new messages are available by monitoring the `receive_request` toggle bit on the tx_pdo
|
||||
|
||||
### Receiving Messages
|
||||
|
||||
```rust
|
||||
fn serial_interface_read_message(&mut self, port: EL6021Port) -> Option<Vec<u8>>
|
||||
```
|
||||
|
||||
Reads received data from the device and acknowledges reception by toggling the `received_acepted` bit on the rx_pdo.
|
||||
Returns None if no response was received.
|
||||
|
||||
### Writing Messages
|
||||
|
||||
```rust
|
||||
fn serial_interface_write_message(&mut self, port: EL6021Port, message: Vec<u8>) -> Result<bool, Error>
|
||||
```
|
||||
|
||||
Sends data to the device by:
|
||||
|
||||
1. Copying message data to the output PDO
|
||||
2. Setting the message length
|
||||
3. Toggling the `transmit_request` bit on RXPDO
|
||||
4. Waiting for `transmit_accepted` toggle on TXPDO
|
||||
|
||||
**Limitations:**
|
||||
|
||||
- Maximum message length: 22 bytes
|
||||
- Empty message (length 0) checks transmission completion status
|
||||
|
||||
Because we wanted to keep the interface for SerialInterfaceDevice clean and reusable we had to resort to a small hack, where a zero length vector is used to indicate that we are waiting for the EL6021 to accept our request:
|
||||
|
||||
```rust
|
||||
// If we write a message of len zero, then this means we are waiting for our write_message to finish on the EL6021
|
||||
if message.len() == 0 {
|
||||
return Ok(rx_pdo.control.transmit_request == tx_pdo.status.transmit_accepted);
|
||||
}
|
||||
|
||||
let mut data_buffer = [0u8; 22];
|
||||
let bytes = message.as_slice();
|
||||
data_buffer[..message.len()].copy_from_slice(&bytes[..message.len()]);
|
||||
rx_pdo.length = message.len() as u8;
|
||||
rx_pdo.data = data_buffer;
|
||||
rx_pdo.control.transmit_request = !rx_pdo.control.transmit_request;
|
||||
return Ok(true);
|
||||
|
||||
```
|
||||
|
||||
The problem is this, we send data to the EL6021, however the data is not instantly processed, so we have to wait until the next cycle.
|
||||
Because of this reason we first send data to the EL6021 then repeatedly call the same function to check if the EL6021 has successfully sent our message
|
||||
Thus the process is like this:
|
||||
|
||||
```rust
|
||||
// This is just pseudocode
|
||||
serialInterfaceDevice; //(El6021)
|
||||
while true {
|
||||
if state == ReadyToTransmit {
|
||||
_ = serialInterfaceDevice.write_message(msg)
|
||||
state = WaitingForTransmitAcknowledge
|
||||
}
|
||||
|
||||
else if state == WaitingForTransmitAcknowledge {
|
||||
let res = serialInterfaceDevice.write_message(vec![]) // EMPTY vector
|
||||
if res == true {
|
||||
modbus_timeout()
|
||||
state = ReadyToTransmit;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. We start in "ReadyToTransmit" and do a write_message and transition into "WaitingForTransmitAcknowledge"
|
||||
2. Now we call write_message on the device with an empty vector, this makes the write_message implementation return:
|
||||
|
||||
```rust
|
||||
rx_pdo.control.transmit_request == tx_pdo.status.transmit_accepted;
|
||||
```
|
||||
|
||||
3. If the result of this is true, then our transmission of data was accepted by the EL6021
|
||||
4. If the result is true, we would then start waiting for messages or continue sending
|
||||
|
||||
### Device Initialization
|
||||
|
||||
```rust
|
||||
fn serial_interface_initialize(&mut self, port: EL6021Port) -> bool
|
||||
```
|
||||
|
||||
Performs device initialization sequence:
|
||||
|
||||
1. **Initial State**: Both `init_request` and `init_accepted` are false
|
||||
2. **Request Init**: Set `init_request` to true
|
||||
3. **Wait for Acceptance**: Device sets `init_accepted` to true
|
||||
4. **Complete Init**: Clear `init_request`, wait for device to clear `init_accepted`
|
||||
5. **Ready**: Both bits false, device ready for communication
|
||||
|
||||
The initialization must be performed over multiple EtherCAT cycles and returns `true` when complete.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The driver includes validation for:
|
||||
|
||||
- **Baudrate/Encoding Compatibility**: Certain combinations are validated during configuration
|
||||
- **Message Length**: Messages exceeding 22 bytes are rejected
|
||||
- **PDO Availability**: Operations fail if required PDOs are not configured
|
||||
|
||||
## Testing
|
||||
|
||||
The driver includes comprehensive unit tests for:
|
||||
|
||||
- PDO bit manipulation and data serialization
|
||||
- Status and control bit handling
|
||||
- Message length validation
|
||||
- Data integrity verification
|
||||
|
||||
## Constants and Identifiers
|
||||
|
||||
```rust
|
||||
pub const EL6021_VENDOR_ID: u32 = 2;
|
||||
pub const EL6021_PRODUCT_ID: u32 = 0x17853052;
|
||||
|
||||
// Revision constants
|
||||
pub const EL6021_REVISION_A: u32 = 0x150000;
|
||||
pub const EL6021_REVISION_B: u32 = 0x140000;
|
||||
pub const EL6021_REVISION_C: u32 = 0x160000;
|
||||
pub const EL6021_REVISION_D: u32 = 0x100000;
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
The driver requires the following crates:
|
||||
|
||||
- `ethercat_hal_derive`: For EtherCAT device and PDO derivation
|
||||
- `bitvec`: For bit-level data manipulation
|
||||
- `anyhow`: For error handling
|
||||
- Standard library components for async operations
|
||||
@@ -0,0 +1,224 @@
|
||||
# Mitsubishi Inverter RS485 Actor Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The Mitsubishi Inverter RS485 Actor is a comprehensive driver implementation for controlling Mitsubishi inverters via RS485 communication using the Modbus RTU protocol and a SerialInterfaceDevice. This actor provides a state machine-based approach to handle communication with the inverter while maintaining proper timing and priority-based request handling.
|
||||
|
||||
## Architecture
|
||||
|
||||
### State Machine
|
||||
|
||||
The actor operates using a finite state machine with the following states:
|
||||
|
||||
```rust
|
||||
pub enum State {
|
||||
Uninitialized, // Initial state, waiting for serial interface initialization
|
||||
ReadyToSend, // Ready to send new requests
|
||||
WaitingForRequestAccept, // Waiting for EL6021 to accept the transmitted request
|
||||
WaitingForResponse, // Waiting for response from the inverter
|
||||
WaitingForReceiveAccept, // Waiting for EL6021 to accept the received response
|
||||
}
|
||||
```
|
||||
|
||||
After WaitingForReceiveAccept the state is set to ReadyToSend again
|
||||
|
||||
## System Registers
|
||||
|
||||
The driver provides access to various Mitsubishi system registers:
|
||||
|
||||
| Register | Address | Description |
|
||||
| --------------------------------- | ------- | -------------------------------------- |
|
||||
| InverterReset | 40002 | Reset/Restart the inverter |
|
||||
| ParameterClear | 40003 | Clear parameters |
|
||||
| AllParameterClear | 40004 | Clear all parameters |
|
||||
| ParamClearNonCommunication | 40006 | Clear non-communication parameters |
|
||||
| AllParameterClearNonCommunication | 40007 | Clear all non-communication parameters |
|
||||
| InverterStatusAndControl | 40009 | Inverter status and control |
|
||||
| OperationModeAndSetting | 40010 | Operation mode settings |
|
||||
| RunningFrequencyRAM | 40014 | Running frequency (RAM) |
|
||||
| RunningFrequencyEEPROM | 40015 | Running frequency (EEPROM) |
|
||||
| MotorFrequency | 40201 | Actual motor output frequency |
|
||||
|
||||
## Control Requests
|
||||
|
||||
### Available Control Commands
|
||||
|
||||
```rust
|
||||
pub enum MitsubishiControlRequests {
|
||||
None,
|
||||
ResetInverter, // Reset/Restart the inverter
|
||||
ClearAllParameters, // Clear ALL parameters
|
||||
ClearNonCommunicationParameter, // Clear a non-communication parameter
|
||||
ClearNonCommunicationParameters, // Clear all non-communication parameters
|
||||
ReadInverterStatus, // Read inverter status
|
||||
StopMotor, // Stop the motor
|
||||
StartForwardRotation, // Start motor in forward direction
|
||||
StartReverseRotation, // Start motor in reverse direction
|
||||
ReadRunningFrequency, // Read current frequency (RAM)
|
||||
WriteRunningFrequency, // Write frequency setpoint
|
||||
ReadMotorFrequency, // Read actual output frequency
|
||||
}
|
||||
```
|
||||
|
||||
### Priority System
|
||||
|
||||
The actor implements a sophisticated priority system to ensure critical commands are executed first:
|
||||
|
||||
- **Highest Priority (65535)**: `StopMotor`, `ResetInverter`
|
||||
- **High Priority (65534)**: `StartForwardRotation`, `StartReverseRotation`
|
||||
- **Medium Priority (65533)**: `ReadMotorFrequency`
|
||||
- **Lower Priority (65531)**: `ReadRunningFrequency`
|
||||
- **Lowest Priority (65529)**: `ReadInverterStatus`
|
||||
|
||||
The priority system includes an "ignored times" mechanism that increases the effective priority of requests that have been waiting, ensuring all requests eventually get executed.
|
||||
|
||||
In essence it works like this:
|
||||
Step 1:
|
||||
| Request | Priority | Ignored | Effective Priority |
|
||||
| ------- | -------- | ------- | ------------------ |
|
||||
| A | 9 | 0 | 9 |
|
||||
| B | 8 | 0 | 8 |
|
||||
|
||||
"A" gets executed and added again by some function
|
||||
|
||||
Step 2:
|
||||
| Request | Priority | Ignored | Effective Priority |
|
||||
| ------- | -------- | ------- | ------------------ |
|
||||
| A | 9 | 0 | 9 |
|
||||
| B | 8 | 1 | 9 |
|
||||
|
||||
"A" gets executed and added again by some function
|
||||
|
||||
Step 3:
|
||||
| Request | Priority | Ignored | Effective Priority |
|
||||
| ------- | -------- | ------- | ------------------ |
|
||||
| A | 9 | 0 | 9 |
|
||||
| B | 8 | 1 | 10 |
|
||||
|
||||
Now B gets executed even though it has a lower priority than A
|
||||
|
||||
## Request Types and Timeouts
|
||||
|
||||
Different request types have specific timeout requirements specfific to the cs80 mitsubishi inverters:
|
||||
|
||||
```rust
|
||||
pub enum RequestType {
|
||||
OperationCommand, // < 12ms timeout (monitoring, operation commands, frequency setting)
|
||||
ReadWrite, // < 30ms timeout (parameter read/write, EEPROM frequency)
|
||||
ParamClear, // < 5s timeout (parameter clearing operations)
|
||||
Reset, // ~300ms timeout (reset operations)
|
||||
}
|
||||
```
|
||||
|
||||
## Response Handling
|
||||
|
||||
### Response Types
|
||||
|
||||
```rust
|
||||
pub enum ResponseType {
|
||||
NoResponse,
|
||||
ReadFrequency,
|
||||
ReadMotorFrequency,
|
||||
WriteFrequency,
|
||||
InverterStatus,
|
||||
InverterControl,
|
||||
}
|
||||
```
|
||||
|
||||
### Inverter Status Structure
|
||||
|
||||
```rust
|
||||
pub struct MitsubishiInverterStatus {
|
||||
pub running: bool, // Motor is running
|
||||
pub forward_running: bool, // Forward rotation active
|
||||
pub reverse_running: bool, // Reverse rotation active
|
||||
pub su: bool, // Speed up signal
|
||||
pub ol: bool, // Overload signal
|
||||
pub no_function: bool, // No function active
|
||||
pub fu: bool, // Frequency up signal
|
||||
pub abc_: bool, // ABC phase signal
|
||||
pub fault_occurence: bool, // Fault has occurred
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Initialization
|
||||
|
||||
```rust
|
||||
let serial_interface = SerialInterface::new(/* parameters */);
|
||||
let mut inverter = MitsubishiInverterRS485Actor::new(serial_interface);
|
||||
```
|
||||
|
||||
### Setting Frequency
|
||||
|
||||
```rust
|
||||
use uom::si::{f64::Frequency, frequency::hertz};
|
||||
|
||||
let target_frequency = Frequency::new::<hertz>(50.0);
|
||||
inverter.set_frequency_target(target_frequency);
|
||||
```
|
||||
|
||||
### Motor Control
|
||||
|
||||
```rust
|
||||
// Start motor in forward direction
|
||||
inverter.add_request(MitsubishiControlRequests::StartForwardRotation.into());
|
||||
|
||||
// Stop motor
|
||||
inverter.add_request(MitsubishiControlRequests::StopMotor.into());
|
||||
|
||||
// Start motor in reverse direction
|
||||
inverter.add_request(MitsubishiControlRequests::StartReverseRotation.into());
|
||||
```
|
||||
|
||||
### Reading Status
|
||||
|
||||
```rust
|
||||
// Read current motor frequency
|
||||
inverter.add_request(MitsubishiControlRequests::ReadMotorFrequency.into());
|
||||
|
||||
// Read inverter status
|
||||
inverter.add_request(MitsubishiControlRequests::ReadInverterStatus.into());
|
||||
```
|
||||
|
||||
### State Machine Flow
|
||||
|
||||
1. **Uninitialized**: Wait for serial interface initialization
|
||||
2. **ReadyToSend**: Send highest priority request from queue
|
||||
3. **WaitingForRequestAccept**: Wait for EL6021 to accept transmission
|
||||
4. **WaitingForResponse**: Wait for inverter response within timeout
|
||||
5. **WaitingForReceiveAccept**: Wait for EL6021 to accept reception
|
||||
|
||||
## Error Handling
|
||||
|
||||
The driver includes comprehensive error handling:
|
||||
|
||||
- **Modbus Exception Codes**: Illegal function, illegal data address, illegal data value
|
||||
- **Timeout Management**: Automatic timeout calculation based on baudrate and message size
|
||||
- **Communication Errors**: Graceful handling of serial communication failures
|
||||
- **State Recovery**: Automatic state recovery on communication errors
|
||||
|
||||
## Frequency Conversion
|
||||
|
||||
The driver handles frequency conversion between different units:
|
||||
|
||||
```rust
|
||||
// Convert Hz to centihz (0.01 Hz units) for Mitsubishi protocol
|
||||
pub fn convert_hz_float_to_word(&mut self, value: Frequency) -> u16 {
|
||||
let scaled = value.get::<centihertz>();
|
||||
scaled.round() as u16
|
||||
}
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The actor is designed to be used in async environments with proper Send bounds on all futures, ensuring thread safety in multi-threaded applications.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `bitvec`: For bit manipulation operations
|
||||
- `uom`: For unit-of-measurement handling (frequency)
|
||||
- `ethercat_hal`: For EtherCAT serial interface integration
|
||||
- `anyhow`: For error handling
|
||||
- Standard library components for collections, time, and async operations
|
||||
Reference in new issue
Block a user