Electronic – Increase SPI transaction speed

beaglebone blackspi

I am using SPI to write and read the data from slave board. I have two questions regarding this process:

Question 1. Why when I am trying to read 1.000.000 bytes through SPI using 1 message, it works slower than if I divide them into K messages?

I tried to play around difference between one huge message per transaction and multiple smaller messages (which in total gives us exactly the same sized buffers) and I observed that lesser the message size, faster SPI transaction speed.

Question 2. Is there a way to increase SPI transaction speed?

According to my tests, pure SPI transaction for 1.000.000 bytes divided into 500 packages is taking 380ms of working time.

Which means that I have 8.000.000 bits per 0.38 seconds => 20.55 Mbits/sec, which is less than 48Mbits/sec that it is supposed to be.

In addition here is some pieces of code that I used to compare and test:

device = "/dev/spidev1.0";
mode = SPI_MODE_0;
bits = 8;
speed = 48000000;

fd = open(device, O_RDWR);

memset(read_buffer, 0, sizeof(read_buffer));

mass_xfer[0].tx_buf = (unsigned long)tx1;

mass_xfer[0].len = 2;
mass_xfer[0].speed_hz = speed;

// sz - amount of bytes within one SPI message

// n - amount of messages in one SPI transaction

unsigned long sz = 2000, i, n = (nbytes + sz - 1) / sz;
for (i = 0; i < n; i++) {
    mass_xfer[i + 1].rx_buf = (unsigned long)read_buffer + i * sz;
    mass_xfer[i + 1].len = min(sz, nbytes - i * sz);
    mass_xfer[i + 1].speed_hz = speed;
}

timestamp_t t0 = get_timestamp();
ret = ioctl(fd, SPI_IOC_MESSAGE(n + 1), mass_xfer);
// ret = ioctl(fd, SPI_IOC_MESSAGE(1), &read_xfer); // When I tried to read using one message, time was more than 600ms
timestamp_t t1 = get_timestamp();
printf(" SPI READ: %.2f (%d) %d\n", (t1 - t0)/1000.0L, n + 1, SPI_MSGSIZE(n + 1)); // After this output, for 1.000.000 bytes I am receiving 380.xx ms
if (ret < 1)
    pabort("can't send spi message");

Any ideas?

UPDATE 1:
OS: Linux 3.15.10-bone8 #1 Fri Sep 26 14:20:19 PDT 2014 armv7l GNU/Linux
CPU: Sitara AM3358 1GHz. According to this CPU, SPI clock max speed is 48MHz.

Code is written in C++ and using spidev.h library.

Basically the board is BeagleBone Black.

UPDATE 2:
I have done lots of tests and figured out what is going on and what causes the problem.

The reason is gaps between byte readings. To be more specific lets review the code lines:
read_xfer.rx_buf = read_buffer;
read_xfer.len = ;
read_xfer.speed_hz = 48000000;
ioctl(fd, SPI_IOC_MESSAGE(1), &read_xfer);

If we place 100 (which is read 100 bytes) instead of we will see next picture on oscilloscope:
Oscilloscope results
As you may notice, it reads 8 bits then pause, then reads another 8 bits. So the problem is those gaps between reading the bytes. Here is some test results using oscilloscope:

  • bytes_amount = gap_width
  • 10 Bytes = 480ns
  • 50 Bytes = 410ns
  • 100 Bytes = 200ns
  • 50 000 Bytes = 200ns
  • 70 000 Bytes = 350ns

According to results, I cannot read messages larger than 100 000 Bytes in one package, since the gaps will create a huge overhead.

ANOTHER QUESTION
What causes those gaps and how to reduce them?

Best Answer

  1. Because the data is getting buffered somewhere with the larger packets?
  2. How is the SPI configured and to what is it connected?

As a general rule this kind of this is not best done this way. Are you actually using this code to do something or are you just trying to instrument the SPI capabilities? What else is running on the board and how is your kernel configured. What's the processor speed actually set to and what priority is your little app?

If you've got some kind of quasi realtime requirement then I'd suggest looking at

a) DMA (offload transmission from userland buffers)

b) Xenomai

c) (for the madly keen) the PRU

Hope this is not a homework question! Here are 2 links to get you started on A.

https://stackoverflow.com/questions/3333959/mapping-dma-buffers-to-userspace https://groups.google.com/forum/#!topic/beagleboard/UPbU2WoVzVI