This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

  • Resolved

CCS/MSP430FR6989: Unable to read values from ICM20948 IMU sensor using SPI

Prodigy 120 points

Replies: 8

Views: 124

Part Number: MSP430FR6989

Tool/software: Code Composer Studio

I am new to embedded systems. I am trying to interface ICM20948 IMU sensor with MSP430FR6989 using SPI protocol. My requirement is to develop code from scratch without using MSP Driver libraries. Based on the example codes available in Resource Explorer, I have configured SPI protocol in the microcontroller. I am able to send data but I am unable to receive data from the IMU sensor. When connect MOSI and SOMI of microcontroller without IMU and initiate transmission, I am able to transmit and receive data. I am trying to read the register for WHO AM I value of the device.

Configuration:
MSP - Master mode
Clock - 7Mhz
Data latch - rising edge
Data transmission - falling edge

I have been struggling with this problem over 3 weeks, kindly help me by pointing out where I am going wrong in the following code.

Thanks in advance.

#include <msp430.h> 


volatile int ch1;
unsigned int i;


// GPIO Configuration
void initGPIO()
{
    // Test code for LED at P1.0
    P8OUT &= ~BIT5;                           // Clear P1.0 output latch for a defined power-on state
    P8DIR |= BIT5;                            // Set P1.0 to output direction


    // USCI_A0 SPI configuration
    // P2.0,2.1,2.2,configured for SPI functionality

    P2SEL1 &= ~(BIT0 | BIT1 | BIT2);
    P2SEL0 |= BIT0 | BIT1 | BIT2;
}
void initClock()
{
    // Clock System Setup
      CSCTL0_H = CSKEY >> 8;                    // Unlock CS registers      
      CSCTL1 = 0x0005 | DCORSEL;             // Set DCO to 7 MHz
      CSCTL2 = SELM__DCOCLK | SELS__DCOCLK | SELA__VLOCLK;
      CSCTL3 = DIVA__1 | DIVS__1 | DIVM__1;     // Set all dividers to 1
      CSCTL4 = LFXTOFF | HFXTOFF;               // Disable external oscillator
      CSCTL0_H = 0;                             // Lock CS registers


}

// SPI Configuration
void initSPI()
{
      UCA0CTLW0 = UCSWRST;                      // **Put state machine in reset**
      //UCA0CTLW0 |= UCMST | UCSYNC | UCCKPL | UCMSB; // 3-pin, 8-bit SPI master
                                                // Clock polarity high, MSB
      UCA0CTLW0 |= UCMST | UCSYNC | UCMSB; // 3-pin, 8-bit SPI master
      UCA0CTLW0 |= UCSSEL__SMCLK;                // ACLK    
      UCA0BR0 = 0;
      UCA0BR1 = 0;                              //
      UCA0MCTLW = 0;                            // No modulation
      UCA0CTLW0 &= ~UCSWRST;                    // **Initialize USCI state machine**
      UCA0IE |= UCRXIE;                         // Enable USCI_A0 RX interrupt

}

void spiWriteTest( int data)
{
    P8OUT &= ~BIT5;               // Slave Select low
    while(!(UCA0IFG&UCTXIFG));
    UCA0TXBUF = data;             // Initiate Transmission
    P8OUT |= BIT5;                // Slave Select high

}
/**
 * main.c
 */
void main(void)
{
    WDTCTL = WDTPW | WDTHOLD;   // stop watchdog timer

        initGPIO();

        // Disable the GPIO power-on default high-impedance mode to activate
        // previously configured port settings
        PM5CTL0 &= ~LOCKLPM5;

        initClock();
        initSPI();

        __bis_SR_register(GIE);


        while(1)
        {

          spiWriteTest(0x7F);    // Select register bank control register

          spiWriteTest(0x80);    // Select register bank 0

          spiWriteTest(0x00);    // Read WHO AM I

          for(i=1000;i>0;i--);

        }       
}

