[TUT][SOFT][C]Beginners guide to use AVR mega as TWI/I2C Slave (low level)

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

This guide will only cover low level basic way to do TWI/I2C comms as a "single slave device".

The question about "How if something happens when TWI/I2C lines get disconnected or interrupted by other slave device or else to do with trouble in the lines"

will be consider as the next level problem.

You should be able to do the "master device" things before you try this or you'll end up in confusion.

 

Forgive me if I typed wrong grammar or unusual word, just simply tell me and I'll edit it. 

 

 

 

TWI/I2C hardware is interrupt base which give us ease to do the TWI/I2C comms as "slave device".

 

This will be  6 steps to do:

 

Step 1. Basic initial setup.

 

Step 2. Handling slave address.

 

Step 3. Checking slave address setup.

 

Step 4. Processing master request in ISR

 

Step 5. Processing received data in ISR

 

Step 6. Processing data to transmit in ISR

 

 

MG

 

 

I don't know why I'm still doing this hobby

Last Edited: Wed. Aug 9, 2017 - 04:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 1. Basic initial setup.

 

As a slave device we only needs to do this:

void I2C_slave_init(unsigned char addr)
{
  TWBR= 0x15;                                            // Bit Rate: 137.931 kHz at 12Mhz MCU
  TWSR= 0x00;                                            // No scaling
  TWAR= addr << 1;                                       // Put slave adrress in position
  TWCR = (1<<TWEN) | (1<<TWEA) | (1<<TWIE) | (1<<TWINT); // see datasheet for more details
}

 

 

Or you can simply ignored the Bitrate if you don't know it:

void I2C_slave_init(unsigned char addr)
{
  TWAR= addr << 1;                                       // Put slave adrress in position
  TWCR = (1<<TWEN) | (1<<TWEA) | (1<<TWIE) | (1<<TWINT); // see datasheet for more details
}

 

That way we enable the TWI/I2C hardware and ready to receive request from master. (I will use "master request" for any transaction from master)

 

 

 

MG

I don't know why I'm still doing this hobby

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

Step 2. Handling slave address.

 

 

AVR mega use 7 bits address and use the LSB as a Write / Read command (status).

So at above code the "addr" is shifted one to the left to make room for status bit.

 

As you know at the master you use SLA_W to address the slave device as receiver and SLA_R to address the slave device as transmitter.

The shifted "addr" is the SLA_W value you transmitted from master device.

So you can't simply use the same value for master SLA_W and for "addr" which shifted to left one.

 

For example:

You want your slave device have address 0x20 so you set your slave init address at 0x20. But at master side you have to define your SLA_W as 0x40 which is (0x20<<1).

So this actually set your slave device to have address 0x40 not 0x20. Confusing right?

 

Don't be confused, just set your slave device "desired address" at "master side" then simply shifted right one for your "slave init address setup". That's it !

 

 

 

The shifted "addr" leaving the LSB that is used to enable recognition of the general call address.

If this bit is set, this bit enables the recognition of a General Call given over the TWI/I2C Serial Bus.

Default address for General Call is 0x00, so your slave device "desired address" have selection from 0x01 to 0x7F (as this address will be shifted left one so address > 0x7F will be shifted to 0x7F).

 

 

Edit: - Removed confusing explanation of slave device address setup.

        - Revised LSB of "addr" is General Call Address Recognition bit not slave device mode.

 

 

MG

I don't know why I'm still doing this hobby

Last Edited: Sat. Jul 8, 2017 - 08:48 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 3. Checking slave address setup.

 

 

After you know how to setup your slave address, now it's time to test it.

TWI/I2C hardware ISR will only fire if the slave address matched the slave address setup or address 0x00;

So this can be use to check matched slave address.

 

 

Setup your slave device like this:

#include <avr\io.h>
#include <avr\interrupt.h>
#include <util\delay.h>

ISR(TWI_vect)
{
  PORTA ^= (1<<0);      // use your desired PORT but beware for port used as SDA and SCL !
  _delay_ms(50);        // for eyes speed

  TWCR |= (1<<TWINT);   // clear interrupt flag
}

