1

I checked the AT45DB321D datasheet and it says it operates at either 66 MHz oder 33 MHz. Both of these speeds are higher than what an Arduino Nano supports (my google fu fails me here, but according to the Arduino SPI doc it's probably on the order of 4 MHz).

According to the datasheet

Data can be clocked in from the input pin (SI) into either buffer 1 or buffer 2. To load data into the standard DataFlash buffer (528 bytes), a 1-byte opcode, 84H for buffer 1 or 87H for buffer 2, must be clocked into the device, followed by three address bytes comprised of 14 don’t care bits and 10 buffer address bits (BFA9-BFA0). The 10 buffer address bits specify the first byte in the buffer to be written. [...] Data will continue to be loaded into the buffer until a low-to-high transition is detected on the CS pin.

Which means to write a single byte I would need

  • 1 opcode byte
  • 3 address bytes
  • 1 data byte

totaling 5 bytes or 40 bits which at 4 MHz yields 10 µs.

My code, as below, yields roughly 48 µs per byte (outputs a duration of ~12380 µs for transfering 256 bytes). I'm using the DataFlash library.

#include <SPI.h>
#include "DataFlash.h"

static const int csPin    = 10;
static const int resetPin = 8;
static const int wpPin    = 7;

DataFlash dataflash;

void setup()
{
  Serial.begin(115200);

  SPI.begin();

  dataflash.setup(csPin, resetPin, wpPin);
  dataflash.begin();

  long start = micros();
  dataflash.bufferWrite(0, 0);
  for(int i = 0; i <= 255; i++) {
    SPI.transfer(i);
  }
  long afterBuffer = micros();

  Serial.print("Writing 256 values to buffer: ");
  Serial.println(afterBuffer - start);
}

void loop()
{
  // nothing
}

It's very likely I'm missing something, but especially if I want to transfer larger chunks of data at once I'd like to speed up that process a bit. Creating a 256 byte buffer and writing it all at once

// outside time measurement
uint8_t buffer256[256];
for(int i = 0; i <= 255; i++) {
  buffer256[i] = i;
}
// inside time measurement
SPI.transfer(buffer256, 256);

takes roughly the same time.

tl;dr: Is everything working correctly/what am I doing wrong? How can I speed up writing to a flash chip using an Arduino Nano?

2
  • 1
    The fastest that SPI can work at is F_CPU/2, so for a 16MHz Arduino that's 8MHz. – Majenko Nov 17 '19 at 15:02
  • I would attach an oscilloscope and measure; that's the best way to see whether my assumptions about clock frequencies and times are right. – the busybee Nov 17 '19 at 17:23
2

"Data will continue to be loaded into the buffer until a low-to-high transition is detected on the CS pin."

The dataflash.bufferWrite command writes the opcode and address bytes and then in the for loop you write 256 data bytes. The data bytes are written at once, without preceding each single one with the opcode and the address. (So it does already what you want to do.)

It doesn't matter whether you write the byte at once or each with a single SPI write. SPI can only send them sequentially. And the initial opcode and address is only send once with the dataflash.bufferWrite command.

BTW: There are two buffers to write to 1 and 2. So I'm curious why the command dataflash.bufferWrite(0, 0); works. IMHO that should be dataflash.bufferWrite(1, 0); or dataflash.bufferWrite(2, 0);.

You also not start the internal writing to the pages. There is no command e.g. dataflash.bufferToPage(1, 7);. The data are only transfered into the flash's buffer.

Because data is not written into a memory page, you'll lose it, if you switch off the power.

UPDATE

I fear I understood your question not completely. As I now see, you wondered why you need 48 µs in average for 256 Bytes when the SPI Bus is driven with 4MHz.

I looked a little in the codes and docs. And that's what I found:

The DataFlash lib selects a different Clock divider:

/* Setup SPI */
SPI.setDataMode(SPI_MODE3);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV2);

SPI_CLOCK_DIV2 results after some computation to a frequency of Fosc/2. The computation are very difficult to follow, because they use an 8 bit number which is divided into 2 nibbles each of them defines a part of a register content. But I'm quite sure the result is Fosc/2. But that would result in a clock frequency of 8 MHz. That can not explain your observed timings.

BTW: There is also in interesting deprecation note about setting the clock speed this way. ;-)

  // This function is deprecated.  New applications should use
  // beginTransaction() to configure SPI settings.
  inline static void setClockDivider(uint8_t clockDiv) {
    SPCR = (SPCR & ~SPI_CLOCK_MASK) | (clockDiv & SPI_CLOCK_MASK);
    SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((clockDiv >> 2) & SPI_2XCLOCK_MASK);
  }

But then I saw another thing that could be a problem. There are to single value transfer functions. One for bytes (uint8_t) and one for integers (uint16_t). Your count variable is an integer so the two byte transfer method is used, resulting in two byte per transfer and therefor 512 bytes. But that also no explanation for the observations. And writing buffers works only with uint8_t[] so ??.

What else could have happened?

The MCU could be clocked by a lower frequency then 16MHZ. I think there are fuses that can be configured to reduce the master clock speed for using the MCU with lower voltage (like 3.6 Volt).

The missing beginTransaction / endTransaction (DataFlash doesn't use this) of the SPI bus could have impact on the interrupts which could distort the timing. (But that's a very wild guess ;-))

The observation that, transferring twice the amount of data with the single int transfer method does take roughly the same time than the buffered method gives us a hint, that possibly the SPI speed is not the problem.

In the function dataflash.bufferWrite(0, 0); there is e.g. a wait function waitUntilReady(); that could cause a long delay of ~12 milli seconds. You can easily try that, by taking the dataflash.bufferWrite(0, 0); out of the timing.

void DataFlash::waitUntilReady()
{
    while(!isReady()) {};
}

uint8_t DataFlash::isReady()
{
    return status() & AT45_READY;
}

uint8_t DataFlash::status()
{
    uint8_t status;
    reEnable();     // Reset command decoder.
    SPI.transfer(DATAFLASH_STATUS_REGISTER_READ);
    status = SPI.transfer(0);
    disable();
    return status;
}
4
  • You're right, bufferWrite(0, 0) works because internally it's (among other things) SPI.transfer(bufferNum ? DATAFLASH_BUFFER_2_WRITE : DATAFLASH_BUFFER_1_WRITE);. Also, I am aware of the transfer of the buffer to the memory page, but that's outside the scope of this question (and takes approx. 50 µs as well ;-). – Raketenolli Nov 17 '19 at 13:50
  • Thank you. I assumed something like that, but was not sure. I read the question a second time and now I hope, I see the problem. I'll update the answer. – Peter Paul Kiefer Nov 17 '19 at 14:28
  • @Raketenolli I've updated the answer. – Peter Paul Kiefer Nov 17 '19 at 16:53
  • Sorry for not getting back to this sooner, and now I'm wondering whether I changed anything back then that I now forgot about. I am now observing between 660 and 670 µs consistently for the entire writing operation, which means roughly 2.6 µs per byte. The SPISettings speedMaximum parameter only has an effect when set to less than 8000000, where the duration will increase to 940 µs (and then in steps to 1520, 2500 and 4560). – Raketenolli Dec 1 '19 at 15:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.