#pragma vector=USCI_A0_VECTOR
__interrupt void USCI_A0_ISR(void)
{
    while(!(UCA0IFG&UCRXIFG));// USCI_A0 TX buffer ready?
  ch1 = UCA0RXBUF;
}

  • Hi Surendar,

    First check this application note to see if there might be an answer.

    Also, do you have an oscilloscope or logic probe that will allow you to view the SPI signals?

    Dennis Lehman

  • In reply to Dennis Lehman:

    Dennis Lehman

    Hi Surendar,

    First check this application note to see if there might be an answer.

    Also, do you have an oscilloscope or logic probe that will allow you to view the SPI signals?

    Hi Dennis,

    Yes, I have checked this application note before I post it here and I did not get any solution.

    Yes, I do have an oscilloscope.

    Without connecting it to any external device, I was able to implement SPI successfully but if I connect it to ICM20948 IMU sensor I receive either 0x00 or 0xFF from the slave. I am not able to figure out why I get these values.

    The procedure to read data from the sensor 

    • Select the register bank
    • Send the register address
    • Read the data in register

    Since I am new to embedded systems, I am unable to translate them to a proper working code. 

    I would appreciate you if you could give a solution or resources that could lead to a solution.

    Thanks.

  • In reply to Surendar Devasundaram:

    From data sheet (DS-000189) Sec 6.5 item (5), each transaction -- while /CS is low -- is at least 2 bytes. This program does only 1-byte transactions.

    The first byte is a register number with the high bit =1 for a Read (this program appears to do Reads with the high bit =0).

    To read a register, send two bytes: (1) the register number+0x80 (2) a dummy byte, which could be 0x00. The second byte you receive is the register contents.

    To write a register, send two bytes: (1) the register number+0x00 (2) the data byte you want to write.

    "Setting the register bank" means writing a number (0x00 for your case) to register 0x7F. It is not a special transaction.

    -----

    I recommend that you not use interrupts, They'll be slow and hard to manage.Simpler and faster would be to use the spix() function discussed over here:

    https://e2e.ti.com/support/microcontrollers/msp430/f/166/p/758000/2800722

    This function exchanges a single byte. Call it twice for each transaction, e.g.:

    unsigned char read_reg(unsigned char reg_number) {
        unsigned char result;
        P8OUT &= ~BIT5; // Assert /CS
        spix(reg_number | 0x80);  // Read request for that register
        result = spix(0x00);    // Send dummy, get register contents
        P8OUT |= BIT5;  // De-assert /CS
        return(result);
    }

    ----------

    It's not clear to me how the device decides whether to use I2C or SPI. There's a Note in data sheet section 6.1 that suggests it may switch to I2C without your realizing it. That Note recommends setting USER_CTRL bit 4, i.e. "write_reg(0x03,0x10);" as the first thing. Keep this in mind, but start by trying to read WHO_AM_I (register 0 of bank 0).

    [Edit: fixed register name]

  • In reply to Bruce McKenney47378:

    Bruce McKenney47378

    From data sheet (DS-000189) Sec 6.5 item (5), each transaction -- while /CS is low -- is at least 2 bytes. This program does only 1-byte transactions.

    The first byte is a register number with the high bit =1 for a Read (this program appears to do Reads with the high bit =0).

    To read a register, send two bytes: (1) the register number+0x80 (2) a dummy byte, which could be 0x00. The second byte you receive is the register contents.

    To write a register, send two bytes: (1) the register number+0x00 (2) the data byte you want to write.

    "Setting the register bank" means writing a number (0x00 for your case) to register 0x7F. It is not a special transaction.

    -----

    I recommend that you not use interrupts, They'll be slow and hard to manage.Simpler and faster would be to use the spix() function discussed over here:

    https://e2e.ti.com/support/microcontrollers/msp430/f/166/p/758000/2800722

    This function exchanges a single byte. Call it twice for each transaction, e.g.:

    unsigned char read_reg(unsigned char reg_number) {
        unsigned char result;
        P8OUT &= ~BIT5; // Assert /CS
        spix(reg_number | 0x80);  // Read request for that register
        result = spix(0x00);    // Send dummy, get register contents
        P8OUT |= BIT5;  // De-assert /CS
        return(result);
    }

    ----------

    It's not clear to me how the device decides whether to use I2C or SPI. There's a Note in data sheet section 6.1 that suggests it may switch to I2C without your realizing it. That Note recommends setting USER_CTRL bit 4, i.e. "write_reg(0x03,0x10);" as the first thing. Keep this in mind, but start by trying to read WHO_AM_I (register 0 of bank 0).

    [Edit: fixed register name]

    Thank you for the explanation. I am able to understand now. As you mentioned I will set the USER_CTRL register. In the above code snippet, you have given the function for reading a register. I came across some code in  https://os.mbed.com/teams/SiliconLabs/code/ICM20648/file/296308a935f5/ICM20648.cpp/ in the function

    void ICM20648::write_register(uint16_t addr, uint8_t data)

    where they write using spix(reg_number & 0x00) (changed to reflect my requirement). Here, the MSB should be 0 to write a register. Here if I use something like this won't the entire value passed to spix function become 0x00?  So kindly tell me how the function will look like for writing the register.

  • In reply to Surendar Devasundaram:

    I don'[t see the line of code you're referring to. I do see a couple of lines that do the equivalent of:

    > spix(reg_number & 0x7F);   // Reg number with R/W=0

    which is what you probably want.

  • In reply to Bruce McKenney47378:

    Bruce McKenney47378

    I don'[t see the line of code you're referring to. I do see a couple of lines that do the equivalent of:

    > spix(reg_number & 0x7F);   // Reg number with R/W=0

    which is what you probably want.

    void ICM20648::write_register(uint16_t addr, uint8_t data)
    {
        uint8_t regAddr;
        uint8_t bank;
     
        regAddr = (uint8_t) (addr & 0x7F);
        bank = (uint8_t) (addr >> 7);
     
        select_bank(bank);
     
        /* Enable chip select */
        m_CS = 0;
     
        /* clear R/W bit - write, send the address */
        m_SPI.write(regAddr & 0x7F);
        m_SPI.write(data);
     
        /* Disable chip select */
        m_CS = 1;
     
        return;
    }

    This is the function I am talking about. I would like to know what is happening inside m_SPI.write(regAddr & 0x7F) function. I couldn't find any function definition and I assume it is from library.

    In another function,

    m_SPI.write(NULL, 0, (char*)data, numBytes);

    is used. So I am not quite sure what that function is doing and how these parameters are used inside the function. Kindly help me understand.

  • In reply to Surendar Devasundaram:

    m_SPI.write(uint8_t) appears to be equivalent to spix(). A difference is that spix() always does both a Tx and an Rx (you can throw away the result if you're not interested).

    It appears that write() is overloaded, so you can also pass an array. spix() doesn't do that.

    They also appear to be encoding the register bank number in bits 15:7 of the register "address". You don't have to do that (but you could if you wanted).

    I suggest you not over-complicate this. What the MBED people did is perhaps interesting, but what you want is much more modest.

  • In reply to Bruce McKenney47378:

    Thank you . I understand now. Still I couldn't get it work as I am not able to figure out the sequence for configuring the IMU. I have reached out to the vendor regarding that but as far as MSP430 is considered I think we can consider this to be solved. I appreciate your time and effort.

This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.