Micro SD Card Duplicator 3

The best data write speed I've achieved so far is 21.5kB/s. That's not much. A 1GB file would take, oh, 14 hours to write to an SD card. That's not good.

It looks like the base speed for SD cards is 12.5MB/s. That's mega bytes per second. So we know SD card technology can move about a thousand times faster than we're currently going. I’ve already mentioned that SD cards have two communications protocols: SPI (SSI) and SDIO. However we don’t really have the option to use SDIO (the native protocol) so we’re just left with pushing SPI as fast as we can get it. If that doesn’t work, the gig is up.

The up side to this “no choice” scenario is that SPI doesn’t define a speed limit. It works at almost any speed, so it just comes down to how fast we can configure it. This is where we need to know more about the actual microcontroller on the Arduino. My EtherTen uses an Atmel ATmega328P microcontroller. My Duemilanove also uses a 328. There are a family of these that all have similar properties. The datasheet is what we need.

Now, I haven’t spent much time reading microcontroller data sheets, so don’t really know what I’m looking for. Unhelpfully, this data sheet doesn’t even provide a table of contents. So, knowing I’m interested in SPI, I’m just going to search for that. In Google Chrome, when you search a term in a PDF file, the vertical scroll bar becomes banded to show you where in the document the term was found. This is a kind of heat map, indicating how frequently the term is used. I scroll down to the first hot (dense) section, nope, next section. When I get about 40% of the way down the document, I strike gold. Heading 18 is SPI – Serial Peripheral Interface, page 166.

18.1 Features lists “Seven Programmable Bit Rates” as a feature. Bingo! I read every word. At 18.5.1 SPCR – SPI Control Register things really heat up. This is where data (from our Arduino sketch) is written into the microcontroller’s hardware to set the speed of the SPI port. Table 18-5 Relationship Between SCK and the Oscillator Frequency shows how the SPI2X, SPR1 and SPR0 bits control speed. SPR1 and SPR0 are the lower two bits in the SPCR register byte (see 18.5.1) and SPI2X is the highest bit in the SPSR register byte (see 18.5.2 SPSR – SPI Status Register).

Since we’re already using SPI and writing to the SD card, we must have configured the SPCR register. But I haven’t seen that in code. It stands to reason this is happening “under the covers” somewhere in the Arduino code. I open my trusty copy of Notepad++ and search the Arduino libraries folder, under SD, for mentions of the SPCR register.

If you don’t have a code editor with search-in-folders functionality, you should download it, or a similar program.

Find in Files

Thankfully, I only get three hits and they’re all on the same file.

  C:\Program Files (x86)\Arduino 1.0.3\libraries\SD\utility\Sd2Card.cpp (3 hits)
    Line 232:   SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
    Line 465:   SPCR &= ~((1 <<SPR1) | (1 << SPR0));
    Line 466:   SPCR |= (sckRateID & 4 ? (1 << SPR1) : 0)

At line 227, there’s a block of code that directly sets the SPCR value:

#ifndef SOFTWARE_SPI
  // 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);
#endif  // SOFTWARE_SPI

And at line 454 there’s a function Sd2Card::setSckRate():

//------------------------------------------------------------------------------
/**
* Set the SPI clock rate.
*
* \param[in] sckRateID A value in the range [0, 6].
*
* The SPI clock will be set to F_CPU/pow(2, 1 + sckRateID). The maximum
* SPI rate is F_CPU/2 for \a sckRateID = 0 and the minimum rate is F_CPU/128
* for \a scsRateID = 6.
*
* \return The value one, true, is returned for success and the value zero,
* false, is returned for an invalid value of \a sckRateID.
*/
uint8_t Sd2Card::setSckRate(uint8_t sckRateID) {
  if (sckRateID > 6) {
    error(SD_CARD_ERROR_SCK_RATE);
    return false;
  }
  // 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);
  return true;
}

The first block of code contains the remark “clock rate f_osc/128”. This makes me happy! If this is indeed the code setting the SPI speed on our application, it’s set to the slowest speed there is. Check back to Table 18-5 of the ATmega data sheet. So, understand the code, make a tweak, go faster!

