/**************************************************************************
* SMComm.C          Bit-bang master Smart Battery Interface for AVR
*
* This file contains an I2C Driver which implements an I2C bit-bang master
* in C on two Port pins. See I2C.H for more information.
*
* Assumes the following connections (but these are easily changed):
*
*                       SCL  SDA
*      .----.----.----.-----.----.----.----.----.
*      | -- | -- | SS*| SCK |MOSI|MISO| TX | RX |  Port D
*      `----^----^----^-----^----^----^----^----'
*        b7   b6   b5   b4    b3   b2   b1   b0
*
* The port is operated in wire-or mode so pull-up resisitors (~10k) will
* be needed on all 6 Port D pins.
*
* Also, it is assumed that the bit-banged clock pulses will be no faster
* than allowed by the I2C spec.  If an exceedingly fast micro is used, it
* may be necessary to set the CLOCKDELAY parameter to some non-zero value.
* Measure the bit times to make sure that the clock hi and lo pulse widths
* are at least 4.7us.
*
***************************************************************************
* Written by Don Carveth, Feb. 2001
*
* Based on I2C software by:
*
* Version 1.00
* (c) Copyright 2000 Grant Beattie
* This software is the property of Grant Beattie who specifically grants
* the user the right to modify, use and distribute this software provided
* this notice is not removed or altered.  All other rights are reserved.
* NO WARRANTY OF ANY KIND IS IMPLIED.  NO LIABILITY IS ASSUMED FOR
* INCIDENTAL OR CONSEQUENTIAL DAMAGES IN CONNECTION WITH OR ARISING FROM
* THE FURNISHING, PERFORMANCE, OR USE OF THIS SOFTWARE.
***************************************************************************/
#include <io.h>
#include <stdlib.h>
#include "SBComm.h"
#include "StdDefs.h"

/*
| Change or remove these to mate with your current CPU Register file(s).
*/
#define  I2CPORT         PORTD
#define  I2CDDR          DDRD
#define  I2CPIN          PIND
#define  SDAbit          2  //0x04     // Indicate the SDA bit on Port D.
#define  SCLbit          3  //0x08     // Indicate the SCL bit on Port D.


#define  CLOCKDELAY        50      // 0, Count value to ensure 4.7us pulse width.
#define  BIDIRECTIONALSCL  1      // Turns on/off SCL stretching.


#define  SCLTIMEOUT       255     // How long to wait on SCL stretching.
#define  SDATIMEOUT       255     // How many clocks to give on SDA hung.


 
/*
| Prototypes: Internal to this file only (private).
*/
void I2cStart(void);
void I2cStop(void);

void SdaLo(void);
void SdaHi(void);
void SclLo(void);
void SclHi(void);

unsigned char GetSda(void);
int I2cRead(void);
unsigned char I2cWriteByte(unsigned char);

#if  CLOCKDELAY
void I2cDelay(void);
#else
#define I2cDelay()
#endif

#if  BIDIRECTIONALSCL
unsigned char GetScl(void);
#else
#define GetScl() 1
#endif


/*
| Global variables (private).
*/

unsigned char SlaveAddr;
unsigned char ErrorI2C;

/*
| Global variables (public).
*/
// I2CPACKET gI2C;




