// Copyright 2009 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------------
//
// BufferedSoftwareSerialOutput, the name says it all:
// - It is software serial.
// - Contrary to the other Software Serial implementations out there which use
// fine-tuned busy loop to delay between bits, this implementation bangs bits
// in a timer interrupt ; while writes are done in an asynchronous, non-blocking
// way to a buffer.
//
// Caveats:
// - The timer rate must be a multiple of the baud rate otherwise this will
// miserably FAIL.
// - Tested only for 2400 (main timer at 31250 Hz) and 4800 (main timer at
// 62500 Hz) baud per seconds.
// - Please check the timing tolerance of your device's UART!
//
// SoftwareSerialOutput is a Vanilla blocking implementation, taken from the
// Arduino libs ; Copyright (c) 2006 David A. Mellis.

#ifndef AVRLIB_SOFTWARE_SERIAL_H_
#define AVRLIB_SOFTWARE_SERIAL_H_

#include "avrlib/avrlib.h"
#include "avrlib/gpio.h"
#include "avrlib/ring_buffer.h"

namespace avrlib {

enum SoftwareSerialState {
  START_BIT = 0,
  END_BIT = 9,
  NEXT_BYTE = 10
};

// Parameters:
// TxPin: digital pin used for transmission.
// timer_rate: frequency (Hz) of the timer which will drive the output.
// baud_rate: target baud rate. must be a divisor of timer_rate.
// buffer_size: prefered buffer size.
template<typename TxPin, uint16_t timer_rate, uint16_t baud_rate,
          uint8_t buffer_size_>
class BufferedSoftwareSerialOutput {
  typedef BufferedSoftwareSerialOutput<TxPin, timer_rate,
                                       baud_rate, buffer_size_> Me;
  typedef RingBuffer<Me> OutputBuffer;
 public:
  typedef uint8_t Value;
  enum {
    prescaler_reset_value = timer_rate / baud_rate,
    buffer_size = buffer_size_,
    data_size = 8
  };
  static void Init() {
    prescaler_counter_ = prescaler_reset_value;
    tx_state_ = NEXT_BYTE;
    TxPin::set_mode(DIGITAL_OUTPUT);
    TxPin::High();
  }
  static inline void Write(Value v) {
    OutputBuffer::Write(v);
  }
  static inline uint8_t writable() { return OutputBuffer::writable(); }
  static inline uint8_t NonBlockingWrite(Value v ) {
    return OutputBuffer::NonBlockingWrite(v);
  }
  static inline void Overwrite(Value v) { OutputBuffer::Overwrite(v); }
  static inline void Tick() {
    --prescaler_counter_;
    if (prescaler_counter_ > 0) {
      return;
    }
    prescaler_counter_ = prescaler_reset_value;
    // Check in which stage of the transmission we are.
    if (tx_state_ == NEXT_BYTE) {
      // Attempt to start the transmission of the next byte in the buffer, (if
      // there is any).
      int16_t next_byte = OutputBuffer::NonBlockingRead();
      if (next_byte >= 0) {
        tx_byte_ = next_byte;
        tx_state_ = START_BIT;
        return;
      } else {
        return;
      }
    } else if (tx_state_ == START_BIT) {
      TxPin::Low();
      tx_symbol_mask_ = 1;
    } else if (tx_state_ == END_BIT) {
      TxPin::High();
    } else {
      TxPin::set_value(tx_byte_ & tx_symbol_mask_);
      tx_symbol_mask_ <<= 1;
    }
    ++tx_state_;
  }

 private:
  static uint8_t prescaler_counter_;

  // Mask shifted every symbol, used to extract bits from the current character
  // to transmit.
  static uint8_t tx_symbol_mask_;

  static uint8_t tx_state_;
  static uint8_t tx_byte_;

  DISALLOW_COPY_AND_ASSIGN(BufferedSoftwareSerialOutput);
};

template<typename TxPin, uint16_t timer_rate, uint16_t baud_rate,
          uint8_t buffer_size_>
uint8_t BufferedSoftwareSerialOutput<TxPin, timer_rate, baud_rate,
                                     buffer_size_>::prescaler_counter_;

template<typename TxPin, uint16_t timer_rate, uint16_t baud_rate,
          uint8_t buffer_size_>
uint8_t BufferedSoftwareSerialOutput<TxPin, timer_rate, baud_rate,
                                     buffer_size_>::tx_symbol_mask_;

template<typename TxPin, uint16_t timer_rate, uint16_t baud_rate,
          uint8_t buffer_size_>
uint8_t BufferedSoftwareSerialOutput<TxPin, timer_rate, baud_rate,
                                     buffer_size_>::tx_state_;

template<typename TxPin, uint16_t timer_rate, uint16_t baud_rate,
          uint8_t buffer_size_>
uint8_t BufferedSoftwareSerialOutput<TxPin, timer_rate, baud_rate,
                                     buffer_size_>::tx_byte_;


// Parameters:
// TxPin: digital pin used for transmission.
// baud_rate: target baud rate. must be a divisor of timer_rate.
// Following code from NewSoftSerial, Copyright (c) 2006 David A. Mellis.
template<typename TxPin, uint16_t baud_rate>
struct SoftwareSerialOutput {
  static void Init() {
    TxPin::set_mode(DIGITAL_OUTPUT);
  }
  static void Write(uint8_t tx_byte) {
    uint8_t oldSREG = SREG;
    cli();

    uint16_t delay = (F_CPU / baud_rate) / 7;
    uint16_t tx_delay = delay - 5;
    TxPin::Low();
    TunedDelay(delay);
    for (uint8_t mask = 1; mask; mask <<= 1) {
      TxPin::set_value(tx_byte & mask);
      TunedDelay(tx_delay);
    }
    TxPin::High();
    SREG = oldSREG;
    TunedDelay(delay);
  }

  static inline void TunedDelay(uint16_t delay) {
    uint8_t tmp = 0;
    asm volatile(
      "sbiw %0, 0x01"  "\n\t"
      "ldi %1, 0xff"   "\n\t"
      "cpi %A0, 0xff"  "\n\t"
      "cpc %B0, %1"    "\n\t"
      "brne .-10"      "\n\t"
      : "+r" (delay), "+a" (tmp)
      : "0" (delay)
    );
  }
};

}  // namespace avrlib

#endif  // AVRLIB_SOFTWARE_SERIAL_H_
