Go Down

Topic: Convert Serial data from a heading sensor to NMEA 0183 (Read 146 times) previous topic - next topic

Oceancoder

Hi,

I have successfully produced a device that displays compass data onto a seven segment display from a Pololu AltiMU v4 sensor.
I now would like to send the data as a NMEA 0183 HDM output to OpenCPN on a windows machine.
The data is currently represented by an integer inside a variable.
What code do I need to add to make this recognisable as NMEA 0183 HDM data to OpenCPN?

Thanks

Riva

You need to determine what the HDM data output should look like (hint).
Work out how to calculate the CRC (hint).
Combine the above to send the sentence over the serial connection then needed/requested.
Don't PM me for help as I will ignore it.

Oceancoder

Thanks Riva,

What I understand is that I need to get the Arduino to calculate the NMEA checksum, is this correct?
Also, can I have some pointers on what code I should use? I'm relatively new to coding, especially with c++.
And should OpenCPN recognise the NMEA code from the serial monitor if I just print it out? It has the ability to find the COM port as a NMEA 0183 connection

Riva

What I understand is that I need to get the Arduino to calculate the NMEA checksum, is this correct?
Yes. Maybe, I just read the Wiki in mode detail and it says " the checksum is optional for most data sentences, but is compulsory for RMA, RMB, and RMC"
Also, can I have some pointers on what code I should use? I'm relatively new to coding, especially with c++.
The CRC hint link in my previous post points to a Wiki article that includes a C example for CRC calculation. It will not directly work on the Arduino but the core part should convert to C++ easily.
And should OpenCPN recognise the NMEA code from the serial monitor if I just print it out? It has the ability to find the COM port as a NMEA 0183 connection
I know nothing about OpenCPN but if it allows you to select a comm port and the expected protocol then it sounds like it will work. When you select the comm port in OpenCPN do you get to choose the baud rate or does it expect either 4800/38400 baud?
Post the code (in code tags) you already have for displaying the heading on the LCD and maybe we can work the NMEA code into this.
Don't PM me for help as I will ignore it.

Oceancoder

Thanks Riva,

Here is the code so far:

Code: [Select]
#include <LedControl.h>
#include <LedPrint.h>
#include <LedPrintJustifiable.h>

#include <Wire.h>
#include <LSM303.h>


LedPrintJustifiable myLed = LedPrintJustifiable /*This is the part for the 7 segment display that is displaying the data */
  (
    11,
    13,
    10,
    8,
    1
   );
   
LSM303 compass;

void setup() {

  myLed.setIntensity(5);
  myLed.justify(0);
 
  Serial.begin(9600);
  Wire.begin();
  compass.init();
  compass.enableDefault();
 
  /*
  Calibration values; the default values of +/-32767 for each axis
  lead to an assumed magnetometer bias of 0. Use the Calibrate example
  program to determine appropriate values for your particular unit.
  */
  compass.m_min = (LSM303::vector<int16_t>){-32767, -32767, -32767};
  compass.m_max = (LSM303::vector<int16_t>){+32767, +32767, +32767};
}

void loop() {
  compass.read();
 
  /*
  When given no arguments, the heading() function returns the angular
  difference in the horizontal plane between a default vector and
  north, in degrees.
 
  The default vector is chosen by the library to point along the
  surface of the PCB, in the direction of the top of the text on the
  silkscreen. This is the +X axis on the Pololu LSM303D carrier and
  the -Y axis on the Pololu LSM303DLHC, LSM303DLM, and LSM303DLH
  carriers.
 
  To use a different vector as a reference, use the version of heading()
  that takes a vector argument; for example, use
 
    compass.heading((LSM303::vector<int>){0, 0, 1});
 
  to use the +Z axis as a reference.
  */
  int heading = compass.heading((LSM303::vector<int>){0,1,0}); /* This is modified because of the placement of the sensor inside its box. */
  Serial.println(heading);
  myLed.println(heading);
  delay(100);
}

This is mostly created from internet sources and GitHub library examples. It works pretty well and is accurate enough to be used for navigation. I am currently also printing the angle, in degrees, to the Serial Monitor. Any help would be appreciated.

Riva

Try the below code. I don't have suitable hardware to compile & test it but hopefully it will work.
Note I have altered the serial baud rate to 4800 (from 9600) so it matches NMEA specifications.


Code: [Select]
#include <LedControl.h>
#include <LedPrint.h>
#include <LedPrintJustifiable.h>

#include <Wire.h>
#include <LSM303.h>


LedPrintJustifiable myLed = LedPrintJustifiable /*This is the part for the 7 segment display that is displaying the data */
  (
    11,
    13,
    10,
    8,
    1
   );
   
LSM303 compass;

char mystring[25];

uint8_t checksum(char *s)
{
  uint8_t c = 0;

  while (*s)
    c ^= *s++;

  return c;
}

void setup() {

  myLed.setIntensity(5);
  myLed.justify(0);
 
  Serial.begin(4800);
  Wire.begin();
  compass.init();
  compass.enableDefault();
 
  /*
  Calibration values; the default values of +/-32767 for each axis
  lead to an assumed magnetometer bias of 0. Use the Calibrate example
  program to determine appropriate values for your particular unit.
  */
  compass.m_min = (LSM303::vector<int16_t>){-32767, -32767, -32767};
  compass.m_max = (LSM303::vector<int16_t>){+32767, +32767, +32767};
}