/*
.--------------------------------------------------------------------------
| unsigned char I2cInit(void);
|
| Initialzes the I2C driver and pins (by sending a stop condition).  If one
| of the pins is jammed, returns failure.  Also clears gI2cError variable.
| This function should be called prior to the first I2C transaction.
|
| Pins In:  No requirements
| Pins Out: Both SCL and SDA are high if successful
|
| Requires: Nothing
| Returns:  0 on success, nonzero on failure
|           ErrorI2C is modified on failure
`--------------------------------------------------------------------------
*/
unsigned char I2cInit(void)
{
ErrorI2C = 0;


/*
| From reset it is assumed that Port D is set as inputs and there are 10k
| pullups on PD0 - PD5. No other devices should be pulling these lines down.
*/
//I2CPORT &= ~(SDAbit|SCLbit);           // Both outputs low - leave them low
cbi(I2CPORT, SDAbit);
cbi(I2CPORT, SCLbit);
                                  
SlaveAddr = 0x16;				// 0b0001 011X, Smart Battery address - 
								// last bit will be modified as R or W bit                                  

SclLo();
// if(I2CPIN & SCLbit)                // Only able to test register bit on HC11.
if(bit_is_set(I2CPIN, SCLbit))
   {
                              // (Cannot read pin when set as output)
   ErrorI2C |= I2CERR_BUS;
   return 1;
   }

SdaLo();                          // SDA low (data can change while the clock is low).
//if(I2CPIN & SDAbit)                // Only able to test register bit on HC11.
if(bit_is_set(I2CPIN, SDAbit))
   {
                             // (Cannot read pin when set as output)
   ErrorI2C |= I2CERR_BUS;
   return 1;
   }

//putchar('a');
//putBCD(ErrorI2C, 6, 1);
// It would only work with at least 3 statements here ???   
I2cDelay();             // Do a simulated stop, complete with correct timing.
I2cDelay();                         // Requires a 4.7us clock low period before stop.
I2cDelay();
I2cDelay();                         
//putchar('b');                        
//putBCD(ErrorI2C, 6, 1);
I2cStop();
//putchar('c');
//putBCD(ErrorI2C, 6, 1);

if(ErrorI2C)
   {
   //putchar('x');
   ErrorI2C |= I2CERR_BUS;
   //putBCD(ErrorI2C, 6, 1);
   return 1;
   }

return 0;
}



/*
.--------------------------------------------------------------------------
| void I2cDelay(void); 
|
| Provides the necessary 4.7us delay required for some of the I2C 
| transitions.  The actual delay time depends on the processor and it's
| crystal frequency.
|
| Pins In:  No requirements
| Pins Out: No changes
|
| Requires: Nothing
| Returns:  Nothing
`--------------------------------------------------------------------------
*/
#if CLOCKDELAY
void I2cDelay(void)
{
unsigned int count = CLOCKDELAY;

while(count)
   count--;
return;
}
#endif



/*
.--------------------------------------------------------------------------
| void I2cStart(void);
|
| Creates a start condition on the I2C pins (a START is defined as SDA
| going low while SCL is high), allowing for proper timing.
|
|
| Pins In:  Assumes both SCL and SDA are high.
| Pins Out: Both SCL and SDA are low.
|
| Requires: Nothing
| Returns:  Nothing
`--------------------------------------------------------------------------
*/
void I2cStart(void)
{
SdaLo();
I2cDelay();
SclLo();
I2cDelay();
}



/*
.--------------------------------------------------------------------------
| void I2cStop(void);
|
| Creates a stop condition on the I2C pins (a STOP is defined as SDA
| going high while SCL is high), allowing for proper timing.  If SDA is
| hung, an attempt is made to free the bus by clocking SCL until SDA is
| released.
|
| Pins In:  Assumes SCL is low (SDA indeterminate - holds last ACK bit).
| Pins Out: Both SCL and SDA are high if successful.
|
| Requires: Nothing
| Returns:  Nothing (ErrorI2C is modified on failure)
`--------------------------------------------------------------------------
*/
void I2cStop(void)
{
unsigned char count;

SdaLo();                          // SCL is initally low, OK to bring SDA
SclHi();                          // low in preparation for the stop.
I2cDelay();                       // Bring SCL high then SDA high for the
SdaHi();                          // STOP condition.
I2cDelay();

if( GetSda() )
   {      
   return;
   }
//putchar('d');
ErrorI2C |= I2CERR_NOSTOP;      // STOP error, SDA hung (low).
count = SDATIMEOUT;               // Someone is holding SDA low.
while(count)                      // Attempt to clock the bus out of the
   {                              // SDA hung state.
   SclLo();
   I2cDelay();
   SclHi();
   if( GetSda() )
      return;
   count--;
   }
}



