sparkfun/SparkFun_ICM-20948_ArduinoLibrary

Unable to connect three IMU using SPI protocol

JacksonLtt opened this issue · 14 comments

Hello,

Subject of the issue

Unable to connect three IMU using SPI protocol

Your workbench

  • What platform are you using?
    I use Arduino IDE to program the code.

  • What version of the device are you using? Is there a firmware version?
    I use Adafruit NRF52832 as microcontroller.
    ICM20948 by Sparkfun works as IMU sensor.
    I use Example1_Basics as basic code to control three IMUs with NRF52832.

  • How is the device wired to your platform?

NRF52832 IMU#1 IMU#2 IMU#3
PIN28 MISO MISO MISO
PIN27 MOSI MOSI MOSI
PIN26 SCK SCK SCK
CS PIN# 2 3 4
GND GND GND GND
3.3V VIN VIN VIN
  • How is everything being powered?
    I used 3.3V to power all imu. The total current of three imus is 10 mA.

  • Are there any additional details that may help us help you?
    I have checked all three IMUs. Each of them works well. The current limitation of 3.3V 400 mA.
    Here is my source code: nrf_six_imu_offline.ino

Steps to reproduce

Tell us how to reproduce this issue. Please post stripped down example code demonstrating your issue to a gist.
I CAN successfully read data from two IMUs if I only connect TWO IMUs and initial THREE IMUs in the code.
Code:

  init_icm(ICM20948_Sensor[0], 0);
  init_icm(ICM20948_Sensor[1], 1);
  init_icm(ICM20948_Sensor[2], 2);

Here is output:
image

Here is how I connect devices:
95efdd1d9fd0d6430287417a8980537

I CANNOT successfully read data from two IMUs if I only connect THREE IMUs and initial THREE IMUs in the code.
Code:

  init_icm(ICM20948_Sensor[0], 0);
  init_icm(ICM20948_Sensor[1], 1);
  init_icm(ICM20948_Sensor[2], 2);

Here is the output:
image

Here is how I connect devices:
e74b65fd6a014ef03bb3bf53def670e

Expected behaviour

Tell us what should happen
If I connect three IMUs, I should be able to read data from IMUs.

Actual behaviour

Tell us what happens instead
When I connect three IMUs, I should be able to read data from IMUs, though I have successfully read data from two IMUs if I only connect and initial two IMUs only.

I guess microcontroller could not identify ID of IMUs.

Hello Jackson (@JacksonLtt ),

Does your six_imu_spi_dmp code work?

I wonder if you are running out of memory? The DMP image will - I think - be included for each individual IMU.

Please try a test without the DMP and see if that works.

Best wishes,
Paul

Hello Paul,

Thank you for your advice. I found I did not run DMP.
I already comment out line 29.

Even i comment out line29, I could not read data from three IMU.

I think the issue of being unable using three IMUs by SPI is because the processor could not configer correct CS_pin to corresponding IMUs.

Hello Taiting,

I do not have an Adafruit NRF52832 so I cannot replicate your hardware. Do you have another SparkFun board you could test?

Best wishes,
Paul

do you mean other sparkfun microcontroller board?

Hello Paul,

If you donot mind, i could mail a nrf52832 to you for free. This project is very meaningful for me. I so appreciate your help.

Hello Taiting,

I have tested three IMU's connected to a SparkFun ESP32 microcontroller and it works well (see below). All I can suggest is that you try a different microcontroller board.

Best wishes,
Paul

image

#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU

#define SERIAL_PORT Serial

#define SPI_PORT SPI // Your desired SPI port.
#define CS_PIN_1 14   // Which pin you connect CS to for Sensor 1
#define CS_PIN_2 27   // Which pin you connect CS to for Sensor 2
#define CS_PIN_3 5    // Which pin you connect CS to for Sensor 3

ICM_20948_SPI myICM1; // Create an ICM_20948_SPI object
ICM_20948_SPI myICM2; // Create an ICM_20948_SPI object
ICM_20948_SPI myICM3; // Create an ICM_20948_SPI object

