Author Topic: MSP430F5529 I2C Anger...  (Read 6588 times)

0 Members and 1 Guest are viewing this topic.

Offline MrAureliusRTopic starter

  • Supporter
  • ****
  • Posts: 373
  • Country: ca
MSP430F5529 I2C Anger...
« on: December 28, 2014, 04:26:51 am »
Hey again everyone. I'm writing a basic piece of code just to try out a new sensor I picked up -- a Bosch BMP180 temperature/barometric pressure sensor. It uses I2C, which I've used umpteen times and am intimately familiar with. I've been using the MSP430F5529 Launchpad a lot lately, and I've used the SPI and UART multiple times with no problem. For some bizarre reason, I just cannot get the I2C working.
I do not want to use interrupts. Yes, I know it might be the 'right' way to do it, but this is quick and dirty code, and it's not doing anything else, so interrupts would probably make it more trouble. All the example code with the MSP430Ware is all interrupt-based. In this case I'm twiddling all the registers manually. I also tried to use the driver library API (I2CEnable, I2CStart, etc) but had just as much miserable failure.

The really strange thing is that occasionally I get a whole bunch of data coming out of the SDA pin, but no clocks on SCL, and the data is not even close to what I've written.

I really hope someone out there spots a glaring, stupid error I've made and rescues me from my own stupidity. I got it (sort of) working in Energia (the Arduino port for TI parts) but the problem ended up being the formulas from the BMP180, however I really don't like using Energia because there's no debugging. You can use it inside CCS to get some debug, but I want to avoid this. PLUS I want to know how to do this in the future, instead of going to libraries by default.

Here's what I've got:

Code: [Select]
//***************************************************************************************
/* Barometric pressure test
 * This is a quick program to test communication with the BMP180 via I2C
 */

//***************************************************************************************
#include <msp430.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "debug_uart.h"

// The BMP180's I2C addresses are 0xEE for write, 0xEF for read
enum {
AC1,
AC2,
AC3,
AC4,
AC5,
AC6,
B1,
B2,
MB,
MC,
MD,
reg_count
};
typedef struct BMP {
// These are all the internal calibration constants
int16_t regs[reg_count + 1];
// Over-sampling setting
uint8_t oss;
// Temperature and pressure data
uint16_t therm;
uint16_t pressure;
// I2C send and receive data
uint8_t sendData;
uint8_t recvData;

} BMP_t;

BMP_t sensor, *pSensor = &sensor;

void I2Cinit(void) {
// I2C uses UCB0, SCL on 3.1, SDA on 3.0
P3SEL |= 0x03;
UCB0CTL1 |= UCSWRST;

UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;
UCB0CTL1 = UCSSEL_2 + UCSWRST; // select SMCLK
UCB0BR0 = 0x78; // divide 12MHz by 120 to get 100kHz (or 100kbps)
UCB0BR1 = 0;
UCB0I2CSA = 0x77;
UCB0CTL1 &= ~UCSWRST;
}

void I2Cgetbyte(BMP_t *slave) {
        UCB0CTL1 |= UCTR + UCTXSTT; // set transmitter mode, and generate start
        while (!(UCB0IFG & UCTXIFG)) ; // wait until data can be written
UCB0TXBUF = slave->sendData; // transmit data
UCB0CTL1 |= UCTXSTT; // send restart
        UCB0CTL1 &= ~UCTR; // set receiver mode
//while(!(UCB0IFG & UCRXIFG)) ; //wait for data
slave->recvData = UCB0RXBUF; //read data
UCB0CTL1 |= UCTXSTP; // send STOP
}

void getCalData(void) {
char j, i, k;
for (j = 0xAA, i = reg_count; i != 0; i--, j += 2) {

pSensor->sendData = j;
for (k = 2; k > 0; k--) {  // for each reg, we need to read two bytes
I2Cgetbyte(pSensor); // read the next byte into the buffer
if (k == 2) { // least significant byte
pSensor->regs[i] |= pSensor->recvData;
} else { // most significant byte
pSensor->regs[i] |= (pSensor->recvData << 8);
}
}
}
}

void calcResult(void) {
      // not implemented yet
}