Now, I’ve developed a method with Arduino programming that helps me locate what code in libraries might be running. It’s simple really, add a Serial.Println() line anywhere. As long as the main sketch has the line Serial.begin() before the code you’re looking at is called (Sd2Card.cpp), then all should be well. The only time this doesn’t work is when Serial.begin() hasn’t been called yet and the code hits a Serial.println() statement.

So under the line:

#ifndef SOFTWARE_SPI

I added

Serial.println("Setting SPCR"); // Testing C.M.M. 2013-02-06

I did this in Notepad++, knowing that when I upload the sketch, this file will be compiled into the application. Upload. Run. Great. I can see “Setting SPCR” in my output, so I know this block of code is being called, and only once. I added another print line to the Sd2Card::setSckRate() function and uploaded again.

The output shows Setting SPCR runs first, and Sd2Card::setSckRate() runs second. Since both write to the SPCR register, I need to modify Sd2Card::setSckRate() and not the other code, else my change will be overwritten. I add another line of code to debug (println) the value of the sckRateID being passed into the function when it’s called, it’s 1. So by finding where sckRateID is called from, I can call it with a different parameter and get a different speed.

I’m running out of time. I need to move faster, sorry if this loses you.

  1. In Sd2Card.cpp, the SPCR register is set by the setSckRate() function
  2. setSckRate() is called from the init() function in the same file. This function takes two parameters.
  3. Looking at my main sketch (taken from example code) the SD card is started using SD.begin() with one parameter.
  4. Since the main sketch includes SD.h, I go to SD.cpp to see what code is in the SD.begin() function.
  5. In SD.cpp, begin() calls init() in the Sd2Card class (Sd2Card.cpp) and, most importantly, sets the card speed to SPI_HALF_SPEED!
  6. SPI_FULL_SPEED, SPI_HALF_SPEED and SPI_QUARTER_SPEED are defined in Sd2Card.h

Of course now I can see I went such an incredibly long way around. If I had started with my main sketch and looked into the SD.begin() function, I would have discovered the easy solution in two steps. But I learned a lot on the way! I try never to begrudge the learning experience, even when it costs hours. What I learned today will come in handy soon, I know it!

By tweaking the begin() function in SD.cpp, we can modify the SPI speed used to access the card. Instead of going faster, I choose slower. Too fast may cause all kinds of glitches. If I choose slower, I expect the SD card will about halve in speed. From 21.5kB/s to around 10. I need to pull my debugging code out of the files I’ve edited, so I’m doing that now too.

I’ve changed the begin() function in SD.cpp to this:

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_HALF_SPEED, csPin) && volume.init(card) && root.openRoot(volume);
  return card.init(SPI_QUARTER_SPEED, csPin) && volume.init(card) && root.openRoot(volume); // Testing C.M.M. 2013-02-06
}

Notice I’ve commented out the SPI_HALF_SPEED line and replaced it with an SPI_QUARTER_SPEED line. Compile, upload, run …

Initializing SD card ... success.
33.33 B/s for 1 bytes written.
66.67 B/s for 2 bytes written.
129.03 B/s for 4 bytes written.
275.86 B/s for 8 bytes written.
533.33 B/s for 16 bytes written.
1032.26 B/s for 32 bytes written.
2000.00 B/s for 64 bytes written.
3764.71 B/s for 128 bytes written.
6918.92 B/s for 256 bytes written.
11377.78 B/s for 512 bytes written.
17355.93 B/s for 1024 bytes written.
23272.73 B/s for 2048 bytes written.
27863.95 B/s for 4096 bytes written.
31148.29 B/s for 8192 bytes written.
33098.99 B/s for 16384 bytes written.
34097.82 B/s for 32768 bytes written.
34620.18 B/s for 65536 bytes written.
34905.99 B/s for 131072 bytes written.
35045.99 B/s for 262144 bytes written.
35116.41 B/s for 524288 bytes written.

End of program

I’m now hitting 35kB/s! But I thought it would slow down! What’s happening? Alrighty then, let’s try SPI_FULL_SPEED! 35kB/s again! Back to half speed. Same again. Bugger.

Previously I had a reliable 21.5kB/s. By modifying the code, and putting everything back the way I’ve found it, I somehow have mysteriously added 50% to my throughput. But changing the speed is having now effect. I’m lost. This is one of those times when you just have to walk away and think about it. No doubt inspiration will come when I’m in the shower or driving to work. Hmmm.

[That is all]

Comments