de-vri-es / serial2-rs

Cross platform serial ports for Rust
Other
41 stars 10 forks source link

RS485 Support #27

Closed omelia-iliffe closed 3 months ago

omelia-iliffe commented 4 months ago

Hello again, Hope you're holiday's were pleasant.

I am using a PCIE RS485 board with some Dynamixels and would love to use this library. I have tested the board (with dynamixels) using the python pyserial library and its working correctly but not sure where to start with modifying this Serial2 crate.

The python lib uses ioctl to set bits of the TIOCSRS485 fcntl.ioctl(self.fd, TIOCSRS485, buf) https://github.com/pyserial/pyserial/blob/7aeea35429d15f3eefed10bbb659674638903e3a/serial/serialposix.py#L195

There is also this rust lib that seems to mirror what python does: https://github.com/mbr/rs485-rs/blob/master/src/lib.rs its possible that this crate can be using independently to set the correct settings and Serial2 to handle the comms.

omelia-iliffe commented 4 months ago

A bit more digging and I have a working example

use rs485::SerialRs485;
use std::{fs::File, os::fd::AsRawFd, thread, time::Duration};
fn main() {
    let port_name = "/dev/ttyS5";
    // open the file. This must remain in scope until the settings have been written
    let file = File::open(port_name).unwrap(); 

    let mut port = SerialRs485::from_fd(file.as_raw_fd()).unwrap(); //read the current settings from the file
    port.set_enabled(true); //enable rs485
    port.set_on_fd(file.as_raw_fd()).unwrap(); //write the settings to the file
    drop(file); //finished with the file now. Dropping it isn't 100% necessary
    let ser = serial2::SerialPort::open(port_name, 115200).unwrap(); //open the serial port like normal

    loop {
        ser.write(b"test").unwrap();
        thread::sleep(Duration::from_millis(500));
    }
}

I'm not sure if this is worth folding into the crate. It could also be done externally I think.

de-vri-es commented 4 months ago

Hmm, serial devices never cease to annoy me. More random ioctls! :D

But yeah, I think I want to support this. Dynamixels with RS485 were one of the main reasons for writing this library. The converters I used didn't need an IOCTL though.

I'm not 100% sure how to integrate this. The main challenge is: if we add a function called enabled_rs485() or something, how do we know what method to use to enable it? I'm worried it will turn out that different devices have different ways to enable it.

Although I guess worst case we can just try all known method until we find one that works.

And: some converters are set to RS485 with hardware switches, or they're always RS485 anyway. Then this ioctl will probably fail, even though the converter is set to RS485. But I think this will need to be solved with documentation.

So yeah, I think it is worth adding support for this. Do you want to write a PR for it? If not, I will try to get to it soon-ish.


For your workaround, it should also work to open the serial port first, and then do the ioctl:

use rs485::SerialRs485;
use std::{fs::File, os::fd::AsRawFd, thread, time::Duration};
fn main() {
    let port_name = "/dev/ttyS5";
    let ser = serial2::SerialPort::open(port_name, 115200).unwrap();
    let mut port = SerialRs485:
    port.set_enabled(true); //enable rs485
    port.set_on_fd(ser.as_raw_fd()).unwrap(); //write the settings to the file

    loop {
        ser.write(b"test").unwrap();
        thread::sleep(Duration::from_millis(500));
    }
}

Hope you're holiday's were pleasant.

They were, thanks :D

omelia-iliffe commented 4 months ago

Yup, I can give it a go. Do you think it should be part of the settings struct, a method you call after opening the port, or a separate method for serial2::SerialPort like open_rs485? Also would you like to use the rs485 crate as a dependency or re implement it

Although I guess worst case we can just try all known method until we find one that works.

I'd be worried that it would quietly fail and than we wouldn't know if it was working or not. Hard to test if it succeeded :/

DanielJoyce commented 3 months ago

Those IOCTLS only exist on some RS-485 devices that support using RTS for signaling send/recive on the same line

https://www.kernel.org/doc/Documentation/serial/serial-rs485.txt

Some CPUs/UARTs (e.g., Atmel AT91 or 16C950 UART) contain a built-in half-duplex mode capable of automatically controlling line direction by toggling RTS or DTR signals. That can be used to control external half-duplex hardware like an RS485 transceiver or any RS232-connected half-duplex devices like some modems.

So we should support it, but not rely on it for RS485 support. Many industrial installations for example just use two half-duplex RS486 lines for full duplex, or the main node directs the children nodes, asking for updates and waiting over the same half duplex line.

It also might make sense to use a type state pattern, and return a Serial and Serial so that certain operations that aren't available under some modes aren't exposed in those type...

de-vri-es commented 3 months ago

Lets continue discussion in this PR: https://github.com/de-vri-es/serial2-rs/pull/29