Cannot connect to gyro using I2C

I am working on a university satellite team, and we have an issue with our main GNC board which we cannot resolve. Hopefully I can get some input from others on here...

The board in question has three magnetometers (LSM303DLHC) as well as three gyroscopes (FXAS21002C) which are connected across three I2C lines - each line having one mag and one gyro. I've attached a picture of our PCB design for reference.

Just for context: unfortunately due to a short timeline, and component shortages, we were unable to have the gyros and mags placed on the board during the manufacturing process and had to solder them separately by hand after the board was made. Although this was done by someone else at the university who is skilled at soldering, it resulted in a power-ground short, presumably from messy solder. We had these parts desoldered, using fresh components, which fixed the ground-power short, but then found that two of the three I2C lines were now shorted.

The third I2C line, which is not shorted, is able to connect and read from the mag, but the gyro is not returning correct data (the values don't change).

We tried to directly read/write the registers on the gyro, but did not get correct responses. Every register just returned 3F, instead of 1 or 0 or whatever value it should be. We know that the software is not the issue, as it works fine on a gyro breakout board. Scanning the I2C line also does not detect the address to the gyro.

Does anyone have any insight into why the gyros are not working? or why they just return the same value over and over again? The I2C lines are pretty simple, and we've checked all the pin connections, and everything checked out. I am not sure what else the issue could be.

Schematic:



Apologies for the messy naming conventions/organization. I was not the original designer.

PCB Design:


The I2C traces that run off the screen are connected to a molex connector. Traces are all connected to the correct pins on the gyro. It is a 4 layer board that has a ground and power plane.

Sample of Code:
The mag reading is currently commented out from when we were trying to isolate the gyro.

Main

#include "mag_wrap.h"
#include "gyro_wrap.h"
#include <Wire.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.print("begin");
//  initMag(&Mag1);
//  startMag(&Mag1);
  initGyro(&Gyro1);
  startGyro(&Gyro1);
}

void loop() {
  double t0 = micros();

//  Serial.print("Read Mag");
//  readMagData(&Mag1);
//  Serial.print("Mag: ");
//  for (int i = 0; i < 3; i++) {
//    Serial.print(" ");
//    Serial.print(Mag1.magXYZ[i],7);
//  }
//  Serial.println();

  Serial.print("Read Gyro");
  readGyroData(&Gyro1);
  Serial.print("Gyro: ");
  for (int i = 0; i < 3; i++) {
    Serial.print(" ");
    Serial.print(Gyro1.gyroXYZ[i],7);
  }
  Serial.println();
  while (micros() - t0 < 1.0 / 25 * 1e6) {
  }
}

Gyro Functions

/*
 * gyro_wrap.c
 *
 *  Created on: Feb 17, 2021
 *      Author: Alex Zhen
 */


#include "gyro_wrap.h"

// gyroscope struct.
gyro_t Gyro1;


/*!
 * @brief read the value of registers of a gyroscope.
 *
 *
 * @param reg The register want to be read.
 * @param value The variable to hold the value of the register.
 * @param valueSize The size of the value of the register.
 * @param Gyro The gyroscope want to be read.
 * @return void
 *
 */
void readRegs(uint8_t reg, uint8_t *value, uint8_t valueSize, gyro_t * Gyro)
{
#if ARDUINO_CODE
    Wire.beginTransmission(GYRO_ADDRESS);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(GYRO_ADDRESS, valueSize);
    int i = 0;
    while (Wire.available()) {
      *(value+i) = Wire.read();
      i++;
    }
#else
    I2C_request(Gyro->gyroHandle, GYRO_ADDRESS, reg, value, valueSize);

#endif
}

/*!
 * @brief write a value to the registers of a gyroscope.
 *
 *
 * @param reg The register want to be written.
 * @param value The value want to assigned to the register.
 * @param valueSize The size of The value want to assigned to the register.
 * @param Gyro The gyroscope want to be written.
 * @return void
 *
 */
void writeReg(uint8_t reg, uint8_t value, gyro_t * Gyro)
{
#if ARDUINO_CODE
  Wire.beginTransmission(GYRO_ADDRESS);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
#else
  I2C_send(Gyro->gyroHandle, GYRO_ADDRESS, reg, &value, 1);
#endif
}

#if ARDUINO_CODE
/*!
 * @brief Turn on a gyroscope. Initialize all parameters of a gyroscope.
 *
 *
 * @param Gyro The gyroscope wants to be initialized
 * @return void
 *
 */
