
/*!
 * \file i2c_eeprom.c
 * \ingroup i2c_eep
 * \brief Routinen zur Ansteuerung eines EEPROMs über I2C
 */

/*!
 * \defgroup i2c_eep I2C-EEPROM-Routinen
 * \ingroup i2c_eep
 * {
 *    \defgroup i2c Ansteuerung der I2C-Schnittstelle
 *    \defgroup eeprom Ansteuerung des seriellen EEPROMs
 * }
 */

#include "main.h"

/* ******************************************************************************************** */
/* *********************************  I 2 C - E E P R O M  ************************************ */
/* ******************************************************************************************** */

/*!
 * \ingroup eeprom
 * \brief Daten von bestimmter Adresse vom I2C-EEPROM lesen
 *
 * Mit dieser Funktion können Daten von einem I2C-EEPROM von einer bestimmten Adresse gelesen werden.
 * Hierbei wird zunächst die zu lesende Adresse übertragen, gefolgt vom Auslesen der vorgegebenen
 * Anzahl an Bytes. Die Daten werden an die über den Pointer angegebene Adresse geschrieben.
 *
 * \param addr Adresse, von der die Daten im EEPROM gelesen werden sollen
 * \param buf Zeiger auf den Buffer, in den die gelesenen Daten geschrieben werden
 * \param len Anzahl der zu lesenden Datenbytes
 *
 * \retval Status: 0=erfolgreich gelesen, 1=Fehler
 */

uint8_t i2cEepromReadSequential(uint16_t addr, uint8_t * buf, uint8_t len)
{
  uint8_t retval = 1;

  // Start-Kommando und Schreibbefehl (für Adresse) senden
  if(i2cStart(FC256_WRITE_CMD) == 0)
  {
    // Adresse übertragen
    if(i2cWrite((addr & 0xFF00) >> 8) == 0)
    {
      i2cWrite(addr & 0x00FF);

      // Repeated Start, um in den Lesemodus zu kommen
      if(i2cStart(FC256_READ_CMD) == 0)
      {
        // Jetzt alle Daten der Reihe nach auslesen und in den Buffer schreiben
        while(len > 0)
        {
          // Beim letzten zu lesenden Byte ein NACK senden, sonst immer ein ACK
          *buf++ = i2cRead((len == 1) ? 0 : 1);
          len--;
        }

        i2cStop();
        return 0;
      }
    }
  }

  // Stop-Bedingung senden
  i2cStop();

  return retval;
}


/*!
 * \ingroup eeprom
 * \brief Daten von aktueller Adresse vom I2C-EEPROM lesen
 *
 * Mit dieser Funktion können Daten von einem I2C-EEPROM ab der zuletzt benutzten Adresse gelesen werden.
 * Das EEPROM führt intern einen Adresszähler, der bei jedem Zugriff automatisch inkrementiert wird.
 * Die Routine gibt deshalb direkt die Bytes aus, die nach dem zuletzt gelesenen Byte im Speicher stehen
 * und schreibt diese in den angegebenen Buffer.
 *
 * \param buf Zeiger auf den Buffer, in den die gelesenen Daten geschrieben werden
 * \param len Anzahl der zu lesenden Datenbytes
 *
 * \retval Status: 0=erfolgreich gelesen, 1=Fehler
 */

uint8_t i2cEepromReadCurrent(uint8_t * buf, uint8_t len)
{
  if(i2cStart(FC256_READ_CMD) == 0)
  {
    while(len > 0)
    {
      // Beim letzten zu lesenden Byte ein NACK senden, sonst immer ein ACK
      *buf++ = i2cRead((len == 1) ? 0 : 1);
      len--;
    }

    i2cStop();
    return 0;
  }
  else
  {
    i2cStop();
    return 1;
  }
}


/*!
 * \ingroup eeprom
 * \brief Datenbyte an bestimmte Adresse im I2C-EEPROM schreiben
 *
 * Diese Funktion schreibt ein einzelnes Datenbyte an die angegebene Adresse im I2C-EEPROM.
 *
 * \param addr Adresse, von der die Daten im EEPROM gelesen werden sollen
 * \param data Zu schreibendes Datenbyte
 *
 * \retval Status: 0=erfolgreich geschrieben, 1=Fehler
 */

uint8_t i2cEepromWriteByte(uint16_t addr, uint8_t data)
{
  // START-Bedingung und Schreibkommando senden
  if(i2cStart(FC256_WRITE_CMD) == 0)
  {
    // Zu schreibende Adresse übertragen
    i2cWrite((addr & 0xFF00) >> 8);
    i2cWrite(addr & 0x00FF);

    // Daten übertragen
    i2cWrite(data);

    // Stop-Bedingung senden
    i2cStop();

    return 0;
  }
  else
  {
    // Irgendwas war faul: STOP senden
    i2cStop();
    return 1;
  }
}


/*!
 * \ingroup eeprom
 * \brief Daten an bestimmte Adresse im I2C-EEPROM schreiben
 *
 * Diese Funktion schreibt Daten an die angegebene Adresse im I2C-EEPROM.
 *
 * Zu beachten: Der Adresszähler des EEPROMs inkrementiert automatisch die 6 niederwertigen
 * Bits nach jedem übertragenen Wert. Werden mehr als 64 Bytes übertragen, läuft der Adresszähler
 * über und es werden die bereits übermittelten Bytes überschrieben. Werden weniger als 64 Bytes
 * übermittelt, behalten die nicht adressierten Bytes ihren vorherigen Wert.
 *
 * Ein Schreibvorgang einer Page dauert bis zu 5ms, während dieser Zeit ist kein neuer Befehl
 * möglich.
 *
 * \param addr Adresse, von der die Daten im EEPROM gelesen werden sollen
 * \param buf Zeiger auf die zu schreibenden Daten (Byte-Array)
 * \param len Anzahl der zu schreibenden Datenbytes (1-64)
 *
 * \retval Status: 0=erfolgreich geschrieben, 1=Fehlerhafte Anzahl Bytes, 2=Fehler beim Schreiben
 */

