Finding the right API for embedded framed protocol

After some valuable feedback from @farnz I rewrote the API of a library that handles a proprietary framed protocol.

I agree, that using something like tokio_util::codec in combination with Framed sounds like a good idea.

However, all this stuff will be running on an NXP i.MX6ULL SoC, which is a single-core 32-bit ARM processor.

Hence, I am unsure whether it'd be reasonable to implement an async interface for this protocol, which would ultimately result in a load of different threads being spawned (right?!).

My question is, how I'd best go about implementing the interface to the to-be-decoded stream of frames handled by the transceiver, so that I'd get an API that may decode frames into a stream of valid packages for a higher-level protocol.

  1. Should this interface be sync or async, given the current hardware limitations.

    • If sync, should I even use the traits from tokio_util and/or tokio-stream?
  2. Should I implement the generic en-/decoding within this library or rather just implement concrete en-/decoding in the higher level library.

I have refactored and rewritten the library already a couple of times. While it "works", I am not really satisfied with its current exposed API, so I'd appreciate any hints and help.

Firstly, things like Embassy and RTIC provide cheap single-threaded async runtimes for embedded (things like Arm Cortex-M0 cores or RISC-V, clocked at single digit megahertz). Async is designed to get you I/O concurrency from a single thread, and is ideal for this application. If you've got Linux atop the core (it's more powerful than the Arm Linux system I work on), then you could use Tokio's current thread runtime quite happily.

Second, even if you decide to use a sync interface, I'd reuse Decoder and Encoder from tokio-util; you'd want to reimplement Framed in a suitable fashion for your interface, however, since that's very firmly in async land.

1 Like

Thank you. Yes, the system runs a Yocto/OE Linux. I may have been too preoccupied with the single core fact, but the system already runs a plethora of threads and processes from the Linux kernel and userland just fine.

If you're already paying the overhead of Linux to begin with, just use Tokio for the serial I/O. It can be faster to use spawn_blocking to handle a sequence of file I/O operations via std::fs instead of tokio::fs, but that's about the only gotcha I can see.

And it's immensely helpful when dealing with a device on the other end of a UART to have tracing spitting out Perfetto-compatible traces, so that you can look at the relationship between events nice and clearly.

2 Likes

If you use Tokio, it might be useful to select the current-thread scheduler — see the runtime module for docs on how its behavior differs in detail. The important part is that it avoids spawning a dedicated thread for async tasks, instead running them from the thread(s) which call block_on(), which may be beneficial on a single-core system.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.