SeaTalk to Nmea 0183 Conversion

Discussion and support for the Nmea2Wifi multiplexer - a 2-input Nmea 0183 wifi multiplexer.
Post Reply
Luis Sa
Site Admin
Posts: 845
Joined: Thu May 04, 2017 4:12 am

SeaTalk to Nmea 0183 Conversion

Post by Luis Sa » Wed Nov 21, 2018 11:45 am

Hello,

Starting with firmware revision v.56 the multiplexer can accept SeaTalk data on Serial Port P1 and convert that data to the Nmea 0183 format. In order to connect a SeaTalk cable to the Nmea2Wifi multiplexer you should follow the following picture.

seatalk-connection1.jpg

You can also take the power supply to the multiplexer from the SeaTalk bus:

seatalk-connection.jpg

Then, in the settings web page you choose SeaTalk on the Baud Rate selector for port P1. If you do that, the multiplexer interprets arriving data at port P1 as being SeaTalk datagrams and performs SeaTalk to Nmea 0183 conversion. When a valid datagram enters P1 the Green Led blinks.

seatalk-setting.gif
seatalk-setting.gif (9.7 KiB) Viewed 30603 times

A detailed technical explanation regarding the correspondence between SeaTalk datagrams and Nmea 0183 sentences can be found in the following programme (updated on 3th November 2020):

Code: Select all

/* ====================================================
 *  Simple Generator of SeaTalk1 Datagrams for Testing,
 *  Debugging & Conversion from SeaTalk1 to NMEA 0183
 * ====================================================
 *  
 *  Runs on an ESP32 Dev Module compiled with Arduino 1.8.7. On the 
 *  Wemos D1 Mini (based on the ESP8266) everything works except the
 *  Send9bits() used to output an "electrical" SeaTalk signal. By some
 *  reason the micros() fails to generate the 208 us ticks for SeaTalk.
 *  
 *  The variables from which we build the SeaTalk datagrams can be initiated
 *  with fixed values in the setup() section or with random values in the loop() 
 *  section so that each time the loop recycles new values can be used in the tests.
 *  
 *  All the SeaTalk datagrams that can be translated to Nmea 0183 are 
 *  sequencially generated. This generated sequence can be repeated
 *  a number of times or repeated for ever,
 *
 *  The function Check_ST() takes the datagrams and extracts its values 
 *  into variables with similar names to the initial ones. If the extracted 
 *  values are different from the initial ones an error can be printed out.
 *  
 *  Based on:
 *  
 *  - SeaTalk Technical Reference by Thomas Knauf http://www.thomasknauf.de/seatalk.htm
 *  - NMEA Revealed by Eric S. Raymond http://www.catb.org/gpsd/NMEA.html
 *  
 */
 
#include <ESP8266WiFi.h>             // remove comment if you use the ESP8266  

//#define TX_PIN     4                   // D2  or GPIO4  pin for transmission
#define TX_PIN    14      
             
#define WAIT     500                   // time in miliseconds between each SeaTalk datagram

char ST[10];                           // array to build the SeaTalk datagram

boolean Is_RandomInit = false;         // initial SeaTalk variables are randomly generated in
                                       // at each cycle in the loop().

int NumberOfCycles = 1;                // if 0 repeats for ever                                       

boolean Is_PrintError = true;         // variables are recovered from datagrams and if errors 
                                       // are found they are printed in the console.                        
boolean Is_PrintNmea = true;           // Nmea 0183 sentences converted from SeaTalk datagrams  
                                       // are printed in the console.      
boolean Is_SendST = false;              // SeaTalk datagrams send out on TX_PIN
boolean Is_PrintST = true;             // SeaTalk datagrams printed on the console               

char nmea_4[81];                       // NMEA buffer                                                                     

int counter = 0;

void setup() {

// remove comment if you use ESP8266 to avoid creation of persistent AP networks
//  WiFi.forceSleepBegin();
//  delay(50);

  pinMode(TX_PIN, OUTPUT);
  digitalWrite(TX_PIN, LOW);          // iddle state is low (inverted by the transistor)
  Serial.begin(115200);                // output to a monitor
  
  if ( Is_RandomInit == false ) {
    InitFixedVar();
  } 
}

void loop() {
  if ( Is_RandomInit == true ) {
    InitRandomVar();
  }
  if ( NumberOfCycles ) { 
    if ( counter < NumberOfCycles ) {
      BuildDatagrams_ST();
      counter = counter + 1;      
      Serial.print(">>>>>>>>>>>>>>>>>> END OF PASS = "); Serial.println(counter); 
      Serial.println();
      delay(10);
    }
  }
  else {
    BuildDatagrams_ST();
    counter = counter + 1;
    Serial.print(">>>>>>>>>>>>>>>>>> END OF PASS = "); Serial.println(counter); 
    Serial.println();
    delay(10);    
  }
}

// variables to build the SeaTalk datagrams
float DepthBT_ST;                      // depth under the transducer in feet for command 00
char DepthfM_ST;                       // 0 = feet (f)   1 = meters (M)
float  AppWindAngle_ST;                // apparent wind angle for command 10
char WindNM_ST;                        // 0 = knots (N)    1 = m/s (M)
float AppWindSpeed_ST;                 // apparent wind speed for command 11
float SpeedTroughWater_ST;             // speed tough water for command 20
float TripMileage_ST;                  // trip mileage for command 21
float TotalMileage_ST;                 // total mileage for command 22
uint8_t WaterTempC_ST;                 // water temperature Celcius for command 23
uint8_t WaterTempF_ST;                 // water temperature Farhenheit for command 23
float TripLog_ST;                      // trip log for command 25
float TotalLog_ST;                     // total log for command 25
float CurrentSpeed_ST;                 // current speed for command 26
float AverageSpeed_ST;                 // average speed for command 26
float WaterTemperature_ST;             // water temperature in Celcius for command 27  minimum value is -10 !
uint8_t LatDegrees_ST;                 // degrees of latitude N or S for command 50 and 58
char LatNS_ST;                         // 0 = North (N)    1 = South (S)
float LatMinutes_ST;                   // minutes of latitude
uint8_t LonDegrees_ST;                 // degrees of longitude W or E for command 51 and 58
char LonWE_ST;                         // 0 = West (W)    1 = East (E)
float LonMinutes_ST;                   // minutes of latitude (will truncate to 3.65)
float SpeedOG_ST;                      // speed over ground for command 52
uint16_t COG_ST;                       // course over ground for command 53
uint8_t GMTHours_ST;                   // GMT hours for command 54
uint8_t GMTMinutes_ST;                 // GMT minutes for command 54
uint8_t GMTSeconds_ST;                 // GMT seconds for command 54
uint8_t Day_ST;                        // day in month for command 56
uint8_t Month_ST;                      // month for command 56
uint8_t Year_ST;                       // year for command 56
uint16_t APCourse_ST;                  // autopilot course for command 84
uint16_t Heading_ST;                   // compass heading for commands 84 89 and 9C
int8_t Variation_ST;                   // positive = West  negative = East

// these variables will contain the values extracted from the SeaTalk datagrams
float DepthBT;                         // depth under the transducer in feet from command 00
char DepthfM;                          // f = feet M = meters
float AppWindAngle;                    // apparent wind angle from command 10
char WindNM;                           // knots = N   meters/s = M
float AppWindSpeed;                    // apparent wind speed from command 11
float SpeedTroughWater;                // speed tough water from command 20
float TripMileage;                     // trip mileage from command 21
float TotalMileage;                    // total mileage from command 22
uint8_t WaterTempC;                    // water temperature Celcius from command 23
uint8_t WaterTempF;                    // water temperature Farhenheit from command 23
float TripLog;                         // trip log from command 25
float TotalLog;                        // total log from command 25
float CurrentSpeed;                    // current speed from command 26
float AverageSpeed;                    // average speed from command 26
float WaterTemperature;                // water temperature in Celcius from command 27
uint8_t LatDegrees;                    // degrees of latitude N or S from command 50 and 58
char LatNS;                            // N = North  S = South
float LatMinutes;                      // minutes of latitude
uint8_t LonDegrees;                    // degrees of longitude W or E from command 51 and 58
char LonWE;                            // W = West  E = East
float LonMinutes;                      // minutes of latitude (will truncate to 3.65)
float SpeedOG;                         // speed over ground from command 52
uint16_t COG;                          // coarse over ground from command 53
uint8_t GMTHours;                      // GMT hours from command 54
uint8_t GMTMinutes;                    // GMT minutes from command 54
uint8_t GMTSeconds;                    // GMT seconds from command 54
uint8_t Day;                           // day in month from command 56
uint8_t Month;                         // month from command 56
uint8_t Year;                          // year from command 56
uint16_t APCourse;                     // autopilot course fromm command 84
uint16_t Heading;                      // compass heading from commands 84 89 and 9C
int8_t Variation;                      // positive = West  negative = East

