pull/9/head
Ondřej Hruška 7 years ago
parent 23a18df899
commit 962ccd054c
  1. 36
      README.md
  2. 252
      TinyFrame.c
  3. 48
      TinyFrame.h
  4. 36
      test.c

@ -1,2 +1,36 @@
# TinyFrame
Minimalist library for building and parsing data frames for serial comm
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.
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".
The library lets you bind listeners waiting for any frame, or a
particular ID. This allows for easy implementation of async communication.
## Frame structure
The frame makeup is inspired by that of SBMP (my other, more complicated
and advanced UART protocol library).
```
<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
```
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.

@ -0,0 +1,252 @@
#include "TinyFrame.h"
#include <string.h>
/* Note: payload length determines the Rx buffer size. Max 256 */
#define TF_MAX_PAYLOAD 256
#define TF_MAX_CALLBACKS 16
#define TF_SOF_BYTE 0x01
enum TFState {
TFState_SOF = 0, //!< Wait for SOF
TFState_ID, //!< Wait for ID
TFState_NOB, //!< Wait for Number Of Bytes
TFState_PAYLOAD, //!< Receive payload
TFState_CKSUM //!< Wait for Checksum
};
/**
* Frame parser internal state
*/
static struct TinyFrameStruct {
/* Own state */
bool peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash)
unsigned int next_id; //!< Next frame / frame chain ID
/* Parser state */
enum TFState state;
unsigned int id; //!< Incoming packet ID
unsigned int nob; //!< Payload length
unsigned char pldbuf[TF_MAX_PAYLOAD+1]; //!< Payload byte buffer
unsigned int rxi; //!< Receive counter (for payload or other fields)
unsigned int cksum; //!< Continually updated checksum
/* Callbacks */
int cb_ids[TF_MAX_CALLBACKS]; //!< Callback frame IDs, -1 = all
TinyFrameListener cb_funcs[TF_MAX_CALLBACKS]; //!< Callback funcs, 0 = free slot
} tf;
/**
* Initialize the TinyFrame engine.
* This can also be used to completely reset it (removing all listeners etc)
*
* @param peer_bit - peer bit to use for self
*/
void TF_Init(bool peer_bit)
{
memset(&tf, 0, sizeof(struct TinyFrameStruct));
tf.peer_bit = peer_bit;
}
/**
* Reset the frame parser state machine.
*/
void TF_ResetParser(void)
{
tf.state = TFState_SOF;
tf.cksum = 0;
}
/**
* Register a receive callback.
*
* @param frame_id - ID of the frame to receive
* @param cb - callback func
* @return Callback slot index (for removing). TF_ERROR (-1) on failure
*/
int TF_AddListener(int frame_id, TinyFrameListener cb)
{
int i;
for (i = 0; i < TF_MAX_CALLBACKS; i++) {
if (tf.cb_funcs[i] == NULL) {
tf.cb_funcs[i] = cb;
tf.cb_ids[i] = frame_id;
return i;
}
}
return TF_ERROR;
}
/**
* Remove a rx callback function by the index received when registering it
*
* @param index - index in the callbacks table
*/
void TF_RemoveListener(int index)
{
tf.cb_funcs[index] = NULL;
}
/**
* Remove a callback by the function pointer
*
* @param cb - callback function to remove
*/
void TF_RemoveListenerFn(TinyFrameListener cb)
{
int i;
for (i = 0; i < TF_MAX_CALLBACKS; i++) {
if (tf.cb_funcs[i] == cb) {
tf.cb_funcs[i] = NULL;
// no break, it can be here multiple times
}
}
return;
}
/**
* Accept incoming bytes & parse frames
*
* @param buffer - byte buffer to process
* @param count - nr of bytes in the buffer
*/
void TF_Accept(const unsigned char *buffer, unsigned int count)
{
unsigned int i;
for (i = 0; i < count; i++) {
TF_AcceptChar(buffer[i]);
}
}
/**
* Process a single received character
*
* @param c - rx character
*/
void TF_AcceptChar(unsigned char c)
{
int i;
bool rv;
switch (tf.state)
{
case TFState_SOF:
if (c == TF_SOF_BYTE) {
tf.cksum = 0;
tf.state = TFState_ID;
}
break;
case TFState_ID:
tf.id = c;
tf.state = TFState_NOB;
break;
case TFState_NOB:
tf.nob = c + 1; // using 0..255 as 1..256
tf.state = TFState_PAYLOAD;
tf.rxi = 0;
break;
case TFState_PAYLOAD:
tf.pldbuf[tf.rxi++] = c;
if (tf.rxi == tf.nob) {
tf.state = TFState_CKSUM;
}
break;
case TFState_CKSUM:
tf.state = TFState_SOF;
if (tf.cksum == (unsigned int)c) {
// Add 0 at the end of the data in the buffer (useful if it was a string)
tf.pldbuf[tf.rxi] = '\0';
// Fire listeners
for (i = 0; i < TF_MAX_CALLBACKS; i++) {
// Fire if used & matches
if (tf.cb_funcs[i] && (tf.cb_ids[i] == -1 || tf.cb_ids[i] == tf.id)) {
rv = tf.cb_funcs[i](tf.id, tf.pldbuf, tf.nob);
// Unbind
if (rv) tf.cb_funcs[i] = NULL;
}
}
} else {
// Fail, return to base state
tf.state = TFState_SOF;
}
break;
}
// Update the checksum
tf.cksum ^= c;
}
/**
* Compose a frame
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param payload - data buffer
* @param len - payload size in bytes, 0 to use strlen
* @param explicit_id - ID to use, -1 to chose next free
* @return nr of bytes in outbuff used by the frame, -1 on failure
*/
int TF_Compose(unsigned char *outbuff, unsigned int *msgid,
const unsigned char *payload, unsigned int payload_len,
int explicit_id
) {
unsigned int i;
unsigned int id;
int xor;
// sanitize len
if (payload_len > TF_MAX_PAYLOAD) return TF_ERROR;
if (payload_len == 0) payload_len = strlen(payload);
// Gen ID
if (explicit_id == TF_NEXT_ID) {
id = tf.next_id++;
if (tf.peer_bit) {
id |= 0x80;
}
if (tf.next_id > 0x7F) {
tf.next_id = 0;
}
} else {
id = explicit_id;
}
outbuff[0] = TF_SOF_BYTE;
outbuff[1] = id & 0xFF;
outbuff[2] = (payload_len - 1) & 0xFF; // use 0..255 as 1..256
memcpy(outbuff+3, payload, payload_len);
xor = 0;
for (i = 0; i < payload_len + 3; i++) {
xor ^= outbuff[i];
}
outbuff[payload_len + 3] = xor;
if (msgid != NULL) *msgid = id;
return payload_len + 4;
}

