//------------------------------------------------------------------
// qfixI2C.h
//
// This class is used for low-level I2C communication.
//
// For TW_ constants see compat/twi.h
//
// Copyright 2005-2006 by KTB mechatronics GmbH
// Author: Stefan Enderle, Florian Schrapp
//------------------------------------------------------------------

#ifndef qfixI2C_h
#define qfixI2C_h

#include "qfix.h"
#include "compat/twi.h"

const int ACTION_SEND = 1;
const int ACTION_GET  = 2;
const int ACTION_UNKNOWN  = 3;


const uint8_t ERROR_NO_ACK   = 1;
const uint8_t ERROR_NO_START = 2;
const uint8_t ERROR_NOT_SENT = 3;     // send() could not send byte(s)



class I2C
{
private:
  uint8_t err;
  uint8_t adr;

  void sendStartSLA_W(uint8_t address);
  void sendStartSLA_R(uint8_t address);
  void sendByte(uint8_t data);
  void sendStop();
  void readByte(uint8_t& data);

public:
  I2C();
  uint8_t error();

// master side //

  void send(uint8_t address, uint8_t data);
  void send(uint8_t address, uint8_t* data, int length);
  void get(uint8_t address, uint8_t* data, int length);

// slave side //

  void setSlaveAdress(uint8_t address);
  bool isAction();
  bool isActionSend();  // true if master sent something
  bool isActionGet();   // true if master wants to get something
  int receive(uint8_t* data); // if action is send, receive the bytes
  void returnBytes(uint8_t* data, int len, bool lastOne);
};



I2C::I2C() 
{
  TWBR = 10;			// I2C bitrate (must be 10 or higher)
  TWCR = 4+64;			// TWI enable bit + ackn  
  TWSR = 0;				// prescaler

  // I2C pull-ups are set in the board file //
  
  err=0;
}

uint8_t I2C::error()
{
  return err;
}


void I2C::setSlaveAdress(uint8_t address)
{
  TWAR = address<<1;   // slave address (shift by one since bit 0 has different meaning)
}


bool I2C::isAction()
{
  if (TWCR&(1<<TWINT)) return true;
  else return false;
}


inline
bool I2C::isActionGet()
{
  return ((TWSR&248)==TW_ST_SLA_ACK);    // own I2C address received, read mode, ACK sent
}


inline
bool I2C::isActionSend()
{
  return ((TWSR&248)==TW_SR_SLA_ACK);    // own I2C address received, write mode, ACK sent
}


void I2C::sendStartSLA_W(uint8_t address)
{
  bool OK = false;
  int counter = 0;
  
  do {
 
	TWCR |= BV(TWINT)|BV(TWSTA);				// send start condition
    while(!(TWCR&BV(TWINT)));					// wait for OK
	
    if ((TWSR&248)!=TW_START) {				// start condition was sent  -> OK
	  err=ERROR_NO_START;
	  return;
	}

    TWDR=(address<<1);							// slave address + write mode
    TWCR = BV(TWINT) | BV(TWEN) | BV(TWEA);	// generate command

    while(!(TWCR&128)) {};					// wait for OK
    if ((TWSR&248)==TW_MT_SLA_ACK) OK=true;   // SLA+W was sent, ACK received  -> OK
    if ((TWSR&248)==TW_MT_SLA_NACK) {			// SLA+W was sent, ACK not received -> ERROR
      counter++;
	  
	  if (counter == 10) {						// After 10 tries...
        err=ERROR_NO_ACK;						// ... return with ERROR_NO_ACK
	    return;
      }
	}

  } while(!OK);
  
  err=0;								// OK
}


void I2C::sendStartSLA_R(uint8_t address)
{
  bool OK=false;
  while (!OK) {
	TWCR |= BV(TWINT)|BV(TWSTA);				// send start condition
//    sbi(TWCR, TWSTA);               // generate start condition
    while(!(TWCR&128));             // wait for OK
    if ((TWSR&248)==8);             // start condition was sent  -> OK

    TWDR=(address<<1) | 1;          // slave address + read mode
    TWCR = BV(TWINT) | BV(TWEN) | BV(TWEA);           // generate command

    while(!(TWCR&128));             // wait for send OK + ACK/NACK from slave
    if ((TWSR&248)==0x38);          // Arbitration lost or NOT ACK -> ERROR
    if ((TWSR&248)==0x40) OK=true;  // SLA+R was sent, ACK received  -> OK
    if ((TWSR&248)==0x48);          // SLA+W was sent, ACK not received -> ERROR
    sbi(TWCR, TWINT);               // OK
  }
}

// This is used by send() called by the master //
void I2C::sendByte(uint8_t data)
{
  TWDR = data;                    			// send one data byte
  TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);  // start transmission

  while(!(TWCR&(1<<TWINT)));				// wait for TWCR to become 0
  if ((TWSR&248)==TW_MT_DATA_ACK);        // data sent, ACK received -> OK
  if ((TWSR&248)==TW_MT_DATA_NACK);       // data sent, ACK not received -> ERROR
}


void I2C::sendStop()
{
  TWCR = BV(TWINT) | BV(TWEN) | BV(TWEA) | BV(TWSTO);       // generate stop command
}

// Note: in qfix, the I2C address is the board identifier 
// and the first data byte ist the logical ID
inline
void I2C::send(uint8_t address, uint8_t data)
{
  send(address, &data, 1);
}


// Note: in qfix, the I2C address is the board identifier 
// and the first data byte ist the logical ID
void I2C::send(uint8_t address, uint8_t* data, int length)
{
  sendStartSLA_W(address);
  if (err!=0) 
  {
	sendStop();
	err = ERROR_NOT_SENT;	
	return;
  }

  for (int i=0; i<length; i++) {
    sendByte(data[i]);
  }
  sendStop();
  err = 0;					// OK
}



int I2C::receive(uint8_t* data)
{
  TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);

  while(!(TWCR&(1<<TWINT)));				// wait for something

  int len = 0;
  while ((TWSR&248)==TW_SR_DATA_ACK) {		// data received (in TWDR), ACK sent 
    data[len] = TWDR;						// read received byte
    len++;
    TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
    while(!(TWCR&(1<<TWINT))) { }			// wait for OK
  }

  if ((TWSR&248)==TW_SR_STOP) { }			// STOP (or new START) received -> OK
  TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);           // generate command

  return len;
}


void I2C::readByte(uint8_t& data)
{
  while(!isAction());

  if ((TWSR&248)==0x50);      // data received: NACK 
  if ((TWSR&248)==0x58);      // data received: ACK
  data = TWDR;
  TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);           // generate command
}


void I2C::get(uint8_t address, uint8_t* data, int length)
{
  sendStartSLA_R(address);
  if (err!=0) 
  {
	sendStop();
	err = ERROR_NOT_SENT;	
	return;
  }
  
  for (int i=0; i<length; i++) {
    readByte(data[i]);
  }
  sendStop();
}




void I2C::returnBytes(uint8_t* data, int len, bool lastOne)
{
  for (int i=0; i<len; i++) {
    TWDR=data[i];                  // byte to send

    if (i==len-1) TWCR = (1<<TWINT) | (1<<TWEN);     // command for last byte (no TWEA is correct!)
    else TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);   // command for all others

    while(!(TWCR&(1<<TWINT)));         // wait for OK

    if ((TWSR&248)==0xB8);      // data sent, ACK ACK received -> OK
    if ((TWSR&248)==0xC0);      // data sent, ACK not received -> ERROR
  }
  TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);           // generate command
}


#endif