void InitRandomVar() {
  // initiate the ST variables with random values 
  DepthBT_ST = Rf1(40,45);             // depth under the transducer in feet for command 00
  DepthfM_ST = Rst('f', 'f');          // 0 = feet (f)   1 = meters (M)
  AppWindAngle_ST = Rf1(323,327);      // apparent wind angle for command 10
  uint16_t ii = AppWindAngle_ST * 2;   // decimal part only .0 or .5
  AppWindAngle_ST = ii;
  AppWindAngle_ST /= 2;
  WindNM_ST = Rst('N', 'N');           // 0 = knots (N)    1 = m/s (M)
  AppWindSpeed_ST = Rf1(13,16);         // apparent wind speed for command 11
  SpeedTroughWater_ST = Rf1(6,6.5);     // speed tough water for command 20
  TripMileage_ST = Rf2(0,10000);       // trip mileage for command 21
  TotalMileage_ST = Rf1(0,5000);       // total mileage for command 22
  WaterTempC_ST = Ru8(14,15);           // water temperature for command 23
  WaterTempF_ST = ( (WaterTempC_ST * 9) / 5 ) + 32;
  TripLog_ST = Rf2(0,10000);           // trip log for command 25
  TotalLog_ST = Rf1(0,100000);         // total log for command 25
  CurrentSpeed_ST = Rf2(6,6.5);        // current speed for command 26
  AverageSpeed_ST = Rf2(6.4,6.4);        // average speed for command 26
  WaterTemperature_ST = Rf1(14,15);   // water temperature in Celcius for command 27
  LatDegrees_ST = Ru8(41,42);           // degrees of latitude N or S for command 50 and 58
  LatNS_ST = Rst('N', 'N');            // 0 = North (N)    1 = South (S)
  LatMinutes_ST = Rf3(33,34);           // minutes of latitude
  LonDegrees_ST = Ru8(12,13);          // degrees of longitude W or E for command 51 and 58
  LonWE_ST = Rst('W', 'W');            // 0 = West (W)    1 = East (E)
  LonMinutes_ST = Rf3(46,47);           // minutes of latitude (will truncate to 3.65)
  SpeedOG_ST = Rf1(6,6.5);              // speed over ground for command 52
  COG_ST = Ru16(34,35);                // course over ground for command 53
  GMTHours_ST = Ru8(15,15);             // GMT hours for command 54
  GMTMinutes_ST = Ru8(22,22);           // GMT minutes for command 54
  GMTSeconds_ST = Ru8(40,41);           // GMT seconds for command 54
  Day_ST = Ru8(23,23);                  // day in month for command 56
  Month_ST = Ru8(11,11);                // month for command 56
  Year_ST = Ru8(18,18);                 // year for command 56
  APCourse_ST = Ru16(33,33);           // autopilot course for command 84
  Heading_ST = Ru16(33,33);            // compass heading for commands 84 89 and 9C
  Variation_ST = Ri8(-2,-3);          // positive = West  negative = East
}

void InitFixedVar() {
  // initiate the ST variables with "hand choosen" values 
  DepthBT_ST = 1470.8;                  // depth under the transducer in feet for command 00
  DepthfM_ST = 'f';                    // 0 = feet (f)   1 = meters (M)
  AppWindAngle_ST = 123.5;             // apparent wind angle for command 10
  WindNM_ST = 'N';                     // 0 = knots (N)    1 = m/s (M)
  AppWindSpeed_ST = 12.3;              // apparent wind speed for command 11
  SpeedTroughWater_ST = 6.5;           // speed tough water for command 20
  TripMileage_ST = 18.23;              // trip mileage for command 21
  TotalMileage_ST = 3459.1;            // total mileage for command 22
  WaterTempC_ST = 14;                  // water temperature for command 23
  WaterTempF_ST = ( (WaterTempC_ST * 9) / 5 ) + 32;
  TripLog_ST = 49.23;                  // trip log for command 25
  TotalLog_ST = 762.7;                 // total log for command 25
  CurrentSpeed_ST = 6.48;              // current speed for command 26
  AverageSpeed_ST = 5.28;              // average speed for command 26
  WaterTemperature_ST = 14.8;          // water temperature in Celcius for command 27
  LatDegrees_ST = 4;                   // degrees of latitude N or S for command 50 and 58
  LatNS_ST = 'S';                      // 0 = North (N)    1 = South (S)
  LatMinutes_ST = 3.7;                 // minutes of latitude
  LonDegrees_ST = 12;                  // degrees of longitude W or E for command 51 and 58
  LonWE_ST = 'W';                      // 0 = West (W)    1 = East (E)
  LonMinutes_ST = 43.956;              // minutes of latitude (will truncate to 3.65)
  SpeedOG_ST = 18.19;                  // speed over ground for command 52
  COG_ST = 321;                        // course over ground for command 53
  GMTHours_ST = 14;                    // GMT hours for command 54
  GMTMinutes_ST = 1;                   // GMT minutes for command 54
  GMTSeconds_ST = 45;                  // GMT seconds for command 54
  Day_ST = 18;                         // day in month for command 56
  Month_ST = 11;                       // month for command 56
  Year_ST = 18;                        // year for command 56
  APCourse_ST = 315;                   // autopilot course for command 84
  Heading_ST = 318;                    // compass heading for commands 84 89 and 9C
  Variation_ST = 2;                   // positive = West  negative = East
}

