1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//! RS485 support for serial devices
//!
//! RS485 is a low-level specification for data transfer. While the spec only
//! defines electrical parameter and very little else, in reality it is most
//! often used for serial data transfers.
//!
//! To realize an RS485 connection, a machine's UARTs are usually used. These
//! support sending and receiving, each through a dedicated pin for RX
//! (receive) and TX (transmit). RS232 can be directly connected this way to
//! allow full duplex (simultaneous send and receive) connections.
//!
//! RS485 differs from RS232 in an important aspect: Instead of dedicating a
//! single line to send and another one to receive, two wires each are used
//! to transport a differential signal. In combination with higher voltage
//! levels and twisted-pair cabling, this allows for much more reliable
//! transmission results.
//!
//! A working full-duplex RS485 connection requires a transceiver chip and
//! four wires, two for RX and TX. To reduce the number of wires back down to
//! two, a suitable protocol can be used to establish a bi-directional
//! half-duplex connection. Commonly, a "master" device will turn on its
//! line driver, send a request, turn it back off and wait for a reply.
//!
//! Most transceivers have a pins dedicated to turning the linw driver on and
//! off. Being able to turn the driver on just before sending and back off
//! after the transmission is complete is a requirement for implementing these
//! protocols.
//!
//! Often a UART's RTS (request-to-send) pin is connected in a way that
//! makes the transceiver enable the line driver when RTS is on but the
//! receiver instead when RTS off.
//! Since this functionality is common, kernel serial drivers usually support
//! turning RTS on/off. This crate allows configuring that functionality,
//! provided it is setup correctly.
//!
//! When running into issues with RS485, verify that the RTS pin of your UART
//! is actually connected to the transceiver, properly pinmuxed (if necessary)
//! and that the UART itself is enabled.

#[macro_use]
extern crate bitflags;
extern crate libc;

use libc::c_ulong;
use std::{mem, io};
use std::os::unix::io::{AsRawFd, RawFd};

// constants stolen from C libs
const TIOCSRS485: c_ulong = 0x542f;
const TIOCGRS485: c_ulong = 0x542e;

// bitflags used by rs485 functionality
bitflags! {
    pub struct Rs485Flags: u32 {
        const SER_RS485_ENABLED        = (1 << 0);
        const SER_RS485_RTS_ON_SEND    = (1 << 1);
        const SER_RS485_RTS_AFTER_SEND = (1 << 2);
        const SER_RS485_RX_DURING_TX   = (1 << 4);
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug)]
/// RS485 serial configuration
///
/// Internally, this structure is the same as a [`struct serial_rs485`]
///(http://elixir.free-electrons.com/linux/latest/ident/serial_rs485).
pub struct SerialRs485 {
    flags: Rs485Flags,
    delay_rts_before_send: u32,
    delay_rts_after_send: u32,
    _padding: [u32; 5],
}

impl SerialRs485 {
    /// Create a new, empty set of serial settings
    ///
    /// All flags will default to "off", delays will be set to 0 ms.
    #[inline]
    pub fn new() -> SerialRs485 {
        unsafe { mem::zeroed() }
    }

    /// Load settings from file descriptor
    ///
    /// Settings will be loaded from the file descriptor, which must be a
    /// valid serial device support RS485 extensions
    #[inline]
    pub fn from_fd(fd: RawFd) -> io::Result<SerialRs485> {
        let mut conf = SerialRs485::new();

        let rval = unsafe { libc::ioctl(fd, TIOCGRS485, &mut conf as *mut SerialRs485) };

        if rval == -1 {
            return Err(io::Error::last_os_error());
        }

        Ok(conf)

    }

