I2C
Jump to navigation
Jump to search
various:
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;
}