int main(void)
{
  DDRA =0x01;
  PORTA=0x00;
  I2C_slave_init(0x20); // SLA_W at master side is 0x40
  sei();                // enable global inteerupt

  while(1)
  {
                        // don't do anything yet
  }
}

 

At the master side:

(do all the setup needed as master device, I only shows the basic master request)

#define slave_SLA_W    0x40
#define slave_SLA_R    0x41

while(1)
{
 I2C_start();                   //start TWI/I2C comms

 I2C_write_data(slave_SLA_W);   //address slave as receiver mode     

 I2C_write_address(data_addr);  //address for first receive data       

 I2C_start();                   //repeated start TWI/I2C comms

 I2C_write_data(slave_SLA_R);   //address slave as transmitter mode      

 I2C_read_data(1);              //get data from slave and ACK   

 I2C_read_data(0);              //NACK     

 I2C_stop();                    //stop TWI/I2C comms
}

 

 

You should have "Blinking LED" which responded to master request.

 

 

Edit: Added more info.

 

 

MG

I don't know why I'm still doing this hobby

Last Edited: Sat. Jul 8, 2017 - 08:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 4. Processing master request in ISR

 

 

This section is for checking what's going on in the ISR, SKIP this if you don't have time to check and want to straight to the working code.

 

 

After you succeeded firing your ISR now it's time to do the real task.

Include twi.h at your project if you haven't already include it.

And setup your ISR like this:

#include <util\twi.h>

unsigned char t, status[16];

ISR(TWI_vect)
{
    if((TWSR & 0xF8) == TW_SR_SLA_ACK)    // own address has been acknowledged and addressed to be a receiver
    {
      t=0;
      status[t] = 0x01;
    }
    else
    if((TWSR & 0xF8) == TW_SR_DATA_ACK)   // data has been received ACK returned in slave receiver mode
    {
      status[++t] = 0x02;
    }
    else
    if((TWSR & 0xF8) == TW_SR_DATA_NACK)  // data has been received NACK returned in slave receiver mode
    {
      status[++t] = 0x03;
    }
    else
    if((TWSR & 0xF8) == TW_ST_SLA_ACK)    // device has been addressed to be a transmitter
    {
      status[++t] = 0x04;
    }
    else
    if((TWSR & 0xF8) == TW_ST_DATA_ACK)   // data has been transmitted ACK returned in slave transmitter mode
    {
      status[++t] = 0x05;
    }
    else
    if((TWSR & 0xF8) == TW_ST_DATA_NACK)  // data has been transmitted NACK returned in slave transmitter mode
    {
      status[++t] = 0x06;
    }
    else
    if((TWSR & 0xF8) == TW_SR_STOP)       // stop or repeated start condition received while selected
    {
      status[++t] = 0x07;
    }
    else                                  // other status
    {
      status[++t] = 0x08;
    }

    TWCR |= (1<<TWINT);
}

int main(void)
{
 unsigned char i;

 DDRA =0xFF;
 PORTA=0x00;
 I2C_slave_init(addr);
 sei();

 while(1)
 {
  for(i=0;i<16;++i)
  {
   PORTA= (i<<4) | (status[i] & 0x0F); // the 4 MSB shows 0 to F for indexing, 4 LSB is status number captured
   _delay_ms(500);                     // for eyes
  }

  /*  above is a simple debug so I won't be bothered by other setup
   *  you can setup your UART to debug it
   */
 }
}

 

Connect your PORTA to 2 BCD to 7 segment display to see the value.

Now you can look at the sequence that ISR responded to master request .

 

Remember that the very first "Start" and the very last "Stop" won't fire the ISR.

 

 

MG

I don't know why I'm still doing this hobby

Last Edited: Thu. Jul 6, 2017 - 06:02 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 5. Processing received data in ISR

 

 

In common way TWI/I2C master handling slave device, master request the slave to be a receiver to accept address (index) of the data which will be transmitted or received.

Then request the slave to be a transmitter to send data requested to the master which addressed above.

 

In this section slave device is addressed as receiver. So will be only deal with SLA_W.

 

