Skip to content

Commit

Permalink
Change RS-485/422 API.
Browse files Browse the repository at this point in the history
  • Loading branch information
de-vri-es committed Apr 9, 2024
1 parent d9e5045 commit 7477603
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 254 deletions.
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ unix = []
# The "windows" features enables windows specific extensions.
windows = []

# The "rs4xx" feature enables RS-485/RS-422 specific extensions on supported platforms.
rs4xx = []

# Add #[doc(cfg(...))] annotations to platform specific items for better documentation (requires nightly toolchain).
doc-cfg = []

serde = ["dep:serde"]

[[example]]
name = "rs485"
required-features = ["rs4xx"]

[dependencies]
serde = { version = "1.0", features = ["derive"], optional = true }
bitflags = "2.4.2"

[target.'cfg(unix)'.dependencies]
libc = "0.2.109"
Expand Down
31 changes: 18 additions & 13 deletions examples/rs485.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use std::{thread, time::Duration};
use std::time::Duration;
use serial2::{SerialPort, Settings};
use serial2::rs4xx::Rs485Config;

fn main() -> std::io::Result<()> {
let port_name = "/dev/ttyS5";
let ser = SerialPort::open(port_name, |mut settings: Settings| {
settings.set_raw();
settings.set_baud_rate(115200)?;
settings.enable_rs485();
Ok(settings)
})?;
let port_name = "/dev/ttyS5";
let serial_port = SerialPort::open(port_name, |mut settings: Settings| {
settings.set_raw();
settings.set_baud_rate(115200)?;
Ok(settings)
})?;

loop {
ser.write(b"test").unwrap();
thread::sleep(Duration::from_millis(500));
}
}
let mut rs485_config = Rs485Config::new();
rs485_config.set_bus_termination(true);
rs485_config.set_full_duplex(true);
serial_port.set_rs4xx_mode(&rs485_config.into())?;

