NMEA 0183 is a communication spec. between marine electronics, in use from 1992-ish to present. It has been mostly replaced by NMEA2000, but 0183 is still extremely useful in 2024 and worth learning.
Data transmissions in 0183 are called “messages”. They are up to 79 ascii characters in length, and are produced by “talkers”. A talker might be something like a depth or wind speed transducer. These are digital (serial) signals, just a stream of periodic high/low voltages sent over 1 or 2 wires in a defined pattern which can be decoded based on rules. Transmissions follow the RS-422 electrical standard, although they’re also compatible with the RS-232 electrical standard. This means the signal voltage could reach as high as 15V.
![](https://xengineering.net/wp-content/uploads/2024/02/tlyo.png)
Devices that accept 0183 messages are called “listeners”. It’s common for a device to be both a talker and a listener, for example, a Chart Plotter might accept depth transducer signals (for display) as a listener, but also send GPS data as a talker to some other device.
![](https://xengineering.net/wp-content/uploads/2024/02/MOShit.png)
Messages always begin (“$” or “!”) and end the same way, leaving 8 bits for useful data. Some of that data may include a Checksum, which is a field calculate-able from simple math/logic on the message’s useful data bits. The talker can calculate and send a Checksum with each message, and it can be re-calculated by the listener after message reception. If the Checksum values are not equal, then one or more of the data bits was transmitted incorrectly, and the message should be ignored. More info on calculating checksums here.
The digital inputs on an Arduino are limited to 5V maximum (and 3.3V for the ESP32), so there is some danger in connecting NMEA0183 wires directly to them, as they could be up to 15V. Always verify with an oscilloscope, but I’ve had good luck with all the signals I’ve checked being 5V, and just using the Arduino. If >5V, there are a number of solutions on google, but I have not personally explored them yet. I wonder about a simple voltage divider…
A working code to spoof a NMEA0183 signal
#include "DHT.h"#define Type DHT11#include <Wire.h>#include <LiquidCrystal_I2C.h>LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 16 chars and 2 line displayint sensePin = 2;DHT HT(sensePin, Type);int humidity;int tempC;float tempF;int setTime = 500;int dt = 10000;float dewPointC;float dewPointF;
void setup() {// put your setup code here, to run once:Serial.begin(38400);HT.begin();delay(setTime);lcd.init(); //initialize the lcdlcd.backlight(); //open the backlight}
void loop() {humidity = HT.readHumidity();tempC = HT.readTemperature();tempF = HT.readTemperature(true);dewPointC = (tempC) - ((100 - humidity) / 5);dewPointF = (dewPointC * 9 / 5) + 32;
String buffer = "$PXDR,P,96276.0,P,0,C,";buffer += String(tempC);buffer += ",C,1,H,";buffer += String(humidity);buffer += ",P,2,C,";buffer += String(dewPointC);buffer += ",C,3,1.1*";
byte checksum = 0;// Skip the '$' and '*'for (int i = 1; i < buffer.length() - 1; i++)checksum ^= buffer[i];
Serial.print(buffer);if (checksum < 0x10)Serial.print('0'); // Leading zeroSerial.println(checksum, HEX);
//Serial.print("$PXDR,P,96276.0,P,0,C,");//Serial.print(tempC);//Serial.print(",C,1,H,");//Serial.print(humidity);//Serial.print(",P,2,C,");//Serial.print(dewPointC);//Serial.println(",C,3,1.1*31");
lcd.setCursor(1, 0);lcd.print("Humidity: ");lcd.print(humidity);lcd.print("%");lcd.setCursor(0, 1);lcd.print("Dp:");lcd.print (dewPointF);lcd.print("f");lcd.print(" T:");lcd.print(tempF);lcd.print("f");
delay(dt);}