• Not Answered

USCI (I2C Mode) and LPM0 on MSP430F5309

Hello,

In my design, I am currently using an MSP430F5309 to communicate with an MPU-6050 sensor by Invensense: http://invensense.com/mems/gyro/sixaxis.html  

I am using the USCI in I2C Mode, and I used the sample code as a guideline.  My intent is to use a Timer to read the sensors via I2C every X ms (let's say for example, every 20 ms, or 50 Hz).  I tried to implement this using TimerA0, and having the MSP430 operate in LPM0.  

I have the following problem.  Before entering LPM0, I do some writing and reading via I2C.  This works out fine, as I expected.  However, after entering LPM0, it seems as if  the system no longer enters the I2C interrupt.  My code is shown below:

-------------------------------------------------

#include <msp430f5310.h>

volatile static const unsigned char MPU6050_ADDR = 0x68;
volatile static unsigned char *PTxData; // Pointer to Tx data
volatile static unsigned char TxByteCtr;
volatile static unsigned char TxData[256];
volatile static unsigned char Temp;
volatile static unsigned char WhoAmI = 0;
volatile static unsigned char TestReg1 = 0;
volatile static unsigned char TestReg2 = 0;
volatile static unsigned char TestReg3 = 0;

volatile static unsigned char RxByteCtr;
volatile static unsigned char RxData[256];
volatile static unsigned char RxByteDummy;

volatile static signed int Accel[3];
volatile static unsigned char Accel_Buffer[6];
volatile static signed int Gyro[3];
volatile static unsigned char Gyro_Buffer[6];

volatile static unsigned char ReadWrite = 0;

volatile static unsigned int Period = 655;
volatile static unsigned char I2C_Stop_Flag = 0;

void I2C_Send(unsigned char length, unsigned char addr);
void Initialize_MPU6050(void);
unsigned char MPU6050_Read_Register(unsigned char TX);
void MPU6050_Read_Sensors(void);

void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT

UCSCTL4 |= SELA_2; // Set ACLK = REFO

// Initialize I2C Port
P4SEL |= 0x06; // Assign I2C pins to USCI_B1
UCB1CTL1 |= UCSWRST; // Enable SW reset
UCB1CTL0 = UCMST + UCMODE_3 + UCSYNC; // I2C Master, synchronous mode
UCB1CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
UCB1BR0 = 10; // fSCL = SMCLK/10 = ~100kHz
UCB1BR1 = 0;
UCB1I2CSA = 0x68; // Slave Address is 068h
UCB1CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
UCB1IE |= UCTXIE + UCRXIE; // Enable TX interrupt

// Initialize MPU-6050
__enable_interrupt();

Initialize_MPU6050();
WhoAmI = MPU6050_Read_Register(0x75);
TestReg1 = MPU6050_Read_Register(0x6B);
TestReg2 = MPU6050_Read_Register(0x1B);
TestReg3 = MPU6050_Read_Register(0x1C);
MPU6050_Read_Sensors();

__disable_interrupt();

P6DIR |= 0x04; // P6.2 output
TA0CCTL0 = CCIE; // CCR0 interrupt enabled
TA0CCR0 = 50000;
TA0CTL = TASSEL_1 + MC_2 + TACLR; // ACLK, Continuous Mode, clear TAR

__bis_SR_register(LPM0_bits + GIE);
}

//------------------------------------------------------------------------------
// The USCIAB1TX_ISR is structured such that it can be used to transmit any
// number of bytes by pre-loading TxByteCtr with the byte count. Also, TXData
// points to the next byte to transmit.
//------------------------------------------------------------------------------
#pragma vector = USCI_B1_VECTOR
__interrupt void USCI_B1_ISR(void)
{
switch(__even_in_range(UCB1IV,12))
{
case 0: break; // Vector 0: No interrupts
case 2:
__no_operation();
break; // Vector 2: ALIFG
case 4:
__no_operation();
break; // Vector 4: NACKIFG
case 6: break; // Vector 6: STTIFG
case 8: break; // Vector 8: STPIFG
case 10: // Vector 10: RXIFG
RxByteDummy = UCB1RXBUF; // Move final RX data to PRxData
if (RxByteCtr == 1) {
RxByteCtr--; // Increment RX byte counter
RxData[RxByteCtr] = RxByteDummy;
UCB1CTL1 |= UCTXSTP; // I2C stop condition
UCB1IFG &= ~UCTXIFG; // Clear USCI_B1 TX int flag
UCB1IFG &= ~UCRXIFG; // Clear USCI_B1 RX int flag
I2C_Stop_Flag = 1;
}
else {
__no_operation();
}
__no_operation();
break;
case 12: // Vector 12: TXIFG
if (!ReadWrite)
{
if (TxByteCtr) // Check TX byte counter
{
UCB1TXBUF = *PTxData++; // Load TX buffer
TxByteCtr--; // Decrement TX byte counter
}
else
{
UCB1CTL1 |= UCTXSTP; // I2C stop condition
UCB1IFG &= ~UCTXIFG; // Clear USCI_B1 TX int flag
I2C_Stop_Flag = 1;
}
}
else {
if (TxByteCtr) // Check TX byte counter
{
UCB1TXBUF = *PTxData++; // Load TX buffer
TxByteCtr--; // Decrement TX byte counter
}
else {
UCB1CTL1 &= ~UCTR;
UCB1CTL1 |= UCTXSTT;
}
}
default: break;
}
}

