1

I'm more of a high level software guy but have been working on some embedded projects lately so I'm sure there's something obvious I'm missing here, though I have spent over a week trying to debug this and every 'MSP' related link in google is purple at this point...

I currently have an MSP430F5529 set up as an I2C slave device whose only responsibility currently is to receive packets from a master device. The master uses industry grade I2C and has been heavily tested and ruled out as the source of my problem here. I'm using Code composer as my IDE using the TI v15.12.3.LTS compiler.

What is currently happening is the master queries how many packets (of size 62 bytes) the slave can hold, then sends over a few packets which the MSP is just currently discarding. This is happening every 100ms on the master side and for the minimal example below the MSP will always just send back 63 when asked how many packets it can hold. I have tested the master with a Total Phase Aardvark and everything is working fine with that so I'm sure it's a problem on the MSP side. The problem is as follows:

The program will work for 15-20 minutes, sending over tens of thousands of packets. At some point the slave starts to hold the clock line low and when paused in debug mode, is shown to be stuck in the start interrupt. The same sequence of events is happening every single time to cause this.

1) Master queries how many packets the MSP can hold.
2) A packet is sent successfully
3) Another packet is attempted but < 62 bytes are received by the MSP (counted by logging how many Rx interrupts I receive). No stop condition is sent so master times out.
4) Another packet is attempted. A single byte is sent before the stop condition is sent.
5) Another packet is attempted to be sent. A start interrupt, then a Tx interrupt happens and the device hangs.

Ignoring the fact that I'm not handling the timeout errors on the master side, something very strange is happening to cause that sequence of events, but that's what happens every single time.

Below is the minimal working example which is reproducing the problem. My particular concern is with the SetUpRx and SetUpTx functions. The examples that the Code Composer Resource Explorer gives only has examples of Rx or Tx, I'm not sure if I'm combining them in the right way. I also tried removing the SetUpRx completely, putting the device into transmit mode and replacing all calls to SetUpTx/Rx with mode = TX_MODE/RX_MODE, which did work but still eventually holds the clock line low. Ultimately I'm not 100% sure on how to set this up to receive both Rx and Tx requests.

#include "driverlib.h"

#define SLAVE_ADDRESS           (0x48)

// During main loop, set mode to either RX_MODE or TX_MODE
// When I2C is finished, OR mode with I2C_DONE, hence upon exit mdoe will be one of I2C_RX_DONE or I2C_TX_DONE
#define RX_MODE                 (0x01)
#define TX_MODE                 (0x02)
#define I2C_DONE                (0x04)
#define I2C_RX_DONE             (RX_MODE | I2C_DONE)
#define I2C_TX_DONE             (TX_MODE | I2C_DONE)


/**
 * I2C message ids
 */
#define MESSAGE_ADD_PACKET      (3)
#define MESSAGE_GET_NUM_SLOTS   (5)

static volatile uint8_t mode = RX_MODE;     // current mode, TX or RX

static volatile uint8_t rx_buff[64] = {0};  // where to write rx data
static volatile uint8_t* rx_data = rx_buff; // used in rx interrupt

static volatile uint8_t tx_len = 0;         // number of bytes to reply with

static inline void SetUpRx(void) {
    // Specify receive mode
    USCI_B_I2C_setMode(USCI_B0_BASE, USCI_B_I2C_RECEIVE_MODE);

    // Enable I2C Module to start operations
    USCI_B_I2C_enable(USCI_B0_BASE);

    // Enable interrupts
    USCI_B_I2C_clearInterrupt(USCI_B0_BASE, USCI_B_I2C_TRANSMIT_INTERRUPT);
    USCI_B_I2C_enableInterrupt(USCI_B0_BASE, USCI_B_I2C_START_INTERRUPT + USCI_B_I2C_RECEIVE_INTERRUPT + USCI_B_I2C_STOP_INTERRUPT);

    mode = RX_MODE;
}