loop {
serial_port.write(b"test").unwrap();
std::thread::sleep(Duration::from_millis(500));
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ mod settings;
pub use settings::{CharSize, FlowControl, Parity, Settings, StopBits, TryFromError, COMMON_BAUD_RATES};

pub mod os;

#[cfg(any(doc, feature = "rs4xx"))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "rs4xx")))]
pub mod rs4xx;
159 changes: 159 additions & 0 deletions src/rs4xx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//! RS-422 and RS-485 configuration.

use std::time::Duration;

/// The mode of a transceiver.
///
/// Some transceivers can be configured in different modes (RS-232, RS-422, RS-485) from software.
///
/// Warning: kernel and driver support for your serial port may be incomplete or bugged.
/// Unsupported features may silently be ignored by the kernel or your device.
/// If your device seems to misbehave, consult the documentation of your platform and your device driver to see what features it supports.
///
/// On Linux, also see the kernel documentation about [RS485 Serial Communication](https://www.kernel.org/doc/html/latest/driver-api/serial/serial-rs485.html).
#[derive(Debug, Clone)]
pub enum TransceiverMode {
/// The default mode.
///
/// If the transceiver supports RS-232 and RS-422 or RS-485, the default mode is usually RS-232.
Default,

/// RS-422 mode.
///
/// Supported in the Linux kernel since version 6.8,
/// but at the time of writing there are no device drivers included with the Linux kernel that use it.
///
/// Note that some device drivers may interpret full duplex RS-485 mode as RS-422 instead.
Rs422,

/// RS-485 mode.
///
/// In RS-485 mode, the kernel will automatically set the RTS (request-to-send) signal high before each transmission,
/// and low again after each transmission.
///
/// For full-duplex (or 4-wire) RS-485 mode, set [`Rs485Config::set_full_duplex()`] to true.
/// Otherwise the receiver will be disabled during transmissions to avoid reading back your own message.
Rs485(Rs485Config),
}

/// RS-485 specific configuration options.
///
/// Note that devices may silently ignore unsupported options instead of raising an error.
#[derive(Debug, Clone, Default)]
pub struct Rs485Config {
/// Enable full-duplex (or 4-wire) RS-485 mode.
///
/// Enable this if your device is using different twisted pairs for transmitting and receiving data.
/// With this mode enabled, the receiver will be left enabled while transmitting data.
full_duplex: bool,

/// Some transceivers allow enabling or disabling a termination resitor for the RS-485 bus.
///
/// If set to true, enable the termination resitor.
///
/// Note that this option may be silently ignored by devices that do not support it.
terminate_bus: bool,

/// Time in milliseconds to delay after setting the RTS signal, before starting transmission.
///
/// May be needed to give some devices on the bus time to activate their receiver.
delay_before_send: Duration,

/// Time in milliseconds to delay after finishing a transmission, before clearing the RTS signal.
///
/// May be needed to give some devices on the bus time to fully receive the message before they disable their receiver.
delay_after_send: Duration,

/// Invert the RTS signal: set it low during transmissions and high after.
invert_rts: bool,
}

impl Rs485Config {
/// Create a new RS-485 configuration with all options disabled and all delays set to zero.
pub fn new() -> Self {
Self::default()
}

/// Enable or disable full-duplex (or 4-wire) mode.
///
/// Enable this if your device is using different twisted pairs for transmitting and receiving data.
/// With this mode enabled, the receiver will be left enabled while transmitting data.
///
/// Note that this option may be silently ignored by devices that do not support it.
pub fn set_full_duplex(&mut self, enable: bool) {
self.full_duplex = enable;
}

/// Check if the full-duplex (or 4-wire) mode is enabled.
pub fn get_full_duplex(&self) -> bool {
self.full_duplex
}

/// Enable or disable the bus termination resistor.
///
/// Note that this option may be silently ignored by devices that do not support it.
pub fn set_bus_termination(&mut self, enable: bool) {
self.terminate_bus = enable;
}

/// Check if the bus termination resistor is enabled.
pub fn get_bus_terminatation(&self) -> bool {
self.terminate_bus
}

/// Set the time to delay after setting the RTS signal, before starting a transmission.
///
/// This may be needed to give some devices on the bus time to activate their receiver.
///
/// The precision will be truncated to whatever the platform supports.
/// On Linux, the delay supports millisecond precision.
///
/// Note that this option may be silently ignored by devices that do not support it.
pub fn set_delay_before_send(&mut self, delay: Duration) {
self.delay_before_send = delay;
}

/// Get the delay time after setting the RTS signal, before starting a transmission.
pub fn get_delay_before_send(&self) -> Duration {
self.delay_before_send
}

/// Set the time to delay after setting the RTS signal, before starting transmission.
///
/// This may be needed to give some devices on the bus time to fully receive the message before they disable their receiver.
///
/// The precision will be truncated to whatever the platform supports.
/// On Linux, the delay supports millisecond precision.
///
/// Note that this option may be silently ignored by devices that do not support it.
pub fn set_delay_after_send(&mut self, delay: Duration) {
self.delay_after_send = delay;
}

/// Get the delay time after setting the RTS signal, before starting a transmission.
pub fn get_delay_after_send(&self) -> Duration {
self.delay_after_send
}

/// Set whether or not to invert the level of the RTS signal.
///
/// If enabled, the RTS signal will be set low during transmissions and high again after each transmission.
///
/// Note that this option may be silently ignored by devices that do not support it.
pub fn set_invert_rts(&mut self, invert: bool) {
self.invert_rts = invert;
}

/// Check if the level of the RTS signal is inverted.
///
/// If enabled, the RTS signal will be set low during transmissions and high again after each transmission.
pub fn get_invert_rts(&self) -> bool {
self.invert_rts
}
}

impl From<Rs485Config> for TransceiverMode {
fn from(other: Rs485Config) -> Self {
Self::Rs485(other)
}
}
45 changes: 45 additions & 0 deletions src/serial_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use std::time::Duration;

use crate::{sys, IntoSettings, Settings};

#[cfg(any(doc, all(feature = "rs4xx", target_os = "linux")))]
use crate::rs4xx;

/// A serial port.
pub struct SerialPort {
inner: sys::SerialPort,
Expand Down Expand Up @@ -385,6 +388,48 @@ impl SerialPort {
pub fn read_cd(&self) -> std::io::Result<bool> {
self.inner.read_cd()
}

/// Get the RS-4xx mode of the serial port transceiver.
///
/// This is currently only supported on Linux.
///
/// Not all serial ports can be configured in a different mode by software.
/// Some serial ports are always in RS-485 or RS-422 mode,
/// and some may have hardware switches or jumpers to configure the transceiver.
/// In those cases, this function will usually report an error or [`rs4xx::TransceiverMode::Default`],
/// even though the serial port is configured is RS-485 or RS-422 mode.
///
/// Note that driver support for this feature is very limited and sometimes inconsistent.
/// Please read all the warnings in the [`rs4xx`] module carefully.
#[cfg(any(doc, all(feature = "rs4xx", target_os = "linux")))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))]
pub fn get_rs4xx_mode(&self) -> std::io::Result<rs4xx::TransceiverMode> {
#[cfg(doc)]
panic!("compiled with cfg(doc)");
#[cfg(not(doc))]
sys::get_rs4xx_mode(&self.inner)
}

/// Set the RS-4xx mode of the serial port transceiver.
///
/// This is currently only supported on Linux.
///
/// Not all serial ports can be configured in a different mode by software.
/// Some serial ports are always in RS-485 or RS-422 mode,
/// and some may have hardware switches or jumpers to configure the transceiver.
/// In that case, this function will usually return an error,
/// but the port can still be in RS-485 or RS-422 mode.
///
/// Note that driver support for this feature is very limited and sometimes inconsistent.
/// Please read all the warnings in the [`rs4xx`] module carefully.
#[cfg(any(doc, all(feature = "rs4xx", target_os = "linux")))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))]
pub fn set_rs4xx_mode(&self, mode: &rs4xx::TransceiverMode) -> std::io::Result<()> {
#[cfg(doc)]
panic!("compiled with cfg(doc)");
#[cfg(not(doc))]
sys::set_rs4xx_mode(&self.inner, mode)
}
}

impl std::io::Read for SerialPort {
Expand Down
12 changes: 0 additions & 12 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,18 +469,6 @@ impl Settings {
&mut self.inner.termios
}

/// Enable RS485 mode.
///
/// This will enable RS485 mode on the serial port using ioctl
///
/// You can use this function to access Unix specific features of the serial port.
/// Your code will not be cross-platform anymore if you use this.
#[cfg(any(doc, all(unix, feature = "unix")))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "unix")))]
pub fn enable_rs485(&mut self) {
self.inner.enable_rs485()
}

/// Get a reference to the raw `DCB` struct.
///
/// You can use this function to access Windows specific features of the serial port.
Expand Down
6 changes: 6 additions & 0 deletions src/sys/unix/linux.rs → src/sys/unix/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use cfg_if::cfg_if;
use std::path::{Path, PathBuf};

#[cfg(feature = "rs4xx")]
mod rs4xx;

#[cfg(feature = "rs4xx")]
pub use rs4xx::*;

cfg_if! {
if #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] {
pub const BAUD_RATES: [(u32, u32); 30] = [
Expand Down
Loading

0 comments on commit 7477603

Please sign in to comment.