SPI is almost 10x slower then it suppose to be :/

Hello to all and thank you for any advices in advance.

I am attempting to pull 510 bytes from FIFO buffer of MPU9250 sensor. I know from its datasheet that it is capable of comunicating up to 20MHz. I am using Arduino Uno so max. SPI speed is 8MHz for me. But from my calculations transfering 510 bytes from FIFO shoul take:
(1/8000000)*510 = 63 microsec. but from my measurement which is illustrated in the code below I get 1124 microsec. So I tried different speeds and none was right. Here is a table

SPI spd| usec | real calculated Freq.
1Mhz | 4700 | 108510
2MHz | 2656 | 192018
4MHz | 1636 | 311735
8MHz | 1124 | 453736
16MHz| 1132 | 450530

I Tried even 16MHz just for fun. I just do not know how it is possible that the transfer speeds are so slow??

Any Help apprecitated.
Thank you very much :slight_smile:

P.S.: The code is not complete, there are a lot of variables unused because it is part of bigger program witch does not affect this issue and would only cluter the code more. But all used methodes are declared and defined. The program jst waits until the sensor has 510 bytes in buffer and I am very sure they are really there when beginning the transfer. Then e just attemt to transfer them to our array... and that takes that long..

#include "SPI.h"
#include "MPU9250.h"

// pins
byte CS_PIN = 3;

 //Flags
 byte READ_FLAG = 0x80;

 //SRAM
 byte SRAM_CS_PINS[] = {4,5,7,6};
 byte numOfSrams = sizeof(SRAM_CS_PINS);
 byte SRAM_READ = 0x03;
 byte SRAM_WRITE = 0x02;

 //SRAM register addresses
 byte RDMODE = 0x05;
 byte WTMODE = 0x01;

 //SRAM config values
 uint32_t SPISPEED = 8000000;
 byte PAGEMODE = 0x80;
 byte SEQMODE = 0x40;
 byte BYTEMODE = 0;

 //SRAM variables
 byte currentSram;
 byte sramData[32];
 byte in[1];
 byte sramAddress[4];
 byte byteArray[4];
 uint32_t lastAddress = 0x1FFFF;
 
 //Registers
 byte CONFIG = 0x1A;
 byte ACCEL_CONF_2 = 0x1D;
 byte WHO_AM_I = 0x75;
 byte USER_CTRL = 0x6A;
 byte INT_PIN = 0x37;
 byte PWR_MGMT_2 = 0x6C;
 byte SAMPLE_DIV = 0x19;

 // FIFO registers
 byte FIFO_EN = 0x23;
 byte FIFO_COUNTH = 0x72;   // first read High bits it latches low bits for read
 byte FIFO_COUNTL = 0x73;
 byte FIFO_R_W = 0x74;
 byte FIFO_MODE = 0x1A;
 
 //Register Vals.
 byte ACCEL_FCHOICE_B = 0x08;
 byte I2C_IF_DIS = 0x10;
 byte BYPASS_EN = 0x02;
 byte LN_MODE = 0x07;
 byte F_5HZ = 0xC7;
 byte F_10HZ = 0x63;
 byte F_100HZ = 0x09;
 
 // FIFO register vals.
 byte FIFO_ACCEL = 0x08;
 byte FIFO_NO = 0;

//variables
uint32_t sramCurrentAddress = 0;
uint16_t currentFifoByteCount = 0;
uint16_t savedFifoByteCount = 0;
int16_t fifoState;
int16_t x,y,z;
int32_t spi_low_speed = 1000000;
int32_t spi_high_speed = 8000000;
int status;
bool fifoFlag = false;
bool saveDataFlag = false;
bool newFifoDataFlag = false;
bool sramFullFlag = false;
// buffers
int8_t regData[2];
int8_t data[7];
byte fifoDataBuffer[510];
int16_t accel[3];
byte fifoLoadedData[6];
byte byteSizedData[10];
byte byteSizedAnalogData[10];
byte byteSizedLoadedData[10];
uint32_t t;
uint16_t samplesCount = 0;
int T = 500;
bool timerFlag = false;
int32_t t_old, t_new;
int16_t analogX,analogY,analogZ;
int16_t q = 1;
// an MPU9250 object with the MPU-9250 sensor on SPI bus 0 and chip select pin 10
MPU9250 IMU(SPI,CS_PIN);

