ATTiny416 acting as TWI/I2C slave device, failure on second read.

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

I'm implementing an I2C slave device on an ATTiny416 (the ATTiny416-XNANO demo board, to be precise), Studio 7, using the START utility. It follows the slave example given, I haven't changed that flow. I have an I2CTools interface tool and a bus monitor, so I can watch the bus pretty closely.

 

The Master is trying to read the slave's "status register" at I2C address C2. For testing, the slave is simply reporting the value '0xA5'. Everything runs properly the first time through: the Master presents the address, the slave triggers an interrupt and acknowledges, the ISR writes 0xA5 to the bus, the master reads it, and acknowledges, and the master sets the STOP condition.

 

The second time through, and thereafter, the slave responds properly to the address interrupt, it writes 0xA5 to the status register, but nothing comes out over the bus. All of the proper ACK and STOP signals are there, but the data line stays high.

 

I think that the ATTiny416 uses a new TWI module, different from the older ones, and the START example certainly doesn't match the older application notes, but I can't find any condition that would cause the TWI module to not put the data out over the bus, especially while properly responding to everything else.

 

START TWI module configuration

 

The TWI ISR, for reference (changed nothing here from the START generated code)

ISR(TWI0_TWIS_vect) {
  //I2C collision
  if (TWI0.SSTATUS & TWI_COLL_bm) {
    I2C_0_collision_callback();
    return;
  }

  //bus error
  if (TWI0.SSTATUS & TWI_BUSERR_bm) {
    I2C_0_bus_error_callback();
    return;
  }

  //address interrupt
  if ((TWI0.SSTATUS & TWI_APIF_bm) && (TWI0.SSTATUS & TWI_AP_bm)) { //Address or Stop Interrupt Flag && Address
    I2C_0_address_callback(); 
    if (TWI0.SSTATUS & TWI_DIR_bm) {  //Master Read
      // Master wishes to read from slave
      I2C_0_read_callback(); //supply byte
      TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc; //ensure interrupt flag cleared and proper responses sent.
    }
    return;
  }
  
  //Data interrupt
  if (TWI0.SSTATUS & TWI_DIF_bm) {  //Data Interrupt Flag
    if (TWI0.SSTATUS & TWI_DIR_bm) { //Master Read
      // Master wishes to read from slave
      if (!(TWI0.SSTATUS & TWI_RXACK_bm)) { //Received Acknowledge
        // Received ACK from master
        I2C_0_read_callback();
        TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc; //ensure interrupt flag cleared and proper responses sent.
      } else {
        // Received NACK from master
        I2C_0_goto_unaddressed(); //reset interrupt flags and respond with an ACK
      }
    } else // Master wishes to write to slave
    {
      I2C_0_write_callback();
    }
    return;
  }

  // STOP was received (separate interrupt from preceding data byte)
  if ((TWI0.SSTATUS & TWI_APIF_bm) && (!(TWI0.SSTATUS & TWI_AP_bm))) {  //Address or Stop Interrupt Flag && Stop
    I2C_0_stop_callback();
    TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;  //complete transaction
    return;
  }
}

The address handler and read handler:

void I2C_address_handler() {
  //a valid address was received. 
  //should be either the current address or all-call (0x00)
  uint8_t received_address = I2C_0_read();
  received_address = received_address & 0xFE; //mask off R/W bit
  if (received_address == matchbox_I2C_address) {
    I2C_0_send_ack();
    matchbox_I2C_state = I2C_STATE_GOOD_ADDRESS;
    XNANO_USER_LED_toggle_level();
  }
  else if (received_address == I2C_GENERAL_CALL_ADDRESS) {
    I2C_0_send_ack();
    matchbox_I2C_state = I2C_STATE_ALL_CALL;
  }
  else {
    I2C_0_send_nack();
    matchbox_I2C_state = I2C_STATE_IDLE;
  }
}

