Simple Protocol for One-way IR Arduino Communications

In a previous post I had posted code that  transmitted a message through an infrared link between two Arduinos. The main issue with this code is that if the infrared beam is intermittently blocked during the transmission, e.g. by waving your fingers in front of the IR LEDs, bits will be dropped and the message received won’t match the message sent. In this post I describe a simple protocol that provides error detection of the received message. In my weather station application, the communications is only one-way and so there is no mechanism for the receiver to re-request a garbled message; the message is sent periodically with updated temperature and barometric pressure data.

The message is put in a simple “envelope”. The format of the packet sent is as follows:

<STX><message buffer><ETX><chksum>

where:

  • <STX> is the “start of transmission” marker and is the character 0x02.
  • <message buffer> is the text message we want to send.
  • <ETX> is the “end of transmission” marker and is the character 0x03.
  • <chksum> is a single byte check-sum of all of the characters in the <message buffer>. You can see in the code that this check-sum is calculated by looping through the message buffer and adding the values of the individual characters.

A check-sum is not going to completely guarantee error detection but it is simpler to implement than a Cyclic Redundancy Check (CRC) check, and is sufficient for my application.

Below is a sample program that transmits “Hello” over the IR link. The function sendMessagePacket constructs the packet:

/*
  IR_Transmitter
  
  Author: Terry Field
  Date  : 20 June 2014
  
  Board: Arduino Uno
  
  Purpose: This demonstration program uses PWM to 
  output a 38 kHz carrier on pin 11 and serial data on
  pin 2. The 38 kHz output is used to modulate the 
  serial output from pin 2 so that it can be detected
  by an IR receiver. 
*/

#include <SoftwareSerial.h>

const int rxPin = 3;  
const int txPin = 2; 

const int STX = 2;
const int ETX = 3;

const long IR_CLOCK_RATE = 38000L;

#define pwmPin 11   // IR Carrier

SoftwareSerial displayPort(rxPin, txPin); // Rx, Tx

void setup()  {
  // set the data rate for the Serial port
  displayPort.begin(2400);
  
  // toggle on compare, clk/1
  TCCR2A = _BV(WGM21) | _BV(COM2A0);
  TCCR2B = _BV(CS20);
  // 38kHz carrier/timer
  OCR2A = (F_CPU/(IR_CLOCK_RATE*2L)-1);
  pinMode(pwmPin, OUTPUT);
}

/*
  sendMessagePacket: this function sends a text message over a serial link.
  The message is in the format <STX><message><ETX><chksum>
*/
void sendMessagePacket(char message[])
{
  int index = 0;
  byte checksum = 0;
  
  displayPort.print((char)STX);
  while (message[index] != NULL)
  {
    displayPort.print(message[index]);
    checksum = checksum + (byte)message[index];
    index++;
  }
  displayPort.print((char)ETX);
  displayPort.print((char)checksum);
}

void loop ()
{
   char msg[] = "Hello";
   sendMessagePacket(msg); 
   delay(1000);
}

The code to read from the serial port and implement error checking is in the function readMessage in the program SerialReaderProtocol shown below:

/*
  Program: SerialReaderProtocol
 
 Author: Terry Field
 Date  : 20 June 2014
 
 Board: Arduino Uno
 
 Purpose: This program reads from a serial port and 
 outputs it to another. This program is expecting 
 messages to be in the format
 <STX><message><ETX><chksum>
 
*/
 
#include <SoftwareSerial.h>

const int STX = 2;
const int ETX = 3;
const int INTERCHAR_TIMEOUT = 50;

const int rxPin = 2;
const int txPin = 255;

// set up a new serial port
SoftwareSerial displayPort =  SoftwareSerial(rxPin, txPin, true);

/*
  Function name: readChar
  
  Input:
    timeout: time in milliseconds to wait for a character on the
    serial port
  
  Output:
    inChar: character read from the serial port
  
  Returns:
    true if a character was read, otherwise false
*/
boolean readChar(unsigned int timeout, byte *inChar)
{
  unsigned long currentMillis  = millis();
  unsigned long previousMillis = currentMillis;

  while (currentMillis - previousMillis < timeout)
  {    
    if (displayPort.available())
    {
      (*inChar) = (byte)displayPort.read();
      return true;
    }
    currentMillis = millis();
  } // while  
  return false;
}

/*
  Function name: readMessage
  
  Input:
    timeout: time in milliseconds to wait for a message
    message: pointer to message buffer
    maxMessageSize: size of message buffer
  
  Output:
    inChar: character read from the serial port
  
  Returns:
    true if a message was received, otherwise false
*/
boolean readMessage(char *message, int maxMessageSize, unsigned int timeout)
{
  byte inChar;
  int  index = 0;
  boolean completeMsg = false;
  byte checksum = 0;

  unsigned long currentMillis  = millis();
  unsigned long previousMillis = currentMillis;

  while (currentMillis - previousMillis < timeout)
  {
    if (displayPort.available())
    {       
      inChar = (byte)displayPort.read();
  
      if (inChar == STX) // start of message?
      {
        while (readChar(INTERCHAR_TIMEOUT, &inChar))
        {
          if (inChar != ETX)
          {
            message[index] = inChar;
            index++;
            if (index == maxMessageSize) //buffer full
              break;
            checksum = checksum + inChar;
          }
          else // got ETX, next character is the checksum
          {
            if (readChar(INTERCHAR_TIMEOUT, &inChar))
            {
              if (checksum == inChar)
              {
                message[index] = NULL; // good checksum, null terminate message
                completeMsg = true;
                break;      
              }
              else // bad checksum
              {
                completeMsg = false;
                break;
              }
            } // read checksum
          } // next character is the checksum
        } // while read until ETX or timeout
      } // inChar == STX
    } // displayPort.available()
    if (completeMsg)
      break;
    currentMillis = millis();
  } // while
  return completeMsg;
}

void setup()  {
  // set the data rate for the SoftwareSerial port
  displayPort.begin(2400);
  Serial.begin(9600);
  Serial.println("\nStarting to listen...\n");
  delay(200);
}


void loop() 
{
  const int MSG_BUF_SIZE = 255;
  char msg[MSG_BUF_SIZE];
  
  if (readMessage(msg, MSG_BUF_SIZE, 5000))
  {
    Serial.println(msg);
  }
  else
    Serial.println("No message");
}



The function receiveMessage reads from the serial port, discarding all characters until it receives a STX. Once the STX is received it reads characters, adding them to the message buffer and calculating the check-sum. If an ETX is received, it assumes the next character is the check-sum. Once the check-sum is received, it compares it with the calculated check-sum; if the two check-sums do not match, the message is discarded.

I am using these functions to send text data only and so I am not expecting the bytes that represent STX (0x02) and ETX (0x03) to be in the message buffer. Note that once the message is received, it is null-terminated.

Note on compiling the code above: WordPress won’t render the null character in the HTML code listing above and so you need to replace NULL in the code listing with the <single quote><backslash>0<single quote>. I’ll see if I can fix the rendering issue.