I need to monitor serial input with NMEA messages of various types, forwarding those required as serial output (Arduino pins 1,2). NMEA messages present as a series of variable length comma separated messages, starting with $ and ending with a CR/LF. The last 2 characters are * followed by a checksum consisting of an exclusive or of all characters in the message between the $ and the *, not inclusive of these. The messages occasionally get corrupted, so the length of the incoming string can vary if it is the newline that is lost.
One of the messages, relating to wind speed and angle, is in the new format, MVW, so needs to be converted to its old equivalent, VWR. Therefore the new checksum needs to be calculated, so that it needs to be in an array of char format to do this efficiently.
While I have a long background in computing, starting with assembler in 1971, my only effort with Arduino/C was about 5 years ago when I built an engine monitor to replace the instrument on a boat. This did not involve swapping strings to char and back again, and approaching 79 I am very rusty.
There are some compilation errors in the code below: most of them conflicts between char and string, and the use of the wrong library functions! It is work in progress, as I keep picking it up, and seem to have a number of buffer names. I could persist, but would welcome any input as to the basic strategy to use and chars and strings for the data buffers. I plan to use a Nano as with the engine monitor.
EDITING POST SEVERAL WEEKS LATER
i am posting below, instead of the code that has sat here for some time, the state of play as at October 16, 2024. Thank you for those of you who have taken an interest, particularly J-M-L who is well ahead of me in this game.
I believe that the sketch below is substantially doing what I want. It is validating sentences, filtering out unwanted traffic, and doing the conversion.. We will see if it works in practice, but that is some time away.
//Conversion of MWV to VWR
// October 16 2024 cleaned up debug, appears to be working subject to further testing
// Test messages $WIMWV,045,R,011.63,N,A*09
// $WIMWV,345,R,011.63,N,A*0A
// $WIMWV,300,R,2.5,N,AK*72
// $WIMWV,5,R,14.8N,A*29
#include <ArduinoTrace.h>;
#include <stdio.h>;
#include <string.h>;
char nmea0183_checksum(char* nmea_data);
void mwv_to_vwr();
bool inchar();
void inputread();
void storechar();
void copy_sentence_out();
bool compstr(char* str1, char* str2);
char sentence_type[4];
bool sentence_complete = false;
char tx_reqd[6];
const int maxchar = 49;
char sentence_in[maxchar + 1];
char sentence_out[maxchar + 1];
char rd;
const char valid_sentences[25] = "XTE,RCA,MWV,RCB,APB,RMC,";
const char startChar = '$';
const char endChar = '\n';
char readableChar = ' ';
const char comma = ',';
int idx = 0;
int ix = 0;
int iix = 0;
int exitix;
bool required_data = false;
char wind_tack[2];
char wind_angle[4];
char wind_speed[8];
int wind_angle_int = "";
bool receiving_data = false;
int crc;
char nullstring[3] = "";
char checkchar[3] = { nullstring }; //passing result out from checksum function
char calculated_check[3] = { nullstring }; //calculated from input string
char actual_check[3] = { nullstring }; //extracted from input string for comparison
void setup() {
Serial.begin(9600);
receiving_data = false;
sentence_complete = false;
sentence_in[0] = 0;
}
/* The main loop will read the incoming serial stream, (starting with a $ and ending in a newline)
until the line is complete, and will then extract the sentence type to compare with the required
sentence types. The checksum, immediately following an asterisk (*) of the input sentence will be
checked against the calculated checksum. If they match the data is valid. Then if the sentence type is
MWV, the data is translated into VWR required by the autopilot.*/
void loop() {
//read serial input
inputread();
if (sentence_complete) { //If not, wait for more characters and repeat input read until it is
bool required_data = true; //Flag to indicate sentence is to be transmitted, not dumped
//Calculate check character for incoming sentence
nmea0183_checksum(sentence_in);
calculated_check[0] = checkchar[0]; //remember what checksum should be if not corrupted
calculated_check[1] = checkchar[1];
//look for check character in incoming sentence, following *
//actual_check = "";
for (ix = 0; ix <= (strlen(sentence_in) - 1); ix++) {
if (sentence_in[ix] == '*') {
actual_check[0] = sentence_in[ix + 1];
actual_check[1] = sentence_in[ix + 2];
break;
}
}
DUMP(calculated_check);
if (strcmp(actual_check, calculated_check) == 0) {
// Look for valid sentences to be processed
//look for required sentences
for (int ix = 3; ix <= 5; ix++) {
sentence_type[ix - 3] = sentence_in[ix];
}
sentence_type[3] = 0;
required_data = false; //data validation flag
/*
Now look at each 3 character comma separated pair in valid_sentences, and compare against sentence_type
that has been received in the input buffer
*/
int valid_stringl = strlen(valid_sentences);
bool exitflag = false;
for (ix = 0; ((ix <= valid_stringl - 4) & (exitflag == false)); ix = ix + 4) { //Is data one of the sentences reqd for autopilot?
for (iix = 0; iix <= 2; iix++) {
tx_reqd[iix] = valid_sentences[ix + iix];
tx_reqd[iix + 1] = 0;
}
if (strcmp(tx_reqd, sentence_type) == 0) {
required_data = true; //Matches sentence so pass through
exitflag = true;
} else { //ignore
}
}
if (strcmp(sentence_type, "MWV") == 0) {
mwv_to_vwr();
} else {
copy_sentence_out();
}
if (required_data) {
Serial.println(sentence_out);
} else {
DUMP(actual_check);
DUMP(calculated_check);
}
}
}
sentence_complete = false;
required_data = false;
//delay(10);
}
void mwv_to_vwr() {
bool done = false;
int exitix;
for (ix = 7; (ix <= 9); ix++) {
if (sentence_in[ix] != comma) {
wind_angle[ix - 7] = sentence_in[ix];
wind_angle[ix - 6] = 0;
} else {
break;
}
}
wind_angle_int = atoi(wind_angle);
//Skip over the next field, not required, and look for the comma
exitix = ix;
for (ix = exitix + 1; ix <= exitix + 2; ix++) {
if (sentence_in[ix] == comma) {
break;
}
}
exitix = ix;
for (ix = exitix + 1; (ix <= exitix + 5); ix++) {
wind_speed[ix - exitix - 1] = sentence_in[ix];
if (sentence_in[ix] == comma) {
break;
}
}
if (wind_angle_int > 180) {
wind_angle_int = 360 - wind_angle_int;
wind_tack[0] = 'L';
} else {
wind_tack[0] = 'R';
}
wind_tack[1] = 0;
for (int i = 0; i <= 2; i++) {
wind_angle[i] = '0';
}
wind_angle[3] = 0;
if (wind_angle_int >= 100) {
wind_angle[0] = '1';
wind_angle_int = wind_angle_int - 100;
}
int tens = wind_angle_int / 10;
wind_angle[1] = '0' + tens;
wind_angle[2] = wind_angle_int % 10 + '0';
//wind_speed[ix - exitix - 1] = 0;
// Format output
sentence_out[0] = 0;
strcat(sentence_out, "$WWVWR,");
strcat(sentence_out, wind_angle);
strcat(sentence_out, ",");
strcat(sentence_out, wind_tack);
strcat(sentence_out, ",");
strcat(sentence_out, wind_speed);
strcat(sentence_out, ",N,,,,,*");
nmea0183_checksum(sentence_out);
strcat(sentence_out, checkchar);
}
void inputread() {
if (Serial.available() > 0) {
rd = Serial.read();
switch (rd) {
case startChar:
idx = 0; //$ encountered, start with empty buffer and store $
receiving_data = true;
storechar();
break;
case endChar:
sentence_complete = true;
receiving_data = false;
break;
default:
if ((rd >= readableChar) && receiving_data) {
storechar();
}
}
}
}
// Save character if safe and valid part of message
void storechar() {
if (idx < maxchar && receiving_data) {
sentence_in[idx] = rd;
idx++;
sentence_in[idx] = 0;
} else {
Serial.println("Buffer overflow");
}
}
//calculate checksum
char nmea0183_checksum(char* nmea_data) {
int checksum = 0;
int i;
// ignore the first $ sign, no checksum in sentence
for (i = 1; i < (strlen(nmea_data) - 2); i++) {
//DUMP(i);
//DUMP(nmea_data[i]);
if (nmea_data[i] == '*') {
break;
}
checksum ^= nmea_data[i];
}
byte lowNibble = checksum & 0x0F; // take the 4 lowest bits
byte highNibble = (checksum >> 4) & 0x0F; // take the 4 highest bits
if (lowNibble >= 10) {
lowNibble += 55;
} else {
lowNibble += 48;
}
highNibble += 48;
checkchar[0] = highNibble;
checkchar[1] = lowNibble;
checkchar[2] = 0;
}
void copy_sentence_out() {
strcpy(sentence_out, sentence_in);
}
Thanks in anticipation, and please don't tell me to RTFM, as I have been trying!
John