NMEA校验和计算计算

时间:2015-08-18 15:36:46

标签: c algorithm gps checksum nmea

我正在尝试找到已经由GPS计算的NMEA句子的校验和。

char GPRMCBuf[POS_BUFFER] = {0xA0, 0xA2, 0x00, 0x48, 0xDD, 
     0x24, 0x47, 0x50, 0x52, 0x4D, 0x43, 0x2C, 0x31, 0x35, 
     0x30, 0x35, 0x32, 0x30, 0x2E, 0x30, 0x30, 0x30, 0x2C, 
     0x41, 0x2C, 0x34, 0x31, 0x32, 0x31, 0x2E, 0x37, 0x39, 
     0x37, 0x37, 0x2C, 0x4E, 0x2C, 0x30, 0x30, 0x32, 0x31, 
     0x30, 0x2E, 0x39, 0x36, 0x36, 0x37, 0x2C, 0x45, 0x2C, 
     0x31, 0x2E, 0x35, 0x30, 0x2C, 0x35, 0x38, 0x2E, 0x32, 
     0x39, 0x2C, 0x32, 0x33, 0x30, 0x37, 0x31, 0x35, 0x2C, 
     0x2C, 0x2C, 0x41, 0x2A, 0x35, 0x38, 0x0D, 0x0A, 0x0F, 
     0x05, 0xB0, 0xB3};

听到最后的第3个和第4个字符是0F05的校验和 但我们想要纠正算法。我们使用的算法如下

Index = first,
checkSum = 0,
while index < msgLen,
checkSum = checkSum + message[index],
checkSum = checkSum AND (2^15-1).
increment index.
我们编写的代码如下:

#include<stdio.h>
main()
{
    unsigned char i;
    unsigned short chk;

    char test[]={ 0x47, 0x50, 0x52, 0x4D, 0x43, 0x2C, 0x31, 
            0x35, 0x30, 0x35, 0x32, 0x30, 0x2E, 0x30, 0x30, 
            0x30, 0x2C, 0x41, 0x2C, 0x34, 0x31, 0x32, 0x31, 
            0x2E, 0x37, 0x39, 0x37, 0x37, 0x2C, 0x4E, 0x2C, 
            0x30, 0x30, 0x32, 0x31, 0x30, 0x2E, 0x39, 0x36, 
            0x36, 0x37, 0x2C, 0x45, 0x2C, 0x31, 0x2E, 0x35, 
            0x30, 0x2C, 0x35, 0x38, 0x2E, 0x32, 0x39, 0x2C, 
            0x32, 0x33, 0x30, 0x37, 0x31, 0x35, 0x2C, 0x2C, 
            0x2C, 0x41,0x2A, 0x35, 0x38, 0x0D, 0x0A}; 

    chk = 0;

    for(i = 0; i < 70; i++)
    {    
        chk = chk + test[i];
        chk = chk & 32767;  
    }
    printf("A=%hu\n", chk);
    return 0;
}

问题是我们得到的是3588,但它应该是3845(0F05)。

请帮助我们解决此算法。

3 个答案:

答案 0 :(得分:4)

你做了一个很好的尝试,但你有一些错误。我认为以下链接是NMEA的一个很好的起点:http://www.gpsinformation.org/dale/nmea.htm

您将在简介中看到每个命令都是自包含的,以$符号开头,以回车符/换行符组合结束。校验和(如果存在)位于消息的末尾,并以星号*开头。您还将看到校验和是$*之间所有字节的XOR,校验和(十六进制)遵循ASCII格式的*

您的输入数据在开头和结尾也有一些噪音,您需要将其丢弃。让我来诠释你的意见:

