I2C
From wikipost
Jump to navigationJump 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; }