avr-oxide 0.1.0

An extremely simple Rusty operating system for AVR microcontrollers
/* serialport.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Abstractions for serial I/O devices, implementing the Read/Write interfaces
//! provided by std::io

// Imports ===================================================================
use crate::hal::generic::serial::{SerialRxTx, ReadHandlerResult, SerialError, SerialPortMode, SerialPortIdentity, SerialWriteEventCallback, SerialReadEventCallback, SerialErrorEventCallback};
use crate::event::{EventSink, EventSource, OxideEvent, OxideEventEnvelope};
use core::marker::PhantomData;
use core::cell::RefCell;
use core::ops::DerefMut;
use crate::alloc::boxed::Box;
use crate::hal::generic::callback::IsrCallback;
use crate::hal::generic::port::{Pin, PinMode};
use crate::panic_if_none;

// Declarations ===============================================================
pub trait SerialCallback = FnMut(SerialPortIdentity, SerialState) -> ();

/**
 * State of the serial port sent with Oxide events.
 */
#[derive(PartialEq,Eq,Clone,Copy)]
pub enum SerialState {
  /// There is data waiting in the buffer for someone to read it
  ReadAvailable,

  /// A serial error has been detected (framing, parity etc.)
  SerialCommsError,

  /// A *break* condition on the receive line has been detected
  BreakDetected,

  /// The receive buffer has overflowed (data was not consumed quickly
  /// enough...)
  ReadBufferOverflow
}

/**
 * Options for controlling how data is transmitted by the serial port device.
 */
#[derive(Copy,Clone)]
pub struct TransmitOptions(u8);


/**
 * Encapsulation of a serial port exposing the standard io::Read/io::Write
 * traits.  Also generates Oxide event notifications.
 */
pub struct SerialPort<'sp,P,T,R,S>
where
  P: 'static + SerialRxTx,
  T: 'static + Pin,
  R: 'static + Pin,
  S: EventSink
{
  port: &'static mut P,
  tx: &'static T,
  rx: &'static R,
  phantom: PhantomData<S>,

  tx_options: TransmitOptions,

  on_event: RefCell<Option<Box<dyn SerialCallback + 'sp>>>
}

// Code ======================================================================

impl TransmitOptions {
  /// Indicate that the device should automatically flush when an end-of-line
  /// (CR or LF) character is sent
  /// is sent.
  pub fn set_flush_on_eol(&mut self, flag: bool) {
    self.0 = match flag {
      true  => self.0 & 0b11111110,
      false => self.0 | 0b00000001
    }
  }

  /// True iff the device should automatically flush when an LF character
  /// is sent.
  pub fn flush_on_eol(self) -> bool {
    self.0 & 0b00000001 > 0
  }

  /// Indicate that CR should be translated to CRLF automatically
  pub fn set_cr_to_crlf(&mut self, flag: bool) {
    self.0 = match flag {
      true  => self.0 & 0b11111101,
      false => self.0 | 0b00000010
    }
  }

  /// True iff the device should automatically translate CR to CRLF.
  pub fn cr_to_crlf(&self) -> bool {
    self.0 & 0b00000010 > 0
  }

  /// Indicate that LF should be translated to CRLF automatically
  pub fn set_lf_to_crlf(&mut self, flag: bool) {
    self.0 = match flag {
      true  => self.0 & 0b11111011,
      false => self.0 | 0b00000100
    }
  }

  /// True iff the device should automatically translate LF to CRLF.
  pub fn lf_to_crlf(&self) -> bool {
    self.0 & 0b00000100 > 0
  }
}

impl Default for TransmitOptions {
  /**
   * Default transmit options:
   *  * DO flush automatically on End-of-Line
   *  * DO NOT translate CR to CRLF
   *  * DO NOT translate LF to CRLF
   */
  fn default() -> Self {
    TransmitOptions(0b00000001)
  }
}

