This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

USCI I2C Multi-Master Implementation

Hello All,

I am looking for guidance on I2C multi-master bus implementation. Instead of wasting hours on debugging and not taking  to the time to fully understand the problem before testing, I spent the equivalent time researching how to implement a multi-master bus implementation and would like some verification of the code I have adapted. 

I started from the SLAA 382- I2C master library - developed by TI and modified it more along the lines of what I will be using it for, as well as adding in Multi-Master functionalities. My system will include multiple multi-master microcontrollers, most likely all will be a MSP430 device. The device(s) I am developing this for is the F5438A, from the experimenter's module, and the F2619. 

________________________________________________________________________________________________
Here is the code I developed: 
#include <msp430.h>
#include "I2C_F5438_MultiMast.h"
signed char mstByteCtr = 0;
signed char slvByteCtr = 0;
unsigned char *Receive_field;
unsigned char *Transmit_field;
unsigned char Transaction_failed;
/**********************************************************************//**
 * @brief  Initializes I2C functionality in the USCIB1 module for Multi-Master 
 *         receive and transmit operation; outputs on P3.7 & P5.3
 *
 * @param  <own_address> address of the I2C master
 * @param  <prescale> value used to divide the SMCLK and set frequency
 *
 * @return none
 *************************************************************************/ 
void Initialize_I2C(unsigned char own_address, unsigned char prescale) {
    P3SEL            |= SDA_PIN;                                                               // Assign I2C pins to USCI_B1
    P5SEL            |= SCL_PIN;
    UCB1CTL1    |= UCSWRST;                                                           // Enable SW reset
    UCB1CTL0     = UCMM + UCMST + UCMODE_3 + UCSYNC; // I2C Multi-Master, synchronous mode
    UCB1CTL1     = UCSSEL_2 + UCSWRST;                                  // Use SMCLK, keep SW reset
    UCB1BR0       = prescale;                                                               // fSCL = SMCLK/prescale
    UCB1BR1       = 0;                                                                             // fSCLmax = fBRCLK/8
    UCB1I2COA   = UCGCEN + own_address                                 // Own Address & Respond to general call
    UCB1CTL1   &= ~UCSWRST;                                                       // Clear SW reset, resume operation
    UCB1IE           |= (UCTXIE|UCRXIE|UCNACKIE|UCALIE);       // Enable TX, RX, AL & NACK interrupts
}
/**********************************************************************//**
 * @brief  Setup the I2C module to multi-master and select the slave device
 *         address. 
 *
 * @param  <slave_address> address of slave to communicate with
 *
 * @return none
 *************************************************************************/ 
void Setup_I2C_MultiMast_Transaction(unsigned char slave_address) {
    UCB1CTL1     |= UCSWRST;                                                           // Enable SW reset
    UCB1CTL0     |= (UCMM|UCMST);                                                 // I2C Multi-Master
    UCB1I2CSA     = slave_address;                                                   // Slave Address
    UCB1CTL1    &= ~UCSWRST;                                                       // Clear SW reset, resume operation
    UCB1IE            |= (UCTXIE|UCRXIE|UCNACKIE|UCALIE);        // Enable TX, RX, AL & NACK interrupts
}
/**********************************************************************//**
 * @brief  This function checks for a slave on the I2C bus
 *
 * @param  <slave_address> address of slave to communicate with
 *
 * @return unsigned char    =>  0: address was not found, 
 *                              1: address found
 *************************************************************************/
unsigned char Check_I2C_slave_present(unsigned char slave_address){
  
  unsigned char IntReg_bak, slaveaddr_bak, returnValue;
  
  IntReg_bak        = UCB1IE;                                                             // restore old UCB1IE
  slaveaddr_bak  = UCB1I2CSA;                                                      // store old slave address
  UCB1IE            &= ~UCNACKIE;                                                     // no NACK interrupt
  UCB1I2CSA       = slave_address;                                                 // set slave address
  __disable_interrupt();
  UCB1CTL1       |= UCTR + UCTXSTT + UCTXSTP;                    // I2C TX, start condition
  while (UCB1CTL1 & UCTXSTP);                                                   // wait for STOP condition
  
  returnValue        = !(UCB1STAT & UCNACKIFG);
  __enable_interrupt();
  UCB1I2CSA      = slaveaddr_bak;                                                 // restore old slave address
  UCB1IE              = IntReg_bak;                                                       // restore old UCB0CTL1
  return returnValue;                                                                           // return whether or not 
                                                                                                               // a NACK occured
}
/**********************************************************************//**
 * @brief  This function checks if the I2C bus communication is in progress
 *
 * @param none
 *
 * @return unsigned char    =>  0: I2C bus is idle, 
 *                              1: communication is in progress
 *************************************************************************/
unsigned char Check_I2C_notready(){
  return (UCB1STAT & UCBBUSY);
}
/**********************************************************************//**
 * @brief  This function checks if the I2C module is configured as a 
 *          Multi-Master  
 *
 * @param none
 *
 * @return unsigned char    =>  0: I2C module is either slave or single-master  
 *                              1: module is configured as Multi-Master
 *************************************************************************/
unsigned char Check_I2C_MultiMast(){
  return (UCB1CTL0 & UCMM);
}
/**********************************************************************//**
 * @brief  This function receives bytes from another I2C device on the bus
 *
 * @param  <byteCount> the number of bytes to receive
 * @param  <field> array variable used to store received data
 *
 * @return none
 *************************************************************************/
