c++serial-portnmea

Unreadable input from serial GNSS device - but not with PuTTY


I am writing a C++ program to get and parse GNSS data from a device connected via serial port. After establishing the connection, all I get is some jibberish and not the expected NMEA sentences. Has anyone an idea where my mistake is? When I use PuTTY to output the data there are parts jibberish and parts good NMEA sentences.

My main code:

#include <iostream>
#include "Serial.h"

int main()
{
    Serial serial;
    unsigned char buffer[256];
    std::string port = "COM19";
    const char *portaddress=port.data();
    std::cout<<serial.Open(portaddress,9600);
    //unsigned char *buffer;
    int read;
    int readlast;
    read=serial.Read(buffer, 255);
    unsigned char line[256];//=new int[];
    int counter=0;
    while(1)
    {
        while(read==0)
        {
            read=serial.Read(buffer, 255);
        }
        counter=0;
        line[counter]=read;
        counter++;
        while(read!=0)
        {
             //std::cout<<read;
             readlast=read;
             read=serial.Read(buffer, 255);
             line[counter]=read;
             counter++;
        }
        for(int i=0;i<sizeof(line)/sizeof(*line);i++)
        {
            std::cout<<line[i];
        }
        break;

    }

    return 0;
}

serial.Read():

int Serial::Read(unsigned char* buffer, int length) {
    WaitForSingleObject(m_Mutex, INFINITE);
    DWORD bytesRead = 0;
    ReadFile(m_SerialHandle, buffer, length, NULL, &m_Overlapped);
    GetOverlappedResult(m_SerialHandle, &m_Overlapped, &bytesRead, 1);
    ReleaseMutex(m_Mutex);

    return (int)bytesRead;
}

The output:

