Go Down

Topic: Faster SPI on the Zero? (Read 11024 times) previous topic - next topic

scswift

#30
Nov 10, 2015, 12:57 am Last Edit: Nov 10, 2015, 12:58 am by scswift
At the beginning of sd2card.h I modified

Code: [Select]
static SPISettings settings;
to
Code: [Select]
static SPISettings settings(12000000, MSBFIRST, SPI_MODE0);

and I changed the default speed to SPI_FULL_SPEED like you. It's strange that our results are diferents...
There is no "static SPISettings settings;" in sd2card.h.  Did you mean sd2card.cpp?

Here's what the first few lines of mine looks like:

Code: [Select]

#define USE_SPI_LIB
#include <Arduino.h>
#include "Sd2Card.h"
//------------------------------------------------------------------------------
#ifndef SOFTWARE_SPI
#ifdef USE_SPI_LIB
#include <SPI.h>
static SPISettings settings;
#endif



What version of the IDE are you running?  A nightly build perhaps?  I'm using the regular old 1.6.6 and I've downloaded the latest board files for the Zero.


 The line you changed relates to this class in SPI.h:

Code: [Select]

class SPISettings {
  public:
  SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {
    if (__builtin_constant_p(clock)) {
      init_AlwaysInline(clock, bitOrder, dataMode);
    } else {
      init_MightInline(clock, bitOrder, dataMode);
    }
  }

  // Default speed set to 4MHz, SPI mode set to MODE 0 and Bit order set to MSB first.
  SPISettings() { init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); }

  private:
  void init_MightInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {
    init_AlwaysInline(clock, bitOrder, dataMode);
  }

  void init_AlwaysInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) __attribute__((__always_inline__)) {
    this->clockFreq = (clock >= (F_CPU / SPI_MIN_CLOCK_DIVIDER) ? F_CPU / SPI_MIN_CLOCK_DIVIDER : clock);

    this->bitOrder = (bitOrder == MSBFIRST ? MSB_FIRST : LSB_FIRST);

    switch (dataMode)
    {
      case SPI_MODE0:
        this->dataMode = SERCOM_SPI_MODE_0; break;
      case SPI_MODE1:
        this->dataMode = SERCOM_SPI_MODE_1; break;
      case SPI_MODE2:
        this->dataMode = SERCOM_SPI_MODE_2; break;
      case SPI_MODE3:
        this->dataMode = SERCOM_SPI_MODE_3; break;
      default:
        this->dataMode = SERCOM_SPI_MODE_0; break;
    }
  }

  uint32_t clockFreq;
  SercomSpiClockMode dataMode;
  SercomDataOrder bitOrder;

  friend class SPIClass;
};


Which relates to this in sd2card.cpp:

Code: [Select]

//------------------------------------------------------------------------------
/**
 * Initialize an SD flash memory card.
 *
 * \param[in] sckRateID SPI clock rate selector. See setSckRate().
 * \param[in] chipSelectPin SD chip select pin number.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.  The reason for failure
 * can be determined by calling errorCode() and errorData().
 */
uint8_t Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) {
  errorCode_ = inBlock_ = partialBlockRead_ = type_ = 0;
  chipSelectPin_ = chipSelectPin;
  // 16-bit init start time allows over a minute
  uint16_t t0 = (uint16_t)millis();
  uint32_t arg;

  // set pin modes
  pinMode(chipSelectPin_, OUTPUT);
  digitalWrite(chipSelectPin_, HIGH);
#ifndef USE_SPI_LIB
  pinMode(SPI_MISO_PIN, INPUT);
  pinMode(SPI_MOSI_PIN, OUTPUT);
  pinMode(SPI_SCK_PIN, OUTPUT);
#endif

#ifndef SOFTWARE_SPI
#ifndef USE_SPI_LIB
  // SS must be in output mode even it is not chip select
  pinMode(SS_PIN, OUTPUT);
  digitalWrite(SS_PIN, HIGH); // disable any SPI device using hardware SS pin
  // Enable SPI, Master, clock rate f_osc/128
  SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
  // clear double speed
  SPSR &= ~(1 << SPI2X);
#else // USE_SPI_LIB
  SPI.begin();
  settings = SPISettings(250000, MSBFIRST, SPI_MODE0);
#endif // USE_SPI_LIB
#endif // SOFTWARE_SPI

  // must supply min of 74 clock cycles with CS high.

...

#ifndef SOFTWARE_SPI
  return setSckRate(sckRateID);
#else  // SOFTWARE_SPI
  return true;
#endif  // SOFTWARE_SPI

 fail:
  chipSelectHigh();
  return false;
}