void initGyro(gyro_t * Gyro)
#else
/*!
 * @brief Turn on a gyroscope. Initialize all parameters of a gyroscope.
 *
 *
 * @param Gyro The gyroscope wants to be initialized.
 * @param gyroHandle The freertos handle of the gyroscope.
 * @param gyroTransfer The transfer information of the gyroscope.
 * @return void
 *
 */
void initGyro(gyro_t * Gyro, lpi2c_rtos_handle_t *gyroHandle)
#endif
{
  if (!Gyro->gyroInitialized)
  {
#if !ARDUINO_CODE
    Gyro->gyroHandle = gyroHandle;
#endif

#if DIFF_TEMP_BIAS_COE
    switch (base_Gyro){
      case LPI2C1:
        static const float gyroBiasValue = {-0.565375, 0.6173333, -0.0121667};
        static const float gyroTempBiasCoeValue = {0.02, 0.02, 0.01};
        static const float gyroTempSensCoeValue = {0.0008, 0.0008, 0.0001};
        Gyro->gyroBias = gyroBiasValue;
        Gyro->gyroTempBiasCoe = gyroTempBiasCoeValue;
        Gyro->gyroTempSensCoe = gyroTempSensCoeValue;
        break;
      case LPI2C2:
        static const float gyroBiasValue = {0, 0, 0};
        static const float gyroTempBiasCoeValue = {0, 0, 0};
        static const float gyroTempSensCoeValue = {0, 0, 0};
        Gyro->gyroBias = gyroBiasValue;
        Gyro->gyroTempBiasCoe = gyroTempBiasCoeValue;
        Gyro->gyroTempSensCoe = gyroTempSensCoeValue;
        break;
      case LPI2C3:
        static const float gyroBiasValue = {0, 0, 0};
        static const float gyroTempBiasCoeValue = {0, 0, 0};
        static const float gyroTempSensCoeValue = {0, 0, 0};
        Gyro->gyroBias = gyroBiasValue;
        Gyro->gyroTempBiasCoe = gyroTempBiasCoeValue;
        Gyro->gyroTempSensCoe = gyroTempSensCoeValue;
        break;
    }
#else

#if COUNT_ZERO_OFFSET
    static const float gyroBiasValue[3] = {-0.565375, 0.6173333, -0.0121667};
    static const float gyroTempBiasCoeValue[3] = {0.02, 0.02, 0.01};
    static const float gyroTempSensCoeValue[3] = {0.0008, 0.0008, 0.0001};
#else
    static const float gyroBiasValue[3] = {.0, .0, .0};
    static const float gyroTempBiasCoeValue[3] = {.0, .0, .0};
    static const float gyroTempSensCoeValue[3] = {.0, .0, .0};
#endif
    memcpy(Gyro->gyroBias,gyroBiasValue, 12);
    memcpy(Gyro->gyroTempBiasCoe,gyroTempBiasCoeValue, 12);
    memcpy(Gyro->gyroTempSensCoe,gyroTempSensCoeValue, 12);
#endif

#if ARDUINO_CODE
    Wire.begin();
#endif
    Gyro->gyroInitialized = 1;
  }
}

/*!
 * @brief Set the gyroscope to desired configurations. Start reading.
 *
 *
 * @param Gyro The gyroscope wants to be set.
 * @return void
 *
 */
void startGyro(gyro_t * Gyro)
{
  if (Gyro->gyroInitialized){
  writeReg(GYRO_CTRL_REG0, GYRO_FSR_NUM, Gyro);
  writeReg(GYRO_CTRL_REG1, (GYRO_ODR_NUM<<2 | 0b10), Gyro);
  }
}

/*!
 * @brief initialize the gyroscope and start the gyroscope's reading. This
 * is the function going to be used on the FSW for starting the gyroscope.
 *
 *
 * @param Gyro The gyroscope wants to be set.
 * @return void
 *
 */
#if ARDUINO_CODE
void quickStartGyro(gyro_t * Gyro)
#else
void quickStartGyro(gyro_t * Gyro, lpi2c_rtos_handle_t *gyroHandle)
#endif
{
#if ARDUINO_CODE
  initGyro(Gyro);
#else
  initGyro(Gyro, gyroHandle);
#endif
  startGyro(Gyro);
}

/*!
 * @brief Read the temperature of a gyroscope.
 *
 *
 * @param Gyro The gyroscope that its temperature want to be read.
 * @return void
 *
 */