void setup()
{

  SERIAL_PORT.begin(115200);
  while (!SERIAL_PORT)
  {
  };

  SPI_PORT.begin();

  myICM1.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial
  myICM2.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial
  myICM3.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial

  bool initialized = false;
  while (!initialized)
  {

    myICM1.begin(CS_PIN_1, SPI_PORT, 7000000);

    SERIAL_PORT.print(F("Initialization of sensor 1 returned: "));
    SERIAL_PORT.println(myICM1.statusString());
    if (myICM1.status != ICM_20948_Stat_Ok)
    {
      SERIAL_PORT.println(F("Trying again..."));
      delay(500);
    }
    else
    {
      initialized = true;
    }
  }

  initialized = false;
  while (!initialized)
  {

    myICM2.begin(CS_PIN_2, SPI_PORT, 7000000);

    SERIAL_PORT.print(F("Initialization of sensor 2 returned: "));
    SERIAL_PORT.println(myICM1.statusString());
    if (myICM2.status != ICM_20948_Stat_Ok)
    {
      SERIAL_PORT.println(F("Trying again..."));
      delay(500);
    }
    else
    {
      initialized = true;
    }
  }

  initialized = false;
  while (!initialized)
  {

    myICM3.begin(CS_PIN_3, SPI_PORT, 7000000);

    SERIAL_PORT.print(F("Initialization of sensor 3 returned: "));
    SERIAL_PORT.println(myICM1.statusString());
    if (myICM2.status != ICM_20948_Stat_Ok)
    {
      SERIAL_PORT.println(F("Trying again..."));
      delay(500);
    }
    else
    {
      initialized = true;
    }
  }
}

bool dataSeen = false;

void loop()
{

  if (myICM1.dataReady())
  {
    myICM1.getAGMT(); // The values are only updated when you call 'getAGMT'
    SERIAL_PORT.print("Sensor 1: ");
    //printRawAGMT( myICM1.agmt);
    printScaledAGMT(&myICM1);
    dataSeen = true;
  }
  if (myICM2.dataReady())
  {
    myICM2.getAGMT(); // The values are only updated when you call 'getAGMT'
    SERIAL_PORT.print("Sensor 2: ");
    //printRawAGMT( myICM2.agmt );
    printScaledAGMT(&myICM2);
    dataSeen = true;
  }
  if (myICM3.dataReady())
  {
    myICM3.getAGMT(); // The values are only updated when you call 'getAGMT'
    SERIAL_PORT.print("Sensor 3: ");
    //printRawAGMT( myICM3.agmt );
    printScaledAGMT(&myICM3);
    dataSeen = true;
  }
  if (dataSeen)
  {
    delay(30);
    dataSeen = false;
  }
  else
  {
    SERIAL_PORT.println("Waiting for data");
    delay(500);
  }
}

// Below here are some helper functions to print the data nicely!

void printPaddedInt16b(int16_t val)
{
  if (val > 0)
  {
    SERIAL_PORT.print(" ");
    if (val < 10000)
    {
      SERIAL_PORT.print("0");
    }
    if (val < 1000)
    {
      SERIAL_PORT.print("0");
    }
    if (val < 100)
    {
      SERIAL_PORT.print("0");
    }
    if (val < 10)
    {
      SERIAL_PORT.print("0");
    }
  }
  else
  {
    SERIAL_PORT.print("-");
    if (abs(val) < 10000)
    {
      SERIAL_PORT.print("0");
    }
    if (abs(val) < 1000)
    {
      SERIAL_PORT.print("0");
    }
    if (abs(val) < 100)
    {
      SERIAL_PORT.print("0");
    }
    if (abs(val) < 10)
    {
      SERIAL_PORT.print("0");
    }
  }
  SERIAL_PORT.print(abs(val));
}

