Triggering TWI interrupt

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

Hello guys I am trying understand AVR315 app note:

http://www.atmel.com/Images/Atmel-2564-Using-the-TWI-Module-as-I2C-Master_ApplicationNote_AVR315.pdf

At page 5 they say:

 

"The TWI module operates as a state machine and is event driven. If a START CONDITION is followed by

a TWI address that matches the address in the Slave’s TWI Address Register (TWAR) the TWINT flag is

set."

 

I connected a device to the TWI bus, and I didn't address the slave device and still program jumps into interrupt vector.

Connected LED for indication.

 


#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define TWI_FREQ 50000

void TWIInit()
{

 // Set pre-scalers (no pre-scaling)
 TWSR = 0;
 // Set bit rate
 TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;
 // Enable TWI and interrupt
 TWCR = (1 << TWIE) | (1 << TWEN);
}


void start()
{
 // send START condition
 TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN) | (1 << TWIE);
 
 //while(!(TWCR & (1<<TWINT)));
 //
 //TWDR = address;
 //TWCR = (1<<TWINT) | (1<<TWEN) | (1 << TWIE);
 //while(!(TWCR & (1<<TWINT)));

}/* i2c_start */


int main(void)
{
 DDRA = (1 << PINA0);
 
 TWIInit();
 sei(); 
    /* Replace with your application code */
 
    while (1) 
    {
  start(); 
    }
}

ISR(TWI_vect)
{
 // Blink LED
 PORTA |= (1 << PINA0);
 _delay_ms(500);
 PORTA &= ~(1 << PINA0);
 _delay_ms(500);
 
}

So basically if I got it right an interrupt is triggered if AVR is connected physically to the i2c bus,

by two events:

Writing exactly these 4 bits to one (even I am not sure why writing (1<<TWINT) would trigger interrupt, isn't writing (1<<TWINT) actually clearing TW interrupt flag) ?

TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN) | (1 << TWIE);
 

or if RX TWDR got something in his internal buffer (TWDR)

 

Here is their whole TX Master code:

*****************************************************************************
*
* Atmel Corporation
*
* File              : TWI_Master.c
* Compiler          : IAR C/C++ Compiler for AVR
                      6.30.1 (6.30.1.687)

* Support mail      : avr@atmel.com
*
* Supported devices : All devices with a TWI module can be used.
*                     The example is written for the ATmega16
*
* AppNote           : AVR315 - TWI Master Implementation
*
* Description       : This is a sample driver for the TWI hardware modules.
*                     It is interrupt driveren. All functionality is controlled through 
*                     passing information to and from functions. Se main.c for samples
*                     of how to use the driver.
*
*
****************************************************************************/

#include "ioavr.h"              
#include "inavr.h"
#include "TWI_Master.h"

static unsigned char TWI_buf[ TWI_BUFFER_SIZE ];    // Transceiver buffer
static unsigned char TWI_msgSize;                   // Number of bytes to be transmitted.
static unsigned char TWI_state = TWI_NO_STATE;      // State byte. Default set to TWI_NO_STATE.

union TWI_statusReg TWI_statusReg = {0};            // TWI_statusReg is defined in TWI_Master.h

