a2d5f1dbe9cd9e988e5851fada1211a815ace17b
[fw/altos] / src / ao_gps_skytraq.c
1 /*
2  * Copyright © 2009 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #ifndef AO_GPS_TEST
19 #include "ao.h"
20 #endif
21
22 #define AO_GPS_LEADER           2
23
24 static const char ao_gps_header[] = "GP";
25
26 __xdata uint8_t ao_gps_mutex;
27 static __xdata char ao_gps_char;
28 static __xdata uint8_t ao_gps_cksum;
29 static __xdata uint8_t ao_gps_error;
30
31 __xdata uint16_t ao_gps_tick;
32 __xdata struct ao_gps_data      ao_gps_data;
33 __xdata struct ao_gps_tracking_data     ao_gps_tracking_data;
34
35 static __xdata uint16_t                         ao_gps_next_tick;
36 static __xdata struct ao_gps_data               ao_gps_next;
37 static __xdata uint8_t                          ao_gps_date_flags;
38 static __xdata struct ao_gps_tracking_data      ao_gps_tracking_next;
39
40 static const char ao_gps_config[] = {
41         0xa0, 0xa1, 0x00, 0x09,         /* length 9 bytes */
42         0x08,                           /* configure nmea */
43         1,                              /* gga interval */
44         1,                              /* gsa interval */
45         1,                              /* gsv interval */
46         1,                              /* gll interval */
47         1,                              /* rmc interval */
48         1,                              /* vtg interval */
49         1,                              /* zda interval */
50         0,                              /* attributes (0 = update to sram, 1 = update flash too) */
51         0x09, 0x0d, 0x0a,
52
53         0xa0, 0xa1, 0x00, 0x03,         /* length: 3 bytes */
54         0x3c,                           /* configure navigation mode */
55         0x00,                           /* 0 = car, 1 = pedestrian */
56         0x00,                           /* 0 = update to sram, 1 = update sram + flash */
57         0x3c, 0x0d, 0x0a,
58 };
59
60 static void
61 ao_gps_lexchar(void)
62 {
63         if (ao_gps_error)
64                 ao_gps_char = '\n';
65         else
66                 ao_gps_char = ao_serial_getchar();
67         ao_gps_cksum ^= ao_gps_char;
68 }
69
70 void
71 ao_gps_skip(void)
72 {
73         while (ao_gps_char >= '0')
74                 ao_gps_lexchar();
75 }
76
77 void
78 ao_gps_skip_field(void)
79 {
80         while (ao_gps_char != ',' && ao_gps_char != '*' && ao_gps_char != '\n')
81                 ao_gps_lexchar();
82 }
83
84 void
85 ao_gps_skip_sep(void)
86 {
87         if (ao_gps_char == ',' || ao_gps_char == '.' || ao_gps_char == '*')
88                 ao_gps_lexchar();
89 }
90
91 __xdata static uint8_t ao_gps_num_width;
92
93 static int16_t
94 ao_gps_decimal(uint8_t max_width)
95 {
96         int16_t v;
97         __xdata uint8_t neg = 0;
98
99         ao_gps_skip_sep();
100         if (ao_gps_char == '-') {
101                 neg = 1;
102                 ao_gps_lexchar();
103         }
104         v = 0;
105         ao_gps_num_width = 0;
106         while (ao_gps_num_width < max_width) {
107                 if (ao_gps_char < '0' || '9' < ao_gps_char)
108                         break;
109                 v = v * (int16_t) 10 + ao_gps_char - '0';
110                 ao_gps_num_width++;
111                 ao_gps_lexchar();
112         }
113         if (neg)
114                 v = -v;
115         return v;
116 }
117
118 static uint8_t
119 ao_gps_hex(uint8_t max_width)
120 {
121         uint8_t v, d;
122
123         ao_gps_skip_sep();
124         v = 0;
125         ao_gps_num_width = 0;
126         while (ao_gps_num_width < max_width) {
127                 if ('0' <= ao_gps_char && ao_gps_char <= '9')
128                         d = ao_gps_char - '0';
129                 else if ('A' <= ao_gps_char && ao_gps_char <= 'F')
130                         d = ao_gps_char - 'A' + 10;
131                 else if ('a' <= ao_gps_char && ao_gps_char <= 'f')
132                         d = ao_gps_char - 'a' + 10;
133                 else
134                         break;
135                 v = (v << 4) | d;
136                 ao_gps_num_width++;
137                 ao_gps_lexchar();
138         }
139         return v;
140 }
141
142 static int32_t
143 ao_gps_parse_pos(uint8_t deg_width) __reentrant
144 {
145         int32_t d;
146         int32_t m;
147         int32_t f;
148
149         d = ao_gps_decimal(deg_width);
150         m = ao_gps_decimal(2);
151         if (ao_gps_char == '.') {
152                 f = ao_gps_decimal(4);
153                 while (ao_gps_num_width < 4) {
154                         f *= 10;
155                         ao_gps_num_width++;
156                 }
157         } else {
158                 f = 0;
159                 if (ao_gps_char != ',')
160                         ao_gps_error = 1;
161         }
162         d = d * 10000000l;
163         m = m * 10000l + f;
164         d = d + m * 50 / 3;
165         return d;
166 }
167
168 static uint8_t
169 ao_gps_parse_flag(char no_c, char yes_c) __reentrant
170 {
171         uint8_t ret = 0;
172         ao_gps_skip_sep();
173         if (ao_gps_char == yes_c)
174                 ret = 1;
175         else if (ao_gps_char == no_c)
176                 ret = 0;
177         else
178                 ao_gps_error = 1;
179         ao_gps_lexchar();
180         return ret;
181 }
182
183 static void
184 ao_nmea_gga()
185 {
186         uint8_t i;
187
188         /* Now read the data into the gps data record
189          *
190          * $GPGGA,025149.000,4528.1723,N,12244.2480,W,1,05,2.0,103.5,M,-19.5,M,,0000*66
191          *
192          * Essential fix data
193          *
194          *         025149.000   time (02:51:49.000 GMT)
195          *         4528.1723,N  Latitude 45°28.1723' N
196          *         12244.2480,W Longitude 122°44.2480' W
197          *         1            Fix quality:
198          *                                 0 = invalid
199          *                                 1 = GPS fix (SPS)
200          *                                 2 = DGPS fix
201          *                                 3 = PPS fix
202          *                                 4 = Real Time Kinematic
203          *                                 5 = Float RTK
204          *                                 6 = estimated (dead reckoning)
205          *                                 7 = Manual input mode
206          *                                 8 = Simulation mode
207          *         05           Number of satellites (5)
208          *         2.0          Horizontal dilution
209          *         103.5,M              Altitude, 103.5M above msl
210          *         -19.5,M              Height of geoid above WGS84 ellipsoid
211          *         ?            time in seconds since last DGPS update
212          *         0000         DGPS station ID
213          *         *66          checksum
214          */
215
216         ao_gps_next_tick = ao_time();
217         ao_gps_next.flags = AO_GPS_RUNNING | ao_gps_date_flags;
218         ao_gps_next.hour = ao_gps_decimal(2);
219         ao_gps_next.minute = ao_gps_decimal(2);
220         ao_gps_next.second = ao_gps_decimal(2);
221         ao_gps_skip_field();    /* skip seconds fraction */
222
223         ao_gps_next.latitude = ao_gps_parse_pos(2);
224         if (ao_gps_parse_flag('N', 'S'))
225                 ao_gps_next.latitude = -ao_gps_next.latitude;
226         ao_gps_next.longitude = ao_gps_parse_pos(3);
227         if (ao_gps_parse_flag('E', 'W'))
228                 ao_gps_next.longitude = -ao_gps_next.longitude;
229
230         i = ao_gps_decimal(0xff);
231         if (i == 1)
232                 ao_gps_next.flags |= AO_GPS_VALID;
233
234         i = ao_gps_decimal(0xff) << AO_GPS_NUM_SAT_SHIFT;
235         if (i > AO_GPS_NUM_SAT_MASK)
236                 i = AO_GPS_NUM_SAT_MASK;
237         ao_gps_next.flags |= i;
238
239         ao_gps_lexchar();
240         ao_gps_next.hdop = ao_gps_decimal(0xff);
241         if (ao_gps_next.hdop <= 50) {
242                 ao_gps_next.hdop = (uint8_t) 5 * ao_gps_next.hdop;
243                 if (ao_gps_char == '.')
244                         ao_gps_next.hdop = (ao_gps_next.hdop +
245                                             ((uint8_t) ao_gps_decimal(1) >> 1));
246         } else
247                 ao_gps_next.hdop = 255;
248         ao_gps_skip_field();
249
250         ao_gps_next.altitude = ao_gps_decimal(0xff);
251         ao_gps_skip_field();    /* skip any fractional portion */
252
253         /* Skip remaining fields */
254         while (ao_gps_char != '*' && ao_gps_char != '\n' && ao_gps_char != '\r') {
255                 ao_gps_lexchar();
256                 ao_gps_skip_field();
257         }
258         if (ao_gps_char == '*') {
259                 uint8_t cksum = ao_gps_cksum ^ '*';
260                 if (cksum != ao_gps_hex(2))
261                         ao_gps_error = 1;
262         } else
263                 ao_gps_error = 1;
264         if (!ao_gps_error) {
265                 ao_mutex_get(&ao_gps_mutex);
266                 ao_gps_tick = ao_gps_next_tick;
267                 memcpy(&ao_gps_data, &ao_gps_next, sizeof (struct ao_gps_data));
268                 ao_mutex_put(&ao_gps_mutex);
269                 ao_wakeup(&ao_gps_data);
270         }
271 }
272
273 static void
274 ao_nmea_gsv(void)
275 {
276         char    c;
277         uint8_t i;
278         uint8_t done;
279         /* Now read the data into the GPS tracking data record
280          *
281          * $GPGSV,3,1,12,05,54,069,45,12,44,061,44,21,07,184,46,22,78,289,47*72<CR><LF>
282          *
283          * Satellites in view data
284          *
285          *      3               Total number of GSV messages
286          *      1               Sequence number of current GSV message
287          *      12              Total sats in view (0-12)
288          *      05              SVID
289          *      54              Elevation
290          *      069             Azimuth
291          *      45              C/N0 in dB
292          *      ...             other SVIDs
293          *      72              checksum
294          */
295         c = ao_gps_decimal(1);  /* total messages */
296         i = ao_gps_decimal(1);  /* message sequence */
297         if (i == 1) {
298                 ao_gps_tracking_next.channels = 0;
299         }
300         done = (uint8_t) c == i;
301         ao_gps_lexchar();
302         ao_gps_skip_field();    /* sats in view */
303         while (ao_gps_char != '*' && ao_gps_char != '\n' && ao_gps_char != '\r') {
304                 i = ao_gps_tracking_next.channels;
305                 c = ao_gps_decimal(2);  /* SVID */
306                 if (i < AO_MAX_GPS_TRACKING)
307                         ao_gps_tracking_next.sats[i].svid = c;
308                 ao_gps_lexchar();
309                 ao_gps_skip_field();    /* elevation */
310                 ao_gps_lexchar();
311                 ao_gps_skip_field();    /* azimuth */
312                 c = ao_gps_decimal(2);  /* C/N0 */
313                 if (i < AO_MAX_GPS_TRACKING) {
314                         if (!(ao_gps_tracking_next.sats[i].c_n_1 = c))
315                                 ao_gps_tracking_next.sats[i].svid = 0;
316                         ao_gps_tracking_next.channels = i + 1;
317                 }
318         }
319         if (ao_gps_char == '*') {
320                 uint8_t cksum = ao_gps_cksum ^ '*';
321                 if (cksum != ao_gps_hex(2))
322                         ao_gps_error = 1;
323         }
324         else
325                 ao_gps_error = 1;
326         if (ao_gps_error)
327                 ao_gps_tracking_next.channels = 0;
328         else if (done) {
329                 ao_mutex_get(&ao_gps_mutex);
330                 memcpy(&ao_gps_tracking_data, &ao_gps_tracking_next,
331                        sizeof(ao_gps_tracking_data));
332                 ao_mutex_put(&ao_gps_mutex);
333                 ao_wakeup(&ao_gps_tracking_data);
334         }
335 }
336
337 static void
338 ao_nmea_rmc(void)
339 {
340         char    a, c;
341         uint8_t i;
342         /* Parse the RMC record to read out the current date */
343
344         /* $GPRMC,111636.932,A,2447.0949,N,12100.5223,E,000.0,000.0,030407,,,A*61
345          *
346          * Recommended Minimum Specific GNSS Data
347          *
348          *      111636.932      UTC time 11:16:36.932
349          *      A               Data Valid (V = receiver warning)
350          *      2447.0949       Latitude
351          *      N               North/south indicator
352          *      12100.5223      Longitude
353          *      E               East/west indicator
354          *      000.0           Speed over ground
355          *      000.0           Course over ground
356          *      030407          UTC date (ddmmyy format)
357          *      A               Mode indicator:
358          *                      N = data not valid
359          *                      A = autonomous mode
360          *                      D = differential mode
361          *                      E = estimated (dead reckoning) mode
362          *                      M = manual input mode
363          *                      S = simulator mode
364          *      61              checksum
365          */
366         ao_gps_skip_field();
367         for (i = 0; i < 8; i++) {
368                 ao_gps_lexchar();
369                 ao_gps_skip_field();
370         }
371         a = ao_gps_decimal(2);
372         c = ao_gps_decimal(2);
373         i = ao_gps_decimal(2);
374         /* Skip remaining fields */
375         while (ao_gps_char != '*' && ao_gps_char != '\n' && ao_gps_char != '\r') {
376                 ao_gps_lexchar();
377                 ao_gps_skip_field();
378         }
379         if (ao_gps_char == '*') {
380                 uint8_t cksum = ao_gps_cksum ^ '*';
381                 if (cksum != ao_gps_hex(2))
382                         ao_gps_error = 1;
383         } else
384                 ao_gps_error = 1;
385         if (!ao_gps_error) {
386                 ao_gps_next.year = i;
387                 ao_gps_next.month = c;
388                 ao_gps_next.day = a;
389                 ao_gps_date_flags = AO_GPS_DATE_VALID;
390         }
391 }
392
393 void
394 ao_gps(void) __reentrant
395 {
396         char    a, c;
397         uint8_t i;
398
399         ao_serial_set_speed(AO_SERIAL_SPEED_9600);
400         for (i = 0; i < sizeof (ao_gps_config); i++)
401                 ao_serial_putchar(ao_gps_config[i]);
402         for (;;) {
403                 /* Locate the begining of the next record */
404                 for (;;) {
405                         c = ao_serial_getchar();
406                         if (c == '$')
407                                 break;
408                 }
409
410                 ao_gps_cksum = 0;
411                 ao_gps_error = 0;
412
413                 /* Skip anything other than GP */
414                 for (i = 0; i < AO_GPS_LEADER; i++) {
415                         ao_gps_lexchar();
416                         if (ao_gps_char != ao_gps_header[i])
417                                 break;
418                 }
419                 if (i != AO_GPS_LEADER)
420                         continue;
421
422                 /* pull the record identifier characters off the link */
423                 ao_gps_lexchar();
424                 a = ao_gps_char;
425                 ao_gps_lexchar();
426                 c = ao_gps_char;
427                 ao_gps_lexchar();
428                 i = ao_gps_char;
429                 ao_gps_lexchar();
430                 if (ao_gps_char != ',')
431                         continue;
432
433                 if (a == (uint8_t) 'G' && c == (uint8_t) 'G' && i == (uint8_t) 'A') {
434                         ao_nmea_gga();
435                 } else if (a == (uint8_t) 'G' && c == (uint8_t) 'S' && i == (uint8_t) 'V') {
436                         ao_nmea_gsv();
437                 } else if (a == (uint8_t) 'R' && c == (uint8_t) 'M' && i == (uint8_t) 'C') {
438                         ao_nmea_rmc();
439                 }
440         }
441 }
442
443 __xdata struct ao_task ao_gps_task;
444
445 static void
446 gps_dump(void) __reentrant
447 {
448         ao_mutex_get(&ao_gps_mutex);
449         printf ("Date: %02d/%02d/%02d\n", ao_gps_data.year, ao_gps_data.month, ao_gps_data.day);
450         printf ("Time: %02d:%02d:%02d\n", ao_gps_data.hour, ao_gps_data.minute, ao_gps_data.second);
451         printf ("Lat/Lon: %ld %ld\n", ao_gps_data.latitude, ao_gps_data.longitude);
452         printf ("Alt: %d\n", ao_gps_data.altitude);
453         printf ("Flags: 0x%x\n", ao_gps_data.flags);
454         ao_mutex_put(&ao_gps_mutex);
455 }
456
457 __code struct ao_cmds ao_gps_cmds[] = {
458         { 'g', gps_dump,        "g                                  Display current GPS values" },
459         { 0,   gps_dump, NULL },
460 };
461
462 void
463 ao_gps_init(void)
464 {
465         ao_add_task(&ao_gps_task, ao_gps, "gps");
466         ao_cmd_register(&ao_gps_cmds[0]);
467 }