SPIFIFO / chip select / compatibility issues

Status
Not open for further replies.

mxxx

Well-known member
any insights / advice on the following would be much be appreciated:

i have several custom audio boards, some of which comply fairly close to the official adapter, as far as pin usage is concerned, some less so. now i've run into an issue which i don't know it can be solved in software or not. specifically, this concerns use of SPIFIFO and Frank B's play_serialflash library in conjunction with the SD card (using play_sd_wav, accessing the SD card, etc).

the issue, in brief is, is: these things work happily together when using SPIFIFO and a native CS pin; they won't work together, it seems, when any other pin is used as CS, ie other than the hardware CS pins. SPIFIFO resp. play_serialflash is fine when used on its own, ie even when not using a native CS pin.

long story short: i know too little about the ins and outs of the various SPI functionalities to figure out what's going on. i can see of course the hardware CS pins are treated differently in SPIFIFO.h, but i don't understand why using them is ok, while using other pins isn't.*

thanks...

* edit: what i can only suspect is that it must have to do with the SPI_PUSHR_EOQ or SPI_SR_EOQF registers, which get involved when using regular pins but not with the hardware CS pins.

** edit 2: i've tried wrapping the SPIFIFO write/read sections with beginTransaction() / endTransaction(), but that doesn't seem to make a difference and i'm not even sure these functions are meant to be used with SPIFIFO or how this might relate to using or not using the hardware CS.

============================
i don't think there'd be a simple way to reproduce the 'error', if it is, because i'd imagine most people if they use the w25Q128FV do so as part of the audio board. anyways, the phenomenon could be easily reproduced with any simple sketch, like the one below which works on the audio adapter (CS_MEM = 6), but refuses to work when moving CS_MEM to another (non-CS) pin. (the CS pin is declared in play_serialflash.h).


Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <play_serialflash.h>

AudioPlaySerialFlash    raw1;
AudioPlaySdWav          wav1;
AudioMixer4             mix;
AudioOutputI2S          dac;   
// NB: not using SGTL5000, so no i2c stuff here 

AudioConnection cx(raw1, 0, mix, 0);
AudioConnection cy(wav1, 0, mix, 1);
AudioConnection c0(mix, 0, dac, 0);
AudioConnection c1(mix, 0, dac, 1);

#define CS_SD 10

void setup() {

 pinMode(CS_SD, OUTPUT);
 digitalWrite(CS_SD, HIGH);
 
 AudioMemory(10);
  
 SPI.setMOSI(7);
 SPI.setSCK(14);
 if (SD.begin(CS_SD)) {
    Serial.println("ok");
   
  }
}

uint32_t wait;

void loop() {
  
 if (millis() - wait > 1000) {  
     /* play both from flash and SD: both work fine on their own, but not together with non-native CS */
     raw1.play(0xAA00);
     wav1.play("atari.wav");
     wait = millis();
 } 
}
 
Last edited:
well, nevermind. i found a compromise i can live with, while users of the audio adapter / spi flash are unlikely to run into this issue. i guess i should get a logic analyzer.

that said, in case anyone knows, i'd still be curious what the problem might be re using native vs non-native CS pins, or if it's generally just a bad idea to use SPIFIFO with non-hardware CS pins? my understanding is the underlying SD stuff is using transactions by default, but it seems easy enough though to derail it when trying to use SPIFIFO with a CS that's not either 6, 9, 10, 15 etc
 
I could think of two issues:

a) Do you have pullups for CS (both, SD & flash) on your board ?

I think now, initializing SPI in the constructor is a bad idea. I should change this as fast a possibleas leave this to the user, in setup()....
Configuring all CS (and setting all to "high") before doing anything else should be the very first after power up. (Well, without pullups)
And perhaps it would be better to use spi-transactions now..

b) You're running your teensy with 120MHz - the player initializes SPI with 30 MHz which is too much for SD (max 25MHz!!) and maybe SD gets "confused"...
Is SD using SPI-Transactions ? I don't know.
 
