Double-buffered SPI: Detecting last byte shifted

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Double-buffered SPI: Detecting last byte shifted

2,373 Views
Wingsy
Contributor I

I've had this question/problem before and I've always been able to work around it, but I'm never happy with my solution. I'm still not.

 

I've got a GB60, doing an SPI transfer to an SD card at 8.67MHz. I've also got a high priority interrupt pounding me at 22KHz. What I'm trying to do is to exit from the SPI routine as quickly as possible once the last byte is shifted out, but detecting when this last byte is gone is what's bugging me. (Yes, I can delay X number of cycles to insure the transfer is over with but surely there is a more efficient solution.) The SPTEF bit tells me when the Tx buffer can accept another byte, and the SPRF bit tells me when a byte is shifted in and/or when the current byte in the shifter has been shifted out. But I can't tell when that last byte is in the process of being shifted out. (Can I?) Here's the code that does the transfer:

 

SPITXB MOV X+,SPID  ;(5) SEND DATA. BRCLR SPTEF,SPIS,* ;(5) WAIT FOR TX BUFFER EMPTY. DBNZA SPITXB  ;(4) LOOP FOR ACCA BYTES (IF A WAS 0 THEN SENDS 256). TST SPID  ;CLR SPRF (WAS SET WHEN SPTEF WAS SET). BRCLR SPRF,SPIS,* ;WAITS FOR TX OF LAST BYTE. RTS

 

 

This works until an interrupt comes along (say just prior to the TST instruction) that steals enough cycles so that the last byte is already gone and the next-to-last line hangs since SPRF has already been set, and cleared. I *could* set & clear the interrupt mask in the loop but doing so would reduce the maximum transfer rate that I have now, and I need all the speed I can get at this point in my code. Also, I'd like something that doesn't block interrupts so I can still use the code later on when I have something with a much slower SPI clock.

 

Suggestions would be welcomed.

Labels (1)
0 Kudos
10 Replies

1,025 Views
jose__
Contributor I

well is an old post, but now i was looking for something to solve exactly the same situation and i could give a trick/solution. The trick is take the normal CS pin and route to other input pin, and use this input pin as ack of trasmision end. This input pin can interrupt ic showing that the SPI have finish the transmission of the whole block.

