13 Commits
7 changed files with 109 additions and 53 deletions
+30 -16
View File
@@ -1,8 +1,8 @@
# TinyFrame
TinyFrame is a simple library for building and parsing frames to be sent
over a serial interface (e.g. UART, telnet etc.). The code is written
to build with `--std=gnu89` and later.
TinyFrame is a simple library for building and parsing data frames to be sent
over a serial interface (e.g. UART, telnet etc.). The code is written to build with
`--std=gnu89` and later.
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
@@ -10,19 +10,32 @@ UDP packets. If you find a good use for it, please let me know so I can add it h
Frames can be protected by a checksum (~XOR, CRC16 or CRC32) and contain
a unique ID field which can be used for chaining related messages. The highest bit
of the generated IDs is different in each peer to avoid collisions.
of the generated frame IDs is different in each peer to avoid collisions.
Peers are functionally equivalent and can send messages to each other
(the names "master" and "slave" are used only for convenience and have special meaning
in the demos).
(the names "master" and "slave" are used only for convenience).
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 lets you
a particular frame Type, or (3) a specific message ID. This high-level API lets the user
easily implement various async communication patterns.
TinyFrame is re-entrant and supports creating multiple instances with the limitation
that their structure (field sizes and checksum type) must be the same. There is a support
for adding multi-threaded access to a shared instance using a mutex (via a callback stub).
TinyFrame also comes with (optional) helper functions for building and parsing message
payloads, those are provided in the `utils/` folder.
## Ports
TinyFrame has been ported to mutiple languages:
- The reference C implementation is in this repo
- Python port - [MightyPork/PonyFrame](https://github.com/MightyPork/PonyFrame)
- Rust port - [cpsdqs/tinyframe-rs](https://github.com/cpsdqs/tinyframe-rs)
- JavaScript port - [cpsdqs/tinyframe-js](https://github.com/cpsdqs/tinyframe-js)
Please note most of the ports are experimental and may exhibit various bugs or missing features. Testers are welcome :)
## Frame structure
All fields in the message frame have a configurable size (see the top of the header file).
@@ -32,21 +45,22 @@ best suit your application needs.
For example, you don't need 4 bytes (`uint32_t`) for the
length field if your payloads are 20 bytes long, using a 1-byte field (`uint8_t`) will save
3 bytes. This may be significant if you high throughput.
3 bytes. This may be significant if you need high throughput.
```
,-----+----+-----+------+------------+- - - -+------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
| 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
'-----+----+-----+------+------------+- - - -+------------'
,-----+-----+-----+------+------------+- - - -+-------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM |
| 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 | <- size (bytes)
'-----+-----+-----+------+------------+- - - -+-------------'
SOF ......... start of frame, 0x01
SOF ......... start of frame, usually 0x01 (optional, configurable)
ID ......... the frame ID (MSb is the peer bit)
LEN ......... nr of data bytes in the frame
LEN ......... number of data bytes in the frame
TYPE ........ message type (used to run Type Listeners, pick any values you like)
HEAD_CKSUM .. header checksum
DATA ........ LEN bytes of data
DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message
DATA ........ LEN bytes of data (can be 0, in which case DATA_CKSUM is omitted as well)
DATA_CKSUM .. data checksum
```
## Usage Hints
+7 -4
View File
@@ -16,10 +16,10 @@
// If the connection is reliable, you can disable the SOF byte and checksums.
// That can save up to 9 bytes of overhead.
// ,-----+----+-----+------+------------+- - - -+------------,
// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
// | 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
// '-----+----+-----+------+------------+- - - -+------------'
// ,-----+-----+-----+------+------------+- - - -+-------------,
// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM |
// | 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 | <- size (bytes)
// '-----+-----+-----+------+------------+- - - -+-------------'
// !!! BOTH PEERS MUST USE THE SAME SETTINGS !!!
@@ -69,6 +69,9 @@ typedef uint8_t TF_COUNT;
// ticks = number of calls to TF_Tick()
#define TF_PARSER_TIMEOUT_TICKS 10
// Error reporting function. To disable debug, change to empty define
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
//------------------------- End of user config ------------------------------
#endif //TF_CONFIG_H
+65 -32
View File
@@ -243,6 +243,8 @@ bool _TF_FN TF_AddIdListener(TinyFrame *tf, TF_Msg *msg, TF_Listener cb, TF_TICK
return true;
}
}
TF_Error("Failed to add ID listener");
return false;
}
@@ -263,6 +265,8 @@ bool _TF_FN TF_AddTypeListener(TinyFrame *tf, TF_TYPE frame_type, TF_Listener cb
return true;
}
}
TF_Error("Failed to add type listener");
return false;
}
@@ -282,6 +286,8 @@ bool _TF_FN TF_AddGenericListener(TinyFrame *tf, TF_Listener cb)
return true;
}
}
TF_Error("Failed to add generic listener");
return false;
}
@@ -298,6 +304,8 @@ bool _TF_FN TF_RemoveIdListener(TinyFrame *tf, TF_ID frame_id)
return true;
}
}
TF_Error("ID listener %d to remove not found", (int)frame_id);
return false;
}
@@ -314,6 +322,8 @@ bool _TF_FN TF_RemoveTypeListener(TinyFrame *tf, TF_TYPE type)
return true;
}
}
TF_Error("Type listener %d to remove not found", (int)type);
return false;
}
@@ -330,6 +340,8 @@ bool _TF_FN TF_RemoveGenericListener(TinyFrame *tf, TF_Listener cb)
return true;
}
}
TF_Error("Generic listener to remove not found");
return false;
}
@@ -428,6 +440,8 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
}
}
}
TF_Error("Unhandled message, type %d", (int)msg.type);
}
//endregion Listeners
@@ -468,7 +482,10 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
{
// Parser timeout - clear
if (tf->parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) {
TF_ResetParser(tf);
if (tf->state != TFState_SOF) {
TF_ResetParser(tf);
TF_Error("Parser timeout");
}
}
tf->parser_timeout_ticks = 0;
@@ -481,10 +498,11 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
#if !TF_USE_SOF_BYTE
if (tf->state == TFState_SOF) {
TF_ParsBeginFrame();
}
pars_begin_frame(tf);
}
#endif
//@formatter:off
switch (tf->state) {
case TFState_SOF:
if (c == TF_SOF_BYTE) {
@@ -513,15 +531,15 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
case TFState_TYPE:
CKSUM_ADD(tf->cksum, c);
COLLECT_NUMBER(tf->type, TF_TYPE) {
#if TF_CKSUM_TYPE == TF_CKSUM_NONE
tf->state = TFState_DATA;
tf->rxi = 0;
#else
// enter HEAD_CKSUM state
tf->state = TFState_HEAD_CKSUM;
tf->rxi = 0;
tf->ref_cksum = 0;
#endif
#if TF_CKSUM_TYPE == TF_CKSUM_NONE
tf->state = TFState_DATA;
tf->rxi = 0;
#else
// enter HEAD_CKSUM state
tf->state = TFState_HEAD_CKSUM;
tf->rxi = 0;
tf->ref_cksum = 0;
#endif
}
break;
@@ -531,11 +549,13 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
CKSUM_FINALIZE(tf->cksum);
if (tf->cksum != tf->ref_cksum) {
TF_Error("Rx head cksum mismatch");
TF_ResetParser(tf);
break;
}
if (tf->len == 0) {
// if the message has no body, we're done.
TF_HandleReceivedMessage(tf);
TF_ResetParser(tf);
break;
@@ -547,7 +567,8 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
CKSUM_RESET(tf->cksum); // Start collecting the payload
if (tf->len >= TF_MAX_PAYLOAD_RX) {
if (tf->len > TF_MAX_PAYLOAD_RX) {
TF_Error("Rx payload too long: %d", (int)tf->len);
// ERROR - frame too long. Consume, but do not store.
tf->discard_data = true;
}
@@ -563,16 +584,16 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
}
if (tf->rxi == tf->len) {
#if TF_CKSUM_TYPE == TF_CKSUM_NONE
// All done
TF_HandleReceivedMessage();
TF_ResetParser();
#else
// Enter DATA_CKSUM state
tf->state = TFState_DATA_CKSUM;
tf->rxi = 0;
tf->ref_cksum = 0;
#endif
#if TF_CKSUM_TYPE == TF_CKSUM_NONE
// All done
TF_HandleReceivedMessage();
TF_ResetParser();
#else
// Enter DATA_CKSUM state
tf->state = TFState_DATA_CKSUM;
tf->rxi = 0;
tf->ref_cksum = 0;
#endif
}
break;
@@ -580,16 +601,22 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
COLLECT_NUMBER(tf->ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value
CKSUM_FINALIZE(tf->cksum);
if (!tf->discard_data && tf->cksum == tf->ref_cksum) {
TF_HandleReceivedMessage(tf);
if (!tf->discard_data) {
if (tf->cksum == tf->ref_cksum) {
TF_HandleReceivedMessage(tf);
} else {
TF_Error("Body cksum mismatch");
}
}
TF_ResetParser(tf);
}
break;
}
//@formatter:on
// we get here after finishing HEAD, if no data are to be received - handle and clear
// TODO verify - this seems unreachable under normal circumstances
if (tf->len == 0 && tf->state == TFState_DATA) {
TF_HandleReceivedMessage(tf);
TF_ResetParser(tf);
@@ -771,16 +798,19 @@ static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener
}
}
// Flush if checksum wouldn't fit in the buffer
if (TF_SENDBUF_LEN - len < sizeof(TF_CKSUM)) {
TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len);
len = 0;
// Checksum only if message had a body
if (msg->len > 0) {
// Flush if checksum wouldn't fit in the buffer
if (TF_SENDBUF_LEN - len < sizeof(TF_CKSUM)) {
TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len);
len = 0;
}
// Add checksum, flush what remains to be sent
len += TF_ComposeTail(tf->sendbuf + len, &cksum);
}
// Add checksum, flush what remains to be sent
len += TF_ComposeTail(tf->sendbuf+len, &cksum);
TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len);
TF_ReleaseTx(tf);
return true;
@@ -840,6 +870,8 @@ bool _TF_FN TF_RenewIdListener(TinyFrame *tf, TF_ID id)
return true;
}
}
TF_Error("Renew listener: not found (id %d)", (int)id);
return false;
}
@@ -860,6 +892,7 @@ void _TF_FN TF_Tick(TinyFrame *tf)
if (!lst->fn || lst->timeout == 0) continue;
// count down...
if (--lst->timeout == 0) {
TF_Error("ID listener %d has expired", (int)lst->id);
// Listener has expired
cleanup_id_listener(tf, i, lst);
}
+1 -1
View File
@@ -10,7 +10,7 @@
* Upstream URL: https://github.com/MightyPork/TinyFrame
*/
#define TF_VERSION "2.0.2"
#define TF_VERSION "2.1.0"
//---------------------------------------------------------------------------
#include <stdint.h> // for uint8_t etc
+2
View File
@@ -22,4 +22,6 @@ typedef uint8_t TF_COUNT;
#define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H
+2
View File
@@ -22,4 +22,6 @@ typedef uint8_t TF_COUNT;
#define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H
+2
View File
@@ -22,4 +22,6 @@ typedef uint8_t TF_COUNT;
#define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H