Saturday, July 16, 2011

I2C Transfer between 2 microcontrollers

In this system, the Olimex board is programmed as an I2C client, and the main controller as an I2C master.


  I2C Client Side

The I2C client code is based on Atmel Application Note AVR311: “Using the TWI module as I2C slave”, and the corresponding Atmel files ‘TWI_Slave.h’ and ‘TWI_Slave.c’. These 2 files shall be included in the project and be part of the compilation. The following line of the ‘TWI_Slave.h’ file should however be changed from:
               #define TWI_BUFFER_SIZE 4
to:
                #define TWI_BUFFER_SIZE 36

Here, we chose a very minimalist implementation where the I2C client is always addressed for reading by the master, never for writing. Each time the I2C client is addressed for reading, it expects that the master will always request a fixed number of 36 bytes.

With this minimal implementation, the required I2C client code is deceptively simple. It requires only the following lines, where ‘ens.messageBuf’ is the address of the first byte of the 36-byte buffer to transmit. The buffer is continually updated by the interrupt service routines of the GPS, the gyrocompass and one of the speed transducers.


#include "TWI_Slave.h"


int main(void)
{
   unsigned char TWI_slaveAddress;

  
   // Own TWI slave address
   TWI_slaveAddress = 0x10;


   // Initialise TWI module for slave operation.
   // Include address and/or enable General Call.
   TWI_Slave_Initialise( (unsigned char)((TWI_slaveAddress<<TWI_ADR_BITS)
      | (TRUE<<TWI_GEN_BIT) ));

  
   …
   // Start the TWI transceiver to enable reseption of the first command

   // from the TWI Master.
   TWI_Start_Transceiver_With_Data((char*)(ens.messageBuf), 36);

   for(;;)
   {
      if (!TWI_Transceiver_Busy())
      {
         TWI_Start_Transceiver_With_Data((char*)(ens.messageBuf), 36);
      }
   }
}





I2C Master Side

Here the code used by the Mavric-IIB master controller to read the I2C client buffer.

typedef union  //  this is the same data structure used by the client
{
   unsigned char messageBuf[36];
   struct
   {
      double heading;
      double heel;
      double pitch;
      double rot;
      double cog;
      double sog;
      int long1;
      int long2;
      int lat1;
      int lat2;
      double speed2;
   };
} package;

volatile package pack;
...

int main(void)
{
   ...
   /* set the I2C bit rate generator to 100 kb/s */
   TWSR &= ~0x03;
   TWBR  = 28;
   TWCR |= _BV(TWEN);

   ...

   for(;;)  
   {
      // begin a new cycle
      ...
  
      // fetch the I2C client

     
      // I2C start
      TWCR = (_BV(TWINT) | _BV(TWEN) | _BV(TWSTA));
      while(!(TWCR & _BV(TWINT)));
     

      // select Olimex client (I2C address 0x10) for reading
      TWDR = (0x10 << 1) + 1;
      TWCR = (_BV(TWINT) | _BV(TWEN));
     
while(!(TWCR & _BV(TWINT)));
     

      // read the first 35 bytes, with acknowledge request
      for(i = 0; i < 35; i++)
      {
         TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
         while(!(TWCR & _BV(TWINT)));
         pack.messageBuf[i] = (unsigned char)TWDR;
      }
     

      // read the last byte without acknowledge request
      TWCR = _BV(TWINT) | _BV(TWEN);
      while(!(TWCR & _BV(TWINT)));
      pack.messageBuf[35] = (unsigned char)TWDR;
     

      // I2C stop
      TWCR = _BV(TWINT) | _BV(TWEN)| _BV(TWSTO);
      while(TWCR & _BV(TWSTO));
  
      ...
      // wait for a timer signal to begin a new cycle
      ...

   }
}




WARNING : this is a minimalist implementation of I2C for the ATMega128, without any error checking. If the I2C link between the master and the client is disconnected, the master will hang forever. This would not be acceptable in a commercial or distributable product, but the objective here was to keep the code as simple as possible.

No comments:

Post a Comment