Well i know you have to waste 2 extra pins of the ic, but you have the absolute control, no need to add waiting periods (nop's of whatever you like) inside interrupt vectors functions.

This make sense for long periods clk SPIs, for fastest clks its dosen't make sense as far the other persons have said.

0 Kudos

1,025 Views
bigmac
Specialist III

Hello,

 

Firstly, with the use of a fast SPI clock, there would be little point in attemptiing to use SPI interrupts because the cycle overheads associated with the entry to, and exit from the ISR will be greater than the byte transmission cycles of the SPI.  For these clock rates, it is more efficient to use polling.

 

The polling loops used, whilst waiting for a flag to become set,  have a five cycle period.  For a 16 cycle byte transmission period, this would mean that the polling loop would repeat four times, for a total of 20 cycles.  There would be a small saving of maybe 3 or 4 cycles with the inclusion of one or two NOP instructions prior to the polling loop, so that the loop would repeat only three times, instead of four.

 

I have my doubts about using double buffering capability for a SPI master (it is of more use for a slave application).  If you are sending N bytes in succession, the transmission time for all bytes will always be 16*N.  Any differences in the total period will depend on the overhead required between each byte.

 

In usual circumstances, a SPI transmission is a bi-directional process, and the aim of the code is to prevent receive overrun, and subsequent loss of receive data.  To achieve this, the SPRF flag must be polled and cleared after each byte.  If this is done, timing is non-critical and interrupts occurring during code execution will not be a problem - they will simply delay the completion of code execution in the usual manner.

 

Once you choose to use the double buffering capability by polling the SPTEF flag instead, the timing becomes critical to prevent a receive overrun condition, and interrupts would usually be globally disabled to alleviate timing problems.  In this case, if it is not permissible to disable interrupts, it is not permissible to use double buffering, especially for very high SPI clock frequencies.

 

For the SD card case, where you appear to have no interest in the returned data, even though receive overrun is not an issue, interrupts can still play havoc because when you eventually clear the SPRF flag preparatory to detecting the completion of the final byte, there is the uncertainty that you have described.  Since you cannot disable interrupts for your application, the uncertainty seems only to be eliminated by clearing the SPRF flag at the conclusion of each byte.  If this is done, the status of the SPTEF flag is unimportant (since the flag will always be set when SPRF becomes set).  The timing penalty would appear to be an additional four cycles between each byte, for the purpose of clearing the SPRF flag.

 

SPITXB: MOV   X+,SPID       ;(5) SEND DATA.        NOP                 ;(1)        BRCLR SPRF,SPIS,*   ;(5*N) WAIT FOR SEND COMPLETE        TST   SPID          ;(4) CLEAR FLAG        DBNZA SPITXB        ;(4) LOOP FOR ACC BYTES        RTS

 Regards,

Mac

 

0 Kudos

1,025 Views
Wingsy
Contributor I

Thanks for your input Mr. Mac.

 

So I'm going to conclude that there is no straightforward way to determine when that last byte is in the process of being clocked out. What seems to work for a high speed SPI clock appears unsuitable for a low speed clock, and vice versa. In the end, this is what I have for my code:

 

SPITXB MOV X+,SPID  ;(5) SEND DATA. BRCLR SPTEF,SPIS,* ;(5) WAIT FOR TX BUFFER EMPTY. DBNZA SPITXB  ;(4) LOOP FOR ACCA BYTES (IF A WAS 0 THEN SENDS 256). BSR SPITXX  ;(11) ALLOW LAST BYTE TO SHIFT OUT.SPITXX RTS   ;(6) RETURNS TO CALLER AFTER LAST BYTE SHIFTED.

 I can live with it. It transmits at the maximum possible speed and interrupts no longer break it. It won't work at slow SPI clock rates (relative to the busclk) so something else will have to be done for that. I just wanted to know if there was a method someone has devised that works well for both fast & slow speeds, and something that allows it to transfer at top speed and not block interrupts. Something I can use over & over without having to tinker with it.

 

0 Kudos

1,025 Views
bigmac
Specialist III

Hello Wingsy,

 

I think I should add that the sample routine of my previous post will work for any reasonable SPI clock rate, but has actually been optimised for the fastest clock rate.  The loop period required to send each byte at the fastest rate would be 29 cycles, an overhead of 13 cycles per byte.  Additionally, receive overrun never occurs, so the method may be easily adapted to receive the return data from the SD card.

 

The transfer of 256 bytes would require in the vicinity of 860 microseconds.  To this must be added the time required by about 19 interruptions at 22kHz.

 

By comparison, the routine that you have suggested is limited to the fastest clock rate only, but also currently has a loop period of 29 cycles per byte (except for the first byte).  Additionally, the method cannot be adapted to receive return data.  For this you would probably end up using something like my method.

 

Incidently, if you were to add the extra NOP to your routine, the loop period should reduce by 4 cycles per byte.  This is because the SPTEF flag should become set after three repeats of the polling loop (15 cycles), rather than four repeats (20 cycles).

 

Regards,

Mac

 

0 Kudos

1,025 Views
Wingsy
Contributor I

"By comparison, the routine that you have suggested is limited to the fastest clock rate only, but also currently has a loop period of 29 cycles per byte (except for the first byte).  Additionally, the method cannot be adapted to receive return data.  For this you would probably end up using something like my method."

 

Actually, no. It runs at 18 cycles per byte. Because I am testing the SPTEF bit, which is set when the Tx buffer is transferred to the shifter and the Tx buffer is free to be loaded with another byte while the shifting takes place. Your routine is testing the SPRF bit, which is only set when the SPI Tx shifter AND the Tx buffer is empty, meaning you'd have to add your loop time to the shift time of every byte. It's not taking advantage of the double buffering in the SPI.

 

The reason it's 18 cycles per byte and not 16 like you might expect is that the SPI eats 2 cycles to transfer the Tx buffer into the Tx shifter. The data rate I'm getting is 963KB/s, or 256 bytes in around 265uS with my interrupts turned off, and a little longer with them turned on.

 

And for receive, I do almost exactly as you suggest in your code block... test the SPRF in the loop, except I start another transfer before I actually pick up the byte and test the loop count, to gain a little more in speed. In my receive I'm getting a 613KB/s transfer rate.

 

All this may seem academic but I've got myself getting simultaneously hammered by the SD card, the radio chip and the FSK chip (not to mention the audio interrupts), so I really do need to get things done as fast as possible. My preference would be a second SPI port. I'd also like to have seen a "TC" bit in the SPI like is present in the SCI.

0 Kudos

1,025 Views
PeterHouse
Contributor I

Running an SPI transfer at 8.67 MHz means that the bit time is only 115nS and the byte time is only 923nS.  Depending on your processor speed, most likely, your loop never loops since the byte is already shifted out by the time your test instruction executes.  At most,  one or two NOP instructions would insure your byte is already gone with no need for testing. 

 

I have done this with excellent success using a GB60 and assembler.  As I recall, I did have to read the status register into the A register to clear the Tx Ready interrupt since each byte of my transmission was interrupt triggered - at the end of the transfer, the interrupt was turned off until the next transmission.

 

Good Luck,

 

Peter House 

0 Kudos

1,025 Views
Wingsy
Contributor I

Thanks Peter. You're right, the last byte out would be completed before I have time to do just about anything. Guaranteed by adding a NOP or 2. That's what I'm doing, but I only get by doing it this way since my SPI clock is 1/2 my bus clock. For a much slower SPI clock that could change significantly. Was looking for a solution that is independent of SPI clock and bus clock speeds.

 

And the SPRF hasn't quite had time to set before the 1st test of the SPRF bit, so the code I posted works OK and does what it's supposed to do... that is, until my IRQ steals a few cycles and causes the last byte to have already set the SPRF before I get there to test it. Doing away with that last test of SPRF and just adding a few NOPs fixes this, for this case. I've scratched my head so many times over this last-byte-shifted thing that I was looking for something that fixes it once & for all for every case.

 

(My last byte MUST be completely shifted out by the time the routine exits since the first thing the calling routine does is to de-assert the chip-select for the SDcard. And this same SPI routine is used to communicate with a DTMF/FSk chip, a flash memory chip, and a CC1100 radio chip.)

0 Kudos

1,025 Views
PeterHouse
Contributor I

Here is how I use the SPI to send multiple bytes with a single chip select:

 

To send an SPI packet, the SPIStart routine fills a data buffer with the data to send including a counter byte (Set to number of bytes) and index byte (set to 2), sets chip select, enables the SPI interrupt, and then transfers the first byte into the SPI TX register.

 

When each byte is transferred into the shift register (TX Buffer Empty), the interrupt routine will: use the index byte to get the next byte, increment the index, decrement the counter, send the byte, and exit.  On the last byte (counter = 0), the interrupt will disable the SPI interrupt, wait for the shift to complete, clear the chip select and exit.

 

It is really very simple code and easy to use and will work for all speeds since the interrupt is generated on the data transfer to the Transmit shift register.

 

For bidirectional transfers use you have to have a receive buffer.  The Nth interrupt will have to read the Nth Receive byte before writing the Nth+1 transmit byte.  The Last interrupt will have to read the Nth+1 receive byte after the Nth+1 transmit byte is shifted and before clearing the chip select.

 

Good Luck,

 

Peter

0 Kudos

1,025 Views
tonyp
Senior Contributor II

I'm not sure if this rearrangement of your code will work better, but based on my understanding of the related flag clearing mechanism, it should:

 

 

SPITXB BRCLR SPTEF,SPIS,* ;(5) WAIT FOR TX BUFFER EMPTY.
        MOV X+,SPID  ;(5) SEND DATA.
 DBNZA SPITXB  ;(4) LOOP FOR ACCA BYTES (IF A WAS 0 THEN SENDS 256).
 BRCLR SPRF,SPIS,* ;WAITS FOR TX OF LAST BYTE.
 TST SPID  ;CLR SPRF.
 RTS

This way, BRCLR SPRF immediately follows the write to SPID (no BRCLR SPTEF is in between to read the SPIS), so the clearing of SPRF should occur only during the TST SPID (unless your interrupts also mess with any of these registers).

 

But, maybe I'm missing something obvious.  (I haven't tried it.)

 

0 Kudos

1,025 Views
Wingsy
Contributor I

But the SPRF would already be set when tested, as it becomes set at the completion of every transfer and is only cleared by a read of SPIS (with SPRF set) then a read of SPID. So the routine would exit while that pesky last byte is still being shifted out.

 

With an SPI clock rate of 1/2 the bus clock (its top speed) then this is not a major problem since that last byte would be out 16 clock cycles after it was started. But for a slow SPI clock it could take a while.

0 Kudos