void BuildDatagrams_ST() {
  
  uint8_t i, j;
  uint16_t ii, jj, kk;
  uint32_t iiii;
  float x;  

  // 00 02 YZ XX XX  Depth below transducer
  // ======================================
  ST[0] = 0x00; ST[1] = 0x02; 
  (DepthfM_ST == 'f') ? ST[2] = 0x00 : ST[2] = 0x40;   // 00 is feet 40 is meters
  ii = DepthBT_ST * 10;
  ST[3] = lowByte(ii); ST[4] = highByte(ii);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();
    if (( DepthBT_ST != DepthBT ) || ( DepthfM_ST != DepthfM ) )  {
      Serial.println("ERROR !!!!!" ); 
      Serial.print("DepthBT was "); Serial.print(DepthBT_ST,1); Serial.print(DepthfM_ST);
      Serial.print(" came as "); Serial.print(DepthBT,1); Serial.println(DepthfM);
    } 
  }
  delay(WAIT);
  
  // 10 01 XX YY  Apparent Wind Angle
  // ================================
  ST[0] = 0x10; ST[1] = 0x01; 
  ii = AppWindAngle_ST * 2;  
  ST[2] = highByte(ii); ST[3] = lowByte(ii);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();
    if ( AppWindAngle_ST != AppWindAngle ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("AppWindAngle was "); Serial.print(AppWindAngle_ST); 
      Serial.print(" came as "); Serial.println(AppWindAngle); 
    } 
  }
  delay(WAIT);
  
  // 11 01 XX 0Y  Apparent Wind Speed
  // ================================
  ST[0] = 0x11; ST[1] = 0x01; 
  ii = AppWindSpeed_ST; ii = ii & 0x7F;             // get the integer part
  jj = AppWindSpeed_ST * 10; jj = jj - 10 * ii;      // get the decimal part
  jj = jj & 0x0F;
  if (WindNM_ST == 'M') { ii = ii | B10000000; }   //set M/S or KTS
  ST[2] = lowByte(ii); ST[3] = lowByte(jj);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }  
  if (Is_PrintError ) { Check_ST();
    if ( ( AppWindSpeed_ST != AppWindSpeed ) || ( WindNM_ST != WindNM ) ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("AppWindSpeed was "); Serial.print(AppWindSpeed_ST,1); Serial.print(WindNM_ST);
      Serial.print(" came as "); Serial.print(AppWindSpeed,1); Serial.println(WindNM);    
    }
  }       
  delay(WAIT);
  
  // 20 01 XX XX  Speed Through Water
  // ================================
  ST[0] = 0x20; ST[1] = 0x01; 
  ii = SpeedTroughWater_ST * 10;                   
  ST[2] = highByte(ii); ST[3] = lowByte(ii);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();
    if ( SpeedTroughWater_ST != SpeedTroughWater ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("SpeedTroughWater was "); Serial.print(SpeedTroughWater_ST,1); 
      Serial.print(" came as "); Serial.println(SpeedTroughWater,1);  
    } 
  }   
  delay(WAIT);

  // 21 02 XX XX 0X Trip Mileage
  // ===========================
  ST[0] = 0x21; ST[1] = 0x02; 
  iiii = TripMileage_ST * 100;
  ST[2] = iiii >> 12; ST[3] = (iiii >> 4) & 0xFF;
  ST[4] = iiii & 0x0F;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();
    if ( TripMileage_ST != TripMileage ) {
      Serial.println("ERROR !!!!!" ); 
      Serial.print("TripMileage was "); Serial.print(TripMileage_ST); 
      Serial.print(" came as "); Serial.println(TripMileage);    
    } 
  }
  delay(WAIT);
  
  // 22 02 XX XX 0X Total Mileage
  // ============================
  ST[0] = 0x22; ST[1] = 0x02; 
  iiii = TotalMileage_ST * 10;
  ST[2] = iiii >> 8; ST[3] = iiii & 0xFF;
  ST[4] = 0;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST(); 
    if ( TotalMileage_ST != TotalMileage ) { 
      Serial.println("ERROR !!!!!" );
      Serial.print("TotalMileage was "); Serial.print(TotalMileage_ST,1); 
      Serial.print(" came as "); Serial.println(TotalMileage,1);    
    } 
  }  
  delay(WAIT);

  // 23 Z1 XX YY  Water Temperature from ST50
  // ========================================
  ST[0] = 0x23; ST[1] = 0x01;           // Z=0?
  ST[2] = WaterTempC_ST;                // Celcius
  ST[3] = WaterTempF_ST;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();  
    if ( ( WaterTempC_ST != WaterTempC ) || ( WaterTempF_ST != WaterTempF ) ){ 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("WaterTempC was "); Serial.print(WaterTempC_ST); 
      Serial.print(" came as "); Serial.println(WaterTempC);
      Serial.print("WaterTempF was "); Serial.print(WaterTempF_ST); 
      Serial.print(" came as "); Serial.println(WaterTempF);  
    } 
  }  
  delay(WAIT);

  // 25 Z4 XX YY UU VV AW Total and Trip Log
  // =======================================
  ST[0] = 0x25;  
  iiii = TotalLog_ST * 10;
  i = iiii / 65536; ST[1] = i << 4; ST[1] |= 0x04;
  iiii = iiii - i * 65536; ST[3] = iiii / 256;
  ST[2] = iiii % 256;
  iiii = TripLog_ST * 100;
  i = iiii / 65536; ST[6] = i;
  iiii = iiii - i * 65536; ST[5] = iiii / 256;
  ST[4] = iiii % 256;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();  
    if ( ( TotalLog_ST != TotalLog ) || ( TripLog_ST != TripLog ) ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("TotalLog was "); Serial.print(TotalLog_ST,1); 
      Serial.print(" came as "); Serial.println(TotalLog,1);
      Serial.print("TripLog was "); Serial.print(TripLog_ST); 
      Serial.print(" came as "); Serial.println(TripLog);    
    }   
  }
  delay(WAIT);

  // 26 04 XX XX YY YY DE Speed Through Water
  // =======================================
  ST[0] = 0x26; ST[1] = 0x04;
  ii = CurrentSpeed_ST * 100;
  ST[2] = highByte(ii); ST[3] = lowByte(ii);
  ii = AverageSpeed_ST * 100;
  ST[4] = highByte(ii); ST[5] = lowByte(ii);
  ST[6] = 0;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();
    if ( ( CurrentSpeed_ST != CurrentSpeed ) || ( AverageSpeed_ST != AverageSpeed ) ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("CurrentSpeed was "); Serial.print(CurrentSpeed_ST); 
      Serial.print(" came as "); Serial.println(CurrentSpeed);
      Serial.print("AverageSpeed was "); Serial.print(AverageSpeed_ST); 
      Serial.print(" came as "); Serial.println(AverageSpeed);  
    } 
  }    
  delay(WAIT);

  // 27 01 XX YY  Water Temperature
  // ==============================
  ST[0] = 0x27; ST[1] = 0x01;     
  x = (WaterTemperature_ST * 10) + 100;  ii = x;                                
  ST[2] = highByte(ii); ST[3] = lowByte(ii);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();
    if ( WaterTemperature_ST != WaterTemperature ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("WaterTemperature was "); Serial.print(WaterTemperature_ST,1); 
      Serial.print(" came as "); Serial.println(WaterTemperature,1);    
    } 
  }   
  delay(WAIT);

  // 50 Z2 XX YY YY Latitude Position
  // ================================
  ST[0] = 0x50; ST[1] = 0x02; 
  ST[2] = LatDegrees_ST;    
  ii = LatMinutes_ST * 100; ii = ii & 0x7FFF;
  if (LatNS_ST == 'S') { ii = ii | 0x8000; }  
  ST[3] = highByte(ii); ST[4] = lowByte(ii);  
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) {  
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();
    // to avoid error report do this because of the 3rd decimal place
    x = LatMinutes_ST * 100; iiii = x; x = iiii; x = x / 100;   
    if ( ( LatDegrees_ST != LatDegrees ) || ( x != LatMinutes ) || ( LatNS_ST != LatNS ) ) { 
      Serial.println("ERROR !!!!!" );
      Serial.print("Latitude was "); Serial.print(LatDegrees_ST); Serial.print(LatNS_ST);
      Serial.print(" "); Serial.print(LatMinutes_ST);
      Serial.print(" came as "); Serial.print(LatDegrees); Serial.print(LatNS);
      Serial.print(" "); Serial.println(LatMinutes);  
    }
  }   
  delay(WAIT);
  
  // 51 Z2 XX YY YY Longitude Position
  // =================================
  ST[0] = 0x51; ST[1] = 0x02; 
  ST[2] = LonDegrees_ST;    
  x = LonMinutes_ST * 100; ii = x; ii = ii & 0x7FFF;
  if (LonWE_ST == 'E') { ii = ii | 0x8000; }
  ST[3] = highByte(ii); ST[4] = lowByte(ii);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();  
    // to avoid error report do this because of the 3rd decimal place
    x = LonMinutes_ST * 100; iiii = x; x = iiii; x = x / 100;   
    if ( ( LonDegrees_ST != LonDegrees ) || ( x != LonMinutes ) || ( LonWE_ST != LonWE ) ) { 
      Serial.println("ERROR !!!!!" );
      Serial.print("Longitude was "); Serial.print(LonDegrees_ST); Serial.print(LonWE_ST);
      Serial.print(" "); Serial.print(LonMinutes_ST);
      Serial.print(" came as "); Serial.print(LonDegrees); Serial.print(LonWE);
      Serial.print(" "); Serial.println(LonMinutes);  
    }
  }
  delay(WAIT);

  // 52 01 XX XX  Speed Over Ground
  // ==============================
  ST[0] = 0x52; ST[1] = 0x01;     
  x = SpeedOG_ST * 10;  ii = x;                                
  ST[2] = highByte(ii); ST[3] = lowByte(ii);
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();
    if ( SpeedOG_ST != SpeedOG ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("SpeedOG was "); Serial.print(SpeedOG_ST,1); 
      Serial.print(" came as "); Serial.println(SpeedOG,1);    
    } 
  }  
  delay(WAIT);

  // 53 U0 VW  Course Over Ground
  // ============================
  ST[0] = 0x53;
  ii = COG_ST / 90; kk = ii ;
  jj = COG_ST % 2;
  if (jj) { kk = (kk | B00001000); }
  ST[1] = kk << 4;
  jj = COG_ST - ii * 90;
  ST[2] = jj / 2; 
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) {  
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  } 
  if (Is_PrintError ) { Check_ST();
    if ( COG_ST != COG ) { 
      Serial.println("ERROR !!!!!" );
      Serial.print("COG was "); Serial.print(COG_ST); 
      Serial.print(" came as "); Serial.println(COG);    
    }  
  }
  delay(WAIT);

  // 54 T1 RS HH GMT Time
  // ====================
  ST[0] = 0x54;  ST[3]= GMTHours_ST;
  i = GMTSeconds_ST & 0x0F; 
  i = i << 4; ST[1] = i | 0x01;
  i = GMTSeconds_ST & 0x30; i = i >> 4;
  j = GMTMinutes_ST; j = j << 2;
  ST[2] = ( i | j );
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();  
    if ( ( GMTHours_ST != GMTHours ) || ( GMTMinutes_ST != GMTMinutes ) || ( GMTSeconds_ST != GMTSeconds ) ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("GMT Time was "); Serial.print(GMTHours_ST); Serial.print(":"); Serial.print(GMTMinutes_ST);
      Serial.print(":"); Serial.print(GMTSeconds_ST);
      Serial.print(" came as "); Serial.print(GMTHours); Serial.print(":"); Serial.print(GMTMinutes);
      Serial.print(":"); Serial.println(GMTSeconds);  
    }
  } 
  delay(WAIT);

  // 56 M1 DD YY  Date
  // =================
  ST[0] = 0x56; 
  i = Month_ST << 4; ST[1] = i | 0x01;                             
  ST[2] = Day_ST; ST[3] = Year_ST;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST(); 
    if ( ( Month_ST != Month ) || ( Day_ST != Day ) || ( Year_ST != Year ) ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("Date DD:MM:YY was "); Serial.print(Day_ST); Serial.print(":"); Serial.print(Month_ST);
      Serial.print(":"); Serial.print(Year_ST);
      Serial.print(" came as "); Serial.print(Day); Serial.print(":"); Serial.print(Month);
      Serial.print(":"); Serial.println(Year);      
    }  
  }     
  delay(WAIT);

  // 58 Z5 LA XX YY LO QQ RR Lat/Lon
  // ===============================
  ST[0] = 0x58; 
  ST[2] = LatDegrees_ST;
  ST[5] = LonDegrees_ST;
  x = LatMinutes_ST * 1000; ii = x;
  ST[3] = ii / 256; ST[4] = ii % 256;
  x = LonMinutes_ST * 1000; ii = x;
  ST[6] = ii / 256; ST[7] = ii % 256;
  i = 0x05;
  if (LatNS_ST == 'S') { i = i | 0x10; }
  if (LonWE_ST == 'E') { i = i | 0x20; }  
  ST[1] = i;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();
    boolean flag = false;
    if ( LatDegrees_ST != LatDegrees ) { flag = true; }   
    if ( LatMinutes_ST != LatMinutes ) { flag = true; } 
    if ( LonDegrees_ST != LonDegrees ) { flag = true; }   
    if ( LonMinutes_ST != LonMinutes ) { flag = true; } 
    if ( LatNS_ST != LatNS ) { flag = true; }  
    if ( LonWE_ST != LonWE ) { flag = true; } 
    if ( flag ) {
      Serial.println("ERROR !!!!!" );
      Serial.print("Latitude was "); Serial.print(LatDegrees_ST); Serial.print(LatNS_ST);
      Serial.print(" "); Serial.print(LatMinutes_ST,3);
      Serial.print(" came as "); Serial.print(LatDegrees); Serial.print(LatNS);
      Serial.print(" "); Serial.println(LatMinutes,3);
      Serial.print("Longitude was "); Serial.print(LonDegrees_ST); Serial.print(LonWE_ST);
      Serial.print(" "); Serial.print(LonMinutes_ST,3);
      Serial.print(" came as "); Serial.print(LonDegrees); Serial.print(LonWE);
      Serial.print(" "); Serial.println(LonMinutes,3);    
    }
  }
  delay(WAIT);

  // 84 U6 VW XY 0Z 0M RR SS TT Heading Course and Rudder
  // ====================================================
  // only Heading and Course to be extracted; rest is zeros
  ST[0] = 0x84; 
  i = Heading_ST / 90; ST[1] = i << 4;
  j = Heading_ST - i * 90; 
  i = j % 2; if ( i ) { ST[1] |= B01000000; }
  ST[1] += 6;    // ST[1] done
  j = j / 2; ST[2] = j;   // 6LSB done
  i = APCourse_ST / 90; j = i << 6;
  ST[2] |= j;    // ST[2] done
  j = APCourse_ST - i * 90; ST[3] = j << 1;
  ST[4] = ST[5] = ST[6] = ST[7] = ST[8] = 0;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();
    if ( ( Heading_ST != Heading ) || ( APCourse_ST != APCourse ) ) {
      Serial.println("ERROR !!!!!" ); 
      Serial.print("Heading was "); Serial.print(Heading_ST); 
      Serial.print(" came as "); Serial.println(Heading);  
      Serial.print("APCourse was "); Serial.print(APCourse_ST); 
      Serial.print(" came as "); Serial.println(APCourse);
    }  
  }  
  delay(WAIT);

  // 89 U2 VW XY 2Z Compass Heading sent by ST40
  // ===========================================
  // only Heading to be extracted; rest is zeros
  ST[0] = 0x89; 
  i = Heading_ST / 90; ST[1] = i << 4;
  j = Heading_ST - i * 90; 
  i = j % 2; if ( i ) { ST[1] |= B01000000; }
  ST[1] += 2;
  ST[2] = j / 2; 
  ST[3] =  ST[4] = 0;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST(); 
    if ( Heading_ST != Heading ) { 
      Serial.println("ERROR !!!!!" );
      Serial.print("Heading was "); Serial.print(Heading_ST); 
      Serial.print(" came as "); Serial.println(Heading);  
    }  
  }
  delay(WAIT);

  // 99 00 XX Compass Variation sent by ST40
  // =======================================
  ST[0] = 0x99; ST[1] = 0; ST[2] = Variation_ST;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    if (n) { Serial.println( nmea_4); nmea_4[0] = 0; }
  }
  if (Is_PrintError ) { Check_ST();  
    if ( Variation_ST != Variation ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("Variation was "); Serial.print(Variation_ST); 
      Serial.print(" came as "); Serial.println(Variation);  
    }
  }    
  delay(WAIT);

  // 9C U1 VW RR Compass Heading and Rudder
  // ======================================
  // only Heading to be extracted; rest is zeros
  ST[0] = 0x9C; 
  i = Heading_ST / 90; ST[1] = i << 4;
  j = Heading_ST - i * 90; 
  i = j % 2; if ( i ) { ST[1] |= B01000000; }
  ST[1] += 1;
  ST[2] = j / 2; 
  ST[3] = 0;
  if ( Is_SendST ) { Send_ST(); }
  if ( Is_PrintST ) { Print_ST(); }
  if ( Is_PrintNmea ) { 
    int n = Parse_ST( ST[0] );
    Serial.println( nmea_4 ); nmea_4[0] = 0; Serial.println();
  }
  if (Is_PrintError ) { Check_ST(); 
    if ( Heading_ST != Heading ) { 
      Serial.println("ERROR !!!!!" ); 
      Serial.print("Heading was "); Serial.print(Heading_ST); 
      Serial.print(" came as "); Serial.println(Heading);  
    }
  }  
}

