/****************************************************************************
      i2c.c
      v1.0 (16/04/2003)
      Software implementation of the I2C protocol
      Low level I2C I/O functions for HI-TECH PIC C - master mode only.
      Input and output may be on the same resister. You need to edit i2c.h to 
      define SCL (clock) and SDA (data) pin.
      Written by Philippe Corbes <philippe.corbes@laposte.net>
      This code is free for personal use. 
      You need my agreement for a commercial use. 
  ****************************************************************************/
  #include    <pic.h>
  #include    "delay.h"
  #include    "i2c.h"
  signed char
  i2c_start(void)
  {
      /*---------------------------------------------------------------------------
       *  Send (re)start condition
       *  Check clock lines at each states
       *  Return: - I2C_BUSY on bus busy
       *          - I2C_OK if bus free
       *             ____________
       *  scl       /            \
       *      _____/              \
       *        __________
       *  sda \/          \ 
       *      /            \_______
       *            x    x      x    Check bus, return I2C_BUSY on error
       *---------------------------------------------------------------------------
       */
      DelayUs(I2C_TBUF);              /* bus free time before a restart */
      SDA_HIGH();                     /* data is high */
      DelayUs(I2C_TSU_DAT);
      SCL_HIGH();                     /* clock is high */
      if(i2c_SCLTimeout() || (!SDA))  /* wait for clock and data released */
          return I2C_BUSY;            /* bus busy by an other node */
      DelayUs(I2C_TSU_STA);
      if((!SCL) || (!SDA))            /* Check if bus always free */
          return I2C_BUSY;            /* bus busy by an other node */
      SDA_LOW();                      /* data high->low transition */
      DelayUs(I2C_THD_STA);
      if(!SCL)                        /* Check only clock */
          return I2C_BUSY;            /* bus busy by an other node */
      SCL_LOW();                      /* clock low */
      return I2C_OK;
  }
  void
  i2c_stop(void)
  {
      /*---------------------------------------------------------------------------
       *  Send stop condition
       *  It's possible to begin a new transaction just after.
       *                   ______________
       *  scl \           /
       *      /\_________/
       *      ___               _________
       *  sda    \             /
       *      ___/\___________/
       *---------------------------------------------------------------------------
       */
      SCL_LOW();              /* cock is low */
      DelayUs(I2C_TSU_DAT);
      SDA_LOW();              /* data is low first */
      DelayUs(I2C_TLOW);      /* clock low time */
      SCL_HIGH();             /* cock is high */
      DelayUs(I2C_TSU_STO);
      SDA_HIGH();             /* float data high */
      return;
  }
  unsigned char
  i2c_SCLTimeout(void)
  {
      /*---------------------------------------------------------------------------
       *  wait for the clock line to be released by slow slaves
       *    - returns TRUE if SCL was not released after the
       *      time out period.
       *    - returns FALSE if and when SCL released
       *---------------------------------------------------------------------------
       */
      if(!SCL) {
          DelayUs(I2C_TTR);
          /* if the clock is still low -> bus error */
          if(!SCL)
              return 1;
      }
      return 0;
  }
  signed char
  i2c_write(unsigned char abyte)
  {
      /*---------------------------------------------------------------------------
       *  Send a byte to the slave and acknowledges the transfer
       *  Return: - I2C_ERROR on bus error
       *          - I2C_ACK_TIMEOUT on ack timeout
       *          - I2C_OK if ack received from slave
       *
       *---------------------------------------------------------------------------
       *  SEND a byte to the slave
       *  Exit with I2C_ERROR on bus error
       *            ________            _____ .. __
       *  scl      /        \          /           \
       *      ____/          \________/             \___
       *       __________________  __________ .. _______
       *  sda /                  \/                     
       *      \__________________/\__________ .. _______
       *      Write Bit 7         Write Bit 6 .. bit0
       *            x                   x Check clock, return I2C_ERROR on error
       * 
       *  THEN If Byte sent, Check for an acknowledge
       *              ________   
       *  scl        /        \ 
       *      ______/          \
       *        ________________
       *  sda  /                  
       *      /
       *                     X Read Ack
       *             x         Check clock, return I2C_ERROR on error
       *---------------------------------------------------------------------------
       */
      volatile int i;
      /* SEND a byte to the slave */
      for(i=7; i>=0; i--) {
          /* data hold time = 0, send data now */
          if ((abyte>>i)&0x01) {      /* bit to send */
              SDA_HIGH();
          } else {
              SDA_LOW();
          }
          DelayUs(I2C_TSU_DAT);
          SCL_HIGH();                 /* clock high */
          if(i2c_SCLTimeout())        /* wait for clock release */
              return I2C_ERROR;       /* bus error */
          DelayUs(I2C_THIGH);         /* clock high time */
          SCL_LOW();                  /* clock low */
          DelayUs(I2C_TLOW - I2C_TSU_DAT);
      }
      /* THEN If Byte sent, Check for an acknowledge */
      SDA_HIGH();                     /* disable data line - listen for ack */
      DelayUs(I2C_TSU_DAT);           /* SCL low to data out valid */
      SCL_HIGH();                     /* float clock high */
      if(i2c_SCLTimeout())            /* wait for clock release */
          return I2C_ERROR;
      DelayUs(I2C_THIGH);
      if (!SDA) {                     /* read the acknowledge */
          i = I2C_OK;
      } else {
          i = I2C_ACK_TIMEOUT;
      }
      SCL_LOW();                      /* clock low */
      return i;
  }
  int
  i2c_read(unsigned char more)
  {
      /*---------------------------------------------------------------------------
       *  Read a byte from the slave and acknowledges the transfer
       *  Return: - the byte
       *          - I2C_ERROR on bus error
       *---------------------------------------------------------------------------
       *  READ a byte from the slave
       *    - returns the byte, or I2C_ERROR if a bus error
       *             ________            ____ .. __
       *  scl       /        \          /          \
       *      _____/          \________/            \___
       *        _____________________________ .. _______
       *  sda  /                                      
       *      /             X Read bit 7          X Read bit 0            
       *             x                   x  Check clock, return I2C_ERROR on error
       *
       *  THEN Send an (~)acknowledge to the slave
       *    - status of I2C_LAST implies this is the last byte to be sent
       *            ________              ________
       *  scl      /        \            /        \
       *      ____/          \      ____/          \
       *                        OR    ______________
       *  sda \                      /                 
       *       \______________      /
       *      Write Ack         OR  Write Nack
       *            x                     x Check clock, return I2C_ERROR on error
       *---------------------------------------------------------------------------
       */
      volatile unsigned char i;
      volatile unsigned char abyte;
      /* READ a byte from the slave */
      SDA_HIGH();                     /* release data line */
      for(i=0; i<8; i++) {
          DelayUs(I2C_TSU_DAT);       /* min clock low  period */
          SCL_HIGH();                 /* float clock high */
          if(i2c_SCLTimeout())
              return I2C_ERROR;
          DelayUs(I2C_THIGH);
          abyte = abyte << 1;         /* read the next bit */
          abyte |= SDA;
          SCL_LOW();
          DelayUs(I2C_TLOW - I2C_TSU_DAT);
      }
      /* THEN Send an (~)acknowledge to the slave  */
      if ( more & 0x01) {
          SDA_LOW();                  /* drive line low -> more to come */
      } else {
          SDA_HIGH();
      }
      DelayUs(I2C_TSU_DAT);
      SCL_HIGH();                     /* float clock high */
      if(i2c_SCLTimeout())
          return I2C_ERROR;
      DelayUs(I2C_THIGH);
      SCL_LOW();
      return (int)abyte;
  }