NMEA Парсер для обработки данных от GPS/ГЛОНАСС приемника

При разработке микроконтроллерной системы, содержащей GPS/ГЛОНАСС приемник, необходимо получать и декодировать его сообщения, превращая их в удобочитаемый формат.gps-structКак правило, GPS приемник выдает сообщения через последовательный порт в формате NMEA. Процедура, показанная ниже, превращает текстовую NMEA посылку в структуру, более удобную для дальнейшей обработки.

Программа тестировалась на микроконтроллере STM32F217IGT6 с GPS/ГЛОНАСС модулем Геос-1М, но подойдет для любого контроллера с UART. Вид структуры, получаемой из NMEA, показан на иллюстрации. В структуру транслируются все имеющиеся данные, от времени и географических координат до списка видимых спутников.

Parser source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include "stm32f2xx_it.h"
#include "main.h"
#include "string.h"
#include <stdlib.h>
 
uint8_t NMEA_Parser_GGA(void);
uint8_t NMEA_Parser_ZDA(void);
uint8_t NMEA_Parser_GSV(void);
uint8_t ControlCheckSum(uint16_t StartIndex);
 
#define GPSBUFLENGTH  (700)         // Буфер побольше, чтобы влезли все нужные посылки (GSV особенно длинная)
char GPSBuf[GPSBUFLENGTH];
volatile unsigned int GPSBufWrPoint;  // Буфер не кольцевой, а линейный, нужна только точка записи
 
uint16_t GSVStartPos = 0; // Эти переменные используются только в NMEA_Parser_GSV(), но обнуляются только
char SatNum = 0;        // в основной программе, потому эти переменные инкрементируются во время нескольких последовательных вызовов GSVStartPos
 
#define RESERVE (0)   // Можно удлинить поля структуры GPGGA_Struct, если изменится формат или при другой необходимости
#define MAXSAT (64)   // Максимальное количество спутников, GPS + ГЛОНАСС, для массива параметров видимых спутников
#define PARSAT (4)    // Номер, угол места, азимут, SNR, для массива параметров видимых спутников
#define ONESAT "nnn"    // Максимальная длина одного поля массива параметров видимых спутников
 
struct GPGGA_Struct         // Структура с данными GPS-ГЛОНАСС-модуля
{
  char Time [sizeof("hhmmss.ss")+RESERVE];      // Время
  char Latitude [sizeof("xxxx.yyyy")+RESERVE];    // Широта
  char NS;                            // Север-Юг
  char Longitude[sizeof("xxxxx.yyyy")+RESERVE]; // Долгота
  char EW;                            // Запад-Восток
  char ReceiverMode;                    // Режим работы приемника
  char SatelliteNum [sizeof("nn")+RESERVE];     // Количество спутников в решении
  char Altitude [sizeof("aaaaa.a")+RESERVE];    // Высота
  char Year[sizeof("yyyy")+RESERVE];          // Год
  char Month[sizeof("mm")+RESERVE];         // Месяц
  char Day[sizeof("dd")+RESERVE];           // Число
  char LocalTimeHr [sizeof("+hh")+RESERVE];     // Поправка на местное время, часы
  char LocalTimeMn [sizeof("mm")+RESERVE];      // Поправка на местное время, минуты
  char Sats [MAXSAT][PARSAT][sizeof(ONESAT)];   // Массив параметров видимых спутников
};
 
struct GPGGA_Struct GPSFixData; // С этой структурой и будем работать
 
// Прием посылок от GPS-приемника
void USART1_IRQHandler(void)
{
  volatile char UsartTemp = 0;
 
  if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
  {
    UsartTemp = USART_ReceiveData(USART1);
    GPSBuf[GPSBufWrPoint] = UsartTemp;
    
    if ((GPSBufWrPoint > 1) && (GPSBufWrPoint < GPSBUFLENGTH))  // Проверка нахождения внутри буфера, чтобы при дальнейшем преобразовании не выскочить за границу
    {
      if ((GPSBuf[GPSBufWrPoint-1] == ',') && (GPSBuf[GPSBufWrPoint] == ','))
      { // Принятая последовательность байт вида ",," превращается в ",0,", тем самым превращая неопроеделенный параметр в четкий 0
        GPSBuf[GPSBufWrPoint]   = '0';
        GPSBuf[GPSBufWrPoint+1] = ',';
        GPSBufWrPoint++;
      }
      if ((GPSBuf[GPSBufWrPoint-1] == ',') && (GPSBuf[GPSBufWrPoint] == '*'))
      { // Принятая последовательность байт вида ",*" превращается в ",0*", тем самым превращая неопроеделенный параметр в четкий 0
        GPSBuf[GPSBufWrPoint]   = '0';
        GPSBuf[GPSBufWrPoint+1] = '*';
        GPSBufWrPoint++;
      }
    }
    
    if (GPSBufWrPoint < GPSBUFLENGTH)
      GPSBufWrPoint++;  // Продолжаем заполнять буфер
    else
    { // Если буфер заполнился, начинаем искать и декодировать NMEA сентенции
      GPSBufWrPoint = 0;
      if (NMEA_Parser_GGA() == 1) // GGA - основная сентенция, выдает координаты
      {
        NMEA_Parser_ZDA();      // ZDA выдает время и дату
        
        GSVStartPos = 0;
        SatNum = 0;
        if (NMEA_Parser_GSV() == 1) // GSV выдает список спутников, видимых приемником. Эту информацию можно использовать для графического отображения
        {
        for (char m = 0; m <=6; m++) // GSV посылки приходят группами, потому что в каждую посылку входит информация максимум о 4-х спутниках, а их примерно 25
          NMEA_Parser_GSV();
        }
      }
    }
  }
}
 