void Check_ST() {
  
  uint8_t i, j;
  uint16_t ii, jj, kk;
  uint32_t iiii;
  float x;
  
  switch (ST[0]) {
  case 0x00:
    DepthBT = (ST[4] << 8) | ST[3] ;
    DepthBT /= 10;
    ( ST[2] & 0x40 ) ? DepthfM ='M' : DepthfM ='f';
  break;
  
  case 0x10:
    AppWindAngle = (ST[2] << 8) | ST[3];
    AppWindAngle /= 2;
  break;

  case 0x11:
    (ST[2] & 0x80) ? WindNM = 'M' : WindNM = 'N';
    ST[2] &= 0x7F; AppWindSpeed = ST[2];
    x = ST[3]; x = x / 10; AppWindSpeed = AppWindSpeed + x;
  break;

  case 0x20:
    SpeedTroughWater = (ST[2] << 8) | ST[3] ;
    SpeedTroughWater /= 10;
  break;

  case 0x21:
    iiii = (ST[2] << 8) | ST[3] ;
    iiii = iiii << 4; TripMileage = iiii | ST[4];
    TripMileage /= 100 ;
  break;

  case 0x22:
    TotalMileage = (ST[2] << 8) | ST[3];
    TotalMileage /= 10;
  break;
  
  case 0x23:
    WaterTempC = ST[2];
    WaterTempF = ST[3];
  break;

  case 0x25:
    i = ST[1] >> 4; 
    iiii = i * 65536 + ST[3] * 256 + ST[2];
    TotalLog = iiii;
    TotalLog /= 10;
    i = ST[6]; 
    iiii = i * 65536 + ST[5] * 256 + ST[4];
    TripLog = iiii; TripLog /= 100;  
  break; 

  case 0x26:
    CurrentSpeed = (ST[2] << 8) | ST[3];
    CurrentSpeed /= 100;
    AverageSpeed = (ST[4] << 8) | ST[5];
    AverageSpeed /= 100;      
  break; 

  case 0x27:
    WaterTemperature = (ST[2] << 8) | ST[3]; 
    WaterTemperature -= 100;
    WaterTemperature /= 10;
  break;

  case 0x50:
    LatDegrees = ST[2];
    ( ST[3] & 0x80 ) ? LatNS = 'S' : LatNS = 'N';  
    ii = (ST[3] << 8) | ST[4]; 
    LatMinutes = ii & 0x7FFF;
    LatMinutes /= 100;
  break;  

  case 0x51:
    LonDegrees = ST[2];
    ( ST[3] & 0x80 ) ? LonWE = 'E' : LonWE = 'W';  
    ii = (ST[3] << 8) | ST[4]; 
    LonMinutes = ii & 0x7FFF;
    LonMinutes /= 100;
  break;  

  case 0x52:
    ii = (ST[2] << 8) | ST[3];
    SpeedOG = ii;
    SpeedOG /= 10;
  break;

  case 0x53:
    ii = ( ST[1] & 0x30 ); ii = (ii >> 4) * 90 ;
    jj = ( ST[2] & 0x7F ); jj = jj << 1;
    kk = ( ST[1] & 0xC0 ); kk = kk >> 7;
    COG = ii + jj + kk;
  break;

  case 0x54:
    GMTHours = ST[3];
    i = ( ST[2] & 0xFC );
    GMTMinutes = i >> 2;
    i = ( ST[2] & 0x03 ); i = i << 4;
    j = ( ST[1] & 0xF0 ); j = j >> 4;
    GMTSeconds = i | j;
  break;

  case 0x56:
    Year = ST[3];
    Day = ST[2];
    Month = ST[1] & 0xF0;  
    Month = Month >> 4;  
  break;

  case 0x58:
    LatDegrees = ST[2];
    LonDegrees = ST[5];    
    ii = ST[3] << 8; LatMinutes = ii + ST[4];
    LatMinutes /= 1000;
    ii = ST[6] << 8; LonMinutes = ii + ST[7];
    LonMinutes /= 1000;
    ( ST[1] & 0x10 ) ? LatNS = 'S' : LatNS = 'N'; 
    ( ST[1] & 0x20 ) ? LonWE = 'E' : LonWE = 'W';      
  break;  
  
  case 0x84:
    i = ( ST[1] & 0x30 ); i = (i >> 4);
    Heading = i * 90 ;
    i = ( ST[2] & 0x3F ); i = i << 1;
    Heading += i;
    i = ST[1] & 0xC0; i = i >> 6;
    j = 1;
    if (i == 0) { j = 0; }
    if (i == 3) { j = 2; }
    Heading += j;
    i = ( ST[2] & 0xC0 ); i = (i >> 6);
    APCourse = i * 90 ;
    i = ST[3] >> 1;
    APCourse += i;    
  break;  

  case 0x89:
    i = ( ST[1] & 0x30 ); i = (i >> 4);
    Heading = i * 90 ;
    i = ( ST[2] & 0x3F ); i = i << 1;
    Heading += i;
    i = ST[1] & 0xC0; i = i >> 6;
    j = i / 2;
    Heading += j;
  break;  

  case 0x99:
    Variation = ST[2];
  break;  

  case 0x9C:
    i = ( ST[1] & 0x30 ); i = (i >> 4);
    Heading = i * 90 ;
    i = ( ST[2] & 0x3F ); i = i << 1;
    Heading += i;
    i = ST[1] & 0xC0; i = i >> 6;
    j = 1;
    if (i == 0) { j = 0; }
    if (i == 3) { j = 2; }
    Heading += j;
  break;  
  }
}

