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(®,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.