0                  ­<@ `▀F ÉòG $òG Ç@     $ ¶â.X■i >uñv       Iuñvÿ1V    fm~wç }w    $       ( ¶â.ÿ■i >uñv       Iuñv├ÿ1V    fm~wç }w    $   ä■i        þÆAö■i «kªv, Ç@ ÓêA     ¼■i àtªv©?½vý■i óiª   ssÑvjsÑvÀÿ1VÇ@ Ç@     ÓêA

When I leave the break out, it does not get any better. The specification of my GNSS board says the output should be in ASCII with protocol NMEA 0813 v4.0.

Edit: This is the output when I use HTerm.exe. There are many garbage bytes, but in between you can find NMEA sentences. Settings are 1 Stopbit, 8 Databits, no Parity, 9600 Baud (as I use in the u-center software from u-blox as well and there it works like a charm).

?b<5><1><2><\0><6><\0><14>7?b<5><1><2><\0><6><\0>
<14>7?b<5><1><2><\0><6><1><15>8?b<5><1><2><\0><6><1>
<15>8?b<5><1><2><\0><6><1><15>8?b<5><1><2><\0><6><1>
<15>8?b<5><1><2><\0><6><1><15>8?b<5><1><2><\0><6><1>
<15>8?b<5><1><2><\0><6><1><15>8?b<5><1><2><\0><6><1>
<15>8?b<5><1><2><\0><6><1><15>8?b<2><20>,<\0><1><\0>
<\0><\0><4>)<\0><\0><4>)<\0><\0><4>)<\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0>??????<\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0>?8
$GNTXT,01,01,02,u-blox AG - www.u-blox.com*4E<\r><\n>
$GNTXT,01,01,02,HW UBX-M8030 00080000*60<\r><\n>
$GNTXT,01,01,02,EXT CORE 3.01 (b8bc67)*66<\r><\n>
$GNTXT,01,01,02,ROM BASE 2.01 (75331)*19<\r><\n>
$GNTXT,01,01,02,FWVER=HPG 1.11*5E<\r><\n>
$GNTXT,01,01,02,PROTVER=20.01*1B<\r><\n>
$GNTXT,01,01,02,MOD=NEO-M8P-2*7B<\r><\n>
$GNTXT,01,01,02,FIS=0xEF4015 (100111)*58<\r><\n>
$GNTXT,01,01,02,GPS;GLO;BDS*06<\r><\n>
$GNTXT,01,01,02,GNSS OTP=GPS;GLO*37<\r><\n>
$GNTXT,01,01,02,LLC=FFFFFFFF-FFFFFFED-FFFFFFFF-FFFFF79E-FFFFFF69*2E<\r><\n>
$GNTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*3E<\r><\n>
$GNTXT,01,01,02,ANTSTATUS=OK*25<\r><\n>
$GNTXT,01,01,02,PF=300*4B<\r><\n>?b
<\n><6>x<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<11><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0>?<2>?b<\n><\b><28><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\n>
<\0><6><\0><\0><\0><20><\0><6><\0><\0><11><20><\0>
<\0>w??b<\n><7><24><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0>)E?b<\n><2>x<\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0>?<17><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><1><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0>?<\0><\0><\0>?<\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><1><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0>???b<\n><9><<\0><\0>?<1><\0>
<\0><\0><\0><\0><\0>@<1><\0>??<1><\0>d<\0>?<3><2><1>
<\0>???<1><\0><\n><11><\f><\r><14><15><1><\0><2>
<3>?<16>?<18>D65E?^<\0><\0><\0><\0>??<1><\0><\0><\0>
<\0><\0>%??b<\n><11><28><\0>/?w?f<\0><\0><\0>????????????<\0>
<\0><\0><\0><\0><\0><\0><\0>]Y?b<2><20>,<\0><1>
<\0><\0><\0>?*<\0><\0>?*<\0><\0>?*<\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0>??????<\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0>???b<2><21><16>
<\0><\0><\0><\0><\0><\0><\0>$@<\0><\0><17><\0><\0>
<1>?+M<29>?b<1><6>4<\0>?*<\0><\0><\0><\0><\0><\0><\0>
<\0><\0>@?C<4>&<\0><\0><\0><\0><\0><\0><\0><\0>???&<\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>?<7><\0>
<\0><15>'<2><\0>?xq<3><18>A?b<1><7>\<\0>?*<\0><\0>?<7><\n>
<18><\0><\0><11>?????<\0><\0><\0><\0><\0><\0><4><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>????????<\0>
v??<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0> N<\0><\0>??<18><1><15>'<\0>
<\0>?x!7<\0><\0><\0><\0><\0><\0><\0><\0>(??b<1>0<\b>
<\0>?*<\0><\0><\0><4><\0><\0>_^?b<1>4<\b><\0>?*<\0>
<\0><1><\0><\0><\0>`??b<1>5<\b><\0>?*<\0><\0><1><\0>
<\0><\0>a??b<1><3><16><\0>?*<\0><\0><\0>@<\0><\b><\0>
<\0><\0><\0><\f>+<\0><\0>?<28>?b<1><1><20><\0>?*<\0>
<\0>?C<4>&<\0><\0><\0><\0><\0><\0><\0><\0>???&???b<1>
<2><28><\0>?*<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0>????????<\0>v??i??b<1><4><18><\0>?*<\0>
<\0><15>'<15>'<15>'<15>'<15>'<15>'<15>'???b<1><17><20>
<\0>?*<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0>?<7><\0><\0><31>*?b<1><18>$<\0>?*<\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0>?<7><\0><\0>??<18>
<1>k??b<1> <16><\0>?*<\0><\0><\0><\0><\0><\0><\0><\0>
<17><\0>????`??b<1>#<20><\0>?*<\0><\0><11><\0><\0><\0>
<\0><\0><\0><\0>k<5><5><\0>???????b<1>$<20><\0>?*<\0>
<\0><11><\0><\0><\0><\0><\0><\0><\0>K<7><3><\0>???????b<1>!<20>
<\0>?*<\0><\0>????<\0><\0><\0><\0>?<7><\n><18><\0>
<\0><11>?Q??b<1>&<24><\0>?*<\0><\0><\0><\0><\0><\0><7>
<17><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0>y<3>?b<1>"<20><\0>?*<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>????|69<\0>@F?b<1><9><20><\0><\0><\0><\0><\0>?*<\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0>@??b<1>;(<\0><\0><\0><\0><\0>?*<\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0>?C?b<1><(<\0><\0><\0><\0><\0>?*<\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0><\0>
<\0>?n?b<1>9<\b><\0>?*<\0><\0><\0><\0><\0><\0>d?
$GNDTM,W84,,0.0,N,0.0,E,0.0,W84*71<\r><\n>
$GNRMC,,V,,,,,,,,,,N*4D<\r><\n>
$GNVTG,,,,,,,,,N*2E<\r><\n>
$GNGNS,,,,,,NN,00,99.99,,,,*7D<\r><\n>
$GNGGA,,,,,,0,00,99.99,,,,,,*56<\r><\n>
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E<\r><\n>
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E<\r><\n>
$GPGSV,1,1,00*79<\r><\n>$GLGSV,1,1,00*65<\r><\n>
$GNGLL,,,,,,V,N*7A<\r><\n>
$GNGRS,,1,,,,,,,,,,,,*7E<\r><\n>
$GNGRS,,1,,,,,,,,,,,,*7E<\r><\n>
$GNGST,,0.0000,,,,3750000,3750000,3750000*66<\r><\n>
$GNZDA,,,,,00,00*56<\r><\n>
$GNGBS,,,,,,,,*5F<\r><\n>
$GNVLW,,,,,,,,*44<\r><\n>
$PUBX,00,000011.00,0000.00000,N,00000.00000,E,0.000,NF,5303301,3750000,0.000,0.00,0.000,,99.99,99.99,99.99,0,0,0*2A<\r><\n>
$PUBX,03,00*1C<\r><\n>
$PUBX,04,000011.00,181015,11.00,1867,17D,0,0.000,21*6C<\r><\n>?b<2> `<2>?

