GPS Device
Loading...
Searching...
No Matches
NEO-6 Internal Helpers

Functions

static ERR_te neo6_calc_checksum (char *msg, uint32_t msg_len, uint8_t *checksum_o)
 Computes the NMEA checksum of a sentence by XORing all bytes between '$' and '*'.
static ERR_te neo6_process_msg (uint8_t *msg, uint32_t msg_len)
 Verifies the checksum of a received NMEA sentence and dispatches it to the appropriate parser.
static ERR_te neo6_process_rmc (char **tokens)
 Parses an RMC sentence and updates time, date, latitude, and longitude.
static ERR_te neo6_process_vtg (char **tokens)
 Parses a VTG sentence and updates movement direction and speed.
static ERR_te neo6_process_gga (char **tokens)
 Parses a GGA sentence and updates fix status, satellites used, orthometric height, and geoid separation.
static ERR_te neo6_process_gsa (char **tokens)
 Parses a GSA sentence and updates fix type, PDOP, HDOP, and VDOP.
static ERR_te neo6_process_gsv (char **tokens)
 Parses a GSV sentence and updates the total satellite count.

Detailed Description

Function Documentation

◆ neo6_calc_checksum()

ERR_te neo6_calc_checksum ( char * msg,
uint32_t msg_len,
uint8_t * checksum_o )
static

Computes the NMEA checksum of a sentence by XORing all bytes between '$' and '*'.

Iterates through msg, skipping the leading '$', XORing each byte until '*' is encountered. The result is the standard NMEA XOR checksum.

Parameters
[in]msgPointer to the null-terminated NMEA sentence string.
[in]msg_lenLength of the message in bytes.
[out]checksum_oPointer to a variable that will receive the computed checksum.
Returns
  • ERR_OK on success
  • ERR_UNKNOWN if '*' is not found before msg_len bytes are consumed

Definition at line 458 of file neo6.c.

458 {
459 uint8_t xor_val = 0;
460 uint32_t char_count = 0;
461
462 while(*msg != '*' && char_count < msg_len) {
463 if(*msg == '$') {
464 msg++;
465 continue;
466 }
467
468 xor_val ^= *msg;
469
470 msg++;
471 char_count++;
472
473 if(char_count == msg_len) {
474 return ERR_UNKNOWN;
475 }
476 }
477 *checksum_o = xor_val;
478
479 return ERR_OK;
480}
@ ERR_UNKNOWN
Definition err.h:37
@ ERR_OK
Definition err.h:36
Here is the caller graph for this function:

◆ neo6_process_msg()

ERR_te neo6_process_msg ( uint8_t * msg,
uint32_t msg_len )
static

Verifies the checksum of a received NMEA sentence and dispatches it to the appropriate parser.

Extracts the two-character hex checksum from the end of the sentence, computes the expected checksum via neo6_calc_checksum, and compares them. On a match, tokenizes the sentence by comma and dispatches to neo6_process_rmc, neo6_process_vtg, neo6_process_gga, neo6_process_gsa, or neo6_process_gsv based on the sentence ID. Unrecognized sentence types are silently ignored.

Parameters
[in]msgPointer to the raw NMEA sentence bytes.
[in]msg_lenLength of the sentence in bytes.
Returns
  • ERR_OK on success or for unrecognized sentence types
  • ERR_DATA_ACQUISITION_FAILURE if the checksum does not match
  • Propagated error from neo6_calc_checksum on failure

Definition at line 501 of file neo6.c.