void loop() {
  compass.read();
 
  /*
  When given no arguments, the heading() function returns the angular
  difference in the horizontal plane between a default vector and
  north, in degrees.
 
  The default vector is chosen by the library to point along the
  surface of the PCB, in the direction of the top of the text on the
  silkscreen. This is the +X axis on the Pololu LSM303D carrier and
  the -Y axis on the Pololu LSM303DLHC, LSM303DLM, and LSM303DLH
  carriers.
 
  To use a different vector as a reference, use the version of heading()
  that takes a vector argument; for example, use
 
    compass.heading((LSM303::vector<int>){0, 0, 1});
 
  to use the +Z axis as a reference.
  */
  int heading = compass.heading((LSM303::vector<int>){0,1,0}); /* This is modified because of the placement of the sensor inside its box. */
  myLed.println(heading);
 
 
  snprintf(mystring, sizeof(mystring), "$--HDM,%d.0,M", heading);
  uint8_t crc = checksum(mystring + 1);
  Serial.print(mystring);
  Serial.print("*");
  if(crc < 16) Serial.print("0");
  Serial.println(crc, HEX);
  delay(100);
}
Don't PM me for help as I will ignore it.

Oceancoder

Hi Riva,

I have tested the code on the hardware and it works! OpenCPN recognises the data as a NMEA 0183 HDM signal and displays the direction. The problem is that the data only refreshes if I click anywhere on the OpenCPN window. I don't know why this happens, and it is quite a problem. I tried altering the refresh speed of the loop code but to no avail. It seems as if OpenCPN is just not refreshing its data. If you have any idea why this may be happening, please help.

Thanks

Riva

I have no idea really, the only thing I can suggest is to give your NMEA data a talker ID.
Looking at this the most appropriate seems to be HC (Heading Compass) so change the snprintf line to below.

Code: [Select]

snprintf(mystring, sizeof(mystring), "$HCHDM,%d.0,M", heading);


Could OpenCPN only update if the heading changes?
Don't PM me for help as I will ignore it.

Oceancoder

Hi Riva,

After I sent that reply, I found out that OpenCPN was receiving the NMEA data every 10 milliseconds and was verifying them, however the heading did not update even if the box was rotated. It is possible that it only refreshes whilst moving, although I haven't tested this as I didn't have time to set up a GPS with it and move somewhere. Will do more testing, and I will replace the code with that sentence just to see if it works.

Thanks

Oceancoder

Hey Riva,

Thanks for your help. I tested the device with a GPS and it worked perfectly. I think it has something to do with OpenCPN thinking that is not necessary to update the heading while there is no GPS data as it assumes you are not moving. Anyway, thanks again for all the help.

Oceancoder

Riva

I have changed the code a bit so it only sends data when the compass heading changes, just in case the OpenCPN software is being swamped with to much data.

Code: [Select]
#include <LedControl.h>
#include <LedPrint.h>
#include <LedPrintJustifiable.h>

#include <Wire.h>
#include <LSM303.h>


LedPrintJustifiable myLed = LedPrintJustifiable /*This is the part for the 7 segment display that is displaying the data */
  (
    11,
    13,
    10,
    8,
    1
   );
   
LSM303 compass;

char mystring[25];
int lastHeading = -1;

uint8_t checksum(char *s)
{
  uint8_t c = 0;

  while (*s)
    c ^= *s++;

  return c;
}

void setup() {

  myLed.setIntensity(5);
  myLed.justify(0);
 
  Serial.begin(4800);
  Wire.begin();
  compass.init();
  compass.enableDefault();
 
  /*
  Calibration values; the default values of +/-32767 for each axis
  lead to an assumed magnetometer bias of 0. Use the Calibrate example
  program to determine appropriate values for your particular unit.
  */
  compass.m_min = (LSM303::vector<int16_t>){-32767, -32767, -32767};
  compass.m_max = (LSM303::vector<int16_t>){+32767, +32767, +32767};
}

void loop() {
  compass.read();
 
  /*
  When given no arguments, the heading() function returns the angular
  difference in the horizontal plane between a default vector and
  north, in degrees.
 
  The default vector is chosen by the library to point along the
  surface of the PCB, in the direction of the top of the text on the
  silkscreen. This is the +X axis on the Pololu LSM303D carrier and
  the -Y axis on the Pololu LSM303DLHC, LSM303DLM, and LSM303DLH
  carriers.
 
  To use a different vector as a reference, use the version of heading()
  that takes a vector argument; for example, use
 
    compass.heading((LSM303::vector<int>){0, 0, 1});
 
  to use the +Z axis as a reference.
  */
  int heading = compass.heading((LSM303::vector<int>){0,1,0}); /* This is modified because of the placement of the sensor inside its box. */
  myLed.println(heading);
 
  if(heading != lastHeading)
  {
    lastHeading = heading;
 
    snprintf(mystring, sizeof(mystring), "$--HDM,%d.0,M", heading);
    Serial.print(mystring);
    Serial.print("*");
    uint8_t crc = checksum(mystring + 1);
    if(crc < 16) Serial.print("0");
    Serial.println(crc, HEX);
  }
  delay(100);
}
Don't PM me for help as I will ignore it.

Go Up