NMEA2000 Ankeralarm mit dem Arduino aufgebaut

Ein Ankeralarm hält die Ankerposition des Bootes fest und ermittelt dann kontinuierlich den Abstand zum Ankerplatz über die Nacht. Es wird ein Alarmradius eingegeben den das Boot nicht verlassen darf. Alle gehen schlafen und sollen geweckt werden, wenn der Anker nicht hält, weil z.B. der Wind stärker geworden ist, der Anker sich gelöst hat und der eingestellte maximale Ankerradius verlassen wird.

Dazu benötige ich einen GPS Empfänger (hier mein Lowrance HDS), ein NMEA2000 Netzwerk, den Arduino mit Schnittstellenplatine zur Berechnung der Positionen und dem Abstand, einen Poti um die Alarmdistanz (maximaler Ankerradius) einstellen zu können und einen Alarmtongeber (hier habe ich nur eine LED angeschlossen) der ggf. noch über ein Relais geschaltet werden kann.

Mit den vorherigen Betrachtungen kann ich die Position einlesen. Die erste Position beim Einschalten wird in meinem Programm als Ankerposition gespeichert (startposition, lat0, lon0).

Der 10kOhm Poti wird an 5V und GND angeschlossen, der Abgriff geht an den AD-Wandler Eingang 15. Der AD Wert (8 Bit = 0….1023 wird in einen Radius 0….1000 / 10, also 0.0… 100,0 Meter umgerechnet und legt damit den Kreis fest der nicht verlassen werden darf.

In diesem Artikel habe ich Peilung und Entfernung zwischen 2 Positionen schon berechnet. Die Funktionen werden hier genau so verwendet.

Wird die Alarmdistanz überschritten aktiviert das den Alarm. Hier geht nur die LED an PIN 34 an. Sie ist über einen Vorwiderstand von 180 Ohm gegen Masse geschaltet.

Der Rest ist bekannt aus den vorherigen NMEA2000 Beiträgen.

Hier der Aufbau

NMEA2000 Ankeralarm

Und das fertige Programm

// NMEA2000 Ankeralarm.
// Eine Anker Start-Position wird zum Start festgelegt.
// Dann wird die Entfernung der aktuellen Position zur 
// Start-Position berechnet und ausgegeben.
//
// Matthias Busse 3.12.2016 Version 1.0

#include <SPI.h>
#include <Arduino.h>
#include <NMEA2000_CAN.h>
#include <N2kMessages.h>
#include <N2kMessagesEnumToStr.h>

#define N2k_CAN_INT_PIN 21
#define MCP_CAN_RX_BUFFER_SIZE 100

double lat0, lat1, lon0, lon1;
double distanz, peilung, alarmdistanz;
int startposition=1;
int LED=34; // LED an PIN 34

typedef struct {
  unsigned long PGN;
  void (*Handler)(const tN2kMsg &N2kMsg); 
} tNMEA2000Handler;

void GNSS(const tN2kMsg &N2kMsg);

tNMEA2000Handler NMEA2000Handlers[]={
  {129029L,&GNSS},
  {0,0}
};

template<typename T> void PrintLabelValWithConversionCheckUnDef(const char* label, T val, double (*ConvFunc)(double val)=0, bool AddLf=false ) {
  Serial.print(label);
  if (!N2kIsNA(val)) {
    if (ConvFunc) { Serial.print(ConvFunc(val)); } else { Serial.print(val); }
  } 
  else Serial.print("not available");
  if (AddLf) Serial.println();
}

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(31, OUTPUT); // Alarmpoti +5V
  digitalWrite(31,1); // +5V ausgeben
  Serial.begin(115200);
  NMEA2000.EnableForward(false);
  NMEA2000.SetMsgHandler(HandleNMEA2000Msg);
  NMEA2000.Open();
}

void loop() {
  NMEA2000.ParseMessages();
}

