Skip to content

Commit

Permalink
chunked transfers for a big improvement in performance
Browse files Browse the repository at this point in the history
If the code doesn't run on an 8bit-AVR, the data is prepared by
transmitting the reads and writes in chunks instead of byte for byte. A
constant is used for the chunk size.

This is a tradeof between RAM/CPU and bus speed: on the 'bigger' arduino
platforms the CPU is a lot faster than the SPI and there is a lot of
RAM avaible, so using more RAM/CPU cycles and then letting the DMA do
its work is the way to go.

The chunked transfers also combine the reads and writes, so the dead
time in between is removed, which is especially important for register
reads of SPI-attached chips.

Chunked transfers give an improvement of about 40% over bytewise
ones, additionally +5% in the case of small reads/writes as used in
BusIO_Register by removing the dead time between writing and reading. This
is for all supported platforms except 8bit AVRs, which are specifically
#if'd out. The special case for the ESP32 is therefore removed and
ARM M0/M4, ESP8266, teensy etc should profit of this improvement too,
without special casing each platform by using non standard arduino
core extensions.
  • Loading branch information
eringerli committed May 7, 2022
1 parent bb7c77a commit a670b04
Showing 1 changed file with 187 additions and 72 deletions.
259 changes: 187 additions & 72 deletions Adafruit_SPIDevice.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
#include "Adafruit_SPIDevice.h"

#include <array>

#if !defined(SPI_INTERFACES_COUNT) || \
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))

//! constant for the buffer size for the chunked transfer
constexpr size_t maxBufferSizeForChunkedTransfer = 64;

//#define DEBUG_SERIAL Serial

#ifdef DEBUG_SERIAL
static printChunk(char *title, uint8_t buffer, uint8_t size,
uint16_t chunkNumber) {
DEBUG_SERIAL.print(F("\t"));
DEBUG_SERIAL.print(title);
DEBUG_SERIAL.print(F(" Chunk #"));
DEBUG_SERIAL.print(chunkNumber);
DEBUG_SERIAL.print(F(", size "));
DEBUG_SERIAL.println(size);

for (uint8_t i = 0; i < size; ++i) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
}
DEBUG_SERIAL.println();
}