501 {
502 ERR_te err;
503
504 uint8_t checksum_recv = ascii_hex_to_byte(msg[msg_len - 4], msg[msg_len - 3]);
505
506 uint8_t checksum_calc = 0;
507 err = neo6_calc_checksum((char*)msg, msg_len, &checksum_calc);
508 if(err != ERR_OK) {
509 return err;
510 }
511
512 char *tokens[CONFIG_NEO6_MAX_TOKENS];
513 uint16_t num_tokens = 0;
514
515 if(checksum_recv == checksum_calc) {
516 str_tokenize((char*)msg, ",", CONFIG_NEO6_MAX_TOKENS, tokens, &num_tokens);
517
518 if(str_cmp("$GPRMC", tokens[0]) == true) {
519 neo6_process_rmc(tokens);
520 }
521 else if(str_cmp("$GPVTG", tokens[0]) == true) {
522 neo6_process_vtg(tokens);
523 }
524 else if(str_cmp("$GPGGA", tokens[0]) == true) {
525 neo6_process_gga(tokens);
526 }
527 else if(str_cmp("$GPGSA", tokens[0]) == true) {
528 neo6_process_gsa(tokens);
529 }
530 else if(str_cmp("$GPGSV", tokens[0]) == true) {
531 neo6_process_gsv(tokens);
532 }
533 }
534 else {
536 }
537
538 return ERR_OK;
539}
bool str_cmp(const char *str1, const char *str2)
Compares two null-terminated strings for equality.
Definition common.c:248
uint8_t ascii_hex_to_byte(char high, char low)
Converts two ASCII hex characters into a single byte value.
Definition common.c:265
int str_tokenize(char *str, const char *separator, uint16_t max_tokens, char **tokens, uint16_t *num_tokens)
Splits a string into tokens separated by a given delimiter.
Definition common.c:287
ERR_te
Standard return type used by all public API functions.
Definition err.h:35
@ ERR_DATA_ACQUISITION_FAILURE
Definition err.h:47
static ERR_te neo6_process_gga(char **tokens)
Parses a GGA sentence and updates fix status, satellites used, orthometric height,...
Definition neo6.c:678
static ERR_te neo6_process_rmc(char **tokens)
Parses an RMC sentence and updates time, date, latitude, and longitude.
Definition neo6.c:556
static ERR_te neo6_process_gsa(char **tokens)
Parses a GSA sentence and updates fix type, PDOP, HDOP, and VDOP.
Definition neo6.c:757
static ERR_te neo6_calc_checksum(char *msg, uint32_t msg_len, uint8_t *checksum_o)
Computes the NMEA checksum of a sentence by XORing all bytes between '$' and '*'.
Definition neo6.c:458
static ERR_te neo6_process_vtg(char **tokens)
Parses a VTG sentence and updates movement direction and speed.
Definition neo6.c:634
static ERR_te neo6_process_gsv(char **tokens)
Parses a GSV sentence and updates the total satellite count.
Definition neo6.c:826
Here is the call graph for this function:
Here is the caller graph for this function:

◆ neo6_process_rmc()

ERR_te neo6_process_rmc ( char ** tokens)
static

Parses an RMC sentence and updates time, date, latitude, and longitude.

If the validity flag (token 2) is not "A" (active), all four fields are set to "No data" and the function returns early. Otherwise: time is formatted as "HH:MM:SS UTC", date as "DD/MM/YYYY", and latitude/longitude have their hemisphere indicator appended.

Parameters
[in]tokensTokenized RMC sentence (comma-separated fields).
Returns
  • ERR_OK on success
  • ERR_UNKNOWN if the validity flag is not "A"

Definition at line 556 of file neo6.c.

