// - - - - -
// DMXSerial - A hardware supported interface to DMX.
// DMXSerial.cpp: Library implementation file
// 
// Copyright (c) 2011 by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
// 
// Documentation and samples are available at http://www.mathertel.de/Arduino
// 25.07.2011 creation of the DMXSerial library.
// 10.09.2011 fully control the serial hardware register
//            without using the Arduino Serial (HardwareSerial) class to avoid ISR implementation conflicts.
// 01.12.2011 include file changed to work with the Arduino 1.0 environment
// 28.12.2011 unused variable DmxCount removed
// 10.05.2012 added method noDataSince to check how long no packet was received
// - - - - -

#include "Arduino.h"

#include "DMXSerial.h"
#include <avr/interrupt.h>


// ----- Constants -----

// Define port & bit values for Hardware Serial Port.
// These definitions are done for nor hardcoding port 0 in this library.
// If you like to use this library with another hardware port on a more advanced chip,
// you can change the 0 (zero) to 1, 2, 3... in these constant definitions.

#define UCSRnA UCSR0A  // Control and Status Register A

#define UCSRnB UCSR0B  // USART Control and Status Register B
#define RXENn  RXEN0   // Enable Receiving
#define TXENn  TXEN0   // Enable Sending
#define RXCIEn RXCIE0  // Enable Receive Complete Interrupt 
#define UDRIEn UDRIE0  // Enable Data Register Empty Interrupt

#define UCSRnC UCSR0C  // Control and Status Register C
#define USBSn  USBS0
#define USCZn0 UCSZ00

#define UBRRnH UBRR0H  // USART Baud Rate Register High
#define UBRRnL UBRR0L  // USART Baud Rate Register LOW

#define UDRn   UDR0    // USART Data Register
#define UDREn  UDRE0   // USART Data Ready
#define FEn    FE0     // Frame Error


// ----- Enumerations -----

// State of receiving DMX Bytes
typedef enum {
  IDLE, BREAK, DATA
} DMXReceivingState;


// ----- DMXSerial Private variables -----
// These variables are not class members because they have to be reached by the interrupt implementations.
// don't use these variable from outside, use the appropriate methods.

volatile DMXMode  _dmxMode; //  Mode of Operation

volatile uint8_t _dmxRecvState;  // Current State of receiving DMX Bytes
volatile int     _dmxChannel;  // the next channel byte to be sent.
volatile int     _dmxMaxChannel = 32; // the last channel used for sending (1..32).
volatile unsigned long _gotLastPacket = 0; // the last time (using the millis function) a packet was received.

// Array of DMX values (raw).
// Entry 0 will never be used for DMX data but will store the startbyte (o for DMX mode).
volatile uint8_t  _dmxData[DMXSERIAL_MAX+1];

// Create a single class instance. Multiple class instances (multiple simultanious DMX ports) are not supported.
DMXSerialClass DMXSerial;

  
// ----- forwards -----

void _DMXSerialBaud(long baud);
void _DMXSerialWriteByte(uint8_t data);


// ----- Class implementation -----

// (Re)Initialize the specified mode.
// The mode parameter should be a value from enum DMXMode.
void DMXSerialClass::init (int mode) {

  // initialize global variables
  _dmxMode = DMXNone;
  _dmxRecvState= IDLE; // initial state
  _dmxChannel = 0;
  _gotLastPacket = millis(); // remember current (relative) time in msecs.
  
  // initialize the DMX buffer
  for (int n = 0; n < DMXSERIAL_MAX+1; n++)
    _dmxData[n] = 0;

  // now start
  _dmxMode = (DMXMode)mode;

  if (_dmxMode == DMXController) {
    // Setup external mode signal 
    pinMode(DmxModePin, OUTPUT); // enables pin 2 for output to control data direction
    digitalWrite(DmxModePin, DmxModeOut); // data Out direction

    // Setup Hardware
    // Enable transmitter and interrupt
    UCSRnB = (1<<TXENn) | (1<<UDRIEn);

    // Start sending a BREAK and loop (forever) in UDRE ISR
    _DMXSerialBaud(115200);
    _DMXSerialWriteByte((uint8_t)0);
    _dmxChannel = 0;
    
  } else if (_dmxMode == DMXReceiver) {
    // Setup external mode signal 
    pinMode(DmxModePin, OUTPUT); // enables pin 2 for output to control data direction
    digitalWrite(DmxModePin, DmxModeIn); // data in direction

    // Setup Hardware
    // Enable receiver and Receive interrupt
    UCSRnB = (1<<RXENn) | (1<<RXCIEn);
    _DMXSerialBaud(250000); // Enable serial reception with a 250k rate

  } else {
    // Enable receiver and transmitter and interrupts
    // UCSRnB = (1<<RXENn) | (1<<TXENn) | (1<<RXCIEn) | (1<<UDRIEn);

  } // if
} // init()


