/************************************************************************** * 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 #include #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; }