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.

MSP430G2553: USCI_B Tbuf parameter violation, sends START condition IMMEDIATELY after STOP condition

Part Number: MSP430G2553

This has me absolutely stumped, I've looked over the data sheet, user's guide, errata sheet and have no idea what I did wrong or how to do anything about it. 

Some context: 

This I2C implementation uses polling (See issue USCI29 in SLAZ440H)

No repeated start conditions are used (See issue USCI35 in SLAZ440H)

Bus speed is 100KHz with 4.7k pull-up resistors, P1.6 jumper is removed. I'm using the launchpad for the MSP430G2 series, rev 1.5.

The goal of this application is to read from register INTCAPB on an MCP23018, when INTB (connected to P2.2) interrupts. The P2 interrupt sets a software flag, and this triggers a write to set the register address then a subsequent read from register INTCAPB.

Yes, I have read Solutions to Common eUSCI and USCI Serial Communication Issues on MSP430™ MCUs (SLAA734)  and the I2C example code for my device. I tried the TI I2C implementation and the I2C interrupts resulted in a call to the TRAPINT, trapping the program counter.

Now to the symptoms:

As you can see from the logic analyzer output, at first the START and STOP conditions are within I2C spec and are acknowledged. They remain so until the end of the most recent write transaction. Neither the slave, nor the logic analyzer recognize the following read transaction.

 Setting a breakpoint right after the STOP condition seems to fix the issue by leaving a massive gap between the STOP and START condition but I haven't been able to resolve the issue otherwise. When debugging I've found this is the offending code: 

