interrupt driven SPI

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

interrupt driven SPI

5,992 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by victorvanacht on Wed Apr 07 07:00:23 MST 2010
I am wrtiting an interrupt-driven SPI driver for the LPC111x. On the SPI interface more than one hardware device will be connected. The chip-select lines of the different SPI devices will be connected to some GPIO lines.
So before the actual transfer of bytes starts, one of the GPIO lines has to be pulled low to select the appropriate device, and after the transfer has ended that GPIO line has to be pulled high again to deselect the device.

For this I need to know when the SPI transfer has actually been completed. Of course I can poll the SSP Status Register (bit 4) but I would prefer to receive an interrupt when all my bytes are transfered through the FIFO. Is this possible?

Can I mis-use the "Rx fifo not empty" or the "Tx fifo not empty" in any way for this purpose? How often do these interrupts trigger anyway? Every CPU clock cycle? Every SPI-clock? every SPI-frame?

[Note that very similar problems pop-up when switching between writing and reading from SPI: When is the data truely available??]
0 Kudos
Reply
13 Replies

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by walterperesson on Fri Feb 05 20:43:20 MST 2016
But that one will only tell you that DMA is ready, there may still be data in the SPI TX FIFO.


Thank you Rob65!!!

I´ve been working hard trying to figure out why there´s a ghost byte appearing in the beginning of my received string. The mistake: I´m using DMA interrupt  to know the end of transmission and reception. The following packet was sending very soon without TXBUSY confirmation.

After reading your post I added a timer to be shure TXBUSY is done. It´s working great now!!!

Thanks again.
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Rob65 on Mon Nov 12 00:35:39 MST 2012
Hi Volker,

Quote: Powerfreak

Please give me some hints again:
...
So if I take care about FIFO-state and busy bits it should be possible to speed up, right?


Hint 1: use a logic analyzer/digital oscilloscope to see what your SPI signals are doing.
Hint 2: start playing with the different settings of the SSP block.

The SSP can do more than just SPI. It's been a long time since I actually wrote my code but I do remember that there are different modes and some of these modes use the hardware SSEL feature of the SSP block.
But this means that after every 'frame' (which is just 8 - 16 bits) there is a delay since the SSEL pin is deasserted and asserted again during the idle time of the SPI bus. This means that your actual data rate is lower than the clock frequency of the SCK line.

About the FIFO depth: I never use hard coded FIFO depths in my code but always just write until the FIFO is full.

Rob
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Sun Nov 11 22:34:30 MST 2012
TBH I didn't understand that on the first pass, but before I try again what is the receiving device?

Can you send it 16-bit words? After all with SPI 2x8 bits is the same as 1x16 bits so if the receiver can keep up you can transmit words instead of bytes.

That will halve your overhead.

EDIT: Interrupts are always slower than polling, do you need to do other things at the same time? The fastest way to send data is in a very tight polling loop, the catch is of course you can't do anything else.


Quote:
Question2: Is the size of the FIFO only 8x16bit or can I use it as  16x8bit if SPI definition is 8Bit?            I assume (unfortunately)  only 8x16Bit, right?

I see now what I said above relates to this question. So yes, you can effectively have a 16x8 IF you load the FIFO with words (concatenated bytes) and not single bytes and set the frame length to 16.
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Powerfreak on Sun Nov 11 08:24:30 MST 2012
Hi Rob,
thanks for your information.

So this means, that I do have to poll the busy-flag... ok.

