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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
//! Tooling for accessing the Serial and UART ports on the uC use core::{ convert::TryInto, ffi::c_void, fmt::{self, Write}, str::{self, Utf8Error}, sync::atomic::AtomicU32, sync::atomic::Ordering, }; use crate::millis; #[cfg(feature = "usb_logging")] pub mod log; #[cfg(feature = "ansi")] pub mod ansi; extern "C" { /// number of bytes available in the receive buffer fn usb_serial_available() -> usize; /// discard any buffered input fn usb_serial_flush_input(); /// get the next character, or -1 if nothing received fn usb_serial_getchar() -> i16; /// peek at the next character, or -1 if nothing received fn usb_serial_peekchar() -> i16; /// read a block of bytes to a buffer. returns the size read fn usb_serial_read(buffer: *mut c_void, size: usize) -> usize; /// push out any buffered output fn usb_serial_flush_output(); /// write a buffer. fn usb_serial_write(buffer: *const c_void, size: usize) -> usize; /// the free bytes available in the output buffer fn usb_serial_write_buffer_free() -> usize; static usb_cdc_line_coding: [u32; 2]; static usb_cdc_line_rtsdtr: u8; } const USB_SERIAL_DTR: u8 = 0x01; const USB_SERIAL_RTS: u8 = 0x02; /// A representation of the parity of a serial port #[repr(u8)] pub enum Parity { /// No Parity None = 0, /// Odd Parity Odd = 1, /// Even Parity Even = 2, } impl From<u8> for Parity { fn from(num: u8) -> Self { match num { 0 => Self::None, 1 => Self::Odd, 2 => Self::Even, x => panic!("Attempted to convert invalid value `{}` to Parity ", x), } } } /// A serial USB connection to a host device. Based off of the Arduino Serial class. /// Do not create an instance of this, instad use the provided SERIAL static /// /// # See Also /// - [Serial - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/communication/serial/) /// - [Teensy Serial Reference](https://www.pjrc.com/teensy/td_serial.html) pub type SERIAL = USBSerial; /// A serial USB connection to a host device. Based off of the Arduino Serial class. /// Do not create an instance of this, instad use the provided SERIAL static /// /// # See Also /// - [Serial - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/communication/serial/) /// - [Teensy Serial Reference](https://www.pjrc.com/teensy/td_serial.html) pub struct USBSerial {} static SERIAL_TIMEOUT: AtomicU32 = AtomicU32::new(1000); impl USBSerial { /// Set the serial read in timeout pub fn set_timeout(timeout: u32) { SERIAL_TIMEOUT.store(timeout, Ordering::Relaxed); } /// Get the number of bytes (characters) available for reading from the serial port. /// This is data that’s already arrived and stored in the serial receive buffer /// /// # See Also /// - [Serial.available() - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/communication/serial/available/) pub fn avaliable() -> usize { // Call the C API and directly return the data unsafe { usb_serial_available() } } /// Get the number of bytes (characters) available for writing in the serial buffer /// without blocking the write operation. /// /// # See Also /// - [Serial.avaliableForWrite() - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/communication/serial/availableforwrite/) pub fn available_for_write() -> usize { unsafe { usb_serial_write_buffer_free() } } /// Clear the input buffer pub fn clear() { // Call into the C API unsafe { usb_serial_flush_input() } } /// Transmit any buffered data as soon as possible. /// Sometimes referred to as flush(). /// // - [Serial.flush() - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/communication/serial/flush/) pub fn send_now() { unsafe { usb_serial_flush_output() } } /// Returns the next byte (character) of incoming serial data without removing it from the internal serial buffer. /// That is, successive calls to peek() will return the same character, as will the next call to read(). /// /// # See Also /// - [Serial.peek() - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/communication/serial/peek/) pub fn peek() -> Option<char> { // Call into the C API and store the result let result = unsafe { usb_serial_peekchar() }; // usb_serial_peekchar returns a -1 if there is no char to read, so return a None if result == -1 { None } else { // If there is a char to read, get it, in a u8 let result: u8 = result.try_into().unwrap(); // Return the u8 as a char Some(result as char) } } /// Read the baud rate setting from the PC or Mac. Communication is always /// performed at full USB speed. The baud rate is useful if you intend to /// make a USB to serial bridge, where you need to know what speed the PC /// intends the serial communication to use. pub fn baud() -> u32 { unsafe { usb_cdc_line_coding[0] } } /// Read the stop bits setting from the PC or Mac. USB never uses stop bits. pub fn stop_bits() -> u8 { // Read in the bytes let b: u8 = unsafe { usb_cdc_line_coding[1].to_be_bytes()[0] }; // Make 0 = 1 if b == 0 { 1 } else { b } } /// Read the parity type setting from the PC or Mac. USB uses CRC checking on all /// bulk mode data packets and automatically retransmits corrupted data, so parity /// bits are never used. pub fn parity_type() -> Parity { unsafe { usb_cdc_line_coding[1].to_be_bytes()[1] }.into() } /// Read the number of bits setting from the PC or Mac. /// USB always communicates 8 bit bytes. pub fn num_bits() -> u8 { unsafe { usb_cdc_line_coding[1].to_be_bytes()[2] } } /// Read the DTR signal state. By default, DTR is low when no software has the serial /// device open, and it goes high when a program opens the port. Some programs override /// this behavior, but for normal software you can use DTR to know when a program is /// using the serial port. pub fn dtr() -> bool { unsafe { usb_cdc_line_rtsdtr & USB_SERIAL_DTR != 0 } } /// Read the RTS signal state. USB includes flow control automatically, so you do not /// need to read this bit to know if the PC is ready to receive your data. No matter /// how fast you transmit, USB always manages buffers so all data is delivered reliably. /// However, you can cause excessive CPU usage by the receiving program is a GUI-based /// java application like the Arduino serial monitior! /// /// For programs that use RTS to signal some useful information, you can read it with this /// function. pub fn rts() -> bool { unsafe { usb_cdc_line_rtsdtr & USB_SERIAL_RTS != 0 } } /// Read in the bytes from a serial buffer for the duration of the timeout, or until the buffer is full pub fn read_bytes_timeout(buffer: &mut [u8]) -> usize { // The current count of read in bytes let mut count = 0usize; // The length of the buffer let length = buffer.len(); // The start time, for timeout let start_millis = millis(); loop { // Increment the read in bytes by the amount that had been filled into the buffer count += unsafe { usb_serial_read(buffer.as_ptr().add(count) as _, length - count) }; // Break the loop if the buffer is full if count >= length { return count; } // Stop the loop if the timeout is reached if millis() - start_millis < SERIAL_TIMEOUT.load(Ordering::Relaxed) { return count; } } } /// Read in the bytes from the serial buffer in one shot without a timeout pub fn read_bytes(buffer: &mut [u8]) -> usize { // Calculate the avaliable bytes to read in by taking the minimum of // the bytes in the serial buffer and in the provided buffer let avaliable_bytes = Self::avaliable().min(buffer.len()); unsafe { usb_serial_read(buffer.as_mut_ptr() as _, avaliable_bytes) } } /// Read in a string from the usb buffer with retrying to fill the buffer all the way /// (max 256 bytes) pub fn read_str_timeout() -> Result<Option<&'static str>, Utf8Error> { static mut BUFFER: [u8; 256] = [0; 256]; let read_in = Self::read_bytes_timeout(unsafe { &mut BUFFER }); if read_in == 0 { Ok(None) } else { str::from_utf8(unsafe { &BUFFER[..read_in] }).map(Some) } } /// Read in a string from the usb buffer without retrying to fill the buffer all the way (max 256 bytes) pub fn read_str() -> Result<Option<&'static str>, Utf8Error> { static mut BUFFER: [u8; 256] = [0; 256]; let read_in = Self::read_bytes(unsafe { &mut BUFFER }); if read_in == 0 { Ok(None) } else { str::from_utf8(unsafe { &BUFFER[..read_in] }).map(Some) } } /// Read in one char of data from the serial port pub fn read() -> Option<char> { // Call into the C API and store the result let result = unsafe { usb_serial_getchar() }; // usb_serial_getchar returns a -1 if there is no char to read, so return a None if result == -1 { None } else { // If there is a char to read, get it, in a u8 let result: u8 = result.try_into().unwrap(); // Return the u8 as a char Some(result as char) } } /// Write a single char out onto the serial port, returning if the write was successful or not pub fn write_char(c: char) -> bool { // Call into the C API with an ascii byte unsafe { usb_serial_write(&c as *const char as _, 1) == 1 } } /// Write a whole string out onto the serial port, returning the amount of bytes successfully written out pub fn write(string: &str) -> usize { // Get the string length let size = string.len(); // Get the pointer to the string let ptr = string.as_ptr(); // Call the C API unsafe { usb_serial_write(ptr as _, size) } } } /// A ZST that can be constructed to use the write! and writeln! macros with the global SERIAL output pub struct USBSerialWriter; impl Write for USBSerialWriter { fn write_str(&mut self, s: &str) -> fmt::Result { let len = s.len(); if SERIAL::write(s) != len { Err(fmt::Error) } else { Ok(()) } } fn write_char(&mut self, c: char) -> fmt::Result { if SERIAL::write_char(c) { Ok(()) } else { Err(fmt::Error) } } }