An Arduino NMEA-0183 Multiplexer - Final Update



Long story short, I've decided to abandon the Arduino. The main reasons are because it can't hold good calibration data for the IMU (leading to bad compass data), it locks up whenever the IMU freezes (or if a wire wiggles loose), and it's a whole lot of unnecessary wires and parts.

But I'm not giving up entirely. I've simply decided to move everything to the Raspberry Pi! I've been using it already to suck the data stream from the Arduino, and then pump that wirelessly to my laptop for OpenCPN. I've already got it up and working, complete with total calibration of the imu, and fault protection. Expect an update on that sometime soon.

Even though I'm done with the Arduino (I'm still going to use it for something.. probably the wind instrument over here), I'm still going to post my final code so that if someone else wants to pick and pull different sections for their own projects, it's available here.

As always, this is written for someone who knows next to nothing about programming--so this isn't the most efficient code out there. It is far from perfect, but it's easy for simpletons like me to understand.

The Whole Arduino NMEA-0183 Code

 ////////////////////////////////////////////////////////////////////////////  
 //  
 // This file is part of RTIMULib-Arduino  
 //  
 // Copyright (c) 2014-2015, richards-tech  
 //  
 // Permission is hereby granted, free of charge, to any person obtaining a copy of  
 // this software and associated documentation files (the "Software"), to deal in  
 // the Software without restriction, including without limitation the rights to use,  
 // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the  
 // Software, and to permit persons to whom the Software is furnished to do so,  
 // subject to the following conditions:  
 //  
 // The above copyright notice and this permission notice shall be included in all  
 // copies or substantial portions of the Software.  
 //  
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,  
 // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A  
 // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT  
 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION  
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE  
 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
 //  
 // BME280 License:  
 // Marshall Taylor @ SparkFun Electronics  
 // https://github.com/sparkfun/SparkFun_BME280_Arduino_Library  
 // This code is released under the [MIT License](http://opensource.org/licenses/MIT).  
 // Please review the LICENSE.md file included with this example. If you have any questions   
 // or concerns with licensing, please contact techsupport@sparkfun.com.  
 // Distributed as-is; no warranty is given.  
 //  
 // Further modified by King Tide Sailing (kingtidesailing.blogspot.com)  
 // Thanks to https://mechinations.wordpress.com for the checksum function  
 //  
 ////////////////////////////////////////////////////////////////////////////  
   
 #include <Wire.h>  
 #include "I2Cdev.h"  
 #include "RTIMUSettings.h"  
 #include "RTIMU.h"  
 #include "RTFusionRTQF.h"  
 #include "CalLib.h"  
 #include <EEPROM.h>  
 #include <PString.h>  
 #include <stdint.h>  
 #include <SparkFunBME280.h>  
 #include <nmea.h>  
   
 RTIMU *imu;               // the IMU object  
 RTFusionRTQF fusion;          // the fusion object  
 RTIMUSettings settings;         // the settings object  
 BME280 atmo;       // the BME-280 Atmospheric Sensor  
 NMEA nmeaDecoder(ALL);         // NMEA data decoder for all sentence types (for the Transducer, and Anemometer)  
    
 // NMEA-0183 Sentences to Print:    
 boolean outputgps = true; // gps  
 boolean outputvhw = true; // waterspeed and heading (speed of vessel relative to water)  
 boolean outputdpt = true; // depth    
 boolean outputmtw = true; // mean temperature of water  
 boolean outputvlw = true; // distance through water   
 boolean outputhdm = true; // magnetic heading    
 boolean outputhdt = true; // true heading    
 boolean outputrot = true; // rate of turn    
 boolean outputxdr = true; // pitch and roll (and rate of pitch and roll)  
 boolean outputvdr = true; // set and drift   
 boolean outputmda = true; // meteorological composite  
   
 // User defined variables (for your own boat, geographic location, and preferences):  
 float magd = -13.73; // magnetic deviation; if it's East, it's negative (13.73E in San Francisco 2015)    
 float tdo = .5;   // transducer offset from waterline (positive) or from bottom of keel (negative) (in meters)    
 float tda = 23.0;  // triducer angle from straight down (in degrees, positive to starboard)  
 float pof = -.07;  // pitch offset from neutral  
 float rof = .39;   // roll offset from neutral  
   
 // Dampening variables  
 const int five = 5;  
 const int ten = 10;  
 const int thirty = 30;  
 int tgps = 0;  
 int tvhw = 0;  
 int t1 = 0;  
 int tp = 0;  
 int tr = 0;  
 int t3 = 0;  
 int t10 = 0;  
 float sogrun[five];  
 float sogtotal = 0;  
 float cogxrun[five];  
 float cogxtotal = 0;  
 float cogyrun[five];  
 float cogytotal = 0;  
 float vhwrun[five];  
 float vhwtotal = 0;  
 float hdmxrun[thirty];  
 float hdmyrun[thirty];  
 float hdmxtotal = 0;  
 float hdmytotal = 0;  
 float rollrun[ten];  
 float rolltotal = 0;  
 float pitchrun[ten];  
 float pitchtotal = 0;  
 float rrlrun[ten];  
 float rrltotal = 0;  
 float rphrun[ten];  
 float rphtotal = 0;  
 float rywrun[ten];  
 float rywtotal = 0;  
 float vdrxrun[ten];  
 float vdryrun[ten];  
 float vdrdrun[ten];  
 float vdrxtotal = 0;  
 float vdrytotal = 0;  
 float drifttotal = 0;  
   
 // Global variables:  
 #define DISPLAY_INTERVAL 100  // rate (in milliseconds) in which MPU9150 results are displayed. This also drives universal counters for ***ref above  
 int gpsworking = 100;      // this ticks up everytime the MPU runs, which is defined in DISPLAY INTERVAL and is used as a counter  
 int wspworking = 100;      // so that if no GPS or Waterspeed data is received for 10 seconds (wspworking*DISPLAY_INTERVAL), then VDR won't calculate  
 int mtwworking = 100;  
 unsigned long lastDisplay;  
 unsigned long lastRate;  
 int sampleCount;  
 boolean vlwfirst = true; // used one time for the VLW sentence  
 float tcd0;       // so is this  
 float r = M_PI / 180.0f; // degrees to radians  
 float d = 180.0f / M_PI; // radians to degrees  
 int t60 = 49;      // used for the temperature sensor  
   
 // Variables exchanged between instruments:  
 float sog; // speed over ground  
 float cog; // course over ground  
 float wsp; // water speed, or hull speed through the water  
 float mtw; // mean temp of water  
 float hdm; // magnetic heading  
 float hdt; // true heading  
 float roll; // roll in degrees  
 float pitch; // pitch  
 float yaw; // yaw (0 yaw corresponds to 270 magnetic)  
 float ror; // rate of roll (degrees per second, negative to port)  
 float rop; // rate of pitch  
 float roy; // rate of yaw  
   
 void setup()  
 {  
  int errcode;  
   
  Serial.begin(57600);  // output port to computer  
  Serial1.begin(4800);  // from GPS  
  Serial2.begin(4800);  // from DST-800 transducer  
  Wire.begin();  
  imu = RTIMU::createIMU(&settings);  
   
  // Checking the IMU, if it fails, simply restarting the connection seems to work sometimes  
  if ((errcode = imu->IMUInit()) < 0) {  
   Serial.print("Failed to init IMU: "); Serial.println(errcode);  
  }  
  if (imu->getCalibrationValid())  
   Serial.println("Using compass calibration");  
  else  
   Serial.println("No valid compass calibration data");  
   
  lastDisplay = lastRate = millis();  
  sampleCount = 0;  
   
  // Slerp power controls the fusion and can be between 0 and 1  
  // 0 means that only gyros are used, 1 means that only accels/compass are used  
  // In-between gives the fusion mix. 0.02 recommended.  
   
  fusion.setSlerpPower(0.02);  
   
  // use of sensors in the fusion algorithm can be controlled here  
  // change any of these to false to disable that sensor  
   
  fusion.setGyroEnable(true);  
  fusion.setAccelEnable(true);  
  fusion.setCompassEnable(true);  
   
  // Setup the dampening functions  
  for (int i = 0; i < five; i++) {  
   sogrun[i] = 0;  
   cogxrun[i] = 0;  
   cogyrun[i] = 0;  
   vhwrun[i] = 0;  
  }  
  for (int i = 0; i < thirty; i++) {  
   hdmxrun[i] = 0;  
   hdmyrun[i] = 0;  
  }  
  for (int i = 0; i < ten; i++) {  
   rollrun[i] = 0;  
   pitchrun[i] = 0;  
   rrlrun[i] = 0;  
   rphrun[i] = 0;  
   rywrun[i] = 0;  
   vdrxrun[i] = 0;  
   vdryrun[i] = 0;  
   vdrdrun[i] = 0;  
  }  
   
  // BME-280 Setup  
  atmo.settings.commInterface = I2C_MODE;  
  atmo.settings.I2CAddress = 0x77;  
  atmo.settings.runMode = 3;  
  atmo.settings.tStandby = 0;  
  atmo.settings.filter = 0;  
  atmo.settings.tempOverSample = 1;  
  atmo.settings.pressOverSample = 1;  
  atmo.settings.humidOverSample = 1;  
  atmo.begin();  
 }  
   
 // calculate checksum function (thanks to https://mechinations.wordpress.com)  
 byte checksum(char* str)  
 {  
  byte cs = 0;  
  for (unsigned int n = 1; n < strlen(str) - 1; n++)  
  {  
   cs ^= str[n];  
  }  
  return cs;  
 }  
   
 // Dampen Function  
 float dampen(float array[], int arraysize, float data, float &total, int t){  
  total = total - array[t];  
  array[t] = data;  
  total = total + array[t];  
  return total / arraysize;  
 }  
   
 void loop()  
 {  
    
  // ***** BU-353 GPS ***** //   
   if (Serial1.available()) {   
   if (nmeaDecoder.decode(Serial1.read())) {  
    char* title = nmeaDecoder.term(0);  
    if (strcmp(title,"GPRMC") == 0) {  
    gpsworking = 0; // resets the "gps is bad" counter back to 0 since it's good  
    sog = nmeaDecoder.term_decimal(7);   
    cog = nmeaDecoder.term_decimal(8);  
    if (sog == 0.0) { // this removes artifacts in cog when sog = 0  
     cog = hdt;   
    }  
   
    // Dampen the last 5 readings  
    float cogxave = dampen(cogxrun, five, cos(cog*r), cogxtotal, tgps);  
    float cogyave = dampen(cogyrun, five, sin(cog*r), cogytotal, tgps);  
    cog = atan2(cogyave,cogxave)*d;  
    if (cog<0.0){  
     cog=360+cog;  
    }  
    sog = dampen(sogrun, five, sog, sogtotal, tgps);  
    tgps++;  
    if (tgps>=five){  
     tgps=0;  
    }  
    if (outputgps == true) {  
     char rmcSentence [71];    
     byte csg;    
     PString strg(rmcSentence, sizeof(rmcSentence));    
     strg.print("$GPRMC,");   
     strg.print(nmeaDecoder.term_decimal(1));   
     strg.print(",");   
     strg.print(nmeaDecoder.term(2));   
     strg.print(",");   
     strg.print(nmeaDecoder.term_decimal(3));   
     strg.print(",");   
     strg.print(nmeaDecoder.term(4));   
     strg.print(",");   
     strg.print(nmeaDecoder.term_decimal(5));   
     strg.print(",");   
     strg.print(nmeaDecoder.term(6));   
     strg.print(",");   
     strg.print(sog);  
     strg.print(",");   
     strg.print(cog);   
     strg.print(",");   
     strg.print(nmeaDecoder.term_decimal(9));   
     strg.print(",,,");   
     strg.print(nmeaDecoder.term(12));   
     strg.print("*");   
     csg = checksum(rmcSentence);    
     if (csg < 0x10) strg.print('0');    
     strg.print(csg, HEX);    
     Serial.println(rmcSentence);    
    }  
    }  
    else {  
    Serial.println(nmeaDecoder.sentence());   
    }  
   }  
   }   
  // ***** End BU-353 GPS ***** //  
   
  // ***** DST-800 Transducer ***** //    
   if (Serial2.available() > 0 ) {    
   if (nmeaDecoder.decode(Serial2.read())) {    
    char* title = nmeaDecoder.term(0);  
   
    // VHW Vessel Water Speed Sentence //  
    if (strcmp(title,"VWVHW") == 0) {    
     wspworking = 0;     // resets the "waterspeed is bad" counter back to 0 since it's good  
     float wsp0 = nmeaDecoder.term_decimal(5);  
   
     // Corrections for heel and speed  
     float wsp1 = wsp0;  
     if (wsp0 > 15){  
      wsp1 = 15;  
     }  
     float pwa = abs(tda - roll); // corrects for the triducer angle offset    
     float fkc = -.02*pwa;    // 5-knot correction for vessel roll  
     float tkc = -.03*pwa-2;   // 10-knot correction for vessel roll  
     if (pwa > 10){  
      tkc = pwa*(.035-.0065*pwa)-2;  
     }  
     float wspcor = fkc*wsp1/5;  
     if (wsp1>5){  
      wspcor = (wsp1*(tkc*(wsp1-5)-2*fkc*(wsp1-10)))/50;  
     }  
     wsp = wsp0+wspcor;  
     wsp = dampen(vhwrun, five, wsp, vhwtotal, tvhw); //dampen over the past 5 readings  
     tvhw++;  
     if (tvhw>=five){  
      tvhw=0;  
     }  
       
     if (outputvhw == true) {    
      char vhwSentence [49];    
      byte csw;    
      PString strw(vhwSentence, sizeof(vhwSentence));    
      strw.print("$VWVHW,");    
      strw.print(hdt);    
      strw.print(",T,");    
      strw.print(hdm);    
      strw.print(",M,");    
      strw.print(wsp);    
      strw.print(",N,");    
      strw.print(wsp*1.15078);    
      strw.print(",M*");    
      csw = checksum(vhwSentence);    
      if (csw < 0x10) strw.print('0');    
      strw.print(csw, HEX);    
      Serial.println(vhwSentence);    
      }    
    }  
   
   // DPT Depth Sentence //  
   if (outputdpt == true) {    
    if (strcmp(title,"SDDPT") == 0) {    
     char dptSentence [22];    
     byte csd;    
     PString strd(dptSentence, sizeof(dptSentence));    
     strd.print("$SDDPT,");    
     strd.print(nmeaDecoder.term_decimal(1)*cos((tda-roll)*r));    
     strd.print(",");    
     strd.print(tdo);    
     strd.print("*");    
     csd = checksum(dptSentence);    
     if (csd < 0x10) strd.print('0');    
     strd.print(csd, HEX);    
     Serial.println(dptSentence);    
    }    
   }  
   
   // MTW Mean Temperature of Water Sentence //  
   if (strcmp(title,"YXMTW") == 0) {  
    mtwworking = 0;  
    mtw = nmeaDecoder.term_decimal(1)*(9/5)+32;  
    if (outputmtw == true) {   
     char mtwSentence [19];  
     byte csf;  
     PString strf(mtwSentence, sizeof(mtwSentence));    
     strf.print("$YXMTW,");    
     strf.print(mtw);    
     strf.print(",F*");    
     csf = checksum(mtwSentence);    
     if (csf < 0x10) strf.print('0');    
     strf.print(csf, HEX);    
     Serial.println(mtwSentence);    
    }    
   }  
   
   // VLW Voyage Log Sentence //  
   if (outputvlw == true) {    
    if (strcmp(title,"VWVLW") == 0) {    
     float tcd = nmeaDecoder.term_decimal(1);    
     if (vlwfirst == true) {    
      tcd0 = tcd;    
      vlwfirst = false;    
     }    
     float dsr = tcd-tcd0;    
     char vlwSentence [25];    
     byte csl;    
     PString strl(vlwSentence, sizeof(vlwSentence));    
     strl.print("$VWVLW,");    
     strl.print(tcd);    
     strl.print(",N,");    
     strl.print(dsr);    
     strl.print(",N*");    
     csl = checksum(vlwSentence);    
     if (csl < 0x10) strl.print('0');    
     strl.print(csl, HEX);    
     Serial.println(vlwSentence);    
     }    
    }    
   }    
  }    
  // ***** End DST-800 Transducer ***** //  
   
  // ***** Integrated Instrumentation (MPU-9150 and Derivatives) ***** //  
  unsigned long now = millis();  
  unsigned long delta;  
  int loopCount = 1;  
   
  while (imu->IMURead()) {  
   // this flushes remaining data in case we are falling behind  
   if (++loopCount >= 10)  
    continue;  
   fusion.newIMUData(imu->getGyro(), imu->getAccel(), imu->getCompass(), imu->getTimestamp());  
   sampleCount++;  
     
   if ((delta = now - lastRate) >= 1000) {  
      
    sampleCount = 0;  
    lastRate = now;  
   }  
   if ((now - lastDisplay) >= DISPLAY_INTERVAL) {  
      
    if (gpsworking < 100) {  
     gpsworking++;  
    }  
    if (wspworking < 100) {  
     wspworking++;  
    }  
    if (mtwworking < 100) {  
     mtwworking++;  
    }  
    lastDisplay = now;  
    RTVector3 pose = fusion.getFusionPose();  
    RTVector3 gyro = imu->getGyro();  
    roll = pose.y() * -1 * d - rof; // negative is port  
    pitch = pose.x() * d - pof;    // negative is nose down  
    yaw = pose.z() * d;    // zero yaw is 270 magnetic  
    ror = gyro.y() * -1 * d;  
    rop = gyro.x() * d;  
    roy = gyro.z() * d;  
    hdm = yaw - 90;  
    if (yaw < 90 && yaw >= -179.99) {  
     hdm = yaw + 270;  
    }  
   
    // Dampen everything out over the last second  
    float hdmxave = dampen(hdmxrun, thirty, cos(hdm*r), hdmxtotal, t3);  
    float hdmyave = dampen(hdmyrun, thirty, sin(hdm*r), hdmytotal, t3);  
    hdm = atan2(hdmyave,hdmxave)*d;  
    if (hdm<0.0){  
     hdm=360+hdm;  
    }  
    pitch = dampen(pitchrun, ten, pitch, pitchtotal, t1);  
    roll = dampen(rollrun, ten, roll, rolltotal, t1);  
    ror = dampen(rrlrun, ten, ror, rrltotal, t1);  
    rop = dampen(rphrun, ten, rop, rphtotal, t1);  
    roy = dampen(rywrun, ten, roy, rywtotal, t1);  
      
    hdt = hdm - magd; // calculate true heading  
    if (hdt > 360.0) {  
     hdt = hdt - 360;  
    }  
    if (hdt < 0.0) {  
     hdt = hdt + 360;  
    }  
   
    // HDM Magnetic Heading //  
    if (outputhdm == true){  
     if (t1 == 9){  
      char hdmSentence [23];  
      byte csm;  
      PString strm(hdmSentence, sizeof(hdmSentence));  
      strm.print("$HCHDM,");  
      strm.print(hdm);  
      strm.print(",M*");  
      csm = checksum(hdmSentence);  
      if (csm < 0x10) strm.print('0');  
      strm.print(csm, HEX);  
      Serial.println(hdmSentence);  
     }  
    }  
   
    // HDT True Heading //  
    if (outputhdt == true) {  
     if (t1 == 9){  
      char hdtSentence [23];  
      byte cst;  
      PString strt(hdtSentence, sizeof(hdtSentence));  
      strt.print("$IIHDT,");  
      strt.print(hdt);  
      strt.print(",T*");  
      cst = checksum(hdtSentence);  
      if (cst < 0x10) strt.print('0');  
      strt.print(cst, HEX);  
      Serial.println(hdtSentence);  
     }  
    }  
   
    // ROT Rate of Turn //  
    if (outputrot == true) {  
     if (t1 == 9){  
      char rotSentence [18];  
      byte csr;  
      PString strr(rotSentence, sizeof(rotSentence));  
      strr.print("$TIROT,");  
      strr.print(roy*cos(pitch)*cos(roll)*60);  
      strr.print(",A*");  
      csr = checksum(rotSentence);  
      if (csr < 0x10) strr.print('0');  
      strr.print(csr, HEX);  
      Serial.println(rotSentence);  
     }  
    }  
   
    // XDR Pitch and Roll //  
    if (outputxdr == true) {  
     if (t1 == 9){  
      char xdrSentence [89];  
      byte csx;  
      PString strp(xdrSentence, sizeof(xdrSentence));  
      strp.print("$IIXDR,A,");  
      strp.print(pitch);  
      strp.print(",D,PTCH,A,");  
      strp.print(roll);  
      strp.print(",D,ROLL,A,");  
      strp.print(rop);  
      strp.print(",D,RPTC,A,");  
      strp.print(ror);  
      strp.print(",D,RROL,A,");  
      strp.print(roy);  
      strp.print(",D,RYAW*");  
      csx = checksum(xdrSentence);  
      if (csx < 0x10) strp.print('0');  
      strp.print(csx, HEX);  
      Serial.println(xdrSentence);  
        
      /*  
      char bdrSentence [37];  
      byte cs3;  
      PString str3(bdrSentence, sizeof(bdrSentence));  
      str3.print("$IIXDR,P,");  
      str3.print(atmo.readFloatPressure()*.00001,5);  
      str3.print(",B,Barometer*");  
      cs3 = checksum(bdrSentence);  
      if (cs3 < 0x10) str3.print('0');  
      str3.print(cs3, HEX);  
      Serial.println(bdrSentence);  
   
      char tdrSentence [37];  
      byte cs4;  
      PString str4(tdrSentence, sizeof(tdrSentence));  
      str4.print("$IIMTA,");  
      str4.print(atmo.readTempF());  
      str4.print(",F*");  
      cs4 = checksum(tdrSentence);  
      if (cs4 < 0x10) str4.print('0');  
      str4.print(cs4, HEX);  
      Serial.println(tdrSentence);  
      */  
        
     }  
    }  
      
     // VDR Set & Drift //   
     if (outputvdr == true) {  
      if (gpsworking < 100 && wspworking < 100) {  
      if (t1 == 9) {  
      float set0 = (cog-hdt)*r; // gets the difference between hdt and cog in radians   
      if (set0 < 0) {   
       set0 = set0+(2*M_PI);   
      }  
      float drift = sqrt(pow(wsp,2)+pow(sog,2)-2*wsp*sog*(cos(set0)));   
      float setr = (M_PI-atan2((sog*sin(set0)),(wsp-sog*cos(set0))))*d;   
      if (wsp == 0 && sog == 0) { // if you're not moving, then set = 0   
       setr = set0;  
      }  
      float set = hdt+setr;   // converting to true instead of relative degrees   
      if (set > 360) {   
       set = set-360;   
      }  
   
      // Dampen out VDR over the last 10 seconds  
      float vdrxave = dampen(vdrxrun, 10, cos(set*r), vdrxtotal, t10);  
      float vdryave = dampen(vdryrun, 10, sin(set*r), vdrytotal, t10);  
      set = atan2(vdryave,vdrxave)*d;  
      if (set<0.0){  
       set=180*(set+1);  
      }  
      drift = dampen(vdrdrun, 10, drift, drifttotal, t10);  
        
      char vdrSentence [37];    
      byte csv;   
      PString strv(vdrSentence, sizeof(vdrSentence));   
      strv.print("$IIVDR,A,");   
      strv.print(set);   
      strv.print(",T,");   
      strv.print(setr);   
      strv.print(",R,");  
      strv.print(drift);   
      strv.print(",N*");   
      csv = checksum(vdrSentence);   
      if (csv < 0x10) strv.print('0');   
      strv.print(csv, HEX);   
      Serial.println(vdrSentence);  
      }  
     }   
     }  
   
     // MDA Meteorological Composite //  
     if (outputmda == true) {  
      if (t1 == 9){  
       float temp = atmo.readTempC();  
       float tempk = atmo.readTempC()+273.15;  
       float pres = atmo.readFloatPressure()*.00001;  
       float rh = atmo.readFloatHumidity();  
       float dewp = (tempk*sqrt(1+(tempk*log(rh/100))/((3.167485-.00244*tempk)*1000000/461.5))-273.15);//*(9/5)+32;  
       char mdaSentence [69];  
       byte csa;  
   
       PString strm(mdaSentence, sizeof(mdaSentence));  
       strm.print("$IIMDA,");  
       strm.print(atmo.readFloatPressure()*.0002953);  
       strm.print(",I,");  
       strm.print(pres);  
       strm.print(",B,");  
       strm.print(temp,1);  
       strm.print(",C,");  
       if (mtwworking<10){  
        strm.print(mtw);  
        strm.print(",C,");  
       }  
       else {  
        strm.print(",C,");  
       }  
       strm.print(rh,0);  
       strm.print(",,");  
       strm.print(dewp);  
       strm.print(",C,,T,,M,,N,,M*");  
       csa = checksum(mdaSentence);  
       if (csa < 0x10) strm.print('0');  
       strm.print(csa, HEX);  
       Serial.println(mdaSentence);  
       t60=0;  
      }  
     }  
   
     // Timers tick up  
     t1++;  
     tp++;  
     tr++;  
     t3++;  
     if(t1>9){  
     t1=0;  
     tp=0;  
     tr=0;  
     t10++;  
     t60++;  
     if (t10>9) {  
      t10 = 0;  
     }  
     if (t60>59){  
       t60=0;  
      }  
     }  
     if (t3>=29){  
      t3=0;  
     }  
   }  
  }  
  // ***** End Integrated Navigation ***** //  
   
 }  

