Merge branch 'master' into skytraq
[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           3
23
24 static const char ao_gps_header[] = "GPG";
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 struct ao_gps_data      ao_gps_data;
32 __xdata struct ao_gps_tracking_data     ao_gps_tracking_data;
33
34 static __xdata struct ao_gps_data               ao_gps_next;
35 static __xdata struct ao_gps_tracking_data      ao_gps_tracking_next;
36
37 static const char ao_gps_config[] = {
38         0xa0, 0xa1, 0x00, 0x09,         /* length 9 bytes */
39         0x08,                           /* configure nmea */
40         1,                              /* gga interval */
41         1,                              /* gsa interval */
42         1,                              /* gsv interval */
43         1,                              /* gll interval */
44         1,                              /* rmc interval */
45         1,                              /* vtg interval */
46         1,                              /* zda interval */
47         0,                              /* attributes (0 = update to sram, 1 = update flash too) */
48         0x09, 0x0d, 0x0a,
49 };
50
51 static void
52 ao_gps_lexchar(void)
53 {
54         if (ao_gps_error)
55                 ao_gps_char = '\n';
56         else
57                 ao_gps_char = ao_serial_getchar();
58         ao_gps_cksum ^= ao_gps_char;
59 }
60
61 void
62 ao_gps_skip(void)
63 {
64         while (ao_gps_char >= '0')
65                 ao_gps_lexchar();
66 }
67
68 void
69 ao_gps_skip_field(void)
70 {
71         while (ao_gps_char != ',' && ao_gps_char != '*' && ao_gps_char != '\n')
72                 ao_gps_lexchar();
73 }
74
75 void
76 ao_gps_skip_sep(void)
77 {
78         if (ao_gps_char == ',' || ao_gps_char == '.' || ao_gps_char == '*')
79                 ao_gps_lexchar();
80 }
81
82 __xdata static uint8_t ao_gps_num_width;
83
84 static int16_t
85 ao_gps_decimal(uint8_t max_width)
86 {
87         int16_t v;
88         __xdata uint8_t neg = 0;
89
90         ao_gps_skip_sep();
91         if (ao_gps_char == '-') {
92                 neg = 1;
93                 ao_gps_lexchar();
94         }
95         v = 0;
96         ao_gps_num_width = 0;
97         while (ao_gps_num_width < max_width) {
98                 if (ao_gps_char < '0' || '9' < ao_gps_char)
99                         break;
100                 v = v * (int16_t) 10 + ao_gps_char - '0';
101                 ao_gps_num_width++;
102                 ao_gps_lexchar();
103         }
104         if (neg)
105                 v = -v;
106         return v;
107 }
108
109 static uint8_t
110 ao_gps_hex(uint8_t max_width)
111 {
112         uint8_t v, d;
113
114         ao_gps_skip_sep();
115         v = 0;
116         ao_gps_num_width = 0;
117         while (ao_gps_num_width < max_width) {
118                 if ('0' <= ao_gps_char && ao_gps_char <= '9')
119                         d = ao_gps_char - '0';
120                 else if ('A' <= ao_gps_char && ao_gps_char <= 'F')
121                         d = ao_gps_char - 'A' + 10;
122                 else if ('a' <= ao_gps_char && ao_gps_char <= 'f')
123                         d = ao_gps_char - 'a' + 10;
124                 else
125                         break;
126                 v = (v << 4) | d;
127                 ao_gps_num_width++;
128                 ao_gps_lexchar();
129         }
130         return v;
131 }
132
133 static int32_t
134 ao_gps_parse_pos(uint8_t deg_width) __reentrant
135 {
136         int32_t d;
137         int32_t m;
138         int32_t f;
139
140         d = ao_gps_decimal(deg_width);
141         m = ao_gps_decimal(2);
142         if (ao_gps_char == '.') {
143                 f = ao_gps_decimal(4);
144                 while (ao_gps_num_width < 4) {
145                         f *= 10;
146                         ao_gps_num_width++;
147                 }
148         } else {
149                 f = 0;
150                 if (ao_gps_char != ',')
151                         ao_gps_error = 1;
152         }
153         d = d * 10000000l;
154         m = m * 10000l + f;
155         d = d + m * 50 / 3;
156         return d;
157 }
158
159 static uint8_t
160 ao_gps_parse_flag(char no_c, char yes_c) __reentrant
161 {
162         uint8_t ret = 0;
163         ao_gps_skip_sep();
164         if (ao_gps_char == yes_c)
165                 ret = 1;
166         else if (ao_gps_char == no_c)
167                 ret = 0;
168         else
169                 ao_gps_error = 1;
170         ao_gps_lexchar();
171         return ret;
172 }
173
174
175 void
176 ao_gps(void) __reentrant
177 {
178         char    c;
179         uint8_t i;
180
181         ao_serial_set_speed(AO_SERIAL_SPEED_9600);
182         for (i = 0; i < sizeof (ao_gps_config); i++)
183                 ao_serial_putchar(ao_gps_config[i]);
184         for (;;) {
185                 /* Locate the begining of the next record */
186                 for (;;) {
187                         c = ao_serial_getchar();
188                         if (c == '$')
189                                 break;
190                 }
191
192                 ao_gps_cksum = 0;
193                 ao_gps_error = 0;
194
195                 /* Skip anything other than GPG */
196                 for (i = 0; i < AO_GPS_LEADER; i++) {
197                         ao_gps_lexchar();
198                         if (ao_gps_char != ao_gps_header[i])
199                                 break;
200                 }
201                 if (i != AO_GPS_LEADER)
202                         continue;
203
204                 /* pull the record identifier characters off the link */
205                 ao_gps_lexchar();
206                 c = ao_gps_char;
207                 ao_gps_lexchar();
208                 i = ao_gps_char;
209                 ao_gps_lexchar();
210                 if (ao_gps_char != ',')
211                         continue;
212
213                 if (c == (uint8_t) 'G' && i == (uint8_t) 'A') {
214                         /* Now read the data into the gps data record
215                          *
216                          * $GPGGA,025149.000,4528.1723,N,12244.2480,W,1,05,2.0,103.5,M,-19.5,M,,0000*66
217                          *
218                          * Essential fix data
219                          *
220                          *         025149.000   time (02:51:49.000 GMT)
221                          *         4528.1723,N  Latitude 45°28.1723' N
222                          *         12244.2480,W Longitude 122°44.2480' W
223                          *         1            Fix quality:
224                          *                                 0 = invalid
225                          *                                 1 = GPS fix (SPS)
226                          *                                 2 = DGPS fix
227                          *                                 3 = PPS fix
228                          *                                 4 = Real Time Kinematic
229                          *                                 5 = Float RTK
230                          *                                 6 = estimated (dead reckoning)
231                          *                                 7 = Manual input mode
232                          *                                 8 = Simulation mode
233                          *         05           Number of satellites (5)
234                          *         2.0          Horizontal dilution
235                          *         103.5,M              Altitude, 103.5M above msl
236                          *         -19.5,M              Height of geoid above WGS84 ellipsoid
237                          *         ?            time in seconds since last DGPS update
238                          *         0000         DGPS station ID
239                          *         *66          checksum
240                          */
241
242                         ao_gps_next.flags = AO_GPS_RUNNING;
243                         ao_gps_next.hour = ao_gps_decimal(2);
244                         ao_gps_next.minute = ao_gps_decimal(2);
245                         ao_gps_next.second = ao_gps_decimal(2);
246                         ao_gps_skip_field();    /* skip seconds fraction */
247
248                         ao_gps_next.latitude = ao_gps_parse_pos(2);
249                         if (ao_gps_parse_flag('N', 'S'))
250                                 ao_gps_next.latitude = -ao_gps_next.latitude;
251                         ao_gps_next.longitude = ao_gps_parse_pos(3);
252                         if (ao_gps_parse_flag('E', 'W'))
253                                 ao_gps_next.longitude = -ao_gps_next.longitude;
254
255                         i = ao_gps_decimal(0xff);
256                         if (i == 1)
257                                 ao_gps_next.flags |= AO_GPS_VALID;
258
259                         i = ao_gps_decimal(0xff) << AO_GPS_NUM_SAT_SHIFT;
260                         if (i > AO_GPS_NUM_SAT_MASK)
261                                 i = AO_GPS_NUM_SAT_MASK;
262                         ao_gps_next.flags |= i;
263
264                         ao_gps_lexchar();
265                         ao_gps_skip_field();    /* Horizontal dilution */
266
267                         ao_gps_next.altitude = ao_gps_decimal(0xff);
268                         ao_gps_skip_field();    /* skip any fractional portion */
269
270                         /* Skip remaining fields */
271                         while (ao_gps_char != '*' && ao_gps_char != '\n' && ao_gps_char != '\r') {
272                                 ao_gps_lexchar();
273                                 ao_gps_skip_field();
274                         }
275                         if (ao_gps_char == '*') {
276                                 uint8_t cksum = ao_gps_cksum ^ '*';
277                                 if (cksum != ao_gps_hex(2))
278                                         ao_gps_error = 1;
279                         } else
280                                 ao_gps_error = 1;
281                         if (!ao_gps_error) {
282                                 ao_mutex_get(&ao_gps_mutex);
283                                 memcpy(&ao_gps_data, &ao_gps_next, sizeof (struct ao_gps_data));
284                                 ao_mutex_put(&ao_gps_mutex);
285                                 ao_wakeup(&ao_gps_data);
286                         }
287                 } else if (c == (uint8_t) 'S' && i == (uint8_t) 'V') {
288                         uint8_t done;
289                         /* Now read the data into the GPS tracking data record
290                          *
291                          * $GPGSV,3,1,12,05,54,069,45,12,44,061,44,21,07,184,46,22,78,289,47*72<CR><LF>
292                          *
293                          * Satellites in view data
294                          *
295                          *      3               Total number of GSV messages
296                          *      1               Sequence number of current GSV message
297                          *      12              Total sats in view (0-12)
298                          *      05              SVID
299                          *      54              Elevation
300                          *      069             Azimuth
301                          *      45              C/N0 in dB
302                          *      ...             other SVIDs
303                          *      72              checksum
304                          */
305                         c = ao_gps_decimal(1);  /* total messages */
306                         i = ao_gps_decimal(1);  /* message sequence */
307                         if (i == 1) {
308                                 ao_gps_tracking_next.channels = 0;
309                         }
310                         done = (uint8_t) c == i;
311                         ao_gps_lexchar();
312                         ao_gps_skip_field();    /* sats in view */
313                         while (ao_gps_char != '*' && ao_gps_char != '\n' && ao_gps_char != '\r') {
314                                 i = ao_gps_tracking_next.channels;
315                                 ao_gps_tracking_next.sats[i].svid = ao_gps_decimal(2);  /* SVID */
316                                 ao_gps_lexchar();
317                                 ao_gps_skip_field();    /* elevation */
318                                 ao_gps_lexchar();
319                                 ao_gps_skip_field();    /* azimuth */
320                                 if (ao_gps_tracking_next.sats[i].c_n_1 = ao_gps_decimal(2))     /* C/N0 */
321                                         ao_gps_tracking_next.sats[i].state = 0xbf;
322                                 else
323                                         ao_gps_tracking_next.sats[i].state = 0;
324                                 ao_gps_tracking_next.channels = i + 1;
325                         }
326                         if (ao_gps_char == '*') {
327                                 uint8_t cksum = ao_gps_cksum ^ '*';
328                                 if (cksum != ao_gps_hex(2))
329                                         ao_gps_error = 1;
330                         }
331                         else
332                                 ao_gps_error = 1;
333                         if (ao_gps_error)
334                                 ao_gps_tracking_next.channels = 0;
335                         else if (done) {
336                                 ao_mutex_get(&ao_gps_mutex);
337                                 memcpy(&ao_gps_tracking_data, &ao_gps_tracking_next,
338                                        sizeof(ao_gps_tracking_data));
339                                 ao_mutex_put(&ao_gps_mutex);
340                                 ao_wakeup(&ao_gps_tracking_data);
341                         }
342                 }
343         }
344 }
345
346 __xdata struct ao_task ao_gps_task;
347
348 static void
349 gps_dump(void) __reentrant
350 {
351         ao_mutex_get(&ao_gps_mutex);
352         ao_gps_print(&ao_gps_data);
353         putchar('\n');
354         ao_gps_tracking_print(&ao_gps_tracking_data);
355         putchar('\n');
356         ao_mutex_put(&ao_gps_mutex);
357 }
358
359 __code struct ao_cmds ao_gps_cmds[] = {
360         { 'g', gps_dump,        "g                                  Display current GPS values" },
361         { 0, gps_dump, NULL },
362 };
363
364 void
365 ao_gps_init(void)
366 {
367         ao_add_task(&ao_gps_task, ao_gps, "gps");
368         ao_cmd_register(&ao_gps_cmds[0]);
369 }