Maybe the settings are wrong? Though I tried to add:

serialParams.DCBlength=sizeof(serialParams);
serialParams.fParity=FALSE;
serialParams.fDtrControl=0;
serialParams.fRtsControl=0;
serialParams.fOutX=TRUE;
serialParams.fInX=TRUE;

serialParams.fBinary=FALSE;
serialParams.fOutxCtsFlow=FALSE;
serialParams.fOutxDsrFlow=FALSE;
serialParams.fDsrSensitivity=FALSE;
serialParams.fErrorChar=FALSE;
serialParams.fNull=FALSE;
serialParams.fAbortOnError=FALSE; 

to Serial::Open() and it still made the same output. Maybe there is a problem with the buffer? I still don't really grasp the concept of buffers and how long they should be, so it is quite possible I made a mistake there.

Edit 2:

As wished (and forgotten) Serial::Open()

int Serial::Open(const char* port, int baudrate) {

    // if connected: close current connection before opening a new one
    if(m_SerialHandle != INVALID_HANDLE_VALUE) {
        Close();
    }

    TCHAR vibroTacPort[15];
    sprintf(vibroTacPort, "\\\\.\\%s", port);

    // Open serial port
    m_SerialHandle = CreateFile(vibroTacPort, // port // "\\\\.\\COM13"
                                GENERIC_READ | GENERIC_WRITE,
                                0,//FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING | FILE_ATTRIBUTE_TEMPORARY, // | FILE_ATTRIBUTE_NORMAL,
                                NULL);

    // FILE_FLAG_OVERLAPPED is necessary for event-driven communication, Handle hast to be flagged for overlapped IO, otherwise Serial::Request() blocks if no event occurs (e.g. if the VibroTac device does not respond to the request)
    // FILE_ATTRIBUTE_TEMPORARY, FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING increase the system performance
    // Note: FILE_FLAG_WRITE_THROUGH is not be supported by all hard disks
    // (http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858%28v=vs.85%29.aspx#caching_behavior)


    if(m_SerialHandle == INVALID_HANDLE_VALUE)
    {
        DWORD errorCode = GetLastError();
        //  Handle the error.
        switch(errorCode) {
        case ERROR_FILE_NOT_FOUND:
        case ERROR_ACCESS_DENIED:
            //  121     The semaphore timeout period has expired.
            //  1167    The device is not connected.
            // fall through and do nothing for now
            ;
        }
        return (errorCode);
    }

    // Do some basic settings
    DCB serialParams = { 0 };
    serialParams.DCBlength = sizeof(serialParams);

    if (!GetCommState(m_SerialHandle, &serialParams)) {
        Close();
        return ERROR_OPEN_FAILED;
    };
    serialParams.BaudRate = CBR_9600;//baudrate;
    serialParams.ByteSize = 8;
    serialParams.StopBits = ONESTOPBIT;
    serialParams.Parity = NOPARITY;

    /*serialParams.DCBlength=sizeof(serialParams);
    serialParams.fParity=FALSE;
    serialParams.fDtrControl=0;
    serialParams.fRtsControl=0;

    serialParams.fOutX=TRUE;
    serialParams.fInX=TRUE;

    serialParams.fBinary=FALSE;
    serialParams.fOutxCtsFlow=FALSE;
    serialParams.fOutxDsrFlow=FALSE;
    serialParams.fDsrSensitivity=FALSE;
    serialParams.fErrorChar=FALSE;
    serialParams.fNull=FALSE;
    serialParams.fAbortOnError=FALSE;*/
    if (!SetCommState(m_SerialHandle, &serialParams)) {
        Close();
        return ERROR_OPEN_FAILED;
    }

    // Set timeouts

    COMMTIMEOUTS timeout = { 1 };
    timeout.ReadIntervalTimeout = 100;  //100   // 0 = "not used", the maximum time allowed to elapse before the arrival of the next byte on the communications line, in milliseconds.
    timeout.ReadTotalTimeoutConstant = 0;   // A constant used to calculate the total time-out period for read operations, in milliseconds. For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier member and the requested number of bytes.
    timeout.ReadTotalTimeoutMultiplier = 80;//20    // The multiplier used to calculate the total time-out period for read operations, in milliseconds. For each read operation, this value is multiplied by the requested number of bytes to be read.
    timeout.WriteTotalTimeoutConstant = 1;
    timeout.WriteTotalTimeoutMultiplier = 1;

    if (!SetCommTimeouts(m_SerialHandle, &timeout)) {
        Close();
        return ERROR_OPEN_FAILED;
    }

    // Set event mask
    // Events:
    // EV_RXCHAR    0x0001  A character was received and placed in the input buffer.
    // EV_ERR       0x0080  A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
    DWORD dwEvtMask = EV_RXCHAR | EV_ERR;
    if(!SetCommMask(m_SerialHandle, dwEvtMask)) {
        Close();
        return ERROR_OPEN_FAILED;
    };

    // initialize m_Overlapped with 0
    memset(&m_Overlapped, 0, sizeof(m_Overlapped));

    // Create an event object for use by WaitCommEvent.
    m_Overlapped.hEvent = CreateEvent(
        NULL,   // default security attributes
        TRUE,   // manual-reset event
        FALSE,  // not signaled
        NULL    // no name
        );


    m_Overlapped.Internal = 0;
    m_Overlapped.InternalHigh = 0;
    m_Overlapped.Offset = 0;
    m_Overlapped.OffsetHigh = 0;

    return 0;
}