void I2C_read_handler() { 
  switch (matchbox_I2C_state) {
  case I2C_STATE_GOOD_ADDRESS :   //a read was received directly after the address, send the status byte
//    I2C_0_write(matchbox_status_byte);
    I2C_0_write(0xA5);  //<== gets to this part every time. 
    break;
  case I2C_STATE_GOOD_COMMAND :   //a good command was received, the master wants to read data
    //do stuff here
    break;
  case I2C_STATE_ALL_CALL : //TBD
  default :                       //anything else
    matchbox_I2C_state = I2C_STATE_IDLE;
    break;
  }  
}

As far as I can tell, both from using the on-board debugger and putting in debugging lines that pulse output ports, it gets to and executes the "I2C_0_write(0xA5);" in the read handler every time, and otherwise properly traces through the logic - but after the first time nothing comes out. If I use the programming utility to reset the ATTiny416, by reading the chip ID (which will reset any AVR, and won't reset the I2C tools in any way), it works again - once.

 

Any help would be greatly appreciated.

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

I've just bought the same dev board for the sole purpose of getting the TWI hardware working properly, so your post is very timely. If you look at my post history you will see that I've encountered issues with the TWI hardware on the latest ATtinys and on the XMegas which are very similar. Although you're dealing with a slave and my code was for master, I feel like we're dealing with the same issue. Unfortunately I wasn't able to get any help on this forum. I use ASF3 which doesn't provide a library for TWI, so I've only got the datasheet to work off.

 

My workaround was to take control of the bus after each write and bit-bang a stop sequence.

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

In the case of the slave mode for the ATTiny416, at least, the START-generated i2c_slave.c has the following code for the all-purpose I2C interrupt handler:

void I2C_0_isr()
{
	if (TWI0.SSTATUS & TWI_COLL_bm) {
		I2C_0_collision_callback();
		return;
	}

	if (TWI0.SSTATUS & TWI_BUSERR_bm) {
		I2C_0_bus_error_callback();
		return;
	}

	if ((TWI0.SSTATUS & TWI_APIF_bm) && (TWI0.SSTATUS & TWI_AP_bm)) {
		I2C_0_address_callback();
		if (TWI0.SSTATUS & TWI_DIR_bm) {
			// Master wishes to read from slave
			I2C_0_read_callback();
			TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc;
		}
		return;
	}
	if (TWI0.SSTATUS & TWI_DIF_bm) {
		if (TWI0.SSTATUS & TWI_DIR_bm) {
			// Master wishes to read from slave
			if (!(TWI0.SSTATUS & TWI_RXACK_bm)) {
				// Received ACK from master
				I2C_0_read_callback();
				TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc;
			} else {
				// Received NACK from master
				I2C_0_goto_unaddressed();
			}
		} else // Master wishes to write to slave
		{
			I2C_0_write_callback();
		}
		return;
	}

	// Check if STOP was received
	if ((TWI0.SSTATUS & TWI_APIF_bm) && (!(TWI0.SSTATUS & TWI_AP_bm))) {
		I2C_0_stop_callback();
		TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;
		return;
	}
}

Recall that when the master reads from or writes to the slave, it first sends a START condition, than a 7-bit address with the R/W bit, then the slave acknowledges the address, then the slave sends or receives a data byte, then the receiver ACKS's the data, then the master issues a STOP condition.

 

The above code assumes that when the master reads a data byte only one interrupt is received for the address and  to supply the data byte, thus the I2C_0_read_callback() call follows the I2C_0_address_callback() call immediately.

Instead, there are two interrupts issued: one when the address is received, and a second for the slave to supply the data-byte. The first interrupt occurs when the address is received, and the second almost immediately, probably following the slave's ACK. The problem, of course, is that the I2C_0_read_callback(); function gets called twice when it should only be called once. Depending on the logic in those callback functions, this can mess things up big time.

(The START code does work when the Master is writing a data byte, because the first I2C_0_read_callback(); call isn't triggered.)

 

The following code works properly:

ISR(TWI0_TWIS_vect) {
//pulse_debug(1);
  //I2C collision
  if (TWI0.SSTATUS & TWI_COLL_bm) {
    I2C_collision_error_handler();
    return;
  }

  //bus error
  if (TWI0.SSTATUS & TWI_BUSERR_bm) {
    I2C_bus_error_handler();
    return;
  }

  //address interrupt
  if ((TWI0.SSTATUS & TWI_APIF_bm) && (TWI0.SSTATUS & TWI_AP_bm)) { //Address or Stop Interrupt Flag && Address
    I2C_address_handler();
    TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc; //ensure interrupt flag cleared and proper responses sent.
    return;
  }

  //Data interrupt
  if (TWI0.SSTATUS & TWI_DIF_bm) {  //Data Interrupt Flag
    if (TWI0.SSTATUS & TWI_DIR_bm) { //Master Read
      // Master wishes to read from slave
        // Received ACK from master
      I2C_read_handler();
      TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc; //ensure interrupt flag cleared and proper responses sent.
//       if (!(TWI0.SSTATUS & TWI_RXACK_bm)) { //Received Acknowledge
//         // Received NACK from master
//         I2C_0_goto_unaddressed(); //reset interrupt flags and respond with an ACK
//       }
    } else { // Master wishes to write to slave
      I2C_write_handler();
      TWI0.SCTRLB = TWI_ACKACT_ACK_gc | TWI_SCMD_RESPONSE_gc; //ensure interrupt flag cleared and proper responses sent.
    }
    return;
  }

  // STOP was received (separate interrupt from preceding data byte)
  if ((TWI0.SSTATUS & TWI_APIF_bm) && (!(TWI0.SSTATUS & TWI_AP_bm))) {  //Address or Stop Interrupt Flag && Stop
    I2C_stop_handler();
    TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;  //complete transaction
    return;
  }
}

 

(I got rid of the deferred handler calls from the xxx_callback() functions and call the handlers directly). The first interrupt only handles the address, the second only handles the data.

The bit following "(!(TWI0.SSTATUS & TWI_RXACK_bm)) " is commented out because for the first byte exchanged on the I2C bus the RXACK bit is, of course, not set (indicating a received NACK)- since it reflects the most recent ACK from the master and there is none. Thus you always get a false NACK indication on the first exchange.  I haven't seen the need to use it so far and haven't figured out how to deal with it yet.

Last Edited: Thu. Apr 11, 2019 - 03:07 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

pblase wrote:
the master reads it, and acknowledges,

According to I2C protocol, when the master is reading data from the slave, it ACK's each data byte until the last data byte received from the slave, it must NAK it to tell the slave it's done reading and release the bus so the master can control the bus, and in this case send STOP. 

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274

 

 

 

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

Hi pblase,

 

Jim said it correctly. Master sends a NACK to slave to indicate the end of transaction just before STOP. Slave should not send a byte for the NACK received. So, to identify the NACK, the check is necessary.

 

You can go through this issue -  https://www.avrfreaks.net/forum/how-clear-rxack-flag-twi-slave-tiny817

 

Tintu

--TintuVayalattu

Last Edited: Tue. Apr 16, 2019 - 06:01 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Sir,

 

I am using ATtiny412 as I2C slave. Can I have ATtiny416 I2C slave full code

because I am new to microcontroller programming. And also can you help me

"how to configure ATtiny412 registers to use pins as GPIOs?"

 

Thanking you

G.Kishore

Kishore.G

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

Please start first with blinking a LED, do some work, learn a bit about how to set input output...start with basics and then you can go to more complex things. you cannot expect to know what is I2C if you dont even know how to set an output/input pin!

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

pblase wrote:
the master reads it, and acknowledges, and the master sets the STOP condition.

If the master only wants one byte of data, it should nak before setting stop!   This tells the slave to release the bus to the master!

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274