void GNSS(const tN2kMsg &N2kMsg) {
// PGN 129029 GNSS Position Data
// kommt typisch 1x pro Sekunde
    unsigned char SID;
    uint16_t DaysSince1970;
    double SecondsSinceMidnight; 
    double Latitude;
    double Longitude;
    double Altitude; 
    tN2kGNSStype GNSStype;
    tN2kGNSSmethod GNSSmethod;
    unsigned char nSatellites;
    double HDOP;
    double PDOP;
    double GeoidalSeparation;
    unsigned char nReferenceStations;
    tN2kGNSStype ReferenceStationType;
    uint16_t ReferenceSationID;
    double AgeOfCorrection;

    if (ParseN2kGNSS(N2kMsg,SID,DaysSince1970,SecondsSinceMidnight,
                  Latitude,Longitude,Altitude,GNSStype,GNSSmethod,
                  nSatellites,HDOP,PDOP,GeoidalSeparation,
                  nReferenceStations,ReferenceStationType,ReferenceSationID,
                  AgeOfCorrection) ) {
      if(startposition==1) { // Beim einschalten die Start Position 0 einmalig speichern
        lon0=Longitude;
        lat0=Latitude;
        startposition=0;
      }
      lon1=Longitude;
      lat1=Latitude;
      alarmdistanz=map(analogRead(15), 0, 1023, 0, 1000)/10.0; // Poti Analogwert auf Alarmdistanz 0-100m mappen
      Serial.print("Alarm bei: ");
      Serial.print(alarmdistanz,1);
      Serial.print(" m,  aktuelle Distanz: ");
      distanz=CalcDistanz(lon0, lat0, lon1, lat1); // Distanz zur 0 Position berechnen
      Serial.print(distanz,1);
      Serial.print(" m, Peilung: ");
      peilung=CalcPeilung(lon0, lat0, lon1, lat1); // Peilung zur 0 Position berechnen
      Serial.print(peilung,0);
      Serial.print(" Grad");
      if(distanz > alarmdistanz) { // Alarmdistanz prüfen
        digitalWrite(LED,1); // Alarm ein
        Serial.print(" Alarm !");
      }
      else { 
        digitalWrite(LED,0); // Alarm aus
      }
      Serial.println();
    } 
    else {
      Serial.print("Fehler PGN: ");
      Serial.println(N2kMsg.PGN);
    }
}

void HandleNMEA2000Msg(const tN2kMsg &N2kMsg) {
  int iHandler;
  
  for (iHandler=0; NMEA2000Handlers[iHandler].PGN!=0 && !(N2kMsg.PGN==NMEA2000Handlers[iHandler].PGN); iHandler++);
  if (NMEA2000Handlers[iHandler].PGN!=0) {
    NMEA2000Handlers[iHandler].Handler(N2kMsg); 
  }
}

double CalcDistanz(double breite1, double laenge1, double breite2, double laenge2) {
// Berechnet die Distance vom breite1/laenge1 zu breite2/laenge2 in Grad
// Rückgabe Distance in m
// Matthias Busse 25.11.2014 Version 1.0
double dlaenge, dbreite, a, c;
double dist = 0.0;

  dlaenge = gtor(laenge2 - laenge1);
  dbreite = gtor(breite2 - breite1);
  a = pow(sin(dbreite/2),2) + cos(gtor(breite1)) * cos(gtor(breite2)) * pow(sin(dlaenge/2),2);
  c = 2.0 * atan2(sqrt(a), sqrt(1-a));
  dist = 6371001 * c;  //Radius der Erde (6378140 m am Äquator / 6371001 m im Mittel)
  return dist;
}

double CalcPeilung(double breite1, double laenge1, double breite2, double laenge2) {
// Berechnet die Peilung von breite1/laenge1 zu breite2/laenge2 in Grad
// Ausgabe Peilung in Grad
// Matthias Busse 25.11.2014 Version 1.0
breite1 = gtor(breite1);
laenge1 = gtor(laenge1);
breite2 = gtor(breite2);
laenge2 = gtor(laenge2);  // Winkel berechnen
  double peil = atan2(sin(laenge2-laenge1)*cos(breite2), (cos(breite1)*sin(breite2))-(sin(breite1)*cos(breite2)*cos(laenge2-laenge1)));
  peil = rtog(peil); // in Grad umrechnen
  peil = fmod((peil + 360.0), 360); // mod macht -90 zu 270
  return peil;
}

double gtor(double fgrad){ // Grad in Rad umwandeln
  return(fgrad * PI / 180);
}

double rtog(double frad){ // Rad in Grad umwandeln
  return(frad * 180.0 / PI);
}

 

Hier die Konsolen Ausgabe:

Alarm bei: 17.6 m,  aktuelle Distanz: 16.6 m, Peilung: 213 Grad
Alarm bei: 17.6 m,  aktuelle Distanz: 17.3 m, Peilung: 214 Grad
Alarm bei: 17.6 m,  aktuelle Distanz: 18.2 m, Peilung: 212 Grad Alarm !
Alarm bei: 17.6 m,  aktuelle Distanz: 18.6 m, Peilung: 212 Grad Alarm !

von Matthias Busse

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.