/******************************************************************** * FUNCTION MODULE: i2c_if.c * * I2C Serial Data Interface Module for Silabs C8051F133 * (c) Didier Juges www.ko4bb.com and Thorsten Godau ********************************************************************* * * DESCRIPTION: * * Serial I2C/SMBus bus access. * * This module is believed to be able to talk to any I2C compliant device. * It has been tested with: * - X64645 EEPROM (obsolete in 2003) * - M24C64 EEPROM * - MAX518 DAC * - MAX5821 DAC * - DS1629 Real Time Clock * - MAX1609 parallel I/O extender * * Note: this module includes two versions: * 1) a version using hardware transceiver and * 2) a version using bit-banging. * * Define appropriate compilation option I2C_HARDWARE or I2C_BIT_BANGED * **********************************************************************/ #define I2C_HARDWARE //#define I2C_BIT_BANGED /* * * REVISION LOG (don't use TABs for rev log): * * Date Name Version Reason * ------- ---------------- ------- ------------------------------- * 08-31-05 D.Juges 0.0.1 Initial design from Silabs and Internet * 09-30-05 D.Juges 0.2.1 bit-banged or hardware options * 11-17-14 D. Juges 0.2.2 Fixed bug in bit-banged I2CGetByte() *********************************************************************/ /*--------------------------------------------------------------------* * INCLUDE FILES *--------------------------------------------------------------------*/ #include "f120.h" #include "comdefs.h" #include "i2c_if.h" /*---------------------------------------------------------------------* * COMMON CONSTANTS *----------------------------------------------------------------------*/ #define ESMB0 2 // SMB interrupt enable bit in sfr EIE1 /*---------------------------------------------------------------------* * common public variables (used by either version) *---------------------------------------------------------------------*/ unsigned char xdata _i2c_error; unsigned char xdata smb_status; /*---------------------------------------------------------------------* * COMMON FUNCTION: I2CCheckError() (used by either version) * * Returns 0 if no error during previous I2C function, a non-zero * error code otherwise. *---------------------------------------------------------------------*/ unsigned char I2CCheckError( void ){ return( _i2c_error ); } /* end I2CCheckError() */ /*---------------------------------------------------------------------* * FUNCTION: I2CClearError() *----------------------------------------------------------------------*/ /*void I2CClearError( void ){ _i2c_error = 0; } /* end I2CClearError() */ #ifdef I2C_HARDWARE /*---------------------------------------------------------------------* * CONSTANTS *---------------------------------------------------------------------*/ #define I2C_INIT 0x40 // enable SMB transceiver #define AA_BIT 0x04 #define STO_BIT 0x10 #define STA_BIT 0x20 /*--------------------------------------------------------------------* * SMBus states for Master Tx or Rx: * * MT = Master Transmitter (Tx) * MR = Master Receiver (Rx) * ADD = Address transmitted * DB = Data Byte transmitted/received *----------------------------------------------------------------------*/ #define SMB_BUS_ERROR 0x00 // (all modes) BUS ERROR #define SMB_START 0x08 // (MT & MR) START transmitted #define SMB_RP_START 0x10 // (MT & MR) repeated START #define SMB_MTADDACK 0x18 // (MT) Slave address + W transmitted; // ACK received #define SMB_MTADDNACK 0x20 // (MT) Slave address + W transmitted; // NACK received #define SMB_MTDBACK 0x28 // (MT) data byte transmitted; ACK rec'vd #define SMB_MTDBNACK 0x30 // (MT) data byte transmitted; NACK rec'vd #define SMB_MTARBLOST 0x38 // (MT) arbitration lost #define SMB_MRADDACK 0x40 // (MR) Slave address + R transmitted; // ACK received #define SMB_MRADDNACK 0x48 // (MR) Slave address + R transmitted; // NACK received #define SMB_MRDBACK 0x50 // (MR) data byte rec'vd; ACK transmitted #define SMB_MRDBNACK 0x58 // (MR) data byte rec'vd; NACK transmitted #define BUF_LEN 14 unsigned char xdata buffer[BUF_LEN]; // ***TEST unsigned char data smb_buf_ndx; // ***TEST /* ----- local function prototypes ----- */ void clearbuffer( void ); /*=====================================================================* * GENERIC I2C FUNCTIONS *=====================================================================*/ /*---------------------------------------------------------------------* * I2CSendAddr() * * addr is an address between 0 and 255 * rd is either WRITE or READ * * - initiates START condition * - send device select address, with appropriate R/W bit as provided in rd * - wait for acknowledge from device * - if timeout before acknlowledge is received, set error flag *---------------------------------------------------------------------*/ void I2CSendAddr( unsigned char addr, unsigned char rd ){ int xdata i=0; EA = FALSE; // Disable global interrupt. if( ! rd ){ clearbuffer(); SMB0CN = I2C_INIT; /* SIO1 enable, with bit frequency set. */ } _i2c_error = 0; /* Clear error flag. */ smb_status = 0; SMB0CN = I2C_INIT | STA_BIT; /* SIO1 send START. */ do{ i++; }while( (! SI) && i>0 ); /* Hold here until Serial Interrupt FLag or */ // while( SI == 0 ); /* counter rollover. */ if( SMB0STA != SMB_START && SMB0STA != SMB_MTDBACK && SMB0STA != SMB_RP_START ){ smb_status= SMB0STA; _i2c_error = 0x01; // START not transmitted? I2CSendStop(); EA = TRUE; return; } if( rd ) /* I2C address fixup. */ addr++; SMB0DAT = addr; /* Load address into Data Register (which */ SMB0CN = I2C_INIT; /* intitiates transfer), SMB enabled */ i = 0; do{ i++; }while( (! SI) && (i) ); // Hold here until Serial Interrupt FLag or // counter rollover. if( SMB0STA == SMB_MTADDNACK ) // SLA + (W || R) has been transmitted, // NOT ACK has been received _i2c_error |= 0x02; // set error #2 if( SMB0STA == SMB_MRADDNACK ) _i2c_error |= 0x04; EA = TRUE; // Enable global interrupt. } /* end I2CSendAddress() */ /*---------------------------------------------------------------------* * I2CSendByte() * * Send Byte and wait for acknowledge * Set error flag if timeout before acknowledge received. *---------------------------------------------------------------------*/ void I2CSendByte( unsigned char byte_data ){ int xdata i=0; EA = FALSE; SMB0DAT = byte_data; SMB0CN = I2C_INIT; // Start Tx do{ i++; }while( (! SI) && i>0 ); // wait for Tx done or timeout if( SMB0STA != SMB_MTDBACK ) // No ACK received after sending data? _i2c_error |= 0x08; EA = TRUE; } /* end I2CSendByte() */ /*---------------------------------------------------------------------* * I2CGetByte() * * Receive Byte and acknowledge *---------------------------------------------------------------------*/ unsigned char I2CGetByte( void ){ int xdata i = 0; EA = FALSE; // Disable global interrupt SMB0CN = I2C_INIT | AA_BIT; /* SIO1 Enable, with ACK. */ do{ i++; }while( (! SI) && i>0 ); if( !i ) /* SI never turned on? */ _i2c_error |= 0x10; /* Yes, set error #4. */ EA = TRUE; /* Enable global interrupt. */ return( SMB0DAT ); } /* end I2CGetByte() */ /*---------------------------------------------------------------------* * FUNCTION: I2CGetLastByte() * * Receive byte and NAK *----------------------------------------------------------------------*/ unsigned char I2CGetLastByte( void ){ int xdata i = 0; EA = FALSE; SMB0CN = I2C_INIT; /* SIO1 Enable, no ACK. */ do{ i++; }while( (! SI) && i>0 ); if( !i ) /* SI never turned on? */ _i2c_error &= 0x20; /* Yes, set error #4. */ EA = TRUE; /* Enable global interrupt. */ return( SMB0DAT ); } // end I2CGetLastByte() /*---------------------------------------------------------------------* * I2CSendStop() *---------------------------------------------------------------------*/ void I2CSendStop( void ){ char xdata i = 0; SMB0CN = I2C_INIT | STO_BIT; /* SIO1 Enable, generate STOP. */ do{ i++; }while( i>0 ); // wait a little SMB0CN = 0; // reset transceiver } /* end I2CSendStop() */ /*---------------------------------------------------------------------* * I2C_Init() *----------------------------------------------------------------------*/ void I2C_Init( void ){ SMB0CN = I2C_INIT; // enable SMBus transceiver SMB0CR = -60; // 100 kHz EIE1 &= ~ESMB0; // disable SMB interrupts XBR0 |= 0x01; // enable SMBus crossbar // SMB0CN = I2C_INIT; /* SIO1 enable, with bit frequency set. */ } /* end I2C_Init() */ /*-----------------------------------------------------------------------------------* * FUNCTION: clearbuffer) *------------------------------------------------------------------------------------*/ void clearbuffer( void ){ char i; for(i=0;i 0 : acknowledge from Slave */ /* 1 : no acknowledge from Slave */ /*------------------------------------------------------------------*/ /* History : 11/99 V1.0 Basic routine */ /* */ /********************************************************************/ unsigned char i2c_check( void ){ EA = 0; // Disable all interrupts for accurate timing SDA = 1; // Release SDA SCL = 1; // Release SCL while( SCL == 0 ) ; // Synchronize clock i2c_delay(); // Force 5µs-delay if( SDA ){ // SDA is high SCL = 0; // Force a clock cycle _i2c_error |= 1; return( 1 ); // No acknowledge from Slave } SCL = 0; // Force a clock cycle i2c_delay(); // Force 5µs-delay EA = 1; // Enable all interrupts return( 0 ); // Acknowledge from Slave } /* end i2c_check() */ /********************************************************************/ /* Function : i2c_write(value) SUB */ /*------------------------------------------------------------------*/ /* Description : This subroutine writes a character to I2C. */ /* MASTER clock synchronisation (needed to handle */ /* SLAVE clock streching) is supported. */ /* (Low-level I2C routine) */ /*------------------------------------------------------------------*/ /* Author : Thorsten Godau NT8 */ /* (Based upon U24C08 EEPROM programming with C51 by */ /* Dimitry S. Obukhov dso@usa.net in 1996/97) */ /*------------------------------------------------------------------*/ /* Input : unsigned char value -> data to I2C device */ /*------------------------------------------------------------------*/ /* Returnvalue : unsigned char -> 0 : Acknowledge received */ /* 1 : No acknowledge received */ /*------------------------------------------------------------------*/ /* History : 11/99 V1.0 Basic routine */ /* */ /********************************************************************/ unsigned char i2c_write( unsigned char value ){ unsigned char idata c; // Bitcounter EA = 0; // Disable all interrupts for a accurate timing for( c = 8; c > 0; c-- ){ if( value & 0x80 ) SDA = 1; else SDA = 0; SCL = 1; // Release SCL SCL = 1; // make it a little longer SCL = 1; while( SCL == 0 ) ; // Synchronize clock i2c_delay(); // Force 5µs-delay SCL = 0; // Force a clock cycle i2c_delay(); value <<= 1; // Prepare next bit for transmission } EA = 1; // Enable all interrupts // Generate a 9th clock cycle and check ACK from SLAVE // Return the result return( i2c_check() ); } /* end i2c_write() */ /********************************************************************/ /* Function : i2c_read() SUB */ /*------------------------------------------------------------------*/ /* Description : This subroutine reads a character from I2C. */ /* MASTER clock synchronisation (needed to handle */ /* SLAVE clock streching) is supported. */ /* (Low-level I2C routine) */ /*------------------------------------------------------------------*/ /* Author : Thorsten Godau NT8 */ /* (Based upon U24C08 EEPROM programming with C51 by */ /* Dimitry S. Obukhov dso@usa.net in 1996/97) */ /*------------------------------------------------------------------*/ /* Input : none */ /*------------------------------------------------------------------*/ /* Returnvalue : unsigned char -> data from I2C device */ /*------------------------------------------------------------------*/ /* History : 11/99 V1.0 Basic routine */ /* */ /********************************************************************/ unsigned char i2c_read( void ){ unsigned char idata r = 0; // Return value with read I2C byte unsigned char idata c = 0; // Bit counter EA = 0; // Disable all interrupts for a accurate timing for( c = 8; c > 0; c-- ){ SDA = 1; // Release SDA SCL = 1; // Release SCL while( SCL == 0 ) ; // Synchronize clock i2c_delay(); // Force 5µs-delay r <<= 1; // Shift left the result if( SDA ) r |= 0x01; // Set actual SDA state to LSB SCL = 0; // Force a clock cycle i2c_delay(); // Force 5µs-delay } EA = 1; // Enable all interrupts // ACK or NACK from MASTER must be generated outside this routine return( r ); } /* end i2c_read() */ #endif // end i2c bit-banged