tinyAVR 1-Series TWI Polled Byte-Read with NACK

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


I've written a polled master TWI byte-read routine for an ATtiny814 (in C++) - it waits for RIF bit to be set in the MSTATUS register, as described in the datasheet:

 

 

The polling for the read+ACK works (i.e. when I am reading multiple bytes & there are more bytes to be read), but the polling for the read+NACK (i.e. reading the last byte) does not work - the RIF bit never gets set.

 

Here is the code (bPutACK is set by the calling routine depending on whether or not this is the last byte to be read from the slave):

uint8_t TWI_read_data(bool bPutACK, uint8_t &data)
{
   if(bPutACK)
      TWI0.MCTRLB &= ~(1 << TWI_ACKACT_bp);  // More bytes to receive, setup ACK
   else
      TWI0.MCTRLB |= TWI_ACKACT_NACK_gc;     // Next byte will be last to be received, setup NACK
      
   // Read data from MDATA register
   data = TWI0.MDATA;
   
   if(bPutACK)
      TWI0.MCTRLB |= TWI_MCMD_RECVTRANS_gc;  // More bytes to come, so execute ACK (followed by byte read)
   else
      TWI0.MCTRLB |= TWI_MCMD_STOP_gc; // End of read, so execute NACK succeeded by issuing a STOP condition

   // RIF bit of MSATUS gets set to 1 when master byte read operation has completed
   // For some reason this does not apply if NACK is executed by master...
   while (!(TWI0.MSTATUS & TWI_RIF_bm));
   
   return TWI0.MSTATUS;
}

 

The "while (!(TWIo.MSTATUS & TWI_RIF_bm));" line is where the polling is done. It works fine for read+ACK, but not for read+NACK (the while loop just continues forever). I can comment out this line for read+NACK and the program will continue just fine with the correct data being read from the slave device - it's just that the RIF bit never seems to get set.

 

This person seemed to have the same issue:

https://www.avrfreaks.net/forum/attiny814-i2c-code

 

Is there something I'm missing with regards to TWI polling on the tinyAVR 1-series?

 

Rob

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

Have you tried asking Atmel Start to make you some TWI code for 814 and seen what they come up with?

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

Hi clawson, thanks for the reply. Yes, I generated an Atmel Start project & went through the code when creating my polled version - it's like wading through pea soup, but it was how I got the commands that I have.

 

In the Atmel Start code there are functions like:

static i2c_fsm_states_t I2C_0_do_I2C_RX(void)
{
	I2C_0_status.addressNACKCheck = 0;

	if (I2C_0_status.data_length == 1)
		TWI0.MCTRLB |= TWI_ACKACT_NACK_gc; // Next byte will be last to be received, setup NACK
	else
		TWI0.MCTRLB &= ~(1 << TWI_ACKACT_bp); // More bytes to receive, setup ACK

	if (--I2C_0_status.data_length) {
		*I2C_0_status.data_ptr = TWI0.MDATA;
		I2C_0_status.data_ptr++;
		TWI0.MCTRLB |= TWI_MCMD_RECVTRANS_gc;
		return I2C_RX;
	} else {
		*I2C_0_status.data_ptr = TWI0.MDATA;
		I2C_0_status.data_ptr++;
		I2C_0_status.bufferFree = true;
		switch (I2C_0_status.callbackTable[i2c_dataComplete](I2C_0_status.callbackPayload[i2c_dataComplete])) {
		case i2c_restart_write:
		case i2c_restart_read:
			return I2C_0_do_I2C_DO_NACK_RESTART();
		default:
		case i2c_continue:
		case i2c_stop:
			return I2C_0_do_I2C_DO_NACK_STOP();
		}
	}
}

and

static i2c_fsm_states_t I2C_0_do_I2C_DO_NACK_STOP(void)
{
	TWI0.MCTRLB |= TWI_ACKACT_NACK_gc;
	TWI0.MCTRLB |= TWI_MCMD_STOP_gc;
	return I2C_0_do_I2C_IDLE();
}

which, as far as I can see, are the same as what I've done. Incidentally, I could not get I2C communications with the Atmel Start code working at all for me, hence rolling my own code, which at least does work if I comment-out the polling.

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

Hi,

 

I see you mentioned one of my posts. I got TWI to work the other day and posted the code in a different post: https://www.avrfreaks.net/forum/...

 

Here's my TWI library:

 

#include <avr/io.h>


