A simple library for building and parsing data frames for serial interfaces (like UART / RS232)
TinyFrame is a simple library for building and parsing data frames to be sent over a serial interface (e.g. UART, telnet, socket). The code is written to build with
--std=gnu99and mostly compatible with
The library provides a high level interface for passing messages between the two peers. Multi-message sessions, response listeners, checksums, timeouts are all handled by the library.
TinyFrame is suitable for a wide range of applications, including inter-microcontroller communication, as a protocol for FTDI-based PC applications or for messaging through UDP packets.
The library lets you register listeners (callback functions) to wait for (1) any frame, (2) a particular frame Type, or (3) a specific message ID. This high-level API is general enough to implement most communication patterns.
TinyFrame is re-entrant and supports creating multiple instances with the limitation that their structure (field sizes and checksum type) is the same. There is a support for adding multi-threaded access to a shared instance using a mutex.
TinyFrame also comes with (optional) helper functions for building and parsing message payloads, those are provided in the
TinyFrame has been ported to mutiple languages:
Please note most of the ports are experimental and may exhibit various bugs or missing features. Testers are welcome :)
The basic functionality of TinyFrame is explained here. For particlars, such as the API functions, it's recommended to read the doc comments in the header file.
Each frame consists of a header and a payload. Both parts can be protected by a checksum, ensuring a frame with a malformed header (e.g. with a corrupted length field) or a corrupted payload is rejected.
The frame header contains a frame ID and a message type. Frame ID is incremented with each new message. The highest bit of the ID field is fixed to 1 and 0 for the two peers, avoiding a conflict.
Frame ID can be re-used in a response to tie the two messages together. Values of the type field are user defined.
All fields in the frame have a configurable size. By changing a field in the config file, such as
TF_LEN_BYTES(1, 2 or 4), the library seamlessly switches between
uint32_tfor all functions working with the field.
,-----+-----+-----+------+------------+- - - -+-------------, | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM | | 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 |
TinyFrame is based on the concept of message listeners. A listener is a callback function waiting for a particular message Type or ID to be received.
There are 3 listener types, in the order of precedence:
ID listeners can be registered automatically when sending a message. All listeners can also be registered and removed manually.
ID listeners are used to receive the response to a request. When registerign an ID listener, it's possible to attach custom user data to it that will be made available to the listener callback. This data (
void *) can be any kind of application context variable.
ID listeners can be assigned a timeout. When a listener expires, before it's removed, the callback is fired with NULL payload data in order to let the user
free()any attached userdata. This happens only if the userdata is not NULL.
Listener callbacks return values of the
TF_CLOSE- message accepted, remove the listener
TF_STAY- message accepted, stay registered
TF_STAY, but the ID listener's timeout is renewed
TF_NEXT- message NOT accepted, keep the listener and pass the message to the next listener capable of handling it.
TinyFrame uses two data buffers: a small transmit buffer and a larger receive buffer. The transmit buffer is used to prepare bytes to send, either all at once, or in a circular fashion if the buffer is not large enough. The buffer must only contain the entire frame header, so e.g. 32 bytes should be sufficient for short messages.
*_Multipart()sending functions, it's further possible to split the frame header and payload to multiple function calls, allowing the applciation to e.g. generate the payload on-the-fly.
In contrast to the transmit buffer, the receive buffer must be large enough to contain an entire frame. This is because the final checksum must be verified before the frame is handled.
If frames larger than the possible receive buffer size are required (e.g. in embedded systems with small RAM), it's recommended to implement a multi-message transport mechanism at a higher level and send the data in chunks.
TF_Config.example.cfor reference how to configure and integrate the library.
TF_SLAVEas the argument. This creates a handle. Use
TF_InitStatic()to avoid the use of malloc().
TF_WriteImpl()- declared at the bottom of the header file as
extern. This function is used by
TF_Send()and others to write bytes to your UART (or other physical layer). A frame can be sent in it's entirety, or in multiple parts, depending on its size.
TF_Tick(). The calling period determines the length of 1 tick. This is used to time-out the parser in case it gets stuck in a bad state (such as receiving a partial frame) and can also time-out ID listeners.
TF_QuerySimple(). Query functions take a listener callback (function pointer) that will be added as an ID listener and wait for a response.
*_Multipart()variant of the above sending functions for payloads generated in multiple function calls. The payload is sent afterwards by calling
TF_Multipart_Payload()and the frame is closed by
TF_CKSUM_CUSTOM8, 16 or 32 and implement the three checksum functions.
TF_Respond()with the msg object you received, replacing the
len) with a response.
TF_ResetParser(). It can also be reset automatically after a timeout configured in the config file.
msg->datato let the user free the userdata. Therefore it's needed to check
msg->databefore proceeding to handle the message.
1in the config file.
You'll find various examples in the
demo/folder. Each example has it's own Makefile, read it to see what options are available.
The demos are written for Linux, some using sockets and
clone()for background processing. They try to simulate real TinyFrame behavior in an embedded system with asynchronous Rx and Tx. If you can't run the demos, the source files are still good as examples.