Hi, Frank. thanks for replying...

a) Do you have pullups for CS (both, SD & flash) on your board ?

I think now, initializing SPI in the constructor is a bad idea. I should change this as fast a possibleas leave this to the user, in setup()....
Configuring all CS (and setting all to "high") before doing anything else should be the very first after power up. (Well, without pullups)
And perhaps it would be better to use spi-transactions now..

no, there aren't any. i didn't try with using the internal pull-ups though; i might give it a go. i did try to add transactions in the places where the non-SPIFIFO version had them. i didn't make a difference, but i might have done it incorrectly.

b) You're running your teensy with 120MHz - the player initializes SPI with 30 MHz which is too much for SD (max 25MHz!!) and maybe SD gets "confused"...
Is SD using SPI-Transactions ? I don't know.

true, but 120 MHz doesn't make a difference when using CS=6 or CS=15 (that's the ones i've tried). i've looked at the SD 'utilities' stuff and they seem to have the transactions implemented, 24 MHz.

the thing that really confuses me is that everything works perfectly when using a hardware CS. i've rewritten some stuff from flash_spi as SPIFIFO and i can now read from and write to my 'pseudo' file system on the flash, use play_serialflash and play_sd_wav with SD, all in the same sketch, at 120MHz. no issues whatsoever. when i try this on a previous iteration of the board, which didn't have hardware CS for the spi_flash, once the flash is initialised, the first call to anything SD related will crash the program. so maybe it's related to the pull-ups -- don't know what the hardware SPI does to the native CS pins.

the only difference between the two cases seems to be this conditional construct (from SPIFIFO.h):

Code:
inline void write(uint32_t b, uint32_t cont=0) __attribute__((always_inline)) {
		uint32_t pcsbits = pcs << 16;
		if (pcsbits) {
			SPI0.PUSHR = (b & 0xFF) | pcsbits | (cont ? SPI_PUSHR_CONT : 0);
			while (((SPI0.SR) & (15 << 12)) > (3 << 12)) ; // wait if FIFO full
		} else {
			*reg = 0;
			SPI0.SR = SPI_SR_EOQF;
			SPI0.PUSHR = (b & 0xFF) | (cont ? 0 : SPI_PUSHR_EOQ);
			if (cont) {
				while (((SPI0.SR) & (15 << 12)) > (3 << 12)) ;
			} else {
				while (!(SPI0.SR & SPI_SR_EOQF)) ;
				*reg = 1;
			}
		}
	}

the true part is when using hardware CS, the other when using non-CS pins. can't make much sense of it admittedly, or why it would interfere with other SPI devices, whereas the upper part doesn't.
 
Last edited:
... so anyone knows what's so special about the hardware CS pins? or what is going on in the one case that wouldn't or couldn't be made to happen in the other, ie when using a regular pin?

it's easy enough to simulate the issue/conflict by commenting out the respective line in SPIFIFO.h

ie if using the audio board:

Code:
    } 
                        //else if (pin == 6) {   // PTD4
			//CORE_PIN6_CONFIG = PORT_PCR_MUX(2);
			//p = 0x02;
		//}

pin 6 is now treated as a regular pin, that is:

Code:
reg = portOutputRegister(pin);
			*reg = 1;
			pinMode(pin, OUTPUT);
			p = 0;

... and it'll result in SPIFIFO.h (or the spi flash raw pcm library, for that matter) tripping up the SPI (SD.h, play_sd_wav, etc). all other things being equal, there's no conflicts with CORE_PIN6_CONFIG = PORT_PCR_MUX(2);
 
Last edited:
I'll look at this.. want to test my SPI-RAM anyway (should be the same effect with RAM, right?)
Does this occur with slow SPI speeds too ?
 
I'll look at this.. want to test my SPI-RAM anyway (should be the same effect with RAM, right?) Does this occur with slow SPI speeds too ?