// Set the maximum used channel.
// This method can be called any time before or after the init() method.
void DMXSerialClass::maxChannel(int channel) {
  if (channel < 1) channel = 1;
  if (channel > DMXSERIAL_MAX) channel = DMXSERIAL_MAX;
  _dmxMaxChannel = channel;
} // maxChannel


// Read the current value of a channel.
uint8_t DMXSerialClass::read(int channel)
{
  // adjust parameter
  if (channel < 1) channel = 1;
  if (channel > DMXSERIAL_MAX) channel = DMXSERIAL_MAX;
  // read value from buffer
  return(_dmxData[channel]);
} // read()


// Write the value into the channel.
// The value is just stored in the sending buffer and will be picked up
// by the DMX sending interrupt routine.
void DMXSerialClass::write(int channel, uint8_t value)
{
  // adjust parameters
  if (channel < 1) channel = 1;
  if (channel > DMXSERIAL_MAX) channel = DMXSERIAL_MAX;
  if (value < 0)   value = 0;
  if (value > 255) value = 255;

  // store value for later sending
  _dmxData[channel] = value;
} // write()


// Calculate how long no data backet was received
unsigned long DMXSerialClass::noDataSince()
{
  unsigned long now = millis();
  return(now - _gotLastPacket);
} // noDataSince()


// Terminale operation
void DMXSerialClass::term(void) {
  // Disable all USART Features, including Interrupts
  UCSRnB = 0;
} // term()


// ----- internel functions and interrupt implementations ----- 

// Initialize the Hardware serial port with the given baud rate
// using 8 data data bits an 2 stop bits.
void _DMXSerialBaud(long baud)
{
  uint16_t baud_setting;

  // calculate baud settings
  baud_setting = (F_CPU / 8 / baud - 1) / 2;

  // assign the baud_setting to the USART Baud Rate Register
  UBRRnH = baud_setting >> 8;
  UBRRnL = baud_setting;

  // 2 stop bits and 8 bit character size, no parity
  UCSRnC = (1<<USBSn) | (3<<USCZn0);
} // _DMXSerialBaud


// send the next byte after current byte was sent completely.
void _DMXSerialWriteByte(uint8_t data)
{
  // putting data into buffer sends the data
  UDRn = data;
} // _DMXSerialWrite


// Interrupt Service Routine, called when a byte or frameerror was received.
ISR(USART_RX_vect) {
  //  digitalWrite(rxStatusPin, HIGH);
  uint8_t  USARTstate= UCSRnA;    //get state before data!
  uint8_t  DmxByte   = UDRn;	    //get data
  uint8_t  DmxState  = _dmxRecvState;	//just load once from SRAM to increase speed

  if (USARTstate &(1<<FEn)) {  	//check for break
    _dmxRecvState = BREAK; // break condition detected.
    _dmxChannel= 0;        // The next data byte is the startbyte

  } else if (DmxState == BREAK) {
    if (DmxByte == 0) {
      _dmxRecvState = DATA;  // normal DMX start code detected
      _dmxChannel= 1;       // start with channel # 1
      _gotLastPacket = millis(); // remember current (relative) time in msecs.
      
    } else {
      // This might be a RDM command -> not implemented so wait for next BREAK !
      _dmxRecvState= IDLE;
    } // if
    
  } else if (DmxState == DATA) {
    _dmxData[_dmxChannel]= DmxByte;	// store received data into dmx data buffer.
    _dmxChannel++;
    if (_dmxChannel >= DMXSERIAL_MAX) { // all channel done.
      _dmxRecvState = IDLE;	// wait for next break
    } // if
   
  } // if

} // ISR(USART_RX_vect)


// Interrupt service routine that is called when the actual byte was sent.
// When sending a DMX sequence it just takes the next channel byte and sends it out.
// In DMXController mode when the buffer was sent completely the DMX sequence will resent, starting with a BREAK pattern.
// In DMXReceiver mode this interrupt is disabled and will not occur.
// In RDM mode this interrupt act like in DMXController mode but the package is not resent automatically.
ISR(USART_UDRE_vect) {
  if ((_dmxMode == DMXController) && (_dmxChannel == -1)) {
    // start sending a BREAK and loop forever in ISR
    _DMXSerialBaud(115200);
    _DMXSerialWriteByte((uint8_t)0);
    _dmxChannel = 0;

  } else if (_dmxChannel == 0) {
    // now back to DMX speed: 250000baud
    _DMXSerialBaud(250000); 
    // write start code
    _DMXSerialWriteByte((uint8_t)0);
    _dmxChannel = 1;

  } else if (_dmxChannel <= _dmxMaxChannel){
    _DMXSerialWriteByte(_dmxData[_dmxChannel]);
 
    if (_dmxChannel < _dmxMaxChannel) {
     _dmxChannel++; // Next time: send next channel.
    } else {
      _dmxChannel   = -1; // this series is done. Next time: restart with break.
    } // if
  } // if
} // ISR(USART_UDRE_vect)

