Quantcast
Channel: MSP low-power microcontroller forum - Recent Threads
Viewing all articles
Browse latest Browse all 22176

MSP430FR5994: Nothing but issues with I2C

$
0
0

Part Number:MSP430FR5994

Come, sit down! Let me weave for you a tale of the most frustrating experience I've ever had with getting I2C to work...This is going to be a long one, but hopefully it helps some other people that are struggling with this board, and hopefully I can get some feedback that clears up the issues I'm still having.

Board: MSP430FR5994 Launchpad

IDE: CCSv8

Compiler: TI v18.1.5.LTS

I'm working with the FR5994 Launchpad, an Adafruit BMP280 breakout, and an Adafruit VL53L0X breakout to develop code for a larger device. I've worked with MSP430s and both sensors before, so I thought it would be a fairly quick process to get I2C up and running with them...not so much. I have spent the last three weeks searching forums, scouring the User Guide and example code, and I am still having trouble. Three weeks. So please, believe me when I say that I've tried damn near everything.

First things first: I'm not using the driverlib. Can't. There's too much going on this chip to clutter it with that. 

Second: Yes, there are pullups (10k Ohm) on the SDA/SCL lines. They are at 3.3V. 

Third: I've been using a scope since week 2 to debug. I'll be attaching pictures. 

