Compare commits

...
2 changed files with 514 additions and 0 deletions

No files matched your search

+290
View File
@@ -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
+224
View File
@@ -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