nmea.c 9.1 KB

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