void Receive_I2C(unsigned char byteCount, unsigned char *field){
  Receive_field    = field;
  
  if ( byteCount == 1 ){
    mstByteCtr      = 0 ;
    __disable_interrupt();
    UCB1CTL1    |= UCTXSTT;                                                           // I2C start condition
    while (UCB1CTL1 & UCTXSTT);                                                 // Start condition sent?
    UCB1CTL1    |= UCTXSTP;                                                          // I2C stop condition
    __enable_interrupt();
  } else if ( byteCount > 1 ) {
    mstByteCtr      = byteCount - 2 ;
    UCB1CTL1    |= UCTXSTT;                                                          // I2C start condition
  }
}
/**********************************************************************//**
 * @brief  This function transmits bytes to another I2C device on the bus
 *
 * @param  <byteCount> the number of bytes to transmit
 * @param  <field> array variable used to pass transmit data
 *
 * @return none
 *************************************************************************/
void Transmit_I2C(unsigned char byteCount, unsigned char *field){
  Transmit_field   = field;
  mstByteCtr          = byteCount;
  UCB1CTL1        |= UCTR + UCTXSTT;                                         // I2C TX, start condition
}
/**********************************************************************//**
 * The USCI_B1_ISR is structured such that it can be used to transmit or receive
 * any number of bytes by pre-loading bytectr with the byte count. 
 * Receive_field points to the address to receive the next byte.
 * Transmit_field points to the address with the next byte to transmit.
 *************************************************************************/
#pragma vector = USCI_B1_VECTOR
__interrupt void USCI_B1_ISR(void)
{
  switch(__even_in_range(UCB1IV,12))
  {
  case  0: break;                                                                                  // Vector  0: No interrupts
  case  2:                                                                                              // Vector  2: ALIFG
    UCB1IFG &= ~UCALIFG;                                                              // Arbitration Lost 
    UCB1IE  |= UCSTPIE;                                                                   // Enable Stop interrupt
    break;                                                                                               //  to handle failed transaction
  case  4:                                                                                              // Vector  4: NACKIFG
    UCB1CTL1 |= UCTXSTP;                                                             // send STOP if slave sends NACK
    UCB1IFG &= ~UCNACKIFG;
    break;
  case  6: break;                                                                                  // Vector  6: STTIFG
  case  8:                                                                                              // Vector  8: STPIFG
    Transaction_failed = 1;                                                                 // Device now in slave mode
    UCB1IFG &= ~UCSTPIFG;                                                           // Reset Flag 
    UCB1IE  &= ~UCSTPIE;                                                               // Disable Stop interrupt
    break;
  case 10:                                                                                             // Vector 10: RXIFG
    // Master Receive operations
    if ( UCB1CTL0 & UCMST) {
        if ( mstByteCtr == 0 ){
            UCB1CTL1 |= UCTXSTP;                                                     // I2C stop condition            
            *Receive_field = UCB1RXBUF;
            Receive_field++;
        }
        else {
            *Receive_field = UCB1RXBUF;
            Receive_field++;
            mstByteCtr--;
        }
    }
    // Slave Receive operations
    else{
        *Receive_field = UCB1RXBUF;
        Receive_field++;
        slvByteCtr++;
    }
    break;
  case 12:                                                                                             // Vector 12: TXIFG
    // Master Transmit operations
    if ( UCB1CTL0 & UCMST) {
        if (mstByteCtr == 0){
            UCB1CTL1 |= UCTXSTP;                                                     // I2C stop condition
            UCB1IFG &= ~UCTXIFG;                                                      // Clear USCI_B1 TX int flag
        }
        else {
            UCB1TXBUF = *TI_transmit_field;
            Transmit_field++;
            mstByteCtr--;
        }
    }
    // Slave Transmit operations
    else{
        UCB1TXBUF = *TI_transmit_field;
        Transmit_field++;
        slvByteCtr++;
    }   
    break;
  default: break;
  }
}

Here is the Example Main I adapted from the SLAA 382 code set
#include "msp430.h"
#include "I2C_F5438_MultMast.h"
unsigned char timercounter;
unsigned char array[40] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb };
unsigned char store[40] = { 13, 13, 13, 13, 13, 13, 13, 0, 0, 0};
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;                   // Stop WDT
  BCSCTL1 = CALBC1_8MHZ; 
  DCOCTL = CALDCO_8MHZ;
  _EINT();
  
  Initialize_I2C(0x55, 0x0A);
  while ( Check_I2C_notready() );                       // wait for bus to be free
  if ( Check_I2C_slave_present(0x56) )             // slave address may differ from
  {                                                                               // initialization
    Setup_I2C_MultiMast_Transaction(0x56);   // Init transaction
    while ( Check_I2C_notready() );                     // wait for bus to be free
    Receive_I2C(4,store);
    while ( Check_I2C_notready() );                     // wait for bus to be free
      
    Transmit_I2C(4,array);                                      // start transmitting 
  }
  
  LPM3;
 }

________________________________________________________________________________________________

Here are a few conditions have to be handled for Multi-Master to work, in comparison to Master I2C bus:
  1. Return to MultiMST after resided to Slave when addressed
      1a. This can be achieved by checking the I2C module state before 
          transmission and the module to MultiMst 
  2. Re-try sending message after going to Slave mode when arbitration lost
      2a. Enable stop interrupt to denote when transaction is done disable the stop interrupt
             and initiate transaction failed behavior
             The behavior will reinitialize MultiMast and re-attempt transaction
  3. Have receive and transmit ISR code for both Master and Slave operating modes
        For my implementation, I only plan to Master-Transmit or Slave-Recieve, not 
        Master-Receive or Slave-Transmit.

The only thing that is not currently implemented is handling the "transaction failed", which should/will simply reinitialize MultiMast and re-attempt transaction.

Please let me know if there are any errors that are seen in the code or could be foreseen in the use of the functions. 
I am looking for veteran assistance that my code will work the way I would like it to and the conditions listed above are the extent of the differences.  

Thank you in advance for any assistance or advice,

Keith 

**Attention** This is a public forum