void Send_ST() {
  int len = ( ST[1] & 0x0F ) + 3;
  Send9bits(ST[0] , 1);
  for (int i=1; i<len; i++) { Send9bits(ST[i] , 0); }
}

void Print_ST() {
  int len = ( ST[1] & 0x0F ) + 3;  
  for (int i=0; i<len; i++) {
    if (ST[i]<16) { Serial.print("0");}
    Serial.print(ST[i],HEX); Serial.print(" ");
  }
   Serial.println();  
}



void Send9bits( byte out, byte sig ) {
  unsigned long time0 = micros();
  // SeaTalk transmits at 4800 baud meaning a "bit time" of 208us
  digitalWrite(TX_PIN, HIGH); delayMicros(208);  // start bit followed by least significative bit ...
  (out & B00000001) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B00000010) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B00000100) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B00001000) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B00010000) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B00100000) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B01000000) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);
  (out & B10000000) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208);  
  (sig) ? digitalWrite(TX_PIN, LOW) : digitalWrite(TX_PIN, HIGH); delayMicros(208); 
  digitalWrite(TX_PIN, LOW); delayMicros(208); delayMicros(208);  // 2 stop bits just in case
  unsigned long time1 = micros();
  Serial.print( "Duration in uS = ");
  Serial.println( time1 - time0);
}

void delayMicros( int n ) {
  unsigned long time0 = micros();
  unsigned long time1 = time0 + n -1 ;
  while( micros() < time1 ){ }
}


float Rf1( float x1, float x2 ) {
  float result;
  result = random( x1 * 10, x2 * 10 );
  result /= 10;
  return result;
}

float Rf2( float x1, float x2 ) {
  float result;
  result = random( x1 * 100, x2 * 100 );
  result /= 100;
  return result;
}
float Rf3( float x1, float x2 ) {
  float result;
  result = random( x1 * 1000, x2 * 1000 );
  result /= 1000;
  return result;
}

uint8_t Ru8( uint8_t x1, uint8_t x2 ) {
  uint8_t result;
  result = random( x1, x2 );
  return result;
}

uint16_t Ru16( uint16_t x1, uint16_t x2) {
  uint16_t result;
  result = random( x1, x2 );
  return result;
}

int8_t Ri8( int8_t x1, int8_t x2 ) {
  int8_t result = random( x1, x2 );
  return result;
}

char Rst( char x1, char x2 ) {
  int x = random( -1, 1 );
  if ( x > 0 ) { return x1; }
  else { return x2; }
}