static inline void SetUpTx(void) {
    //Set in transmit mode
    USCI_B_I2C_setMode(USCI_B0_BASE, USCI_B_I2C_TRANSMIT_MODE);

    //Enable I2C Module to start operations
    USCI_B_I2C_enable(USCI_B0_BASE);

    //Enable master trasmit interrupt
    USCI_B_I2C_clearInterrupt(USCI_B0_BASE, USCI_B_I2C_RECEIVE_INTERRUPT);
    USCI_B_I2C_enableInterrupt(USCI_B0_BASE, USCI_B_I2C_START_INTERRUPT + USCI_B_I2C_TRANSMIT_INTERRUPT + USCI_B_I2C_STOP_INTERRUPT);

    mode = TX_MODE;
}


/**
 * Parse the incoming message and set up the tx_data pointer and tx_len for I2C reply
 *
 * In most cases, tx_buff is filled with data as the replies that require it either aren't used frequently or use few bytes.
 * Straight pointer assignment is likely better but that means everything will have to be volatile which seems overkill for this
 */
static void DecodeRx(void) {
    static uint8_t message_id = 0;

    message_id = (*rx_buff);
    rx_data = rx_buff;

    switch (message_id) {
    case MESSAGE_ADD_PACKET:        // Add some data...
        // do nothing for now
        tx_len = 0;

        break;

    case MESSAGE_GET_NUM_SLOTS:     // How many packets can we send to device
        tx_len = 1;

        break;

    default:
        tx_len = 0;

        break;
    }
}

void main(void) {
    //Stop WDT
    WDT_A_hold(WDT_A_BASE);

    //Assign I2C pins to USCI_B0
    GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P3, GPIO_PIN0 + GPIO_PIN1);

    //Initialize I2C as a slave device
    USCI_B_I2C_initSlave(USCI_B0_BASE, SLAVE_ADDRESS);

    // go into listening mode
    SetUpRx();

    while(1) {
        __bis_SR_register(LPM4_bits + GIE);

        // Message received over I2C, check if we have anything to transmit
        switch (mode) {
        case I2C_RX_DONE:
            DecodeRx();
            if (tx_len > 0) {
                // start a reply
                SetUpTx();
            } else {
                // nothing to do, back to listening
                mode = RX_MODE;
            }
            break;

        case I2C_TX_DONE:
            // go back to listening
            SetUpRx();

            break;

        default:
            break;
        }
    }
}

/**
 * I2C interrupt routine
 */
#pragma vector=USCI_B0_VECTOR
__interrupt void USCI_B0_ISR(void) {
    switch(__even_in_range(UCB0IV,12)) {
    case USCI_I2C_UCSTTIFG:
        break;

    case USCI_I2C_UCRXIFG:
        *rx_data = USCI_B_I2C_slaveGetData(USCI_B0_BASE);
        ++rx_data;

        break;

    case USCI_I2C_UCTXIFG:
        if (tx_len > 0) {
            USCI_B_I2C_slavePutData(USCI_B0_BASE, 63);
            --tx_len;
        }

        break;

    case USCI_I2C_UCSTPIFG:
        // OR'ing mode will let it be flagged in the main loop
        mode |= I2C_DONE;
        __bic_SR_register_on_exit(LPM4_bits);

        break;
    }
}

Any help on this would be much appreciated! Thank you!

Muckle_ewe
  • 1,123
  • 3
  • 12
  • 18
  • TX/RX is controlled by the master; you should not ever need to change the module configuration for that. – CL. Nov 09 '16 at 13:05
  • Hi CL. Ok that's fine, so the I2C enable/setMode/enableInterrupt should only be done at the start right? If so I have already tried that and had the same affect. – Muckle_ewe Nov 09 '16 at 13:17
  • What exactly do you mean with "stuck in the start interrupt"? – CL. Nov 09 '16 at 13:32
  • When I suspend the debugger in Code Composer after detecting there has been an issue, the code will jump to the I2C interrupt vector and is in the start interrupt case statement. Subsequent starts and suspends yield the same result. – Muckle_ewe Nov 09 '16 at 13:49
  • Might be erratum USCI30 or USCI39. – CL. Nov 09 '16 at 13:56
  • Thanks for that suggestion, the datasheet revisions look promising and the symptoms seems to match. I'll have a look at the workarounds to see if they fix the problem – Muckle_ewe Nov 09 '16 at 14:02

0 Answers0