int main(void) {
char output[32], *pOutput = &output[0];
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
//------- VCORE RESET SECTION -------//
PMMCTL0_H = 0xA5; // Open PMM module registers for write access
PMMCTL0 = 0xA500 + PMMCOREV_3; // Set VCore to 1.85 V
SVSMLCTL = SVMLE + SVSMLRRL_6 + SVSLFP +
SVSLRVL_3 + SVSLE + SVMLFP; // Enable Vcore supply monitor, level 6, high performance mode,
// and Vcore supply supervisor, level 3, high performance mode.
SVSMHCTL = SVSHE + SVSHFP + SVSHRVL_3 +
SVSMHRRL_3; // Enable DVcc supply supervisor, level 3, high performace
while ((PMMIFG & SVSMLDLYIFG) == 0)
; // Wait for Vcore delay element to expire
PMMIFG &= ~(SVMLVLRIFG + SVMLIFG); // Clear already set flags
if ((PMMIFG & SVMLIFG)) // If Vcore still below target, wait for it to rise
while ((PMMIFG & SVMLVLRIFG) == 0)
;
SVSMLCTL &= ~SVMLE; // Disable Low side SVM
PMMCTL0_H = 0x00; // Lock PMM module registers for write access
//------- END VCORE RESET SECTION -------//

//------- CLOCK CONFIG SECTION -------//
P5SEL |= 0x0C; // set XT2 crystal pins as clock pins, not I/O
UCSCTL3 = SELREF__XT2CLK; // select XT2 as FLL reference clock
UCSCTL1 = DCORSEL_6; // select proper DCO tap based on expected freq, below
UCSCTL2 = FLLD__2 + FLLN1; // times 2, times 3. With XT2 at 4MHz, DCOCLK will be
// 24MHz, and DCOCLKDIV will be 12MHz
UCSCTL4 = SELA__REFOCLK + SELS__DCOCLKDIV + SELM__DCOCLK;
// set ACLK to REFOCLK (32.768kHz), MCLK to DCOCLK (24MHz),
// SMCLK to DCOCLKDIV (12MHz)
UCSCTL5 = DIVS__2; // Divide SMCLK down to 6MHz
//------- END CLOCK CONFIG -------//

uartInit();
I2Cinit();
getCalData();

sprintf(output, "The cal data is:\n");
uart_puts(pOutput, 17);
sprintf(output, "\t MSB: %x \t LSB: %x \n", pSensor->regs[0], pSensor->regs[1]);
uart_puts(pOutput, 26);
sprintf(output, "\t MSB: %x \t LSB: %x \n", pSensor->regs[2], pSensor->regs[3]);
uart_puts(pOutput, 26);
sprintf(output, "\t MSB: %x \t LSB: %x \n", pSensor->regs[4], pSensor->regs[5]);
uart_puts(pOutput, 26);
sprintf(output, "\t MSB: %x \t LSB: %x \n", pSensor->regs[6], pSensor->regs[7]);
uart_puts(pOutput, 26);
sprintf(output, "\t MSB: %x \t LSB: %x \n", pSensor->regs[8], pSensor->regs[9]);
uart_puts(pOutput, 26);
sprintf(output, "\t MSB: %x \t LSB: %x \n", pSensor->regs[10], pSensor->regs[11]);
uart_puts(pOutput, 26);
return 0;
}


Often, it'll stall at the while (!(UCB0IFG & UCTXIFG)) ; , but other times it'll go through fine. I really can't see what I'm doing wrong as I can't find any example code anywhere that's not interrupt-based. I know that the I2Cinit() function is correct, I've checked that over and over and that part is usually the same in interrupt-based code (of course, they're set up the same, the transfers just take place differently). It's likely the I2Cgetbyte() function needs delays between the different actions, but again there's no example out there. Waiting for the start and stop bits to be reset doesn't seem to work either, even though that's what the family user's guide says to do...
« Last Edit: December 28, 2014, 04:42:42 am by MrAureliusR »
--------------------------------------
Canadian hacker
 

Offline MrAureliusRTopic starter

  • Supporter
  • ****
  • Posts: 373
  • Country: ca
Re: MSP430F5529 I2C Anger...
« Reply #1 on: December 28, 2014, 05:09:49 am »
I just tried the example code that comes with MSP430Ware, just to make sure I'm not insane. It's supposed to send four bytes. I ran it, and it sends the address (0x90) and then does nothing...
Seeing as how even the example code doesn't work properly... what on earth am I doing wrong?!?

EDIT: Just realized I'm an idiot. The example code needs another MSP430 to acknowledge the address, of course... so at least it seems the I2C module IS working.

Changed the address to my BMP180's address and it does indeed send the bytes. So the interrupt version works... trying to figure out what they're doing differently...

How can I implement it with interrupts? I can't get my head around what the code should look like, how it should be structured. It needs to get the calibration constants from the BMP180, then trigger a conversion process, read out the results, and then do a bunch of math with the calibration data to get the end result. When I get to the part of the code where I need to read or write bytes over I2C, I can't figure out how to write it. I've worked with interrupts before, for things like comparators and ADCs, but this feels like I should be doing it differently. Anyway... I'll keep working at it. All suggestions welcome.
« Last Edit: December 28, 2014, 05:17:29 am by MrAureliusR »
--------------------------------------
Canadian hacker
 

Offline AndreasF

  • Frequent Contributor
  • **
  • Posts: 251
  • Country: gb
    • mind-dump.net