void printRawAGMT(ICM_20948_AGMT_t agmt)
{
  SERIAL_PORT.print("RAW. Acc [ ");
  printPaddedInt16b(agmt.acc.axes.x);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.acc.axes.y);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.acc.axes.z);
  SERIAL_PORT.print(" ], Gyr [ ");
  printPaddedInt16b(agmt.gyr.axes.x);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.gyr.axes.y);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.gyr.axes.z);
  SERIAL_PORT.print(" ], Mag [ ");
  printPaddedInt16b(agmt.mag.axes.x);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.mag.axes.y);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.mag.axes.z);
  SERIAL_PORT.print(" ], Tmp [ ");
  printPaddedInt16b(agmt.tmp.val);
  SERIAL_PORT.print(" ]");
  SERIAL_PORT.println();
}

void printFormattedFloat(float val, uint8_t leading, uint8_t decimals)
{
  float aval = abs(val);
  if (val < 0)
  {
    SERIAL_PORT.print("-");
  }
  else
  {
    SERIAL_PORT.print(" ");
  }
  for (uint8_t indi = 0; indi < leading; indi++)
  {
    uint32_t tenpow = 0;
    if (indi < (leading - 1))
    {
      tenpow = 1;
    }
    for (uint8_t c = 0; c < (leading - 1 - indi); c++)
    {
      tenpow *= 10;
    }
    if (aval < tenpow)
    {
      SERIAL_PORT.print("0");
    }
    else
    {
      break;
    }
  }
  if (val < 0)
  {
    SERIAL_PORT.print(-val, decimals);
  }
  else
  {
    SERIAL_PORT.print(val, decimals);
  }
}