Edit 3:

I figured maybe it would be best to just read a single byte at a time until I reach a $ char and afterwards a \n char to determine which parts are useful for me. Could someone explain how I can read a single byte using the handler created in Serial::Open()? If I use the former way and limit the length aka "NumberOfBytesToRead", how can I test if I reached a relevant part?


Solution

  • Well, after a lot of trial and error as well as hours of a smoking head I found an answer that works at least in my case:

    I changed main.cpp like this:

    #include <iostream>
    #include "Serial.h"
    #include "GPSInfo.h"
    #include "NMEAParser.h"
    
    int main()
    {
        Serial serial;
        unsigned char buffer[256];
        std::string port = "COM19";
        const char *portaddress=port.data();
        serial.Open(portaddress,9600);
    
        std::string read;
        read=serial.Read(buffer);
        while(1)
        {
             read=serial.Read(buffer);
             std::cout<<read;
        }
    
        return 0;
    }
    

    and Serial::Read() like this:

    std::string Serial::Read(unsigned char* buffer) {
        WaitForSingleObject(m_Mutex, INFINITE);
        DWORD bytesRead = 0;
        DWORD nNumberOfBytesToRead=1; // new
        char a=0;
        char *ptr=&a;
        char last=0;
        std::string output="";
        ReadFile(m_SerialHandle, ptr, nNumberOfBytesToRead, NULL, &m_Overlapped);
        GetOverlappedResult(m_SerialHandle, &m_Overlapped, &bytesRead, 1);
        if(a=='$')
        {
            last=a;
            ReadFile(m_SerialHandle, ptr, nNumberOfBytesToRead, NULL, &m_Overlapped);
            if(a=='G'||a=='P'||a=='S')
            {
    
                output+=last;
                output+=a;
                while (a!='\n')
                {
    
                    ReadFile(m_SerialHandle, ptr, nNumberOfBytesToRead, NULL, &m_Overlapped);
                    output+=a;
    
                }
            }
        }
        ReleaseMutex(m_Mutex);
        return output;
    }
    

    Now I have the following output:

    $GNDTM,W84,,0.0,N,0.0,E,0.0,W84*71
    $GNRMC,095639.00,A,4804.88405,N,01116.55021,E,0.415,,181016,,,A*6C
    $GNVTG,,T,,M,0.415,N,0.769,K,A*35
    $GPGSV,4,1,14,01,12,263,,07,00,284,,08,57,301,18,10,66,091,24*7B
    $GPGSV,4,2,14,11,23,280,16,14,00,152,,15,04,026,,16,26,194,*7E
    $GPGSV,4,3,14,18,37,056,13,21,11,079,,26,03,179,18,27,78,145,18*78
    $GPGSV,4,4,14,30,03,310,,32,16,131,*7B
    $GLGSV,3,1,09,70,43,047,15,71,57,137,33,72,17,181,17,76,09,244,*6F
    $GLGSV,3,2,09,77,22,293,,78,12,347,,85,29,101,25,86,71,030,*63
    $GLGSV,3,3,09,87,29,306,13*5F
    $GNGLL,4804.88405,N,01116.55021,E,095639.00,A,A*7A
    $GNGRS,095639.00,1,-2.7,7.0,,,,,,,,,,*7F
    $GNTXT,01,01,00,txbuf alloc*61
    $GNTXT,01,01,02,u-blox AG - www.u-blox.com*4E
    $GNTXT,01,01,02,HW UBX-M8030 00080000*60
    $GNTXT,01,01,02,EXT CORE 3.01 (b8bc67)*66
    $GNTXT,01,01,02,ROM BASE 2.01 (75331)*19
    $GNTXT,01,01,02,FWVER=HPG 1.11*5E
    $GNTXT,01,01,02,PROTVER=20.01*1B
    

    which is exactly what I want. (Sometimes there is still garbage, but I don't think this will affect my parsing.)

    Thank you for all of your comments and answers, it helped a lot.