most likely, yes. anything that uses SPIFIFO.h and non-CS pins as CS seems to cause issues. as to speeds, i've tried with both 96MHz and 120MHz, respectively 24MHz/30MHz. makes no difference. i haven't tried going slower.

edit. it's strange in as much SPIFIFO.h is very robust otherwise / plays nice with SD.h. At this point, I've mostly given up but I'd still be curious whether it's related to pin choice per se (in which case, what's different, physically?) or whether the conflict is caused further downstream, ie in the SPIFIFO write function.
 
Last edited:
... so anyone knows what's so special about the hardware CS pins?

Normally, you'd have to use digitalWrite() or digitalWriteFast() or a pointer to the hardware register to assert (low) the chip select. Then you do the SPI transfers, and again manipulate the pin to de-assert the chip select. That approach is simple and easy to understand. With digitalWriteFast() or a register pointer, it's pretty fast too.

The hardware CS feature gives you a way to eliminate the small amount of time spent doing the register writes. But it comes at a cost of substantial complexity. Often the speedup is minimal, or even zero, or possibly even slower than the simple way, depending on how your code is designed. In practice, it's actually quite challenging to use this feature to really good effect.

But the feature itself if seductive, if you want to optimize for speed. The SPI port will automatically assert any combination of the 5 special chip selects for you. The pins automatically assert as it begins the transfer. You can choose to have the pins remain asserted, or automatically de-assert after the transfer completes. All you have to do is put the pin into a mode where it's controlled by SPI instead of GPIO. Then the SPI port controls whether the pin is high or low. Easy, right?

The hard part is you have to write the chip select info with every data byte, in advance. On the last SPI write, you have to write differently so the chip select will de-assert. Sometimes that's fairly simple, but sometimes code just isn't structured to do the last write differently than all the others. If you use the FIFO to good effect, you can prep all but the first data while the port is busy. But if you're not careful, it's possible to waste more time getting that first transfer ready than you would have spent with the simpler approach of pointers to the GPIO registers.

One place where the hardware CS signals really shine is the ILI9431 displays. Those displays have 2 signals, a chip select that needs to be asserted during a transfer, and an address bit that needs to be different states during command bytes and data bytes. Using hardware CS for both allows commands and data to be written into the FIFO, since the FIFO also carries the 5 CS bits. The FIFO is only 4 deep, and usually using 3 of the 4 is the fastest strategy (as least that I've discovered), but having those last 3 data bytes/words in the FIFO gives you a little time to get back to whatever drawing code is pushing the pixels. In a lot of cases, that lets you get the next command and its data into the FIFO before the last of those 3 data bytes is done. That's how ILI9341_t3 achieves most of its speed.

But leveraging the hardware CS and FIFO is quite difficult. If you read the old threads, you'll see it took me many tries to finally get ILI9341_t3 working well.

SPIFIFO was meant to make this stuff easier, but it's still quite difficult to make sure you balance every write with a read, while also generating the CS stuff ahead of time. Normal SPI library usage, where each thing you write in the code is a single and complete operation, and you write them in the order you want everything to happen, is so much simpler and so much easier to get right.
 
thanks, Paul. I have a basic understanding of what is going on with SPIFIFO vs normal SPI library. not that i see through the details (in either case) but what puzzles me is why SPIFIFO.h (which is used in Frank's W25Q128FV / audio library) just "works" with hardware CS but refuses to cooperate when *not* using any of the hardware CS pins. ie i'm wondering is this really/actually a "hardware" issue and that's it or is it about the code, thus fixable?

so when you say "it's quite difficult ... generating the CS stuff ahead of time" does/could that mean this bit of code would cause the issue: ?

Code:
else {
			*reg = 0;
			SPI0.SR = SPI_SR_EOQF;
			SPI0.PUSHR = (b & 0xFF) | (cont ? 0 : SPI_PUSHR_EOQ);
			if (cont) {
				while (((SPI0.SR) & (15 << 12)) > (3 << 12)) ;
			} else {
				while (!(SPI0.SR & SPI_SR_EOQF)) ;
				*reg = 1;
			}
		}

my understanding is it emulates the hardware CS behaviour.
 
Code:
	inline void write(uint32_t b, uint32_t cont=0) __attribute__((always_inline)) {
		uint32_t pcsbits = pcs << 16;
		if (pcsbits) {
			SPI0.PUSHR = (b & 0xFF) | pcsbits | (cont ? SPI_PUSHR_CONT : 0);
			while (((SPI0.SR) & (15 << 12)) > (3 << 12)) ; // wait if FIFO full
		} else {
			*reg = 0;
			SPI0.SR = SPI_SR_EOQF;
			SPI0.PUSHR = (b & 0xFF) | (cont ? 0 : SPI_PUSHR_EOQ);
			if (cont) {
				while (((SPI0.SR) & (15 << 12)) > (3 << 12)) ;
			} else {
				while (!(SPI0.SR & SPI_SR_EOQF)) ;
				*reg = 1;
			}
		}
	}

I think this is not correct. It writes 0 to CS ( *reg = 0; ) but does not wait for the fifo to be emtpy - there may be a transmission - before, the device could still transmit with a a native CS at this point, esp. when the fifo is filled.
I'll test this this evening.
 
Last edited:
I think this is not correct. It writes 0 to CS ( *reg = 0; ) but does not wait for the fifo to be emtpy - there may be a transmission - before, the device could still transmit with a a native CS at this point, esp. when the fifo is filled.
I'll test this this evening.[/QUOTE]

Hm, no, it must be something different. If SPIFIO is used correctly, there can't be an ongoing transmission.

But.. can you please try the following:
Code:
	inline void clear(void) __attribute__((always_inline)) {
		SPI0.MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x1F) | SPI_MCR_CLR_TXF | SPI_MCR_CLR_RXF;
               [B] [I]if (!pcs) *reg = 1; //<--add this line [/I][/B]
	}
And add SPIFIFO.clear(); in update() (at the end) in the flashplayer.

only as a test :)
 
Last edited:
ok, this does not help.
I was able to reproduce it.

crash.jpg

The red line is the CS SD-Card, the green line CS FLASH.
The skect plays from flash, and after a delay(10) the playing from SD-Card is startet

This is interesting, because its clear that CS Flash is correct.
After 10ms, CS SD-CARD goes low. This is correct, too.
But then..crash.. it stays low forever (not visible in this screenshot) and the teensy stops working.
I have to investigate this further..

Edit: This is from playing a wav only (no flash-access: )
wav only.png
CS is low very irregularly - and for very long times, much more than 3ms
hmm.. perhaps, my measuring is incorrect ?
if not, that's a problem, because the flash is not buffered and the flashplayer needs fresh data at 3ms intervals.

Its too late now, tomorrow more.


One last .... better this time (don't know what the above was(?):
wav only 2.png

At the right, we see the wav-file playing.
before that, the card gets initialized.
 
Last edited:
thank you, Frank, for looking into this. that's what i'm seeing, ie first image in #12. things crash as soon as SD comes into play again after doing anything SPIFIFO.
 
thank you, Frank, for looking into this. that's what i'm seeing, ie first image in #12. things crash as soon as SD comes into play again after doing anything SPIFIFO.

ok, i know a bit more. it happens in sd.open(). at this point the things become complicated...
sd.h uses the fifo too, but with it's own routines, not spififo.h
and it uses digitalWrite to set CS.

wired. without a debugger, the problem is hard to find.
 
Last edited:
ok, i know a bit more. it happens in sd.open(). at this point the things become complicated...
sd.h uses the fifo too, but with it's own routines, not spififo.h
and it uses digitalWrite to set CS.

wired. without a debugger, the problem is hard to find.

mmh, interesting. i figured SD was basically "safe" because of the transactions, so the issue must be with SPIFIFO / non-native CS.
 
Status
Not open for further replies.
Back
Top