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 datavolatile 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
Mehdi Rahman However, after entering LPM0, it seems as if the system no longer enters the I2C interrupt.
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)
_____________________________________Before posting bug reports or ask for help, do at least quick scan over this article. It applies to any kind of problem reporting. On any forum. And/or look here.If you cannot discuss your problem in the public, feel free to start a private conversation: click on my name and then 'start conversation'. But please do so only if you really cannot do it in a public thread, as I usually read all threads. And I prefer to answer where others can profit from it (or contribute to it) too.
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.
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 RahmanWould 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?
The transfer function itself may enter LPM0 until its own ISR tells that the transfer is done. This would save some power. :)
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
This is a very useful post. Thanks Mehdi!