Jump to content
Sign in to follow this  
canid

I2C multi-slave automation project.

Recommended Posts

This is very much in progress, and there's nothing fancy about it, but I wanted to share with you what I'm working on at the moment since I'm back into the 430 again after a while in arm/linux land.

 

Only the relay stuff is implemented for now and the master is currently just a test which advances the relay selections by push of the s2 button. Criticism, ideas and insults welcome :D

 

I would like to do various slave firmwares for sensors, etc and I will be incoprorating also a BBB controller at some point.

 

Code shamefully stolen/modified from TI examples.

 

Master.c

//******************************************************************************
//   MSP430G2x31 - I2C Automation Controller/Sensor, Master
//
//   Description: This is the test firmware for the I2C master controller. It
// currently advances, by action of s2 on the launchpad (falling edge of P1.3),
// the control word from 0x00 through 0x1111 were each bit is the relay to be
// (0) opened or (1) closed. It will be updated to reflect the desired control
// protocol shortly.
//
//******************************************************************************
 
#include <msp430g2231.h>
 
char MST_Data = 0;                      // Variable for transmitted data
char SLV_Addr = 0x90;                   // Address is 0x48 << 1 bit + 0 for Write
int I2C_State = 0;                      // State variable
 
void main(void) {
    volatile unsigned int i;             // Use volatile to prevent removal
 
    WDTCTL = WDTPW + WDTHOLD;            // Stop watchdog
    if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF) {
        while(1);                          // If calibration constants erased do not load, trap CPU!!
    }
    BCSCTL1 = CALBC1_1MHZ;               // Set DCO
    DCOCTL = CALDCO_1MHZ;
 
    P1OUT = 0xC0;                        // P1.6 & P1.7 Pullups, others to 0
    P1OUT |= BIT3;
    P1REN |= 0xC0;                       // P1.3, P1.6 & P1.7 Pullups
    P1REN |= BIT3;
    P1DIR = 0xFF;
    P1DIR &= ~BIT3;                   // Unused pins as outputs. s2 as input.
    P1IE |= BIT3;                   // Enable P1 interrupt for P1.3
    P2OUT = 0;
    P2DIR = 0xFF;
 
    USICTL0 = USIPE6+USIPE7+USIMST+USISWRST; // Port & USI mode setup
    USICTL1 = USII2C+USIIE;              // Enable I2C mode & USI interrupt
    USICKCTL = USIDIV_3+USISSEL_2+USICKPL; // Setup USI clocks: SCL = SMCLK/8 (~125kHz)
    USICNT |= USIIFGCC;                  // Disable automatic clear control
    USICTL0 &= ~USISWRST;                // Enable USI
    USICTL1 &= ~USIIFG;                  // Clear pending flag
    P1IFG &= ~BIT3;                     // Clear p1.3 interrupt flag.
    _EINT();
 
    while(1) {
        USICTL1 |= USIIFG;                 // Set flag and start communication
        LPM0;                              // CPU off, await USI interrupt
        _NOP();                            // Used for IAR
        for (i = 0; i < 5000; i++);        // Dummy delay between communication cycles
    }
}
 
/******************************************************
// Port1 isr
******************************************************/
 
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void) {
    USICTL1 &= ~USIIE;          // Disable I2C interrupt until we finish.
 
    P1IE &= ~BIT3;              // Clear own interrupt until we finish
 
    if(MST_Data < 15) {
        MST_Data++;            // Increment Master data
    } else {
        MST_Data = 0;
    }
    P1OUT &= ~BIT0;
    __delay_cycles(1000000);
    P1OUT |= BIT0;
 
    P1IE |= BIT3;               // Re-enable own interrupt and clear interrupt flag.
    P1IFG &= ~BIT3;
 
    USICTL1 |= USIIE;           // Re-enable I2C interrupt and clear pending flag.
    USICTL1 &= ~USIIFG;
}
 
