I2C

From wikipost
Revision as of 21:01, 16 March 2014 by Admin (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search


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;
}