Which you can see calls setSckRate(sckRateID); at the end...

Which is this in the same file:

Code: [Select]

uint8_t Sd2Card::setSckRate(uint8_t sckRateID) {
  if (sckRateID > 6) {
    error(SD_CARD_ERROR_SCK_RATE);
    return false;
  }
#ifndef USE_SPI_LIB
  // see avr processor datasheet for SPI register bit definitions
  if ((sckRateID & 1) || sckRateID == 6) {
    SPSR &= ~(1 << SPI2X);
  } else {
    SPSR |= (1 << SPI2X);
  }
  SPCR &= ~((1 <<SPR1) | (1 << SPR0));
  SPCR |= (sckRateID & 4 ? (1 << SPR1) : 0)
    | (sckRateID & 2 ? (1 << SPR0) : 0);
#else // USE_SPI_LIB
  switch (sckRateID) {
    case 0:  settings = SPISettings(25000000, MSBFIRST, SPI_MODE0); break;
    case 1:  settings = SPISettings(4000000, MSBFIRST, SPI_MODE0); break;
    case 2:  settings = SPISettings(2000000, MSBFIRST, SPI_MODE0); break;
    case 3:  settings = SPISettings(1000000, MSBFIRST, SPI_MODE0); break;
    case 4:  settings = SPISettings(500000, MSBFIRST, SPI_MODE0); break;
    case 5:  settings = SPISettings(250000, MSBFIRST, SPI_MODE0); break;
    default: settings = SPISettings(125000, MSBFIRST, SPI_MODE0);
  }
#endif // USE_SPI_LIB
  return true;
}


But where does sckRateID come from?

That comes from SD.cpp:

Code: [Select]

boolean SDClass::begin(uint8_t csPin) {
  /*

    Performs the initialisation required by the sdfatlib library.

    Return true if initialization succeeds, false otherwise.

   */
  return card.init(SPI_FULL_SPEED, csPin) && // *** MODIFIED from SPI_HALF_SPEED ***
         volume.init(card) &&
         root.openRoot(volume);
}



Which you see needed to be modified to pass SPI_FULL_SPEED.  Which is in s2card.h, and = 0.

So we can see that at the end of the SD card initialization, the speed is set to:
Code: [Select]
case 0:  settings = SPISettings(25000000, MSBFIRST, SPI_MODE0); break;

And before that when the card is initializing it's set to:
Code: [Select]
settings = SPISettings(250000, MSBFIRST, SPI_MODE0);

Which it needs to be for the card to work.  So I'm not sure how your code could override either of those settings and if it can, then it could potentially break card reading on cards which have to have the slow initialization which is part of the SD card standard.


AloyseTech

#31
Nov 10, 2015, 01:14 am Last Edit: Nov 10, 2015, 01:15 am by AloyseTech
I did in fact modified the sd2card.cpp, not .h, sorry.

I will go back to the initial files and only modify the SD.begin function. I was not aware of a special slower initialisation of SD card. It works at full speed though.

I'm also working with IDE 1.6.6 and samd core 1.6.2.

AloyseTech

#32
Nov 10, 2015, 10:08 pm Last Edit: Nov 10, 2015, 10:29 pm by AloyseTech
did you know this thoughts about SPI with DMA ?
http://forum.arduino.cc/index.php?topic=344029.msg2371173#msg2371173

https://github.com/manitou48/ZERO
https://github.com/manitou48/ZERO/blob/master/SPIdma.ino
Dirk67, thanks to your code, I have been able to achieve 10.15FPS for Raw picture from SD card to Oled and 27.55FPS for Raw array in program memory to Oled. I used the spiwrite(...) function only, and there is probably the possibility to add the spiread(...) function in the SD library as well. I will investigate.

