Skip to main content
Topic: Possible latency issues with bin mode SPI / serial comms? (Read 5735 times) previous topic - next topic

Possible latency issues with bin mode SPI / serial comms?

I think I may be encountering some latency issues with bin mode SPI under Linux. Admittedly I am trying to push a lot of data across the wire, but certainly not more than the bus pirate hardware can handle.

My (Ruby) script is designed to drive a Nokia 3310 LCD. I plan to conribute my script (+ connection diagram) later in the week, once the code is beautified a little.

To write all 84x48 pixels requires 504 bytes of data. This takes 32 SPI bulk transfers, so 536 bytes are written to the bus pirate, and 536 are read back (32 acknowledges, 504 bytes of data).

My code writes bytes to the serial port, and reads bytes from it in lockstep -- write, read, write, read, etc. And I think this is where the latency issue occurs.

Basically, writing the 536 bytes is exceedingly slow -- it takes quite a few seconds. Roughly speaking, it should take only 1/20th of a second to send this data at 115200 baud. The bus pirate should be able to deal with the data very quickly, especially with 8MHz SPI mode enabled.

I think that the slowness comes from the OS having to timeout after each write, and then timing out after each read. It make sense that the OS would wait a little while for more data to arrive, given the overhead of constructing a USB transfer for a single byte; similarly, I can imagine that the FT232 would wait a little while to see if more data was on its way from the bus pirate.

I can't see an elegant way of getting around around this latency issue per se. I tried simply not reading from the serial port after each data byte in the bulk transfer is sent. Suddenly the 504 bytes are transferred instantaneously. But then on sending subsequent commands to the bus pirate, the script gets back this data (because it is buffered somewhere) when it should be getting back acknowledgements.

Perhaps, I am missing something about interfacing with serial ports? Has anyone been able to transfer this amount of data across SPI at speeds even close to the upper limit imposed by 115200 baud?

If, indeed, I am right about this, would it be possible to extend the protocol to allow a flag to be set to turn off the transmitting of reads in response to every SPI write? Many devices (including many LCDs) are write only devices, so this could come in handy in a lot of cases. This would mean that my script could stay in sync.

What do people think about this suggestion? Am I trying to push the bus pirate beyond its intended purpose?

Cheers,

blue.zener

Re: Possible latency issues with bin mode SPI / serial comms?

Reply #1
There's probably three layers of buffer between the script and the Bus Pirate:
1. PC OS and FTDI driver have a big buffer. USB transfer latency isn't great already, but your OS will wait for a certain number of bytes (or time) to accumulate before it sends a packet to the chip.
2. The FTDI chips also has a quite large internal buffer, and it feeds bytes to the Bus Pirate at 115200.
3. The Bus Pirate has a three byte deep internal UART buffer.

And it has to do it all again for a single byte on the way back.

I'm not sure the best way to handle this. I'll add a silent mode write like you suggest, that should help.

Really, though, you should be able to send all the data and the OS should buffer it to the chip without issue, sending 0x01 and reads when possible. Let's try to figure out what's going on there.
Have you tried to break it down into smaller (128 byte?) segments?
Are you clearing the PC UART after all the reads come in?
You mention that script gets back the data it sends instead of acknowledgements - could you please elaborate? The bulk SPI command only responds 0x01 after the bulk read command, then it responds with a read from the SPI port for every byte written.
Got a question? Please ask in the forum for the fastest answers.

Re: Possible latency issues with bin mode SPI / serial comms?

Reply #2
[quote author="ian"]
There's probably three layers of buffer between the script and the Bus Pirate...
[/quote]

Yes - I was oversimplifying things a bit... there is also any buffering that the Ruby IO class does. Although this can be circumvented by setting .sync=true (for writes at least), which I am doing.

[quote author="ian"]
I'm not sure the best way to handle this. I'll add a silent mode write like you suggest, that should help.
[/quote]

Cool - I think that the silent mode would be a neat addition.

[quote author="ian"]
Really, though, you should be able to send all the data and the OS should buffer it to the chip without issue, sending 0x01 and reads when possible. Let's try to figure out what's going on there.
[/quote]

OK - so if I understand you right, I should just be able to do all my writes -- say 1 + 16 bytes for a maximum size bulk transfer and then (after all the writes) just do 1 + 16 reads -- rather than what I am doing at present, namely, write one byte, read one byte, write one byte, read one byte, etc.

What worries me about this approach is that it makes the assumption that the intermediate buffering can handle at least 17 bytes. This is probably a fair assumption to make, but it is nevertheless an assumption, and if for some reason this was not the case, the code would break.

