parent
c83cdf36e2
commit
61c82b614b
@ -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; |
||||
} |
||||
``` |
||||
|
Loading…
Reference in new issue