Updated & heavily improved

pull/9/head
Ondřej Hruška 7 years ago
parent c83cdf36e2
commit 61c82b614b
  1. 3
      .gitignore
  2. 2
      CMakeLists.txt
  3. 10
      Makefile
  4. 149
      README.md
  5. 347
      TinyFrame.c
  6. 145
      TinyFrame.h
  7. 97
      test.c

3
.gitignore vendored

@ -34,3 +34,6 @@
cmake-build-debug/
tf.bin
.idea/

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.7)
project(tf)
set(CMAKE_CXX_STANDARD GNU99)
set(CMAKE_CXX_STANDARD GNU89)
set(SOURCE_FILES
test.c

@ -0,0 +1,10 @@
build: tf.bin
run: tf.bin
./tf.bin
debug: tf.bin
gdb -q -ex run ./tf.bin
tf.bin: test.c TinyFrame.c TinyFrame.h
gcc -Os --std=gnu89 -Wall -Wno-main -Wno-unused -Wextra test.c TinyFrame.c -I. -o tf.bin

@ -1,56 +1,113 @@
# TinyFrame
TinyFrame is a simple library for building and parsing frames
(packets) to be sent over a serial interface (like UART). It's implemented
to be compatible with C89 and platform agnostic.
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
in `--std=gnu89`.
Frames are protected by a checksum and contain a "unique" ID,
which can be used for chaining messages. Each peer uses a different
value for the first bit of all IDs it generates (the "master flag"
or "peer_bit") to ensure there are no clashes. Typically the master
(PC, main microcontroller) will use "1" and the surrogate (WiFi module,
USB-serial connected gadget, display driver...) uses "0".
Frames are protected by a checksum (~XOR, CRC16 or CRC32) and contain
a unique ID field, which can be used for chaining messages. The highest value
of the ID is different for each peer (TF_MASTER or TF_SLAVE) to avoid collisions.
The library lets you bind listeners waiting for any frame, or a
particular ID. This allows for easy implementation of async communication.
All fields in the frame have configurable size (see the top of the header file).
By just changing a value, like `TF_LEN_BYTES`, the library seamlessly switches
from `uint8_t` to `uint16_t` or `uint32_t` to support longer payloads.
## Frame structure
The library lets you bind listeners waiting for any frame, a particular frame Type,
or a specific message ID. This lets you easily implement asynchronous
communication.
The frame makeup is inspired by that of SBMP (my other, more complicated
and advanced UART protocol library).
## Frame structure
```
<SOF><ID><NOB><PAYLOAD><CKSUM>
SOF ... start of frame, 0x01
ID ... (master_flag | 7-bit counter) - the frame ID
NOB ... nr of payload bytes in the frame (1..256)
PAYLOAD ... NOB bytes of data, can contain any byte values 1..256
CKSUM ... checksum, implemented as XOR of all preceding bytes in the message
,-----+----+-----+------+------------+- - - -+------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
| 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
'-----+----+-----+------+------------+- - - -+------------'
SOF ......... start of frame, 0x01
ID ......... the frame ID (MSb is the peer bit)
LEN ......... nr 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
```
The frame ID (in SBMP called "session ID") can be used to chain multiple related
messages and maintain the context this way. For example, a response may copy
the frame ID of the request frame, which then triggers a callback bound by the
requesting peer. Such behavior is application specific and is thus left to the
upper layers of the protocol.
## Usage hints
- Both sides of the protocol (slave and master) should include the same TinyFrame
code.
- Master inits the lib with `TF_Init(1);`, while slave uses `TF_Init(0);`. This is to avoid a message ID conflict.
- Both sides can add Generic and Type listeners (callbacks) using `TF_AddGenericListener(func)` and `TF_AddTypeListener(type, func)`. The listener is a function as showin in the example file test.c or declared in TinyFrame.h
`bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int len) { ... }`
The listener returns `true` if the message was consumed. If it returns `false`, it can be handled by some other listener (possibly a Generic Listener, if you added one)
- A message is sent using `TF_Send()`, and to use it, the `TF_WriteImpl()` stub must be implemented in the application code. See `test.c` for an example.
There are also helper functions `TF_Send1()` and `TF_Send2()` which send one or two bytes.
- To reply, use `TF_Respond()` with ID same as in the received message (the listener gets this as it's argument). A listener provided as the last parameter to `TF_Send()` will be called after receiving the response.
- To remove a listener, use `TF_RemoveListener()`. *Always remove your ID listeners after handling the response!* There's a limit to the number of listeners.
- The function `TF_Accept()` is used to handle received chars. Call this in your UART Rx interrupt handler or a similar place.
## Usage Hints
- Both peers must include the library with the same parameters (config in the header file)
- Start by calling `TF_Init()` with MASTER or SLAVE as the argument
- Implement `TF_WriteImpl()` - declared at the bottom of the header file as `extern`.
This function is used by `TF_Send()` to write bytes to your UART (or other physical layer).
Presently, always a full frame is sent to this function.
- If you wish to use `TF_PARSER_TIMEOUT_TICKS`, periodically call `TF_Tick()`. The 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).
- Bind Type or Generic listeners using `TF_AddTypeListener()` or `TF_AddGenericListener()`.
- Send a message using `TF_Send()` or the other Send functions.
If you provide a listener callback (function pointer) to the function,
the listener will be added as an ID listener and wait for a response.
- To reply to a message (when your listener gets called), use `TF_Respond()`
with the same frame_id as in the received message.
- Remove the ID listener using `TF_RemoveIdListener()` when it's no longer
needed. (Same for other listener types.) The slot count is limited.
- If the listener function returns `false`, some other listener will get
a chance to handle it
- Manually reset the parser using `TF_ResetParser()`
### The concept of listeners
Listeners are callback functions that are called by TinyFrame when a message which
they can handle is received.
There are 3 listener types:
- ID listeners
- Type listeners
- Generic listeners
They handle the message in this order, and if they decide not to handle it, they can return `false`
and let it be handled by some other listener, or discarded.
### Implementing "synchronous query"
Sometimes it's necessary to send a message and wait for a response to arrive.
One (not too pretty) way to do this is using a global variable - pseudocode:
```c
#define MSG_PING 42
volatile bool onResponse_done = false;
/** ID listener */
bool onResponse(TF_ID frame_id, TF_TYPE type, const uint8_t *data, TF_LEN len)
{
// ... Do something ...
// (eg. copy data to a global variable)
onResponse_done = true;
return true;
}
bool syncQuery(void)
{
TF_ID id;
// Send our request
TF_Send0(MSG_PING, onResponse, &id); // Send0 sends zero bytes of data, just TYPE
// Wait for the response
bool suc = true;
while (!onResponse_done) {
//delay
if (/*timeout*/) {
TF_RemoveIdListener(id); // free the listener slot
return false;
}
}
// ... Do something with the received data? ...
// (can be passed from the listener using a global variable)
return true;
}
```

@ -3,6 +3,13 @@
#include <string.h>
//---------------------------------------------------------------------------
// Compatibility with ESP8266 SDK
#ifdef ICACHE_FLASH_ATTR
#define _TF_FN ICACHE_FLASH_ATTR
#else
#define _TF_FN
#endif
enum TFState {
TFState_SOF = 0, //!< Wait for SOF
TFState_LEN, //!< Wait for Number Of Bytes
@ -14,12 +21,12 @@ enum TFState {
};
typedef struct _IdListener_struct {
unsigned int id;
TF_ID id;
TF_LISTENER fn;
} IdListener;
typedef struct _TypeListener_struct_ {
unsigned char type;
TF_TYPE type;
TF_LISTENER fn;
} TypeListener;
@ -45,6 +52,7 @@ static struct TinyFrameStruct {
TF_CKSUM cksum; //!< Checksum calculated of the data stream
TF_CKSUM ref_cksum; //!< Reference checksum read from the message
TF_TYPE type; //!< Collected message type number
bool discard_data; //!< Set if (len > TF_MAX_PAYLOAD) to read the frame, but ignore the data.
/* --- Callbacks --- */
@ -57,17 +65,30 @@ static struct TinyFrameStruct {
size_t count_type_lst;
size_t count_generic_lst;
// Buffer for building frames
uint8_t sendbuf[TF_MAX_PAYLOAD + TF_OVERHEAD_BYTES];
} tf;
//region Checksums
#if TF_CKSUM_TYPE == 0
// NONE
#define CKSUM_RESET(cksum)
#define CKSUM_ADD(cksum, byte)
#define CKSUM_FINALIZE(cksum)
//region Optional impls
#if TF_USE_CRC16
#elif TF_CKSUM_TYPE == 8
// ---- CRC16 checksum impl ----
// ~XOR
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum ^= byte; } while(0)
#define CKSUM_FINALIZE(cksum) do { cksum = ~cksum; } while(0)
#elif TF_CKSUM_TYPE == 16
/** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */
const uint16_t crc16_table[256] = {
static const uint16_t crc16_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
@ -102,28 +123,78 @@ const uint16_t crc16_table[256] = {
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
static inline uint16_t crc16_byte(uint16_t crc, const uint8_t data)
static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte)
{
return (crc >> 8) ^ crc16_table[(crc ^ data) & 0xff];
return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff];
}
#endif
//endregion Optional impls
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum = crc16_byte(cksum, byte); } while(0)
#define CKSUM_FINALIZE(cksum)
#elif TF_CKSUM_TYPE == 32
static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
// --- macros based on config ---
static inline uint32_t crc32_byte(uint32_t cksum, const uint8_t byte)
{
return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8));
}
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0)
#define CKSUM_RESET(cksum) do { cksum = (TF_CKSUM)0xFFFFFFFF; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum = crc32_byte(cksum, byte); } while(0)
#define CKSUM_FINALIZE(cksum) do { cksum = (TF_CKSUM)~cksum; } while(0)
#if TF_USE_CRC16
// CRC16 checksum
#define CKSUM_ADD(cksum, byte) do { cksum = crc16_byte(cksum, byte); } while(0)
#else
// XOR checksum
#define CKSUM_ADD(cksum, byte) do { cksum ^= byte; } while(0)
#endif
//endregion
void TF_Init(TF_PEER peer_bit)
void _TF_FN TF_Init(TF_PEER peer_bit)
{
// Zero it out
memset(&tf, 0, sizeof(struct TinyFrameStruct));
@ -133,7 +204,7 @@ void TF_Init(TF_PEER peer_bit)
//region Listeners
bool TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
{
size_t i;
for (i = 0; i < TF_MAX_ID_LST; i++) {
@ -149,7 +220,7 @@ bool TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
return false;
}
bool TF_AddTypeListener(unsigned char frame_type, TF_LISTENER cb)
bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb)
{
size_t i;
for (i = 0; i < TF_MAX_TYPE_LST; i++) {
@ -165,7 +236,7 @@ bool TF_AddTypeListener(unsigned char frame_type, TF_LISTENER cb)
return false;
}
bool TF_AddGenericListener(TF_LISTENER cb)
bool _TF_FN TF_AddGenericListener(TF_LISTENER cb)
{
size_t i;
for (i = 0; i < TF_MAX_GEN_LST; i++) {
@ -180,7 +251,7 @@ bool TF_AddGenericListener(TF_LISTENER cb)
return false;
}
bool TF_RemoveIdListener(TF_ID frame_id)
bool _TF_FN TF_RemoveIdListener(TF_ID frame_id)
{
size_t i;
for (i = 0; i < tf.count_id_lst; i++) {
@ -196,7 +267,7 @@ bool TF_RemoveIdListener(TF_ID frame_id)
return false;
}
bool TF_RemoveTypeListener(unsigned char type)
bool _TF_FN TF_RemoveTypeListener(TF_TYPE type)
{
size_t i;
for (i = 0; i < tf.count_type_lst; i++) {
@ -212,7 +283,7 @@ bool TF_RemoveTypeListener(unsigned char type)
return false;
}
bool TF_RemoveGenericListener(TF_LISTENER cb)
bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb)
{
size_t i;
for (i = 0; i < tf.count_generic_lst; i++) {
@ -228,7 +299,7 @@ bool TF_RemoveGenericListener(TF_LISTENER cb)
}
/** Handle a message that was just collected & verified by the parser */
static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data)
static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data, TF_LEN data_len)
{
size_t i;
@ -240,8 +311,8 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
// ID listeners first
for (i = 0; i < tf.count_id_lst; i++) {
if (tf.id_listeners[i].fn && tf.id_listeners[i].id == tf.id) {
if (tf.id_listeners[i].fn(tf.id, tf.type, tf.data, tf.len)) {
if (tf.id_listeners[i].fn && (tf.id_listeners[i].id == frame_id)) {
if (tf.id_listeners[i].fn(frame_id, type, data, data_len)) {
return;
}
}
@ -249,8 +320,8 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
// Type listeners
for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn && tf.type_listeners[i].type == tf.type) {
if (tf.type_listeners[i].fn(tf.id, tf.type, tf.data, tf.len)) {
if (tf.type_listeners[i].fn && (tf.type_listeners[i].type == type)) {
if (tf.type_listeners[i].fn(frame_id, type, data, data_len)) {
return;
}
}
@ -259,7 +330,7 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
// Generic listeners
for (i = 0; i < tf.count_generic_lst; i++) {
if (tf.generic_listeners[i].fn) {
if (tf.generic_listeners[i].fn(tf.id, tf.type, tf.data, tf.len)) {
if (tf.generic_listeners[i].fn(frame_id, type, data, data_len)) {
return;
}
}
@ -268,7 +339,7 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
//endregion Listeners
void TF_Accept(const uint8_t *buffer, size_t count)
void _TF_FN TF_Accept(const uint8_t *buffer, size_t count)
{
size_t i;
for (i = 0; i < count; i++) {
@ -276,16 +347,28 @@ void TF_Accept(const uint8_t *buffer, size_t count)
}
}
void TF_ResetParser(void)
void _TF_FN TF_ResetParser(void)
{
tf.state = TFState_SOF;
}
void TF_AcceptChar(unsigned char c)
{
// QUEUE IF PARSER LOCKED
// FIRST PROCESS ALL QUEUED
/** SOF was received */
static void _TF_FN TF_ParsBeginFrame(void) {
// Reset state vars
CKSUM_RESET(tf.cksum);
#if TF_USE_SOF_BYTE
CKSUM_ADD(tf.cksum, TF_SOF_BYTE);
#endif
tf.discard_data = false;
// Enter ID state
tf.state = TFState_ID;
tf.rxi = 0;
}
void _TF_FN TF_AcceptChar(unsigned char c)
{
// Timeout - clear
if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) {
TF_ResetParser();
@ -296,81 +379,109 @@ void TF_AcceptChar(unsigned char c)
#define COLLECT_NUMBER(dest, type) dest = ((dest) << 8) | c; \
if (++tf.rxi == sizeof(type))
#if !TF_USE_SOF_BYTE
if (tf.state == TFState_SOF) {
TF_ParsBeginFrame();
}
#endif
switch (tf.state) {
case TFState_SOF:
if (c == TF_SOF_BYTE) {
TF_ParsBeginFrame();
}
break;
case TFState_ID:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.id, TF_ID) {
// Enter LEN state
tf.state = TFState_LEN;
tf.len = 0;
tf.rxi = 0;
// Reset state vars
CKSUM_RESET(tf.cksum);
}
break;
case TFState_LEN:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.len, TF_LEN) {
// enter HEAD_CKSUM state
tf.state = TFState_HEAD_CKSUM;
tf.ref_cksum = 0;
// Enter TYPE state
tf.state = TFState_TYPE;
tf.rxi = 0;
}
break;
case TFState_TYPE:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.type, TF_TYPE) {
#if TF_CKSUM_TYPE == 0
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;
case TFState_HEAD_CKSUM:
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM){
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value
CKSUM_FINALIZE(tf.cksum);
if (tf.cksum != tf.ref_cksum) {
TF_ResetParser();
break;
}
// Enter ID state
tf.state = TFState_ID;
tf.rxi = 0;
tf.cksum = 0; // Start collecting the payload
}
break;
case TFState_ID:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.id, TF_ID) {
// Enter TYPE state
tf.state = TFState_TYPE;
tf.rxi = 0;
}
break;
if (tf.len == 0) {
TF_HandleReceivedMessage(tf.id, tf.type, NULL, 0);
TF_ResetParser();
break;
}
case TFState_TYPE:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.type, TF_TYPE) {
// Enter DATA state
tf.state = TFState_DATA;
tf.rxi = 0;
CKSUM_RESET(tf.cksum); // Start collecting the payload
if (tf.len >= TF_MAX_PAYLOAD) {
// ERROR - frame too long. Consume, but do not store.
tf.discard_data = true;
}
}
break;
case TFState_DATA:
CKSUM_ADD(tf.cksum, c);
tf.data[tf.rxi++] = c;
if (tf.discard_data) {
tf.rxi++;
} else {
CKSUM_ADD(tf.cksum, c);
tf.data[tf.rxi++] = c;
}
if (tf.rxi == tf.len) {
// Enter DATA_CKSUM state
tf.state = TFState_DATA_CKSUM;
tf.rxi = 0;
tf.ref_cksum = 0;
#if TF_CKSUM_TYPE == 0
// All done
TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len);
TF_ResetParser();
#else
// Enter DATA_CKSUM state
tf.state = TFState_DATA_CKSUM;
tf.rxi = 0;
tf.ref_cksum = 0;
#endif
}
break;
case TFState_DATA_CKSUM:
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value
if (tf.cksum == tf.ref_cksum) {
// LOCK PARSER
TF_HandleReceivedMessage(tf.id, tf.type, tf.data);
// UNLOCK PARSER
CKSUM_FINALIZE(tf.cksum);
if (!tf.discard_data && tf.cksum == tf.ref_cksum) {
TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len);
}
TF_ResetParser();
@ -379,7 +490,20 @@ void TF_AcceptChar(unsigned char c)
}
}
int TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
/**
* Compose a frame (used internally by TF_Send and TF_Respond).
* The frame can be sent using TF_WriteImpl(), or received by TF_Accept()
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param type - message type
* @param data - data buffer
* @param len - payload size in bytes
* @param explicit_id - ID to use in the frame (8-bit)
* @param use_expl_id - whether to use the previous param
* @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure
*/
static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID explicit_id, bool use_expl_id)
@ -402,12 +526,15 @@ int TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
id = explicit_id;
}
else {
id = (TF_ID) ((tf.next_id++) & TF_ID_MASK);
id = (TF_ID) (tf.next_id++ & TF_ID_MASK);
if (tf.peer_bit) {
id |= TF_ID_PEERBIT;
}
}
if (id_ptr != NULL)
*id_ptr = id;
// DRY helper for writing a multi-byte variable to the buffer
#define WRITENUM_BASE(type, num, xtra) \
for (i = sizeof(type)-1; i>=0; i--) { \
@ -420,36 +547,48 @@ int TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
#define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b))
// --- Start ---
CKSUM_RESET(cksum);
outbuff[pos++] = TF_SOF_BYTE;
cksum = 0;
WRITENUM_CKSUM(TF_LEN, data_len);
WRITENUM(TF_CKSUM, cksum);
#if TF_USE_SOF_BYTE
outbuff[pos++] = TF_SOF_BYTE;
CKSUM_ADD(cksum, TF_SOF_BYTE);
#endif
// --- payload begin ---
cksum = 0;
WRITENUM_CKSUM(TF_ID, id);
WRITENUM_CKSUM(TF_LEN, data_len);
WRITENUM_CKSUM(TF_TYPE, type);
// DATA
for (i = 0; i < data_len; i++) {
b = data[i];
outbuff[pos++] = b;
CKSUM_ADD(cksum, b);
}
#if TF_CKSUM_TYPE != 0
CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum);
#endif
WRITENUM(TF_CKSUM, cksum);
// --- payload begin ---
if (data_len > 0) {
CKSUM_RESET(cksum);
// DATA
for (i = 0; i < data_len; i++) {
b = data[i];
outbuff[pos++] = b;
CKSUM_ADD(cksum, b);
}
if (id_ptr != NULL)
*id_ptr = id;
#if TF_CKSUM_TYPE != 0
CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum);
#endif
}
return pos;
}
bool TF_Send(TF_TYPE type, const uint8_t *payload, TF_LEN payload_len,
TF_LISTENER listener, TF_ID *id_ptr)
bool _TF_FN TF_Send(TF_TYPE type,
const uint8_t *payload, TF_LEN payload_len,
TF_LISTENER listener,
TF_ID *id_ptr)
{
TF_ID msgid;
TF_ID msgid = 0;
int len;
len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false);
if (len == TF_ERROR) return false;
@ -461,7 +600,8 @@ bool TF_Send(TF_TYPE type, const uint8_t *payload, TF_LEN payload_len,
return true;
}
bool TF_Respond(TF_TYPE type,
// Like TF_Send, but with explicit frame ID
bool _TF_FN TF_Respond(TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID frame_id)
{
@ -473,10 +613,20 @@ bool TF_Respond(TF_TYPE type,
return true;
}
/**
* Like TF_Send(), but with no data
*/
bool _TF_FN TF_Send0(TF_TYPE type,
TF_LISTENER listener,
TF_ID *id_ptr)
{
return TF_Send(type, NULL, 0, listener, id_ptr);
}
/**
* Like TF_Send(), but with just 1 data byte
*/
bool TF_Send1(TF_TYPE type, uint8_t b1,
bool _TF_FN TF_Send1(TF_TYPE type, uint8_t b1,
TF_LISTENER listener,
TF_ID *id_ptr)
{
@ -487,7 +637,7 @@ bool TF_Send1(TF_TYPE type, uint8_t b1,
/**
* Like TF_Send(), but with just 2 data bytes
*/
bool TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
bool _TF_FN TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
TF_LISTENER listener,
TF_ID *id_ptr)
{
@ -495,7 +645,8 @@ bool TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
return TF_Send(type, b, 2, listener, id_ptr);
}
void TF_Tick(void)
/** Timebase hook - for timeouts */
void _TF_FN TF_Tick(void)
{
if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) {
tf.parser_timeout_ticks++;

@ -1,13 +1,22 @@
#ifndef TinyFrameH
#define TinyFrameH
//---------------------------------------------------------------------------
#include <stdint.h> // for uint8_t etc
#include <stdbool.h> // for bool
#include <stdlib.h> // for NULL
//#include "messages.h" // for your message IDs (enum or defines)
//#include <esp8266.h> // when using with esphttpd
//---------------------------------------------------------------------------
// A static buffer of this size will be allocated
#define TF_MAX_PAYLOAD 2048
//----------------------------- PARAMETERS ----------------------------------
// Maximum send / receive payload size (static buffers size)
// Larger payloads will be rejected.
#define TF_MAX_PAYLOAD 1024
// --- Listener counts - determine sizes of the static slot tables ---
// Frame ID listeners (wait for response / multi-part message)
#define TF_MAX_ID_LST 20
@ -20,55 +29,109 @@
// ticks = number of calls to TF_Tick()
#define TF_PARSER_TIMEOUT_TICKS 10
#define TF_USE_CRC16 1
//----------------------------- FRAME FORMAT ---------------------------------
// The format can be adjusted to fit your particular application needs
// 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)
// '-----+----+-----+------+------------+- - - -+------------'
// !!! BOTH SIDES MUST USE THE SAME SETTINGS !!!
// Adjust sizes as desired (1,2,4)
#define TF_ID_BYTES 1
#define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1
// Select checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32)
#define TF_CKSUM_TYPE 16
// Use a SOF byte to mark the start of a frame
#define TF_USE_SOF_BYTE 1
// Value of the SOF byte (if TF_USE_SOF_BYTE == 1)
#define TF_SOF_BYTE 0x01
//------------------------- End of user config ------------------------------
//----------------------------- USAGE HINTS ---------------------------------
//---------------------------------------------------------------------------
// ,-----+-----+-----------+----+------+- - - -+------------,
// | SOF | LEN | LEN_CKSUM | ID | TYPE | DATA | PLD_CKSUM |
// | 1 | 2? | 1? | 1? | 1? | ... | 1? |
// '-----+-----+-----------+----+------+- - - -+------------'
// '----- payload -----'
//region Resolve data types
#if TF_LEN_BYTES == 1
typedef uint8_t TF_LEN;
#elif TF_LEN_BYTES == 2
typedef uint16_t TF_LEN;
#elif TF_LEN_BYTES == 4
typedef uint32_t TF_LEN;
#else
#error Bad value of TF_LEN_BYTES, must be 1, 2 or 4
#endif
// The data section is designed thus so the higher levels of TinyFrame
// (message chaining, listeners etc) could be re-used for a different
// framing layer (e.g. sent in UDP packets)
// Fields marked '?' can have their size adjusted by changing the
// typedefs below
#if TF_TYPE_BYTES == 1
typedef uint8_t TF_TYPE;
#elif TF_TYPE_BYTES == 2
typedef uint16_t TF_TYPE;
#elif TF_TYPE_BYTES == 4
typedef uint32_t TF_TYPE;
#else
#error Bad value of TF_TYPE_BYTES, must be 1, 2 or 4
#endif
// Change those typedefs to adjust the field sizes. BOTH PEERS MUST HAVE THE SAME SIZES!
typedef uint8_t TF_ID; // Message ID. Effectively limits the nr of concurrent request-response sessions.
typedef uint8_t TF_TYPE; // Message type (sent together with the data payload, which can be empty)
typedef uint16_t TF_LEN; // Length of the data payload.
// TF_OVERHEAD_BYTES - added to TF_MAX_PAYLOAD for the send buffer size.
// (TODO - buffer-less sending)
#if TF_USE_CRC16
typedef uint16_t TF_CKSUM;
#define TF_OVERHEAD_BYTES 9
#if TF_ID_BYTES == 1
typedef uint8_t TF_ID;
#elif TF_ID_BYTES == 2
typedef uint16_t TF_ID;
#elif TF_ID_BYTES == 4
typedef uint32_t TF_ID;
#else
#error Bad value of TF_ID_BYTES, must be 1, 2 or 4
#endif
#if TF_CKSUM_TYPE == 8 || TF_CKSUM_TYPE == 0
// ~XOR (if 0, still use 1 byte - it won't be used)
typedef uint8_t TF_CKSUM;
#define TF_OVERHEAD_BYTES 7
#elif TF_CKSUM_TYPE == 16
// CRC16
typedef uint16_t TF_CKSUM;
#elif TF_CKSUM_TYPE == 32
// CRC32
typedef uint32_t TF_CKSUM;
#else
#error Bad value for TF_CKSUM_TYPE, must be 8, 16 or 32
#endif
//---------------------------------------------------------------------------
#define TF_SOF_BYTE 0x01
// Bytes added to TF_MAX_PAYLOAD for the send buffer size.
#define TF_OVERHEAD_BYTES (1+sizeof(TF_ID)+sizeof(TF_LEN)+sizeof(TF_CKSUM)+sizeof(TF_TYPE)+sizeof(TF_CKSUM))
//endregion
//---------------------------------------------------------------------------
#define TF_ERROR -1
// Type-dependent masks for bit manipulation in the ID field
#define TF_ID_MASK ((1 << (sizeof(TF_ID)*8 - 1)) - 1)
#define TF_ID_PEERBIT (1 << (sizeof(TF_ID)*8) - 1)
#define TF_ID_MASK (TF_ID)(((TF_ID)1 << (sizeof(TF_ID)*8 - 1)) - 1)
#define TF_ID_PEERBIT (TF_ID)((TF_ID)1 << ((sizeof(TF_ID)*8) - 1))
//---------------------------------------------------------------------------
/** Peer bit enum (used for init) */
typedef enum {
TF_SLAVE = 0,
TF_MASTER = 1,
} TF_PEER;
//---------------------------------------------------------------------------
/**
* TinyFrame Type Listener callback
* @param frame_id - ID of the received frame
@ -146,25 +209,6 @@ bool TF_AddGenericListener(TF_LISTENER cb);
*/
bool TF_RemoveGenericListener(TF_LISTENER cb);
/**
* Compose a frame (used internally by TF_Send and TF_Respond).
* The frame can be sent using TF_WriteImpl(), or received by TF_Accept()
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param type - message type
* @param data - data buffer
* @param len - payload size in bytes
* @param explicit_id - ID to use in the frame (8-bit)
* @param use_expl_id - whether to use the previous param
* @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure
*/
int TF_Compose(uint8_t *outbuff,
TF_ID *id_ptr,
TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID explicit_id, bool use_expl_id);
/**
* Send a frame, and optionally attach an ID listener.
*
@ -180,6 +224,11 @@ bool TF_Send(TF_TYPE type, const uint8_t *data, TF_LEN data_len,
TF_LISTENER listener,
TF_ID *id_ptr);
/**
* Like TF_Send(), but no data, just the type
*/
bool TF_Send0(TF_TYPE type, TF_LISTENER listener, TF_ID *id_ptr);
/**
* Like TF_Send(), but with just 1 data byte
*/

@ -1,22 +1,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "TinyFrame.h"
// helper func for testing
static void dumpFrame(const uint8_t *buff, TF_LEN len)
{
int i;
for(i = 0; i < len; i++) {
printf("%3u \033[34m%02X\033[0m", buff[i], buff[i]);
if (buff[i] >= 0x20 && buff[i] < 127) {
printf(" %c", buff[i]);
} else {
printf(" \033[31m.\033[0m", buff[i]);
}
printf("\n");
}
printf("--- end of frame ---\n");
}
static void dumpFrame(const uint8_t *buff, TF_LEN len);
/**
* This function should be defined in the application code.
@ -24,65 +11,67 @@ static void dumpFrame(const uint8_t *buff, TF_LEN len)
*/
void TF_WriteImpl(const uint8_t *buff, TF_LEN len)
{
printf("\033[32;1mTF_WriteImpl - sending frame:\033[0m\n");
printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len);
// Send it back as if we received it
TF_Accept(buff, len);
}
/** An example listener function */
bool myListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{
printf("\033[33mrx frame %s, len %d, id %d\033[0m\n", buff, len, frame_id);
printf("\033[33mRX frame\n"
" type: %02Xh\n"
" data: \"%.*s\"\n"
" len: %u\n"
" id: %Xh\033[0m\n", type, len, buff, len, frame_id);
return true;
}
void main()
bool testIdListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{
int i;
TF_ID msgid;
int len;
char buff[100];
printf("OK - ID Listener triggered for msg (type %02X, id %Xh)!", type, frame_id);
return true;
}
void main(void)
{
// Set up the TinyFrame library
TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(myListener);
printf("------ Simulate sending a message --------\n");
// Send a message
// args - payload, length (0 = strlen), listener, id_ptr (for storing the frame ID)
// (see the .h file for details)
TF_Respond(0xA5, (unsigned char*)"Hello TinyFrame", 16, 0x15);
// This builds the frame in an internal buffer and sends it to
// TF_WriteImpl()
TF_Send(0x22, (unsigned char*)"Hello TinyFrame", 16, NULL, NULL);
const char *longstr = "Lorem ipsum dolor sit amet.";
TF_Send(0x33, (unsigned char*)longstr, (TF_LEN)(strlen(longstr)+1), NULL, NULL);
printf("------ Simulate receiving a message --------\n");
TF_Send(0x44, (unsigned char*)"Hello2", 7, NULL, NULL);
// Adding global listeners
// Listeners can listen to any frame (fallback listeners),
// or to a specific Frame Type (AddTypeListener). There are
// also ID listeners that can be bound automatically in TF_Send().
//
// Type listeners are matched by the first character of the payload,
// ID listeners by the message ID (which is the same in the response as in the request)
TF_AddGenericListener(myListener);
//TF_AddTypeListener(0xF1, myTypeListener);
//TF_AddIdListener(msgID, myIdListener);
TF_Send0(0xF0, NULL, NULL);
// This lets us compose a frame (it's also used internally by TF_Send and TF_Respond)
len = TF_Compose((unsigned char*)buff, // Buffer to write the frame to
&msgid, // Int to store the message ID in
0xA5,
(unsigned char*)"Hello TinyFrame", // Payload bytes
16, // Length - this will cut it at "ALPHA" (showing that it works)
// For string, we can use "0" to use strlen() internally
0x15, true); // Message ID - we could specify a particular ID if we were
// trying to build a response frame, which has the same ID
// as the request it responds to.
TF_Send1(0xF1, 'Q', NULL, NULL);
printf("The frame we'll receive is:\n");
dumpFrame((unsigned char*)buff, (TF_LEN)len);
TF_Send2(0xF2, 'A', 'Z', NULL, NULL);
TF_Send0(0x77, testIdListener, NULL);
}
// Accept the frame
// You will normally call this method in the UART IRQ handler etc
TF_Accept((unsigned char*)buff, (TF_LEN)len);
// helper func for testing
static void dumpFrame(const uint8_t *buff, TF_LEN len)
{
int i;
for(i = 0; i < len; i++) {
printf("%3u \033[34m%02X\033[0m", buff[i], buff[i]);
if (buff[i] >= 0x20 && buff[i] < 127) {
printf(" %c", buff[i]);
} else {
printf(" \033[31m.\033[0m");
}
printf("\n");
}
printf("--- end of frame ---\n");
}

Loading…
Cancel
Save