impl<P,T,R,S> SerialPort<'_,P,T,R,S>
where
  P: 'static + SerialRxTx,
  T: 'static + Pin,
  R: 'static + Pin,
  S: EventSink
{
  /**
   * Create an instance of SerialPort that will communicate over the given
   * port and pins.
   */
  pub fn using_port_and_pins(port: &'static mut P, tx: &'static mut T, rx: &'static mut R) -> Self {
    rx.listen(IsrCallback::Nop(()));



    port.enable_rxtx(true);
    tx.set_mode(PinMode::Output);
    rx.set_mode(PinMode::InputFloating);

    SerialPort {
      port, tx, rx,
      phantom: PhantomData::default(),

      tx_options: TransmitOptions::default(),

      on_event: RefCell::new(None)
    }
  }

  /**
   * Set the serial port comms parameters.
   */
  pub fn mode(self, mode: SerialPortMode) -> Self {
    // Disconnect our pins while we reconfigure mode to avoid splurging junk
    // out onto the line
    self.tx.set_mode(PinMode::InputFloating);
    self.rx.set_mode(PinMode::InputFloating);

    self.port.mode(mode);
    self.port.enable_rxtx(true);
    // Now we can enable the transmit pin again
    self.tx.set_mode(PinMode::Output);

    self
  }

  /**
   * Get the currently configured transmission options.
   */
  pub fn get_transmit_options(&self) -> TransmitOptions {
    self.tx_options
  }

  /**
   * Set the transmission options.
   */
  pub fn set_transmit_options(&mut self, options: TransmitOptions) {
    self.tx_options = options;
  }
}

impl<P,T,R,S> EventSource for SerialPort<'_,P,T,R,S>
where
  P: 'static + SerialRxTx,
  T: 'static + Pin,
  R: 'static + Pin,
  S: EventSink
{
  fn listen(&'static self) {
    // We won't send any events on write.
    self.port.set_write_complete_callback(SerialWriteEventCallback::Nop(()));

    // When the serial port receives a byte, send a ReadAvailable message
    self.port.set_read_callback(SerialReadEventCallback::WithData(|src,byte, udata|{
      S::event(OxideEventEnvelope::to(unsafe {&*(panic_if_none!(udata) as *const SerialPort<P,T,R,S> as *const dyn EventSource)},
                                      OxideEvent::SerialEvent(src, SerialState::ReadAvailable)));
      ReadHandlerResult::Buffer(byte)
    },self as *const dyn core::any::Any));

    // When the serial port generates an error, send a suitable message
    self.port.set_error_callback(SerialErrorEventCallback::WithData(|src,error, udata|{
      S::event(OxideEventEnvelope::to(unsafe {&*(panic_if_none!(udata) as *const SerialPort<P,T,R,S> as *const dyn EventSource)},
      OxideEvent::SerialEvent(src, match error {
        SerialError::BufferOverflow => SerialState::ReadBufferOverflow,
        SerialError::Break => SerialState::BreakDetected,
        _ => SerialState::SerialCommsError
      })));
    }, self as *const dyn core::any::Any));
  }

  fn process_event(&self, evt: OxideEvent) {
    match (self.on_event.borrow_mut().deref_mut(), evt) {
      (Some(f), OxideEvent::SerialEvent(source, state)) => {
        (*f)(source,state)
      },
      _ => {}
    }
  }
}

impl<P,T,R,S> crate::io::Read for SerialPort<'_,P,T,R,S>
where
  P: 'static + SerialRxTx,
  T: 'static + Pin,
  R: 'static + Pin,
  S: EventSink
{
  fn read(&mut self, _buf: &mut [u8]) -> crate::io::Result<usize> {
    todo!()
  }
}

impl<P,T,R,S> crate::io::Write for SerialPort<'_,P,T,R,S>
where
  P: 'static + SerialRxTx,
  T: 'static + Pin,
  R: 'static + Pin,
  S: EventSink
{
  fn write(&mut self, buf: &[u8]) -> crate::io::Result<usize> {
    let cnt : usize = 0;

    for byte in buf {

      // Special handling before we send the character
      match byte {
        b'\n' => {
          if self.tx_options.lf_to_crlf() {
            self.port.write_u8(b'\r');
          }
        },
        _ => {}
      }

      // Send the character
      self.port.write_u8(*byte);

      // Special handling after we send the character
      match byte {
        b'\r' => {
          if self.tx_options.cr_to_crlf() {
            self.port.write_u8(b'\n');
          }
          if self.tx_options.flush_on_eol() {
            self.port.flush();
          }
        },
        b'\n' => {
          if self.tx_options.flush_on_eol() {
            self.port.flush();
          }
        },
        _ => {}
      }
    }
    Ok(cnt)
  }

  fn flush(&mut self) -> crate::io::Result<()> {
    self.port.flush();
    Ok(())
  }
}

impl<P,T,R,S> core::fmt::Write for SerialPort<'_,P,T,R,S>
where
  P: 'static + SerialRxTx,
  T: 'static + Pin,
  R: 'static + Pin,
  S: EventSink
{
  fn write_str(&mut self, s: &str) -> core::fmt::Result {
    let _ignore = crate::io::Write::write(self, s.as_bytes());
    Ok(())
  }
}