Atmega I2c only reads 0x00

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

Hmm.. I have abit of a problem. My I2c reader code only reads 0x00 back... Here's the register write and read methods, which I'm trying to communicate with a lidar-lite with

 

 

 


void _wait() {
	while (!(TWCR & (1<<TWINT)));
}

int _i2c_start(){
	uint8_t status;
	TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
	_wait();
	status = TW_STATUS & 0xF8;
	if((status != TW_START) && (status != TW_REP_START)) {
		return status;
	}
	return 0;

}

void _i2c_stop() {
	TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}

int i2c_write_register(uint8_t address, uint8_t register_addr, uint8_t data) {
	uint8_t status;
	status = _i2c_start();
	
	if(!status) { // Kui status = 0, siis on OK
		TWDR = address;
		TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
		_wait();
		status = TW_STATUS & 0xF8;
		if((status != TW_MT_SLA_ACK) && (status != TW_MR_SLA_ACK)) { // Katki läks
			_i2c_stop();
			return status;
		}
		
		TWDR = register_addr;
		TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
		_wait();
		status = TW_STATUS & 0xF8;
		if(status != TW_MT_DATA_ACK) {  // Katki läks
			_i2c_stop();
			return status;
		}
		
		TWDR = data;
		TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
		_wait();
		status = TW_STATUS & 0xF8;
		if(status != TW_MT_DATA_ACK) {  // Katki läks
			_i2c_stop();
			return status;
		}
		_i2c_stop();
		
		return 0;
	}
	_i2c_stop();
	return status;
	
}

uint8_t i2c_read_register(uint8_t address_write, uint8_t address_read, uint8_t register_addr) {
	uint8_t status;
	
	status = _i2c_start();
	
	if(!status) {
		TWDR = address_write;
		TWCR = (1 << TWINT) | (1 << TWEN)| (1 << TWEA);
		_wait();
		status = TW_STATUS & 0xF8;
		if((status != TW_MT_SLA_ACK) && (status != TW_MR_SLA_ACK)) {  // Katki läks
			_i2c_stop();
			return -1;
		}
		
		TWDR = register_addr;
		TWCR = (1 << TWINT) | (1 << TWEN)| (1 << TWEA);
		_wait();
		status = TW_STATUS & 0xF8;
		if(status != TW_MT_DATA_ACK) {  // Katki läks
			_i2c_stop();
			return -1;
		}
		_i2c_stop();
		
		status = _i2c_start();
		if(!status) {
			TWDR = address_read;
			TWCR = (1 << TWINT) | (1 << TWEN);
			_wait();
			status = TW_STATUS & 0xF8;
			if((status != TW_MT_SLA_ACK) && (status != TW_MR_SLA_ACK)) {  // Katki läks
				_i2c_stop();
				return -1;
			}
			
			TWCR = (1<<TWINT)|(1<<TWEN);
			_wait();
			uint8_t res = TWDR;
			_i2c_stop();
			return res;
		}
		
	}
	
}

And here's how I'm trying to communicate with the sensor. All the reads give back 0x00 :/ 

 

#define ADDR_WRITE 0xC4
#define ADDR_READ 0xC5


uint16_t LidarLite_measure() {
	uint8_t status = i2c_write_register(ADDR_WRITE,0x00,0x04);
	while((i2c_read_register(ADDR_WRITE,ADDR_READ,0x01) & 0x01)) {}
	uint8_t high = i2c_read_register(ADDR_WRITE, ADDR_READ, 0x0f);
	uint8_t low = i2c_read_register(ADDR_WRITE, ADDR_READ, 0x10);
	return ((uint16_t)high << 8) | low;
}

 

And a relevant snap from Saleae Logic 

 

 

Can anyone please give me a second set of eyes on this? 

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

 

I didn't inspect too closely, just a general thought (that bit me a few times before, oh the pain)

A lot of times for reading, the system is entering a repeated start state & use those return compare codes for checking (rather than the non-repeated start)

 

Now it's important to understand which transfer modes the master is in: The first time the device address is sent, the master is in MT (transmit)  mode for both the address and the word address transfer. Then, after the repeated start condition, the master is is MR (rcv)  mode. This important, because the status codes come from different tables. Here's the complete random read code

look under: interfacing EEPROM

http://www.avrbeginners.net/

 

Some other goodies (you can ignore the interrupt stuff, unless you don't want to wait around for TWI):

http://www.chrisherring.net/all/tutorial-interrupt-driven-twi-interface-for-avr-part1/

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Sun. Aug 23, 2020 - 09:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And of course you have pullup resistors on the I2C lines?

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

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

Your logic output seems to show the sda pin is always low when the master is not driving it. You have a logic analyzer, so it would at least show the pullups working (the device has its own pullups it seems).

 

It also looks like you do a device reset (write 0 -> 0), and a reset takes 22ms so if the device internal pullups were doing the job one would think all you would see from the device while it starts up is an sda line pulled up and should not be getting any ack's.

 

So, I would guess a (mis)connection problem.

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

You can't do a i2c_stop() followed by a i2c_start(). You must code a proper i2c_restart().

 

uint8_t i2c_read_register(uint8_t address_write, uint8_t address_read, uint8_t register_addr) {
    uint8_t status;
    
    status = _i2c_start();
    
    if(!status) {
        TWDR = address_write;
        ...
        ...
        if(status != TW_MT_DATA_ACK) {  // Katki läks
            _i2c_stop();
            return -1;
        }
        _i2c_stop();
        
        status = _i2c_start();
        if(!status) {
            TWDR = address_read;
            TWCR = (1 << TWINT) | (1 << TWEN);
            _wait();
            status = TW_STATUS & 0xF8;
            if((status != TW_MT_SLA_ACK) && (status != TW_MR_SLA_ACK)) {  // Katki läks
                _i2c_stop();
                return -1;
            }
...

 

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

Yes,  you can do i2c_stop() followed by a i2c_start()  but you must wait for the stop() to complete.

 

I always advise using a proven library first.    Get your application working 100%.

 

Then write / develop your own I2C code if the topic interests you.

You have a working application to "test it on"

 

Regarding Stop-Start or Restart.   Some Slaves require Restart i.e. they don't want you to release the bus between setting a register and subsequent read.

 

Most Slave devices don't mind whether you release the bus with Stop-Start.   Obviously another Master could claim the bus when you release it.

 

David.

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

Yup, I just figured that out myself, and waiting for the stop to complete, works perfectly :D 

 

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

Note that different Slaves behave differently.   e.g. a 24Cxxx EEPROM requires a Stop to initiate a Page-Write.   But will not respond to a Start while busy with the Page-Write.    The Stop completes in a few us but a subsequent Start will not work for 3000us.

 

David.

Last Edited: Mon. Aug 24, 2020 - 11:04 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

Yes,  you can do i2c_stop() followed by a i2c_start()  but you must wait for the stop() to complete.

But a generic I2C routine cannot determine what the slave device will do upon receiving the i2c_stop(). A worst case scenario being a slave device could clear it's internal address register; i.e. the one you've only just written.

 

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

That was my point.   Different devices use the I2C bus in different ways.  e.g. a stop() might initiate a page-write or an analog-conversion.

 

Most commonly,  a start(W) resets an index register.   a subsequent start(R) reads the contents of that index register.

 

But you should NEVER assume.   Always read the datasheet.

 

Regarding general purpose functions.    A twi_master_trans() function might write X bytes from a txbuffer followed by reading Y bytes to a rxbuffer. using start(W), restart(R), stop()

 

You can cope with almost all eventualities by using the appropriate X=0 or Y=0.

The whole trans() operation can be handled via interrupts.

 

David.