void readTempData(gyro_t * Gyro)
{
  uint8_t rawTempData;
  readRegs(GYRO_TEMP, &rawTempData, 1, Gyro);
  Gyro->temperature = (int8_t) rawTempData;
}

/*!
 * @brief Read the angular rates of a gyroscope (x,y,z axes).
 *
 *
 * @param Gyro The gyroscope that its angular rates  want to be read.
 * @return void
 *
 */
void readGyroData(gyro_t * Gyro)
{
  readTempData(Gyro);
  unsigned char rawData[6];  // x/y/z gyro register data stored here
  readRegs(GYRO_OUT_X_MSB,rawData, 6, Gyro);  // Read the six raw data registers into data array


#if COUNT_TEMP_BIAS
  int8_t tempDelta = Gyro->temperature - GYRO_TEMP_0;
#endif
  for (int i = 0; i<3; i++){
    short tempValue = ((short)(((unsigned short)rawData[2*i]) << 8 | ((unsigned short) rawData[2*i + 1])));
#if COUNT_TEMP_BIAS
    Gyro->gyroXYZ[i] = tempValue*GYRO_SENSITIVITY*(1 + (Gyro->gyroTempSensCoe[i])*(int16_t) tempDelta) - (Gyro->gyroBias[i]) - Gyro->gyroTempBiasCoe[i]*(int16_t) tempDelta;
#else
    Gyro->gyroXYZ[i] = ((float) tempValue)*GYRO_SENSITIVITY  - (Gyro->gyroBias[i]);
#endif
  }


}

/*!
 * @brief Reset a gyroscope. The i2c connection won't be reset.
 *
 *
 * @param Gyro The gyroscope to be reset
 * @return void
 *
 */
void resetGyro(gyro_t * Gyro){
  writeReg(GYRO_CTRL_REG1, 0b1000000, Gyro); // set reset bit to 1 to assert software reset to zero at end of boot process

  uint8_t flag;
  readRegs(GYRO_INT_SRC_FLAG, &flag, 1, Gyro);
  while(!(flag & 0x08))  { // wait for boot end flag to be set
      readRegs(GYRO_INT_SRC_FLAG, &flag, 1, Gyro);
  }
}

Gyro Header

/*
 * gyro_wrap.h
 *
 *  Created on: Feb 17, 2021
 *      Author: Alex Zhen
 */

#ifndef GYRO_WRAP_H_
#define GYRO_WRAP_H_

#define ARDUINO_CODE            1

#if ARDUINO_CODE
#include <Wire.h>
#else
#include "fsl_lpi2c.h"
#include "fsl_lpi2c_freertos.h"
#include "peripherals.h"
#endif

#define COUNT_ZERO_OFFSET     0
#define COUNT_TEMP_BIAS       0     // if the code count temperature influence on output
#define MULTI_GYROS         0   // if there are multiple gyroscopes(three)
#define DIFF_TEMP_BIAS_COE      0   // if the gyroscopes have different temperature bias and sensitivity coefficients.


// register addresses FXAS21002C_H_
#define GYRO_OUT_X_MSB        0x01
#define GYRO_CTRL_REG0        0x0D
#define GYRO_TEMP         0x12
#define GYRO_CTRL_REG1        0x13
#define GYRO_INT_SRC_FLAG     0x0B

// gyro parameters

#define GYRO_ODR_NUM        0b101
#define GYRO_FSR_NUM        0b11
#define GYRO_ODR_VALUE        25
#define GYRO_FSR_VALUE        250
#define GYRO_SENSITIVITY      7.8125e-3
#define GYRO_TEMP_0         23
#define GYRO_ADDRESS        (uint8_t)0x20
/*!
 * @brief Structure contains information about one gyroscope
 *
 */
typedef struct _Gyro
{
  float gyroXYZ[3];           /* measured angular rates*/
  int8_t temperature;           /* measured temperature*/
#if !ARDUINO_CODE
  lpi2c_rtos_handle_t * gyroHandle;   /* gyroscope i2c handle?*/
#endif
  float gyroBias[3];            /* gyroscope zero-off set(bias)*/
  float gyroTempBiasCoe[3];       /* gyroscope temperature bias coefficients*/
  float gyroTempSensCoe[3];       /* gyroscope temperature sensitivity coefficients*/
  char  gyroInitialized; /* gyroscope status */
} gyro_t;

