On minimal, robust, small footprint, performance messaging protocols for text streams. It could be worth spending 5 minutes scanning the FIX protocol.
https://www.fixtrading.org/For the context here, you can ignore all the application tags and only consider the protocol tags.
Features of use, probably on UART.
* Preamble w/ message type and length.
* Postamble with checksum and trailer
* Bi-directional message sequence numbering
* Reconnection with or without correct sequence numbers*
* Message resend or "gap fill" to resynchronize. (in the case of an MCU I would expect that to be 99.9% "gap fill" only maybe keeping messages classed as "Last will and testament" priority.
* bidirectional heartbeats/pings
*connection in this case refers to the handshake of establishing sequence numbers and "identity" of the party. While TCP is usually used for the protocol, FIX can arguably be sent on any async bi-directional protocol.
EDIT: I am going to try and use it for my own purposes. A few notes:
It should not be hard at all to find an extremely high performance, minimal footprint FIX parser in C or ASM for Arm. (There I would need to be a little careful with latent IP). The basic parser works much like any UART protocol of "mixed length" async messages. Read the minimal header to detect an incoming message, start streaming it into a tokenizer which pops out tag=value pairs at you. Keep popping them onto a stack until you get the length. Then you can reserve space and let the stream run while you carry on tokenizing your way through the message. You can tokenize it into a big tree structure in memory which is slow, but only needs done once, or you can "strike through" process and trigger events in a state machine as and when combinations of tags are received without waiting on the rest of the message. Risky, but where this is used any dirty trick makes money.
In the real world a lot of code goes into the session layer with identities and parties and authentication/encryptions, but we don't need none of that non-sense here. So other than checking the SenderCompID is "Your devices's ID", all of that goes away. Sequence number coherence is up to the sender and receiver. In the most simple form, you just sent gap fills both directions or demand a reset sequence numbers on all connections and abandon resent completely. It can be very, very handy though, if you get SQ# 1, 2, 3, 6, 7... you can send a "WTF?" message back asking for 4 and 5. On low foot print without memory to reorder yourself, you could just pretend you didn't get 6 or 7 either. Given the other end might be low memory as well, it's likely to just say, "Sod it, new sequence number is 8, we are good.". It's just that the protocol has mechanisms to enforce these so that both parties are 100% aware of any message loss.
I think one hurdle in your case will be the ModBus/RS485 protocol of any other devices on the bus. You will need to at minimum filter only messages from devices you want at the Modbus layer. Also. Doesn't modbus work more like I2C with register sequence read/write. You could wrap that in FIX to get resilience, convert registers to tags or just a blob in one tag. FIX might be better suited to bar UART.