/******************************************************
// USI isr
******************************************************/
#pragma vector = USI_VECTOR
__interrupt void USI_TXRX (void) {
    switch(I2C_State) {
        case 0: // Generate Start Condition & send address to slave
              //P1OUT |= 0x01;           // LED on: sequence start
            USISRL = 0x00;           // Generate Start Condition...
            USICTL0 |= USIGE+USIOE;
            USICTL0 &= ~USIGE;
            USISRL = SLV_Addr;       // ... and transmit address, R/W = 0
            USICNT = (USICNT & 0xE0) + 0x08; // Bit counter = 8, TX Address
            I2C_State = 2;           // Go to next state: receive address (N)Ack
            break;
 
        case 2: // Receive Address Ack/Nack bit
            USICTL0 &= ~USIOE;       // SDA = input
            USICNT |= 0x01;          // Bit counter = 1, receive (N)Ack bit
            I2C_State = 4;           // Go to next state: check (N)Ack
            break;
 
        case 4: // Process Address Ack/Nack & handle data TX
            USICTL0 |= USIOE;        // SDA = output
            if (USISRL & 0x01) {       // If Nack received, send stop
                USISRL = 0x00;
                USICNT |=  0x01;       // Bit counter = 1, SCL high, SDA low
                I2C_State = 10;        // Go to next state: generate Stop
                //P1OUT |= 0x01;         // Turn on LED: error
            } else { // Ack received, TX data to slave...
                USISRL = MST_Data;     // Load data byte
                USICNT |=  0x08;       // Bit counter = 8, start TX
                I2C_State = 6;         // Go to next state: receive data (N)Ack
                //P1OUT &= ~0x01;        // Turn off LED
            } break;
 
        case 6: // Receive Data Ack/Nack bit
            USICTL0 &= ~USIOE;       // SDA = input
            USICNT |= 0x01;          // Bit counter = 1, receive (N)Ack bit
            I2C_State = 8;           // Go to next state: check (N)Ack
            break;
 
        case 8: // Process Data Ack/Nack & send Stop
            USICTL0 |= USIOE;
            if (USISRL & 0x01) {       // If Nack received...
                //P1OUT |= 0x01;         // Turn on LED: error
            } else {                     // Ack received
                //P1OUT &= ~0x01;        // Turn off LED
            }
            // Send stop...
            USISRL = 0x00;
            USICNT |=  0x01;         // Bit counter = 1, SCL high, SDA low
            I2C_State = 10;          // Go to next state: generate Stop
            break;
 
        case 10:// Generate Stop Condition
            USISRL = 0x0FF;          // USISRL = 1 to release SDA
            USICTL0 |= USIGE;        // Transparent latch enabled
            USICTL0 &= ~(USIGE+USIOE);// Latch/SDA output disabled
            I2C_State = 0;           // Reset state machine for next transmission
            LPM0_EXIT;               // Exit active for next transfer
            break;
    }
    USICTL1 &= ~USIIFG;                  // Clear pending flag
}

Slave.c

//******************************************************************************
//   MSP430G2x31 - I2C Automation Controller/Sensor, Slave Node
//
//   Description: This is the firmware for the i2c slave controller
//
//******************************************************************************

#include <msp430.h>

// peripheral selections
#define ADC                                // Define if slave needs ADC10

//   Select peripheral type for each pin:
// RELAY for relay or other digital output such as MOSFET or other TTL control,
// DIGITAL for digtal input such as TTL sensor, switch, etc.
// CT for current transformer,
// NTC for negative temp. coefficient thermistor,
// PTC for positive temp. coefficient thermistor,
#define P1_0 RELAY
#define P1_1 RELAY
#define P1_2 RELAY
#define P1_3 RELAY
#define P1_4 CT
#define P1_5 NTC

