Skip to content

Commit 8c01516

Browse files
earlephilhowerdevyte
authored andcommitted
Allow unaligned input/output to SPI::transferBytes (#5709)
* Allow unaligned input/output to SPI::transferBytes Fixes #4967 Support any alignment of input and output pointers and transfer lengths in SPI::transferBytes. Use 32-bit transfers and FIFO as much as possible. * Refactor misaligned transfer, avoid RMW to FIFO The SPI FIFO can't properly do RMW (i.e. bytewise updates) because when you read the FIFO you are actually reading the SPI read data, not what was written into the write FIFO. Refactor the transferBytes to take account of this. For aligned input and outputs, perform as before (but handle non-x4 sizes properly). For misaligned inputs, if it's unidirectional then do bytewise until the direction data pointer is aligned and then do 32b accesses. Fod bidirectional and misaligned inputs, copy the output data to an aligned buffer, do the transfer, then copy the read back data from temp aligned buffer to the real input buffer. * Fix comments, clean condition checks, save stack Add more comments and adjust naming to be more informative in transferBytes_ and *aligned_. Save 64bytes of stack in double misaligned case. * Optimize misaligned transfers, reduce code size On any misaligned input or output, always use a temp buffer. No need for special casing and bytewise ::transfer(). This should be faster as bytewise ::transfer involves a significant number of IO register accesses and setup. Thanks to @devyte for the suggestion.
1 parent 56268b1 commit 8c01516

File tree

2 files changed

+48
-22
lines changed

2 files changed

+48
-22
lines changed

libraries/SPI/SPI.cpp

+47-22
Original file line numberDiff line numberDiff line change
@@ -509,9 +509,6 @@ void SPIClass::writePattern(const uint8_t * data, uint8_t size, uint32_t repeat)
509509
}
510510

511511
/**
512-
* Note:
513-
* in and out need to be aligned to 32Bit
514-
* or you get an Fatal exception (9)
515512
* @param out uint8_t *
516513
* @param in uint8_t *
517514
* @param size uint32_t
@@ -538,45 +535,73 @@ void SPIClass::transferBytes(const uint8_t * out, uint8_t * in, uint32_t size) {
538535
* @param in uint8_t *
539536
* @param size uint8_t (max 64)
540537
*/
541-
void SPIClass::transferBytes_(const uint8_t * out, uint8_t * in, uint8_t size) {
538+
539+
void SPIClass::transferBytesAligned_(const uint8_t * out, uint8_t * in, uint8_t size) {
540+
if (!size)
541+
return;
542+
542543
while(SPI1CMD & SPIBUSY) {}
543544
// Set in/out Bits to transfer
544545

545546
setDataBits(size * 8);
546547

547-
volatile uint32_t * fifoPtr = &SPI1W0;
548-
uint8_t dataSize = ((size + 3) / 4);
548+
volatile uint32_t *fifoPtr = &SPI1W0;
549549

550-
if(out) {
551-
uint32_t * dataPtr = (uint32_t*) out;
552-
while(dataSize--) {
553-
*fifoPtr = *dataPtr;
554-
dataPtr++;
555-
fifoPtr++;
550+
if (out) {
551+
uint8_t outSize = ((size + 3) / 4);
552+
uint32_t *dataPtr = (uint32_t*) out;
553+
while (outSize--) {
554+
*(fifoPtr++) = *(dataPtr++);
556555
}
557556
} else {
557+
uint8_t outSize = ((size + 3) / 4);
558558
// no out data only read fill with dummy data!
559-
while(dataSize--) {
560-
*fifoPtr = 0xFFFFFFFF;
561-
fifoPtr++;
559+
while (outSize--) {
560+
*(fifoPtr++) = 0xFFFFFFFF;
562561
}
563562
}
564563

565564
SPI1CMD |= SPIBUSY;
566565
while(SPI1CMD & SPIBUSY) {}
567566

568-
if(in) {
569-
uint32_t * dataPtr = (uint32_t*) in;
567+
if (in) {
568+
uint32_t *dataPtr = (uint32_t*) in;
570569
fifoPtr = &SPI1W0;
571-
dataSize = ((size + 3) / 4);
572-
while(dataSize--) {
573-
*dataPtr = *fifoPtr;
574-
dataPtr++;
575-
fifoPtr++;
570+
int inSize = size;
571+
// Unlike outSize above, inSize tracks *bytes* since we must transfer only the requested bytes to the app to avoid overwriting other vars.
572+
while (inSize >= 4) {
573+
*(dataPtr++) = *(fifoPtr++);
574+
inSize -= 4;
575+
in += 4;
576+
}
577+
volatile uint8_t *fifoPtrB = (volatile uint8_t *)fifoPtr;
578+
while (inSize--) {
579+
*(in++) = *(fifoPtrB++);
580+
}
581+
}
582+
}
583+
584+
585+
void SPIClass::transferBytes_(const uint8_t * out, uint8_t * in, uint8_t size) {
586+
if (!((uint32_t)out & 3) && !((uint32_t)in & 3)) {
587+
// Input and output are both 32b aligned or NULL
588+
transferBytesAligned_(out, in, size);
589+
} else {
590+
// HW FIFO has 64b limit and ::transferBytes breaks up large xfers into 64byte chunks before calling this function
591+
// We know at this point at least one direction is misaligned, so use temporary buffer to align everything
592+
// No need for separate out and in aligned copies, we can overwrite our out copy with the input data safely
593+
uint8_t aligned[64]; // Stack vars will be 32b aligned
594+
if (out) {
595+
memcpy(aligned, out, size);
596+
}
597+
transferBytesAligned_(out ? aligned : nullptr, in ? aligned : nullptr, size);
598+
if (in) {
599+
memcpy(in, aligned, size);
576600
}
577601
}
578602
}
579603

604+
580605
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPI)
581606
SPIClass SPI;
582607
#endif

libraries/SPI/SPI.h

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class SPIClass {
7979
uint8_t pinSet;
8080
void writeBytes_(const uint8_t * data, uint8_t size);
8181
void transferBytes_(const uint8_t * out, uint8_t * in, uint8_t size);
82+
void transferBytesAligned_(const uint8_t * out, uint8_t * in, uint8_t size);
8283
inline void setDataBits(uint16_t bits);
8384
};
8485

0 commit comments

Comments
 (0)