556 {
557 if(str_cmp(tokens[2], "A") == false) {
558 str_cpy(internal_state.neo6_info.time, "No data",
559 get_str_len("No data") + 1);
560
561 str_cpy(internal_state.neo6_info.date, "No data",
562 get_str_len("No data") + 1);
563
564 str_cpy(internal_state.neo6_info.lon, "No data",
565 get_str_len("No data") + 1);
566
567 str_cpy(internal_state.neo6_info.lat, "No data",
568 get_str_len("No data") + 1);
569
570 return ERR_UNKNOWN;
571 }
572
573 // UTC — format hhmmss → HH:MM:SS UTC
574 uint8_t time_len = 6;
575 int8_t real_counter = 0;
576 for(uint8_t j = 0; j < time_len; j++) {
577 internal_state.neo6_info.time[real_counter++] = tokens[1][j];
578
579 if((real_counter + 1) % 3 == 0 && real_counter <= 5) {
580 internal_state.neo6_info.time[real_counter++] = ':';
581 }
582 }
583 internal_state.neo6_info.time[real_counter++] = ' ';
584 internal_state.neo6_info.time[real_counter++] = 'U';
585 internal_state.neo6_info.time[real_counter++] = 'T';
586 internal_state.neo6_info.time[real_counter++] = 'C';
587 internal_state.neo6_info.time[real_counter++] = '\0';
588
589 // Latitude
590 uint8_t lat_txt_len = get_str_len(tokens[3]);
591 for(uint8_t j = 0; j < lat_txt_len; j++) {
592 internal_state.neo6_info.lat[j] = tokens[3][j];
593 }
594 internal_state.neo6_info.lat[lat_txt_len] = tokens[4][0];
595 internal_state.neo6_info.lat[lat_txt_len + 1] = '\0';
596
597 // Longitude
598 uint8_t lon_txt_len = get_str_len(tokens[5]);
599 for(uint8_t j = 0; j < lon_txt_len; j++) {
600 internal_state.neo6_info.lon[j] = tokens[5][j];
601 }
602 internal_state.neo6_info.lon[lon_txt_len] = tokens[6][0];
603 internal_state.neo6_info.lon[lon_txt_len + 1] = '\0';
604
605 // Date — format ddmmyy → DD/MM/20YY
606 internal_state.neo6_info.date[0] = tokens[9][0];
607 internal_state.neo6_info.date[1] = tokens[9][1];
608 internal_state.neo6_info.date[2] = '/';
609 internal_state.neo6_info.date[3] = tokens[9][2];
610 internal_state.neo6_info.date[4] = tokens[9][3];
611 internal_state.neo6_info.date[5] = '/';
612 internal_state.neo6_info.date[6] = '2';
613 internal_state.neo6_info.date[7] = '0';
614 internal_state.neo6_info.date[8] = tokens[9][4];
615 internal_state.neo6_info.date[9] = tokens[9][5];
616 internal_state.neo6_info.date[10] = '\0';
617
618 return ERR_OK;
619}
static struct internal_state_s internal_state
Singleton instance of the SysTick driver internal state.
int str_cpy(char *str_to, const char *str_from, uint32_t len)
Copies a null-terminated string into a destination buffer.
Definition common.c:333
uint32_t get_str_len(char const *str)
Returns the length of a string, excluding the null terminator.
Definition common.c:22
Here is the call graph for this function:
Here is the caller graph for this function:

◆ neo6_process_vtg()

ERR_te neo6_process_vtg ( char ** tokens)
static

Parses a VTG sentence and updates movement direction and speed.

Direction is taken from the true-north track field. Speed is taken from the speed-over-ground field and has " kph" appended. Empty fields result in "No data" being written instead.

Parameters
[in]tokensTokenized VTG sentence.
Returns
  • ERR_OK always

Definition at line 634 of file neo6.c.

634 {
635 // Movement direction (relative to true north)
636 uint8_t track_made_good_len = get_str_len(tokens[VTG_TMG_T_DEGREE_POS]);
637 if(track_made_good_len == 0) {
638 str_cpy(internal_state.neo6_info.mov_dir, "No data",
639 get_str_len("No data") + 1);
640 }
641 else {
642 str_cpy(internal_state.neo6_info.mov_dir, &tokens[VTG_TMG_T_DEGREE_POS][0],
643 track_made_good_len + 1);
644 }
645
646 // Movement speed (kph)
647 uint8_t kph_len = get_str_len(tokens[VTG_SPEED_OVER_GROUND_POS]);
648 if(kph_len == 0) {
649 str_cpy(internal_state.neo6_info.mov_speed, "No data",
650 get_str_len("No data") + 1);
651 }
652 else {
653 str_cpy(internal_state.neo6_info.mov_speed, &tokens[VTG_SPEED_OVER_GROUND_POS][0],
654 kph_len + 1);
655 internal_state.neo6_info.mov_speed[kph_len++] = ' ';
656 internal_state.neo6_info.mov_speed[kph_len++] = 'k';
657 internal_state.neo6_info.mov_speed[kph_len++] = 'p';
658 internal_state.neo6_info.mov_speed[kph_len++] = 'h';
659 internal_state.neo6_info.mov_speed[kph_len++] = '\0';
660 }
661
662 return ERR_OK;
663}
#define VTG_SPEED_OVER_GROUND_POS
Definition neo6.c:33
#define VTG_TMG_T_DEGREE_POS
Definition neo6.c:27
Here is the call graph for this function:
Here is the caller graph for this function:

◆ neo6_process_gga()

ERR_te neo6_process_gga ( char ** tokens)
static

Parses a GGA sentence and updates fix status, satellites used, orthometric height, and geoid separation.