So let's start with what works! After fruitlessly trying to get the interrupt method outlined in the user guide to work, I decided to go back to basics and try a simple polling protocol. This works for the BMP280, but there are some weird (let's call them) idiosyncrasies that keep it from working with the VL53L0X. Sidenote: I was only able to get this working after finding this equally frustrated individual: (https://e2e.ti.com/support/microcontrollers/msp430/f/166/t/702571?MSP430FR5994-I2C-Inaccurate-eUSCI-description-on-User-s-Guide)  

I first initialize the board pins and eUSCI bus:

void bmp280::initialize(){
    // Configure GPIO
    P1OUT &= ~BIT0;             // Clear P1.0 output latch
    P1DIR |= BIT0;              // For LED

    // Configure interface ports
    P7SEL0 |=  (BIT0 | BIT1);   // Connect ports P7.0 and P7.1 to
    P7SEL1 &= ~(BIT0 | BIT1);   // eUSCI-B1, SDA and SCL, respectively
    P7DIR  &= ~(BIT0 | BIT1);   // Configure as if they are inputs

    PM5CTL0 &= ~LOCKLPM5;       // Disable GPIO power-on high-impedance mode

    UCB2CTLW0 = UCSWRST;        // Reset I2C interface for configuration

    UCB2CTLW0 =                 /* USCI - B2 configuration register */
                UCMST         | // Master mode
                UCSYNC        | // Synchronous mode
                UCMODE_3      | // I2C mode
                UCSSEL__SMCLK | // Select SMCLK
                UCTR          | // Transmitter mode
                UCSWRST       | // Don't release reset (yet)
                0;

    UCB2CTLW1 =
                UCCLTO_0      | // Clock low time-out select (disabled)
                UCASTP_0      | // Automatic STOP condition generation (disabled)
                UCGLIT_0      | // Deglitch time (50ns)
                0;

    UCB2BRW = 10;               // Bit clock divider 1M/10 = 100kHz

    UCB2CTLW0 &= ~UCSWRST;      // Clear reset bit to start operation

    __delay_cycles(50);
}

Then I read/write to the device, manually polling for the RX/TX flags, and setting the start/stop bits. Here's a read example:

void bmp280::readID(){
    UCB2I2CSA = BMP280_ADDRESS;             // Set slave address
    UCB2CTLW0 |= UCTR | UCTXSTT;            // Start request as transmitter

    while(!(UCB2IFG & UCTXIFG));            // Wait until TXBUFF is empty
    UCB2TXBUF = BMP280_REGISTER_CHIPID;     // Send first data byte

    while(UCB1CTLW0 & UCTXSTT);             // Wait for slave's response

    while(!(UCB2IFG & UCTXIFG));            // Wait for the last byte to be loaded to shift register
    UCB2CTLW0 |= UCTXSTP;                   // Request a stop condition
    while (UCB2CTLW0 & UCTXSTP);            // Wait until stop was sent

    UCB2IFG &= ~UCTXIFG;                    // Clear TX flag

    UCB2CTLW0 &= ~UCTR;                     // Put I2C bus in receive mode

    UCB2CTLW0 |= UCTXSTT;                   // Start request as a receiver

    rxCount = 1;                            // Set number of bytes to receive

    while(rxCount){                         // Loop until data is collected
        UCB2CTL1 |= UCTXSTP;               // Generate I2C stop condition (with a nack before that, which is handled by hardware automatically)
        while(!(UCB2IFG & UCRXIFG));        // Wait until RXBUF is full
        rxBuffer = UCB2RXBUF;               // Pull data from buffer
        rxCount--;
    }

    chipID = rxBuffer;                      // Store data
}

Here's a write example: 

void bmp280::configure(){
txCount = 2; // Set number of bytes to send

UCB2I2CSA = BMP280_ADDRESS; // Set slave address
UCB2CTLW0 |= UCTR | UCTXSTT; // Start request as transmitter

while(!(UCB2IFG & UCTXIFG)); // Wait until TXBUFF is empty
UCB2TXBUF = BMP280_REGISTER_CONTROL; // Send first data byte

while(UCB1CTLW0 & UCTXSTT); // Wait for slave's response

if (UCB2IFG & UCNACKIFG) { // If there was no response
UCB2IFG &= ~UCNACKIFG; // Clear NACK flag
UCB2CTLW0 |= UCTXSTP; // Request a stop
while (UCB2CTLW0 & UCTXSTP); // And wait until stop was actually sent
UCB2IFG &= ~UCSTPIFG; // Clear stop flag (not actually using this..)
}
else { // If there was a reply from the slave,
while (--txCount) { // send the remaining bytes
while(!(UCB2IFG & UCTXIFG));
UCB2TXBUF = 0x26; // Temp/Pressure oversampling x1, forced mode
// Must rewrite "forced mode" command for each measurement
}
}

while(!(UCB2IFG & UCTXIFG)); // Wait for the last byte to be loaded to shift register
UCB2CTLW0 |= UCTXSTP; // Request a stop condition
while (UCB2CTLW0 & UCTXSTP); // Wait until stop was sent

UCB2IFG &= ~UCTXIFG; // Clear TX flag
}

These work, but the read has the frustrating result of sometimes (somehow) returning two bytes instead of 1. How this happens, I have no idea, and because it's intermittent I'm now struggling to get a scope shot of it. Additionally, and maybe this has more to do with CCS than the MSP430, when I'm in Debug mode, the program will sometimes get hung at this line: while(!(UCB2IFG & UCTXIFG)); Again, no idea why this happens, because if I power cycle the board and let it run freely, not in debug mode, it works just fine! Another example of someone having this issue can be found here: http://e2e.ti.com/support/microcontrollers/msp430/f/166/p/558315/2046359?tisearch=e2e-sitesearch&keymatch=fr5994#2046359

Regardless, the real issues lie in the interrupt method that TI seems to favor in their User Guide and example code. This is also the method that is required for the project I'm working on. We can't risk the polling method getting hung intermittently, and we don't want I2C data getting dropped because other interrupts override the I2C data transfer. So interrupts are a must.

After learning the lessons I learned with the polling method I show above, I was finally (finally!) able to get the I2C interrupts to work...but it required HEAVY tweaking/debugging...and it's still not working 100% how you would expect. 

I first initialize the board pins and configure the I2C bus to use interrupts. I also have a secondary I2C configuration function for instances when interrupts don't work well:

void bmp280::initialize(){
    // Configure GPIO
    P1OUT &= ~BIT0;             // Clear P1.0 output latch
    P1DIR |= BIT0;              // For LED

    // Configure interface ports
    P7SEL0 |=  (BIT0 | BIT1);   // Connect ports P5.0 and P5.1 to
    P7SEL1 &= ~(BIT0 | BIT1);   // eUSCI-B1, SDA and SCL, respectively
    P7DIR  &= ~(BIT0 | BIT1);   // Configure as if they are inputs

    PM5CTL0 &= ~LOCKLPM5;       // Disable GPIO power-on high-impedance mode

    i2cConfig(0x01);            // Configure i2c bus with stop count of 1 byte

    __delay_cycles(50);
    __bis_SR_register(GIE);     // Enable interrupts
}

void bmp280::i2cConfig(uint8_t stop_count){
    UCB2CTLW0 = UCSWRST;        // Reset I2C interface for configuration

    UCB2CTLW0 =                 /* USCI - B2 configuration register */
                UCMST         | // Master mode
                UCSYNC        | // Synchronous mode
                UCMODE_3      | // I2C mode
                UCSSEL__SMCLK | // Select SMCLK
                UCTR          | // Transmitter mode
                UCSWRST       | // Don't release reset (yet)
                0;

    UCB2CTLW1 =
                UCCLTO_1      | // Clock low time-out select (28ms)
                UCASTP_2      | // Automatic STOP condition generation (enabled)
                UCGLIT_0      | // Deglitch time (50ns)
                0;

    UCB2TBCNT = stop_count;

    UCB2BRW = 10;               // Bit clock divider 1M/10 = 100kHz

    UCB2CTLW0 &= ~UCSWRST;      // Clear reset bit to start operation

    UCB2IE = 0;

    UCB2IE = UCNACKIE         | // Enable Nack interrupt
             UCTXIE           | // Enable TX interrupt
             UCRXIE           | // Enable RX interrupt
             UCCLTOIE         | // Enable clock low time-out interrupt
             0;
}

void bmp280::i2cConfig_noInt(){
    UCB2CTLW0 = UCSWRST;        // Reset I2C interface for configuration

        UCB2CTLW0 =                 /* USCI - B2 configuration register */
                    UCMST         | // Master mode
                    UCSYNC        | // Synchronous mode
                    UCMODE_3      | // I2C mode
                    UCSSEL__SMCLK | // Select SMCLK
                    UCTR          | // Transmitter mode
                    UCSWRST       | // Don't release reset (yet)
                    0;

        UCB2CTLW1 =
                    UCCLTO_1      | // Clock low time-out select (28ms)
                    UCASTP_0      | // Automatic STOP condition generation (enabled)
                    UCGLIT_0      | // Deglitch time (50ns)
                    0;

        UCB2BRW = 10;               // Bit clock divider 1M/10 = 100kHz

        UCB2CTLW0 &= ~UCSWRST;      // Clear reset bit to start operation

        UCB2IE = 0;
}

I found that trying to manually set the stop bit while in the interrupt led to overflow/timing issues. These went away when I used the auto stop...however, the drawback of this is you have to reconfigure the I2C bus every time you want to write/read a different number of bytes. So, for instance, if I want to read in 3 bytes, I would have to set the stop counter to 1, transmit the register to read from, reset the stop counter to 3, then read in the 3 bytes of data. This is clunky, and (not surprisingly) does not work as advertised...more on this later. In general, the auto stop works as long as I'm not changing the stop counter all the time. 

Here are my read/write functions and the interrupt definitions: 

void bmp280::write(uint8_t reg, uint8_t data){
    i2cConfig(0x02);

    mBuffer[0] = reg;
    mBuffer[1] = data;
    transmit(mBuffer,2);
    for(int i=0;i<50;i++);
}

void bmp280::read(uint8_t reg){
    i2cConfig(0x01);
    __delay_cycles(50);

    transmit(&reg,1);
    for(int i=0;i<50;i++);
    receive(mBuffer,1);
    for(int i=0;i<100;i++);
}

void bmp280::read16(uint8_t reg){
    i2cConfig_noInt();

    UCB2I2CSA = BMP280_ADDRESS;             // Set slave address
    UCB2CTLW0 |= UCTR | UCTXSTT;            // Start request as transmitter

    while(!(UCB2IFG & UCTXIFG));            // Wait until TXBUFF is empty
    UCB2TXBUF = reg;                        // Send first data byte

    while(UCB1CTLW0 &  UCTXSTT);            // Wait for slave's response

    while(!(UCB2IFG & UCTXIFG));            // Wait for the last byte to be loaded to shift register
    UCB2CTLW0 |= UCTXSTP;                   // Request a stop condition
    while (UCB2CTLW0 & UCTXSTP);            // Wait until stop was sent

    UCB2IFG &= ~UCTXIFG;                    // Clear TX flag

    i2cConfig(0x02);
    rxData = 0;

    receive(mBuffer,2);
    for(int i=0;i<50;i++);
    
    rxData = (mBuffer[0] << 8) | mBuffer[1];
}

void bmp280::read24(uint8_t reg){
    i2cConfig_noInt();


    UCB2I2CSA = BMP280_ADDRESS;             // Set slave address
    UCB2CTLW0 |= UCTR | UCTXSTT;            // Start request as transmitter

    while(!(UCB2IFG & UCTXIFG));            // Wait until TXBUFF is empty
    UCB2TXBUF = reg;                        // Send first data byte

    while(UCB1CTLW0 &  UCTXSTT);            // Wait for slave's response

    while(!(UCB2IFG & UCTXIFG));            // Wait for the last byte to be loaded to shift register
    UCB2CTLW0 |= UCTXSTP;                   // Request a stop condition
    while (UCB2CTLW0 & UCTXSTP);            // Wait until stop was sent

    UCB2IFG &= ~UCTXIFG;                    // Clear TX flag

    i2cConfig(0x03);
    rxData = 0;

    receive(mBuffer,3);
    for(int i=0;i<50;i++);
    
    uint32_t temp = (mBuffer[0] << 8) | mBuffer[1];
    rxData = (temp << 8) | mBuffer[2];
}

void bmp280::transmit(uint8_t *field, uint8_t count){
    UCB2I2CSA = BMP280_ADDRESS;             // Set slave address
    txCount = count;
    txBuffer = field;
    
    UCB2CTLW0 |= UCTR | UCTXSTT;            // Start request as transmitter
}

void bmp280::receive(uint8_t *field, uint8_t count){
    UCB2I2CSA = BMP280_ADDRESS;             // Set slave address
    rxCount = count;
    rxBuffer = field;
    UCB2CTLW0 &= ~UCTR;                     // Put I2C bus in receive mode

    UCB2CTLW0 |= UCTXSTT;                   // Start request as a receiver
}

#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=USCI_B2_VECTOR
__interrupt
#elif defined(__GNUC__)
__attribute__((interrupt(USCI_B2_VECTOR)))
#endif
void USCIB2_ISR(void)
{
    switch(__even_in_range(UCB2IV, USCI_I2C_UCBIT9IFG))
  {
        case USCI_NONE:             // No interrupts break;
            break;
        case USCI_I2C_UCALIFG:      // Arbitration lost
            break;
        case USCI_I2C_UCNACKIFG:    // NAK received (master only)
            nack_received = true;
            UCB2CTLW0 |= UCTXSTP;                 // when an acknowledge is missing the slave gets a stop condition
            UCB2IFG   &= ~UCTXIFG;
            break;
        case USCI_I2C_UCRXIFG0:             // RXIFG0
            if(rxCount){
                receive_initiated = true;
                *rxBuffer = UCB2RXBUF;      // Get RX data
                rxBuffer++;
                rxCount--;
            }
            break;
        case USCI_I2C_UCTXIFG0:     // TXIFG0
            transmit_initiated = true;
            UCB2TXBUF = *txBuffer++;
            break;
        case USCI_I2C_UCCLTOIFG:    // Clock low timeout - clock held low too long
            clock_timeout = true;
            break;
        default:
            break;
  }
}

I'll start from the top: 

1) Write: I configure the I2C bus to use interrupts and have a stop count of 2 bytes. I load a buffer with the register I'm writing to and the data, then pass that buffer to my transmit function. And yes, the empty for loop is necessary. If I take it out either nothing gets transmitted, or the transmitted data gets cut off halfway through and the clock times out. (Pictured below)