char ownAddr = 0x90;                  // Address is 0x48<<1 for R/W
int i2cState = 0;                     // State variable
unsigned char RXData=0;

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;            // Stop watchdog
  if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)
  {
    while(1);                          // If calibration constants erased
                                       // do not load, trap CPU!!
  }

  // Peripherals setup
  // port setup stuff
  #if P1_0 == RELAY
    P1DIR |= BIT0;                        // set pin to output.
  #elif P1_0 == THERM
    P1DIR &= ~BIT0;                        // set pin to input.
    ADC10CTL1 |= INCH0;                    // enable adc chan. 0
  #elif P1_0 == CT
    P1DIR &= ~BIT0;                        // set pin to input.
    ADC10CTL1 |= INCH0;                    // enable adc chan
  #elif P1_0 == ACLK

  #elif P1_0 == TA0CLK

  #endif

  #if P1_1 == RELAY
    P1DIR |= BIT1;                        // set pin to output.
  #elif P1_1 == THERM
    P1DIR &= ~BIT1;                        // set pin to input.
    ADC10CTL1 |= INCH1;                    // enable adc chan.
  #elif P1_1 == CT
    P1DIR &= ~BIT1;                        // set pin to input.
    ADC10CTL1 |= INCH1;                    // enable adc chan.
  #elif P1_1 == TA0

  #endif

  #if P1_2 == RELAY
      P1DIR |= BIT2;                        // set pin to output.
  #elif P1_2 == THERM
    P1DIR &= ~BIT2;                        // set pin to input.
    ADC10CTL1 |= INCH2;                    // enable adc chan.
  #elif P1_2 == CT
    P1DIR &= ~BIT2;                        // set pin to input.
    ADC10CTL1 |= INCH2;                    // enable adc chan.
  #elif P1_2 == TA0

  #endif

  #if P1_3 == RELAY
      P1DIR |= BIT3;                        // set pin to output.
  #elif P1_3 == NTC
    P1DIR &= ~BIT3;                        // set pin to input.
    ADC10CTL1 |= INCH3;                    // enable adc chan.
  #elif P1_3 == CT
    P1DIR &= ~BIT3;                        // set pin to input.
    ADC10CTL1 |= INCH3;                    // enable adc chan.
  #elif P1_3 == ADC10CLK

  #elif P1_3 == VREFn

  #endif

  #if P1_4 == RELAY
      P1DIR |= BIT4;                        // set pin to output.
  #elif P1_4 == THERM
    P1DIR &= ~BIT4;                        // set pin to input.
    ADC10CTL1 |= INCH4;                    // enable adc chan.
  #elif P1_4 == CT
    P1DIR &= ~BIT4;                        // set pin to input.
    ADC10CTL1 |= INCH4;                    // enable adc chan.
  #elif P1_4 == SMCLK

  #elif P1_4 == VREFp

  #endif

  #if P1_5 == RELAY
      P1DIR |= BIT5;                        // set pin to output.
  #elif P1_5 == THERM
      P1DIR &= ~BIT5;                        // set pin to input.
      ADC10CTL1 |= INCH5;                    // enable adc chan.
  #elif P1_5 == CT
      P1DIR &= ~BIT5;                        // set pin to input.
      ADC10CTL1 |= INCH5;                    // enable adc chan.
  #elif P1_5 == SCLK

  #endif


  BCSCTL1 = CALBC1_1MHZ;               // Set DCO
  DCOCTL = CALDCO_1MHZ;

  P1OUT = 0xC0;                        // P1.6 & P1.7 Pullups
  P1REN |= 0xC0;                       // P1.6 & P1.7 Pullups
  P2OUT = 0;
  P2DIR = 0xFF;

  USICTL0 = USIPE6+USIPE7+USISWRST;    // Port & USI mode setup
  USICTL1 = USII2C+USIIE+USISTTIE;     // Enable I2C mode & USI interrupts
  USICKCTL = USICKPL;                  // Setup clock polarity
  USICNT |= USIIFGCC;                  // Disable automatic clear control
  USICTL0 &= ~USISWRST;                // Enable USI
  USICTL1 &= ~USIIFG;                  // Clear pending flag
  _EINT();

  #ifdef ADC
    ADC10CTL0 |= ENC + ADC10SC;
  #endif

  while(1)
  {
    LPM0;                              // CPU off, await USI interrupt
    _NOP();                            // Used for IAR
  }
}

