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 231 232 233
//! DMX512 //! //! The "Digital Multiplex" (DMX) protocol is used to control stage lighting //! and effects in large and small setups. It is electrically based on [RS485] //! (https://en.wikipedia.org/wiki/RS-485) and can (almost) easily be //! implemented on microcontrollers as well as soft real-time capable operating //! systems. //! //! ## The protocol //! //! The protocol itself assumes a single-master/multiple-slave system. The //! master periodically sends updates for up to 512 *channels*. Each one of //! these updates is called a DMX *packet*, which consists of a *start code* //! and any number of channels, in-order starting from channel 1. Packets can //! contain less than 512 channels, but must always start at 1 and progress //! in order. //! //! Channels are byte values ranging from 0 to 255. For most channels these //! are intensity values, with 0 being "off" and 255 "full brightness". However //! other functions may be connected to a channel, such as selecting a blink //! sequence or setting a servo position. //! //! ## Technical details //! //! DMX is transmitted using serial protocol at the unusual bitrate of //! 250,000 baud, with no parity and two stop bits. //! //! To begin a transmission, a sender must first pull the line low to send a //! so called *break*, followed by pulling it high to send a *mark*. The //! duration of this break is fairly long, as is the mark, both being far //! longer than the time it usually takes to submit a single byte. //! //! After the break/mark-after-break sequence, regular transmission begins at //! 250,000 baud by sending a single-byte start code. This start code is almost //! always `0xFF`, unless special functions are used, which are //! vendor-specific. //! //! Right after the start code, any number of channels may be transmitted. //! //! ## Refresh rate //! //! The refresh rate depends on the number of channels transmitted. For the //! full 512 channels, the maximum achievable standard-compliant refresh rate //! is about 44 frames per second. If less than 512 channels are sent inside //! a packet, higher refresh rate are possible. //! //! It should be noted that there is a minimum time between breaks (and //! therefore DMX packets) of 1204 microseconds, theoretically capping the //! refresh rate at about 830 updates per second. //! //! DMX is usually meant to be sent continuously, with at least one update //! per second. A lot of devices will switch off if intervals become too large. //! //! ## More information //! //! The [DMX512-A standard] //! (http://tsp.esta.org/tsp/documents/docs/E1-11_2008R2013.pdf) contains the //! detailed specification. //! //! # Implementations //! //! Currently, only an implementation using Linux serial devices is available. //! Connecting a UART to an RS485 transceiver is enough to get this working. //! The implementation is not 100% optimal for DMX: As most Linux kernels //! are not real-time capable, perfectly stable frame rates are not always //! achievable. However, the DMX protocol is fairly tolerant of loose timing. //! //! The UARTs must support non-standard baudrates and reasonably fast baud-rate //! switching. Sending a break is done by switch to a slow baud-rate, sending //! a single `0x00` byte, then waiting a bit and switching back to 250,000 //! baud. //! //! ## Example //! //! The interface is fairly simple to use: //! //! ```no_run //! use dmx::{self, DmxTransmitter}; //! use std::{thread, time}; //! //! let mut dmx_port = dmx::open_serial("/dev/ttyS1").unwrap(); //! //! // a typical 4-channel device, expecting RGBV values. this will set a //! // fairly bright yellow. //! let data = &[0xe4, 0xe4, 0x00, 0xca]; //! //! loop { //! dmx_port.send_dmx_packet(data).unwrap(); //! //! // repeat about every 51 ms. for more accurate frame timings, //! // consider using the ticktock crate. //! thread::sleep(time::Duration::new(0, 50_000_000)); //! } //! ``` use std::ffi::OsStr; extern crate dmx_serial as serial; #[macro_use] extern crate lazy_static; use std::{cmp, thread, time}; // The ideal baudrate for sending a break is 45,455 baud. // At this rate, sendin an 8-bit 0x00 will take the recommended 176 us // The following stop bit would take a reasonable 22 us. // // However, this non-standard baud rate is not supported. The closest common // baud rates are 57,600 bit/s and 38,400 bit/s. The former is chose here, // resulting in the following timings: // // BREAK: 138 us (spec minimum is 92 uS) // actual BREAK // MARK-AFTER-BREAK: 17 us (spec minimum is 8 uS) const BREAK_SETTINGS: serial::PortSettings = serial::PortSettings { baud_rate: serial::Baud57600, char_size: serial::Bits7, parity: serial::ParityNone, stop_bits: serial::Stop1, flow_control: serial::FlowNone, }; const DMX_SETTINGS: serial::PortSettings = serial::PortSettings { // DMX calls for 250_000 baud baud_rate: serial::BaudOther(250_000), char_size: serial::Bits8, parity: serial::ParityNone, stop_bits: serial::Stop2, flow_control: serial::FlowNone, }; lazy_static! { // break will take at least 138 uS, followed by MAB of at least 8 uS // we use a sleep + discard instead of using the kernel's builtin flushing // functions, as they are much too slow static ref SERIAL_TOTAL_BREAK: time::Duration = time::Duration::new(0, 136_000); } /// A DMX transmitter. /// /// Usually there is one transmitter on a bus, the master. Transmitters send /// DMX data. pub trait DmxTransmitter { /// Send a single break. /// /// Sends a break and returns as soon as possible afterwards. A caller is /// itself responsible for waiting an appropriate amount of time before /// sending data. fn send_break(&mut self) -> serial::Result<()>; /// Send raw data. /// /// Sends out bytes at the appropriate bitrate for DMX. Does **not** send /// a break first. Returns after the data is buffered, which might be /// before transmitting is complete. fn send_raw_data(&mut self, data: &[u8]) -> serial::Result<()>; /// Blocking send a full DMX packet. /// /// Preprends a default start code of `0x00` before sending a /// `break` and channel data. /// Will block until the full send is buffered to be sent out, but may /// return before the transmission is complete. /// /// This will create an additional stack copy of `channels`; see /// `send_dmx_alt_packet` for details. #[inline(always)] fn send_dmx_packet(&mut self, channels: &[u8]) -> serial::Result<()> { self.send_dmx_alt_packet(channels, 0x00) } /// Blocking send a full DMX packet with a non-standard start code. /// /// Prepends an arbitrary start code `start` to a packet using a buffer /// on the stack (no allocations are made, but the cost of an extra copy /// is incurred). If required, this can be avoided by using /// `send_raw_dmx_packet`. /// /// Like `send_dmx_packet` will send a break first and returns after /// buffering. #[inline] fn send_dmx_alt_packet(&mut self, channels: &[u8], start: u8) -> serial::Result<()> { let mut prefixed = [0; 513]; let dlen = cmp::min(channels.len(), 512); // prepare prefixed packet prefixed[0] = start; prefixed[1..(dlen + 1)].clone_from_slice(channels); self.send_raw_dmx_packet(&prefixed) } /// Blocking send a DMX packet including start code. /// /// Sends a break, followed by the specified data. Returns after buffering. fn send_raw_dmx_packet(&mut self, data: &[u8]) -> serial::Result<()>; } impl<T: serial::SerialPort> DmxTransmitter for T { #[inline(always)] fn send_break(&mut self) -> serial::Result<()> { self.configure(&BREAK_SETTINGS)?; self.write(&[0x00])?; Ok(()) } #[inline(always)] fn send_raw_data(&mut self, data: &[u8]) -> serial::Result<()> { self.configure(&DMX_SETTINGS)?; self.write(data)?; Ok(()) } #[inline] fn send_raw_dmx_packet(&mut self, data: &[u8]) -> serial::Result<()> { // make a copy to ensure no pointer dereferencing overhead later let min_brk = *SERIAL_TOTAL_BREAK; self.send_break()?; thread::sleep(min_brk); self.send_raw_data(data)?; Ok(()) } } /// Opens a serial device with DMX support. pub fn open_serial<T: AsRef<OsStr> + ?Sized>(port: &T) -> serial::Result<serial::SystemPort> { serial::open(port) }