9 Commits
Author SHA1 Message Date
MightyPork b74bda7cf5 added logging support for better debug 2018-01-12 10:09:52 +01:00
MightyPorkandGitHub 530577958d Update README.md 2017-12-28 15:23:57 +01:00
MightyPorkandGitHub 2d121ddce9 Update README.md 2017-12-28 15:17:56 +01:00
MightyPork 1e6cdf22f0 remove lies from readme 2017-12-27 19:26:08 +01:00
MightyPork 148b8a0fe6 added better comment to config example 2017-12-27 19:22:43 +01:00
MightyPorkandGitHub 8205ab93b0 Update README.md 2017-12-27 17:25:19 +01:00
MightyPork 88d6a5cd23 v bump 2017-12-27 17:15:43 +01:00
MightyPork c4b169cb80 fix call to undefined function if SOF byte is not used 2017-12-27 17:15:29 +01:00
MightyPorkandGitHub 03377b4b9f Update README.md 2017-12-27 00:37:52 +01:00
7 changed files with 75 additions and 25 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
+31 -4
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,7 +498,7 @@ 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
@@ -532,6 +549,7 @@ 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;
}
@@ -550,6 +568,7 @@ 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) {
TF_Error("Rx payload too long: %d", (int)tf->len);
// ERROR - frame too long. Consume, but do not store.
tf->discard_data = true;
}
@@ -582,8 +601,12 @@ 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);
@@ -593,6 +616,7 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
//@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);
@@ -846,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;
}
@@ -866,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.4"
#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