nmea.c 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. #include <avr/wdt.h>
  2. #include <stddef.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <avr/sleep.h>
  6. #include "nmea.h"
  7. #include "main.h"
  8. #include "uart0.h"
  9. #include "uart1.h"
  10. #include "xprintf.h"
  11. /*----------------------------------------------------*/
  12. /* Get a line received from GPS module */
  13. /*----------------------------------------------------*/
  14. UINT get_line ( /* 0:Brownout or timeout, >0: Number of bytes received. */
  15. char *buff,
  16. UINT sz_buf
  17. )
  18. {
  19. char c;
  20. UINT i = 0;
  21. set_timer(recv_timeout, 1000);
  22. for (;;) {
  23. wdt_reset();
  24. if (FLAGS & (F_LVD | F_POWEROFF))
  25. return 0; /* A brownout is detected */
  26. if (timer_expired(recv_timeout))
  27. return 0; /* timeout; continue the main loop */
  28. if (!uart0_test()) {
  29. sleep();
  30. continue;
  31. }
  32. c = (char)uart0_get();
  33. if (i == 0 && c != '$' )
  34. continue; /* Find start of line */
  35. if (c == '\n' || c == '\r') {
  36. buff[i++] = '\0'; /* add null termination for string */
  37. break; /* EOL */
  38. }
  39. buff[i++] = c;
  40. uart1_put(c);
  41. if (i >= sz_buf)
  42. i = 0; /* Buffer overflow (abort this line) */
  43. }
  44. uart1_put('\r');
  45. uart1_put('\n');
  46. return i;
  47. }
  48. /* Compare sentence header string */
  49. BYTE gp_comp (const char *str1, __flash const char *str2)
  50. {
  51. char c;
  52. do {
  53. c = pgm_read_byte(str2++);
  54. } while (c && c == *str1++);
  55. return c;
  56. }
  57. #define FIELD_BUF_LEN 32
  58. /* Get a column item */
  59. static
  60. const char* gp_col ( /* Returns pointer to the item (returns a NULL when not found) */
  61. const char* buf, /* Pointer to the sentence */
  62. BYTE col /* Column number (0 is the 1st item) */
  63. ) {
  64. BYTE c;
  65. static char field_buf[FIELD_BUF_LEN];
  66. unsigned char length = 0;
  67. while (col) {
  68. do {
  69. c = *buf++;
  70. if (c <= ' ') return NULL;
  71. } while (c != ',');
  72. col--;
  73. }
  74. while (*buf && *buf != ',' && length < FIELD_BUF_LEN-1) {
  75. field_buf[length++] = *buf++;
  76. }
  77. field_buf[length] = '\0';
  78. return field_buf;
  79. }
  80. unsigned int gp_val(const char *db, unsigned char count) {
  81. unsigned int out = 0;
  82. while (count--) {
  83. unsigned char n;
  84. n = *db - '0';
  85. if (n >= 10)
  86. return 0;
  87. out *= 10;
  88. out += n;
  89. db++;
  90. }
  91. return out;
  92. }
  93. static time_t gp_rmc_parse(const char *str) {
  94. const char *p;
  95. struct tm tmc;
  96. p = gp_col(str, 1); /* Get h:m:s */
  97. if (!p)
  98. return 0;
  99. tmc.tm_hour = gp_val(p, 2);
  100. tmc.tm_min = gp_val(p+2, 2);
  101. tmc.tm_sec = gp_val(p+4, 2);
  102. p = gp_col(str, 9); /* Get y:m:d */
  103. if (!p)
  104. return 0;
  105. tmc.tm_mday = gp_val(p, 2);
  106. tmc.tm_mon = gp_val(p+2, 2) - 1;
  107. tmc.tm_year = gp_val(p+4, 2) + 100;
  108. utc = mktime(&tmc); /* Check time validity */
  109. if (utc == -1)
  110. return 0;
  111. p = gp_col(str, 2); /* Get status */
  112. if (!p || *p != 'A') {
  113. System.location_valid = LOC_INVALID;
  114. FLAGS &= ~F_GPSOK;
  115. return 0; /* Return 0 even is time is valid (comes from module's internal RTC) */
  116. }
  117. FLAGS |= F_GPSOK;
  118. return utc;
  119. }
  120. static void gp_gga_parse(const char *str) {
  121. const char *p;
  122. double tmp;
  123. p = gp_col(str, 7); /* satellites used */
  124. System.satellites_used = atoi(p);
  125. /* check validity */
  126. p = gp_col(str, 6);
  127. if (*p == '0') {
  128. System.location_valid = LOC_INVALID;
  129. return;
  130. }
  131. System.location_valid = LOC_VALID_NEW;
  132. /* parse location */
  133. p = gp_col(str, 2); /* latitude */
  134. location.lat = gp_val(p, 2); /* degrees */
  135. p += 2;
  136. xatof(&p, &tmp); /* minutes */
  137. tmp /= 60; /* convert minutes to degrees */
  138. location.lat += tmp;
  139. p = gp_col(str, 3); /* N/S */
  140. if (*p != 'N')
  141. location.lat = -location.lat;
  142. p = gp_col(str, 4); /* longitude */
  143. location.lon = gp_val(p, 3); /* degrees */
  144. p += 3;
  145. xatof(&p, &tmp); /* minutes */
  146. tmp /= 60; /* convert minutes to degrees */
  147. location.lon += tmp;
  148. p = gp_col(str, 5); /* E/W */
  149. if (*p != 'E')
  150. location.lon = -location.lon;
  151. p = gp_col(str, 6); /* fix type */
  152. if (*p == '2')
  153. System.sbas = 1;
  154. else
  155. System.sbas = 0;
  156. p = gp_col(str, 9); /* MSL altitude */
  157. xatof(&p, &tmp);
  158. location.alt = tmp;
  159. location.time = utc; /* parsed from RMC */
  160. }
  161. /*$PMTK355*31<CR><LF>
  162. Return $PMTK001,355,3,1,0,0*2E “$PMTK001,355,3,GLON_Enable,BEIDOU_Enable,GALILEO_Enable”
  163. The GLONASS search mode is enabled. */
  164. static void pmtk001_parse(const char *str) {
  165. const char *p;
  166. /* check validity */
  167. p = gp_col(str, 1);
  168. if (strcmp_P(p, PSTR("355"))) /* not the PMTK355 reply */
  169. return;
  170. p = gp_col(str, 2);
  171. if (*p == '0' || *p == '1') { /* invalid / unsupported */
  172. System.gps_only = 1;
  173. xputs_P(PSTR("GPS only\r\n"));
  174. } else {
  175. System.gps_only = 0;
  176. xputs_P(PSTR("Multi GNSS\r\n"));
  177. }
  178. gps_initialize();
  179. }
  180. unsigned char nmea_checksum(const char *str) {
  181. unsigned char cs = 0;
  182. while (*str) {
  183. cs ^= *str++; /* NMEA checksum is quite primitive, but still should catch simple bitstream errors or truncated lines */
  184. }
  185. return cs;
  186. }
  187. time_t gps_parse(char *str) { /* Get all required data from NMEA sentences */
  188. unsigned int len = strlen(str);
  189. const char *checksum;
  190. unsigned char calc_checksum, inc_checksum;
  191. unsigned int i;
  192. char c;
  193. if (len < 4)
  194. return 0; /* each message must contain $.*xx where . is one or more actual data characters and xx is the checksum */
  195. for (i = len-1; i && i >= (len-2); i--) { /* find checksum */
  196. if (str[i] == '*')
  197. break;
  198. }
  199. checksum = str+i+1; /* skip * character */
  200. inc_checksum = 0; /* parse hex string */
  201. while (*checksum) {
  202. inc_checksum *= 16;
  203. c = *checksum++;
  204. if (c >= '0' && c <= '9') {
  205. inc_checksum += c - '0';
  206. } else if (c >= 'a' && c <= 'f') {
  207. inc_checksum += c - 'a' + 10;
  208. } else if (c >= 'A' && c <= 'F') {
  209. inc_checksum += c - 'A' + 10;
  210. } else {
  211. xputs_P(PSTR("Invalid NMEA: malformed checksum\r\n"));
  212. return 0; /* invalid checksum character */
  213. }
  214. }
  215. str[i] = '\0'; /* drop checksum (*xx) and initial $ */
  216. str++;
  217. calc_checksum = nmea_checksum(str);
  218. if (inc_checksum != calc_checksum) {
  219. xputs_P(PSTR("Invalid NMEA checksum received\r\n"));
  220. return 0;
  221. }
  222. if (!gp_comp(str, PSTR("GPRMC")) || !gp_comp(str, PSTR("GNRMC")) || !gp_comp(str, PSTR("BDRMC")) || !gp_comp(str, PSTR("GARMC"))) {
  223. return gp_rmc_parse(str);
  224. }
  225. if (!gp_comp(str, PSTR("GPGGA")) || !gp_comp(str, PSTR("GNGGA")) || !gp_comp(str, PSTR("BDGGA")) || !gp_comp(str, PSTR("GAGGA"))) {
  226. gp_gga_parse(str);
  227. return 0;
  228. }
  229. if (!System.gps_initialized && !gp_comp(str, PSTR("PMTK011"))) {
  230. gps_initialize();
  231. return 0;
  232. }
  233. if (!gp_comp(str, PSTR("PMTK001"))) {
  234. pmtk001_parse(str);
  235. return 0;
  236. }
  237. return 0;
  238. }
  239. void uart0_put_wrap(int c) {
  240. uart0_put((char)c);
  241. }
  242. void gps_initialize(void) {
  243. /*
  244. * PMTK355: query gnss search mode (will fail if only GPS is supported)
  245. * PMTK353: set gnss search mode (GPS/Galileo/Glonass/Beidou)
  246. * PMTK313: enable SBAS
  247. */
  248. if (!System.gps_initialized)
  249. xfprintf(uart0_put_wrap, PSTR("$PMTK355*31\r\n"));
  250. if (get_flag(CONFFLAG_ENABLE_SBAS))
  251. xfprintf(uart0_put_wrap, PSTR("$PMTK313,1*2E\r\n"));
  252. else
  253. xfprintf(uart0_put_wrap, PSTR("$PMTK313,0*2F\r\n"));
  254. if (!System.gps_only) {
  255. switch (System.conf.gnss_mode) {
  256. default:
  257. case GNSS_MODE_GPS_GLONASS_GALILEO:
  258. xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,1,1,0,0*2A\r\n"));
  259. break;
  260. case GNSS_MODE_GPS:
  261. xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,0,0,0*2A\r\n"));
  262. break;
  263. case GNSS_MODE_GPS_GALILEO:
  264. xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,1,0,0*2B\r\n"));
  265. break;
  266. case GNSS_MODE_GALILEO:
  267. xfprintf(uart0_put_wrap, PSTR("$PMTK353,0,0,1,0,0*2A\r\n"));
  268. break;
  269. case GNSS_MODE_GPS_BEIDOU:
  270. xfprintf(uart0_put_wrap, PSTR("$PMTK353,1,0,0,0,1*2B\r\n"));
  271. break;
  272. case GNSS_MODE_BEIDOU:
  273. xfprintf(uart0_put_wrap, PSTR("$PMTK353,0,0,0,0,1*2A\r\n"));
  274. break;
  275. }
  276. } else {
  277. System.conf.gnss_mode = GNSS_MODE_GPS;
  278. }
  279. xputs_P(PSTR("GPS init sent\r\n"));
  280. System.gps_initialized = 1;
  281. }