TWI/I2C comms is "very fast", so to handle quite big data we have to wait for all the transaction to finish, so we need buffer for that.

 

Setup your slave device:

#include <util\twi.h>

#define addr         0x20
#define buffer_size  16

uint8_t addr_index, data_address, rxbuffer[buffer_size];

ISR(TWI_vect)
{
  uint8_t data;

  data=TWDR;

    if((TWSR & 0xF8) == TW_SR_SLA_ACK)
    {
     addr_index = 0;
    }
    else
    if((TWSR & 0xF8) == TW_SR_DATA_ACK)
    {
      if(!addr_index)
      {
       data_address = data;
       addr_index++;
      }
      else
      {
        // store the data at the current address
        rxbuffer[data_address] = data;

        // increment the buffer address
        data_address++;

        if(data_address < buffer_size)
        {
         TWCR |= (1<<TWEA);  // if there is still enough space inside the buffer ACK
        }
        else
        {
         TWCR &= ~(1<<TWEA); // NACK
        }
       }
    }

    TWCR |= (1<<TWINT);
}

int main(void)
{
 I2C_slave_init(addr);
 sei();
 
 while(1)
 {
   debug rxbuffer here
 }
}

 

And setup master side:

#define slave_SLA_W   0x40
#define data_addr     0

uint8_t i;

for(i=0;i<16;i++) tx_buffer[i]=i;

while(1)
{
 I2C_start();

 I2C_write_data(slave_SLA_W);         

 I2C_write_address(data_addr);        

 for(i=0;i<16;i++) I2C_write_data(tx_buffer[i]);

 I2C_stop();

 _delay_ms(500); //give some times for device to debug
}

 

 

You should get the value transmitted by master.

 

 

 

MG

I don't know why I'm still doing this hobby

Last Edited: Thu. Jul 6, 2017 - 07:00 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 6. Processing data to transmit in ISR

 

 

In this section slave device is addressed as receiver then as tranceiver. So will be deal with SLA_W and SLA_R.

 

As noted above TWI/I2C comms is "very fast", so to handle quite big data we have to wait for all the transaction to finish, so we need buffer for that.

 

Setup your slave device:

#include <util\twi.h>

#define addr         0x20
#define buffer_size  16

uint8_t addr_index, data_address, txbuffer[buffer_size];

ISR(TWI_vect)
{
  uint8_t data;

  data=TWDR;

    if((TWSR & 0xF8) == TW_SR_SLA_ACK)
    {
     addr_index = 0;
    }
    else
    if((TWSR & 0xF8) == TW_SR_DATA_ACK)
    {
      if(!addr_index)
      {
       data_address = data;
       addr_index++;
      }
    }
    else
    if((TWSR & 0xF8) == TW_ST_DATA_ACK)
    {
        // send the data at the current address
        TWDR=txbuffer[data_address];

        // increment the buffer address
        data_address++;

        if(data_address < buffer_size)
        {
         TWCR |= (1<<TWEA);  // ACK if there is another buffer address that can be sent
        }
        else
        {
         TWCR &= ~(1<<TWEA); // NACK
        }
       }
    }

    TWCR |= (1<<TWINT);
}

int main(void)
{
 uint8_t i;

 I2C_slave_init(addr);
 sei();

 while(1)
 {
   for(i=0;i<16;i++) txbuffer[i]=i;//update data to send
 }
}

And the master side:

#define slave_SLA_W  0x40
#define slave_SLA_R  0x41
#define data_addr    0

uint8_t i;

while(1)
{
 I2C_start();

 I2C_write_data(slave_SLA_W);         

 I2C_write_address(data_addr);        

 I2C_start();

 I2C_write_data(slave_SLA_R);         

 for(i=0;i<16;i++) rx_buffer[i] = I2C_read_data(1);    

 I2C_read_data(0);                    

 I2C_stop(); 

 //debug rxbuffer here
}

 

 

Now your master side should received the data send by slave.

 

 

 

MG

I don't know why I'm still doing this hobby

Last Edited: Thu. Jul 6, 2017 - 09:37 AM