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?
Hello,
ReplyDeleteI'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
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.
Delete1) 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.
Mattias, what NMEA0183 sentence are you using for voltage, charge/discharge current, engine temp? I cant find this for NMEA0183, only 2000?
DeleteAccording to my findings only RPM exist but not the others (Oil temp, pressure, engine temp, volt level) in NMEA0183.
This comment has been removed by the author.
Delete