Fix quality is mapped to a human-readable string: "No valid fix" (0), "GPS fix" (1), "DGPS fix" (2). Height and separation values have their unit character appended. Empty fields result in "No data".

Parameters
[in]tokensTokenized GGA sentence.
Returns
  • ERR_OK always

Definition at line 678 of file neo6.c.

678 {
679 // Quality of the fix
680 uint8_t fix_status_len = get_str_len(tokens[GGA_QUALITY_FIX_POS]);
681 if(fix_status_len == 0) {
682 str_cpy(internal_state.neo6_info.fix_status, "No data",
683 get_str_len("No data") + 1);
684 }
685 else if(str_cmp(tokens[GGA_QUALITY_FIX_POS], "0") == true) {
686 char msg[] = "No valid fix";
687 str_cpy(internal_state.neo6_info.fix_status, msg, get_str_len(msg) + 1);
688 }
689 else if(str_cmp(tokens[GGA_QUALITY_FIX_POS], "1") == true) {
690 char msg[] = "GPS fix";
691 str_cpy(internal_state.neo6_info.fix_status, msg, get_str_len(msg) + 1);
692 }
693 else if(str_cmp(tokens[GGA_QUALITY_FIX_POS], "2") == true) {
694 char msg[] = "DGPS fix";
695 str_cpy(internal_state.neo6_info.fix_status, msg, get_str_len(msg) + 1);
696 }
697 else {
698 char msg[] = "?";
699 str_cpy(internal_state.neo6_info.fix_status, msg, get_str_len(msg) + 1);
700 }
701
702 // Number of satellites used
703 uint8_t num_sats_used_len = get_str_len(tokens[GGA_NUM_SATS_POS]);
704 if(num_sats_used_len == 0) {
705 str_cpy(internal_state.neo6_info.num_sats_used, "No data",
706 get_str_len("No data") + 1);
707 }
708 else {
709 str_cpy(internal_state.neo6_info.num_sats_used, tokens[GGA_NUM_SATS_POS],
710 get_str_len(tokens[GGA_NUM_SATS_POS]) + 1);
711 }
712
713 // Orthometric height
714 uint8_t ort_height_len = get_str_len(tokens[GGA_ORTH_HEIGHT_POS]);
715 if(ort_height_len == 0) {
716 str_cpy(internal_state.neo6_info.ort_height, "No data",
717 get_str_len("No data") + 1);
718 }
719 else {
720 str_cpy(internal_state.neo6_info.ort_height, tokens[GGA_ORTH_HEIGHT_POS],
721 ort_height_len + 1);
722 internal_state.neo6_info.ort_height[ort_height_len] = ' ';
723 internal_state.neo6_info.ort_height[ort_height_len + 1] = tokens[GGA_ORTH_HEIGHT_UNIT_POS][0];
724 internal_state.neo6_info.ort_height[ort_height_len + 2] = '\0';
725 }
726
727 // Geoid separation
728 uint8_t geoid_sep_len = get_str_len(tokens[GGA_GEOID_SEP_POS]);
729 if(geoid_sep_len == 0) {
730 str_cpy(internal_state.neo6_info.geoid_sep, "No data",
731 get_str_len("No data") + 1);
732 }
733 else {
734 str_cpy(internal_state.neo6_info.geoid_sep, tokens[GGA_GEOID_SEP_POS],
735 geoid_sep_len + 1);
736 internal_state.neo6_info.geoid_sep[geoid_sep_len] = ' ';
737 internal_state.neo6_info.geoid_sep[geoid_sep_len + 1] = tokens[GGA_GEOID_SEP_UNIT_POS][0];
738 internal_state.neo6_info.geoid_sep[geoid_sep_len + 2] = '\0';
739 }
740
741 return ERR_OK;
742}
#define GGA_GEOID_SEP_POS
Definition neo6.c:48
#define GGA_GEOID_SEP_UNIT_POS
Definition neo6.c:49
#define GGA_ORTH_HEIGHT_UNIT_POS
Definition neo6.c:47
#define GGA_ORTH_HEIGHT_POS
Definition neo6.c:46
#define GGA_QUALITY_FIX_POS
Definition neo6.c:43
#define GGA_NUM_SATS_POS
Definition neo6.c:44
Here is the call graph for this function:
Here is the caller graph for this function:

◆ neo6_process_gsa()

