/* * Functions for the UBLOX 8 GPS * Copyright (C) 2014 Richard Meadows * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "samd20.h" #include "hw_config.h" #include "system/system.h" #include "system/port.h" #include "system/interrupt.h" #include "sercom/sercom.h" #include "sercom/usart.h" #include "util/dbuffer.h" #include "ubx_messages.h" #include "gps.h" #include "watchdog.h" /** * UBX Constants */ const uint16_t ubx_header = (0xB5 | (0x62 << 8)); /** * UBX ACK Message Types */ enum { UBX_ACK_NACK = (UBX_ACK | (0x00 << 8)), UBX_ACK_ACK = (UBX_ACK | (0x01 << 8)), }; /** * Internal buffers and things */ #define UBX_BUFFER_LEN 0x80 #define SFD1 0xB5 #define SFD2 0x62 enum sfd_state { SFD_WAITING = -1, SFD_GOT1, } sfd_state = SFD_WAITING; int32_t ubx_index = SFD_WAITING; uint16_t ubx_payload_length = 0; uint8_t ubx_irq_buffer[UBX_BUFFER_LEN]; volatile enum gps_error_t gps_error_state; /** * UBX Messages */ volatile struct ubx_cfg_ant ubx_cfg_ant = { .id = (UBX_CFG | (0x13 << 8)) }; volatile struct ubx_cfg_gnss ubx_cfg_gnss = { .id = (UBX_CFG | (0x3E << 8)) }; volatile struct ubx_cfg_nav5 ubx_cfg_nav5 = { .id = (UBX_CFG | (0x24 << 8)) }; volatile struct ubx_cfg_tp5 ubx_cfg_tp5 = { .id = (UBX_CFG | (0x31 << 8)) }; volatile struct ubx_cfg_prt ubx_cfg_prt = { .id = (UBX_CFG | (0x00 << 8)) }; volatile struct ubx_cfg_pwr ubx_cfg_pwr = { .id = (UBX_CFG | (0x57 << 8)) }; volatile struct ubx_cfg_rxm ubx_cfg_rxm = { .id = (UBX_CFG | (0x11 << 8)) }; volatile struct ubx_nav_posllh ubx_nav_posllh = { .id = (UBX_NAV | (0x02 << 8)) }; volatile struct ubx_nav_timeutc ubx_nav_timeutc = { .id = (UBX_NAV | (0x21 << 8)) }; volatile struct ubx_nav_sol ubx_nav_sol = { .id = (UBX_NAV | (0x06 << 8)) }; volatile struct ubx_nav_status ubx_nav_status = { .id = (UBX_NAV | (0x03 << 8)) }; /** * UBX Message Type List */ volatile ubx_message_t* const ubx_messages[] = { (ubx_message_t*)&ubx_cfg_ant, (ubx_message_t*)&ubx_cfg_gnss, (ubx_message_t*)&ubx_cfg_nav5, (ubx_message_t*)&ubx_cfg_tp5, (ubx_message_t*)&ubx_cfg_prt, (ubx_message_t*)&ubx_cfg_pwr, (ubx_message_t*)&ubx_cfg_rxm, (ubx_message_t*)&ubx_nav_posllh, (ubx_message_t*)&ubx_nav_timeutc, (ubx_message_t*)&ubx_nav_sol, (ubx_message_t*)&ubx_nav_status}; /** * Platform specific handlers */ #define _send_buffer(tx_data, length) \ do { usart_write_buffer_wait(GPS_SERCOM, tx_data, length); } while (0) #define _error_handler(error_type) \ do { gps_error_state = error_type; } while (0) /** * Calculate a UBX checksum using 8-bit Fletcher (RFC1145) */ uint16_t _ubx_checksum(uint8_t* data, uint8_t len) { uint16_t ck = 0; uint8_t* cka = (uint8_t*)&ck; uint8_t* ckb = (cka + 1); for(uint8_t i = 0; i < len; i++) { *cka += *data; *ckb += *cka; data++; } return ck; } /** * Processes UBX ack/nack packets */ void ubx_process_ack(ubx_message_id_t message_id, enum ubx_packet_state state) { for (uint32_t i = 0; i < sizeof(ubx_messages)/sizeof(ubx_message_t*); i++) { if (message_id == ubx_messages[i]->id) { /* Match! */ /* Set the message state */ ubx_messages[i]->state = state; } } } /** * Process a single ubx frame. Runs in the IRQ so should be short and sweet. */ void ubx_process_frame(uint8_t* frame) { uint16_t* frame16 = (uint16_t*)frame; uint16_t message_id = frame16[0]; uint16_t payload_length = frame16[1]; uint16_t checksum = ((uint16_t*)(frame + payload_length + 4))[0]; uint16_t calculated_checksum = _ubx_checksum(frame, payload_length + 4); /* Checksum.. */ if (calculated_checksum != checksum) { _error_handler(GPS_ERROR_BAD_CHECKSUM); return; } /** Parse the message ID */ if (message_id == UBX_ACK_ACK) { /* Ack */ ubx_process_ack(frame16[2], UBX_PACKET_ACK); return; } else if (message_id == UBX_ACK_NACK) { /* Nack */ ubx_process_ack(frame16[2], UBX_PACKET_NACK); return; } else { /** Otherwise it could be a message frame, search for a type */ for (uint32_t i = 0; i < sizeof(ubx_messages)/sizeof(ubx_message_t*); i++) { if (message_id == ubx_messages[i]->id) { // Match! /* Populate struct */ memcpy((void*)(ubx_messages[i]+1), frame + 4, payload_length); /* Set the message state */ ubx_messages[i]->state = UBX_PACKET_UPDATED; return; } } } /* Unknown frame */ _error_handler(GPS_ERROR_INVALID_FRAME); } /** * Rx Callback. Processes a stream of usart bytes */ void gps_rx_callback(SercomUsart* const hw, uint16_t data) { (void)hw; /* Start of Frame Delimeter 1 */ if (data == SFD1) { sfd_state = SFD_GOT1; } else { /* SFD2 preceeded by SFD1 */ if (sfd_state == SFD_GOT1 && data == SFD2) { /* Start a new frame */ ubx_index = 0; ubx_payload_length = 0; sfd_state = SFD_WAITING; return; } sfd_state = SFD_WAITING; } /** * If we have a valid index. That is: * We're in a frame * And we've not reached class/message + length + payload + checksum * And we've not reached the end of the buffer */ if (ubx_index != SFD_WAITING && ubx_index < (4 + ubx_payload_length + 2) && ubx_index < UBX_BUFFER_LEN) { /* Data in */ ubx_irq_buffer[ubx_index++] = data; /* Extract length */ if (ubx_index == 4) { ubx_payload_length = ((uint16_t*)ubx_irq_buffer)[1]; } /* Complete Frame? */ if (ubx_index >= (4 + ubx_payload_length + 2)) { /* Process it */ ubx_process_frame(ubx_irq_buffer); } } } /** * Sends a standard UBX message */ void _ubx_send_message(ubx_message_t* message, uint8_t* payload, uint16_t length) { uint8_t ubx[UBX_BUFFER_LEN]; uint8_t* ubx_buffer = ubx; /* Clear the message state */ message->state = UBX_PACKET_WAITING; /* Clear error state */ gps_error_state = GPS_NOERROR; /* Copy little endian */ memcpy(ubx_buffer, &ubx_header, 2); ubx_buffer += 2; /* Header */ memcpy(ubx_buffer, &message->id, 2); ubx_buffer += 2; /* Message Type */ memcpy(ubx_buffer, &length, 2); ubx_buffer += 2; /* Length */ memcpy(ubx_buffer, payload, length); ubx_buffer += length; /* Payload */ uint16_t checksum = _ubx_checksum(ubx + 2, length + 4); memcpy(ubx_buffer, &checksum, 2); ubx_buffer += 2; /* Checksum */ _send_buffer(ubx, length + 8); } /** * Polls the GPS for packets */ enum gps_error_t _ubx_poll(ubx_message_t* message) { /* Send the message */ _ubx_send_message(message, NULL, 0); /* Wait for acknoledge */ while (message->state == UBX_PACKET_WAITING && gps_error_state == GPS_NOERROR); return gps_error_state; } /** * Disable NMEA messages on the uBlox */ void gps_set_io_config(uint32_t baud_rate) { ubx_cfg_prt.payload.portID = 1; ubx_cfg_prt.payload.res0 = 0; ubx_cfg_prt.payload.txReady = 0; ubx_cfg_prt.payload.mode = 0x08D0; /* 8 bit, No Parity */ ubx_cfg_prt.payload.baudRate = baud_rate; ubx_cfg_prt.payload.inProtoMask = 0x7; /* UBX */ ubx_cfg_prt.payload.outProtoMask = 0x1; /* UBX */ ubx_cfg_prt.payload.flags = 0; _ubx_send_message((ubx_message_t*)&ubx_cfg_prt, (uint8_t*)&ubx_cfg_prt.payload, sizeof(ubx_cfg_prt.payload)); for (int i = 0; i < 1000*100; i++); } /** * Sends messages to the GPS to update the time */ void gps_update_time(void) { _ubx_send_message((ubx_message_t*)&ubx_nav_timeutc, NULL, 0); }; /** * Sends messages to the GPS to update the position */ void gps_update_position(void) { _ubx_send_message((ubx_message_t*)&ubx_nav_posllh, NULL, 0); _ubx_send_message((ubx_message_t*)&ubx_nav_sol, NULL, 0); // _ubx_send_message((ubx_message_t*)&ubx_nav_status, NULL, 0); } /** * Indicates a pending time update from the GPS */ int gps_update_time_pending(void) { return (ubx_nav_timeutc.state == UBX_PACKET_WAITING && gps_error_state == GPS_NOERROR); } /** * Indicates a pending position update from the GPS */ int gps_update_position_pending(void) { return (((ubx_nav_posllh.state == UBX_PACKET_WAITING) || (ubx_nav_sol.state == UBX_PACKET_WAITING)) && gps_error_state == GPS_NOERROR); //(ubx_nav_status.state == UBX_PACKET_WAITING); } /** * Gets the current error state of the GPS to check validity of last * request */ enum gps_error_t gps_get_error_state(void) { return gps_error_state; } /** * Return the latest received messages */ struct ubx_nav_posllh gps_get_nav_posllh() { return ubx_nav_posllh; } struct ubx_nav_sol gps_get_nav_sol() { return ubx_nav_sol; } struct ubx_nav_timeutc gps_get_nav_timeutc() { return ubx_nav_timeutc; } /** * Returns if the GPS has a position lock */ uint8_t gps_is_locked(void) { return (ubx_nav_sol.payload.gpsFix == 0x2) || (ubx_nav_sol.payload.gpsFix == 0x3) || (ubx_nav_sol.payload.gpsFix == 0x4); } /** * Verify that the uBlox 6 GPS receiver is set to the <1g airborne * navigaion mode. */ void gps_set_platform_model(void) { /* Poll for the current settings */ _ubx_poll((ubx_message_t*)&ubx_cfg_nav5); /* If we need to update */ if (ubx_cfg_nav5.payload.dynModel != GPS_PLATFORM_MODEL) { /* Update */ ubx_cfg_nav5.payload.dynModel = GPS_PLATFORM_MODEL; /* Send */ _ubx_send_message((ubx_message_t*)&ubx_cfg_nav5, (uint8_t*)&ubx_cfg_nav5.payload, sizeof(ubx_cfg_nav5.payload)); } } /** * Set the GPS timepulse settings using the CFG_TP5 message */ void gps_set_timepulse_five(uint32_t frequency) { /* Poll for the current settings */ _ubx_poll((ubx_message_t*)&ubx_cfg_tp5); /* Define the settings we want */ ubx_cfg_tp5.payload.tpIdx = 0; ubx_cfg_tp5.payload.antCableDelay = 50; /* 50 nS */ /* frequency, lock to GPS, active */ ubx_cfg_tp5.payload.flags = UBX_TP5_ENABLE | UBX_TP5_LOCK_TO_GPS | UBX_TP5_IS_FREQ | UBX_TP5_ALIGN_RISING | UBX_TP5_USE_GRID_UTC; ubx_cfg_tp5.payload.freqPeriod = frequency; ubx_cfg_tp5.payload.pulseLenRatio = 0x80000000; /* 50 % duty cycle*/ /* Write the new settings */ _ubx_send_message((ubx_message_t*)&ubx_cfg_tp5, (uint8_t*)&ubx_cfg_tp5.payload, sizeof(ubx_cfg_tp5.payload)); } /** * Set which GNSS constellations to use */ void gps_set_gnss(void) { /* Poll for the current settings */ _ubx_poll((ubx_message_t*)&ubx_cfg_gnss); switch (ubx_cfg_gnss.payload.msgVer) { case 0: /* For each configuration block */ for (uint8_t i = 0; i < ubx_cfg_gnss.payload.numConfigBlocks; i++) { /* If it's the configuration for something other than GPS */ if (ubx_cfg_gnss.payload.block[i].gnssID != UBX_GNSS_GPS) { /* Disable this GNSS system */ ubx_cfg_gnss.payload.block[i].flags &= ~0x1; } } break; default: break; } /* Write the new settings */ _ubx_send_message((ubx_message_t*)&ubx_cfg_gnss, (uint8_t*)&ubx_cfg_gnss.payload, 4 + (8 * ubx_cfg_gnss.payload.numConfigBlocks)); } /** * Sets the powersave mode */ void gps_set_powersave(bool powersave_on) { ubx_cfg_rxm.payload.lpMode = (powersave_on ? UBX_POWERSAVE_ON : UBX_POWERSAVE_OFF); /* Write the new settings */ _ubx_send_message((ubx_message_t*)&ubx_cfg_rxm, (uint8_t*)&ubx_cfg_rxm.payload, sizeof(ubx_cfg_rxm.payload)); } /** * Sets the PWR power state */ void gps_set_power_state(bool gnss_running) { ubx_cfg_pwr.payload.messageVersion = 1; ubx_cfg_pwr.payload.state = (gnss_running ? UBX_PWR_STATE_GNSS_RUNNING : UBX_PWR_STATE_GNSS_STOPPED); /* Write the new settings */ _ubx_send_message((ubx_message_t*)&ubx_cfg_pwr, (uint8_t*)&ubx_cfg_pwr.payload, sizeof(ubx_cfg_pwr.payload)); } /** * Sets the powersave mode automatically based on if we're locked. * * Only call after we've updated our position */ bool _last_gps_is_locked = false; void gps_set_powersave_auto(void) { bool is_locked = gps_is_locked(); if (is_locked != _last_gps_is_locked) { if (is_locked) { /* Just locked */ gps_set_powersave(true); } else { /* Just unlocked */ gps_set_powersave(false); } _last_gps_is_locked = is_locked; } } /** * Init + enable for the usart at the given baud rate */ void gps_usart_init_enable(uint32_t baud_rate) { /* USART */ usart_init(GPS_SERCOM, USART_DATAORDER_LSB, /** Bit order (MSB or LSB first) */ USART_TRANSFER_ASYNCHRONOUSLY, /** Asynchronous or synchronous mode */ USART_PARITY_NONE, /** USART parity */ USART_STOPBITS_1, /** Number of stop bits */ USART_CHARACTER_SIZE_8BIT, /** USART character size */ GPS_SERCOM_MUX, /** USART pinout */ false, /** Immediate buffer overflow notification */ false, /** Enable IrDA encoding format */ 19, /** Minimum pulse length required for IrDA rx */ false, /** Enable LIN Slave Support */ false, /** Enable start of frame dection */ false, /** Enable collision dection */ baud_rate, /** USART Baud rate */ true, /** Enable receiver */ true, /** Enable transmitter */ false, /** Sample on the rising edge of XLCK */ false, /** Use the external clock applied to the XCK pin. */ 0, /** External clock frequency in synchronous mode. */ true, /** Run in standby */ GPS_GCLK, /** GCLK generator source */ GPS_SERCOM_MOGI_PINMUX, /** PAD0 pinmux */ GPS_SERCOM_MIGO_PINMUX, /** PAD1 pinmux */ PINMUX_UNUSED, /** PAD2 pinmux */ PINMUX_UNUSED); /** PAD3 pinmux */ usart_enable(GPS_SERCOM); } /** * GPS reset pin */ void gps_reset_on(void) { #ifdef GPS_RESET_PIN port_pin_set_output_level(GPS_RESET_PIN, 0); /* active low */ #endif } void gps_reset_off(void) { #ifdef GPS_RESET_PIN port_pin_set_output_level(GPS_RESET_PIN, 1); /* active low */ #endif } /** * Reset. Places the GPS in a RESET state */ void gps_reset(void) { #ifdef GPS_RESET_PIN port_pin_set_config(GPS_RESET_PIN, PORT_PIN_DIR_OUTPUT, /* Direction */ PORT_PIN_PULL_NONE, /* Pull */ false); /* Powersave */ port_pin_set_output_level(GPS_RESET_PIN, 0); /* active low */ #endif } /** * Init */ void gps_init(void) { /* Bring GPS out of reset */ gps_reset_off(); /* Enable usart */ gps_usart_init_enable(GPS_BAUD_RATE); kick_the_watchdog(); /* We use ubx protocol */ gps_set_io_config(GPS_BAUD_RATE); /* Incoming ubx messages are handled in an irq */ usart_register_rx_callback(GPS_SERCOM, gps_rx_callback, GPS_SERCOM_INT_PRIO); kick_the_watchdog(); /* Set the platform model */ gps_set_platform_model(); /* Set which GNSS constellation we'd like to use */ gps_set_gnss(); kick_the_watchdog(); /* Exit powersave mode to start */ gps_set_powersave(false); /* Set the timepulse */ gps_set_timepulse_five(GPS_TIMEPULSE_FREQ); } /** * Quick and dirty loopback test. Should print 0x34 */ void usart_loopback_test(void) { uint16_t data; usart_init(GPS_SERCOM, USART_DATAORDER_LSB, /** Bit order (MSB or LSB first) */ USART_TRANSFER_ASYNCHRONOUSLY, /** Asynchronous or synchronous mode */ USART_PARITY_NONE, /** USART parity */ USART_STOPBITS_1, /** Number of stop bits */ USART_CHARACTER_SIZE_8BIT, /** USART character size */ USART_MUX_LOOPBACK, /** USART pin out */ false, /** Immediate buffer overflow notification */ false, /** Enable IrDA encoding format */ 19, /** Minimum pulse length required for IrDA rx */ false, /** Enable LIN Slave Support */ false, /** Enable start of frame dection */ false, /** Enable collision dection */ GPS_BAUD_RATE, /** USART Baud rate */ true, /** Enable receiver */ true, /** Enable transmitter */ false, /** Sample on the rising edge of XLCK */ false, /** Use the external clock applied to the XCK pin. */ 0, /** External clock frequency in synchronous mode. */ false, /** Run in standby */ GPS_GCLK, /** GCLK generator source */ GPS_SERCOM_MOGI_PINMUX, /** PAD0 pinmux */ GPS_SERCOM_MIGO_PINMUX, /** PAD1 pinmux */ PINMUX_UNUSED, /** PAD2 pinmux */ PINMUX_UNUSED); /** PAD3 pinmux */ usart_enable(GPS_SERCOM); usart_write_wait(GPS_SERCOM, 0x34); usart_read_wait(GPS_SERCOM, &data); usart_disable(GPS_SERCOM); }