I was frustrated because message transport between cube bases was slow/erratic. At 115200 b/sec, the best case is ~11KB/sec. For a 22 byte message, that’s 2 ms best case. That’s a pretty short in timelines measured in 10s of ms. But I’d see a successful message every few seconds. Huh?
I put a hook in to use the new button to toggle between the current send+receive and receive-only behaviors. It was still slow, but more reliable when transmit was turned off on the receiving box. Maybe that had to do with not throwing away messages in the flush() call. The flush() call also flushes both transmit and receive fifos. Since we flush immediately after sending, there was the possibility that it would truncate the send buffer, so I tried clearing only the receive buffer. It didn’t seem to make any difference. That makes sense in the light of later observations.
Let’s measure something
A quick check with a one char print every time a message was transmitted showed maybe one message going out per second! The example app does 10 reads with 10ms delay between and then one write (to both sides). Assuming the programmed 10 ms delay is the main delay, it should transmit about every 100ms. Where’s the time going? About the only thing in the loop 9 times out of 10 is a call to rxpoll(). Bracketing that call with calls to millis(), it turns out rxpoll() takes ~72 ms to read both channels with no data coming in!
When receiving actual messages, rxpoll() took 4 reads for a total of 1220 ms to receive a 55 B message, and 523 ms in 2 reads for a 13 B message. That’s 20-40 ms/byte in receive time.
How about the send side? Bracketing just the new writeMsg() call, that took 186 ms to write a 13 B message (including sync, len, cksum) and 610 ms for 55 B. That’s ~12 ms/byte. Hmm – that’s still noticeably less than the time it takes to receive, so the analysis isn’t very complete.
What can be done?
Since the I²C lib calls are obviously expensive, how can we reduce that time? I noticed when I was looking into the lib that the transmit and receive functions transfer a single byte at a time. The I²C bus supports multi-byte reads and writes. If we could use those, it could help a lot. I don’t know how directly that’s supported by the Wire lib, but it looks like the primitives Wire exposes are extremely low level – like “send a START condition”. With a little digging into the I²C protocol, we might be able to build up appropriate commands to do multi-byte sends and receives.
The Hackstrich MultiSerial lib only provides one-byte reads and writes, so we’d have to add new lib calls for the efficient stuff. That shouldn’t be very difficult (if we knew how to do it).
The good news is that if slow, the current implementation essentially works. We can tweak with different transmit/receive ratios for R and L channels based on how we need apps to behave and improve performance some. And if/when we add more efficient multi-byte reads/writes to the lib it will be just a few-line change to the app and perhaps an order of magnitude performance improvement.
I’ll look into whether the SC16IS7{5,6}2 chips support multi-byte reads/writes this weekend and see if I can get support into the library if so. Shouldn’t be that difficult if it’s supported.
By the way, we’re Strich Labs, Hackstrich is the R&D arm (i.e. my person projects page). Thanks for the shout-out! 🙂
I believe the chip does support multi-byte read/writes from/to RHR/THR. I’m about to code some multi-byte lib calls to test it out. The Wire lib pretty clearly supports it.
I’m also going to pull the several delay(10)s to see if they’re really needed, ast the TODO comments ask. That should help quite a bit by itself.