void I2C_Send(unsigned char length, unsigned char addr)
{
PTxData = (unsigned char *)TxData; // TX array start address
// Place breakpoint here to see each
// transmit operation.
TxByteCtr = length; // Load TX byte counter

UCB1I2CSA = addr;
I2C_Stop_Flag = 0;
UCB1CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition

__no_operation();

while (!I2C_Stop_Flag); // Ensure stop condition got sent

__no_operation();
}

void Initialize_MPU6050(void)
{
TxData[0] = 0x6B; TxData[1] = 0x01; // Power Management Register
unsigned char Buffer_Size = 2; ReadWrite = 0;
I2C_Send(Buffer_Size, MPU6050_ADDR);
__no_operation();

Buffer_Size = 2; ReadWrite = 0;
TxData[0] = 0x1B; TxData[1] = 0x08; // Gyro Range Register
I2C_Send(Buffer_Size, MPU6050_ADDR);
__no_operation();

Buffer_Size = 2; ReadWrite = 0;
TxData[0] = 0x1C; TxData[1] = 0x18; // Lin Acc Range Register
I2C_Send(Buffer_Size, MPU6050_ADDR);
__no_operation();

}


// Timer0 A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void)
{
P6OUT ^= 0x04; // Toggle P6.2
MPU6050_Read_Sensors();
TA0CCR0 += Period;
}

unsigned char MPU6050_Read_Register(unsigned char TX)
{
RxByteCtr = 1;
unsigned char Buffer_Size = 1;
TxData[0] = TX;
ReadWrite = 1;
for (int i=0;i<10;i++); // Delay required between transaction
I2C_Send(Buffer_Size,MPU6050_ADDR);
ReadWrite = 0;
for (int i = 0; i < 10; i++); // Delay required between transaction
return RxData[0];
}

void MPU6050_Read_Sensors(void)
{
Accel_Buffer[0] = MPU6050_Read_Register(0x3B);
Accel_Buffer[1] = MPU6050_Read_Register(0x3C);
Accel_Buffer[2] = MPU6050_Read_Register(0x3D);
Accel_Buffer[3] = MPU6050_Read_Register(0x3E);
Accel_Buffer[4] = MPU6050_Read_Register(0x3F);
Accel_Buffer[5] = MPU6050_Read_Register(0x40);

for (int j = 0; j < 6; j = j + 2)
{
Accel[j >> 1] = (((signed int)Accel_Buffer[j]) << 8) | Accel_Buffer[j+1];
}

Gyro_Buffer[0] = MPU6050_Read_Register(0x43);
Gyro_Buffer[1] = MPU6050_Read_Register(0x44);
Gyro_Buffer[2] = MPU6050_Read_Register(0x45);
Gyro_Buffer[3] = MPU6050_Read_Register(0x46);
Gyro_Buffer[4] = MPU6050_Read_Register(0x47);
Gyro_Buffer[5] = MPU6050_Read_Register(0x48);

for (int j = 0; j < 6; j = j + 2)
{
Gyro[j >> 1] = (((signed int)Gyro_Buffer[j]) << 8) + Gyro_Buffer[j+1];
}
}

-------------------------------------------------

I would greatly appreciate any assistance anyone can provide.

Sincerely,

Mehdi

