Simple Serial Framing

Here is a simple C program that demonstrates encapsulating a message into a frame, sending the message through a channel, and then unpacking the message from the frame. The code assumes that the transmitter may transmit at any time and that the receiver may start receiving at any time. If the receiver starts reading from the middle of a frame, it ignores the rest of the frame. The program assumes a fixed frame length.


Quite often, I need to asynchronously send a chunk of data through a serial communication link byte by byte. By asynchronous, I mean that a whole byte may be sent and received at any time. The link could use protocols like UART, SPI, I2C, etc. I originally wrote this code for use with UART, but I removed all the platform-specific stuff here and made it suitable for demonstration on a personal computer.

The code takes inspiration from the PPP which itself derives its framing protocol from HDLC [1]:

An Example

Message: A, B, 0x7E, C, 0x7D, D

Framed Message: 0x7E, A, B, 0x7D, 0x5E, C, 0x7D, 0x5D, D

Note that a frame may include only one 0x7E character, which is the frame's header. All control characters are preceded by 0x7D. Note that if the receiver misses the 0x7D escape character and starts reading from 0x5E, the XOR with 0x20 prevents the receiver from falsely thinking that it is receiving a frame's header.

Read the Code (or the Comments)

The program looks long because of the comments. Don't be afraid! Just read the comments.

The code simulates sending a message byte by byte from put_string() and receiving the message byte by byte from get_string(). The final received message is placed in the global received array. In application, put_string() and get_string() would run on different systems. The transmit() function corresponds to copying the byte to send to the communication device. In a microcontroller, this would be like writing to a UART peripheral device's TX register.

The code is trivial, apart from the receiving function get_string(), which involves a state machine with three states: WAIT, ACCEPT, and ESCAPE.

Here is the code and Makefile for convenience: delim.zip

#include <stdio.h>
#include <stdint.h>

#define C_BEGIN 0x7E // note that this is '~' tilde
#define C_ESC   0x7D // note that this is '}' right curly brace
#define C_XOR   0x20 // special flag escaping operator

#define BUF_LEN 256
static uint8_t received[BUF_LEN];

void get_string(uint8_t buf[], uint8_t char_reg);
void put_string(uint8_t string[], uint16_t buf_len);

/* Let's send a string.*/
int main(void)
  uint8_t string[BUF_LEN] = "{I am a cat} Nyah~~~";

  put_string(string, BUF_LEN); // the string is sent
  printf("%s\n", (char*)received);

  return 0;
} // main()

/* This function simulates a transmission of a single byte */
void transmit(char_reg)
uint8_t char_reg;  // transmitted character
  get_string(received, char_reg); // the other end receives the character
} // transmit()

/* This function packages a string into a frame and sends it. */
void put_string(string, buf_len)
uint8_t string[]; // string to send
uint16_t buf_len; // length of buffer
  uint16_t i;
  transmit(C_BEGIN); // send begin flag

  for(i=0; i<buf_len; i++)
    if(string[i] == C_BEGIN || string[i] == C_ESC) // need to escape 
      transmit(C_ESC);              // send escape flag
      transmit(string[i] ^ C_XOR);  // send encoded byte
    else // send without escaping
      transmit(string[i]); // Copy buffer to string
  } // for i
} // put_usart()

enum RxState {WAIT, ACCEPT, ESCAPE};

/* This function receives a new character and appends the character to a
 * temporary buffer. If the message is completed (enough characters received),
 * the function copies the message from the temporary buffer to the main
 * receiving buffer.
void get_string(buf, char_reg)
uint8_t buf[];      // main receiving buffer
uint8_t char_reg;   // register where new character is stored
  static uint8_t rx_state = WAIT;  // Start by waiting for new character.
  static uint8_t tmp_buf[BUF_LEN]; // new characters get appended here. 
  static uint16_t str_end = 0;     // index where next character goes.

  uint8_t tmp_byte = char_reg;     // copy new character from register.
  uint16_t i;

    /* During the WAIT state, look for the header flag character. If the header
     * flag is present, start accepting characters by going to the ACCEPT state.
    case WAIT:
      if(tmp_byte == C_BEGIN)
        rx_state = ACCEPT;

    /* During the ACCEPT state, append unescaped characters to the main buffer.
     * If the current character is an escape character, treat the next character
     * as an escaped character by going to the ESCAPE state.
     * Adding functionality for recognizing additional control characters may be
     * added here.
    case ACCEPT:
      if(tmp_byte == C_BEGIN) // header should never exist here
        str_end = 0;
        rx_state = WAIT;
      if(tmp_byte == C_ESC)
        rx_state = ESCAPE;             // escape next character
        tmp_buf[str_end++] = tmp_byte; // write character

    /* During the ESCAPE state, escape the current character by XOR-ing it with
     * the special character C_XOR. Move to ACCEPT to continue.
    case ESCAPE:
      tmp_buf[str_end++] = tmp_byte ^ C_XOR;
      rx_state = ACCEPT;
  }; // switch rx_state

  /* If the temporary buffer is full after adding a new character, copy the
   * message from the temporary buffer to a new receiving buffer.
   * If this function is run on an interrupt, the following block must be
   * treated as an atomic, critical section.
  if(str_end == BUF_LEN)
    for(i = 0; i < BUF_LEN; i++)
      buf[i] = tmp_buf[i]; // copy completed string
    str_end = 0;
    rx_state = WAIT;
  } // if end of message

} // get_string


Written on the 2nd of April in 2016


  1. W. Simpson, "PPP in HDLC-like Framing," RFC 1662 https://tools.ietf.org/html/rfc1662#page-8" , July 1994.