// following are variables that are stored and later
// combined to construct NMEA sentences

char AppWindAng[8] = ",";         // ex:  "3276.5,"

char TotalMil[10];                // ex:   "6553.6,N,"
boolean Is_TotalMil = false;

boolean Is_MagVar = false;
char MagVar[6] = ",";          // ex:   "90,E"

// minutes have 2 decimal points
char GPS_lat0[11] = ",,";      // ex:  "4104.33,N," meaning N41 4.33
char GPS_lon0[12] = ",,"; 
boolean Is_GPS0 = false;

// minutes have 3 decimal points
// will be used except if Is_GPS1 == false
char GPS_lat1[12] = ",,";
char GPS_lon1[13] = ",,";
boolean Is_GPS1 = false;

char GPS_time[10] = ",,";      // ex: "hhmmss,A,"
char GPS_date[8] = ",";        // ex: "ddmmyy," 
char GPS_sog[8] = ",";         // ex: "6553.5,"

// to know if precision water temp is recently available (datagram 27)
// if yes datagram 23 is ignored
boolean Is_WaterTemp = false;
uint32_t  WaterTemp_Date = 0;
const uint32_t WaterTemp_Recent = 5000; // value in miliseconds

// to know if precision water speed is recently available (datagram 26)
// if yes datagram 20 is ignored
boolean Is_WaterSpeed = false;
unsigned long WaterSpeed_Date = 0;
const uint32_t WaterSpeed_Recent = 5000; // value in miliseconds

// to know if Compass Heading is recently available
boolean Is_CompHead = false;
uint32_t CompHead_Date = 0;
const uint32_t CompHead_Recent = 5000; // value in miliseconds
uint16_t CompHead;

uint16_t Parse_ST( byte b ) {
  switch (b) {
    case 0x00: return Parse00(); break; 
    case 0x10: return Parse10(); break; 
    case 0x11: return Parse11(); break; 
    case 0x20: return Parse20(); break; 
    case 0x21: return Parse21(); break; 
    case 0x22: return Parse22(); break; 
    case 0x23: return Parse23(); break; 
    case 0x25: return Parse25(); break; 
    case 0x26: return Parse26(); break; 
    case 0x27: return Parse27(); break; 
    case 0x50: return Parse50(); break; 
    case 0x51: return Parse51(); break; 
    case 0x52: return Parse52(); break; 
    case 0x53: return Parse53(); break; 
    case 0x54: return Parse54(); break; 
    case 0x56: return Parse56(); break; 
    case 0x58: return Parse58(); break; 
    case 0x84: return Parse84(); break; 
    case 0x89: return Parse89(); break; 
    case 0x99: return Parse99(); break; 
    case 0x9C: return Parse84(); break; // same as 84
  }  
}

int checkSum( char msg[] ) {
  int XOR = 0;
  int len = strlen(msg) - 1;
  for (int i = 1; i < len; i++) {
    XOR = XOR ^ msg[i];
  }
  return XOR;
}

//// DBT using floating see below for integer
//// ========================================
//uint16_t Parse00() {
//  long time0 = micros();
//  const char DBT[] = "$SDDBT,";
//  uint16_t len = 0; float x, y;
//  len += sprintf(nmea_4, DBT);
//  // now you can test the filtering
//  float DepthBT = (ST[3] << 8) | ST[4] ; 
//  if ( CheckNmeaFlt_4() == false ) { return 0; }
//  DepthBT /= 10; 
//  if ( ST[2] & 0x40 ) { // DephBT is meters
//    x = DepthBT * 3.28084; // x is in feet
//    y = DepthBT * 0.546807; // y is phatoms
//    len += sprintf(nmea_4 + len, "%.1f,f,%.1f,M,%.1f,F*", x, DepthBT, y );
//  }
//  else { // DephBT is feet
//    x = DepthBT * 0.3048; // x is in meters
//    y = DepthBT * 0.1667; // y is in fathoms
//    len += sprintf(nmea_4 + len, "%.1f,f,%.1f,M,%.1f,F*", DepthBT, x, y );    
//  }
//  int XOR = checkSum( nmea_4 );
//  len += sprintf(nmea_4 + len, "%.2X", XOR );
//  Serial.println( micros() - time0);
//  return len + 1;
//}


// DBT using integers
// ==================
uint16_t Parse00() {
//  long time0 = micros();
  const char DBT[] = "$SDDBT,";
  uint16_t len = 0;
  len += sprintf(nmea_4, DBT);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; }  
  uint16_t ii = (ST[4] << 8) | ST[3] ;
  uint16_t DD = ii / 10;
  uint8_t F = ii - DD * 10;
//  if ( ST[2] & 0x40 ) { // DephBT is meters
//    len += sprintf(nmea_4 + len, ",,%u.%u,M,,*", DD, F );
//  }
//  else { // DephBT is feet
//    len += sprintf(nmea_4 + len, "%u.%u,f,,,,*", DD, F );    
//  }

  float x = ii * 0.03048;  // ii has depth in feet x 10  x will be meters!
  len += sprintf(nmea_4 + len, "%u.%u,f,%.1f,M,,*", DD, F, x ); 


  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
//  Serial.println( micros() - time0);
  return len + 1;
}

uint16_t Parse10() { 
  // only stores AppWindAng returns 0 no NMEA!
  uint16_t ii = (ST[2] << 8) | ST[3];
  char Z5 = '0'; if ( ii % 2 ) { Z5 = '5'; }
  ii = ii / 2;
  sprintf(AppWindAng, "%u.%c,", ii, Z5 );
  return 0;  
}

// this is VWR alternative to MWV below
// ====================================
//uint16_t Parse11() {
//  const char VWR[] = "$WIVWR,";
//  uint16_t len = 0; float x, y;
//  len += sprintf(nmea_4, VWR);
//  // now you can test the filtering
//  if ( CheckNmeaFlt_4() == false ) { return 0; }
//  boolean IsMS;
//  (ST[2] & 0x80) ? IsMS = true : IsMS = false;   
//  float AppWindSpeed = ST[3];
//  AppWindSpeed /= 10;
//  ST[2] &= 0x7F; AppWindSpeed += ST[2];
//  if (IsMS ) {                     // is meter/second
//    x = AppWindSpeed * 1.944;      // knots
//    y = AppWindSpeed * 3.6;        // km/hour
//    len += sprintf(nmea_4 + len, "%sR,%.1f,N,%.1f,M,%.1f,K*", AppWindAng, x, AppWindSpeed, y );    
//  }
//  else {                           // knots
//    x = AppWindSpeed * 0.5144;     // meter/second
//    y = AppWindSpeed * 1.852;      // km/hour
//    len += sprintf(nmea_4 + len, "%sR,%.1f,N,%.1f,M,%.1f,K*", AppWindAng, AppWindSpeed, x, y );  
//  }
//  int XOR = checkSum( nmea_4 );
//  len += sprintf(nmea_4 + len, "%.2X", XOR );
//  return len + 1;
//}

uint16_t Parse11() {
  const char MWV[] = "$WIMWV,";
  uint16_t len = 0;
  len += sprintf(nmea_4, MWV);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; }  
  boolean IsMS;
  (ST[2] & 0x80) ? IsMS = true : IsMS = false;   
  char AppWindSpeed[7];   // ex: "127.9," 
  uint8_t F = ST[3];
  uint8_t DD = (ST[2] & 0x7F);
  if (IsMS ) {                     // is meter/second
    len += sprintf(nmea_4 + len, "%sR,%u.%u,M,A*", AppWindAng, DD, F );    
  }
  else {                           // knots
    len += sprintf(nmea_4 + len, "%sR,%u.%u,N,A*", AppWindAng, DD, F );  
  }
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1;
}