char GPRMCBuf[POS_BUFFER] = {
    0xA0, 0xA2, 0x00, 0x48, 0xDD, // these bytes are not part of the message

    0x24, // this is the '$' character, so this is the message start byte

    // checksum calculation starts with the next byte (0x47)
    0x47, 0x50, 0x52, 0x4D, 0x43, 0x2C, 0x31, 0x35,        // GPRMC,15
    0x30, 0x35, 0x32, 0x30, 0x2E, 0x30, 0x30, 0x30, 0x2C,  // 0520.000,
    0x41, 0x2C, 0x34, 0x31, 0x32, 0x31, 0x2E, 0x37, 0x39,  // A,4121.79
    0x37, 0x37, 0x2C, 0x4E, 0x2C, 0x30, 0x30, 0x32, 0x31,  // 77,N,0021
    0x30, 0x2E, 0x39, 0x36, 0x36, 0x37, 0x2C, 0x45, 0x2C,  // 0.9667,E,
    0x31, 0x2E, 0x35, 0x30, 0x2C, 0x35, 0x38, 0x2E, 0x32,  // 1.50,58.2
    0x39, 0x2C, 0x32, 0x33, 0x30, 0x37, 0x31, 0x35, 0x2C,  // 9,230715,
    0x2C, 0x2C, 0x41,                                      // ,,A
    // checksum calculation ends here

    0x2A,       // The '*' character, i.e. message/checksum delimiter
    0x35, 0x38, // The checksum, '5' and '8', so the checksum is 0x58
    0x0D, 0x0A, // The CR/LF line terminator

    0x0F, 0x05, 0xB0, 0xB3 // these bytes are not part of the message
};

因此,校验和计算是:

chk = 0;
chk = chk ^ 0x47; // chk = 0x47
chk = chk ^ 0x50; // chk = 0x17
chk = chk ^ 0x52; // chk = 0x45
...
chk = chk ^ 0x41; // chk = 0x58

请注意,您最终会得到0x58,该消息在0x35 0x38消息中。因此,一旦你正确地构造了消息并调整了for循环来迭代校验和字节,循环体就会变成:

chk ^= test[i];

在循环之后,您需要将chk的两个半字节转换为ASCII并与信号校验和进行比较,或将信号校验和转换为二进制值并与chk进行比较。

答案 1 :(得分:1)

过去,我曾使用此功能来计算NMEA校验和 为了与NMEA消息中的校验和进行比较。

int calc_NMEA_Checksum( char *buf, int cnt )
{
    char Character;
    int Checksum = 0;
    int i;              // loop counter



    //foreach(char Character in sentence)
    for (i=0;i<cnt;++i)
    {
        Character = buf[i];
        switch(Character)
        {
            case '$':
                // Ignore the dollar sign
                break;
            case '*':
                // Stop processing before the asterisk
                i = cnt;
                continue;
            default:
                // Is this the first value for the checksum?
                if (Checksum == 0)
                {
                    // Yes. Set the checksum to the value
                    Checksum = Character;
                }
                else
                {
                    // No. XOR the checksum with this character's value
                    Checksum = Checksum ^ Character;
                }
                break;
        }
    }

    // Return the checksum
    return (Checksum);

} // calc_NEMA_Checksum()

在此计算之后,从NMEA消息中提取两个字节的校验和,将该2字节十六进制值重新格式化为1byte值,然后与上面计算的值进行比较

答案 2 :(得分:1)

正如之前的答案所说,你在校验和计算中排除了'$'和'*'。您可以使用C库libnmea中的函数:

#include <stdint.h>

#define NMEA_END_CHAR_1 '\n'
#define NMEA_MAX_LENGTH 70

uint8_t
nmea_get_checksum(const char *sentence)
{
    const char *n = sentence + 1; // Plus one, skip '$'
    uint8_t chk = 0;

    /* While current char isn't '*' or sentence ending (newline) */
    while ('*' != *n && NMEA_END_CHAR_1 != *n) {
        if ('\0' == *n || n - sentence > NMEA_MAX_LENGTH) {
            /* Sentence too long or short */
            return 0;
        }
        chk ^= (uint8_t) *n;
        n++;
    }

    return chk;
}

然后用你的句子字符串作为参数调用函数:

uint8_t chk = nmea_get_checksum(GPRMCBuf);