2) Trasmit: Sets the count and passes the start point of the buffer to the interrupt, which then iterates through the buffer, passing the data to UCB2TXBUF. I realize the txCount isn't used in this code (due to the auto stop) but it was handy for debugging...I took it out of this example though. 

This all makes sense right? And it works...like 50% of the time! See below:

With the empty for loop: (And sometimes I still have issues with this...it's playing nice today)

 

Without the empty for loop:

Now for reading data:

3) Read: I configure the I2C bus to use interrupts and have a stop count of 1 byte, so it should transmit 1, and receive 1. I use the same transmit function to send the register to read from, then pass my buffer to my receive function. And again, yes the empty for loops are necessary. If taken out, nothing works properly.

4) Receive: Sets the count (again, not necessary, but useful for debugging) and passes the starting point of the buffer to the interrupt, which pulls the data from UCB2RXBUF.

All nice and good, right? But again, plagued with issues. See below:

Without the for loop between transmit and receive in the read function: (Transmits the register, but does not receive the data afterward)

With the for loop between transmit and receive: (Transmits the register, but the receive doesn't show up on the scope for some reason if I'm in debug mode...no clue why...but the data is still received!!! It reads into my variable with the correct value!!!)

However, if I power cycle the board and let the code run freely (not in debug mode in CCS), I see the transmit, delay, and receive on the scope:

With the correct data: 

What is strange is this same read (I'm reading the chip ID) works perfectly with the polling method, and requires no delay whatsoever! None. Why on earth would interrupts require such a significant delay between transmitting the register to read from, and actually reading from the register? I've demonstrated this to multiple people with more experience using this hardware than me, and they don't understand it either. So TI, if you're listening...please explain.

Now, moving forward to yet another weird issue...the multi reads. This is one of the issues I mentioned above, where resetting the byte counter too often seems to lead to issues. To make this work, I *have to* use the polling method to transmit the register to read from, *then* reconfigure the I2C bus to use interrupts and the byte counter to pull in the data. This defeats the entire purpose of using the interrupts in my opinion...but I haven't been able to find any combination of settings, additional delays, or whatever, that makes it so I can use purely interrupts for a multi read...

5) Read16: I disable interrupts and manually transmit the register to read from using the polling method from above, then reconfigure the I2C bus to use interrupts with a byte counter of 2. I pass the buffer to my receive function. The empty for loop is necessary after calling receive, otherwise the buffer isn't filled properly and I lose part of the data, even though it shows it being received on the scope. 

Here is the transmit. The receive doesn't show up on the scope, but it *does* actually happen and return the correct data.

Here is the read from the same register using the polling method, no interrupts: 

These same issues happen with read24 as well.

So, in summary, I have I2C code that works, but only if I'm using a polling methodology. If I use interrupts I have to shoehorn it into working by using delays and reconfiguring the I2C bus again and again. Is there something I'm missing? I've noticed this board has an errata sheet that's about twice as long as most other TI boards...so perhaps there is something inherently wrong with this chip? 

I've tried other FR5994 Launchpads and gotten the same results. I haven't tried using another compiler yet, but if these problems remain unsolved that will happen.

Thank you in advance to anyone who reads this all the way through and offers any advice or solutions. It will be greatly appreciated. 


Viewing all articles
Browse latest Browse all 22176

Trending Articles