uint16_t Parse20() {
//  long time0 = micros();
  if ( Is_WaterSpeed ) { if ( ( millis() - WaterSpeed_Date ) < WaterSpeed_Recent ) { return 0; } }
  const char VHW[] = "$VWVHW,";
  uint16_t len = 0;
  len += sprintf(nmea_4, VHW);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; }
  uint16_t ii = (ST[2] << 8) | ST[3] ;
  uint16_t DD = ii / 10;
  uint8_t F = ii - DD * 10; 
  boolean flag = false; 
  if (Is_CompHead ) { if ( ( millis() - CompHead_Date ) < CompHead_Recent ) { flag = true; } }
  if ( flag ) {
    len += sprintf(nmea_4 + len, ",,%u,M,%u.%u,N,,,*" , CompHead, DD, F );      
  }
  else {
    len += sprintf(nmea_4 + len, ",,,,%u.%u,N,,,*" , DD, F );  
  }
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
//  Serial.println( micros() - time0);  
  return len + 1;  
}

uint16_t Parse21() {
  if ( Is_TotalMil == false ) { return 0; }
  const char VLW[] = "$IIVLW,";
  uint16_t len = 0;
  len += sprintf(nmea_4, VLW);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; }  
  uint32_t iiii = (ST[2] << 8) | ST[3] ;
  iiii = iiii << 4; iiii = iiii | ST[4];
  uint16_t DD = iiii / 100;
  uint16_t FF = iiii - DD * 100;
  len += sprintf(nmea_4 + len, "%s%u.%u,N*" , TotalMil, DD, FF );  
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1;  
}

uint16_t Parse22() {
  uint16_t ii = (ST[2] << 8) | ST[3];
  uint16_t DD = ii / 10;
  uint8_t F = ii - DD * 10;
  sprintf(TotalMil, "%u.%u,N," , DD, F );
  Is_TotalMil = true;
  return 0;  
}
uint16_t Parse23() {
  if ( Is_WaterTemp ) { if ( ( millis() - WaterTemp_Date ) < WaterTemp_Recent ) { return 0; } }
  const char MTW[] = "$IIMTW,";
  uint16_t len = 0;
  len += sprintf(nmea_4, MTW);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; }  
  uint8_t WaterTemperature = ST[2]; 
  len += sprintf(nmea_4 + len, "%u,C*" , WaterTemperature );  
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1;  
}

uint16_t Parse25() {
//  long time0 = micros();
  const char VLW[] = "$IIVLW,";
  uint16_t len = 0;
  len += sprintf(nmea_4, VLW);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; } 
  uint8_t i = ST[1] >> 4; 
  uint32_t iiii = i * 65536 + ST[3] * 256 + ST[2];
  uint32_t D_Tot = iiii / 10;
  uint8_t F_Tot = iiii - D_Tot * 10;
  i = ST[6]; 
  iiii = i * 65536 + ST[5] * 256 + ST[4];
  uint32_t D_Tri = iiii / 100;
  uint8_t F_Tri = iiii - D_Tri * 100;  
  len += sprintf(nmea_4 + len, "%u.%.1u,N,%u.%.2u,N*" , D_Tot, F_Tot, D_Tri, F_Tri );  
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
//  Serial.println( micros() - time0);  
  return len + 1; 
}

uint16_t Parse26() {
  Is_WaterSpeed = true; WaterSpeed_Date = millis();
  const char VHW[] = "$VWVHW,";
  uint16_t len = 0;
  len += sprintf(nmea_4, VHW);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; }
  uint16_t ii = (ST[2] << 8) | ST[3] ;
  uint16_t DD = ii / 100;
  uint16_t FF = ii - DD * 100;
  boolean flag = false; 
  if (Is_CompHead ) { if ( ( millis() - CompHead_Date ) < CompHead_Recent ) { flag = true; } }
  if ( flag ) {
    len += sprintf(nmea_4 + len, ",,%u,M,%u.%.2u,N,,,*" , CompHead, DD, FF );      
  }
  else {
    len += sprintf(nmea_4 + len, ",,,,%u.%.2u,N,,,*" , DD, FF );  
  }
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1;  
}

uint16_t Parse27() {
  Is_WaterTemp = true; WaterTemp_Date = millis();
  const char MTW[] = "$IIMTW,";
  uint16_t len = 0;
  len += sprintf(nmea_4, MTW);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; } 
  uint16_t ii = (ST[2] << 8) | ST[3]; 
  ii = ii - 100;
  uint16_t DD = ii / 10;
  uint8_t F = ii - DD * 10;
  len += sprintf(nmea_4 + len, "%u.%.1u,C*" , DD, F );  
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1;  
}

uint16_t Parse50() {
  uint16_t DD = ST[2];  // LatDegrees
  char LatNS = 'N'; if ( ST[3] & 0x80 ) { LatNS = 'S'; }
  uint16_t ii = (ST[3] << 8) | ST[4]; 
  ii = ii & 0x7FFF;
  uint16_t MM = ii / 100;
  uint16_t FF = ii - MM * 100;
  sprintf(GPS_lat0, "%u%.2u.%u,%c," , DD, MM, FF, LatNS ); 
  Is_GPS0 = true;
  return 0;
}

uint16_t Parse51() {
  uint16_t DD = ST[2];
  char LonWE = 'W'; if ( ST[3] & 0x80 ) { LonWE = 'E'; }
  uint16_t ii = (ST[3] << 8) | ST[4]; 
  ii = ii & 0x7FFF;
  uint16_t MM = ii / 100;
  uint16_t FF = ii - MM * 100;
  sprintf(GPS_lon0, "%u%.2u.%u,%c," , DD, MM, FF, LonWE ); 
  Is_GPS0 = true;
  return 0;
}

uint16_t Parse52() {
  uint16_t ii = (ST[2] << 8) | ST[3];
  uint8_t SS = ii / 10;
  uint8_t FF = ii - SS * 10; 
  sprintf(GPS_sog, "%u.%u," , SS, FF );
  return 0;  
}
uint16_t Parse53() {
  if ( ( Is_GPS0 == false ) && ( Is_GPS1 == false ) )  { return 0; } 
  const char RMC[] = "$GPRMC,";
  uint16_t len = 0;
  len += sprintf(nmea_4, RMC);
  // now you can test the filtering
  if ( CheckNmeaFlt_4() == false ) { return 0; } 
  len += sprintf(nmea_4 + len, GPS_time );;
  if ( Is_GPS1 ) {
    len += sprintf(nmea_4 + len, "%s%s", GPS_lat1, GPS_lon1 );  
  }
  else {
    len += sprintf(nmea_4 + len, "%s%s", GPS_lat0, GPS_lon0 );      
  }
  len += sprintf(nmea_4 + len, GPS_sog ); 
  uint16_t ii = ( ST[1] & 0x30 ); ii = (ii >> 4) * 90 ;
  uint16_t jj = ( ST[2] & 0x7F ); jj = jj << 1;
  uint16_t kk = ( ST[1] & 0xC0 ); kk = kk >> 7;
  uint16_t COG = ii + jj + kk;
  len += sprintf(nmea_4 + len, "%u,%s,%s*", COG, GPS_date, MagVar ); 
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1; 
}
uint16_t Parse54() {
  uint8_t HH = ST[3];
  uint8_t i = ( ST[2] & 0xFC );
  uint8_t MM = i >> 2;
  i = ( ST[2] & 0x03 ); i = i << 4;
  uint8_t j = ( ST[1] & 0xF0 ); j = j >> 4;
  uint8_t SS = i | j;
  sprintf(GPS_time, "%u%.2u%.2u,A," , HH, MM, SS ); 
  return 0;  
}

uint16_t Parse56() {
  uint8_t YY = ST[3];
  uint8_t DD = ST[2];
  uint8_t MM = ST[1] & 0xF0;  
  MM = MM >> 4; 
  sprintf(GPS_date, "%.2u%.2u%.2u" , DD, MM, YY );  
  return 0;  
}