[quote author="ian"]
Have you tried to break it down into smaller (128 byte?) segments?
[/quote]

The writes, as mandated by the bulk transfer command, are broken down into 16 byte segments.

[quote author="ian"]
Are you clearing the PC UART after all the reads come in?
[/quote]

I can't see a way of doing this using the Ruby serialport library. Am I right to think that if I could work out a way to do this, I could just clear the UART after I do all my SPI writes and all the echoed back reads would be discarded?

[quote author="ian"]
You mention that script gets back the data it sends instead of acknowledgements - could you please elaborate? The bulk SPI command only responds 0x01 after the bulk read command, then it responds with a read from the SPI port for every byte written.
[/quote]

Sorry for being unclear. By this, I just mean that if I do my SPI writes and simply ignore the reads echoed back, these reads build up in the (FTDI/OS/IO class) buffer. Consequently, when I issue a subsequent command (say to configure peripherals) and do a  read, I get the oldest byte in the buffer back, which corresponds to one of the echoed SPI reads, rather than the acknowledgement (0x01).

I'm pretty sure that this could be got around by clearing the UART as you suggest, just before issuing the subsequent command. Writing my original post, I wasn't aware that this could actually be done. If it can be done, then chances are it can be done by some means using the Ruby serialport library -- I'll try to find out. I guess if it can't be done it might be time to look at another serial port library -- or switch to the forthcoming silent mode.

Cheers,

blue.zener

Re: Possible latency issues with bin mode SPI / serial comms?

Reply #3
Quote
What worries me about this approach is that it makes the assumption that the intermediate buffering can handle at least 17 bytes. This is probably a fair assumption to make, but it is nevertheless an assumption, and if for some reason this was not the case, the code would break.

I don't know anything about Ruby.... But I would assume that the serial port won't fail silently. I think (didn't check the datasheet) that the FTDI chip has a 128 byte buffer, and the system driver should have 1000's, if not 'unlimited' buffer space for outgoing serial TX. At the very least, the Ruby library should block the write until buffer space is available.

Quote
I can't see a way of doing this using the Ruby serialport library. Am I right to think that if I could work out a way to do this, I could just clear the UART after I do all my SPI writes and all the echoed back reads would be discarded?

There's usually a flush command for serial com libraries. In the Perl demo scripts I just used the read command without a character limit to get everything currently in the buffer (which clears it).

Quote
Consequently, when I issue a subsequent command (say to configure peripherals) and do a  read, I get the oldest byte in the buffer back, which corresponds to one of the echoed SPI reads, rather than the acknowledgement (0x01).

It sounds like you're almost there.  It sounds like the reply bytes are (correctly) being held in an internal system buffer, you just need to read and discard the proper number of bytes before trying to get the response you're interested in.

Something like:

1. Write 536 bytes
2. read and discard 536 bytes in a single command or a for loop
3. Issue next command.

maybe?
Got a question? Please ask in the forum for the fastest answers.

Re: Possible latency issues with bin mode SPI / serial comms?

Reply #4
[quote author="ian"]
There's usually a flush command for serial com libraries. In the Perl demo scripts I just used the read command without a character limit to get everything currently in the buffer (which clears it).
[/quote]

You're a genius Ian! I took another trawl through the Ruby IO docs and found "readpartial(maxlen)" which will read up to maxlen bytes from a stream. It will only block if no data is available from the stream. By calling this with maxlen set to 536 bytes, I can effectively clear the buffer, so that after the next command I issue, the next byte read is the acknowledgement.

Thanks for your help -- I will post a link to my script in the next day or so.

Cheers,

blue.zener


Re: Possible latency issues with bin mode SPI / serial comms?

Reply #6
Really great job on the write up. I posted it on the blog, and will add it to the manual.
Got a question? Please ask in the forum for the fastest answers.

Re: Possible latency issues with bin mode SPI / serial comms?

Reply #7
...adding, the refresh rate is fantastic for 115200bps! Great work.
Got a question? Please ask in the forum for the fastest answers.

Re: Possible latency issues with bin mode SPI / serial comms?

Reply #8
Cheers!

I too was impressed by the refresh rate. I guess the time the bus pirate spends setting up and executing an 8Mhz SPI transfer is negligible. So it comes down to the speed of the serial line. At 115200 8N1, the best case is 11,520 bytes per second. At 536 bytes per frame, that's a theoretical maximum of 21.5 frames per second.

In practice I'm not getting quite that refresh rate, but I am inserting a brief delay (50ms) before I do my non-blocking read, to make sure any reads that have been buffered have been flushed. This effectively adds a 50ms delay between frames.

Now to find something else to hook up to the bus pirate...

b.z