7 Replies

  • Mehdi Rahman
     However, after entering LPM0, it seems as if  the system no longer enters the I2C interrupt.

    It has nothing to do with LPM0.

    You call a function (MPU5060_Read_Sensors)  that work based on an ISR from within an ISR. So it runs in interrupt context itself, and normally, an ISR cannot be interrupted by anothe rinterrupt (interrupt priorities determine which one to handle first when two are pending, not that the higher priority one can interrupt the other)

    What you should do is: in the timer ISR you exit LPM. And following the enter LPM code in main the read_register code is executed and then LPM is entered again.

    P.s.: you can allow other interrupts to interrupt a running ISR by setting GIE again inside the ISR. However, this is highly dangerous. The ISR that does it must be reentrant (because it may interrupt itself), stack usage may significantly rise up to stack overflow and many other possible fatal problems. Nested interrupts are really only for top experts. And there is almost no case ever where it is really necessary (it's usually a general application desing flaw if it appear to be necessary)

  • In reply to Jens-Michael Gross:

    Hi Jens-Michael Gross,

    Thank you for your reply.

    Based on the following, which method is better.  This MSP430 will always act as the master to I2C devices, and will always initiate a transmission.  Instead of using the solution method you described, would it be better to have a function which implements polling of the I2C module.  This function can be called every time Timer_A0 is entered.

    I guess another consideration is reducing power consumption.  Would it not be more power-efficient to stay in LPM0 rather than exiting into active mode to perform the I2C task, and then re-entering LPM0?  

    Thank you.

    Sincerely,

    Mehdi

  • In reply to Mehdi Rahman:

    Generally, ISR should be fast-in, fast out. Just recognize an event, set  aflag, wake the main program. Doing a complete I2C transfer is nothing that belongs into an ISR. ISRs are not parallel threads.

    So no, polling the USCI for th etransfer would not only prevent all otehr itnerrupts (if any) to be handled, it would also keep the processor needlessly active.

    Mehdi Rahman
    Would it not be more power-efficient to stay in LPM0 rather than exiting into active mode to perform the I2C task, and then re-entering LPM0?  

    In the described szenario it makes no difference. If you do the transfer inside the ISR or in main, the processor is active in both cases. And entering/exiting LPM0 is a matter of one clock cycle.

    The transfer function itself may enter LPM0 until its own ISR tells that the transfer is done. This would save some power. :)

  • In reply to Jens-Michael Gross:

    Hello Jens-Michael Gross,

    Thank you for your assistance.  I do see your point in having an ISR simply set a flag which then sets certain actions in the main routine, as opposed to doing a whole set of actions in the ISR.  I will re-think my approach to allow for this.

    In the meantime, I decided to do more I2C testing.  I changed the clock speed from 100 kHz to 400 kHz , and also increased the DCO speed to 8 MHz, by using the following initialization code.

    ------------------------------------------------------------------------------------------------------------------------------

    UCSCTL3 = SELREF_2; // Set DCO FLL reference = REFO
    UCSCTL4 |= SELA_2; // Set ACLK = REFO
    UCSCTL0 = 0x0000; // Set lowest possible DCOx, MODx

    // Loop until XT1,XT2 & DCO stabilizes - In this case only DCO has to stabilize
    do
    {
    UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
    // Clear XT2,XT1,DCO fault flags
    SFRIFG1 &= ~OFIFG; // Clear fault flags
    }while (SFRIFG1&OFIFG); // Test oscillator fault flag

    __bis_SR_register(SCG0); // Disable the FLL control loop
    UCSCTL1 = DCORSEL_5; // Select DCO range 16MHz operation
    UCSCTL2 |= 249; // Set DCO Multiplier for 8MHz
    // (N + 1) * FLLRef = Fdco
    // (249 + 1) * 32768 = 8MHz
    __bic_SR_register(SCG0); // Enable the FLL control loop

    // Worst-case settling time for the DCO when the DCO range bits have been
    // changed is n x 32 x 32 x f_MCLK / f_FLL_reference. See UCS chapter in 5xx
    // UG for optimization.
    // 32 x 32 x 8 MHz / 32,768 Hz = 250000 = MCLK cycles for DCO to settle
    __delay_cycles(250000);

    // Initialize I2C Port
    P4SEL |= 0x06; // Assign I2C pins to USCI_B1
    UCB1CTL1 |= UCSWRST; // Enable SW reset
    UCB1CTL0 = UCMST + UCMODE_3 + UCSYNC; // I2C Master, synchronous mode
    UCB1CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
    UCB1BR0 = 20; // fSCL = SMCLK/20 = ~400kHz
    UCB1BR1 = 0;
    UCB1I2CSA = MPU6050_ADDR; // Slave Address is 068h
    UCB1CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
    UCB1IE |= UCTXIE + UCRXIE; // Enable TX interrupt

    ------------------------------------------------------------------------------------------------------------------------------

    I took some oscilloscope tracings, which I have attached here.  It seems as if the 400 kHz tracings are quite noisy (what do you think).  I just want to make sure I'm not violating any rules.  

    100 kHz

    400 kHz

     

    Sincerely,

    Mehdi

  • This is a very useful post. Thanks Mehdi!

  • In reply to Mehdi Rahman:

    hello mehdi. I'm using ez430-rf2500 to interface with mpu6050. Can u guide me how to convert your code to use for msp430f2272. I was wondering a line:

    P6DIR |= 0x04; // P6.2 output

    TA0CCTL0 = CCIE; // CCR0 interrupt enabled

    TA0CCR0 = 50000;

    TA0CTL = TASSEL_1 + MC_2 + TACLR; // ACLK, Continuous Mode, clear TAR

    what the purpose of this code? 

    sinceryly

    Phuoc Nguyen

  • In reply to nguyen phuoc:

    This code calls the TIMER0_A0_VECTOR ISR after 50000 ACLK ticks.
    Probably for a timeout detection, or a delay.

    Details about the individual meanings are found in the 2x family users guide.