/*
.--------------------------------------------------------------------------
| void SdaLo(void)
|
| Sets the SDA pin as an output and low.
| 
| Requires: Nothing
| Returns:  Nothing
`--------------------------------------------------------------------------
*/
void SdaLo(void)
{
//I2CDDR  |=  SDAbit;                 // SDA is an output (set direction).
sbi(I2CDDR, SDAbit);
}



/*
.--------------------------------------------------------------------------
| void SdaHi(void)
|
| Sets the SDA pin as an output and high.
| 
| Requires: Nothing
| Returns:  Nothing
`--------------------------------------------------------------------------
*/
void SdaHi(void)
{
//I2CDDR &= ~SDAbit;                  // Making SDA an input allows it to go high.
cbi(I2CDDR, SDAbit);
}



/*
.--------------------------------------------------------------------------
| void SclLo(void)
|
| Sets the SCL pin as an output and low.
|
| Requires: Nothing
| Returns:  Nothing
`--------------------------------------------------------------------------
*/
void SclLo(void)
{
#if  BIDIRECTIONALSCL
I2CDDR  |= SCLbit;                  // SCL is an output (set direction).
sbi(I2CDDR, SCLbit);
#endif
}



/*
.--------------------------------------------------------------------------
| void SclHi(void); 
|
| Releases SCL and verifies that it is high before returning (to allow for
| clock-stretching peripherals).  If SCL is frozen low, re-tries until
| finally giving up on a timeout.
|
| Pins In:  Assumes SCL is low.
| Pins Out: Sets SCL high if successful
|
| Requires: Nothing 
| Returns:  Nothing (ErrorI2C is modified on failure)
`--------------------------------------------------------------------------
*/
#if  BIDIRECTIONALSCL
void SclHi(void)
{
unsigned char count;

//I2CDDR  &= ~SCLbit;                 // Making SCL an input allows it to go high.
cbi(I2CDDR, SCLbit);
//if(I2CPIN & SCLbit)                // Release SCL, if it goes high, exit OK.
if(bit_is_set(I2CPIN, SCLbit))
   return;

count = SCLTIMEOUT;               // Someone is holding it low.
while(count)                      // Give them until the TIMEOUT val or
   {                              // else fatal bus hung error.
   //if(I2CPIN & SCLbit)
   if(bit_is_set(I2CPIN, SCLbit))
      return;
   count--;
   }
ErrorI2C |= I2CERR_BUS;
}
#else
void SclHi(void)
{
//I2CDDR  &= ~SCLbit;                  // SCL is high (set level first).
cbi(I2CDDR, SCLbit);
}
#endif



/*
.--------------------------------------------------------------------------
| unsigned char GetSda(void)
|
| Returns the bit value of the SDA pin.
|
| Requires: Nothing
| Returns:  0 on pin low, non-zero on pin high
`--------------------------------------------------------------------------
*/
unsigned char GetSda(void)
{
//return(I2CPIN & SDAbit);
return(I2CPIN & (0x01 << SDAbit));
}




/*
.--------------------------------------------------------------------------
| unsigned char GetScl(void)
|
| Returns the bit value of the SCL pin.
|
| Requires: Nothing
| Returns:  0 on pin low, non-zero on pin high
`--------------------------------------------------------------------------
*/
#if  BIDIRECTIONALSCL
unsigned char GetScl(void)
{
return(I2CPIN & SCLbit);
return(I2CPIN & (0x01 << SCLbit));
}
#endif