uint8_t NMEA_Parser_GGA(void)
{
  uint8_t CommaPos;
  uint8_t k;
  uint16_t i,StartPos = 0;    // Начало строки
  
  while (!( (GPSBuf[StartPos]   == '$')
        &&(GPSBuf[StartPos+3] == 'G')
        &&(GPSBuf[StartPos+4] == 'G')
        &&(GPSBuf[StartPos+5] == 'A'))
        && StartPos < 7)
    StartPos++;
  
  // начало пакета слишком сильно смещено, отбрасываем пакет
  if (StartPos>5) return 0;
 
  if (ControlCheckSum(StartPos) == 0) return 0; // Проверка контрольной суммы
  
  memset(&GPSFixData, 0, sizeof(GPSFixData));   // Очищаем структуру
 
  for (i = StartPos+6; i < GPSBUFLENGTH ; i++)   // Ждем или конца буфера или 10-й запятой
  {
    if (GPSBuf[i] == ',')
    {
      CommaPos++; // Наращиваем счетчик запятых
      k=0;
    }    
    else
      switch (CommaPos) // Пользуемся тем, что данные разделяются запятыми. Неопределенные данные (две запятые подряд) мы заменили нулем в обработчике прерывания USART1_IRQHandler()
      {
        // После первой запятой идет время, но мы возьмем его позже из ZDA сентенции, синхронно с датой
        case 2: if(k < sizeof(GPSFixData.Latitude)-1) GPSFixData.Latitude[k++]=GPSBuf[i]; break;
        case 3: if(k < sizeof(GPSFixData.NS)) GPSFixData.NS=GPSBuf[i]; break;
        case 4: if(k < sizeof(GPSFixData.Longitude)-1) GPSFixData.Longitude[k++]=GPSBuf[i]; break;
        case 5: if(k < sizeof(GPSFixData.EW)) GPSFixData.EW=GPSBuf[i]; break;
        case 6: if(k < sizeof(GPSFixData.ReceiverMode))  GPSFixData.ReceiverMode=GPSBuf[i]; break;
        case 7: if(k < sizeof(GPSFixData.SatelliteNum)-1) GPSFixData.SatelliteNum[k++]=GPSBuf[i]; break;
        case 9: if(k < sizeof(GPSFixData.Altitude)-1) GPSFixData.Altitude[k++]=GPSBuf[i]; break;
        case 10: return 1; break;
        default: break;
      }
  }       
  return 1;
}
 
uint8_t NMEA_Parser_ZDA(void)
{
  uint8_t CommaPos = 0;
  uint8_t k;              // Просто счетчик
  uint16_t i,StartPos = 0;    // Начало строки
  
  while (!( (GPSBuf[StartPos]   == '$')
        &&(GPSBuf[StartPos+3] == 'Z')
        &&(GPSBuf[StartPos+4] == 'D')
        &&(GPSBuf[StartPos+5] == 'A'))
        && StartPos < (GPSBUFLENGTH - sizeof("$GNZDA,hhmmss.ss,dd,mm,yyyy,+hh,mm*cs")+1))
    StartPos++;
  
  // начало пакета слишком сильно смещено, отбрасываем этот пакет
  if (StartPos > (GPSBUFLENGTH - sizeof("$GNZDA,hhmmss.ss,dd,mm,yyyy,+hh,mm*cs")-1)) return 0;
  
  if (ControlCheckSum(StartPos) == 0) return 0; // Проверка контрольной суммы
  
  for (i = StartPos+5; i < GPSBUFLENGTH ; i++)   // Ждем или конца буфера или 7-й запятой
  {
    if (GPSBuf[i] == ',')
    {
      CommaPos++; // Наращиваем счетчик запятых
      k=0;
    }    
    else
      switch (CommaPos) // Пользуемся тем, что данные разделяются запятыми. Неопределенные данные (две запятые подряд) мы заменили нулем в обработчике прерывания USART1_IRQHandler()
      {
        case 1: if(k < sizeof(GPSFixData.Time)-1) GPSFixData.Time[k++]=GPSBuf[i]; break;
        case 2: if(k < sizeof(GPSFixData.Day)-1) GPSFixData.Day[k++]=GPSBuf[i]; break;
        case 3: if(k < sizeof(GPSFixData.Month)-1) GPSFixData.Month[k++]=GPSBuf[i]; break;
        case 4: if(k < sizeof(GPSFixData.Year)-1) GPSFixData.Year[k++]=GPSBuf[i];  break;
        case 5: if(k < sizeof(GPSFixData.LocalTimeHr)-1) GPSFixData.LocalTimeHr[k++]=GPSBuf[i]; break;
        case 6: if(k < sizeof(GPSFixData.LocalTimeMn)-1) GPSFixData.LocalTimeMn[k++]=GPSBuf[i]; break;
        case 7: return 1; break;
        default: break;
      }
  }       
  return 1;
}
 