Dirk67

Quote
Dirk67, thanks to your code...
the code is from mantoui, to be correct ... :)
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

AloyseTech

#34
Nov 11, 2015, 10:16 am Last Edit: Nov 11, 2015, 10:16 am by AloyseTech
Thanks to both of you then ! :)

scswift

#35
Nov 11, 2015, 11:56 am Last Edit: Nov 11, 2015, 12:03 pm by scswift
I've applied some more optimizations.  It's not as fast as the DMA one, but it has more of a chance of making it into the official libraries.

http://rabidprototypes.com/wp-content/uploads/2015/11/pixel_speedtest.zip

The main thing I did, besides all the other optimizations we discussed earlier, is I added a write() method to the SPI class.  Now instead of just having transfer() which writes and returns a byte you have an inline method which writes a byte and ignores the received data.

This greatly sped up the portion of my demo where I draw lots of colored rectangles, because in my fastrect() function I have to transfer two bytes for each pixel. 

I could have, I suppose, stored the bytes in an integer and then called the transfer function I wrote that transfers a buffer and discards the returned data, but that would have resulted in a bunch of unnecessary overhead for passing the byte count, doing the loop, etc.  Sometimes you just need to write a byte or two, and this is the fastest way of doing it. 

Of course this may change the behavior of the write function in the sercom library in some way.  But it seems to work just fine with the SD card reading and such.

Code: [Select]

void SERCOM::writeDataSPI(uint8_t data)
{

/*
while( sercom->SPI.INTFLAG.bit.DRE == 0 )
  {
    // Waiting Data Registry Empty
  }

  sercom->SPI.DATA.bit.DATA = data; // Writing data into Data register

  while( sercom->SPI.INTFLAG.bit.TXC == 0 || sercom->SPI.INTFLAG.bit.DRE == 0 )
  {
    // Waiting Complete Transmission
  }
*/

sercom->SPI.DATA.bit.DATA = data; // Initiate byte transfer.
     while(sercom->SPI.INTFLAG.bit.RXC == 0); // Wait for data to be available in the receive buffer.

// Is RXC cleared when writing data to the transfer buffer?

}

uint16_t SERCOM::readDataSPI()
{
  while( sercom->SPI.INTFLAG.bit.DRE == 0 || sercom->SPI.INTFLAG.bit.RXC == 0 )
  {
    // Waiting Complete Reception
  }

  return sercom->SPI.DATA.bit.DATA;  // Reading data
}
 


Looking at this again now, I see another optimization I might be able to do.  I see no reason why, if I have arranged the write function to wait until the last bit of the returned byte shows up, that I cannot change the read function to simply return the contents of the DATA register, and skip those checks above.

With the optimized block transfer functions in place, this probably won't speed up the SD card reading noticeably, but there's no point in leaving unnecessary checks in there. 

On the other hand, the read method may not even be being called any more, because the transfer method now calls an optimized transfer method in the sercom library.  But maybe there's some hidden calls somewhere in the SD lib.

[edit]

Yeah, I just tried commenting out those checks and I don't see a change in speed, so I don't think the read method is being called any more.

oe1wkl

@scswift: I tried to compile your demo (on the Pixel), but I am getting compiling errors - it seems it cannot find the new SERCOM functions. I added SERCOM into my libraries as I did with SPI and all the others in your zip file, but while the compiler recognizes the replacement for the SPI library, it obviously doesn't for the SERCOM library. What do I have to do in order to fix this?
Thanks a lot,
Willi
oe1wkl

sandeepmistry

A few related updates on this topic:

1) Pull request #180 has been merged and included since the 1.6.9 SAMD core release. This improves the performance of the SPI.transfer(...) function

2) A new public facing API has been added to the SD library to select the SPI frequency (see pull request #25): SD.begin(spiFrequency, csPin)


Both of these changes increase SD card read performance.

jlsilicon

Do we have a schematic for this ?

Is it the same Wiring as SdCard to the UNO ?

Go Up