Shop OBEX P1 Docs P2 Docs Learn Events
L3G4200D Gyroscope Data Using SPI — Parallax Forums

L3G4200D Gyroscope Data Using SPI

Hello,

I completed the SPI example on the Learn website given here: http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-protocols/spi-example

I would now like to read data from the Parallax L3G4200D gyroscope module using the 3-wire SPI mode. Here is my code so far:
/*
  Gyroscope Test XYZ Axes.c
*/
#include "simpletools.h"                                          // Include simple tools

const int DATA = 12;
const int CLK = 13;
const int CS = 14;

const int CTRL_REG1 = 0x20;
const int CTRL_REG2 = 0x21;
const int CTRL_REG3 = 0x21;
const int CTRL_REG4 = 0x22;
const int STATUS_REG = 0x27;
unsigned char statusReg;

signed char x;                                                    // X-axis value
signed char y;                                                    // Y-axis value
signed char z;                                                    // Z-axis value

int main()                                                        // Main function
{
  high(CS);                                                       // CS line high (inactive)
  low(CLK);                                                       // CLK line low
  
  low(CS);                                                        // CS line low (start SPI)
  shift_out(DATA, CLK, MSBFIRST, 16, 0b0010000000011111);          // Write CTRL_REG1
  shift_out(DATA, CLK, MSBFIRST, 16, 0b0010001000001000);          // Write CTRL_REG3
  shift_out(DATA, CLK, MSBFIRST, 16, 0b0010001110000001);          // Write CTRL_REG4
  high(CS);                                                       // CS line high (stop SPI)
 
  while(1)                                                         // Main loop
  {
    low(CS);                                                       // CS low selects chip
    shift_out(DATA, CLK, MSBFIRST, 8, 10100111);                        // Send read register address, x-axis
    statusReg = shift_in(DATA, CLK, MSBPOST, 16);                               // Get value from register
    
    if(statusReg == 0)
    {
      shift_out(DATA, CLK, MSBFIRST, 8, 10100111);                        // Send read register address, x-axis
      statusReg = shift_in(DATA, CLK, MSBPOST, 16);                               // Get value from register
    }
    else
    {
      shift_out(DATA, CLK, MSBFIRST, 8, 10100111);                        // Send read register address, x-axis
      statusReg = shift_in(DATA, CLK, MSBPOST, 16);                               // Get value from register
      
            
      
      
    

    //x = shift_in(DATA, CLK, MSBPRE, 8);                            // Get value from register
    
    /*
    cmd = YOUT8 & readMask;
    shift_out(DATA, CLK, MSBFIRST, 7, cmd);                        // Send read register address, y-axis
    shift_out(DATA, CLK, MSBFIRST, 1, 0b0);                        // Send don't care value
    y = shift_in(DATA, CLK, MSBPRE, 8);                            // Get value from register
    
    cmd = ZOUT8 & readMask;
    shift_out(DATA, CLK, MSBFIRST, 7, cmd);                        // Send read register address, z-axis
    shift_out(DATA, CLK, MSBFIRST, 1, 0b0);                        // Send don't care value
    z = shift_in(DATA, CLK, MSBPRE, 8);                            // Get value from register
    high(CS);   
    */                                                   // De-select chip
    
   // print("%cx=%d  y=%d  z=%d%c", HOME, x, y, z, CLREOL);          // Display measurement
    
    pause(500);                                                    // Wait 0.5s before repeat    
  }  
}

I am trying to develop this code without using the bit masks first so I can make sure I understand everything. My first question deals with reading data from the device. In the datasheet on page 26, we see:

"The SPI read command is performed with 16 clock pulses:
Bit 0: READ bit. The value is 1.
Bit 1: MS bit. When 0, do not increment address; when 1, increment address in multiple
reading.
Bit 2-7: address AD(5:0). This is the address field of the indexed register.
Bit 8-15: data DO(7:0) (read mode). This is the data that is read from the device (MSb first).
The multiple read command is also available in 3-wire mode."

Why is there a bit 8-15? Isn't that the data we are trying to get? I thought it would be something like this (taken from my code above):
shift_out(DATA, CLK, MSBFIRST, 8, 10100111);                        // Send read register address, x-axis
    statusReg = shift_in(DATA, CLK, MSBPOST, 16);                               // Get value from register

Where we are sending a read bit (1) followed by 7 bits containing the address of STATUS_REG. Then the shift_in function would display the values in that register. However, the datasheet seems to say that there should be 16 bits. Can someone please explain what I'm missing and if I am generally heading in the right direction?

Thank you,
David

Comments

  • If it helps, I can upload my notes with register address calculations for the hex to binary values and the write data. For example,
    shift_out(DATA, CLK, MSBFIRST, 16, 0b0010000000011111);          // Write CTRL_REG1
    

    corresponds to a write command to address 20h with an output data rate of 100 Hz (00), bandwidth selection of 25 Hz (01), power down mode to normal mode (1), and all axes enabled (111).

    Thank you,
    David
  • I don't have time to look at your code in depth at the moment (I'm going to try and find such time when I get home... no promises), but here's my L3G implementation and here is SRLM's. You may be able to find what you're looking for there before I get back to this tonight.

    The above two implementations are for the L3GD20, but according to this document from ST, the software interface is the same (except for the I2C bus address).
  • Hi David,

    Thank you for your reply. I really appreciate your willingness to help. Since I'm pretty new to programming I'm having a hard time understanding your code. I don't want to copy it because I really want to understand what is going on so I can code my own driver. I may be wrong but it seems more involved than the SPI example on the Parallax Learn site. Is there a simpler way to get data from the gyroscope that is similar to the accelerometer SPI example? I will keep working at it.

    Thank you,
    David
  • I'm actually unable to get my own L3G code to work... been trying to figure that out for the last hour :P
    I'll update you once I have this fixed.
  • Okay, my own bug was simple, and is now fixed. The code I pointed you to was correct, but the demo that I have elsewhere in the code base was incorrect.

    So, your original tthoughts were correct, reading a register is a two-step process: 1) write the register address, 2) read the register value.The datasheet probably assumes you're running an SPI bus in full-duplex mode, where it rotates bits out onto the MOSI line while simultaneously rotating bits in from the MISO line. This, of course, requires separate MISO and MOSI lines. Your example uses a shared MOSI/MISO line, so that's not possible. There's also an issue with that because the Simple library's shift_in and shift_out functions don't do full-duplex SPI (else there'd be only function, called shift :P ).

    Your program is returning 0 after every read, and I'm not sure why. I did modify a couple things, namely changing this
    shift_out(DATA, CLK, MSBFIRST, 8, 10100111);
    

    to this
    shift_out(DATA, CLK, MSBFIRST, 8, 0b10100111);
    
  • Your code appears to have a number of issues. It is possible (though I'm not 100% sure about this) that one of those issues is a deficiency in the shift_out and shift_in methods. Not a bug, but simply something that is unsupported. It doesn't look like those functions support a high clock phase and high clock polarity (SPI Mode 3 according to this wiki page and PropWare's SPI modes).

    Now, with that said, I decided to try out PropWare's SPI class with your code. This would let me confirm whether or not your binary numbers were appropriate. This has been very difficult, however, because I am not intimately familiar with with the L3G's register set. The comments you have to the right of each line are somewhat helpful, but not enough. I'll show you an example of what I'm talking about...

    Where you have a single line to write 16-bits:
    shift_out(DATA, CLK, MSBFIRST, 16, 0b0010000000011111);          // Write CTRL_REG1
    

    I use many, many lines:

    This line is a function invocation...
    this->write8(PropWare::L3G::CTRL_REG1, NIBBLE_0);
    

    And then the write8(...) function is many more lines:
    void write8 (uint8_t registerAddress, const uint8_t registerValue) const {
        uint16_t combinedWord;
    
        // 1
        registerAddress &= ~BIT_7;  // Clear the RW bit (write mode)
    
        // 2
        combinedWord = ((uint16_t) registerAddress) << 8;
        combinedWord |= registerValue;
    
        // 3
        this->m_cs.clear();
        // 4
        this->m_spi->shift_out(16, combinedWord);
        // 5
        this->m_cs.set();
    }
    

    Your code is faster, I'll give you that. But the above lines add a negligble performance hit making it far more obvious what is happening:

    1) Clear the read/write bit (to put it in write mode) which is the MSB of the address byte
    2) Join the address byte and data byte into a single 16-bit word, and let the address byte reside in the high-order byte while the data byte resides in the low-order byte
    3) Clear the CS line (make it low)
    4) Send the data
    5) Set the CS line (make it high)

    This example would be even better if I explained why I'm using "NIBBLE_0" in the first line too. Oh well... maybe I'll fix that now that I'm looking at it...

    Anyway, functions like this are crucial not only so that you can understand your own code in one or two or three months time, but so that your fellow programmers can read your code and offer help.

    So, with that being said, I did what I could to test your code, which isn't a whole lot, but it's still not working:
    high(CS);
    
        SPI spi = SPI::get_instance();
        spi.set_mosi(Pin::P0);
        spi.set_miso(Pin::P1);
        spi.set_sclk(Pin::P2);
    
    
        low(CS);
        spi.shift_out(16, 0b0010000000011111);
        spi.shift_out(16, 0b0010001000001000);
        spi.shift_out(16, 0b0010001110000001);
        high(CS);
    
        while (1) {
            low(CS);
            spi.shift_out(8, 10100111);
    
            const uint16_t rawX = spi.shift_in(16);
            const uint16_t rawY = spi.shift_in(16);
            const uint16_t rawZ = spi.shift_in(16);
    
            pwOut << "X: " << L3G::convert_to_dps(rawX, L3G::DPSMode::DPS_250) << '\t';
            pwOut << "Y: " << L3G::convert_to_dps(rawY, L3G::DPSMode::DPS_250) << '\t';
            pwOut << "Z: " << L3G::convert_to_dps(rawZ, L3G::DPSMode::DPS_250) << '\n';
    
            waitcnt(100 * MILLISECOND + CNT);
        }
    

    This uses the same binary numbers that you were using, but swaps PropWare's SPI class Simple's shift_out and shift_in functions. This eliminates the possibility of an SPI protocol mismatch and gets us down to testing the binary values. Since it still doesn't work, that means your binary numbers are wrong, and that's the whole reason I gave the shpeel above... because I have no idea why your binary numbers are wrong, and I don't really feel like breaking them apart to understand every bit of every byte.

    I also use PropWare::L3G's convert_to_dps static function, which converts the 16-bit values to floating point numbers that represent "degrees per second" so that the numbers printed to the terminal are actually meaningful.

    Finally, I did a quick google search for "l3g propeller parallax" and came up with very few results. It may be that you're the first one to attempt to use the Simple library with the L3G.
  • David,

    Thank you for looking at my code. Do you recommend that I try using I2C instead?
  • If you insist on using the Simple library, then yes, that might work better. But PropWare is readily available and you can either use its SPI object directly (therefore letting you accomplish the same amount of learning) or its L3G object.
Sign In or Register to comment.