I2C

From wikipost
Revision as of 11:01, 16 March 2014 by Admin (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.


various:

i2c-master

i2c-slave


I'm currently working on coding a reliable I2C library that can act as a master, slave or both.

The current library is not a stand-alone file, but needs to be included in the code. Below is the latest (not fully operational) i2cmaster.h, use at own risk.


// --- I2C Functions ---
// --- I2C Functions ---
// --- I2C Functions ---

void i2cpin(int line, int state)
{
  // set the I2C SDA or SCL PIN
  // works on the PORTB registers
  
  if (state == 0)
  {
    // set the pin low

    DDRB |= (1 << line);     // set line to output
    PORTB &= ~(1 << line);   // set it to zero

  } else {

    // set the pin high (let external pull-up resistor bring it high)

    DDRB &= ~(1 << line);  // set line to input
   
  }

  //_delay_ms(500);   // use this as a debug tool
 

}

void i2c_delay()
{
  // default delay in between I2C commands
  _delay_ms(10);   // seems to hover around 2-3ms
}


void i2c_tx(char d)
{

  // SDA is LOW, SCL is HIGH (inherited from i2c-start command)

  // bring clock low to start data transfer

  i2cpin(SCL,LOW);

     
  
  char x;
  //static int b;
  for(x=8; x; x--) 
  { 
    if(d&0x80) 
    {
      i2cpin(SDA,HIGH);  
    } else {
      i2cpin(SDA,LOW);  
    }

    i2cpin(SCL,HIGH);                  // strobe clock


    // allow for I2C clock stretching (SCL can be held low by slave)
    // see I2C_STRETCH_TIMEOUT_MS

    int stretch_counter;
    
    for (stretch_counter=0; stretch_counter < I2C_STRETCH_TIMEOUT_MS; stretch_counter++)
    {
      // allow for some cycles for SCL to go high  

      if (bit_is_clear(PINB,SCL))
      {
        // SCL is held low by slave
        // we'll just wait a little 

        _delay_ms(1);

      } else {

        // SCL has risen, ready to move on
        stretch_counter = I2C_STRETCH_TIMEOUT_MS;
      }


    }

    //_delay_us(1);                      // 1-3 us  
    i2cpin(SCL,LOW);

    d <<= 1;                 // shift byte

  }

  i2cpin(SDA,HIGH);         // first bring SDA into listening mode

  //_delay_ms(1);             // delay no longer needed, this used to be 1.6ms
  //_delay_us(600);

  // read acknowledge bit if any

  // an addressed i2c slave will have already set SDA low
  _delay_us(5);             // give the bus some time to possibly bring the pin high
  i2cpin(SCL,HIGH);         // start the clock pulse

  _delay_us(5);             // this should be 10us or so

  if (bit_is_set(PINB,SDA))
  {
    got_ack=0;
  } else {
    got_ack=1;
  }

  i2cpin(SDA,LOW);         // we now bring SDA low 
  _delay_us(10);          // give the bus some time?    ----- may go 

  i2cpin(SCL,LOW);          // end the clock pulse

  // slave detects SCL is low and will release SDA 


  _delay_ms(3);    // this should be 10us or so


  // return b;

}



void i2c_start(char i2c_id)
{
  // SDA goes from high to low while SCL is high

  i2cpin(SDA,HIGH); 
  _delay_us(10);    // give bus some time to bring SDA high
  i2cpin(SCL,HIGH);

  _delay_us(10);    

  i2cpin(SDA,LOW);
  _delay_us(10);    


  // send the I2C Device ID

  // send as Write (last bit is 0)

  
  i2c_tx(i2c_id<<1);



}

void i2c_stop()
{
  // SDA goes from low to high while SCL is high

  i2cpin(SDA,LOW);
  _delay_us(20);    // give bus some time to bring SDA high
  i2cpin(SCL,LOW); 

  _delay_ms(6);

  i2cpin(SCL,HIGH);
  _delay_ms(6);

  i2cpin(SDA,HIGH);
  _delay_ms(4);

}


void i2c_lcd_text(char *StrData)
{

  int p = 0;
  int q = strlen(StrData);
  int temp = 0;

  for (p = 0; p < q; p++)
  {
    temp = StrData[p];
    i2c_tx(temp);
  }
}

void i2c_lcd_number(int number)
{
  int digits;

  if (number == 0)
  {
    digits = 0;
    int array[0];
    array[0]=0;
    i2c_tx(array[0]+48);

  } else {

    // determine the number of digits
    digits = (int) (log(number)/log(10))+1;

    // split up the number's digits into an array
    int i = digits -1;
    int array[digits];
    while (number > 0)
    {
      array[i--] = number % 10;
      number /= 10;
    }

    // send array over i2c
    for (i =0; i <= digits-1; i++)
    {
      i2c_tx(array[i]+48);
    }

  }
}

void i2c_lcd_hex(uint8_t x)
{

  // sends one byte in hex (without 0x prefix) to the lcd

  char nibble;

  /* extract high nibble */
  nibble = x & 0xf0;        // (resets lower nibble to all zero's)
  nibble = x >> 4;          // (moves high nibble to lower nibble)

  // output the high nibble
  if(nibble < 10)
  {
    i2c_tx(48 + nibble);
  } else {
    i2c_tx(65 + nibble - 10);
  }

  /* do the same on the low nibble */
  nibble = x & 0x0f;

  if(nibble < 10)
  {
    i2c_tx(48 + nibble);
  } else {
    i2c_tx(65 + nibble - 10);
  }

}


usage:

in main()

    // -- set up lines for I2C --
    pinMode(SDA,INPUT);
    pinMode(SCL,INPUT);
    PORTB = 0;    // disable internal pullup resistors
    PORTB &= ~(1 << SDA);  // set SDA LOW (makes line output)
    PORTB &= ~(1 << SCL);  // set SCL LOW (makes line output)


    i2c_start(LCD_I2C_DEVICE_ID);
    i2c_tx(12); //clear screen
    i2c_tx(' ');
    i2c_lcd_number(voltage_int); // measured voltage, whole numbers 
    i2c_tx('.');
    i2c_lcd_number(voltage_dec1); // decimal 1
    i2c_lcd_number(voltage_dec2); // decimal 2
    i2c_tx('V');
    i2c_tx(' ');
    i2c_tx(' ');
    i2c_lcd_number(run_counter);   
    i2c_stop();   



/*

  i2cmaster.h

  defines functions for bit-banging the I2C protocol on generic I/O pins

  Author: M. Post
  Last modified: 28 January 2012



  Add the following to your main.c in order to make use of this header file:

  
  #define HIGH      1
  #define LOW       0
  #define INPUT     1
  #define OUTPUT    0

  // I2C pin definitions
  #define SCL  PB0    // Define Clock pin  
  #define SDA  PB1    // Define Data pin  
  int got_ack;
  int LCD_I2C_DEVICE_ID=58;
  int I2C_STRETCH_TIMEOUT_MS=100;

  <other includes>

  #include "i2cmaster.h"

  <defines, rest of code, etc..>

*/


void setPin(int line, int state)
{
  // set the I2C SDA or SCL PIN

  if (state == LOW)
  {
    // set the pin low

    DDRB |= (1 << line);     // set line to output
    PORTB &= ~(1 << line);   // set it to zero

  } else {

    // set the pin high (let external pull-up resistor bring it high)

    DDRB &= ~(1 << line);  // set line to input
   
  }
}

void i2c_delay()
{
  // default delay in between I2C commands
  _delay_ms(10);   // seems to hover around 2-3ms
}


void i2c_tx(char d)
{

  int stretch_counter;
  char x;

  // SDA is LOW, SCL is HIGH (inherited from i2c-start command)

  // bring clock low to start data transfer

  setPin(SCL,LOW);
  
  for(x=8; x; x--) 
  { 
    //_delay_us(10); // (additional time inbetween scl pulses)

    // if statement:  if (d & 0x80) 
    // 0x80 = 128d = 10000 0000b


    if(d&0x80) 
    {
      _delay_us(15);
      setPin(SDA,HIGH);  
    } else {
      setPin(SDA,LOW);
    }

    _delay_us(30); // (additional time before scl pulse)
    setPin(SCL,HIGH);                  // strobe clock


    // allow for I2C clock stretching (SCL can be held low by slave)
    // see I2C_STRETCH_TIMEOUT_MS

    
    for (stretch_counter=0; stretch_counter < I2C_STRETCH_TIMEOUT_MS; stretch_counter++)
    {
      // allow for some cycles for SCL to go high  

      if (bit_is_clear(PINB,SCL))
      {
        // SCL is held low by slave
        // we'll just wait a little 

        _delay_ms(1);

      } else {

        // SCL has risen, ready to move on
        stretch_counter = I2C_STRETCH_TIMEOUT_MS;
      }


    }

    //_delay_us(10);         // additional SCL-high time
    setPin(SCL,LOW);

    d <<= 1;                 // shift byte

  }

  // byte is now sent
  // next is to read a possible ack from the slave device
  // scl is already set low by the master

  setPin(SDA,HIGH);         // first bring SDA into listening mode
  
  // read i2c slave acknowledge bit if any

  // an addressed i2c slave will set SDA low very quickly

  _delay_us(40);           // space between the previous SCL pulse
  setPin(SCL,HIGH);        // start the clock pulse

  //_delay_us(5);          // this should be 5us or so

  if (bit_is_set(PINB,SDA))
  {
    got_ack=0;
  } else {
    got_ack=1;
  }

  setPin(SDA,LOW);         // we now bring SDA low (so we don't see a spike on SDA
                           // when the master brings SCL low again
 
  _delay_us(25);           // show a nice long SCL high pulse for easier debugging

  setPin(SCL,LOW);         // end the clock pulse
                           // this is also a signal for the i2c slave to release SDA 

  _delay_us(10);           // give the bus some time   

}

void i2c_start()
{

  // SDA goes LOW while SCL is HIGH

  // TODO: check the state of the lines first (they should both be HIGH)

  _delay_us(10);    
  setPin(SDA,LOW);  // start condition
  _delay_us(10);    

}


void i2c_repeatedstart()
{

  // SDA goes LOW while SCL is HIGH

  // first bring SCL and SDA LOW  (SCL and SDA should already be low)

  _delay_us(10);    
  setPin(SCL,LOW); 
  _delay_us(10);    
  setPin(SDA,LOW); 

   // then we bring them both HIGH
 
  _delay_us(10);    
  setPin(SDA,HIGH); 
  _delay_us(10);    
  setPin(SCL,HIGH); 
  _delay_us(100);    

  // then we generate the repeated start condition

  setPin(SDA,LOW); 
  _delay_us(100);
  setPin(SCL,LOW); 
  _delay_us(100);

}


void i2c_startRead(char i2c_id)
{
  // SDA goes from high to low while SCL is high
 
  i2c_start();

  // send the I2C Device ID

  // send as Read (last bit is 1)
  
  i2c_id = i2c_id << 1; // shift all bits one position to the left
  i2c_id |= 1 << 0;     // then set bit 0 to 1
  i2c_tx(i2c_id); 

}



void i2c_startWrite(char i2c_id)
{
  // SDA goes from high to low while SCL is high

  i2c_start();

  // send the I2C Device ID

  // send as Write (last bit is 0)

  i2c_id = i2c_id << 1; // shift all bits one position to the left
  i2c_id |= 0 << 0;     // then set bit 0 to 0
  i2c_tx(i2c_id);

}

void i2c_stop()
{
  // SDA goes HIGH while SCL is HIGH

  // TODO: check if both lines are low

  _delay_us(10);
  setPin(SCL,LOW); 
  _delay_us(10);
  setPin(SDA,LOW);
  _delay_us(10); 

  setPin(SCL,HIGH); 
  _delay_us(100);    // at least 100 us for I2C_LCD module  :-/
  setPin(SDA,HIGH);  // stop condition
  _delay_us(10);

}


void i2c_lcd_text(char *StrData)
{

  int p = 0;
  int q = strlen(StrData);
  int temp = 0;

  for (p = 0; p < q; p++)
  {
    temp = StrData[p];
    i2c_tx(temp);
  }
}

void i2c_lcd_number(int number)
{
  int digits;

  if (number == 0)
  {
    digits = 0;
    int array[0];
    array[0]=0;
    i2c_tx(array[0]+48);

  } else {

    // determine the number of digits
    digits = (int) (log(number)/log(10))+1;

    // split up the number's digits into an array
    int i = digits -1;
    int array[digits];
    while (number > 0)
    {
      array[i--] = number % 10;
      number /= 10;
    }

    // send array over i2c
    for (i =0; i <= digits-1; i++)
    {
      i2c_tx(array[i]+48);
    }

  }
}

void i2c_lcd_hex(uint8_t x)
{

  // sends one byte in hex (without 0x prefix) to the lcd

  char nibble;

  /* extract high nibble */
  nibble = x & 0xf0;        // (resets lower nibble to all zero's)
  nibble = x >> 4;          // (moves high nibble to lower nibble)

  // output the high nibble
  if(nibble < 10)
  {
    i2c_tx(48 + nibble);
  } else {
    i2c_tx(65 + nibble - 10);
  }

  /* do the same on the low nibble */
  nibble = x & 0x0f;

  if(nibble < 10)
  {
    i2c_tx(48 + nibble);
  } else {
    i2c_tx(65 + nibble - 10);
  }

}


unsigned char i2c_rxbyte()
{

  unsigned char rxbyte=0;
  int bitcount=8;

  setPin(SDA,HIGH);  // release SDA so we can listen what the slave has to say
 
   // the ic2_tx command lastly sent the ack pulse and now has SDA and SCL set to LOW
   // we now control the read from the i2c slave device by pulsing SCL and reading SDA 
  
  
  while (bitcount > 0)
  { 
  
    rxbyte = rxbyte << 1; // shift all bits in rxbyte one position to the left
    
        setPin(SCL,HIGH);         // start the clock pulse
    _delay_us(I2C_SCL_T_HIGH_US);           // give the bus some time to possibly bring the pin high

        if (bit_is_set(PINB,SDA))
    {
      // -- detected bit '1' --
         
      rxbyte |= 1 << 0 ; // sets bit 0 of rxbyte to 1

    } else {

      // -- detected bit '0' --

    }
    
    setPin(SCL,LOW);         // stop the clock pulse
    _delay_us(I2C_SCL_T_LOW_US);           // give the bus some time to possibly bring the pin high

        
    bitcount--;  // decrement bitcounter
        
  }
  
  setPin(SCL,HIGH); 
        
  return rxbyte;
}




unsigned char i2c_read(uint8_t i2c_id, uint16_t DEV_REG)
{

  // reading the memory address contents from an I2C Slave Device
  
  unsigned char read_result=0;
  
  // send start sequence as write (so we can specify which address we want to read)
  i2c_startWrite(i2c_id);  // sends i2c start condition + i2c address+0(w)

  // send the address word we want to query on the device
  
  // for the 24LC256 we need to do this in two goes. First the MSB then the LSB
 
  i2c_tx(DEV_REG>>8);        // shift 8 bits to the right to get the high byte 
  i2c_tx(DEV_REG & 0xff);    // mask out the lower 8 bits to get the low byte

  // -- send repeated start --

  i2c_repeatedstart();

  // send the I2C Device ID as Read (last bit is 1)
  
  i2c_id = i2c_id << 1; // shift all bits one position to the left
  i2c_id |= 1 << 0;     // then set bit 0 to 1
  i2c_tx(i2c_id); 


  // the slave will acknowledge and then transmit the 8-bit data word

  // read data from device, one byte long
  read_result=i2c_rxbyte();
  
  i2c_stop();  

  return read_result;
}