extern gyro_t Gyro1;                /* gyroscope 1*/

#if MULTI_GYROS
extern gyro_t Gyro2;
extern gyro_t Gyro3;
#endif


/*!
 * @brief read the value of registers of a gyroscope.
 *
 *
 * @param reg The register want to be read.
 * @param value The variable to hold the value of the register.
 * @param valueSize The size of the value of the register.
 * @param Gyro The gyroscope want to be read.
 * @return void
 *
 */
void readRegs(uint8_t reg, uint8_t *value, uint8_t valueSize, gyro_t * Gyro);

/*!
 * @brief write a value to the registers of a gyroscope.
 *
 *
 * @param reg The register want to be written.
 * @param value The value want to assigned to the register.
 * @param valueSize The size of The value want to assigned to the register.
 * @param Gyro The gyroscope want to be written.
 * @return void
 *
 */
void writeReg(uint8_t reg, uint8_t value, gyro_t * Gyro);


#if ARDUINO_CODE
/*!
 * @brief Turn on a gyroscope. Initialize all parameters of a gyroscope.
 *
 *
 * @param Gyro The gyroscope wants to be initialized
 * @return void
 *
 */
void initGyro(gyro_t * Gyro);
#else
/*!
 * @brief Turn on a gyroscope. Initialize all parameters of a gyroscope.
 *
 *
 * @param Gyro The gyroscope wants to be initialized.
 * @param gyroHandle The freertos handle of the gyroscope.
 * @param gyroTransfer The transfer information of the gyroscope.
 * @return void
 *
 */
void initGyro(gyro_t * Gyro, lpi2c_rtos_handle_t *gyroHandle);
#endif

/*!
 * @brief Set the gyroscope to desired configurations. Start reading.
 *
 *
 * @param Gyro The gyroscope wants to be set.
 * @return void
 *
 */
void startGyro(gyro_t * Gyro);

/*!
 * @brief initialize the gyroscope and start the gyroscope's reading
 *
 *
 * @param Gyro The gyroscope wants to be set.
 * @return void
 *
 */
/*!
 * @brief initialize the gyroscope and start the gyroscope's reading. This
 * is the function going to be used on the FSW for starting the gyroscope.
 *
 *
 * @param Gyro The gyroscope wants to be set.
 * @return void
 *
 */
#if ARDUINO_CODE
void quickStartGyro(gyro_t * Gyro);
#else
void quickStartGyro(gyro_t * Gyro, lpi2c_rtos_handle_t *gyroHandle);
#endif


/*!
 * @brief Read the temperature of a gyroscope.
 *
 *
 * @param Gyro The gyroscope that its temperature want to be read.
 * @return void
 *
 */
void readTempData(gyro_t * Gyro);

/*!
 * @brief Read the angular rates of a gyroscope (x,y,z axes).
 *
 *
 * @param Gyro The gyroscope that its angular rates  want to be read.
 * @return void
 *
 */
void readGyroData(gyro_t * Gyro);

/*!
 * @brief Reset a gyroscope. The i2c connection won't be reset.
 *
 *
 * @param Gyro The gyroscope to be reset
 * @return void
 *
 */
void resetGyro(gyro_t * Gyro);

#endif /* GYRO_WRAP_H_ */

It there are only two I2C lines not three. Your schematic is not very clear on this matter.

All we need is a schematic no need for board layouts.
Each of the two I2C lines needs a pull-up resistor to the processor's supply.
If your devices run off lower voltages than your processor then you need a level shifter.

I'll update the post with a schematic. Apologies if it was not clear, but there are 3 sets of I2C connections (1 for each pair of mag and gyro) - 'lines' probably wasn't the best word choice here. Each has its own SDA and SCL traces, and the photo provided only shows one of those connections, as the other ones do not have gyros attached.

What is on the other side of the cable ?
Do you know that the I2C bus is not designed to go through a cable ?
If that cable is 10cm or 20m long, then it might be okay.
Is the 52 pin connector used to attach this board to the processor board ? Which processor is it ?

How does that work then?
What processor are you using?
I don't know of a processor with three I2C pairs of lines. There are a few with two but not three that I know of.

You are not making the mistake that as on an Arduino the I2C lines are A4 & A5, therefore you can use A0 & A1 along with A2 & A3 as I2C lines are you
.