/*
.--------------------------------------------------------------------------
| unsigned char I2cRead(void);
|
| Reads and returns two bytes from the battery
|
| Pins In:  Assumes Both SCL and SDA are low.
| Pins Out: SDA high, SCL low.  (A stop is required after.)
|
| Requires: Nothing
| Returns:  16 bit Integer value representing Smart Battery Data
`--------------------------------------------------------------------------
*/
int I2cRead(void)
{
unsigned char mask;
unsigned char value;
unsigned char SBDataL, SBDataH;
int SBData;
unsigned char index = 2;
while(index)
	{
   mask  = 0x80;
   value = 0x00;
   while(mask)                    // Do the 8 data bits...
      {
      SclHi();                    // Set SCL and wait for it to go hi.
      I2cDelay();
      if( GetSda() )              // Read the bit, and if high set it in
         value |= mask;           // the returned byte.
      SclLo();                    // Bring SCL low again to complete bit.
      I2cDelay();
      mask >>= 1;
      }

	if(index - 1)
		{
		SBDataL = value;
		
   		SdaLo();                    // Bring SDA low for ACK.
   		SclHi();                    // Clock high.
   		I2cDelay();
   		SclLo();                    // Clock low.
   		SdaHi();                    // Release SDA.
   		I2cDelay();		
   		}
	else
		SBDataH = value;
		
   index--;
	}

SdaHi();                          // SDA high for NACK on last byte.
SclHi();                          // Clock high.
I2cDelay();
SclLo();                          // Clock low.
I2cDelay();

SBData = SBDataH;
SBData = SBData << 8;

return SBDataL + SBData;
}


/*
.--------------------------------------------------------------------------
| unsigned char I2cWriteByte(unsigned char byte);
|
| Write one to the I2C port (it is the I2C address).
|
| Pins In:  Assumes Both SCL and SDA are low.
| Pins Out: SDA indeterminate, SCL low.
|
| Requires: addr - the address of chars to send.
| Returns:  0 on success, 1 on failure
|           ErrorI2C is modified on failure.
`--------------------------------------------------------------------------
*/
unsigned char I2cWriteByte(unsigned char byte)
{
unsigned char mask = 0x80;
   
while(mask)                    // Do the 8 data bits...
   {
   if(byte & mask)             // Set one bit (order is msb to lsb).
   		// putchar('H');		// TESTING
      SdaHi();
   else
   	   // putchar('L');			// TESTING
      SdaLo();
   SclHi();                    // Set SCL and wait for it to go hi.
   I2cDelay();
   SclLo();                    // Bring SCL low again to complete bit.
   I2cDelay();
   mask >>= 1;
   }

SdaHi();                       // Release SDA, let slave control it.
SclHi();                       // Set SCL and wait for it to go hi.
I2cDelay();

if( GetSda() )                 // Check the acknowledge bit.
   {
   SclLo();                    // No ACK, finish SCK (back low again).
   I2cDelay();                 // Return failure (No ACK).
   // putchar('1');				// TESTING
   ErrorI2C |= I2CERR_NOACK;
   return 1;                      
   }
      
SclLo();                       // ACK OK, finish SCK (back low again).
I2cDelay();                    // Return success (ACK OK).
return 0;
}



/*
.--------------------------------------------------------------------------
| unsigned char SBRead(command);
|
| Transfers the two data bytes from the intended target. 
|
| Pins In:  Assumes Both SCL and SDA are high.
| Pins Out: SCL, SDA high if successful.
|
| Requires: command - from the Smart Battery command chart.
|
| Returns:  Data as 2 byte integer
`--------------------------------------------------------------------------
*/
int SBRead(unsigned char command)
{
int SBData;

ErrorI2C = 0;

if((GetSda()==0) || (GetScl()==0))// Both bits should be high on entrance.
   {
   //putchar('h');
   ErrorI2C |= I2CERR_BUS;      // Will require a re-init or h/w fix.
   return 1000;
   }


I2cStart();                       // Send START condition.
if( I2cWriteByte(SlaveAddr) )          // Send SB address - write mode.
   {
   I2cStop();                     // Address NACK'd, do STOP & exit failure.
   return 2000;
   }

if( I2cWriteByte(command) )          // Send SB command.
   {
   I2cStop();                     // Address NACK'd, do STOP & exit failure.
   return 3000;
   }

SdaHi();                          // Release SDA while clock low.
I2cDelay();
SclHi();                          // Release SCL, can now do a START.
I2cDelay();
I2cStart(); 					// Send Repeated START condition.

if( I2cWriteByte(SlaveAddr + 1) )          // Send I2c address.
   {
   I2cStop();                     // Address NACK'd, do STOP & exit failure.
   //putchar('c');
   return 3000;
   }
SBData = I2cRead();				// Read the data word - 2 bytes

/*
| Finally, do the STOP.
*/
I2cStop();

return SBData;
}