The Arduino Pre-Setup

Most of it is pretty self-explanatory. If not, just browse my previous posts to get a more clear picture. A few changes, though:
  • I added a pitch and roll offset (pof and rof). I left my boat sitting in its slip for a minute, and recorded the average pitch and roll. That's what you see there. Of course, getting down to tenths of a degree is pretty absurd because it doesn't really change anything.
  • Transducer offset. My triducer (this one here: Airmar DST-800 Thru-Hull Triducer Smart Sensor) is mounted on the right side of my boat, at an angle of 23 degrees from straight down.
  • Dampening variables. A few posts ago, I decided to dampen everything. That's a good idea, but pretty much just for heading and VDR. Why's that? Well, the boat is large enough that any speed changes won't take effect for a couple of seconds, thereby negating most dampening benefits. Also, I looked at my "undampened" logs, and the speeds don't really change enough to warrant the extra code. Oh well.
Most everything else in the pre-setup are just timers and variables exchanged between instruments.

Setup()

Self-explananotry, and/or already covered. The differences here are the initialization of the dampening variables, which is necessary for it to work properly, and the addition of the BMP-280 Atmospheric Sensor, which I picked up from sparkfun.com for about $12. It measures temperature, humidity, and pressure, all of which are useful boat numbers.

The Loop()