//******************************************************************************
// USI interrupt service routine
//******************************************************************************
#pragma vector = USI_VECTOR
__interrupt void USI_TXRX (void)
{
  if (USICTL1 & USISTTIFG)             // Start entry?
  {
    P1OUT |= 0x01;                     // LED on: sequence start
    i2cState = 2;                     // Enter 1st state on start
  }

  switch(i2cState)
    {
      case 0: // Idle, should not get here
              break;

      case 2: // RX Address
              USICNT = (USICNT & 0xE0) + 0x08; // Bit counter = 8, RX address
              USICTL1 &= ~USISTTIFG;   // Clear start flag
              i2cState = 4;           // Go to next state: check address
              break;

      case 4: // Process Address and send (N)Ack
              if (USISRL & 0x01)       // If read...
                ownAddr++;            // Save R/W bit
              USICTL0 |= USIOE;        // SDA = output
              if (USISRL == ownAddr)  // Address match?
              {
                USISRL = 0x00;         // Send Ack
                P1OUT &= ~0x01;        // LED off
                i2cState = 8;         // Go to next state: RX data
              }
              else
              {
                USISRL = 0xFF;         // Send NAck
                P1OUT |= 0x01;         // LED on: error
                i2cState = 6;         // Go to next state: prep for next Start
              }
              USICNT |= 0x01;          // Bit counter = 1, send (N)Ack bit
              break;

      case 6: // Prep for Start condition
              USICTL0 &= ~USIOE;       // SDA = input
              ownAddr = 0x90;         // Reset slave address
              i2cState = 0;           // Reset state machine
              break;

      case 8: // Receive data byte
              USICTL0 &= ~USIOE;       // SDA = input
              USICNT |=  0x08;         // Bit counter = 8, RX data
              i2cState = 10;          // Go to next state: Test data and (N)Ack
              break;

      case 10:// Check Data & TX (N)Ack
              RXData = USISRL;                 // Get RX data

              USICTL0 |= USIOE;        // SDA = output
              P1OUT = 0x0;
              if(RXData & BIT0) {
                  P1OUT |= BIT1;
              } if(RXData & BIT1) {
                  P1OUT |= BIT2;
              } if(RXData & BIT2) {
                  P1OUT |= BIT3;
              } if(RXData & BIT3) {
                  P1OUT |= BIT4;
              } USISRL = 0x00;         // Send Ack
              P1OUT &= ~0x01;        // LED off
/*              {
                USISRL = 0xFF;         // Send NAck
                P1OUT |= 0x01;         // LED on: error
              }
*/
              USICNT |= 0x01;          // Bit counter = 1, send (N)Ack bit
              i2cState = 6;           // Go to next state: prep for next Start
              break;
    }

  USICTL1 &= ~USIIFG;                  // Clear pending flags
}

And I got lazy and did a fraunchpad version for testing which uses LED 5-8 instead of external relays and the extra voltage level.

//******************************************************************************
//  MSP430G2x31 - I2C Relay Controller, Fraunchpad Slave
//
//   Description: This is a test version of the slave firmware for conveniently
// testing using the Fraunchpad and it's user LEDs 5-8 in place of relays.
//
//******************************************************************************
 
 
#include <msp430.h>
 
unsigned char RXData;
 
int main(void)
 