uint8_t i2cEepromWritePage(uint16_t addr, uint8_t * buf, uint8_t len)
{
  if((len == 0) || (len > 64))
  {
    return 1;
  }

  // START-Bedingung und Schreibkommando senden
  if(i2cStart(FC256_WRITE_CMD) == 0)
  {
    // Zu schreibende Adresse übertragen
    i2cWrite((addr & 0xFF00) >> 8);
    i2cWrite(addr & 0x00FF);

    // Daten übertragen
    while(len > 0)
    {
      i2cWrite(*buf++);
      len--;
    }

    i2cStop();
    return 0;
  }
  else
  {
    i2cStop();
    return 2;
  }
}

/* ******************************************************************************************** */
/* ****************************************  I 2 C  ******************************************* */
/* ******************************************************************************************** */

/*!
 * \ingroup i2c
 * \brief I2C-Interface initialisieren
 *
 * Diese Funktion initialisiert die I2C-Schnittstelle und setzt die zur Übertragung benutzte
 * Baudrate. Die Baudrate wird über die Werte TWBR (Baudratenregister) und die Bits TWPS im
 * Register TWSR (Prescaler) gesetzt und errechnet sich nach folgender Formel:
 *
 * \f$ f_{SCL} &=& \frac{f_{CPU}}{16 + 2 \times TWBR \times 4^{TWPS}} \f$
 */

void i2cInit(void)
{
  TWSR = 0;                               // kein Prescaler
  TWBR = ((F_CPU/SCL_CLOCK)-16)/2;        // vorgegebenen Clock einstellen; TWBR sollte > 10 sein!
}


/*!
 * \ingroup i2c
 * \brief I2C-Interface: START-Bedingung oder REPEATED START-Bedingung senden
 *
 * Diese Funktion sendet eine START-Bedingung auf der I2C-Schnittstelle und überträgt anschließend
 * die als Parameter übergebene Device-Adresse. Die Funktion kann auch während einer aktiven Übertragung
 * (also dann, wenn bereits eine START-Bedingung zuvor gesendet wurde) benutzt werden, um eine
 * REPEATED START-Bedingung zu senden.
 *
 * \param addr Device-Adresse des anzusprechenden I2C-Devices
 * \retval Statuscode: 0 = erfolgreich, 1 = Fehler beim Senden der START-Bedingung, 2 = Deviceadresse
 * nicht bestätigt
 */

uint8_t i2cStart(uint8_t addr)
{
  // START-Bedingung senden
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);

  // Warte, bis START-Bedingung gesendet wurde
  while(!(TWCR & (1 << TWINT)));

  // Prüfe Status-Code, ob Fehler beim Senden von START (oder repeated START) auftraten
  if((TW_STATUS != TW_START) && (TW_STATUS != TW_REP_START))
  {
    // Abbruch: STOP-Bedingung senden und auf Bestätigung warten
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
    return 1;
  }

  // Device-Adresse senden (enthält R/W-Bit)
  TWDR = addr;
  TWCR = (1 << TWINT) | (1 << TWEN);

  // Warte, bis START-Bedingung gesendet wurde
  while(!(TWCR & (1 << TWINT)));

  if((TW_STATUS != TW_MT_SLA_ACK) && (TW_STATUS != TW_MR_SLA_ACK))
  {
    // Abbruch: STOP-Bedingung senden und auf Bestätigung warten
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
    return 2;
  }

  return 0;
}


/*!
 * \ingroup i2c
 * \brief I2C-Interface: STOP-Bedingung senden
 *
 * Diese Funktion sendet eine STOP-Bedingung auf der I2C-Schnittstelle und beendet damit die laufende
 * Datenübertragung und gibt den Bus wieder frei.
 */

void i2cStop(void)
{
  // STOP-Bedingung senden und auf Bestätigung warten
  TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
}


/*!
 * \ingroup i2c
 * \brief I2C-Interface: Daten an adressiertes Device senden
 *
 * Diese Funktion sendet ein Byte an ein zuvor adressiertes I2C-Device.
 *
 * \param data Zu übertragendes Datenbyte.
 * \retval Übertragungsstatus: 0 = Übertragung OK (ACK erhalten), 1 = Fehler bei Übertragung.
 */

uint8_t i2cWrite(uint8_t data)
{
  // Daten senden und auf Übertragungsende warten
  TWDR = data;
  TWCR = (1 << TWINT) | (1 << TWEN);
  while(!(TWCR & (1 << TWINT)));

  // Prüfe, ob Daten korrekt bestätigt wurden
  if(TW_STATUS == TW_MT_DATA_ACK)
    return 0;
  else
    return 1;
}


/*!
 * \ingroup i2c
 * \brief I2C-Interface: Daten von adressiertem Device empfangen
 *
 * Diese Funktion empfängt ein Byte von einem zuvor adressierten I2C-Device. Der Parameter gibt
 * an, ob nach der Übertragung ein ACK gesendet werden soll (1) oder nicht (0).
 *
 * \param ack Gibt an, ob nach der Übertragung ein ACK gesendet werden soll (1) oder nicht (0).
 * \retval Empfangenes Datenbyte
 */

uint8_t i2cRead(uint8_t ack)
{
  // Übertragung initialisieren und auf Fertigstellung warten
  if(ack == 1)
    TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN);
  else
    TWCR = (1 << TWINT) | (1 << TWEN);

  while(!(TWCR & (1 << TWINT)));

  // Empfangene Daten zurückgeben
  return TWDR;
}