    /// Enable RS485 support
    ///
    /// Unless enabled, none of the settings set take effect.
    #[inline]
    pub fn set_enabled<'a>(&'a mut self, enabled: bool) -> &'a mut Self {
        if enabled {
            self.flags |= SER_RS485_ENABLED;
        } else {
            self.flags &= !SER_RS485_ENABLED;
        }

        self
    }

    /// Set RTS high or low before sending
    ///
    /// RTS will be set before sending, this setting controls whether
    /// it will be set high (`true`) or low (`false`).
    #[inline]
    pub fn set_rts_on_send<'a>(&'a mut self, rts_on_send: bool) -> &'a mut Self {
        if rts_on_send {
            self.flags |= SER_RS485_RTS_ON_SEND;
        } else {
            self.flags &= !SER_RS485_RTS_ON_SEND;
        }

        self
    }

    /// Set RTS high or low after sending
    ///
    /// RTS will be set after sending, this setting contrls whether
    /// it will be set high (`true`) or low (`false`).
    #[inline]
    pub fn set_rts_after_send<'a>(&'a mut self, rts_after_send: bool) -> &'a mut Self {
        if rts_after_send {
            self.flags |= SER_RS485_RTS_AFTER_SEND;
        } else {
            self.flags &= !SER_RS485_RTS_AFTER_SEND;
        }

        self
    }

    /// Delay before sending in ms
    ///
    /// If set to non-zero, transmission will not start until
    /// `delays_rts_before_send` milliseconds after RTS has been set
    #[inline]
    pub fn delay_rts_before_send_ms<'a>(&'a mut self, delay_rts_before_send: u32) -> &'a mut Self {
        self.delay_rts_before_send = delay_rts_before_send;
        self
    }

    /// Hold RTS after sending, in ms
    ///
    /// If set to non-zero, RTS will be kept high/low for
    /// `delays_rts_after_send` ms after the transmission is complete
    #[inline]
    pub fn delay_rts_after_send_ms<'a>(&'a mut self, delay_rts_after_send: u32) -> &'a mut Self {
        self.delay_rts_after_send = delay_rts_after_send;
        self
    }

    /// Allow receiving whilst transmitting
    ///
    /// Note that turning off this option sometimes seems to make the UART
    /// misbehave and cut off transmission. For this reason, it is best left on
    /// even when using half-duplex.
    pub fn set_rx_during_tx<'a>(&'a mut self, set_rx_during_tx: bool) -> &'a mut Self {
        if set_rx_during_tx {
            self.flags |= SER_RS485_RX_DURING_TX
        } else {
            self.flags &= !SER_RS485_RX_DURING_TX;
        }
        self
    }

    /// Apply settings to file descriptor
    ///
    /// Applies the constructed configuration a raw filedescriptor using
    /// `ioctl`.
    #[inline]
    pub fn set_on_fd(&self, fd: RawFd) -> io::Result<()> {
        let rval = unsafe { libc::ioctl(fd, TIOCSRS485, self as *const SerialRs485) };

        if rval == -1 {
            return Err(io::Error::last_os_error());
        }

        Ok(())
    }
}


/// Rs485 controls
///
/// A convenient trait for controlling Rs485 parameters.
pub trait Rs485 {
    /// Retrieves RS485 parameters from target
    fn get_rs485_conf(&self) -> io::Result<SerialRs485>;

    /// Sets RS485 parameters on target
    fn set_rs485_conf(&self, conf: &SerialRs485) -> io::Result<()>;

    /// Update RS485 configuration
    ///
    /// Combines `get_rs485_conf` and `set_rs485_conf` through a closure
    fn update_rs485_conf<F: FnOnce(&mut SerialRs485) -> ()>(&self, f: F) -> io::Result<()>;
}

impl<T: AsRawFd> Rs485 for T {
    #[inline]
    fn get_rs485_conf(&self) -> io::Result<SerialRs485> {
        SerialRs485::from_fd(self.as_raw_fd())
    }

    #[inline]
    fn set_rs485_conf(&self, conf: &SerialRs485) -> io::Result<()> {
        conf.set_on_fd(self.as_raw_fd())
    }

    #[inline]
    fn update_rs485_conf<F: FnOnce(&mut SerialRs485) -> ()>(&self, f: F) -> io::Result<()> {
        let mut conf = self.get_rs485_conf()?;
        f(&mut conf);
        self.set_rs485_conf(&conf)
    }
}