void printScaledAGMT(ICM_20948_SPI *sensor)
{
  SERIAL_PORT.print("Scaled. Acc (mg) [ ");
  printFormattedFloat(sensor->accX(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->accY(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->accZ(), 5, 2);
  SERIAL_PORT.print(" ], Gyr (DPS) [ ");
  printFormattedFloat(sensor->gyrX(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->gyrY(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->gyrZ(), 5, 2);
  SERIAL_PORT.print(" ], Mag (uT) [ ");
  printFormattedFloat(sensor->magX(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->magY(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->magZ(), 5, 2);
  SERIAL_PORT.print(" ], Tmp (C) [ ");
  printFormattedFloat(sensor->temp(), 5, 2);
  SERIAL_PORT.print(" ]");
  SERIAL_PORT.println();
}

I was using the SparkFun MicroMod Asset Tracker because it has an ICM20948 on-board. I was using the MicroMod ESP32 Processor. I connected two ICM20949 breakout boards - the same as yours - to the SPI/D0/D1 pins. I only have two breakout boards - this was the only way I could test three sensors simultaneously.

I can recommend the ESP32 WROOM Thing Plus.

Hello Paul,

I will try to use Sparkfun ESP32-Plus as you recommend.

I want to use NRF52832 because it has MUCH lower power BLE consumption than ESP32.

I found documentation NRF52832 about SPI.
According to documentation, it only supports 3 x Hardware SPI master, 3 x Hardware SPI slave.

Does it mean the maximum number of SPI slaves that I could connect is three? In other words, I am only able to connect 2 IMUs only?

Hi Jackson,

We use the terms SPI Controller and Peripheral. The old terminology is offensive.

The documentation means that the NRF52832 can operate up to three hardware SPI ports simultaneously. But you can connect many IMU's to a single SPI port. The only limitation is how many Chip Select lines you have available.

Just because it is nearly Christmas, I tried a test for you where I instantiate multiple IMU's - all using the same SPI port and all using the same CS pin. It is really just a test of how many instantiations a board can support before it runs out of memory. See below for the code.

I tested it on an ATmega328P board - which has only 2 kBytes of RAM - and it could support up to 12 IMU's before it ran out of memory. That was with support for the DMP disabled. With support for the DMP enabled, the memory use will be much higher. On the ESP32, I seem to be able to create 500+ instantiations without difficulty...

The code may help you diagnose what is happening on your board.

That is all the help I can give you. I need to get back to my other work. If you need more help, please ask in the IMU Forum. Someone there may be using the NRF52832 and may know how to solve your issue.

Best wishes,
Paul

/****************************************************************
 * Example11_MultipleSPITest.ino
 * ICM 20948 Arduino Library Demo
 * Use the default configuration to stream 9-axis IMU data on multiple IMUs over SPI
 * Paul Clark @ SparkFun Electronics
 * Original Creation Date: Dec 18th 2021
 * 
 * You can use a single IMU with a single CS pin if you want to!
 * This is really just a test of how many instantiations of the IMU your platform can support.
 *
 * Please see License.md for the license information.
 *
 * Distributed as-is; no warranty is given.
 ***************************************************************/
#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU

#define SERIAL_PORT Serial

#define SPI_PORT SPI // Your desired SPI port.
#define CS_PIN 21    // SPI Chip Select

#define NUMBER_OF_SENSORS 500 //The number of IMU instantiations

ICM_20948_SPI *ICM20948_Sensor[NUMBER_OF_SENSORS]; //Create a set of pointers to the sensor class

void setup()
{

  SERIAL_PORT.begin(115200);
  while (!SERIAL_PORT)
  {
  };

  SPI_PORT.begin();

  for (int x = 0; x < NUMBER_OF_SENSORS; x++)
  {
    ICM20948_Sensor[x] = new ICM_20948_SPI();

    bool initialized = false;
    while (!initialized)
    {
      ICM20948_Sensor[x]->begin(CS_PIN, SPI_PORT);
  
      SERIAL_PORT.print(F("Initialization of sensor "));
      SERIAL_PORT.print(x);
      SERIAL_PORT.print(F(" returned: "));
      SERIAL_PORT.println(ICM20948_Sensor[x]->statusString());
      if (ICM20948_Sensor[x]->status != ICM_20948_Stat_Ok)
      {
        SERIAL_PORT.println(F("Trying again..."));
      }
      else
      {
        initialized = true;
      }
      delay(250);
    }
  }
}

void loop()
{
  for (int x = 0; x < NUMBER_OF_SENSORS; x++)
  {
    if (ICM20948_Sensor[x]->dataReady())
    {
      ICM20948_Sensor[x]->getAGMT(); // The values are only updated when you call 'getAGMT'
      SERIAL_PORT.print("Sensor ");
      SERIAL_PORT.print(x);
      SERIAL_PORT.print(": ");
      printScaledAGMT(ICM20948_Sensor[x]);
    }
    delay(30); // Make sure fresh data will be available the next time we call dataReady
  }
}

// Below here are some helper functions to print the data nicely!

void printFormattedFloat(float val, uint8_t leading, uint8_t decimals)
{
  float aval = abs(val);
  if (val < 0)
  {
    SERIAL_PORT.print("-");
  }
  else
  {
    SERIAL_PORT.print(" ");
  }
  for (uint8_t indi = 0; indi < leading; indi++)
  {
    uint32_t tenpow = 0;
    if (indi < (leading - 1))
    {
      tenpow = 1;
    }
    for (uint8_t c = 0; c < (leading - 1 - indi); c++)
    {
      tenpow *= 10;
    }
    if (aval < tenpow)
    {
      SERIAL_PORT.print("0");
    }
    else
    {
      break;
    }
  }
  if (val < 0)
  {
    SERIAL_PORT.print(-val, decimals);
  }
  else
  {
    SERIAL_PORT.print(val, decimals);
  }
}

void printScaledAGMT(ICM_20948_SPI *sensor)
{
  SERIAL_PORT.print("Scaled. Acc (mg) [ ");
  printFormattedFloat(sensor->accX(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->accY(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->accZ(), 5, 2);
  SERIAL_PORT.print(" ], Gyr (DPS) [ ");
  printFormattedFloat(sensor->gyrX(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->gyrY(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->gyrZ(), 5, 2);
  SERIAL_PORT.print(" ], Mag (uT) [ ");
  printFormattedFloat(sensor->magX(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->magY(), 5, 2);
  SERIAL_PORT.print(", ");
  printFormattedFloat(sensor->magZ(), 5, 2);
  SERIAL_PORT.print(" ], Tmp (C) [ ");
  printFormattedFloat(sensor->temp(), 5, 2);
  SERIAL_PORT.print(" ]");
  SERIAL_PORT.println();
}

Hello Paul,

Thank you for your reply.
Do you have experience of how to create two SPI ports in NRF52?

Best wishes,
Taiting