Please give me some hints again:
I wanted to speed up the SPI transfere. (all values at 18MHz SPI-clock rate and 72MHz CPU-clock)
Background: I thought, that the eg. LPC1313 has a 8 stage buffer (according datasheet) and/but I had to learn, that I cannot fill this 8 stage buffer as I wish to do... NO, I had to check if the buffer is full " /* as long as TNF bit is set (TxFIFO is not full), I can always transmit */     while ( !(SSP0SR & SSPSR_TNF) ); "  (By the way: this "while" costs app 120ns in average, so I don't care)
Question1: the FIFO is 8x16 bytes long. If I do know I will be within this range, do I really need to check of the FIFO is full?
            I assume no (had no faults during test for 8 Bytes)
Question2: Is the size of the FIFO only 8x16bit or can I use it as 16x8bit if SPI definition is 8Bit?            I assume (unfortunately) only 8x16Bit, right?

The second while is demanding more time and importance
"/* Wait until the Busy bit is cleared */
    while ( !(SSP0SR & SSPSR_BSY) );"
Question3: If the SSP Interrupt is activated, all error related INTs enabled "SSP1IMSC = SSPIMSC_RORIM | SSPIMSC_RTIM;"     (see all NXP examples)  AND I make no dummy-read @ "SSP0DR" OR I comment this "SSPSR_BSY-while" I immediately
    get a "hard fault". I set a breakpoint in my interrupt, but the hard fault occures before my breakpoint was reached (LPC-Link)
    Do you have any idea why? I only expected an interrupt! (yes, in my interrupt I made a dummy read and cleard the int-flag) Ideas are welcome
   

   
Back to my first target... to increase the bit rate means, to reduce the "byte to byte time" = the gap between two 8Bit messages...
The time from start of byte1 to start of byte2 WITH the two "while" loops is 3,32µs.
If I made a comment for the first and second AND disable SSPIMSC_RORIM and SSPIMSC_RTIM I have 2,36µs and no hard fault any more. So the "/* Wait until the Busy bit is cleared */" takes app 1µs which is app 72clock cycles

So if I take care about FIFO-state and busy bits it should be possible to speed up, right?
(I only need MOSI=I am only transmitting!)

Volker
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Rob65 on Sun Nov 11 00:52:16 MST 2012
You are right, there is no interrupt to confirm that a transaction has completed.
The interrupt that is there (TX FIFO half empty) is there to make sure that you are not running out of data on large transfers.

I still have found no other way than just poll the BSY bit in the status register.

Using the DMA interrupt (on chips that have DMA attached to SPI) will give you a transfer ready interrupt. But that one will only tell you that DMA is ready, there may still be data in the SPI TX FIFO.

In Pellek's code I noticed:
delay_div10_LPC(1);

inside the SSP1_IRQHandler. It's a bad idea to have something like a delay (any type of delay) in an interrupt handler. Even stuff like polling a status flag is not done.


Quote:
Can I mis-use the "Rx fifo not empty" or the "Tx fifo not empty" in any  way for this purpose? How often do these interrupts trigger anyway?  Every CPU clock cycle? Every SPI-clock? every SPI-frame?


There is no tx fifo not empty interrupt.
The Rx FIFO not empty will only become active once each time the RX fifo goes from empty to not empty.

One thing to know when a transfer is complete is to use the TX FIFO half empty interrupt and use that one to start a timer interrupt. When you know the timing of your SPI port, you also know when the transfer is complete (i.e. exactly a given number of cycles later). Then in the timer interrupt you check the BSY bit. It should be done by then. If not, just schedule a new timer interrupt a little bit later to check again.

It all depends on your application. I use one SPI port attached to two devices. I leave the chip select line of the device just active until the moment when I want to start my next transfer.
Works for me ... All devices I have use an internal 'transfer complete' mechanism to act on. The CS lines are just used to enable/disable listening on the SPI port.

Rob
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Powerfreak on Sat Nov 10 09:57:52 MST 2012
unfortunately I'm searching the same... an SPI interrupt, when sending is done == buffer is empty.
If anybody has a good idea for this, please dont't hesitate :)

How do you solve this kind of issues without a "done" interrupt?
polling the busy-flag :confused: (which i do not really like)
I have several SPI-chip-selects...


Thanks
Volker
0 Kudos
Reply

3,389 Views
brianknittel
Contributor II

Oops, I replied to my reply. Set a GPIO rising edge interrupt on the hardware SSEL output pin and you will get an interrupt when the transfer is complete. I am successfully using this method with LPC11U24 and I assume it will work on others. If you are using multiple SSEL outputs that you are controlling manually, you still want to enable the hardware SSEL output on one of the available muxed pins, but leave it disconnected. The GPIO interrupt hardware will still monitor what's being driven on that unused pin and you'll get an interrupt when the transfer finishes. Then you can manually raise the SSEL output that you were using.

0 Kudos
Reply

4,017 Views
brianknittel
Contributor II

[EDIT: see the follow-up post I made on March 10, 2023. You don't need to connect the SSEL output back to an input pin, you can fire an interrupt from the output pin]

How about this: Enable an SSEL output pin and connect it back to another GPIO input pin, and use a edge (0->1) interrupt on that pin to detect the rising edge, which means that the transfer has completed. This would work, right? If you have multiple SPI devices on the channel, you will still have to drive their chip selects manually, but, you can still use the channel's automatic SSEL output to detect its rise to signal end of transmission.  This at least will let you avoid polling.

0 Kudos
Reply

3,390 Views
brianknittel
Contributor II

This works! And you don't have to connect the SSEL output back to an input pin., You can set a GPIO edge interrupt on the SSEL output pin and you get an interrupt when SSEL is released. This works on LPC11U24 and I would assume on others.

0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pellek on Mon May 14 07:37:08 MST 2012
I read multiple times the datasheet and my code, i think SPI properly initialized in "CPL_init()", or hope so ^^

spi_read() set the 4th and 5th wires i spoke of, to initialize the communication, then it clears a GPIO(named CS) that is directly connected to SSEL1 (to create a fake software Chip select). I have then added a delay of 10 ms (but it should be __WFI once it s working)

int main(void) {
    
    CPL_init();
    delay_LPC(8000);
    while(1)
    {
        k=0;
        spi_read();
        delay_LPC(16);
    }
    return 0 ;
}


Only the interrupt on half-full buffer is unmasked :
(i got the answer of my previous question, interrupt occur on half of maximum size :), so half of 16 bits, and end of my 8-bit message )
void SSP1_IRQHandler (void){

    if(LPC_SSP1->MIS & 0x4){
        cfg[k]=LPC_SSP1->DR;

        if(k<2)k++;
        else k=0;
        CS=1;
        BU_Therm=!BU_Therm;
        delay_div10_LPC(1);
        CS=0;
    }
    return;
}

BU_therm is nothing but a GPIO i use as a hardware flag (reading it on an oscilloscope)


Still 2 problems :
- No interrupts on the 3 first loops of my main.
- if "__WFI();" , it won't wake up althrough i configured interrupts as follows :
   LPC_SSP1->IMSC |= (0x01 << 2);
    NVIC_EnableIRQ(SSP1_IRQn);
    NVIC_SetPriority(SSP1_IRQn,0);
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pellek on Mon May 14 07:25:07 MST 2012
I forgot to precise I'm using the LPC11U14, I haven't seen a DMA in the datasheet, there is one ?
Or is it only in LPC17XX serie ?

If you got any interrupt-driven SPI, I'd be glad to read some :)

For now, i manage to get interrupts at the good moment, but not when in sleep mode (i'm using only Sleep mode, no deepsleep or pwrdwn).
Worse, i get interrupts only from the 4th communication, the 3first ones aren't responding (always the same number of tries before receiving...)
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Superfred on Mon May 14 03:02:37 MST 2012
Hello,

use SPI with DMA, the DMA controller gives you the "transfer ready" interrupt.

I modified the SSPDMA code in the MCB1700 examples, i can post it if you need it.

Fred
0 Kudos
Reply

4,685 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pellek on Mon May 14 00:12:53 MST 2012
Hi,
I'm currently working on a similar project.

I am trying to realize an interrupt-driven SPI too, with N*8-bit framed messages.

The only interrupts that seem available are those when buffer is half-full or half-empty. Is it half the maximum size (16 bit), or half the size selected in CS0->DSS ?

/edit : the SPI i want to do is not 'traditionnal' : my µc is slave, however, the master (providing the clock) has no Chip-Select (SSEL), but requires its slave to issue signals on a 4th and 5th wire. (I know it's 'wired' but no choice ^^)
0 Kudos
Reply