/*************************************************************************** * Copyright (C) 2008, Crawlerz * * * * Description: * * Low-level Software I2C Master "C" code snippets * * 330Kbps, 360B CODE SPACE (EZ-USB FX2, Keil C51) * * Version: * * 2008.35.2 * * Notes: * * You may wish to disable interrupts during read/write calls * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ BYTE I2C_return; // return code from I2C routines #define I2C_no_ack 1 // error flag, in I2C_return variable #define I2C_bus_fault 2 // error flag, in I2C_return variable #define I2C_busy 4 // error flag, in I2C_return variable // TODO CHANGE for your target hardware #define CPUSPEED 48 // X in MHz... enter 12, 24 or 48 (div4 8052 core) #define SLAVE_ADDRESS 0xA1 // EEPROM address 0xA1 #define MAX_I2C_TRIES 4 // maximum number (+1) of retries allowed after I2C bus error #define SDA PB5 // SDA pin location #define SDA_LO SDA = 0, OEB |= 0x20 // make the sda pin a low output #define SDA_HI OEB &= ~0x20 // make the sda pin an input #define SCL PB7 // SCL pin location #define SCL_LO SCL = 0, OEB |= 0x80 // make the scl pin a low output #define SCL_HI OEB &= ~0x80 // make the scl pin an input #define RDSDA PB5 // assumes pin(s) are already an input #define RDSCL PB7 // assumes pin(s) are already an input #define I2C_delay // if needed, add I2C_sw_delay() here BYTE I2C_stream[ 64 ]; // holds the data sent to/received from the I2C bus // Prototypes BYTE I2C_send_stream( BYTE slave_address, BYTE *p_source, BYTE byte_count ); BYTE I2C_rcv_stream( BYTE slave_address, BYTE *p_dest, BYTE byte_count ); void I2C_begin( BYTE slave_address ); void I2C_send_byte( BYTE sendme ); BYTE I2C_rcv_byte( BYTE byte_count ); void I2C_send_stop( void ); void I2C_scl_high( void ); void I2C_sw_delay( void ); ////////////////////////////////////////////////////////////////////////////// // // // I2C Bus Read and Write: // // ---------------------- // // // // I2C_send_stream and I2C_rcv_stream return a non-zero error code // // if any errors occurred. If this error code is received, the // // function should be re-called. // // // ////////////////////////////////////////////////////////////////////////////// // // Send a stream of data. Returns 0x00 if no errors, otherwise returns a // non-zero value. // // Sends "byte_count" bytes from "*p_source" to the device at slave address // "slave_address". Note that "slave_address" is actually the 7-bit slave // address left-shifted by one (e.g., 0xA0 for a 24C02 EEPROM). // BYTE I2C_send_stream( BYTE slave_address, BYTE *p_source, BYTE byte_count ) { I2C_begin( slave_address ); // Send START and the slave address. while( byte_count-- && ~( I2C_return & I2C_no_ack & I2C_bus_fault ) ) { I2C_send_byte( *p_source++ ); // Send a stream of bytes. } I2C_send_stop( ); // Send STOP. return( I2C_return ); // Return any error codes. } // // Receive a stream of data. // // Receives "byte_count" bytes from the device at slave address // "slave_address", and store it at "*p_source". Note that "slave_address" is // actually the 7-bit slave address left-shifted by one (e.g., 0xA0 for a 24C02 // EEPROM). Note also that this function sets the "read" bit of that slave // address automatically (i.e., this function should not be called with // slave_address set to 0xA1). // BYTE I2C_rcv_stream( BYTE slave_address, BYTE *p_dest, BYTE byte_count ) { I2C_begin( ++slave_address ); // Send START and the slave // address (with the "read" bit // set). while( byte_count-- && !( I2C_return & ( I2C_no_ack | I2C_bus_fault ) ) ) { *p_dest++ = I2C_rcv_byte( byte_count ); // Receive a stream of bytes. } I2C_send_stop( ); // Send STOP. return( I2C_return ); // Return any error codes. } // // Acquire the bus and send the start signal and slave address. // // If there's a bus fault, this function attempts to clear it before // sending the start signal and slave address. If unsuccessful, // we return with I2C_return's I2C_bus_fault bit set. // void I2C_begin( BYTE slave_address ) { BYTE recovery_clocks; // Holds the number of clocks // we'll send in an attempt to // clear any bus faults. I2C_return |= I2C_busy; // Assume bus is busy. I2C_return &= ~I2C_no_ack & ~I2C_bus_fault; // Assume no bus fault and no // ack failure. // At this point, SCL and SDA should be in the idle (high) state. if( !RDSCL || !RDSDA ) { // If the bus is locked up (i.e., // if SCL or SDA is low), attempt // to unlock it by sending dummy // clock pulses: I2C_return |= I2C_bus_fault; // Set the bus-fault bit. SDA_HI; // Make sure we've released SCL_HI; // both lines. if( RDSCL ) { // If nothing's holding the // SCL line low... recovery_clocks = 9; // Send up to 9 clocks, // reading the SDA line while( !RDSDA && recovery_clocks ) { // before each one. Stop // clocking when SDA is I2C_scl_high( ); // released (i.e., when it I2C_delay; // goes high). // SCL_LO; // I2C_delay; // // --recovery_clocks; // } // I2C_scl_high( ); // Leave the SCL line in the // idle (high) state. } if( RDSCL && RDSDA ) { // If the bus is now // unlocked... I2C_send_stop( ); // Send a STOP signal. I2C_return &= ~I2C_bus_fault; // Clear the bus-fault bit. } } if( !( I2C_return & I2C_bus_fault ) ) { // If no bus fault... SDA_LO; // Send a START signal. I2C_delay; // SCL_LO; // I2C_delay; // // I2C_send_byte( slave_address ); // Send the slave address. } } // // Send a byte and check for an ACK. If no ACK, return with // I2C_return's I2C_no_ack bit set. // // void I2C_send_byte( BYTE sendme ) { BYTE count, bit_mask; for( count = 8, bit_mask = 0x80; // Examine each of the 8 bits of count != 0; // the "sendme" byte: --count, bit_mask >>= 1 ) { // if( sendme & bit_mask ) { // Set SDA if the current bit // is 1, clear SDA if it's 0. SDA_HI; // } else { // SDA_LO; // } // I2C_scl_high( ); // Clock that bit onto the I2C_delay; // bus. // SCL_LO; // I2C_delay; // } SDA_HI; // Release SDA and start clocking I2C_scl_high( ); // in the ACK signal that we I2C_delay; // expext to receive from the // slave device. // Slave device should signal ACK at this point, by pulling SDA low. if( RDSDA ) { // If no ACK... I2C_return |= I2C_no_ack; // Set the no-ack bit. } SCL_LO; // Finish clocking in the ACK. I2C_delay; // } // // Receive a byte and return it to the calling function. I2C requires that we // send an ACK after receiving every byte except the last one in a string -- we // signal that we're not going to read another byte by NOT ACKing the last byte // we receive -- so enter with byte_count non-zero to send an ACK, or // byte_count equal to zero to NOT send an ACK. // BYTE I2C_rcv_byte( BYTE byte_count ) { BYTE bit_count, rcvd_byte; for( bit_count = 8; // Receive 8 bits: bit_count != 0; // --bit_count ) { // I2C_scl_high( ); // Start clocking a bit in. I2C_delay; // rcvd_byte <<= 1; // Assume this bit will be a 0. if( RDSDA ) { // If it's a 1.... rcvd_byte |= 1; // Set the appropriate bit // in rcvd_byte. } SCL_LO; // Finish clocking that bit. I2C_delay; } if( byte_count ) { // If this isn't the last byte // we'll be receiving... SDA_LO; // Signal ACK. } else { // Otherwise... SDA_HI; // Don't. } I2C_scl_high( ); // Clock the ACK bit out. I2C_delay; // // SCL_LO; // SDA_HI; // Release SDA. I2C_delay; // return( rcvd_byte ); // Return the byte we received. } // // Send a "Stop" signal. Clears I2C_return's I2C_busy flag. // void I2C_send_stop( void ) { SDA_LO; // Send a STOP signal. I2C_scl_high( ); // I2C_delay; // // SDA_HI; // I2C_delay; // I2C_return &= ~I2C_busy; // Now that we're done with this // transaction, clear the // I2C_busy flag. } // // Attempt to pull SCL high, then wait while any clock-stretching // slave devices hold it low, then exit when it finally goes high. // void I2C_scl_high (void) { SCL_HI; while( !RDSCL ) { // This infinite loop might not be the // most defensive way to wait, but it's ; // the easiest. If it bothers you, wrap } // a timer around it and set "I2C_bus_fault" // if it times out. } // // Delay approximately 6 microseconds. // // This function uses the #defined value of CPUSPEED to produce a // more-or-less constant delay of approximately 6 microseconds, // whether the CPU is running at 12, 24, or 48 MHz. // void I2C_sw_delay( void ) { BYTE i; for( i = 0; i < ( CPUSPEED / 4 ); i++ ) { _nop_( ); // nop; +1 } // inc; +1 } // cjne; +4 = 6i/c