uint8_t NMEA_Parser_GSV(void)
{
  uint8_t CommaPos = 0;
  uint8_t k;              // Просто счетчик
  uint16_t i = 0;
  
  while (!( (GPSBuf[GSVStartPos]   == '$')
        &&(GPSBuf[GSVStartPos+3] == 'G')
        &&(GPSBuf[GSVStartPos+4] == 'S')
        &&(GPSBuf[GSVStartPos+5] == 'V'))
        && GSVStartPos < (GPSBUFLENGTH - sizeof("$GPGSV,n,n,nn,11,22,333,44,11,22,333,44,11,22,333,44,11,22,333,44*cs")+1))
    GSVStartPos++;
  
  // начало пакета слишком сильно смещено, отбрасываем его
  if (GSVStartPos > (GPSBUFLENGTH - sizeof("$GPGSV,n,n,nn,11,22,333,44,11,22,333,44,11,22,333,44,11,22,333,44*cs")-1)) return 0;
  
  if (ControlCheckSum(GSVStartPos) == 0) return 0;  // Проверка контрольной суммы
  
  for (i = GSVStartPos+5; i < GPSBUFLENGTH ; i++)    // Ждем или конца буфера или * в приемном буфере
  {
    if (GPSBuf[i] == '*')
    {
      GSVStartPos = i;
      SatNum++;
      return 1;
    }
    
    if (GPSBuf[i] == ',')
    {
      if ((CommaPos == 7) || (CommaPos == 11) || (CommaPos == 15) || (CommaPos == 19)) SatNum++;  // Наращиваем номер текущего спутника
      CommaPos++; k=0;  // Наращиваем счетчик запятых
    }    
    else
      switch (CommaPos) // Пользуемся тем, что данные разделяются запятыми. Неопределенные данные (две запятые подряд) мы заменили нулем в обработчике прерывания USART1_IRQHandler()
      { // После первых трех запятых идет информация об общем количестве спутников, об общем количестве GSV посылок и о номере этой конкретной GSV посылки. Эта информация нам не нужна
        case 4: case 8: case 12: case 16:  if(k < sizeof(ONESAT)-1) GPSFixData.Sats[SatNum][0][k++]=GPSBuf[i]; break// Номер спутника. case 4: case 8: case 12: case 16: - номера запятых, после которых идет соответствующая информация. В каждой GSV посылке есть информация о максимум 4-х спутниках, поэтому 20-ю запятую можно не обрабатывать
        case 5: case 9: case 13: case 17:  if(k < sizeof(ONESAT)-1) GPSFixData.Sats[SatNum][1][k++]=GPSBuf[i]; break// Угол места
        case 6: case 10: case 14: case 18: if(k < sizeof(ONESAT)-1) GPSFixData.Sats[SatNum][2][k++]=GPSBuf[i]; break// Азимут
        case 7: case 11: case 15: case 19: if(k < sizeof(ONESAT)-1) GPSFixData.Sats[SatNum][3][k++]=GPSBuf[i]; break// Уровень сигнала, SNR
        default: break;
      }
  }       
  return 1;
}
 
uint8_t ControlCheckSum(uint16_t StartIndex)
{
  uint8_t  CheckSum = 0, MessageCheckSum = 0;   // Контрольная сумма
  uint16_t i = StartIndex+1;                // Смещаемся на один шаг вправо от символа $
 
  while (GPSBuf[i]!='*'
  {
    CheckSum^=GPSBuf[i];
    if (++i == GPSBUFLENGTH) return 0; // Не найден признак контрольной суммы         
  }
  
  if (GPSBuf[++i]>0x40) MessageCheckSum=(GPSBuf[i]-0x37)<<4; // Условие для корректной обработки десятичных
  else                  MessageCheckSum=(GPSBuf[i]-0x30)<<4;  // и шестнадцатиричных чисел, представленных в коде ASCII
  if (GPSBuf[++i]>0x40) MessageCheckSum+=(GPSBuf[i]-0x37);
  else                  MessageCheckSum+=(GPSBuf[i]-0x30);
 
  if (MessageCheckSum != CheckSum) return 0; // Неправильное значение контрольной суммы
 
  return 1; // Все ОК
}

 

Обратная связь

Интересуют вопросы реализации алгоритмов, программирования, выбора электроники и прочая информация, постараюсь осветить в отдельных статьях

пишите мне на netdm@mail.ru