@ -0,0 +1,48 @@
//---------------------------------------------------------------------------
#ifndef TinyFrameH
#define TinyFrameH
//---------------------------------------------------------------------------
#include <stdint.h>
#include <stdbool.h>
/**
* TinyFrame receive callback trype.
*
* @param frame_id - ID of the received byte (if response, same as the request)
* @param buff - byte buffer with the payload
* @param len - number of bytes in the buffer
* @return return true, if the callback should be removed.
*/
typedef bool (*TinyFrameListener)(
unsigned int frame_id,
const unsigned char *buff,
unsigned int len
);
// Forward declarations (comments in .c)
void TF_ResetParser(void);
void TF_Init(bool peer_bit);
void TF_Accept(const unsigned char *buffer, unsigned int count);
void TF_AcceptChar(unsigned char c);
#define TF_ANY_ID -1
int TF_AddListener(int frame_id, TinyFrameListener cb); // returns slot index
void TF_RemoveListener(int index);
void TF_RemoveListenerFn(TinyFrameListener cb);
// returns "frame_id"
#define TF_NEXT_ID -1
#define TF_ERROR -1
// returns nr of bytes in the output buffer used, or TF_ERROR
int TF_Compose(unsigned char *outbuff, unsigned int *msgid,
const unsigned char *payload, unsigned int payload_len,
int explicit_id
);
#endif

@ -0,0 +1,36 @@
#include <stdio.h>
#include <stdlib.h>
#include "TinyFrame.h"
#define BUFLEN 200
bool listen(unsigned int frame_id, const unsigned char *buff, unsigned int len)
{
printf("\033[33mrx frame %s, len %d, id %d\033[0m\n", buff, len, frame_id);
return false; // Do not unbind
}
void main()
{
TF_Init(1);
int msgid;
unsigned char buff[BUFLEN];
TF_AddListener(TF_ANY_ID, listen);
int len = TF_Compose(buff, &msgid, "Hello TinyFrame", 0, TF_NEXT_ID);
printf("Used = %d, id=%d\n",len, msgid);
for(int i=0;i<len; i++) {
printf("%3u %c\n", buff[i], buff[i]);
}
TF_Accept(buff, len);
len = TF_Compose(buff, &msgid, "PRASE PRASE", 5, TF_NEXT_ID);
printf("Used = %d, id=%d\n",len, msgid);
for(int i=0;i<len; i++) {
printf("%3u %c\n", buff[i], buff[i]);
}
TF_Accept(buff, len);
}
Loading…
Cancel
Save