SPI.transfer problem

Here is my basic issue using SPI.transfer.
I load in some bytes from a file on the SD card. I then wish to transfer say 21 of these bytes to SPI. I then wish to repeat this. The first SPI.transfer works fine but the second sends all zeros. What am I missing.

        myFile = SD.open("init.txt", FILE_READ);// OPEN FILE
        nb_file = myFile.size();// GET FILE SIZE
        myFile.read(read_data, nb_file);//READ BYTES FROM FILE
        myFile.close();
        SPI.transfer(&read_data[0], 21);// SPI WRITE 1st 21 bytes
        delay(1000);
        SPI.transfer(&read_data[0], 21);// REPEAT SAME SPI WRITE

The rest of the code.

Perhaps I distilled the example down too much. Its part of a larger code set. I added in some details.
Note the first SPI write observed on a scope works just fine so the "rest of the code" seems to be in place for this. But the second identical set of code acts as if the all zeros were sent as observed on a scope.
Comments?

        myFile = SD.open("init.txt", FILE_READ);// OPEN FILE
        nb_file = myFile.size();// GET FILE SIZE
        
        myFile.read(read_data, nb_file);//READ BYTES FROM FILE
        myFile.close();
        
        SPI.beginTransaction(SPISettings(fclk_w, MSBFIRST, 0));
        SPI.transfer(&read_data[0], 21);// SPI WRITE 1st 21 bytes
        SPI.endTransaction();

        delay(1000);

        SPI.beginTransaction(SPISettings(fclk_w, MSBFIRST, 0));
        SPI.transfer(&read_data[0], 21);// REPEAT SAME SPI WRITE
        SPI.endTransaction();

As @gfvalvo noted, snippets are generally frowned upon, given that the problem being presented usually begins elsewhere in the rest of the sketch.

For instance, what exactly are you transferring that buffer to? I don't see a SPI device's CS output being driven low before the transfer begins. What's listening on the bus?

So it's not possible to give a definitive answer without the complete sketch, I'll take a WAG and guess that you didn't take a look at the documentation for SPI.transfer. If you had, you might have noticed the following:

SPI transfer is based on a simultaneous send and receive: the received data is returned in receivedVal (or receivedVal16). In case of buffer transfers the received data is stored in the buffer in-place (the old data is replaced with the data received).

The HW I am using is a Teensy 4.1 BTW. However I think the issue is my limited understanding of how pointers work when they are marshalled out to SPI.transfer.

Why can't you just post the complete code so we can compile and test any suggestions? If the full code is too large, ugly, and messy, post an MRE.

1 Like

Ahh I misunderstood that part. That makes sense as you explain it. The parlance of the help file was not appreciated by me initially but now makes sense.

I guess I need to create an expendable dummy array and refresh it unless there is a way to not have the received data overwrite the buffer.

Thanks

You're quite welcome. Now that your question has been answered, please take a moment to mark the topic as solved by clicking the :ballot_box_with_check: Solution button at the bottom of the reply that answered your question, as shown in How to get the best out of this forum.

Will do the close out thing.

I did just now verify as a brute force effort that if I re-read the bytes from the file after the first SPI.transfer all is good. Ill research how best to handle this more efficiently. I am a MATLAB guy and writing over input arguments is not a thing at the base workspace. I vaguely remember this from some C work along time ago. I am old now, in fact this is the oldest I have ever been. :slight_smile:

I did read the help but did not fully appreciate/realize what is going on. Reading how to do and actually doing are different things :slight_smile:

The source code tells all. Have a peek SPI.h and SPI.cpp. In the former you'll find this prototype:

	void transfer(const void * buf, void * retbuf, size_t count);

It's implemented in SPI.cpp as:

void SPIClass::transfer(const void * buf, void * retbuf, size_t count)
{

	if (count == 0) return;
    uint8_t *p_write = (uint8_t*)buf;
    uint8_t *p_read = (uint8_t*)retbuf;
    size_t count_read = count;

	// Pass 1 keep it simple and don't try packing 8 bits into 16 yet..
	// Lets clear the reader queue
	port().CR = LPSPI_CR_RRF | LPSPI_CR_MEN;	// clear the queue and make sure still enabled. 

	while (count > 0) {
		// Push out the next byte; 
		port().TDR = p_write? *p_write++ : _transferWriteFill;
		count--; // how many bytes left to output.
		// Make sure queue is not full before pushing next byte out
		do {
			if ((port().RSR & LPSPI_RSR_RXEMPTY) == 0)  {
				uint8_t b = port().RDR;  // Read any pending RX bytes in
				if (p_read) *p_read++ = b; 
				count_read--;
			}
		} while ((port().SR & LPSPI_SR_TDF) == 0) ;

	}

	// now lets wait for all of the read bytes to be returned...
	while (count_read) {
		if ((port().RSR & LPSPI_RSR_RXEMPTY) == 0)  {
			uint8_t b = port().RDR;  // Read any pending RX bytes in
			if (p_read) *p_read++ = b; 
			count_read--;
		}
	}
}

So, if you supply 'nullptr' for the first buffer it won't attempt to send any data out of the SPI interface (Read Only). Conversely, if you supply 'nullptr' as the second buffer parameter, it won't attempt to read any data from the SPI interface (Write Only). Thus, the write buffer is never overwritten.

Much thanks for the tip.

Ill gave this a try. One thing, it looks like the arguments to transfer are 2 buffers and the size but in the Arduino help for SPI.transfer it only shows one buffer and the size argument. Soo for fun I tried the second buffer and viola it works! That is the buffer I do not want over written is not over written just like I needed.

SPI.transfer(&read_data[(i>>2) * 21], &tmp[0], 21);

tmp is a dummy buffer and read_data is what I need preserved.

Its all working good now. All 4 SPI line CLK MISO MOSI and SS are as they should be.

I was previously using memcpy to a dummy variable which worked but the new way you pointed out is much cleaner in my mind.

Thanks again

Mark

That was written for the generic version of the library. You're using a T4.1. That's one of the reasons I tend to trust the source code, not the Arduino documentation.

There no reason to supply the dummy buffer tmp at all. Like I said, just supply a nullptr:

  SPI.transfer(&read_data[(i >> 2) * 21], nullptr, 21);

I added in the nullptr and works like a champ. Even more clean now.

Much thanks

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.