/****************************************************************************
Call this function to set up the TWI master to its initial standby state.
Remember to enable interrupts from the main application after initializing the TWI.
****************************************************************************/
void TWI_Master_Initialise(void)
{
  TWBR = TWI_TWBR;                                  // Set bit rate register (Baudrate). Defined in header file.
  TWDR = 0xFF;                                      // Default content = SDA released.
  TWCR = (1<<TWEN)|                                 // Enable TWI-interface and release TWI pins.
         (0<<TWIE)|(0<<TWINT)|                      // Disable Interupt.
         (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|           // No Signal requests.
         (0<<TWWC);                                 //
}    
    
/****************************************************************************
Call this function to test if the TWI_ISR is busy transmitting.
****************************************************************************/
unsigned char TWI_Transceiver_Busy( void )
{
  return ( TWCR & (1<<TWIE) );                  // IF TWI Interrupt is enabled then the Transceiver is busy
}

/****************************************************************************
Call this function to fetch the state information of the previous operation. The function will hold execution (loop)
until the TWI_ISR has completed with the previous operation. If there was an error, then the function 
will return the TWI State code. 
****************************************************************************/
unsigned char TWI_Get_State_Info( void )
{
  while ( TWI_Transceiver_Busy() );             // Wait until TWI has completed the transmission.
  return ( TWI_state );                         // Return error state.
}

/****************************************************************************
Call this function to send a prepared message. The first byte must contain the slave address and the
read/write bit. Consecutive bytes contain the data to be sent, or empty locations for data to be read
from the slave. Also include how many bytes that should be sent/read including the address byte.
The function will hold execution (loop) until the TWI_ISR has completed with the previous operation,
then initialize the next operation and return.
****************************************************************************/
void TWI_Start_Transceiver_With_Data( unsigned char *msg, unsigned char msgSize )
{
  unsigned char temp;

  while ( TWI_Transceiver_Busy() );             // Wait until TWI is ready for next transmission.

  TWI_msgSize = msgSize;                        // Number of data to transmit.
  TWI_buf[0]  = msg[0];                         // Store slave address with R/W setting.
  if (!( msg[0] & (TRUE<<TWI_READ_BIT) ))       // If it is a write operation, then also copy data.
  {
    for ( temp = 1; temp < msgSize; temp++ )
      TWI_buf[ temp ] = msg[ temp ];
  }
  TWI_statusReg.all = 0;      
  TWI_state         = TWI_NO_STATE ;
  TWCR = (1<<TWEN)|                             // TWI Interface enabled.
         (1<<TWIE)|(1<<TWINT)|                  // Enable TWI Interupt and clear the flag.
         (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|       // Initiate a START condition.
         (0<<TWWC);                             //
}

/****************************************************************************
Call this function to resend the last message. The driver will reuse the data previously put in the transceiver buffers.
The function will hold execution (loop) until the TWI_ISR has completed with the previous operation,
then initialize the next operation and return.
****************************************************************************/
void TWI_Start_Transceiver( void )
{
  while ( TWI_Transceiver_Busy() );             // Wait until TWI is ready for next transmission.
  TWI_statusReg.all = 0;      
  TWI_state         = TWI_NO_STATE ;
  TWCR = (1<<TWEN)|                             // TWI Interface enabled.
         (1<<TWIE)|(1<<TWINT)|                  // Enable TWI Interupt and clear the flag.
         (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|       // Initiate a START condition.
         (0<<TWWC);                             //
}

/****************************************************************************
Call this function to read out the requested data from the TWI transceiver buffer. I.e. first call
TWI_Start_Transceiver to send a request for data to the slave. Then Run this function to collect the
data when they have arrived. Include a pointer to where to place the data and the number of bytes
requested (including the address field) in the function call. The function will hold execution (loop)
until the TWI_ISR has completed with the previous operation, before reading out the data and returning.
If there was an error in the previous transmission the function will return the TWI error code.
****************************************************************************/
unsigned char TWI_Get_Data_From_Transceiver( unsigned char *msg, unsigned char msgSize )
{
  unsigned char i;

  while ( TWI_Transceiver_Busy() );             // Wait until TWI is ready for next transmission.

  if( TWI_statusReg.lastTransOK )               // Last transmission competed successfully.              
  {                                             
    for ( i=0; i<msgSize; i++ )                 // Copy data from Transceiver buffer.
    {
      msg[ i ] = TWI_buf[ i ];
    }
  }
  return( TWI_statusReg.lastTransOK );                                   
}

// ********** Interrupt Handlers ********** //
/****************************************************************************
This function is the Interrupt Service Routine (ISR), and called when the TWI interrupt is triggered;
that is whenever a TWI event has occurred. This function should not be called directly from the main
application.
****************************************************************************/
#pragma vector=TWI_vect
__interrupt void TWI_ISR(void)
{
  static unsigned char TWI_bufPtr;
  
  switch (TWSR)
  {
    case TWI_START:             // START has been transmitted  
    case TWI_REP_START:         // Repeated START has been transmitted
      TWI_bufPtr = 0;                                     // Set buffer pointer to the TWI Address location
    case TWI_MTX_ADR_ACK:       // SLA+W has been tramsmitted and ACK received
    case TWI_MTX_DATA_ACK:      // Data byte has been tramsmitted and ACK received
      if (TWI_bufPtr < TWI_msgSize)
      {
        TWDR = TWI_buf[TWI_bufPtr++];
        TWCR = (1<<TWEN)|                                 // TWI Interface enabled
               (1<<TWIE)|(1<<TWINT)|                      // Enable TWI Interupt and clear the flag to send byte
               (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|           //
               (0<<TWWC);                                 //  
      }else                    // Send STOP after last byte
      {
        TWI_statusReg.lastTransOK = TRUE;                 // Set status bits to completed successfully. 
        TWCR = (1<<TWEN)|                                 // TWI Interface enabled
               (0<<TWIE)|(1<<TWINT)|                      // Disable TWI Interrupt and clear the flag
               (0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|           // Initiate a STOP condition.
               (0<<TWWC);                                 //
      }
      break;
    case TWI_MRX_DATA_ACK:      // Data byte has been received and ACK tramsmitted
      TWI_buf[TWI_bufPtr++] = TWDR;
    case TWI_MRX_ADR_ACK:       // SLA+R has been tramsmitted and ACK received
      if (TWI_bufPtr < (TWI_msgSize-1) )                  // Detect the last byte to NACK it.
      {
        TWCR = (1<<TWEN)|                                 // TWI Interface enabled
               (1<<TWIE)|(1<<TWINT)|                      // Enable TWI Interupt and clear the flag to read next byte
               (1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|           // Send ACK after reception
               (0<<TWWC);                                 //  
      }else                    // Send NACK after next reception
      {
        TWCR = (1<<TWEN)|                                 // TWI Interface enabled
               (1<<TWIE)|(1<<TWINT)|                      // Enable TWI Interupt and clear the flag to read next byte
               (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|           // Send NACK after reception
               (0<<TWWC);                                 // 
      }    
      break; 
    case TWI_MRX_DATA_NACK:     // Data byte has been received and NACK tramsmitted
      TWI_buf[TWI_bufPtr] = TWDR;
      TWI_statusReg.lastTransOK = TRUE;                 // Set status bits to completed successfully. 
      TWCR = (1<<TWEN)|                                 // TWI Interface enabled
             (0<<TWIE)|(1<<TWINT)|                      // Disable TWI Interrupt and clear the flag
             (0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|           // Initiate a STOP condition.
             (0<<TWWC);                                 //
      break;      
    case TWI_ARB_LOST:          // Arbitration lost
      TWCR = (1<<TWEN)|                                 // TWI Interface enabled
             (1<<TWIE)|(1<<TWINT)|                      // Enable TWI Interupt and clear the flag
             (0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|           // Initiate a (RE)START condition.
             (0<<TWWC);                                 //
      break;
    case TWI_MTX_ADR_NACK:      // SLA+W has been tramsmitted and NACK received
    case TWI_MRX_ADR_NACK:      // SLA+R has been tramsmitted and NACK received    
    case TWI_MTX_DATA_NACK:     // Data byte has been tramsmitted and NACK received
    case TWI_BUS_ERROR:         // Bus error due to an illegal START or STOP condition
    default:     
      TWI_state = TWSR;                                 // Store TWSR and automatically sets clears noErrors bit.
                                                        // Reset TWI Interface
      TWCR = (1<<TWEN)|                                 // Enable TWI-interface and release TWI pins
             (0<<TWIE)|(0<<TWINT)|                      // Disable Interupt
             (0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|           // No Signal requests
             (0<<TWWC);                                 //
  }
}

I find their Interrupt handler rather confusing to understand, would it be simpiler if they used some mechanism to synchronize MTX and MRX in their ISR with a mutex or semaphore.

 

Also at page 8, for a function TWI_Start_Transceiver_with_Data( unsigned char *msg, unsignedchar msgSize)

 they say "

Call this function to send a prepared message. The first byte must contain the slave address and the read/write bit."

 

Why would user have to think that every time he tries to use write function he needs not forget to put first byte in a buffer as an address with R/W bit.

Why not simply add address in some internal function (or contructor in C++) like TWI.setAddress(byte newAddress).

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

I have not read your message very carefully.

 

Most AVR interrupt flags get cleared automatically.   The TWI interrupt must be cleared manually.   If you don't,  it will fire forever.

 

Yes,  the AVR315 operation is not very intuitive.   But the example works.

 

Quite honestly,  it is as easy to poll TWI rather than use interrupts.   The Fleury code is very straightforward and easy to use.

If you do want to use interrupts,  the CodeVision API is sensibly organised.    Even if it waits for completion.

I would use that API.   Start the transaction.   Get on with the rest of my life.    Poll for completion at a later date.

 

I think that CV only provides TWI with a full licence.

The Arduino API is not unreasonable.    And is free.

 

I suggest that you get your project operational with Arduino (or CV).    Then think about modifying AVR315 in a more logical way.

Note that the TWDR register uses the 8-bit Slave address.    Different libraries and device data sheets use 8-bit or 7-bit.

 

David.

 

 

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

TWIE combined with the I bit in SREG is what enables the modules interrupts.  Just remove (1 << TWIE) wherever you want to poll TWINT.

Last Edited: Sun. Mar 12, 2017 - 06:23 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

I have not read your message very carefully.

 

Most AVR interrupt flags get cleared automatically.   The TWI interrupt must be cleared manually.   If you don't,  it will fire forever.

 

Yes,  the AVR315 operation is not very intuitive.   But the example works.

 

Quite honestly,  it is as easy to poll TWI rather than use interrupts.   The Fleury code is very straightforward and easy to use.

If you do want to use interrupts,  the CodeVision API is sensibly organised.    Even if it waits for completion.

I would use that API.   Start the transaction.   Get on with the rest of my life.    Poll for completion at a later date.

 

I think that CV only provides TWI with a full licence.

The Arduino API is not unreasonable.    And is free.

 

I suggest that you get your project operational with Arduino (or CV).    Then think about modifying AVR315 in a more logical way.

Note that the TWDR register uses the 8-bit Slave address.    Different libraries and device data sheets use 8-bit or 7-bit.

 

David.

 

 

 

I've been using Fluery's library so far and even built my own polling i2c library which works just fine.

 

Having ISR like this:

ISR(TWI_vect)
{
 // Blink LED
 PORTA |= (1 << PINA0);
 _delay_ms(500);
 PORTA &= ~(1 << PINA0);
 _delay_ms(500);
 //TWCR = (1 << TWINT);
}

would cause LED to blink constantly as you have mentioned.

 

And clearing TWINT flag  like in this example would make program enter only once in ISR,

 

ISR(TWI_vect)
{
 // Blink LED
 PORTA |= (1 << PINA0);
 _delay_ms(500);
 PORTA &= ~(1 << PINA0);
 _delay_ms(500);
 TWCR = (1 << TWINT);
}

I thought that TWINT gets cleared by Atmega once it find RETI at the end of ISR.

 

 

I am trying to build my own Interrupt driven library.

I have a strange habit of building my own libraries (it's kind a fun for me and I always learn something interesting)

 

So here is a UML diagram of my idea

https://s11.postimg.org/d1039cy2b/UML_Diagram_I2_C.png

 

As of Arduino library, I was checking it yesterday and their code is much more intuitive but still ISR handling is kind of hard to follow same like Atmel's app note.

They are basically same.

 

I also read this article which it has some quite nice explanation

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

 

What I was thinking is maybe having combination of polling and Interrupt TWI adapter, initiating start would include polling and read/write methods would use interrupts.

 

 

Basically communication would look something like this:


#include <avr/io.h>
#include "I2CCommController.h"


int main(void)
{
 //Optional
 //I2CCommController i2cControllerWithNonDefaultConstructor(FREQUENCY,ADDRESS);
 I2CCommController i2cController;
 
 // Enable I2C I/O stream
 i2cController.open();
 i2cController.setFrequency(FREQUENCY);
 
 uint8_t rxBuffer[2];

    while (1) 
    { 
  // set TEMPERATURE SENSOR ADDRESS
  i2cController.setAddress(TEMP_ADDRESS);
  
  if(!i2cController.start())
  {
   // handle ERROR 
  }else
  {
   if(!i2cController.write(TEMP_REGISTER))
   {
    //handle error
   }
   if(!i2cController.readBytes(rxBuffer, 2))
   {
    //handle error
   }
   // Do calculations to get proper temperature
  }
  i2cController.stop();
  
  // set EEPROM chip address
  i2cController.setAddress(EEPROM_ADDRESS);
  
  uint8_t txBuffer[] = {TEMPERATURE, DATA, DATA};
  if(!i2cController.start())
  {
   // handle error
  }else
  {
   if(!i2cController.write(EEPROM_REGISTER))
   {
    //handle error
   }
   if(!i2cController.writeBytes(txBuffer, strlen(txBuffer)))
   {
    //handle error
   }
   // Do calculations to get proper temperature
  }
  i2cController.stop();
  i2cController.close();
    }
}

 

 

From a point of user view, do you find my approach intuitive and easy to use, not having to know anything about i2c?

 

Look at their example, they have a start method, wouldn't you think as a user to try use stop method, wouldn't that be logical if you have start you should have stop method too.

 

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

AVR_Coder_1 wrote:

TWIE combined with the I bit in SREG is what enables the modules interrupts.  Just remove (1 << TWIE) wherever you want to poll TWINT.

Yes I am aware that I bit should be set in SREG and that setting interrupt flag would cause interrupt like in timers and other avr periphetals.

But try removing START bit and program wont jump into ISR.

 

// TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN) | (1 << TWIE);
TWCR = (1<<TWINT) |  (1<<TWEN) | (1 << TWIE);

 

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

Think about it. Most operations consist of transmitting N bytes and receiving M bytes.
.
If you use interrupts, you need to tell the ISR() which Slave, the address of the transmit buffer, receive buffer, N, M.
Start it off and set a flag when done. It is handy to have an error status. Could even use it for the 'done' flag.
.
Polling a la Fleury makes it possible to change stuff as you go. And get error status at every stage.
.
Seriously, I would concentrate on what makes a convenient API.
.
David.

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

Yes David, polling is very practical since one can be able to find exactly where error occoured at every step.

 

Well I thought that user would set address via method setAddres(byte Address) and after that he would continue communicating with a device through read and write methods.

If he wants to address another device he would call again set setAddress method.

So setAddress method should always be called before writing and reading operations.

So interruptHandler which is a class method would know address and which device he is talking to.

 

Of course I would check status register as they are doing in ISR.

Writing operation would set ATmega into Master Transmitter and reading operation would set it into Master Receiver.

 

If you have some other suggestions I would be grateful.

Also why do I need to write START bit into TWI control register to trigger an interrupt, why doesnt writing TWIE and TWINT bits simply make it jump?

 

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

A Start or Stop are important parts of the protocol. I have great respect for the CV and the Fleury polled API. And the CV and Arduino interrupt API. I think that both interrupt implementations could be improved.
.
I am entitled to my personal opinions. You have your own. I suspect that we would both agree about the rubbish that is often posted here. Void functions, global variables, untidy functions, ...
.
I always prefer to start with a solution e.g. proven library, manufacturer app note, ...
Get your application working.
Then replace parts with your improved versions. Until you have made a complete replacement.
.
Most importantly, design how you want to use your new code. Don't just throw it together. This is why you should study other people's API.
.
David.

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

I study currently atmel app code and will try improve it.

I have another question and it's kind a offtopic but I want to add a folder into ATMEL stuido 7.0 and add my .c and .h files,

but I get weird error that I don't know how to resolve,

I tried to find solution online, but wasn't successful.

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

Have you solved the twi based interrupt library?