void setup(){
  SPI.begin();
  Serial.begin(1000000); // So it is able to transfer 4000*3*16 = 192 000 bit/s
  status = IMU.mybegin();
  
    if (status < 0) {
      Serial.println(F("IMU initialization unsuccessful"));
      Serial.println(F("Check IMU wiring or try cycling power"));
      Serial.print(F("Status: "));
      Serial.println(status);
      while(1) {}
    }
  // resetting FIFO to empty at the beginning
  // fifoReset();
  Serial.println(F("Start"));
}
void loop(){
  // checks how many bytes are in FIFO buffer
  byte hbyte,lbyte;
  int16_t count;
  readRegister(FIFO_COUNTH,1,&hbyte,1000000);
  readRegister(FIFO_COUNTL,1,&lbyte,1000000);
  count = hbyte << 8 | lbyte;
  currentFifoByteCount = count;
  
  // sets flag when 510 bytes are present in FIFO
  if(savedFifoByteCount < currentFifoByteCount){
      savedFifoByteCount = currentFifoByteCount;
      if (currentFifoByteCount == 510){
        newFifoDataFlag = true;
      }
  }
  
  if (newFifoDataFlag){
    SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE3));
    digitalWrite(CS_PIN,LOW); // select the MPU9250 chip
    SPI.transfer(FIFO_R_W | 0x80); // specify the starting register address
      t_old = micros();
      for(uint16_t i = 0; i < 510; i++){
        fifoDataBuffer[i] = SPI.transfer(0x00); // read the data
      }
      t_new = micros() - t_old;
      digitalWrite(CS_PIN,HIGH); // deselect the MPU9250 chip
      SPI.endTransaction(); // end the transaction
    Serial.println(t_new);
    while(1);
  }
}

int readRegister(uint8_t subAddress, uint16_t count, uint8_t* dest, int32_t spi_speed){
    SPI.beginTransaction(SPISettings(spi_speed, MSBFIRST, SPI_MODE3));
    digitalWrite(CS_PIN,LOW); // select the MPU9250 chip
    SPI.transfer(subAddress | 0x80); // specify the starting register address
    for(uint16_t i = 0; i < count; i++){
      dest[i] = SPI.transfer(0x00); // read the data
    }
    digitalWrite(CS_PIN,HIGH); // deselect the MPU9250 chip
    SPI.endTransaction(); // end the transaction
    return 1;
}

int writeRegister(uint8_t subAddress, uint8_t data, int32_t spi_speed){
  /* write data to device */
    SPI.beginTransaction(SPISettings(spi_speed, MSBFIRST, SPI_MODE3)); // begin the transaction
    digitalWrite(CS_PIN,LOW); // select the MPU9250 chip
    SPI.transfer(subAddress); // write the register address
    SPI.transfer(data); // write the data
    digitalWrite(CS_PIN,HIGH); // deselect the MPU9250 chip
    SPI.endTransaction(); // end the transaction
    
    // Now test if write was succesfull
    delayMicroseconds(10);
    byte writtenData;
    /* read back the register */
    readRegister(subAddress,1,writtenData,spi_speed);
    /* check the read back register against the written register */
    if(writtenData == data) {
      return 1;
    }
    else{
      return -1;
    }
}

A byte has 8 bits.

Indeed, and there is overhead to the entire transaction, which approximately accounts for the reported factor of 10x.

Delta_G:
That would be the time for 510 bits. A byte has 8 bits.

Oh my god! Is it possible that I am that dumb!
Of course.
Thank you! :slight_smile:

jremington:
Indeed, and there is overhead to the entire transaction, which approximately accounts for the reported factor of 10x.

Can I ask you what type of overhead is there? Is there a way to emilinate it? Or make it faster?
Thank you fir your answers once more!

1 Like

bobes131:
Can I ask you what type of overhead is there?

The code has to read a byte from the SPI hardware and transfer it to your memory buffer, this code does takes time to run.

bobes131:
Can I ask you what type of overhead is there? Is there a way to eliminate it? Or make it faster?

