px-fwlib 0.10.0
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers generated with Doxygen 1.9.2
px_nmea.c
1/* =============================================================================
2 ____ ___ ____ ___ _ _ ___ __ __ ___ __ __ TM
3 | _ \ |_ _| / ___| / _ \ | \ | | / _ \ | \/ | |_ _| \ \/ /
4 | |_) | | | | | | | | | | \| | | | | | | |\/| | | | \ /
5 | __/ | | | |___ | |_| | | |\ | | |_| | | | | | | | / \
6 |_| |___| \____| \___/ |_| \_| \___/ |_| |_| |___| /_/\_\
7
8 Copyright (c) 2010 Pieter Conradie <https://piconomix.com>
9
10 License: MIT
11 https://github.com/piconomix/px-fwlib/blob/master/LICENSE.md
12
13 Title: px_nmea.h : NMEA parser
14 Author(s): Pieter Conradie
15 Creation Date: 2010-05-28
16
17============================================================================= */
18
19/* _____STANDARD INCLUDES____________________________________________________ */
20#include <string.h>
21
22/* _____PROJECT INCLUDES_____________________________________________________ */
23#include "px_nmea.h"
24#include "px_log.h"
25
26/* _____LOCAL DEFINITIONS____________________________________________________ */
27PX_LOG_NAME("px_nmea");
28
29// Receive and transmit buffer size
30#define PX_NMEA_BUFFER_SIZE 128
31
32typedef enum
33{
34 PX_NMEA_RX_STATE_START = 0,
35 PX_NMEA_RX_STATE_PAYLOAD,
36 PX_NMEA_RX_STATE_CHECKSUM1,
37 PX_NMEA_RX_STATE_CHECKSUM2,
38 PX_NMEA_RX_STATE_END_CR,
39 PX_NMEA_RX_STATE_END_LF,
40} px_nmea_rx_state_t;
41
42/* _____MACROS_______________________________________________________________ */
43
44/* _____GLOBAL VARIABLES_____________________________________________________ */
45px_nmea_data_t px_nmea_data;
46
47/* _____LOCAL VARIABLES______________________________________________________ */
48static px_nmea_tx_byte_t px_nmea_tx_byte_fn;
49static px_nmea_on_valid_str_t px_nmea_on_valid_str_fn;
50static px_nmea_on_valid_gps_data_t px_nmea_on_valid_gps_data_fn;
51
52static uint8_t px_nmea_rx_buf[PX_NMEA_BUFFER_SIZE];
53static uint16_t px_nmea_rx_index;
54static uint8_t px_nmea_rx_checksum;
55static px_nmea_rx_state_t px_nmea_rx_state;
56
57/* _____LOCAL FUNCTION DECLARATIONS__________________________________________ */
58static void px_nmea_tx_byte (uint8_t data);
59
60static bool px_nmea_cmp_nibble_with_hex_ascii(uint8_t nibble, char ascii);
61static char * px_nmea_parse_str_to_u32 (char * buf, uint32_t * value);
62static char * px_nmea_parse_str_to_s16 (char * buf, int16_t * value);
63static char * px_nmea_parse_str_to_u16 (char * buf, uint16_t * value);
64static char * px_nmea_parse_str_fraction_to_u16(char * buf, uint16_t * value, uint8_t precision);
65static char * px_nmea_parse_str_to_u8 (char * buf, uint8_t * value);
66static char * px_nmea_parse_str_fraction_to_u8 (char * buf, uint8_t * value, uint8_t precision);
67static char * px_nmea_find_next_param (char * buf);
68
69static void px_nmea_on_rx_frame (char * buf);
70
71/* _____LOCAL FUNCTIONS______________________________________________________ */
72static void px_nmea_tx_byte(uint8_t data)
73{
74 if(px_nmea_tx_byte_fn == NULL)
75 {
76 return;
77 }
78 (*px_nmea_tx_byte_fn)(data);
79}
80
81static bool px_nmea_cmp_nibble_with_hex_ascii(uint8_t nibble, char ascii)
82{
83 if(nibble <= 9)
84 {
85 nibble += '0';
86 }
87 else
88 {
89 nibble += ('A' - 10);
90 }
91
92 if(nibble == ascii)
93 {
94 return true;
95 }
96 else
97 {
98 return false;
99 }
100}
101
102static char * px_nmea_parse_str_to_u32(char * buf, uint32_t * value)
103{
104 uint32_t u32Value = 0;
105 while((*buf != ',') && (*buf != '.') && (*buf != '\0'))
106 {
107 if(*buf == ' ')
108 {
109 // Ignore whitespace
110 buf++;
111 continue;
112 }
113 u32Value *= 10;
114 u32Value += (*buf++) - '0';
115 }
116 *value = u32Value;
117 return buf;
118}
119
120static char * px_nmea_parse_str_to_s16(char * buf, int16_t * value)
121{
122 bool bNeg = false;
123 int16_t s16Value = 0;
124
125 while((*buf != ',') && (*buf != '.') && (*buf != '\0'))
126 {
127 if(*buf == ' ')
128 {
129 // Ignore whitespace
130 buf++;
131 continue;
132 }
133 else if(*buf == '-')
134 {
135 // Remove sign
136 bNeg = true;
137 buf++;
138 continue;
139 }
140 s16Value *= 10;
141 s16Value += (*buf++) - '0';
142 }
143 // Add sign if necessary
144 if(bNeg) s16Value *= -1;
145
146 *value = s16Value;
147 return buf;
148}
149
150static char * px_nmea_parse_str_to_u16(char * buf, uint16_t * value)
151{
152 uint16_t u16Value = 0;
153 while((*buf != ',') && (*buf != '.') && (*buf != '\0'))
154 {
155 if(*buf == ' ')
156 {
157 // Ignore whitespace
158 buf++;
159 continue;
160 }
161 u16Value *= 10;
162 u16Value += (*buf++) - '0';
163 }
164 *value = u16Value;
165 return buf;
166}
167
168static char * px_nmea_parse_str_fraction_to_u16(char * buf, uint16_t * value, uint8_t precision)
169{
170 uint16_t u16Value = 0;
171 while((*buf != ',') && (*buf != '.') && (*buf != '\0') && (precision != 0))
172 {
173 if(*buf == ' ')
174 {
175 // Ignore whitespace
176 buf++;
177 continue;
178 }
179 u16Value *= 10;
180 u16Value += (*buf++) - '0';
181 precision--;
182 }
183 while(precision != 0)
184 {
185 precision--;
186 u16Value *= 10;
187 }
188 *value = u16Value;
189 return buf;
190}
191
192static char * px_nmea_parse_str_to_u8(char * buf, uint8_t * value)
193{
194 uint8_t u8Value = 0;
195 while((*buf != ',') && (*buf != '.') && (*buf != '\0'))
196 {
197 if(*buf == ' ')
198 {
199 // Ignore whitespace
200 buf++;
201 continue;
202 }
203 u8Value *= 10;
204 u8Value += (*buf++) - '0';
205 }
206 *value = u8Value;
207 return buf;
208}
209
210static char * px_nmea_parse_str_fraction_to_u8(char * buf, uint8_t * value, uint8_t precision)
211{
212 uint8_t u8Value = 0;
213 while((*buf != ',') && (*buf != '.') && (*buf != '\0') && (precision != 0))
214 {
215 if(*buf == ' ')
216 {
217 // Ignore whitespace
218 buf++;
219 continue;
220 }
221 u8Value *= 10;
222 u8Value += (*buf++) - '0';
223 precision--;
224 }
225 while(precision != 0)
226 {
227 precision--;
228 u8Value *= 10;
229 }
230 *value = u8Value;
231 return buf;
232}
233
234static char * px_nmea_find_next_param(char * buf)
235{
236 while(*buf != ',')
237 {
238 if(*buf == '\0') return buf;
239 buf++;
240 }
241 return (++buf);
242}
243
244static void px_nmea_on_rx_frame(char * buf)
245{
246 // Notify handler with valid NMEA string
247 (*px_nmea_on_valid_str_fn)(buf);
248
249 // Make sure that message ID starts with "GP"
250 if(*buf++ != 'G')
251 {
252 return;
253 }
254 if(*buf++ != 'P')
255 {
256 return;
257 }
258
259 // Parse GGA (Global Positioning System Fixed Data) string
260 if(strncmp(buf,PX_NMEA_GGA_STR,3) == 0)
261 {
262#if 0
263 if(px_nmea_data.gga_valid_flag)
264 {
265 // Enable VTG message @ 1Hz update rate
266 px_nmea_tx_frame("$PSRF103,05,00,01,01");
267 }
268#endif
269 buf += 4;
270 px_nmea_data.gga_valid_flag = false;
271 px_nmea_data.latitude_fraction = 0;
272 px_nmea_data.longitude_fraction = 0;
273 // UTC Time
274 buf = px_nmea_parse_str_to_u32(buf, &px_nmea_data.utc_time);
275 if(*buf == '.')
276 {
277 buf++;
278 buf = px_nmea_parse_str_fraction_to_u16(buf, &px_nmea_data.utc_time_fraction, 3);
279 }
280 buf = px_nmea_find_next_param(buf);
281
282 // Latitude
283 buf = px_nmea_parse_str_to_u16(buf, (uint16_t *)&px_nmea_data.latitude);
284 if(*buf == '.')
285 {
286 buf++;
287 buf = px_nmea_parse_str_fraction_to_u16(buf, &px_nmea_data.latitude_fraction, 4);
288 }
289 buf = px_nmea_find_next_param(buf);
290 if(*buf == 'S')
291 {
292 px_nmea_data.latitude *= -1;
293 }
294 buf = px_nmea_find_next_param(buf);
295
296 // Longitude
297 buf = px_nmea_parse_str_to_u16(buf, (uint16_t *)&px_nmea_data.longitude);
298 if(*buf == '.')
299 {
300 buf++;
301 buf = px_nmea_parse_str_fraction_to_u16(buf, &px_nmea_data.longitude_fraction, 4);
302 }
303 buf = px_nmea_find_next_param(buf);
304 if(*buf == 'W')
305 {
306 px_nmea_data.longitude *= -1;
307 }
308 buf = px_nmea_find_next_param(buf);
309 // Fix valid
310 if(*buf == '1')
311 {
312 px_nmea_data.gga_valid_flag = true;
313 }
314 buf = px_nmea_find_next_param(buf);
315 // Number of satelites
316 buf = px_nmea_parse_str_to_u8(buf, &px_nmea_data.sattelites_used);
317 buf = px_nmea_find_next_param(buf);
318 // HDOP
319 buf = px_nmea_parse_str_to_u8(buf, &px_nmea_data.hdop);
320 if(*buf == '.')
321 {
322 buf++;
323 buf = px_nmea_parse_str_fraction_to_u8(buf, &px_nmea_data.hdop_fraction, 1);
324 }
325 buf = px_nmea_find_next_param(buf);
326 // Altitude
327 buf = px_nmea_parse_str_to_s16(buf, &px_nmea_data.altitude);
328 if(*buf == '.')
329 {
330 buf++;
331 buf = px_nmea_parse_str_fraction_to_u8(buf, &px_nmea_data.altitude_fraction, 2);
332 }
333
334 px_nmea_data.gga_valid_flag = true;
335
336 // See if all the fields were populated
337 if(px_nmea_data.gga_valid_flag && px_nmea_data.vtg_valid_flag)
338 {
339 (*px_nmea_on_valid_gps_data_fn)();
340 px_nmea_data.gga_valid_flag = false;
341 px_nmea_data.vtg_valid_flag = false;
342 }
343 }
344 // Parse VTG (Course Over Ground and Ground Speed) string
345 else if(strncmp(buf, PX_NMEA_VTG_STR, 3) == 0)
346 {
347 buf += 4;
348
349 // Heading
350 buf = px_nmea_parse_str_to_u16(buf,&px_nmea_data.heading);
351 if(*buf == '.')
352 {
353 buf++;
354 buf = px_nmea_parse_str_fraction_to_u8(buf,&px_nmea_data.heading_fraction,2);
355 }
356 buf = px_nmea_find_next_param(buf);
357 buf = px_nmea_find_next_param(buf);
358 buf = px_nmea_find_next_param(buf);
359 buf = px_nmea_find_next_param(buf);
360 buf = px_nmea_find_next_param(buf);
361 buf = px_nmea_find_next_param(buf);
362
363 // Speed
364 buf = px_nmea_parse_str_to_u8(buf,&px_nmea_data.speed);
365 if(*buf == '.')
366 {
367 buf++;
368 buf = px_nmea_parse_str_fraction_to_u8(buf,&px_nmea_data.speed_fraction,2);
369 }
370
371 px_nmea_data.vtg_valid_flag = true;
372
373 // See if all the fields were populated
374 if(px_nmea_data.gga_valid_flag && px_nmea_data.vtg_valid_flag)
375 {
376 (*px_nmea_on_valid_gps_data_fn)();
377 px_nmea_data.gga_valid_flag = false;
378 px_nmea_data.vtg_valid_flag = false;
379 }
380 }
381}
382
383/* _____GLOBAL FUNCTIONS_____________________________________________________ */
385 px_nmea_on_valid_str_t on_valid_str,
386 px_nmea_on_valid_gps_data_t on_valid_gps_data)
387{
388 // Save function pointers
389 px_nmea_tx_byte_fn = tx_byte;
390 px_nmea_on_valid_str_fn = on_valid_str;
391 px_nmea_on_valid_gps_data_fn = on_valid_gps_data;
392
393 // Reset state variables
394 px_nmea_rx_state = PX_NMEA_RX_STATE_START;
395 px_nmea_rx_index = 0;
396 px_nmea_data.gga_valid_flag = false;
397 px_nmea_data.vtg_valid_flag = false;
398}
399
400void px_nmea_on_rx_byte(uint8_t data)
401{
402 switch(px_nmea_rx_state)
403 {
404 case PX_NMEA_RX_STATE_START :
405 {
406 // Check start sequence
407 if(data != '$')
408 {
409 break;
410 }
411 px_nmea_rx_state = PX_NMEA_RX_STATE_PAYLOAD;
412 // Reset index and checksum
413 px_nmea_rx_checksum = 0;
414 px_nmea_rx_index = 0;
415 return;
416 }
417 case PX_NMEA_RX_STATE_PAYLOAD :
418 {
419 // Check for unexpected characters in payload
420 if( (data == '$' )
421 ||(data == '\r')
422 ||(data == '\n')
423 ||(data >= 0x80) )
424 {
425 break;
426 }
427 // Check for checksum marker
428 if(data == '*')
429 {
430 px_nmea_rx_state = PX_NMEA_RX_STATE_CHECKSUM1;
431 return;
432 }
433 // Update checksum of payload
434 px_nmea_rx_checksum ^= data;
435 // Put received byte into buffer
436 px_nmea_rx_buf[px_nmea_rx_index] = data;
437 // Check for buffer overflow
438 if(++px_nmea_rx_index >= (PX_NMEA_BUFFER_SIZE-1))
439 {
440 break;
441 }
442 return;
443 }
444 case PX_NMEA_RX_STATE_CHECKSUM1 :
445 {
446 // Check high nibble of checksum
447 if(!px_nmea_cmp_nibble_with_hex_ascii(((px_nmea_rx_checksum>>4)&0x0f), data))
448 {
449 break;
450 }
451
452 px_nmea_rx_state = PX_NMEA_RX_STATE_CHECKSUM2;
453 return;
454 }
455 case PX_NMEA_RX_STATE_CHECKSUM2 :
456 {
457 // Check low nibble of checksum
458 if(!px_nmea_cmp_nibble_with_hex_ascii((px_nmea_rx_checksum&0x0f),data))
459 {
460 break;
461 }
462
463 px_nmea_rx_state = PX_NMEA_RX_STATE_END_CR;
464 return;
465 }
466 case PX_NMEA_RX_STATE_END_CR :
467 {
468 // Check Carriage Return
469 if(data != '\r')
470 {
471 break;
472 }
473 px_nmea_rx_state = PX_NMEA_RX_STATE_END_LF;
474 return;
475 }
476 case PX_NMEA_RX_STATE_END_LF:
477 {
478 // Check Line Feed
479 if(data != '\n')
480 {
481 break;
482 }
483 // Append terminating zero
484 px_nmea_rx_buf[px_nmea_rx_index] = '\0';
485 // String successfully received
486 px_nmea_on_rx_frame((char *)px_nmea_rx_buf);
487 break;
488 }
489 }
490 // Error detected... reset receiver
491 px_nmea_rx_state = PX_NMEA_RX_STATE_START;
492}
493
494void px_nmea_tx_frame(char * frame)
495{
496 uint8_t data;
497 uint8_t checksum = '$';
498
499 // Send data and calculate checksum
500 while(*frame)
501 {
502 data = *frame++;
503 checksum ^= data;
504 px_nmea_tx_byte(data);
505 }
506
507 // Add checksum
508 px_nmea_tx_byte('*');
509 // Send high nibble
510 if(checksum < 0xA0)
511 {
512 px_nmea_tx_byte(((checksum >> 4) & 0x0f) + '0');
513 }
514 else
515 {
516 px_nmea_tx_byte(((checksum >> 4) & 0x0f) + ('A' - 10));
517 }
518 // Send low nibble
519 checksum &= 0x0f;
520 if(checksum < 0x0A)
521 {
522 px_nmea_tx_byte(checksum + '0');
523 }
524 else
525 {
526 px_nmea_tx_byte(checksum + ('A' - 10));
527 }
528
529 // Add end sequence
530 px_nmea_tx_byte('\r');
531 px_nmea_tx_byte('\n');
532}
533
#define NULL
NULL pointer.
Definition: px_defs.h:49
#define PX_LOG_NAME(name)
Macro to declare a log name string once for each file to reduce code size.
Definition: px_log.h:349
void(* px_nmea_on_valid_gps_data_t)(void)
Definition for a pointer to a function that will be called when GPS data is valid.
Definition: px_nmea.h:72
#define PX_NMEA_GGA_STR
Time, position and fix type data.
Definition: px_nmea.h:46
void(* px_nmea_on_valid_str_t)(const char *data)
Definition for a pointer to a function that when a valid NMEA string is received.
Definition: px_nmea.h:66
#define PX_NMEA_VTG_STR
Course and speed information relative to the ground.
Definition: px_nmea.h:52
void(* px_nmea_tx_byte_t)(uint8_t data)
Definition for a pointer to a function that will be called to send a character.
Definition: px_nmea.h:60
void px_nmea_tx_frame(char *frame)
Function that is called to send an NMEA frame with the checksum appended.
Definition: px_nmea.c:494
void px_nmea_on_rx_byte(uint8_t data)
Function handler that is fed all raw received data.
Definition: px_nmea.c:400
void px_nmea_init(px_nmea_tx_byte_t tx_byte, px_nmea_on_valid_str_t on_valid_str, px_nmea_on_valid_gps_data_t on_valid_gps_data)
Initialise NMEA parser module.
Definition: px_nmea.c:384
Parsed time, position, quality data.
Definition: px_nmea.h:76