/*
.--------------------------------------------------------------------------
| unsigned char SBWrite2Bytes(unsigned char command, unsigned char byteLSB, unsigned char byteMSB);
|
| Transfers the two data bytes to the Smart Battery. 
|
| Pins In:  Assumes Both SCL and SDA are high.
| Pins Out: SCL, SDA high if successful.
|
| Requires: command - from the Smart Battery command chart.
|			byteLSB - Least Significant Byte of data
|			byteMSB - Most Significant Byte of Data
|
| Returns:  0 on success, 1 on failure
|           ErrorI2C is modified on failure.
`--------------------------------------------------------------------------
*/
unsigned char SBWrite2Bytes(unsigned char command, 
						unsigned char byteLSB, unsigned char byteMSB)
{

	if((GetSda()==0) || (GetScl()==0))// Both bits should be high on entrance.
	   {
	   ErrorI2C |= I2CERR_BUS;      // Will require a re-init or h/w fix.
	   return 1;
	   }

	I2cStart();                       // Send START condition.
	if( I2cWriteByte(SlaveAddr) )     // Send SB address - write mode.
	   {
	   I2cStop();                     // Address NACK'd, do STOP & exit failure.
	   return 1;
	   }

	if( I2cWriteByte(command) )       // Send SB command.
	   {
	   I2cStop();                     // Address NACK'd, do STOP & exit failure.
	   return 1;
		}
		
	if( I2cWriteByte(command) )       // Send byteLSB data.
	   {
	   I2cStop();                     // Address NACK'd, do STOP & exit failure.
	   return 1;
		}
		
	if( I2cWriteByte(command) )       // Send byteMSB data.
	   {
	   I2cStop();                     // Address NACK'd, do STOP & exit failure.
	   return 1;
		}
		
	I2cStop();						  // Stop	
   return 0;		
}

						
/*
.--------------------------------------------------------------------------
| unsigned char SBWriteInt(unsigned char command, int IntVal);
|
| Transfers the 16 bit positive integer as two data bytes to the Smart Battery. 
|
| Pins In:  Assumes Both SCL and SDA are high.
| Pins Out: SCL, SDA high if successful.
|
| Requires: command - from the Smart Battery command chart.
|			IntVal - Signed 16 bit Integer value
|
| Calls:    SBWrite2Bytes(Command, ByteLSB, ByteMSB)
|
| Returns:  0 on success, 1 on failure
|           ErrorI2C is modified on failure.
`--------------------------------------------------------------------------
*/
void SBWriteInt(unsigned char Command, int IntVal)
{
	unsigned char LSB, MSB;
	LSB = IntVal & 0xFF;
	MSB = (IntVal << 8) & 0xFF;
	SBWrite2Bytes(Command, LSB, MSB);
}


/*
.--------------------------------------------------------------------------
| unsigned char I2cGetLastError(void);
|
| Returns the value of the ErrorI2C variable.  The results refer to only
| the last I2C transaction and are cleared when a new transaction begins.
|
| Requires: Nothing
| Returns:  The ErrorI2C var
`--------------------------------------------------------------------------
*/
unsigned char I2cGetLastError(void)
{
return ErrorI2C;
}



