/* hadie - High Altitude Balloon flight software */ /*============================================================*/ /* Copyright (C)2010 Philip Heron */ /* */ /* This program is distributed under the terms of the GNU */ /* General Public License, version 2. You may use, modify, */ /* and redistribute it under the terms of this license. A */ /* copy should be included with this source. */ #include "config.h" #include #include #include #include #include "gps.h" #ifdef UBLOX5 #include #endif /************* UART RX interrupt handler and buffer *************************/ /* Timeout counter */ volatile static uint8_t timeout_clk = 0; inline void gps_tick(void) { if(timeout_clk) timeout_clk--; } /* The maximum length of the NMEA string is 82 characters */ #define RXBUFSIZE (82 + 1) /* Helper to convert uppercase hex ascii character to integer */ #define HTOI(c) (((c) - '0' > '9' ? (c) - 'A' + 10 : (c) - '0') & 0x0F) #ifdef UBLOX5 #define UBXBUFSIZE (16) static volatile uint8_t ubx_rx[UBXBUFSIZE]; static volatile uint8_t ubx_rx_len = 0; static volatile uint8_t ubx_want_packet = 0; static inline void isr_ubx(uint8_t b) { static uint8_t rx[UBXBUFSIZE]; static uint8_t rx_len = 0; static uint16_t pkt_len = 8; static uint8_t ck_a = 0, ck_b = 0; rx[rx_len++] = b; if(rx_len < 2) return; /* Should have received the sync pattern */ if(rx_len == 2) { if(rx[0] != 0xB5 || rx[1] != 0x62) { /* Nope */ rx[0] = rx[1]; rx_len = 1; } return; } /* Update the checksum */ if(rx_len <= pkt_len - 2) ck_b += ck_a += b; /* Add the payload data length to the packet size */ if(rx_len == 6) { pkt_len += (rx[5] << 8) + rx[4]; /* Drop the packet if it's going to be too big for the buffer */ if(pkt_len > UBXBUFSIZE) goto clear; } /* Wait until the complete packet has been received */ if(rx_len < pkt_len) return; /* The entire packet should now be in the buffer, test the checksum */ if(rx[rx_len - 2] != ck_a) goto clear; if(rx[rx_len - 1] != ck_b) goto clear; /* A valid packet has been received - copy it and set the flag */ memcpy((void *) ubx_rx, rx, rx_len); ubx_rx_len = rx_len; ubx_want_packet = 0; /* Done with this packet */ clear: rx_len = 0; pkt_len = 8; ck_a = ck_b = 0; } #endif /* Received GPGGA lines are copied into this buffer by the interrupt */ static volatile char rxline[RXBUFSIZE]; static volatile char rx_lock = 0; ISR(USART1_RX_vect) { static char rx[RXBUFSIZE]; static uint8_t rx_len = 0; /* Number of characters in the buffer */ static uint8_t rx_checksum = 0; /* Calculated checksum */ static uint8_t rx_have_checksum = 0; /* If the string has a checksum */ static uint8_t checksum; /* The checksum at the end of the string */ uint8_t b; /* Read the received character */ b = UDR1; #ifdef UBLOX5 if(ubx_want_packet) isr_ubx(b); #endif switch(b) { case '$': break; case '*': rx_have_checksum = 1; return; case '\n': case '\r': /* A complete line has been received - is the checksum valid? */ if(!rx_have_checksum) break; if(checksum != rx_checksum) break; /* Is it a GPGGA message? */ rx[rx_len] = '\0'; if(strncmp(rx, "GPGGA", 5) != 0) break; /* Got a valid GPGGA line, copy it if not locked */ if(!rx_lock) strncpy((char *) rxline, rx, RXBUFSIZE); break; default: if(!rx_have_checksum) { /* Clear the buffer if full */ if(rx_len >= RXBUFSIZE) break; /* Store the new character and update checksum */ rx[rx_len++] = b; rx_checksum ^= b; } else { checksum <<= 4; checksum |= HTOI(b); } return; } /* Clear the buffer */ rx_len = 0; rx_checksum = 0; rx_have_checksum = 0; } #ifdef UBLOX5 static void tx_byte(uint8_t b) { /* Wait for empty transmit buffer */ while(!(UCSR1A & (1 << UDRE1))); /* Put data into buffer, sends the data */ UDR1 = b; } #endif /* Like atoi but for fixed point numbers. Processes at most n bytes and * returns a pointer the next position in the string. Reads dp decimal * places */ int32_t strntofp(const char *s, char **endptr, size_t n, char dp) { int32_t i = 0; char neg = 0, fp = 0; if(n <= 0) goto out; /* Test for a + or - sign */ switch(*s) { case '-': neg = !neg; case '+': s++; n--; } /* Read in the number */ while(*s && n && (!fp || dp)) { char d = *s; if(d >= '0' && d <= '9') { /* Add the digit */ i *= 10; i += d - '0'; if(fp) dp--; } else if(dp > 0 && d == '.') fp = 1; else break; /* Next... */ s++; n--; } while(dp > 0) { i *= 10; dp--; } /* Fix result if it's negative */ if(neg) i = -i; out: /* Set the end pointer if needed */ if(endptr) *endptr = (char *) s; return(i); } int32_t strntoi(const char *s, char **endptr, size_t n) { return(strntofp(s, endptr, n, 0)); } char *gps_field(char *s, int f) { while(*s && f > 0) if(*(s++) == ',') f--; if(f == 0) return(s); return(NULL); } char gps_parse(gpsfix_t *gps) { char i; memset(gps, 0, sizeof(gpsfix_t)); rx_lock = 1; for(i = 0; i < 10; i++) { char *r = gps_field((char *) rxline, i); if(!r) { rx_lock = 0; return(-1); } switch(i) { case 1: /* Fix Time */ gps->hour = strntoi(r, &r, 2); gps->minute = strntoi(r, &r, 2); gps->second = strntoi(r, &r, 2); break; case 2: /* Latitude */ gps->latitude_i = strntoi(r, &r, 2); gps->latitude_f = strntofp(r, &r, 7, 4) * 100 / 60; break; case 3: /* Latitude hemisphere */ gps->latitude_h = (*r == 'S' ? 'S' : 'N'); break; case 4: /* Longitude */ gps->longitude_i = strntoi(r, &r, 3); gps->longitude_f = strntofp(r, &r, 7, 4) * 100 / 60; break; case 5: /* Longitude hemisphere */ gps->longitude_h = (*r == 'W' ? 'W' : 'E'); break; case 6: /* Fix quality */ gps->fix = strntoi(r, NULL, 3); break; case 7: /* Satellites */ gps->sats = strntoi(r, NULL, 3); break; case 9: /* Altitude */ gps->altitude = strntoi(r, NULL, 10); break; } } rx_lock = 0; return(0); } #ifdef UBLOX5 /* From the FSA03 guide on the ukhas wiki: * * http://ukhas.org.uk/guides:falcom_fsa03 */ PROGMEM const uint8_t ubx_setnav[] = { 0xB5,0x62,0x06,0x24,0x24,0x00,0xFF,0xFF,0x06,0x03,0x00,0x00,0x00,0x00,0x10,0x27, 0x00,0x00,0x05,0x00,0xFA,0x00,0xFA,0x00,0x64,0x00,0x2C,0x01,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0xDC }; PROGMEM const uint8_t ubx_setnav_ack[] = { 0x05,0x01,0x02,0x00,0x06,0x24 }; char gps_ubx_init(void) { uint8_t i; /* Send the prepared command */ for(i = 0; i < sizeof(ubx_setnav); i++) tx_byte(pgm_read_byte(&ubx_setnav[i])); /* Wait for a response */ ubx_want_packet = 1; timeout_clk = 255; /* 0.85 seconds at 300hz */ while(timeout_clk) { if(ubx_want_packet == 1) continue; /* Got a packet... the ACK for the command? */ if(!memcmp_P((void *) &ubx_rx[2], ubx_setnav_ack, sizeof(ubx_setnav_ack))) { /* GPS device acknowledged the command */ return(0); } ubx_want_packet = 1; } /* Timeout waiting for ACK packet */ ubx_want_packet = 0; return(-1); } #endif void gps_init(void) { rxline[0] = '\0'; /* Do UART1 initialisation, 9600 baud @ 7.3728 MHz */ UBRR1H = 0; UBRR1L = 47; /* Enable RX, TX and RX interrupt */ UCSR1B = (1 << RXEN1) | (1 << TXEN1) | (1 << RXCIE1); /* 8-bit, no parity and 1 stop bit */ UCSR1C = (1 << UCSZ11) | (1 << UCSZ10); }