void TWI_Init(char baud)
{
	TWI0.MBAUD = baud;
	TWI0.MCTRLB |= TWI_FLUSH_bm;
	TWI0.MSTATUS |= (TWI_RIF_bm | TWI_WIF_bm);
}

void TWI_Enable()
{
	TWI0.MCTRLA = 1<<TWI_SMEN_bp | 1 << TWI_ENABLE_bp;
	TWI0_MSTATUS |= TWI_BUSSTATE_IDLE_gc;
}

char TWI_Start(char addr)
{
	if ((TWI0.MSTATUS & TWI_BUSSTATE_gm) != TWI_BUSSTATE_BUSY_gc)
	{
		TWI0.MCTRLB &= ~(1<<TWI_ACKACT_bp);
		TWI0.MADDR = addr;
		
		if (addr & 1)
		{
			while (!(TWI0_MSTATUS & TWI_RIF_bm));
		}
		else
		{
			while (!(TWI0_MSTATUS & TWI_WIF_bm));
		}
		return TWI0.MSTATUS;
	}
	else
		return TWI0.MSTATUS;
}

char TWI_Read(char * c, char ACKorNACK)
{
	if ((TWI0.MSTATUS & TWI_BUSSTATE_gm)==TWI_BUSSTATE_OWNER_gc)
	{
		while (!(TWI0.MSTATUS & TWI_RIF_bm));
		
		if (ACKorNACK)
		TWI0.MCTRLB &= ~(1<<TWI_ACKACT_bp);	//Send ACK
		else
		TWI0.MCTRLB |= 1<<TWI_ACKACT_bp;	//Send NACK
		
		*c = TWI0.MDATA;
		
		return TWI0.MSTATUS;
	}
	else
		return TWI0.MSTATUS;
}

char TWI_Write(char data)
{
	if ((TWI0.MSTATUS&TWI_BUSSTATE_gm) == TWI_BUSSTATE_OWNER_gc)
	{
		while (!((TWI0.MSTATUS & TWI_WIF_bm) | (TWI0_MSTATUS & TWI_RXACK_bm)));
		
		TWI0.MDATA = data;
		return TWI0.MSTATUS;
	}
	else
		return TWI0.MSTATUS;
}

void TWI_Stop()
{
	TWI0.MCTRLB |= TWI_MCMD_STOP_gc;
}

You haven't posted how you configured the hardware, but I found that Quick Command has to be disabled (QCEN).

 

Don't waste your time with Atmel Start.

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

Hi trevorg, thanks for posting your code - interesting that you poll RIF bit being unset at the beginning of your write function, whereas I am polling for it being set at the end of my read function. There must be some way of checking that the read command has been completed... At this point I think I'll just give up on trying to get the polling to work correctly, and just disable the checking of the RIF bit altogether - the read function seems to work without it for my use case.

 

Just for interest, my TWI control register initialisation code at the start of a TWI operation is:

TWI0.MCTRLA = 1 << TWI_ENABLE_bp      // Enable TWI Master: enabled
            | 0 << TWI_QCEN_bp        // Quick Command Enable: disabled
            | 0 << TWI_RIEN_bp        // Read Interrupt Enable: disabled
            | 0 << TWI_SMEN_bp        // Smart Mode Enable: disabled
            | TWI_TIMEOUT_DISABLED_gc // Bus Timeout Disabled
            | 0 << TWI_WIEN_bp;       // Write Interrupt Enable: disabled

TWI0.MCTRLB |= TWI_FLUSH_bm;
TWI0.MSTATUS |= TWI_BUSSTATE_IDLE_gc;
// Reset module
TWI0.MSTATUS |= (TWI_RIF_bm | TWI_WIF_bm);

 

TBH I would have happily used the Atmel Start code if it had just worked, but sadly it didn't and trying to debug that code with all of its data structures and callback routines and function pointers was way more difficult than just rolling my own implementation. It's a shame there's no application note for TWI/I2C on the ATtiny814, because that would have been useful. Oh well.

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

For other issues, here you can find links for all the app notes for ATtiny 1 & 0 series:

 

https://www.avrfreaks.net/forum/...

 

BR,

Moe

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

Hi,

 

I found that polling the RIF bit before doing a read was key to getting my code working. You'd think that it would acceptable to read it after initiating a read, but the hardware doesn't seem to work like that.

 

Looking at your initialization code, the difference that stands out to me is you have Smart Mode Disabled. My code won't work with this disabled. Also, you I'm sure you have done it else where otherwise nothing would work, but I don't see you setting the MBAUD register.