I2C troubleshooting

Go To Last Post
7 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

ATmega644pa, 16MHz, 5V

I've been trying to write some i2c code for some sensors I have on my board but cannot get anything to work.

I know it is wired correctly because it works when I compile with all the Arduino libraries and the Wire library.

I have been looking at the i2C master library and the atmega644pa datasheet page 221 ( http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega164A_PA-324A_PA-644A_PA-1284_P_Data-Sheet-40002070A.pdf ).

#define Prescaler 1
#define TWBR_val ((F_CPU / F_SCL)-16)/(2*pow(4,Prescaler));
volatile uint8_t* _twiDDR = &DDRC;
volatile uint8_t* _twiPORT = &PORTC;
volatile uint8_t sda_PIN = PINC1;
volatile uint8_t scl_PIN = PINC0;

void i2c_init(void){
  *_twiDDR &= ~(1 << scl_PIN ); // SCL Input
  *_twiDDR &= ~(1 << sda_PIN); // SDA Input
  TWBR = (uint8_t) TWBR_val;
}

uint8_t i2c_start(uint8_t address){
  // reset TWI control register
  TWCR = 0;
  // transmit START condition
  TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
  // wait for end of transmission
  while( !(TWCR & (1<<TWINT)) );

  // check if the start condition was successfully transmitted
  if((TWSR & 0xF8) != TW_START){ return 1; }

  // load slave address into data register
  TWDR = address;
  // start transmission of address
  TWCR = (1<<TWINT) | (1<<TWEN);
  // wait for end of transmission
  while( !(TWCR & (1<<TWINT)) );

  // check if the device has acknowledged the READ / WRITE mode
  uint8_t twst = TW_STATUS & 0xF8;
  if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK)) return 1;

  return 0;
}

For some reason i2c_start(0x40) always returns 1 for error. 

What I noticed is that the TWBR value for the atmega644pa, specified in the datasheet, is different than what the i2c_master and Wire libraries use. It doesn't make a difference no matter what value I use.

This topic has a solution.
Last Edited: Tue. Jul 2, 2019 - 06:07 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you want non-Arduino, plain C support for I2C I would suggest:

 

code: http://homepage.hispeed.ch/peter...

manual: http://homepage.hispeed.ch/peter...

 

This is the code most folks use if they just want to "get on with things" and use an I2C device. Of course I realise that maybe your goal here is simply to learn about writing an I2C driver from scratch? If so then the Fleury library makes something good to compare against anyway. For example you could compare your i2c_start() with his:

unsigned char i2c_start(unsigned char address)
{
    uint8_t   twst;

	// send START condition
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

	// wait until transmission completed
	while(!(TWCR & (1<<TWINT)));

	// check value of TWI Status Register. Mask prescaler bits.
	twst = TW_STATUS & 0xF8;
	if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;

	// send device address
	TWDR = address;
	TWCR = (1<<TWINT) | (1<<TWEN);

	// wail until transmission completed and ACK/NACK has been received
	while(!(TWCR & (1<<TWINT)));

	// check value of TWI Status Register. Mask prescaler bits.
	twst = TW_STATUS & 0xF8;
	if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;

	return 0;

}

Actually they look remarkably similar !!

 

EDIT: Oh is this about the difference between 7 and 8 bit addressing?

Last Edited: Tue. Jul 2, 2019 - 02:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

  Of course I realise that maybe your goal here is simply to learn about writing an I2C driver from scratch? 

I always look for a good exercise, but this is more of just getting on with using I2C.

EDIT: Oh is this about the difference between 7 and 8 bit addressing?

I'm not sure what you mean by this. What I mean, is that there is an issue with the i2C_start always returning an error. The wiring is correct. So I am just looking for errors in this start routine.

 

For arduino libraries, the Wire library is designed around the atmega328 where the TWBR value is ((F_CPU/SCL_CLOCK)-16)/2; 

 

However this is different for the atmega644pa it should be ((F_CPU / F_SCL)-16)/(2*pow(4,Prescaler));

 

But using either doesn't make a difference.

Last Edited: Tue. Jul 2, 2019 - 02:38 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joshagirgis wrote:
I'm not sure what you mean by this.
Well I2C actually supports 128 addresses. The "address byte" you put out therefore has 7 bits that identify the device then the 8th bit is the Read/Write bit. So say you have a device that has I2C address 0x5A. Then (I can't remember which way round it is!) you actually address 0xB4 to read and 0xB5 to write (or is it the other way round). That is the the 7 bit 101 1010 device address then an added 0/1 to mean read/write making 1011 0100 and 1011 0101. Now some libraries will just take the "address" as 0x5A and they then stuff in the extra 0/1 in the bit 0 position depending on read/write. While other libraries (and Fleury is one) would use the full 8 bit address so 0xB4 not 0x5A.

 

So try doubling/halving whatever I2C address you are trying to use and see if that helps (obviously if bit 7 is already set is can only be halved as it must already be an 8 bit address!)

 

EDIT: typical - looked it up and I got it the wrong way - bit0=0 is "write" and bit0=1 is "read" so in my example B4=write, B5=read.

Last Edited: Tue. Jul 2, 2019 - 02:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This definitely seems to be the issue then. Multiplying the address by 2 rendered the i2c_start condition to return a 0.

Ignorance on my part! Thanks

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

For some reason I cannot address the read part of 0x40. The Write address works.

This is the process for my sensor to read data (SHT20). S is start and P is stop.

 

  i2c_start(0b10000000);//write address
    i2c_write(0b11100101);
    //i2c_stop();
    i2c_start(0b10000001);//read address THIS RETURNS an ERROR
    //read address
    _delay_ms(10);
    uint8_t msb, lsb, checksum;
    msb = i2c_read_ack();
    lsb = i2c_read_ack();
    checksum = i2c_read_nack();
    i2c_stop();
    uint16_t rawValue = ((uint16_t) msb << 8) | (uint16_t) lsb;
    return rawValue & 0xFFFC;

 

 

Last Edited: Tue. Jul 2, 2019 - 05:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joshagirgis wrote:

i2c_start(0b10000000);//write address
    i2c_write(0b11100101);
    //i2c_stop();
    i2c_start(0b10000001);//read address THIS RETURNS an ERROR

The second start is actually a repeated_start which has its own status code.

Fleury does

twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;

Spot the difference with what you're doing

if((TWSR & 0xF8) != TW_START){ return 1; }