ERR_te neo6_process_gsa ( char ** tokens)
static

Parses a GSA sentence and updates fix type, PDOP, HDOP, and VDOP.

Fix type is mapped to a human-readable string: "Not available" (1), "2D" (2), "3D" (3). VDOP strips the trailing NMEA checksum suffix (*XX) before storing. Empty fields result in "No data".

Parameters
[in]tokensTokenized GSA sentence.
Returns
  • ERR_OK always

Definition at line 757 of file neo6.c.

757 {
758 uint8_t msg_part_len = get_str_len(tokens[GSA_FIX_TYPE_POS]);
759 if(msg_part_len == 0) {
760 str_cpy(internal_state.neo6_info.fix_type, "No data",
761 get_str_len("No data") + 1);
762 }
763 else {
764 if(str_cmp(tokens[GSA_FIX_TYPE_POS], "1") == true) {
765 str_cpy(internal_state.neo6_info.fix_type, "Not available",
766 get_str_len("Not available") + 1);
767 }
768 else if(str_cmp(tokens[GSA_FIX_TYPE_POS], "2") == true) {
769 str_cpy(internal_state.neo6_info.fix_type, "2D",
770 get_str_len("2D") + 1);
771 }
772 else if(str_cmp(tokens[GSA_FIX_TYPE_POS], "3") == true) {
773 str_cpy(internal_state.neo6_info.fix_type, "3D",
774 get_str_len("3D") + 1);
775 }
776 }
777
778 msg_part_len = get_str_len(tokens[GSA_PDOP_POS]);
779 if(msg_part_len == 0) {
780 str_cpy(internal_state.neo6_info.pdop, "No data",
781 get_str_len("No data") + 1);
782 }
783 else {
784 str_cpy(internal_state.neo6_info.pdop, tokens[GSA_PDOP_POS],
785 msg_part_len + 1);
786 }
787
788 msg_part_len = get_str_len(tokens[GSA_HDOP_POS]);
789 if(msg_part_len == 0) {
790 str_cpy(internal_state.neo6_info.hdop, "No data",
791 get_str_len("No data") + 1);
792 }
793 else {
794 str_cpy(internal_state.neo6_info.hdop, tokens[GSA_HDOP_POS],
795 msg_part_len + 1);
796 }
797
798 // VDOP: subtract 2 to strip the trailing checksum suffix (*XX)
799 msg_part_len = get_str_len(tokens[GSA_VDOP_POS]) - 2;
800 if(msg_part_len == 0) {
801 str_cpy(internal_state.neo6_info.vdop, "No data",
802 get_str_len("No data") + 1);
803 }
804 else {
805 str_cpy(internal_state.neo6_info.vdop, tokens[GSA_VDOP_POS],
806 msg_part_len);
807 internal_state.neo6_info.vdop[msg_part_len] = '\0';
808 }
809
810 return ERR_OK;
811}
#define GSA_VDOP_POS
Definition neo6.c:59
#define GSA_HDOP_POS
Definition neo6.c:58
#define GSA_FIX_TYPE_POS
Definition neo6.c:55
#define GSA_PDOP_POS
Definition neo6.c:57
Here is the call graph for this function:
Here is the caller graph for this function:

◆ neo6_process_gsv()

ERR_te neo6_process_gsv ( char ** tokens)
static

Parses a GSV sentence and updates the total satellite count.

Only the total number of satellites in view is extracted. The per-satellite detail fields (PRN, elevation, azimuth, SNR) are not currently stored. Empty fields result in "No data".

Parameters
[in]tokensTokenized GSV sentence.
Returns
  • ERR_OK always

Definition at line 826 of file neo6.c.

826 {
827 uint8_t msg_part_len = get_str_len(tokens[GSV_NUM_SATS_VISIBLE_POS]);
828 if(msg_part_len == 0) {
829 str_cpy(internal_state.neo6_info.num_sats_all, "No data",
830 get_str_len("No data") + 1);
831 }
832 else {
833 str_cpy(internal_state.neo6_info.num_sats_all, tokens[GSV_NUM_SATS_VISIBLE_POS],
834 msg_part_len + 1);
835 }
836
837 return ERR_OK;
838}
#define GSV_NUM_SATS_VISIBLE_POS
Definition neo6.c:64
Here is the call graph for this function:
Here is the caller graph for this function: