Feb 11, 2015

Generate a CRC for NMEA Strings, Arduino Function

One common format for sending data between computers for from sensors to computers is to send human-readable text strings. While it is less compact than other methods, it is easy to implement, debug and parse in any language without explanation or software decoding, and is more than adequate for moderate message lengths and data rates. A common form is seen in GPS NMEA messages. These are comma-delimited lines with the form of
$MSGTYPE,value,value,value*HH
The output from the program below is:   
$XY,10.1,20.0*03
The content is all of the characters between the $ and the *. The last two characters are a CRC checksum (cyclic redundancy code) to provide an option for the receiving device to verify the message. It is 8 bits, formatted as two hex digits. A search for the way to generate this will lead to many variations of codes. It was a long search to find a solution that actually generated the correct 8-bit code used in gps NMEA style messages. [Here is a working calculator] The key part of the algorithm is the exclusive-OR of successive bytes:

    for (byte x = start_with+1; x<end_with; x++){ // XOR chars between '$' and '*'
      CRC = CRC ^ buffer[x];
    } 
 
I wanted to use this format to send data from several different sensors between two embedded computer boards via serial hardware port. I didn't immediately need CRC coding, but decided to implement it for the sake of robustness that may be required in a future implementation. I converted the code that Elimeléc Lopez* posted into a callable function for Arduino. I used a global character array to hold the result. This code also illustrates the use of character arrays and String variables together in C++ syntax.

Code is executed on a Teensyduino 3.1. Output is printed to the monitor and to UART2, wired to the UART on an O-Droid C1 running linux. Its output is viewed via its comm port directed back to PC and observed in Teraterm.

(Update now maintained on gitHub.)

Usage: 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Demonstrate the use of a function to generate the NMEA 8-bit CRC
// - A message is built and placed into a buffer string
// - The checksum is computed, and formatted to two chars
// - The full string is printed both to the IDE monitor and to a hardware UART
// EX1:  $test*   --> 16
// EX2:  $GPRMC,023405.00,A,1827.23072,N,06958.07877,W,1.631,33.83,230613,,,A*  --> 42

#define SERIALN Serial2    // define which hardware serial port (1 on TD2; 1.2.3 on TD3
const byte buff_len = 90;
char CRCbuffer[buff_len];

// create pre-defined strings to control flexible output formatting
String sp = " ";
String delim = ",";
String splat = "*";
String msg = ""; 

// --------------------------------------------------
void setup() {
  SERIALN.begin(115200, SERIAL_8N1); // configure Teensy HW UART serial port

  Serial.begin(115200); // Init and set rate for serial output (value doesn't matter for Teensy 3)
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo & Teensyduino 3
  }
  // build msg
  Serial.println("NMEA CRC Demo");
  char strX[8];
  char strY[8];
  float x = 10.1;
  float y = 20;
  String cmd = "$XY";    // a command name
  dtostrf(x,4,1,strX);      // format float value to string XX.X
  dtostrf(y,4,1,strY);      
  msg = cmd + delim + strX + delim + strY + splat;
  outputMsg(msg); // print the entire message string, and append the CRC
}

// -----------------------------------------------------------------------
void loop(){ 
}

// -----------------------------------------------------------------------
void outputMsg(String msg) {
  msg.toCharArray(CRCbuffer,sizeof(CRCbuffer)); // put complete string into CRCbuffer
  byte crc = convertToCRC(CRCbuffer);
 
  Serial.print(msg);  // omit CRC in console msg
 
  SERIALN.print(msg);  // repeat for UART output
  if (crc < 16) SERIALN.print("0"); // add leading 0 if needed
  SERIALN.println(crc,HEX);
}

// -----------------------------------------------------------------------
byte convertToCRC(char *buff) {
  // NMEA CRC: XOR each byte with previous for all chars between '$' and '*'
  char c;
  byte i;
  byte start_with = 0;
  byte end_with = 0;
  byte crc = 0;

  for (i = 0; i < buff_len; i++) {
    c = buff[i];
    if(c == '$'){
      start_with = i;
    }
    if(c == '*'){
      end_with = i;
    }      
  }
  if (end_with > start_with){
    for (i = start_with+1; i < end_with; i++){ // XOR every character between '$' and '*'
      crc = crc ^ buff[i] ;  // compute CRC
    }
  }
  else { // else if error, print a msg (to both ports)
    Serial.println("CRC ERROR");
    SERIALN.println("CRC ERROR");
  }
  return crc;
  //based on code by Elimeléc López - July-19th-2013
}



*Based on:  Generate commands and include NMEA-style 1-byte hex CRC. The algorithm in ArduinoWiring is from nmea-checksum-calculator.html by Elimeléc López