Our main computer is designed around an NXP MIMXRT1021DAG5A, which has four I2C modules, which is somewhat aside the point as we are just using Arduino's for preliminary testing. We also are only running one I2C connection to test, not all three.

We have tested an off the shelf gyro breakout and it works fine, so we are a bit puzzled why we can only get a response out of the mag on this I2C.

We are using a pc104 to connect this board to our main computer developed on an NXP MIMXRT1021DAG5A. It is a 2U CubeSat which is quite small, and cables are not long.

Which is not an Arduino, this is an Arduino forum.

Would have been good to mention that at the start of this thread instead of dragging in erroneous facts.

Still waiting for that schematic. It will enable us to see where you screwed up.

How can you even create a PCB without a schematic? That is something I never let my undergraduate University students do.

@mitchhambling, by changing your top post, you kind of broke the flow of the topic. That is not big deal, but you could have mentioned it.

Which Arduino board do you use for testing ?
I hope you use a 3.3V Arduino board.

As far as I can tell, everything is normal, but I'm not sure about the molex connector.
It is probably not a gyro problem, but a bad soldering or damaged sensor.

You don't have to try a library. Run a I2C Scanner sketch, and if it is not detected, then it is not there.

A broader view and specific details are always important for us. It is normal that we ask a lot of things that might not seem relevant, but they are.

I have not designed a PCB yet by myself, so I was hoping that someone else mentions the very small spacing in a number of places in the layout. A very small gap between solder pads for a chip is not okay.

I appreciate you taking the time to help. It is always nice to get thoughtful questions.

The components in your schematic, like resistors & caps, don't show their values, just some long and, to us, meaningless part number. I see pull up resistors on the i²c lines, what values are those?

SAMD21 could be configured to have 3, I believe, using 3 of its SERCOM modules. Also rp2040, maybe?

We are using an uno and sometimes an arduino mega. The bad soldering is definitely a possibility, but I wanted to double check with the Arduino forum just in case theres not something else I might not have considered. Theres a few other similar posts, but they didnt get too far.

Apologies for editing the top post also - I wanted to include the schematic for others to see easily.

and yeah I agree, theres a lot of design considerations that aren't great. It's a bit tough to come into a project and pick up work that other students have started, but thats the unfortunate reality here lol.

The Uno and Mega have a 5V I2C bus and the Mega has 10k pullup resistors to 5V. That is enough to damage the sensors (the 4k7 pullup might prevent that, but I'm not sure).

Pullup resistors are 4k7, the RS73G1JTTD4701B at mouser.

They are 4.7K Ohm resistors, which is recommended in the gyro reference manual.

Super sorry about the schematic organization, im not sure why the original designer didn't use proper labeling/netting... ill try my best to clarify

A component will be soldered on top of the pads and a trace running under the component ?
afbeelding

Is this small spacing under the chip pins a design mistake ?
afbeelding

Unfortunately yeah, there are a lot of design flaws and we are kind of stuck with it for now. I am just trying to make the most out of what I walked into.

If by small space you mean the trace that is not lined up correctly to the pad, then yeah it is a design mistake. Although, since its on the layer under the solder mask it shouldnt be a big issue. Same goes for the trace under the component.

The gyro is rated for over 5V so that wouldnt be an issue would it? although the gyro is still powered by 3v

No that has only got two, but it is available on a variety of pin but only two busses imaginative called 0 and 1.

As to the schematic it is too blurry to make out much. Looks like this bird will never fly, I must say you chose quite a poor University to attend. The schematic should have been sorted out in a schematic review before it was even made into a PCB.

It would be if the voltage rating was an absolute value and not a working voltage. Absolute values are a stress rating and not a grantees working voltage.

I take back my previous comment. It seems that the gyro is rated for a max interface input of 3.9V, not 5. I was also not aware that the uno is 5V logic, so in that case it would make sense why the gyro isnt working I suppose

good thing its not a bird! maybe it will still have a chance :smiley:

Can you prove that it is 3.9V ?

The Arduino Uno has only internal pullup resistors of 30k (30k or 50k or 60k or so, I forgot what it was). If your 4k7 are always connected to 3.3V, then it can cause no harm.
Using a 3.3V Arduino board of a I2C level shifter is better.

I suggest to try to verify all connections with a multimeter and perhaps a sensor is damaged, for example during soldering, or perhaps the 3.3V was too strong and caused damaged with a shortcut somewhere.
As I wrote before, everything is normal as far as I can tell. I'm afraid there is not much we can do.

1 Like