if (status & I2C_REG_SET) {           // If setting the register counter for a later read operation
        if (status & KEYPAD_READ) {         // If reading the state of the keypad
                                            // MCP23018's address will already be loaded
          if (UCB0TXBUF == INTCAPB) {       // If the last byte sent was INTCAPB
            // Control byte was successfully sent
            // Now the STOP condition can be sent, wait 4.7us, then send START condition as a receiver
            status &= ~I2C_REG_SET;         // Reset I2C_REG_SET bit, the counter was successfully sent
            UCB0CTL1 |= UCTXSTP;            // Send STOP condition
            while(UCB0CTL1 & UCTXSTP);      // Ensure STOP condition is finished before proceeding
            __delay_cycles(Tbuf);           // Delay 4.7us (1/SMCLK * 75)
            UCB0CTL1 &= ~UCTR;              // Become reciever
            UCB0CTL1 |= UCTXSTT;            // Send START condition
          } else {                          // Otherwise:
            UCB0TXBUF = INTCAPB;            // Send INTCAPB as the register number byte
            i2c_i = INTCAPB;                // Update the master's copy of the register iterator
            __delay_cycles(Tbuf);           // Delay 4.7us (1/SMCLK * 75)
          }
        }
      }


  • Hi Stephen,

    Thank you for the detailed information and I'm glad you were able to find the Solutions to Common eUSCI and USCI Serial Communication Issues on MSP430™ MCUs (SLAA734) application report. After looking at the MCP23018 datasheet, it looks like you are violating the start condition setup time. You noted this previously as well. According to this document, the start condition setup time needs to be at least 4.7 us and you're observing 0.48 us.

    My first thought is that you're not implementing a long enough delay (__delay_cycles(Tbuf);). To test if this function is delaying long enough, can you place it in a separate location such as the main loop and toggle a GPIO before and after calling it. Then measure the time between the toggles?

    Also, could you provide you I2C initialization code and your MCLK/SMCLK initialization?

    Best regards,
    Caleb Overbay
  • My first thought was that the delay wasn't long enough, however even multiplying the delay by 10 had absolutely no effect. I use __delay_cycles(Tbuf); in the rest of the code and it works perfectly, the issue only appears when running the code in the original post.

    #define Tbuf            75                              // The proper bus free time period (in CPU cycles) as specified by I2C spec 1.1 (4.6875us)

      // Clock system configuration code:
      DCOCTL = CALDCO_16MHZ;                    // Set DCO register to the precalibrated 16MHz speed, verified 16MHz by logic analyzer via P1.4 SMCLK output
      BCSCTL1 = CALBC1_16MHZ;                   // Set RESL bits
    BCSCTL3 = LFXT1S_2;                       // Set ACLK to VLOCLK
      // Initialize USCI_B0 to Philips 1.1 spec I2C master mode. Pins are already assigned to SCL and SDA
      UCB0CTL0 = UCMST | UCMODE_3 | UCSYNC;     // 7-bit addressing, single master
      UCB0CTL1 |= UCSSEL_2 | UCTR | UCSWRST;    // Transmitter mode, sourced from SMCLK
      UCB0BR1 = 0;                              // Set the upper baud rate register to 0
      UCB0BR0 = I2C_CLOCK_DIV;                  // 100KHz bus frequency, (16MHz/160)
      UCB0STAT = 0;                             // Reset state change flags
      IFG2 = 0;                                 // Reset all transmit and recieve flags, including USCI_A
      UCB0I2CSA = MCP23018_OPCODE;              // Initialize slave address to MCP23018's base address
      UCB0I2CIE = 0;                            // Disable all I2C related interrupts (See issue USCI29 in SLAZ440H)
      IE2 = 0;                                  // Disable USCI_A and USCI_B TX and RX interrupts (See issue USCI29 in SLAZ440H)
      UCB0CTL1 &= ~UCSWRST;                     // Reset UCSWRST, starting I2C communication
      UCB0CTL1 |= UCTXSTT;                      // Start condition


    Here's the same exact code and logic output, but zoomed out, you can see the delay between START and STOP works great the first time, however the second time, that little blip after '17' is supposed to be a STOP and START condition.

  • As I said in my last post, the length of Tbuf definitely not at all the issue here, was the first thing I checked. I'm going to try using the TA0 interrupts to wake the device from LPM1 to verify it's not an issue of time on my end.

    My guess is either:

    1. The compiler is trying to "optimize" my code by combining

    UCB0CTL1 |= UCTXSTP;

     and

    UCB0CTL1 |= UCTXSTT;

    Into: 

    UCB0CTL1 |= UCTXSTT + UCTXSTP;

    2. There is an undiscovered bug with the USCI_B module.

    I would wager it's a compiler issue, since it only appears in that particular block and happens 100% of the time. I'll report back once I replace __delay_cycles() with TimerA interrupts.

  • Hi Stephen,

    I'll look into this closer in the morning but in the meantime could you turn optimization off and run the code again? This will let you know if your first guess is true. Depending on your level of optimization, I think this scenario could certainly be occurring. Again, I hesitate to think this is a bug with the USCI_B module. There are a lot of working pieces here that could affect this situation such as the clock source, power supply, code structure, etc. I can also try to replicate the issue on my end in the morning. Once I'm at the same stage as you, it will make debugging this much faster.

    Best regards ,
    Caleb Overbay
  • My current optimization settings are: --opt_level=0 --opt_for_speed=1. I'm not quite sure how to change it.

    I can't imagine that there is an issue with the power supply or clock source since I'm using the launchpad as the power supply and the factory calibrated clock values. I've checked the speed and consistency of SMCLK with my logic analyzer via P1.4.

    Hopefully it is my code structure. In the mean time, I will put

    UCB0CTL1 |= UCTXSTT;

     in the TA0_A0 ISR, to prevent the compiler from combining the two instructions.

    Later today once I have more time I will post the full code, including the header files.

  • Hi Stephen,

    In CCS you can change the optimization level by right clicking on your project and selecting "Properties". Then follow Build --> MSP430 Compiler --> Optimization and you will see the optimization settings.

    From here you can use the drop down as seen below to turn off optimization.

    I think you've mapped out a good path forward with this. Keep me updated on the progress.

    Best regards, 

    Caleb Overbay

  • I've updated my optimization level, it's now off. I've also implemented TA0CCR0 interrupt in place of the __delay_cycles() functions.

    Now I'm having a totally different issue, the CPUOFF bit is still set, even when the ISR resets the SR from LPM1. Here's where it gets stuck:

    Here's a copy of the main.c:

    #include <msp430.h>
    #include "MCP23018.h"
    #include "12_key_board.h"
    /*
    Connection diagram:
    
    MSP430|  Vcc   Vcc     |MCP23018
          |   |      |     |
          | 4.7k   4.7k    |
          |   |      |     |
       SDA|---|------+-----|SDA
       SCL|---+------------|SCL
      P2.2|----------------|INTB
    
       */
    /* program flow:
     * first, watchdog timer is held, clocks are configured, Pins initialized and USCI_B (I2C Mode) initialized
     * immediately after initialization, a START condition on the I2C bus is sent (UCB0CTL1 |= UCTXSTT;)
     * the I2C device initialization loops will run their course, before the main loop can run
     * MCP23018 initialization loop: load a byte into the buffer, repeat until 23 bytes have been sent, if a NACK is recieved, initialization starts over.
     * The 23 byte sequence determine the MCP23018's initial state.
     * After initialization the device enters LPM3 and when INTB interrupts, it triggers a write to the MCP's register counter, it writes '17', register INTCAPB
     * After successfully sending the control byte, the MSP becomes a receiver and begins a 1-byte read from the MCP.
     * If the recieved byte matches the byte in the list, the security pin iterator is incremented, after 4 increments, the iterator overflows into the next largest bit, 
     *	SUC. At this point the system is unlocked and the alarm (later to be connected to GPIOA.2) is turned off.
     */
    
    #define ACLK_FREQ       14800                           // VLO on this particular chip ranges from 14.8KHz to 10KHz,
    //#define ACLK_FREQ        32768                        // Later to be sourced from a 12.5pf crystal on XIN and XOUT
    #define SMCLK_FREQ      16000000                        // 16MHz SMCLK
    #define I2C_CLOCK_FREQ  100000                          // 100KHz SCL
    #define I2C_CLOCK_DIV   SMCLK_FREQ/I2C_CLOCK_FREQ       // scale the clock down by (16MHz SMCLK)/(100KHz SCL)
    #define Tbuf            75                              // The proper bus free time period (in CPU cycles) as specified by I2C spec 1.1 (4.7us)
    
    #define KEYPAD          15                              // Keypad pins are on lower 4 bits of INTCAPB
    #define I2C_ON          BIT0                            // If set, the MSP430 cannot shut off the CPU, as the I2C relies on polling
    #define KEYPAD_READ     BIT1                            // If set, the device is in the process of reading from the MCP23018's INTCAPB register
    #define I2C_REG_SET     BIT2                            // If set, the MSP430 is only transmitting the slave's register iterator for a later read
    #define MCP23018_INIT   BIT4                            // If set, MCP23018 has already been initialized to the correct state
    #define SEC0            BIT5                            // This is the first bit of the security pin iterator
    #define SEC1            BIT6                            // This is the second bit of the security pin iterator
    #define SEC             BIT5|BIT6                       // This is the security pin iterator
    #define SUC             BIT7                            // If set, the system is unlocked and disarmed
    
    unsigned int status;                                    // status word
    unsigned char i2c_i;                                    // i2c slave register counter
    unsigned char NACKd;                                    // A count of how many times the master was NACK'd
    unsigned char tick;                                     // tick
    const unsigned char PIN[4] = {THREE, NINE, FIVE, TWO};  // a 4-digit pin for the security code
    
    int main(void)
    {
      // Watchdog Configuration:
      WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
    
      // Clock system configuration code:
      DCOCTL = CALDCO_16MHZ;                    // Set DCO register to the precalibrated 16MHz speed
      BCSCTL1 = CALBC1_16MHZ;                   // Set RESL bits
      BCSCTL3 = LFXT1S_2;                       // Set ACLK to VLOCLK, when crystal is soldered on, update to the code below
    
      // Initialize variables:
      status = 0;                               // All status bits reset
      i2c_i = 0;                                // Slave register iterator starts at 0
    
      // Pin configuration code:
      P1OUT = BIT3;                             // Pin 1.3 is pulled up (SW2)
      P1DIR = BIT0 | BIT4;                      // Pin 1.0 and 1.4 are outputs (ACLK and SMCLK)
      P1REN = BIT3;                             // P1.3 pull up resistor enabled (SW2)
      P1IE = BIT3;                              // P1.3 interrupt enabled (SW2)
      P1IES = BIT3;                             // P1.3 High to low triggers interrupt (When SW2 is pressed)
      P1IFG = 0;                                // Initialize P1 flags to 0;
      P1SEL = BIT0 | BIT4 | BIT6 | BIT7;        // P1.0 outputs ACLK P1.4 outputs SMCLK P1.6 and P1.7 are routed to USCI_B0
      P1SEL2 = BIT6 | BIT7;                     // P1.6 and P1.7 are routed to USCI_B0
      // Initialize Port 2
      P2DIR = BIT0 | BIT1;                      // P2.0-1 are TA1 outputs, P2.2 is INTB, from the MCP23018 all other pins are inputs
      P2REN = 255 & ~(BIT0|BIT1);               // All pins except for P2.1 pullup resistor enabled
      P2OUT = 255 & ~(BIT0|BIT1);               // All pins except for P2.1 are high
      P2SEL = BIT0|BIT1;                        // P2.1 is selected for TA1 module output
      P2IE = BIT2;                              // Enable interrupts on P2.2 (INTB from MCP23018)
      P2IES = BIT2;                             // A high to low transition interrupt on P2.2 (normally high)
      P2IFG = 0;                                // Set interrupt flags to 0 just in case
      /* The code below is to save power on 20-pin packages */
      P3DIR = 0;                                // All Port 3 pins initialized to be inputs
      P3REN = 255;                              // All Port 3 pull up/down resistors enabled
      P3OUT = 255;                              // All Port 3 pins are pulled up
    
      // TimerA0: Interval timer for I2C timing
      TA0CTL = TASSEL_2 | MC_0 | TACLR;         // SMCLK source, stopped, clear TAR
      TA0CCR0 = Tbuf-13;                        // Tbuf - 13 cycles, to account for ISR overhead and ISR contents
      TA0CCTL0 = 0;                             // Disable interrupts, clear interrupt flag
      // Initialize TimerA1, P2.0 & P2.1 are already allocated for it
      TA1CTL = TASSEL_1 | MC_1 | TACLR;        // ACLK clock sourced, up mode clear TAR
      TA1CCR0 = ACLK_FREQ;                     // Set to 1s intervals, (ACLK * 4000)
      //TA1CCTL0 = CCIE | OUTMOD_0;            // Enable interrupt and set TA1CCR0 to output to P2.0 (if selected)
      TA1CCR1 = ACLK_FREQ>>1;                  // Set to 0.5s intervals, the speed of ACLKx2000
      TA1CCTL1 = OUTMOD_3;                     // Set TA1CCR1 to set/reset P2.1
    
      // Initialize USCI_B0 to Philips 1.1 spec I2C master mode. Pins are already assigned to SCL and SDA
      UCB0CTL0 = UCMST | UCMODE_3 | UCSYNC;     // 7-bit addressing, single master
      UCB0CTL1 |= UCSSEL_2 | UCTR | UCSWRST;    // Transmitter mode, sourced from SMCLK
      UCB0BR1 = 0;                              // Set the upper baud rate register to 0
      UCB0BR0 = I2C_CLOCK_DIV;                  // 100KHz bus frequency, (16MHz/160)
      UCB0STAT = 0;                             // Reset state change flags
      IFG2 = 0;                                 // Reset all transmit and recieve flags, including USCI_A
      UCB0I2CSA = MCP23018_OPCODE;              // Initialize slave address to MCP23018's base address
      UCB0I2CIE = 0;                            // Disable all I2C related interrupts (See issue USCI29 in SLAZ440H)
      IE2 = 0;                                  // Disable USCI_A and USCI_B TX and RX interrupts (See issue USCI29 in SLAZ440H)
      UCB0CTL1 &= ~UCSWRST;                     // Reset UCSWRST, starting I2C communication
      UCB0CTL1 |= UCTXSTT;                      // Start condition
    
      __bis_SR_register(GIE);                   // Enable global interrupts
    
      while (~status & MCP23018_INIT) {         // While the MCP23018 is NOT initialized
        if (UCB0STAT & UCNACKIFG) {             // If the MCP23018 did NOT acknowledge, the entire initialization must be restarted
          UCB0CTL1 |= UCTXSTP;                  // Send STOP condition
          i2c_i = 0;                            // Reset slave register counter
          NACKd++;                              // Increment Not Ackowledged counter
          while (UCB0CTL1 & UCTXSTP);           // Wait for the STOP condition to finish
          if (NACKd < 30) {                     // If it has not tried 30 times
            TA0CTL = MC_1;                      // Start TimerA0
            TA0CCTL0 |= CCIE;                   // Enable TA0CCR0 interrupt
            __bis_SR_register(LPM1 + GIE);      // Enter LPM1 with GIE bit set (ISR sends START condition)
          } else {								// After 30 failed attempts, acknowledgement is hopeless
              __bis_SR_register(LPM4_bits);     // Enter a deep sleep
          }
          UCB0STAT &= ~UCNACKIFG;               // Clear NACK flag
          IFG2 &= ~UCB0TXIFG;                   // Reset TX IFG (See USCI25 for details)
        } else if (IFG2 & UCB0TXIFG) {          // If the TXBUF needs to be loaded or a STOP condition sent (provided there is no NACK because of the code above)
          IFG2 = 0;                             // Clear all USCI flags
          UCB0TXBUF = MCP23018_INIT_DATA[i2c_i];// Load the buffer with the appropriate byte, starting at 0
          i2c_i++;                              // Increment the iterator
          if (i2c_i > OLATB+1) {                // If the slave's register iterator has rolled over (more than (1 control)+(21 data) bytes sent)
            UCB0CTL1 |= UCTXSTP;                // Send STOP condition
            while (UCB0CTL1 & UCTXSTP);         // Wait for the STOP condition to finish
            i2c_i = 0;                          // Roll over the master's register iterator
            status |= MCP23018_INIT;            // All bytes written successfully, MCP23018 now initialized to correct state
          }
        } else if (UCB0STAT & UCALIFG) {        // If arbitration is somehow lost (only master on the bus)
            TA1CCTL0 = OUTMOD_4;         		// Set TimerA1 to toggle P2.0 at ~0.5hz, to indicate a fatal error
            __bis_SR_register(LPM4_bits);       // Enter a deep sleep without interrupts
        }
      }
      while (1)
      { // This I2C implementation uses polling (See issue USCI29 in SLAZ440H)
        // No repeated start conditions are used (See issue USCI35 in SLAZ440H)
        // TXIFG is manually reset when servicing a NACK (See issue USCI25 for details)
        if (UCB0STAT & UCNACKIFG) {             // If a NACK was recieved
          IFG2 &= ~UCB0TXIFG;                   // Reset TX flag (See USCI25)
          i2c_i = 0;                            // Reset address iterator
          UCB0CTL1 |= UCTXSTP;                  // Send STOP condition
          while(UCB0CTL1 & UCTXSTP);            // Wait for the STOP condition to finish
          TA0CTL |= MC_1;                       // Start TimerA0
          TA0CCTL0 |= CCIE;                     // Enable TA0CCR0 interrupt (sends START condition)
          __bis_SR_register(LPM1 + GIE);        // Enter LPM1 with GIE bit set until TA0A0 interrupts
          NACKd++;                              // Increment NACKd
          UCB0STAT &= ~UCNACKIFG;               // Reset NACK flag
        } else if (IFG2 & UCB0TXIFG) {          // If it is time to load the TXBUF or send a STOP condition
          if (status & I2C_REG_SET) {           // If setting the register counter for a later read operation
            if (status & KEYPAD_READ) {         // If reading the state of the keypad
                                                // MCP23018's address will already be loaded
              if (UCB0TXBUF == INTCAPB) {       // If the last byte sent was INTCAPB
                // Control byte was successfully sent
                // Now the STOP condition can be sent, wait 4.7us, then send START condition as a reciever
                status &= ~I2C_REG_SET;         // Reset I2C_REG_SET bit, the counter was successfully sent
                UCB0CTL1 |= UCTXSTP;            // Send STOP condition
                while(UCB0CTL1 & UCTXSTP);      // Ensure STOP condition is finished before proceeding
                TA0CTL |= MC_1;                 // Start TimerA0
                TA0CCTL0 |= CCIE;               // Enable TA0CCR0 interrupt
                UCB0CTL1 &= ~UCTR;              // Become reciever
                __bis_SR_register(LPM1 + GIE);  // Enter LPM1 with GIE bit set (ISR sends START condition)
              } else {                          // Otherwise:
                UCB0TXBUF = INTCAPB;            // Send INTCAPB as the register number byte
                i2c_i = INTCAPB;                // Update the master's copy of the register iterator
              }
            }
          }
          IFG2 &= ~(UCB0TXIFG);                 // Clear TX flag
        } else if(IFG2 & UCB0RXIFG) {           // If recieved a byte
          if ((status & KEYPAD_READ) && (UCB0I2CSA == MCP23018_OPCODE)) {// If reading the state of the keypad and the slave address is the MCP23018
            UCB0CTL1 |= UCTXSTP;                // Send STOP condition
            // Later versions evaluate upper 4 bits of UCB0RXBUF here
            UCB0RXBUF &= KEYPAD;                // Erase the lower bytes of UCB0RXBUF
            if (UCB0RXBUF & PIN[(status & SEC)>>5]) { // If RXBUF matches the current security pin
              status += SEC0;                   // Increment status word by SEC0
              if (status & SUC) {               // If unlocked
                status &= ~SEC;                 // Reset security pin iterator
                  // Later versions will turn off alarm here
              }
            } else if (status & SUC) {         	// If a key is pressed while the system is unlocked
              if (UCB0RXBUF == STAR) {          // If the asterisk is pressed
                  // Later versions allow for password change
              }
            }
          status &= ~(KEYPAD_READ|I2C_ON|I2C_REG_SET); // Reset KEYPAD_READ, I2C_ON and I2C_REG_SET
          IFG2 &= ~UCB0RXIFG;                   // Clear RX flag
          }
        } else if (UCB0STAT & UCALIFG) {        // If arbitration lost (this is not okay, sice the MSP430 is the only master)
          TA1CCTL0 = CCIE | OUTMOD_4;           // Set TimerA1 to toggle P2.0 at ~0.5hz to inndicate a fatal error
          __bis_SR_register(LPM4_bits);         // Enter a deep sleep without interrupts
        } else if ((status & KEYPAD_READ) && (~status & I2C_ON)) {// If it's time to read the keypad, and no other I2C operation is taking place
          UCB0I2CSA = MCP23018_OPCODE;          // Set the slave address to the MCP23018
          UCB0CTL1 |= UCTR;                     // Set UCTR bit
          TA0CTL |= MC_1;                       // Start TimerA0
          TA0CCTL0 |= CCIE;                     // Enable TA0CCR0 interrupt
          __bis_SR_register(LPM1 + GIE);        // Enter LPM1 with GIE bit set (ISR sends START condition)
          status |= I2C_ON|I2C_REG_SET;         // Set I2C_ON and I2C_REG_SET
        } else if (~status & I2C_ON) {          // If the device is NOT communicating through I2C
          // turn off the USCI_B and CPU, to save power
          __bis_SR_register(LPM3);              // Enter LPM3
        }
      } // end main loop
    } // end main()
    
    #pragma vector= TRAPINT_VECTOR
    __interrupt void TRAPINT_ISR(void)
    {
    __no_operation();
    }
    
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void TIMER0_A0_ISR (void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) Timer_A (void)
    #else
    #error Compiler not supported!
    #endif
    {
      TA0CCR0 = 0;                              // Reset interrupt flag, disable interrupt
      TA0CTL |= MC_0 + TACLR;                   // Stop timer and reset TAR
      UCB0CTL1 |= UCTXSTT;                      // Send START condition
      __bic_SR_register_on_exit(LPM1_bits);     // Exit LPM1, continuing the program
    }
    
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=TIMER1_A0_VECTOR
    __interrupt void TIMER1_A0_ISR (void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(TIMER1_A0_VECTOR))) Timer_A (void)
    #else
    #error Compiler not supported!
    #endif
    {
      tick++;                                   // Increment tick
    }											// Back to sleep
    
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(PORT1_VECTOR))) Port_1 (void)
    #else
    #error Compiler not supported!
    #endif
    {
       P1OUT ^= BIT0;                           // Toggle P1.0
       P1IFG = 0;                               // Reset P1 IFG
    }                                           // Go back to sleep
    
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=PORT2_VECTOR
    __interrupt void Port_2(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(PORT2_VECTOR))) Port_2 (void)
    #else
    #error Compiler not supported!
    #endif
    {
        // Only P2.2 (INTB) interrupt is enabled, so there is no need to check which flag was set
        status |= KEYPAD_READ;                  // Tell the main loop it needs to read the keypad
        P2IFG = 0;                              // Reset Port 2 interrupt flags
        __bic_SR_register_on_exit(LPM3_bits);   // Wake up and return to the program
    }
    

    MCP23018.h:

    /*
    * MCP23018_driver.h
    *
    * Created on: Feb 22, 2017
    * Author: Stephen
    */
    
    #ifndef MCP23018_H_
    #define MCP23018_H_
    #define IODIRA 0 // 1 for input, 0 for output
    #define IODIRB 1 // 1 for input, 0 for output
    #define IPOLA 2 // input polarity, if set, GPIO.x will reflect the opposite logic state of input pin
    #define IPOLB 3 // input polarity, if set, GPIO.x will reflect the opposite logic state of input pin
    #define GPINTENA 4 // interrupt on change enable, if set GPIO.x will trigger a corresponding interrupt when it's state changes
    #define GPINTENB 5 // interrupt on change enable, if set GPIO.x will trigger a corresponding interrupt when it's state changes
    #define DEFVALA 6 // if the corresponding value is opposite from the pin
    // level, an interrupt will be triggered, depending on GPINTENA and INTCONA
    #define DEFVALB 7 // if the corresponding value is opposite from the pin
    // level, an interrupt will be triggered, depending on GPINTENB and INTCONB
    #define INTCONA 8 // interrupt-on-change control register, 0 = pin value compared against previous value, 1 = compared against DEFVALA
    #define INTCONB 9 // interrupt-on-change control register, 0 = pin value compared against previous value, 1 = compared against DEFVALB
    #define IOCONA 10// IOCON configures how registers are arranged, seq mode
    #define IOCONB 11// IOCON configures how registers are arranged, seq mode
    #define GPPUA 12// GPio Pull-Up, enables and disables pull-up resistors
    #define GPPUB 13// GPio Pull-Up, enables and disables pull-up resistors
    #define INTFA 14// INTF interrupt flags, if set indicates that corresponding pin in port A that caused an interrupt
    #define INTFB 15// INTF interrupt flags, if set indicates that corresponding pin in port B that caused an interrupt
    #define INTCAPA 16// Interrupt Captured value, captures state of the port A pins at the time of interrupt
    #define INTCAPB 17// Interrupt Captured value, captures state of the port B pins at the time of interrupt
    #define GPIOA 18// reflects the logic states of the corresponding pins in port A
    #define GPIOB 19// reflects the logic states of the corresponding pins in port B
    #define OLATA 20// output latches, when set, port A pins configured for output will be set
    #define OLATB 21// output latches, when set, port B pins configured for output will be set
    
    #define MCP23018_OPCODE 64>>1// (right shifted because USCI_B does not include the R/~W bit in the address)
    
    const char MCP23018_INIT_DATA[23] = {0, // first byte 0, this is the register address byte sent over I2C
    0, // IODIRA: pins on Port A are set to outputs
    255, // IODIRB: pins on Port B are set to inputs
    0, // IPOLA: if Port A interrupts, the state will reflect the actual state
    0, // IPOLB: if Port B interrupts, the state will reflect the actual state
    0, // GPINTENA: interrupts disabled for Port A
    255, // GPINTENB: interrupts enabled for Port B
    0, // DEFVALA: interrupt compare value 0, Port A interrupts disabled anyway
    0, // DEFVALB: interrupt compare value 0, a bit changes from 0 an interrupt occurs
    0, // INTCONA: interrupt control value is 0. Port A interrupts disabled anyway
    31, // INTCONB: lower 5 bits are set to be comapred against DEFVALB
    1, // IOCON: Set INTCC to 1, reading INTCAP will clear associated interrupts
    1, // IOCON: Set INTCC to 1, reading INTCAP will clear associated interrupts
    0, // GPPUA: internal pullup resistors are disabled for Port A
    255, // GPPUB: internal pullup resistors are enabled for Port B
    0, // INTFA: set to 0, Port A interrupts are disabled anyway
    0, // INTFB: Port B interrupt flags cleared for initialization
    0, // INTCAPA: is 0, as interrupts are disabled for Port A
    0, // INTCAPB: is read-only, so this byte has no effect
    0, // GPIOA: is set to all low, lighting up the LEDs to indicate successful initialization
    0, // GPIOB: is set to all low, these pins are inputs
    0, // OLATA: is set to all low, lighting up the LEDs to indicate successful initialization
    0}; // OLATB: is set to all low, these pins are inputs
    
    
    #endif /* MCP23018_H_ */

    last and certainly least, 12_key_board.h: 

    #define NONE        0
    #define ZERO        1
    #define ONE         2
    #define TWO         3
    #define THREE       4
    #define FOUR        5
    #define FIVE        6
    #define SIX         7
    #define SEVEN       8
    #define EIGHT       9
    #define NINE        10
    #define STAR        11
    #define POUND       12
    
    // a reading of 0 means no buttons are currently being pressed
    // any other reading means a button has been pressed. Only one button
    // may be pressed at a time.
    
    #define ASCII_DECIMAL   48                  // when a decimal is pressed, send 48 + decimal value to address 125 for the logic analyzer to see
    #define ASCII_STAR      42
    #define ASCII_POUND     35
    

  • After removing

    UCB0CTL1 |= UCTXSTT;

     from the TA0_A0 ISR, I've found that the program gets stuck on the

    UCB0CTL1 |= UCTXSTT

    , not the 

    status |= I2C_ON|I2C_REG_SET;

    I still have no idea why the CPUOFF bit is set when it shouldn't be.

  • Hi Caleb, thank you for your help with adjusting the optimization, I went back to my __delay_cycles(Tbuf); approach and turning off optimization fixed it.

    I'll have to make another project later on to tinker with the TA0_A0 ISR issue.

    Now I just have to figure out how to send the NACK and the STOP bit before the MCP has time to finish sending the INTCAPB contents. It should be fairly easy.

  • Hi Stephen,

    I took the code you posted loaded it on an MSP430G2553 and then used another MSP430G2553 to emulate the MCP23018. I can the the MCP2018 initialization work appropriately then I pull P2.2 of the master low to make it read from the slave. When this happens, I see the address sent, the INTCAPB code sent, then a stop condition. After this it seems like your state machine is out of sync and a repeated start is not generated. The code is looping through the main while loop and status = 0x13 (MCP23018_INIT + KEYPAD_READ + I2C_ON).

    I see that inside your TA0 ISR you set TA0CCR0 = 0 but never reset it elsewhere in your code. This seems to be causing some of your issues. Take a look into this and let me know where it takes you.

    Best regards,
    Caleb Overbay
  • Caleb Overbay said:
    I see that inside your TA0 ISR you set TA0CCR0 = 0 but never reset it elsewhere in your code. This seems to be causing some of your issues. Take a look into this and let me know where it takes you.

    Now how in the world did I mess that up? I meant to set TA0CCTL0 to 0, not the CCR0 register. Thanks again, Caleb.

  • Hi Stephen,

    Glad to hear things are working again. I see you're using a polling approach to workaround some of the errata for the USCI module but you might be interested in the code below. This is an interrupt based approach that properly handles all the errata and will most likely be easier to debug than the polling approach. Just want to show it to you as an FYI.

    #include <msp430.h>
    #include <stdint.h>
    
    /* Slave Address */
    #define Slave_Addr 0x28
    
    /* I2C Communication States */
    #define Send_Reg 1
    #define RX_Data 2
    #define TX_Data 3
    #define Idle 0
    
    /* I2C Write and Read Functions */
    int8_t I2C_Write_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt);
    int8_t I2C_Read_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt);
    
    /* Global Variables */
    uint8_t I2C_Reg_Index = 0;
    uint8_t *I2C_Reg_Data;
    uint8_t TXByteCtr = 0;
    uint8_t RXByteCtr = 0;
    uint8_t RX = 0;
    uint8_t Comm_State = Idle;
    uint8_t Write_buffer[10];
    uint8_t Read_buffer[10];
    
    
    int main(void){
    
        WDTCTL = WDTPW | WDTHOLD;   // Stop watch dog timer
    
        /* Initialize clock system*/
        BCSCTL1=CALBC1_8MHZ;
        DCOCTL=CALDCO_8MHZ;
        BCSCTL2|=DIVS_2;            //SMCLK divider = 4;
    
        /* Initialize I2C pins */
        P1SEL|=BIT6+BIT7;
        P1SEL2|=BIT6+BIT7;          // P1.6=SCL (I2C) P1.7=SDA (I2C)
    
        /* Initialize USCI_B for I2C Communication */
        UCB0CTL1 |= UCSWRST;                      // Enable SW reset
        UCB0CTL1 |=UCSSEL_3 + UCTR;               // select SMCLK, transmitter
        UCB0CTL0 |=UCMODE_3 + UCMST + UCSYNC;     // set I2C MODE MASTER MODE
        UCB0BR0=20;                               // clk divider from SMCLK, 100kHz
        UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
    
        __delay_cycles(7000000);                  // Wait for slave to setup
    
        /* Example of writing to slave */
        Write_buffer[0] = 0x80;
        Write_buffer[1] = 0x0C;
        I2C_Write_Reg(Slave_Addr, 0x3F, &Write_buffer[0], 1);   // Write 1 byte to slave register 0x3F
        I2C_Write_Reg(Slave_Addr, 0x3D, &Write_buffer[1], 1);   // Write 1 byte to slave register 0x3D
    
    
        /* Example of reading from slave */
        I2C_Read_Reg(Slave_Addr, 0x20, Read_buffer, 1);         // Read 1 byte from register 0x20
        I2C_Read_Reg(Slave_Addr, 0x20, Read_buffer, 8);         // Read 8 bytes from register 0x20
    
        __bis_SR_register(CPUOFF + GIE);                        // Enter LPM0 w/ interrupts
    }
    
    
    
    /* Input:
    
        dev_addr - slave I2C address
    
        reg_addr - address of the first register to be read
    
        *reg_data - pointer to the location where received data will be stored
    
        *cnt - number of bytes to be read
    
    */
    int8_t I2C_Read_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt)
    {
    
        int8_t status = 0;    //0 = no error
    
        /* Initialize slave address */
        UCB0I2CSA = dev_addr;
    
        /* Initialize state machine */
        Comm_State = Send_Reg;
        RX = 1;                                 // Signal this is a read operation
        I2C_Reg_Index = reg_addr;
        I2C_Reg_Data = reg_data;
        RXByteCtr = cnt;
    
        /* Start I2C communication */
        __disable_interrupt();
        while (UCB0CTL1 & UCTXSTP);             // Ensure stop condition got sent
        IE2 &= ~UCB0RXIE;                       // Disable RX interrupt
        IE2 |= UCB0TXIE;                        // Enable TX interrupt
        UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
        __bis_SR_register(CPUOFF + GIE);        // Enter LPM0 w/ interrupts
    
        return status;
    
    }
    
    /* Input:
    
        dev_addr - slave I2C address
    
        reg_addr - address of the register to write
    
        *reg_data - pointer to the location with data to write
    
        *cnt - number of bytes to write
    
    */
    int8_t I2C_Write_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt)
    {
    
        int8_t status=0;
    
        /* Initialize slave address */
        UCB0I2CSA = dev_addr;
    
        /* Initialize state machine */
        Comm_State = Send_Reg;
        RX = 0;                                 // Signal this is a write operation
        I2C_Reg_Index = reg_addr;
        I2C_Reg_Data = reg_data;
        TXByteCtr = cnt;
    
        /* Start I2C communication */
        __disable_interrupt();
        while (UCB0CTL1 & UCTXSTP);             // Ensure stop condition got sent
        IFG2 &= ~(UCB0TXIFG + UCB0RXIFG);
        IE2 &= ~UCB0RXIE;                       // Disable RX interrupt
        IE2 |= UCB0TXIE;                        // Enable TX interrupt
        UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
        __bis_SR_register(CPUOFF + GIE);        // Enter LPM0 w/ interrupts
    
        return status;
    }
    
    /* Start / Stop / NACK ISR */
    #pragma vector = USCIAB0RX_VECTOR
    __interrupt void USCIAB0RX_ISR(void)
    {
        if ((UCB0STAT & UCNACKIFG))
        {//Nack received
    
            /* Handle NACK based on state of communication */
            switch(Comm_State)
            {
                case Send_Reg:
                    break;
    
                case TX_Data:
                    IFG2 &= ~UCB0TXIFG;   // Clear TXIFG USCI25 Workaround
                    break;
    
                case RX_Data:
                    break;
    
                default:
                    __no_operation();
                    break;
            }
        }
    }
    
    /* RX and TX ISR */
    #pragma vector = USCIAB0TX_VECTOR
    __interrupt void USCIAB0TX_ISR(void)
    {
        static uint8_t temp;
        if(IFG2 & UCB0RXIFG)
        { //RX Interrupt
    
            //must read RXBUF first for USCI30 Workaround
            temp = UCB0RXBUF;
    
            if(RXByteCtr)
            { //Receive byte
                *I2C_Reg_Data++ = temp;
                RXByteCtr--;
            }
    
            if(RXByteCtr == 1)
            { // Done receiving
                UCB0CTL1 |= UCTXSTP;
            }
            else if(RXByteCtr == 0)
            {
                IE2 &= ~UCB0RXIE;
                Comm_State = Idle;
                __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
            }
        }
        else if (IFG2 & UCB0TXIFG)
        { //TX Interrupt
    
            /* Handle TXIFG based on state of communication */
            switch(Comm_State)
            {
                case Send_Reg: // Transmit register index to slave
                    UCB0TXBUF = I2C_Reg_Index;    // Send data byte
                    if(RX)
                        Comm_State = RX_Data;     // Next state is to receive data
                    else
                        Comm_State = TX_Data;     // Next state is to transmit data
                    break;
    
                case RX_Data:  // Switch to receiver
                    IE2 &= ~UCB0TXIE;             // Disable TX interrupt
                    IE2 |= UCB0RXIE;              // Enable RX interrupt
                    UCB0CTL1 &= ~UCTR;            // Switch to receiver
                    if(RXByteCtr == 1)
                    {
                        UCB0CTL1 |= UCTXSTT;                    // I2C start condition
                        while (UCB0CTL1 & UCTXSTT);             // Start condition sent?
                        UCB0CTL1 |= UCTXSTP;                    // Send stop condition
                    }
                    else
                    {
                        UCB0CTL1 |= UCTXSTT;                // Send repeated start
                    }
                    break;
    
                case TX_Data: // Transmit byte to slave
                    if(TXByteCtr)
                    { // Send byte
                        UCB0TXBUF = *I2C_Reg_Data++;
                        TXByteCtr--;
                    }
                    else
                    { // Done transmitting data
                        UCB0CTL1 |= UCTXSTP;     // Send stop condition
                        Comm_State = Idle;
                        IE2 &= ~UCB0TXIE;                       // disable TX interrupt
                        __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
                    }
                    break;
    
                default:
                    __no_operation();
                    break;
            }
        }
    }
    
    /* Trap ISR for USCI29 Workaround */
    #pragma vector= TRAPINT_VECTOR
    __interrupt void TRAPINT_ISR(void)
    {
        __no_operation();   // Add breakpoint
    }
    
    

    Best regards,

    Caleb Overbay

  • Thanks for showing this to me, when I was first reading and testing out the TI I2C examples I had planned something similar.

    I decided against it because: from the errata it seems that if the failure condition described in USCI29 occured, a call to the TRAPINT_ISR would take place instead of the intended USCIAB_RX or USCIAB_TX. Because the application would rely on the instructions in the USCI ISRs to proceed, it would stall not only the application, but the other devices on the I2C bus.
  • Hi Stephen,

    I understand your concern but to my knowledge, when the code exits the trap ISR, execution return to normal application code. This means the interrupt request (TX or RX) will then be serviced once exiting the trap ISR.

    Best regards,
    Caleb Overbay

**Attention** This is a public forum