{
 
    WDTCTL = WDTPW + WDTHOLD;
 
    // Init SMCLK = MCLk = ACLK = 1MHz
    CSCTL0_H = 0xA5;
    CSCTL1 |= DCOFSEL0 + DCOFSEL1;          // Set max. DCO setting = 8MHz
    CSCTL2 = SELA_3 + SELS_3 + SELM_3;      // set ACLK = MCLK = DCO
    CSCTL3 = DIVA_3 + DIVS_3 + DIVM_3;      // set all dividers to 1MHz
  
    // Configure Pins for I2C
    P1SEL1 |= BIT6 + BIT7;                  // Pin init
 
    // Configure pins for LEDs 5-8
    P3DIR = BIT4+BIT5+BIT6+BIT7;
 
    // eUSCI configuration
    UCB0CTLW0 |= UCSWRST ;              //Software reset enabled
    UCB0CTLW0 |= UCMODE_3  + UCSYNC;        //I2C mode, sync mode
    UCB0I2COA0 = 0x48 + UCOAEN;         //own address is 0x48 + enable
    UCB0CTLW0 &=~UCSWRST;               //clear reset register
    UCB0IE |=  UCRXIE0;                 //receive interrupt enable
 
    __bis_SR_register(CPUOFF + GIE);        // Enter LPM0 w/ interrupts
    __no_operation();
}
 
 
 
 
#pragma vector = USCI_B0_VECTOR
__interrupt void USCIB0_ISR(void)
 
{
 
   switch(__even_in_range(UCB0IV,0x1E))
    {
      case 0x00: break;                     // Vector 0: No interrupts break;
      case 0x02: break;                     // Vector 2: ALIFG break;
      case 0x04: break;                     // Vector 4: NACKIFG break;
      case 0x06: break;                     // Vector 6: STTIFG break;
      case 0x08: break;                     // Vector 8: STPIFG break;
      case 0x0a: break;                     // Vector 10: RXIFG3 break;
      case 0x0c: break;                     // Vector 14: TXIFG3 break;
      case 0x0e: break;                     // Vector 16: RXIFG2 break;
      case 0x10: break;                     // Vector 18: TXIFG2 break;
      case 0x12: break;                     // Vector 20: RXIFG1 break;
      case 0x14: break;                     // Vector 22: TXIFG1 break;
      case 0x16:
        RXData = UCB0RXBUF;                 // Get RX data
                if(RXData & BIT0) {
                    P3OUT |= BIT4;
                } else P3OUT &= ~BIT4;
                if(RXData & BIT1) {
                    P3OUT |= BIT5;
                } else P3OUT &= ~BIT5;
                if(RXData & BIT2) {
                    P3OUT |= BIT6;
                } else P3OUT &= ~BIT6;
                if(RXData & BIT3) {
                    P3OUT |= BIT7;
                } else P3OUT &= ~BIT7;
        break;                              // Vector 24: RXIFG0 break;
      case 0x18: break;                     // Vector 26: TXIFG0 break;
      case 0x1a: break;                     // Vector 28: BCNTIFG break;
      case 0x1c: break;                     // Vector 30: clock low timeout break;
      case 0x1e: break;                     // Vector 32: 9th bit break;
      default: break;
    }
 
}

Share this post


Link to post
Share on other sites

I'll be updating soonish, but I'm merging the slave code and trying to do pheripheral selection/config using preprocessing.

 

peripherals I'm incorporating into the slave code:

* Digital out:    Relays and TTL,

* Digital  in:     Switches and TTL,

* Thermistors: Specific NTC at least.

* CTs

 

When I've got that fleshed out, I'll get back to the first master code and rethink the comand protocol.

Share this post


Link to post
Share on other sites

Just a thought, why are you using a switch case in your USCI_BO_ISR? If you only care about one or two cases then it would make much more sense to just use a straight if/ else statement as switch statements can take up a lot of time. They are essentially just massive if/else strings, depending on your compiler. So, for example (and again it depends on how a compiler compiles it), if the flag is 0x1e, the code will go through and check each of the values above it before it hits that case. I'm not the best at coding so you may have a reason for doing it, it just jumped out at me.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×