static printBuffer(char *title, uint8_t buffer, size_t len) {
DEBUG_SERIAL.print(F("\t"));
DEBUG_SERIAL.println(title);
for (size_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (read_len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
}
#endif

/*!
* @brief Create an SPI device with the given CS pin and settings
* @param cspin The arduino pin number to use for chip select
Expand Down Expand Up @@ -160,7 +198,6 @@ void Adafruit_SPIDevice::transfer(uint8_t *buffer, size_t len) {
// Serial.print(send, HEX);
for (uint8_t b = startbit; b != 0;
b = (_dataOrder == SPI_BITORDER_LSBFIRST) ? b << 1 : b >> 1) {

if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
Expand Down Expand Up @@ -326,49 +363,77 @@ void Adafruit_SPIDevice::endTransactionWithDeassertingCS() {
bool Adafruit_SPIDevice::write(const uint8_t *buffer, size_t len,
const uint8_t *prefix_buffer,
size_t prefix_len) {
#if !defined(__AVR__)
std::array<uint8_t, maxBufferSizeForChunkedTransfer> chunkBuffer;

auto chunkBufferIterator = chunkBuffer.begin();

#ifdef DEBUG_SERIAL
uint8_t chunkNumber = 1;
#endif

beginTransactionWithAssertingCS();

// do the writing
#if defined(ARDUINO_ARCH_ESP32)
if (_spi) {
if (prefix_len > 0) {
_spi->transferBytes(prefix_buffer, nullptr, prefix_len);
}
if (len > 0) {
_spi->transferBytes(buffer, nullptr, len);
}
} else
for (size_t i = 0; i < prefix_len; ++i) {
*chunkBufferIterator++ = prefix_buffer[i];

if (chunkBufferIterator == chunkBuffer.end()) {
transfer(chunkBuffer.data(), maxBufferSizeForChunkedTransfer);
chunkBufferIterator = chunkBuffer.begin();

#ifdef DEBUG_SERIAL
printChunk("write() Wrote", chunkBuffer, maxBufferSizeForChunkedTransfer,
chunkNumber++);
#endif
{
for (size_t i = 0; i < prefix_len; i++) {
transfer(prefix_buffer[i]);
}
for (size_t i = 0; i < len; i++) {
transfer(buffer[i]);
}
}
endTransactionWithDeassertingCS();

for (size_t i = 0; i < len; ++i) {
*chunkBufferIterator++ = buffer[i];

if (chunkBufferIterator == chunkBuffer.end()) {
transfer(chunkBuffer.data(), maxBufferSizeForChunkedTransfer);
chunkBufferIterator = chunkBuffer.begin();

#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Wrote: "));
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
for (uint16_t i = 0; i < prefix_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(prefix_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
printChunk("write() Wrote", chunkBuffer, maxBufferSizeForChunkedTransfer,
chunkNumber++);
#endif
}
}
for (uint16_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (i % 32 == 31) {
DEBUG_SERIAL.println();
}

if (chunkBufferIterator != chunkBuffer.begin()) {
auto numberByteToTransfer = chunkBufferIterator - chunkBuffer.begin();
transfer(chunkBuffer.data(), numberByteToTransfer);

#ifdef DEBUG_SERIAL
printChunk("write() Wrote remaining", mpBuffer, numberByteToTransfer,
chunkNumber++);
#endif
}
DEBUG_SERIAL.println();

endTransactionWithDeassertingCS();

#else // !defined(__AVR__)

beginTransactionWithAssertingCS();

for (size_t i = 0; i < prefix_len; i++) {
transfer(prefix_buffer[i]);
}
for (size_t i = 0; i < len; i++) {
transfer(buffer[i]);
}

endTransactionWithDeassertingCS();

#ifdef DEBUG_SERIAL
printBuffer("write() prefix_buffer", prefix_buffer, prefix_len);
printBuffer("write() buffer", buffer, len);
#endif

#endif // !defined(__AVR__)

return true;
}

Expand All @@ -390,16 +455,7 @@ bool Adafruit_SPIDevice::read(uint8_t *buffer, size_t len, uint8_t sendvalue) {
endTransactionWithDeassertingCS();

#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Read: "));
for (uint16_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
printBuffer("read() buffer", buffer, len);
#endif

return true;
Expand All @@ -421,53 +477,112 @@ bool Adafruit_SPIDevice::read(uint8_t *buffer, size_t len, uint8_t sendvalue) {
bool Adafruit_SPIDevice::write_then_read(const uint8_t *write_buffer,
size_t write_len, uint8_t *read_buffer,
size_t read_len, uint8_t sendvalue) {
#if !defined(__AVR__)
std::array<uint8_t, maxBufferSizeForChunkedTransfer> chunkBuffer;

auto chunkBufferIterator = chunkBuffer.begin();

#ifdef DEBUG_SERIAL
uint8_t chunkNumber = 1;
#endif

beginTransactionWithAssertingCS();
// do the writing
#if defined(ARDUINO_ARCH_ESP32)
if (_spi) {
if (write_len > 0) {
_spi->transferBytes(write_buffer, nullptr, write_len);
}
} else

for (size_t i = 0; i < write_len; ++i) {
*chunkBufferIterator++ = write_buffer[i];

if (chunkBufferIterator == chunkBuffer.end()) {
transfer(chunkBuffer.data(), maxBufferSizeForChunkedTransfer);
chunkBufferIterator = chunkBuffer.begin();

#ifdef DEBUG_SERIAL
printChunk("write_then_read() Wrote", chunkBuffer,
maxBufferSizeForChunkedTransfer, chunkNumber++);
#endif
{
for (size_t i = 0; i < write_len; i++) {
transfer(write_buffer[i]);
}
}

auto readBufferIterator = read_buffer;
auto readFromIterator = chunkBufferIterator;
size_t readBufferLen = read_len;

for (size_t i = 0; i < read_len; ++i) {
*chunkBufferIterator++ = sendvalue;

if (chunkBufferIterator == chunkBuffer.end()) {
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Wrote: "));
for (uint16_t i = 0; i < write_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(write_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (write_len % 32 == 31) {
DEBUG_SERIAL.println();
printChunk("write_then_read() before transmit", chunkBuffer,
maxBufferSizeForChunkedTransfer, chunkNumber);
#endif

transfer(chunkBuffer.data(), maxBufferSizeForChunkedTransfer);

while (readBufferLen) {
if (readFromIterator != chunkBuffer.end()) {
*readBufferIterator++ = *readFromIterator++;
--readBufferLen;
} else {
break;
}
}

#ifdef DEBUG_SERIAL
printChunk("write_then_read() after transmit", chunkBuffer,
maxBufferSizeForChunkedTransfer, chunkNumber++);
#endif

chunkBufferIterator = chunkBuffer.begin();
readFromIterator = chunkBuffer.begin();
}
}
DEBUG_SERIAL.println();

if (chunkBufferIterator != chunkBuffer.begin()) {
auto numberByteToTransfer = chunkBufferIterator - chunkBuffer.begin();

#ifdef DEBUG_SERIAL
printChunk("write_then_read() before transmit remaining", chunkBuffer,
maxBufferSizeForChunkedTransfer, chunkNumber);
#endif

transfer(chunkBuffer.data(), numberByteToTransfer);

#ifdef DEBUG_SERIAL
printChunk("write_then_read() after transmit remaining", chunkBuffer,
numberByteToTransfer, chunkNumber);
#endif

// do the reading
while (readBufferLen) {
if (readFromIterator != chunkBuffer.end()) {
*readBufferIterator++ = *readFromIterator++;
--readBufferLen;
} else {
break;
}
}
}

endTransactionWithDeassertingCS();

#else // !defined(__AVR__)

beginTransactionWithAssertingCS();

for (size_t i = 0; i < write_len; i++) {
transfer(write_buffer[i]);
}

for (size_t i = 0; i < read_len; i++) {
read_buffer[i] = transfer(sendvalue);
}

endTransactionWithDeassertingCS();

#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Read: "));
for (uint16_t i = 0; i < read_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(read_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (read_len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
printBuffer("write_then_read() write_buffer", write_buffer, write_len);
printBuffer("write_then_read() read_buffer", read_buffer, read_len);
#endif

endTransactionWithDeassertingCS();
#endif // !defined(__AVR__)

return true;
}
Expand Down

0 comments on commit a670b04

Please sign in to comment.