No need to go over what I've already covered. I dampen out the GPS info, which is totally unnecessary, and whenever my groundspeed is zero, I change the course to my true heading (just a pet peeve when I'm sitting in my slip trying to troubleshoot and my chart plotter is spinning all over).

Next up, the triducer. In this post (http://kingtidesailing.blogspot.com/2015/12/how-to-calibrate-water-speed-for-vessel.html), I described how to correct the paddle wheel/hullspeed/waterspeed data for sensor heel and speed. That's pretty much all that's going on there. Oh, I also add my heading to the sentence. That's kind of useful, since now I only need to log this sentence and the RMC sentence for heading/waterspeed, and course/groundspeed.

I also add a .5 meter offset to the depth sounding to give me depth of the water (my sensor is .5 meters down from the surface), as well as correct it for boat heel and pitch (technically, a boat heeled over at 90 degrees will give a depth sounding reading to the moon).

Temp of the water? I convert it to F, instead of C.

With the VLW sentence, I simply run a quick bit of code to give me the total distance travelled since initialization, since that's impossible otherwise. This is just a bit of gee-whiz info.

The IMU

On to the heart of the code (and it's downfall). The IMU, which I've already explained in this post, runs at 10 times per second, but only prints a sentence once per second. I dampen out pretty much everything (unnecessarily so), and reformatted the XDR sentence to be compatible with NKE marine instruments. 

I've also added two additional sentences for Air Temp and Barometer, which are supposedly compatible with NKE, but I couldn't get them to work. Or with OpenCPN. Which is why they are commented out.

Then we're on to Set and Drift, which was explained here (http://kingtidesailing.blogspot.com/2015/10/set-and-drift-or-speed-and-direction-of.html), and then finally I've implemented a quick routine for the BMP-280 Atmospheric Sensor.

NMEA-0183 MDA Meteorological Composite

I dusted off my old college textbooks, and figure out how to derive the dewpoint from temperature, pressure, and humidity, and compiled it all in the MDA sentence. The MDA sentence, however, is no longer supported by most things. I think it worked with OpenCPN, but it was giving me such a headache that I kind of just quit.

It goes without saying that the barometer is of utmost importance to the sailor, but for me, I mainly want to know the humidity inside the cabin. I'm tired of re-oiling all my woodwork.

Conclusion of the Arduino

I haven't given up on getting calibrated, good data from my boat. I've just given up on using the Arduino because of its limitations.

Like I said earlier, I already have the Raspberry Pi up and working using some python scripts (which I had to learn), but until I get that posted here, I highly suggest you check out the OpenPlotter project, which is essentially what I was trying to do (until I discovered the OpenPlotter project).

Go check it out over here (http://www.sailoog.com/en), since most of that works right out of the box, and it's cheap, and, well, who wants to duplicate effort?


Comments

  1. Hello,

    I'm trying to make use of your code as a NMEA multiplexer (Wind, depth, speed etc. available from legacy boat instruments) and would like to add some analog data (voltage, charge/discharge current, engine temp. ....) and send everything to a RASPI for further processing and display.
    Right now, I have connected a 9 DOF IMU (I2C) and a GPS (serial) to a MEGA.
    Everything is working for a couple of seconds (correct data send), then the MEGA is freezing. Sometimes output stops for a short time, resumes before it finally crashes.
    Did you recognize this kind of behavior? Is there a known dependency on baud rates, DISPLAY_INTERVALS, ...
    Any other thoughts?

    Thank you very much for your kind support

    Matthias

    ReplyDelete
    Replies
    1. How exactly have you connected your IMU to the Arduino? What you're describing has happened to me, and I narrowed it down to a few different causes.

      1) Wiring of the IMU. Make sure you only have the i2C, VCC, and GND connected. Everything else is unnecessary. Also double check they are connected to the right wire (SDA-SDA, SCL-SCL, etc..)

      2) Length of i2C wires. I had to get the short jumper cables to connect it, because if the wires are too long, the voltage drops for i2c and the data is lost. So, make sure the length of wire between the Arduino and the IMU is short (less than 6 inches, but less is better)

      3) Soldering. Ensure the soldered pin heads are done correctly. One of mine had only a little solder, which kept dropping the signal and freezing as you're describing. Part of this was because I had to resolder it multiple times, as I change the location of the IMU and it's orientation with respect to the boat.

      4) Finally, I had to buy a whole new IMU. I had an MPU-9150, and it kept freezing up like you're saying, or else it would completely go hawywire every 10 seconds and the headings would swing a full 360 degrees. I never figured out why. But when I bought a new one (the MPU-9250), I made sure to do one rock solid soldering job, and then it worked flawlessly.

      As far as your other concerns, you could try lowering the Display Interval to whatever resolution you need (ie 100ms, or 1000ms (10hz and 1hz respectively)). Aside from that, I would strongly recommend you just switch over to a RPi and ditch the Arduino. Why? Because you can get what's called an "ellipsoid" and an accelerometer calibration with the RPi too, but you can't with the Arduino due to memory requirements. Even the author of the RTIMULib program has ditched Arduino support for this reason (this library is old, but the RTIMULib2 library is new). Since you're already using a RPi, it would make sense to go this route. I have a detailed writeup of how to integrate all that here: http://kingtidesailing.blogspot.com/2016/04/make-wireless-nmea-0183-multiplexer.html

      Good luck and let me know if any of this helps.

      Delete
    2. Mattias, what NMEA0183 sentence are you using for voltage, charge/discharge current, engine temp? I cant find this for NMEA0183, only 2000?

      According to my findings only RPM exist but not the others (Oil temp, pressure, engine temp, volt level) in NMEA0183.

      Delete
    3. I'm not sure what he's using, but I also couldn't find any NMEA-0183 sentences for those other than RPM. What application are you using to view this?

      If your program can display the XDR sentence, then you'll have to use that, but be sure to follow the exact format (including the valid data and units fields).

      Delete

Post a Comment

Popular Posts