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