Example-1 of overhead codes
In this example UNO (Master) and NANO (Slave) are exchanging 0x32 and 0x67 at 500 kbits/sec. The data exchange time should be theoretically 16 us (10-3/5001[bbc]8[/bbc]); but, practically it is greater than 16 us due to overhead codes that check the 'data readiness bit' -- the SPIF bit of SPSR Register for HIGH state (Fig-1).

spi328x.png
Figure-1:

Master SPI UNO Codes

#include <SPI.h>

void setup (void)
{
  Serial.begin(115200);
  SPI.begin ();
  digitalWrite(SS, LOW);    //Slave is selected
  SPI.setClockDivider(SPI_CLOCK_DIV32);  //500 Kbits/sec
}

void loop()
{
  SPDR = 0x32;
  while(bitRead(SPSR, SPIF) != HIGH)
  {
    ;   //wait and check the SPIF bit for HIGH state
  }
  byte x = SPDR;
  Serial.print(x, HEX);  //shows: 0x97
  while(1);
}

Slave SPI NANO Codes:

#include <SPI.h>

void setup ()
{
  Serial.begin(115200);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= !(_BV(MSTR)); //Arduino is Slave
  SPDR = 0x67;  //test value
}

void loop()
{
  while (bitRead(SPSR, SPIF) != HIGH)
  {
    ;   //wait and check the SPIF bit for HIGH state
  }
  byte x = SPDR;
  Serial.print(x, HEX);  //shows: 0x32
  while (1);
}

Screen shot: Master
smspiS.png

Screen shot: Slave
smspiM.png

Example-2 of overhead codes
In this example UNO (Master) is requesting NANO (Slave) by sending command byte 11 to get 3-byte data at 500 kbits/sec. The data exchange time should be theoretically 64 us (10-3/50014; but, practically it is greater than 64 us due to overhead codes that are related to Slave for responding to SPI-interrupt, checking the index, updating the SPDR with new data etc.

Master Codes:

#include <SPI.h>
byte myData[] = {11, 0x00, 0x00, 0x00};
void setup (void)
{
  Serial.begin(115200);
  SPI.begin ();
  digitalWrite(SS, LOW);    //Slave is selected
  SPI.setClockDivider(SPI_CLOCK_DIV32);  //500 Kbits/sec
  delay(100);
  //-----------------------------------------
  for (int i = 0; i < 4; i++)
  {
    myData[i] = SPI.transfer(myData[i]);
    delay(1);
  }
  Serial.print("Received data in response of 11: ");
  Serial.print(myData[1], DEC);
  Serial.print(" and ");
  Serial.println((myData[2] << 8 | myData[3]), DEC);
}

void loop()
{

}

Slave Codes:

#include <SPI.h>
volatile bool flag1 = false, flag2 = false;
byte myData[] = {0xFA, 0x01, 0x0E};
int i = 0;
byte x;

void setup ()
{
  Serial.begin(115200);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= !(_BV(MSTR)); //Arduino is Slave
  SPCR |= _BV(SPIE); //SPI.attachInterrupt();
  sei();
  SPDR = 0x67;  //test value
  // delay(100);
}

void loop()
{

}

ISR(SPI_STC_vect)
{
  if (flag1 == false)
  {
    x = SPDR;
    if (x == 11)
    {
      flag1 = true;
    }
  }
  SPDR = myData[i];
  i++;
  if (i == 3)
  {
    flag1 = false;
    i = 0;
  }
}

smspiM.png

smspiS.png

spi328x.png

Can I ask you what type of overhead is there?

It is required to set addresses, and use handshake transactions on the bit level (acknowledge) for each byte transferred. And, as noted, the byte must be transferred to the user buffer.

Edit: Oops, wrong diagram posted! See "latency" note in lower right corner (from http://ww1.microchip.com/downloads/en/devicedoc/61106g.pdf)

jremington:
It is required to set addresses, and use handshake transactions on the bit level (acknowledge) for each byte transferred.

I'm not that familiar with SPI, but what you showed is I2C.

You may want to explicitly disable the MPU925X's magnetometer as part of your MPU setup; the MPU925X's magnetometer is on a I2C buss.