uint16_t Parse58() {
  char LatNS, LonWE;
  ( ST[1] & 0x10 ) ? LatNS = 'S' : LatNS = 'N'; 
  ( ST[1] & 0x20 ) ? LonWE = 'E' : LonWE = 'W';
  uint16_t DD = ST[2];
  uint16_t ii = ST[3] << 8; ii = ii + ST[4];
  uint16_t MM = ii / 1000;
  uint16_t FF = ii - MM * 1000;
  sprintf(GPS_lat1, "%u%.2u.%.3u,%c," , DD, MM, FF, LatNS ); 
  // now longitude
  DD = ST[5]; 
  ii = ST[6] << 8; ii = ii + ST[7];
  MM = ii / 1000;
  FF = ii - MM * 1000;
  sprintf(GPS_lon1, "%u%.2u.%.3u,%c," , DD, MM, FF, LonWE );
  Is_GPS1 = true;  
  return 0;  
}

uint16_t Parse84() {
  uint8_t i, j;
  i = ( ST[1] & 0x30 ); i = (i >> 4);
  CompHead = i * 90 ;
  i = ( ST[2] & 0x3F ); i = i << 1;
  CompHead += i;
  i = ST[1] & 0xC0; i = i >> 6;
  j = 1;
  if (i == 0) { j = 0; }
  if (i == 3) { j = 2; }
  CompHead += j;
  Is_CompHead = true; CompHead_Date = millis();
  uint16_t len = 0;
  if ( Is_MagVar ) {
    const char HDG[] = "$HCHDG,";
    len += sprintf(nmea_4, HDG);
    // now you can test the filtering
    if ( CheckNmeaFlt_4() == false ) { return 0; }
    len += sprintf(nmea_4 + len, "%u,,,%s*", CompHead , MagVar );     
  }
  else {
    const char HDM[] = "$HCHDM,";
    len += sprintf(nmea_4, HDM);
    // now you can test the filtering
    if ( CheckNmeaFlt_4() == false ) { return 0; }
    len += sprintf(nmea_4 + len, "%u,M*", CompHead );  
  }
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1; 
}

uint16_t Parse89() {
  uint8_t i, j;
  i = ( ST[1] & 0x30 ); i = (i >> 4);
  CompHead = i * 90 ;
  i = ( ST[2] & 0x3F ); i = i << 1;
  CompHead += i;
  i = ST[1] & 0xC0; i = i >> 6;
  j = i / 2;
  CompHead += j;
  Is_CompHead = true; CompHead_Date = millis();
  uint16_t len = 0;
  if ( Is_MagVar ) {
    const char HDG[] = "$HCHDG,";
    len += sprintf(nmea_4, HDG);
    // now you can test the filtering
    if ( CheckNmeaFlt_4() == false ) { return 0; }
    len += sprintf(nmea_4 + len, "%u,,,%s*", CompHead , MagVar );     
  }
  else {
    const char HDM[] = "$HCHDM,";
    len += sprintf(nmea_4, HDM);
    // now you can test the filtering
    if ( CheckNmeaFlt_4() == false ) { return 0; }
    len += sprintf(nmea_4 + len, "%u,M*", CompHead );  
  }
  int XOR = checkSum( nmea_4 );
  len += sprintf(nmea_4 + len, "%.2X", XOR );
  return len + 1; 
}

uint16_t Parse99() {
  int8_t i = ST[2];
  char EW = 'E' ; if ( i >= 0 ) { EW = 'W' ; } 
  i = abs(i);
  sprintf(MagVar, "%u,%c", i, EW );
  Is_MagVar = true;
  return 0;  
}

boolean CheckNmeaFlt_4() {
  return true;  // this is the Nmea sentence filtering - not used here
}

Regards, Luis

lula6894
Posts: 3
Joined: Mon Jul 13, 2020 9:09 pm

Re: SeaTalk to Nmea 0183 Conversion

Post by lula6894 » Tue Jul 14, 2020 7:25 am

Good morning Luis,
I arrived to your forum yesterday looking for help to connect a NodeMCU with Seatalk. I try to read ST through Thomas Knauf bidirectional interface (modified by Berreizeta to work at +5v) to emulate a ST60 Maxiview remote control.
I am trying to use parts of your software ("Simple Generator of SeaTalk1 Datagrams for Testing, Debugging & Conversion from SeaTalk1 to NMEA 0183") but not sure about:
"Runs on an ESP32 Dev Module compiled with Arduino 1.8.7. On the Wemos D1 Mini (based on the ESP8266) everything works except the Send9bits() used to output an "electrical" SeaTalk signal. By some reason the micros() fails to generate the 208 us ticks for SeaTalk"
I understood that the timer used was not enough precise and make a modification suggested in some examples in the web to get a high precision timer. But unfortunately does not work. My first trial is to emulate ST and present it over the ST60 Maxiview, without success.
Reading your forum I discovered you have develop several HW/SW for ST, NMEA, ... may be you can help me the way forward. I could provide more details if needed, let me know.
All the best

Luis Sa
Site Admin
Posts: 845
Joined: Thu May 04, 2017 4:12 am

Re: SeaTalk to Nmea 0183 Conversion

Post by Luis Sa » Tue Jul 14, 2020 9:55 am

Hello,

There was a problem in my receiving end (Seatalk1 input timing) and the problem was not on the Send9bits. Please note that in a real bus you need bus contention which my software does not considers. That program was used by me to decipher the conversion. Later I discovered that I had a Raymarine conversion box and revised the multiplexer firmware based on the experiments that I made with the box. I did not return back to correct my initial simulation Seatalk1 software.

I can send by email the ino file that I am using in the multiplexer. I arrived in Greece last week and I have little time for development but I would like to know more about your project. If you develop a bus contention hardware I could consider to make the multiplexer bidirectional on the Seatalk1 port.

Regards, Luis

lula6894
Posts: 3
Joined: Mon Jul 13, 2020 9:09 pm

Re: SeaTalk to Nmea 0183 Conversion

Post by lula6894 » Tue Jul 14, 2020 10:46 am

Hi Luis,
Good winds sailing in Greece!!!
I'll appreciate if you send me the ino file, is it a ESP8622? I have no problem to change to an arduino if I can run the software easily. I can back to ESP8622 after having the system running.
I also own a Raymarine ST-NMEA and I remember that using Raytech v6 (Raymarine software) was also possible to access through the RS232 connector to ST bus and create your own screens. A point to investigate.
By the way, I'm based in Cadiz, Spain. Do not hesitate to contact if you stop here anytime.
Best

lula6894
Posts: 3
Joined: Mon Jul 13, 2020 9:09 pm

Re: SeaTalk to Nmea 0183 Conversion

Post by lula6894 » Mon Nov 02, 2020 5:56 pm

Luis Sa wrote:
Tue Jul 14, 2020 9:55 am
Hello,

There was a problem in my receiving end (Seatalk1 input timing) and the problem was not on the Send9bits. Please note that in a real bus you need bus contention which my software does not considers. That program was used by me to decipher the conversion. Later I discovered that I had a Raymarine conversion box and revised the multiplexer firmware based on the experiments that I made with the box. I did not return back to correct my initial simulation Seatalk1 software.

I can send by email the ino file that I am using in the multiplexer. I arrived in Greece last week and I have little time for development but I would like to know more about your project. If you develop a bus contention hardware I could consider to make the multiplexer bidirectional on the Seatalk1 port.

Regards, Luis
Hi again Luis,
I have my seatalk ST60 Maxiview remote working well, thanks for your information. Please don't forget to send the ino file you mention, to have a look and to improve my solution.
All the best

Luis Sa
Site Admin
Posts: 845
Joined: Thu May 04, 2017 4:12 am

Re: SeaTalk to Nmea 0183 Conversion

Post by Luis Sa » Tue Nov 03, 2020 3:15 am

Hello Jose Luis,

The ino file is that one here in this post. I just replaced with my last version and just run it in both the ESP32 and ESP8266.


.... writing ....

Luis Sa
Site Admin
Posts: 845
Joined: Thu May 04, 2017 4:12 am

Re: SeaTalk to Nmea 0183 Conversion

Post by Luis Sa » Tue Nov 03, 2020 3:24 am

Hello Jose Luis,

Is the ino file you are referring to, the one here in this post, above? I just replaced it with my last version and just run it in both the ESP32 and ESP8266.

I am not sure if you had a look to a SeaTalk1 generator found here.

Regards, Luis

Post Reply