Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b74bda7cf5
|
||
|
|
530577958d | ||
|
|
2d121ddce9 | ||
|
|
1e6cdf22f0
|
||
|
|
148b8a0fe6
|
||
|
|
8205ab93b0 | ||
|
|
88d6a5cd23
|
||
|
|
c4b169cb80
|
||
|
|
03377b4b9f | ||
|
|
1a62d78443 | ||
|
|
1488abb517 | ||
|
|
1f52a17fc2 | ||
|
|
7f6f21cdb7 | ||
|
|
38952f8405 | ||
|
|
68ea3bb3a4 | ||
|
|
ce06abfdbf
|
@@ -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
@@ -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
|
||||
|
||||
+67
-9
@@ -169,6 +169,13 @@ TinyFrame * _TF_FN TF_Init(TF_Peer peer_bit)
|
||||
return tf;
|
||||
}
|
||||
|
||||
/** Release the struct */
|
||||
void TF_DeInit(TinyFrame *tf)
|
||||
{
|
||||
if (tf == NULL) return;
|
||||
free(tf);
|
||||
}
|
||||
|
||||
//region Listeners
|
||||
|
||||
/** Reset ID listener's timeout to the original value */
|
||||
@@ -236,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;
|
||||
}
|
||||
|
||||
@@ -256,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;
|
||||
}
|
||||
|
||||
@@ -275,6 +286,8 @@ bool _TF_FN TF_AddGenericListener(TinyFrame *tf, TF_Listener cb)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
TF_Error("Failed to add generic listener");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -291,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;
|
||||
}
|
||||
|
||||
@@ -307,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;
|
||||
}
|
||||
|
||||
@@ -323,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;
|
||||
}
|
||||
|
||||
@@ -365,6 +384,12 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
|
||||
if (res == TF_RENEW) {
|
||||
renew_id_listener(ilst);
|
||||
}
|
||||
else if (res == TF_CLOSE) {
|
||||
// Set userdata to NULL to avoid calling user for cleanup
|
||||
ilst->userdata = NULL;
|
||||
ilst->userdata2 = NULL;
|
||||
cleanup_id_listener(tf, i, ilst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -382,8 +407,12 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
|
||||
res = tlst->fn(tf, &msg);
|
||||
|
||||
if (res != TF_NEXT) {
|
||||
// if it's TF_CLOSE, we assume user already cleaned up userdata
|
||||
// TF_RENEW doesn't make sense here because type listeners don't expire
|
||||
// type listeners don't have userdata.
|
||||
// TF_RENEW doesn't make sense here because type listeners don't expire = same as TF_STAY
|
||||
|
||||
if (res == TF_CLOSE) {
|
||||
cleanup_type_listener(tf, i, tlst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -397,12 +426,22 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
|
||||
res = glst->fn(tf, &msg);
|
||||
|
||||
if (res != TF_NEXT) {
|
||||
// if it's TF_CLOSE, we assume user already cleaned up userdata
|
||||
// TF_RENEW doesn't make sense here because generic listeners don't expire
|
||||
// generic listeners don't have userdata.
|
||||
// TF_RENEW doesn't make sense here because generic listeners don't expire = same as TF_STAY
|
||||
|
||||
// note: It's not expected that user will have multiple generic listeners, or
|
||||
// ever actually remove them. They're most useful as default callbacks if no other listener
|
||||
// handled the message.
|
||||
|
||||
if (res == TF_CLOSE) {
|
||||
cleanup_generic_listener(tf, i, glst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TF_Error("Unhandled message, type %d", (int)msg.type);
|
||||
}
|
||||
|
||||
//endregion Listeners
|
||||
@@ -443,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) {
|
||||
if (tf->state != TFState_SOF) {
|
||||
TF_ResetParser(tf);
|
||||
TF_Error("Parser timeout");
|
||||
}
|
||||
}
|
||||
tf->parser_timeout_ticks = 0;
|
||||
|
||||
@@ -456,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) {
|
||||
@@ -506,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;
|
||||
@@ -522,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;
|
||||
}
|
||||
@@ -555,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) {
|
||||
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);
|
||||
@@ -696,7 +748,7 @@ static size_t _TF_FN TF_ComposeBody(uint8_t *outbuff,
|
||||
* @param cksum - checksum variable used for the body
|
||||
* @return nr of bytes in outbuff used
|
||||
*/
|
||||
static size_t _TF_FN TF_ComposeTail(uint8_t *outbuff, const TF_CKSUM *cksum)
|
||||
static size_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum)
|
||||
{
|
||||
int8_t si = 0; // signed small int
|
||||
uint8_t b = 0;
|
||||
@@ -746,6 +798,8 @@ static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -754,8 +808,9 @@ static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener
|
||||
|
||||
// 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_WriteImpl(tf, (const uint8_t *) tf->sendbuf, len);
|
||||
TF_ReleaseTx(tf);
|
||||
|
||||
return true;
|
||||
@@ -815,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;
|
||||
}
|
||||
|
||||
@@ -835,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);
|
||||
}
|
||||
|
||||
+8
-1
@@ -10,7 +10,7 @@
|
||||
* Upstream URL: https://github.com/MightyPork/TinyFrame
|
||||
*/
|
||||
|
||||
#define TF_VERSION "2.0.0"
|
||||
#define TF_VERSION "2.1.0"
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
#include <stdint.h> // for uint8_t etc
|
||||
@@ -235,6 +235,13 @@ TinyFrame *TF_Init(TF_Peer peer_bit);
|
||||
*/
|
||||
void TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit);
|
||||
|
||||
/**
|
||||
* De-init the dynamically allocated TF instance
|
||||
*
|
||||
* @param tf
|
||||
*/
|
||||
void TF_DeInit(TinyFrame *tf);
|
||||
|
||||
/**
|
||||
* Reset the frame parser state machine.
|
||||
* This does not affect registered listeners.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user