Re: MSP430F5529 I2C Anger...
« Reply #2 on: December 28, 2014, 11:34:25 am »
I doubt it's the fact that you're not using interrupts.  Having had a quick glance at the reference manual and then your code, I don't see you writing the slave's address to the "UCBxI2CSA Register" anywhere:

Quote
36.3.4.2.2 I2C Master Receiver Mode
After initialization, master receiver mode is initiated by writing the desired slave address to the UCBxI2CSA register, selecting the size of the slave address with the UCSLA10 bit, clearing UCTR for receiver mode, and setting UCTXSTT to generate a START condition.
The USCI module checks if the bus is available, generates the START condition, and transmits the slave address. As soon as the slave acknowledges the address, the UCTXSTT bit is cleared.
After the acknowledge of the address from the slave, the first data byte from the slave is received and acknowledged and the UCRXIFG flag is set. ...
my random ramblings mind-dump.net
 

Offline MrAureliusRTopic starter

  • Supporter
  • ****
  • Posts: 373
  • Country: ca
Re: MSP430F5529 I2C Anger...
« Reply #3 on: December 28, 2014, 09:06:01 pm »
I doubt it's the fact that you're not using interrupts.  Having had a quick glance at the reference manual and then your code, I don't see you writing the slave's address to the "UCBxI2CSA Register" anywhere:

Quote
36.3.4.2.2 I2C Master Receiver Mode
After initialization, master receiver mode is initiated by writing the desired slave address to the UCBxI2CSA register, selecting the size of the slave address with the UCSLA10 bit, clearing UCTR for receiver mode, and setting UCTXSTT to generate a START condition.
The USCI module checks if the bus is available, generates the START condition, and transmits the slave address. As soon as the slave acknowledges the address, the UCTXSTT bit is cleared.
After the acknowledge of the address from the slave, the first data byte from the slave is received and acknowledged and the UCRXIFG flag is set. ...


   UCB0BR0 = 0x78; // divide 12MHz by 120 to get 100kHz (or 100kbps)
   UCB0BR1 = 0;
   UCB0I2CSA = 0x77;
   UCB0CTL1 &= ~UCSWRST;

No, I definitely wrote to that register. It requires the 7-bit address because it automatically sets up the correct 8-bit address for you. I've tried moving that write to the slave address register around, but it doesn't make any difference.
--------------------------------------
Canadian hacker
 

Offline lapm

  • Frequent Contributor
  • **
  • Posts: 564
  • Country: fi
Re: MSP430F5529 I2C Anger...
« Reply #4 on: December 31, 2014, 06:18:23 am »
One thing that came to mind is, isint i2c slave supposed to send presence pulse after receivingin address? Have you confirmed this happens? That might block any further sending of bytes if slave docent answer...
Electronics, Linux, Programming, Science... im interested all of it...
 

Offline eck

  • Contributor
  • Posts: 15
Re: MSP430F5529 I2C Anger...
« Reply #5 on: December 31, 2014, 01:50:08 pm »
This is pretty much untested, but might work:

Code: [Select]
void I2Cgetbyte(BMP_t *slave) {
        UCB0CTL1 |= UCTR + UCTXSTT; // set transmitter mode, and generate start
        while (!(UCB0IFG & UCTXIFG)) ; // wait until data can be written
UCB0TXBUF = slave->sendData; // transmit data

        UCB0CTL1 &= ~UCTR;              // set receiver mode
UCB0CTL1 |= UCTXSTT; // send restart
while(!(UCB0CRL1 & UCTXSTT)) ;  // wait for start condition done
UCB0CTL1 |= UCTXSTP;            // send STOP
       
        while(!(UCB0IFG & UCRXIFG)) ;   //wait for data
slave->recvData = UCB0RXBUF;    //read data
}

The main change for data reception is to set the STOP condition right after the START condition has been generated.
 

Offline eck

  • Contributor
  • Posts: 15
Re: MSP430F5529 I2C Anger...
« Reply #6 on: January 03, 2015, 08:27:28 pm »
Need to correct myself, seems you need to still check for the start condition to complete before sending the repeated start condition. Also, the checks for start condition completion need to be negated:

Code: [Select]
void I2Cgetbyte(BMP_t *slave) {
        UCB0CTL1 |= UCTR + UCTXSTT; // set transmitter mode, and generate start
        while (!(UCB0IFG & UCTXIFG)) ; // wait until data can be written
UCB0TXBUF = slave->sendData; // transmit data
while(UCB0CRL1 & UCTXSTT) ;     // wait for start condition done

        UCB0CTL1 &= ~UCTR;              // set receiver mode
UCB0CTL1 |= UCTXSTT; // send restart
while(UCB0CRL1 & UCTXSTT) ;     // wait for start condition done
UCB0CTL1 |= UCTXSTP;            // send STOP
       
        while(!(UCB0IFG & UCRXIFG)) ;   //wait for data
slave->recvData = UCB0RXBUF;    //read data
}
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf