blob: c1ad3a8990ddf1f116ac6d0e81f5955c9b7250fd [file] [log] [blame]
Sylvain Munautef5fe382022-01-12 11:55:44 +01001/*
2 * gps.c
3 *
4 * Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
5 * SPDX-License-Identifier: GPL-3.0-or-later
6 */
7
8#include <stddef.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <string.h>
12
13#include "console.h"
14#include "gps.h"
15#include "misc.h"
Sylvain Munaut70c10f02022-01-12 11:58:34 +010016#include "usb_gps.h"
Sylvain Munautef5fe382022-01-12 11:55:44 +010017#include "utils.h"
18
19#include "config.h"
20
21
22struct gps_uart {
23 uint32_t data;
24 uint32_t csr;
25} __attribute__((packed,aligned(4)));
26
27#define GPS_UART_CSR_DIV(baud) ((SYS_CLK_FREQ+(baud)/2)/(baud)-2)
28#define GPS_UART_CSR_TX_EMPTY (1 << 29)
29#define GPS_UART_DATA_EMPTY (1 << 31)
30
31static volatile struct gps_uart * const gps_uart_regs = (void*)(GPS_UART_BASE);
32
33
34static struct {
35 /* NMEA rx state */
36 struct {
37 enum {
38 GNS_WAIT = 0,
39 GNS_READ = 1,
40 GNS_CK_HI = 2,
41 GNS_CK_LO = 3,
42 GNS_END_CR = 4,
43 GNS_END_LF = 5,
44 } state;
45
46 int len;
47 uint8_t cksum;
48 char buf[95]; /* Max length is actually 82 + 1 (\0) */
49 } nmea;
50
51 /* Current lock state */
52 struct {
53 bool valid;
54 } fix;
55
56 /* Reported antenna state */
57 enum gps_antenna_state antenna;
58} g_gps;
59
60
61static void
62_gps_empty(bool wait_eol)
63{
64 bool eol = false;
65
66 uint32_t c;
67 while (1) {
68 c = gps_uart_regs->data;
69 if (c & GPS_UART_DATA_EMPTY) {
70 if (!wait_eol || eol)
71 break;
72 } else {
73 eol = (c == '\n');
74 }
75 }
76}
77
78static void
79_gps_send(const char *s)
80{
81 char cksum = 0;
82
83 /* Start sentence */
84 gps_uart_regs->data = '$';
85
86 /* Send payload */
87 while (*s)
88 cksum ^= (gps_uart_regs->data = *s++);
89
90 /* Send checksum */
91 gps_uart_regs->data = '*';
92
93 s = hexstr(&cksum, 1, false);
94 gps_uart_regs->data = *s++;
95 gps_uart_regs->data = *s++;
96
97 gps_uart_regs->data = '\r';
98 gps_uart_regs->data = '\n';
99}
100
101static const char *
102_gps_query(void)
103{
104 uint32_t c;
105 while (1) {
106 /* Get next char */
107 c = gps_uart_regs->data;
108 if (c & GPS_UART_DATA_EMPTY)
109 break;
110
111 /* Always store it */
112 if (g_gps.nmea.len == sizeof(g_gps.nmea.buf))
113 g_gps.nmea.state = GNS_WAIT;
114 else
115 g_gps.nmea.buf[g_gps.nmea.len++] = c;
116
117 /* State */
118 if (c == '$') {
119 /* '$' always triggers reset */
120 g_gps.nmea.state = GNS_READ;
121 g_gps.nmea.cksum = 0;
122 g_gps.nmea.len = 1;
123 g_gps.nmea.buf[0] = '$';
124 } else {
125 switch (g_gps.nmea.state) {
126 case GNS_READ:
127 if (c == '*')
128 g_gps.nmea.state = GNS_CK_HI;
129 else
130 g_gps.nmea.cksum ^= c;
131 break;
132 case GNS_CK_HI:
133 g_gps.nmea.cksum ^= hexval(c) << 4;
134 g_gps.nmea.state = GNS_CK_LO;
135 break;
136 case GNS_CK_LO:
137 g_gps.nmea.cksum ^= hexval(c);
138 g_gps.nmea.state = GNS_END_CR;
139 break;
140 case GNS_END_CR:
141 g_gps.nmea.state = (c == '\r') ? GNS_END_LF : GNS_WAIT;
142 break;
143 case GNS_END_LF:
144 g_gps.nmea.state = GNS_WAIT;
145 g_gps.nmea.buf[g_gps.nmea.len] = 0x00;
146 if (c == '\n')
147 return g_gps.nmea.buf;
148 break;
149 default:
150 g_gps.nmea.state = GNS_WAIT;
151 g_gps.nmea.len = 0;
152 break;
153 }
154 }
155 }
156 return NULL;
157}
158
159void
160_gps_parse_nmea(const char *nmea)
161{
162 /* Very basic parsing, we just look at GSA messages and consider
163 * that if we have a 3D fix with PDOP < 5, the timing data should
164 * be usable
165 */
166 if (!strncmp(nmea, "$GPGSA", 6))
167 {
168 /* Check for Autonomous 3D fix (fixed field positions) */
169 if ((nmea[7] != 'A') || (nmea[9] != '3')) {
170 g_gps.fix.valid = false;
171 return;
172 }
173
174 /* Find PDOP */
175 const char *p = nmea;
176 for (int i=0; (i < 15) && (*p != '*'); i += (*(p++)==',') );
177
178 /* Is it low enough ? */
179 g_gps.fix.valid = (p[1] < '5');
180 }
181
182 /* Parse TXT ANTENNA Status */
183 if (!strncmp(nmea, "$GPTXT", 6) && nmea[13] == '0' && nmea[14] == '1')
184 {
185 if (!strncmp(&nmea[16], "ANTENNA OK", 10))
186 g_gps.antenna = ANT_OK;
187 else if (!strncmp(&nmea[16], "ANTENNA OPEN", 12))
188 g_gps.antenna = ANT_OPEN;
189 else if (!strncmp(&nmea[16], "ANTENNA SHORT", 13))
190 g_gps.antenna = ANT_SHORT;
191 else
192 g_gps.antenna = ANT_UNKNOWN;
193 }
194}
195
196
197bool
198gps_has_valid_fix(void)
199{
200 return g_gps.fix.valid;
201}
202
203enum gps_antenna_state
204gps_antenna_status(void)
205{
206 return g_gps.antenna;
207}
208
209void
210gps_poll(void)
211{
212 const char *nmea;
213
214 /* Check if we have anything from the module */
215 nmea = _gps_query();
216 if (!nmea)
217 return;
218
219 /* If we do, process it locally to update our state */
220 _gps_parse_nmea(nmea);
Sylvain Munaut70c10f02022-01-12 11:58:34 +0100221
222 /* And queue it for USB */
223 usb_gps_puts(nmea);
Sylvain Munautef5fe382022-01-12 11:55:44 +0100224}
225
226void
227gps_init(void)
228{
229 int i;
230
231 /* State init */
232 memset(&g_gps, 0x00, sizeof(g_gps));
233
234 /* Configure reset gpio */
235 gpio_out(3, false);
236 gpio_dir(3, true);
237
238 /* Attempt reset sequence at 9600 baud and then 115200 baud */
239 for (i=0; i<2; i++)
240 {
241 uint32_t start_time = time_now_read();
242 bool init_ok;
243
244 /* Assert reset */
245 gpio_out(3, false);
246
247 /* Configure uart and empty buffer */
248 gps_uart_regs->csr = i ? GPS_UART_CSR_DIV(115200) : GPS_UART_CSR_DIV(9600);
249 _gps_empty(false);
250
251 /* Wait 100 ms */
252 delay(100);
253
254 /* Release reset line */
255 gpio_out(3, true);
256
257 /* Wait for first line of output as sign it's ready, timeout after 1s */
258 while (!time_elapsed(start_time, SYS_CLK_FREQ))
259 if ((init_ok = (_gps_query() != NULL)))
260 break;
261
262 if (init_ok) {
263 printf("[+] GPS ok at %d baud\n", i ? 115200 : 9600);
264 break;
265 }
266 }
267
268 /* Failed ? */
269 if (i == 2) {
270 printf("[!] GPS init failed\n");
271 return;
272 }
273
274 /* If success was at 9600 baud, need to speed up */
275 if (i == 0) {
276 /* Configure GPS to use serial at 115200 baud */
277 _gps_send("PCAS01,5");
278
279 /* Add dummy byte which will be mangled during baudrate switch ... */
280 gps_uart_regs->data = 0x00;
281 while (!(gps_uart_regs->csr & GPS_UART_CSR_TX_EMPTY));
282
283 /* Set uart to 115200 and empty uart buffer, line aligned */
284 gps_uart_regs->csr = GPS_UART_CSR_DIV(115200) ;
285 _gps_empty(true);
286 }
287
288 /* Configure GPS to be GPS-only (no GLONASS/BEIDOU) */
289 _gps_send("PCAS04,1");
290}