Shop OBEX P1 Docs P2 Docs Learn Events
My SPI communication is taking a long time... can't figure out why. I am measuring elapsed time — Parallax Forums

My SPI communication is taking a long time... can't figure out why. I am measuring elapsed time

Hey guys,
I am measuring elapsed time over various functions and displaying it through the Parallax Serial Terminal.
I noticed that my SPI functionality is taking a long time (it takes 105 milliseconds to execute the SPI function below).

Here is my method for measuring elapsed time:
Time := -cnt
'Functions to time go here
nRF.ReadPayload
Time += cnt - 544
PST.Str(string(13,"Time to read payload: "))
PST.Dec(Time / (clkfreq / 1000)) 'Display in milliseconds

Inside the ReadPayload function I started commenting stuff out so I could see what is taking so long, turns out the "Read RX Payload" part is what takes 105 milliseconds! Both the "Low(SPI_Ce)" and the "High(SPI_Ce)" take only 82 us (microseconds) each to execute.
PUB ReadPayload | idx, fifoRegister

  'Stop receiving
  Low(SPI_Ce)
    
  'Read RX payload   
  Low(SPI_Csn)    
  SpiReadWrite(R_RX_PAYLOAD) 
  repeat idx from 0 to 31
    payload[idx] := SpiReadWrite(NOOP)
  High(SPI_Csn)

  'Start receiving
  High(SPI_Ce)

So I dug into the "SpiReadWrite" function:
PUB SpiReadWrite(byte_out) : byte_in | bit
{{
  SPI read-write procedure (8-bit SPI mode 0)
  Read and write are synced, i.e. for each byte in it is one byte out
  For deatails see: http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
}}

  byte_in := byte_out

  repeat bit from 0 to 7
    'Write MOSI on trailing edge of previous clock
    if (byte_in & $80)
      High(SPI_Mosi)
    else
      Low(SPI_Mosi)
    byte_in <<= 1
 
    'Half a clock cycle before leading/rising edge
    TIME.PauseUSec(1)
    High(SPI_Sck)
 
    'Half a clock cycle before trailing/falling edge
    TIME.PauseUSec(1)
 
    'Read MISO on trailing edge
    byte_in |= Read(SPI_Miso)
    Low(SPI_Sck)

  return byte_in


PUB PinDelay(Pin)
  if Pin == SPI_Ce
    waitcnt(clkfreq / CE_DELAY + cnt) 'CE_DELAY = 100_000
  elseif Pin == SPI_Csn
    waitcnt(clkfreq / CSN_DELAY + cnt) 'CSN_DELAY = 100_000

  
PUB High(Pin)
    dira[Pin]~~
    outa[Pin]~~
    PinDelay(Pin)

       
PUB Low(Pin)
    dira[Pin]~~
    outa[Pin]~
    PinDelay(Pin)
      
    
PUB Read(Pin)
    dira[Pin]~
    return ina[Pin]

So I don't understand what the problem is here? Shouldn't this be executing like a 100 times faster? Thanks and any help is greatly appreciated!

Comments

  • Depending on the device, you can very likely remove the pause and delay calls altogether. Spin is not fast - on the order of 400x slower than assembly.

    It would also be worth moving the contents of High and Low into your read/write function, and only setting dira[Pin]~~ once, instead per iteration in the loop.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2016-05-04 23:26
    If chip enable "only" takes 82us that is a cause for alarm. By way of example my Tachyon SPI read and write instructions which run on the same cog enable the chip enable and read or write 8-bits in one instruction and takes only around 2us to execute so by the time Spin has simply enabled the chip I have had enough time to transfer 41 bytes. The truth is Spin is too slow for this stuff but to use PASM you need to use another cog and then spend time communicating with it. It looks like the code you are using hasn't been optimized for speed either as has been mentioned.
  • MahonroyMahonroy Posts: 175
    edited 2016-05-05 01:21
    Thanks for the heads up so far!
    So it looks like I really need to get my hands on a SPI object thats written in assembly, and use that in place of my functions? I'm still shocked that its that much slower... so even the wait instructions are probably not accurate either.

    EDIT:
    So if I am reading the datasheet correctly for the nRF24L01+, it looks like it can handle an 8Mhz SPI clock speed.
    I found the SPI_Asm.spin object, but I am a little confused what value to use for start(Delay,state). So state should be 0 so the Clock starts low.... but what is an appropriate delay to use? So 8 mhz is 0.125 microseconds correct? it says if I use a delay of 5 that is 500 nanoseconds, and a delay of 15 is 1000 nanoseconds. So it seems I could use the smallest delay e.g. 1 for the fastest speed?

    Thanks again for the advice!
  • Mahonroy, I see a few errors in your code. As far as what you've written the only delay required is Spi_ce. "CE" refers to the radio. To transmit data it is held high for 10us, maybe 8us. For the PRX module it stays high until you get an RX fifo interrupt.
    If you want, I will post some code that just increments. It's only one byte at a time but it has no delays.
  • lardom wrote: »
    Mahonroy, I see a few errors in your code. As far as what you've written the only delay required is Spi_ce. "CE" refers to the radio. To transmit data it is held high for 10us, maybe 8us. For the PRX module it stays high until you get an RX fifo interrupt.
    If you want, I will post some code that just increments. It's only one byte at a time but it has no delays.

    Sure that sounds good. So you are saying the CE or CNS does not need a delay? I was under the impression that it did. The CE is cycled high then low to trigger the nRF to transmit.
  • Spin is slow enough that
    outa[pin]~~
    outa[pin]~
    
    takes on the order of 10 us. So just drop the delay, and use back to back output assignments.

    If that's too short, turn the pin on two or three times, without turning it off, just to waste time.
  • lardomlardom Posts: 1,659
    edited 2016-05-05 03:39
    I'll post it tomorrow. In mode zero whenever csn goes low it says that data is going to be sent. When it goes high it's finished sending data.
    Whenever csn goes low sck cycles high and low eight times. In mode zero data is read(clocked in) when sck is high. Then there will be a short pause before the next byte begins.
    The four spi pins are mosi, miso, csn and sck.
    Irq signals that data has been sent or received and ce controls the radio.
  • The driver was written by Duane Degn. I tweaked it a bit because I was only interested in controlling motors as opposed to sending byte strings.
    There is no 'Shockburst' since it slows things down noticeably.
  • If you want to run Spin code faster you can compile it to PASM with fastspin or spincvt (https://github.com/totalspectrum/spin2cpp/releases). The output is 2-4x bigger than regular Spin bytecodes, but runs 4-10 times faster.
Sign In or Register to comment.