/** ****************************************************************************** * * @file GPS.c * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. * @brief GPS module, handles GPS and NMEA stream * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "openpilot.h" #include "buffer.h" // constants/macros/typdefs #define NMEA_BUFFERSIZE 128 #define MAXTOKENS 20 //token slots for nmea parser // Message Codes #define NMEA_NODATA 0 // No data. Packet not available, bad, or not decoded #define NMEA_GPGGA 1 // Global Positioning System Fix Data #define NMEA_GPVTG 2 // Course over ground and ground speed #define NMEA_GPGLL 3 // Geographic position - latitude/longitude #define NMEA_GPGSV 4 // GPS satellites in view #define NMEA_GPGSA 5 // GPS DOP and active satellites #define NMEA_GPRMC 6 // Recommended minimum specific GPS data #define NMEA_UNKNOWN 0xFF// Packet received but not known // Debugging //#define GPSDEBUG #ifdef GPSDEBUG #define NMEA_DEBUG_PKT ///< define to enable debug of all NMEA messages #define NMEA_DEBUG_GGA ///< define to enable debug of GGA messages #define NMEA_DEBUG_VTG ///< define to enable debug of VTG messages #endif // Private functions static void gpsTask(void* parameters); static void periodicEventHandler(UAVObjEvent* ev); static void registerObject(UAVObjHandle obj); static void updateObject(UAVObjHandle obj); static int32_t addObject(UAVObjHandle obj); static int32_t setUpdatePeriod(UAVObjHandle obj, int32_t updatePeriodMs); // functions void nmeaInit(void); char* nmeaGetPacketBuffer(void); uint8_t nmeaProcess(cBuffer* rxBuffer); void nmeaProcessGPGGA(char* packet); void nmeaProcessGPVTG(char* packet); cBuffer gpsRxBuffer; static char gpsRxData[1024]; // Global variables extern GpsInfoType GpsInfo; char NmeaPacket[NMEA_BUFFERSIZE]; // Private constants #define MAX_QUEUE_SIZE 20 #define STACK_SIZE 100 #define TASK_PRIORITY (tskIDLE_PRIORITY + 5) #define REQ_TIMEOUT_MS 500 #define MAX_RETRIES 3 // Private types // Private variables static COMPortTypeDef gpsPort; static xQueueHandle queue; static xTaskHandle gpsTaskHandle; /** * Initialise the gps module * \return -1 if initialisation failed * \return 0 on success */ int32_t GpsInitialize(void) { // Create object queue //queue = xQueueCreate(MAX_QUEUE_SIZE, sizeof(UAVObjEvent)); // TODO: Get gps settings object gpsPort = COM_USART2; // Init input buffer size 512 bufferInit(&gpsRxBuffer, (unsigned char *)gpsRxData, 1024); // Process all registered objects and connect queue for updates //UAVObjIterate(®isterObject); // Start gps task xTaskCreate(gpsTask, (signed char*)"GPS", configMINIMAL_STACK_SIZE, NULL, TASK_PRIORITY, &gpsTaskHandle); return 0; } /** * Register a new object, adds object to local list and connects the queue depending on the object's * telemetry settings. * \param[in] obj Object to connect */ void registerObject(UAVObjHandle obj) { // Setup object for periodic updates addObject(obj); // Setup object for telemetry updates updateObject(obj); } /** * Update object's queue connections and timer, depending on object's settings * \param[in] obj Object to updates */ void updateObject(UAVObjHandle obj) { } /** * gps task. Processes input buffer. It does not return. */ static void gpsTask(void* parameters) { int32_t gpsRxOverflow=0; char c; portTickType xDelay = 70 / portTICK_RATE_MS; // Loop forever while(1) { /* This blocks the task until there is something on the buffer */ while(PIOS_COM_ReceiveBufferUsed(gpsPort) > 0) { c=PIOS_COM_ReceiveBuffer(gpsPort); if( !bufferAddToEnd(&gpsRxBuffer, c) ) { // no space in buffer // count overflow gpsRxOverflow++; break; } //PIOS_COM_SendFormattedStringNonBlocking(COM_USART1, "%d\r", PIOS_COM_ReceiveBufferUsed(gpsPort)); //vTaskDelay(xDelay); } nmeaProcess(&gpsRxBuffer); vTaskDelay(xDelay); } } /** * Event handler for periodic object updates (called by the event dispatcher) */ static void periodicEventHandler(UAVObjEvent* ev) { // Push event to the telemetry queue xQueueSend(queue, ev, 0); // do not wait if queue is full } /** * Setup object for periodic updates. * \param[in] obj The object to update * \return 0 Success * \return -1 Failure */ static int32_t addObject(UAVObjHandle obj) { UAVObjEvent ev; // Add object for periodic updates ev.obj = obj; ev.instId = UAVOBJ_ALL_INSTANCES; ev.event = EV_UPDATED_MANUAL; return EventPeriodicCreate(&ev, &periodicEventHandler, 0); } /** * Set update period of object (it must be already setup for periodic updates) * \param[in] obj The object to update * \param[in] updatePeriodMs The update period in ms, if zero then periodic updates are disabled * \return 0 Success * \return -1 Failure */ static int32_t setUpdatePeriod(UAVObjHandle obj, int32_t updatePeriodMs) { UAVObjEvent ev; // Add object for periodic updates ev.obj = obj; ev.instId = UAVOBJ_ALL_INSTANCES; ev.event = EV_UPDATED_MANUAL; return EventPeriodicUpdate(&ev, &periodicEventHandler, updatePeriodMs); } char* nmeaGetPacketBuffer(void) { return NmeaPacket; } /** * Prosesses NMEA sentences * \param[in] cBuffer for prosessed nmea sentences * \return Message code for found packet * \return 0xFF NO packet found */ uint8_t nmeaProcess(cBuffer* rxBuffer) { uint8_t foundpacket = NMEA_NODATA; uint8_t startFlag = FALSE; //u08 data; uint16_t i,j; // process the receive buffer // go through buffer looking for packets while(rxBuffer->datalength) { // look for a start of NMEA packet if(bufferGetAtIndex(rxBuffer,0) == '$') { // found start startFlag = TRUE; // when start is found, we leave it intact in the receive buffer // in case the full NMEA string is not completely received. The // start will be detected in the next nmeaProcess iteration. // done looking for start break; } else bufferGetFromFront(rxBuffer); } // if we detected a start, look for end of packet if(startFlag) { for(i=1; i<(rxBuffer->datalength)-1; i++) { // check for end of NMEA packet if((bufferGetAtIndex(rxBuffer,i) == '\r') && (bufferGetAtIndex(rxBuffer,i+1) == '\n')) { // have a packet end // dump initial '$' bufferGetFromFront(rxBuffer); // copy packet to NmeaPacket for(j=0; j<(i-1); j++) { // although NMEA strings should be 80 characters or less, // receive buffer errors can generate erroneous packets. // Protect against packet buffer overflow if(j<(NMEA_BUFFERSIZE-1)) NmeaPacket[j] = bufferGetFromFront(rxBuffer); else bufferGetFromFront(rxBuffer); } // null terminate it NmeaPacket[j] = 0; // dump from rxBuffer bufferGetFromFront(rxBuffer); bufferGetFromFront(rxBuffer); //DEBUG //iprintf("$%s\r\n",NmeaPacket); // #ifdef NMEA_DEBUG_PKT //PIOS_COM_SendFormattedStringNonBlocking(COM_DEBUG_USART,"$%s\r\n",NmeaPacket); #endif // found a packet // done with this processing session foundpacket = NMEA_UNKNOWN; //PIOS_LED_Toggle(LED2); break; } } } if(foundpacket) { // check message type and process appropriately if(!strncmp(NmeaPacket, "GPGGA", 5)) { // process packet of this type nmeaProcessGPGGA(NmeaPacket); // report packet type foundpacket = NMEA_GPGGA; } else if(!strncmp(NmeaPacket, "GPVTG", 5)) { // process packet of this type nmeaProcessGPVTG(NmeaPacket); // report packet type foundpacket = NMEA_GPVTG; } } else if(rxBuffer->datalength >= rxBuffer->size) { // if we found no packet, and the buffer is full // we're logjammed, flush entire buffer bufferFlush(rxBuffer); } return foundpacket; } /** * Prosesses NMEA GPGGA sentences * \param[in] Buffer for parsed nmea GPGGA sentence */ void nmeaProcessGPGGA(char* packet) { char *tokens; //*p, *tokens[MAXTOKENS]; char *last; char *delimiter = ","; char *pEnd; uint8_t i=0; long deg,desim; double degrees, minutesfrac; #ifdef NMEA_DEBUG_GGA //PIOS_COM_SendFormattedStringNonBlocking(COM_USART1,"NMEA: %s\r\n",packet); #endif // start parsing just after "GPGGA," // attempt to reject empty packets right away if(packet[6]==',' && packet[7]==',') return; // tokenizer for nmea sentence /*for ((p = strtok_r(packet, ",", &last)); p; (p = strtok_r(NULL, ",", &last)), i++) { if (i < MAXTOKENS - 1) tokens[i] = p; }*/ //GPGGA header tokens = strtok_r(packet, delimiter, &last); // get UTC time [hhmmss.sss] tokens = strtok_r(NULL, delimiter, &last); strcpy(GpsInfo.PosLLA.TimeOfFix.c,tokens); // next field: latitude // get latitude [ddmm.mmmmm] tokens = strtok_r(NULL, delimiter, &last); //strcpy(GpsInfo.PosLLA.lat.c,token); if(1) // 5 desimal output { deg=strtol (tokens,&pEnd,10); desim=strtol (pEnd+1,NULL,10); GpsInfo.PosLLA.lat.i=deg*100000+desim; } else // 4 desimal output { deg=strtol (tokens,&pEnd,10); desim=strtol (pEnd+1,NULL,10); GpsInfo.PosLLA.lat.i=deg*10000+desim; } // convert to pure degrees [dd.dddd] format /* minutesfrac = modf(GpsInfo.PosLLA.lat.f/100, °rees); GpsInfo.PosLLA.lat.f = degrees + (minutesfrac*100)/60; // convert to radians GpsInfo.PosLLA.lat.f *= (M_PI/180);*/ // next field: N/S indicator // correct latitute for N/S tokens = strtok_r(NULL, delimiter, &last); //if(*tokens[3] == 'S') GpsInfo.PosLLA.lat.f = -GpsInfo.PosLLA.lat.f; // next field: longitude // get longitude [ddmm.mmmmm] tokens = strtok_r(NULL, delimiter, &last); //strcpy(GpsInfo.PosLLA.lon.c,token); if(1) // 5 desimal output { deg=strtol (tokens,&pEnd,10); desim=strtol (pEnd+1,NULL,10); GpsInfo.PosLLA.lon.i=deg*100000+desim; } else // 4 desimal output { deg=strtol (tokens,&pEnd,10); desim=strtol (pEnd+1,NULL,10); GpsInfo.PosLLA.lon.i=deg*10000+desim; } // convert to pure degrees [dd.dddd] format /*minutesfrac = modf(GpsInfo.PosLLA.lon.f/100, °rees); GpsInfo.PosLLA.lon.f = degrees + (minutesfrac*100)/60; // convert to radians GpsInfo.PosLLA.lon.f *= (M_PI/180);*/ // next field: E/W indicator // correct latitute for E/W tokens = strtok_r(NULL, delimiter, &last); //if(*tokens[5] == 'W') GpsInfo.PosLLA.lon.f = -GpsInfo.PosLLA.lon.f; // next field: position fix status // position fix status // 0 = Invalid, 1 = Valid SPS, 2 = Valid DGPS, 3 = Valid PPS // check for good position fix tokens = strtok_r(NULL, delimiter, &last); //if(&tokens[6] != '0' || &tokens[6] != 0) // GpsInfo.PosLLA.updates++; // next field: satellites used // get number of satellites used in GPS solution tokens = strtok_r(NULL, delimiter, &last); GpsInfo.numSVs = atoi(tokens); // next field: HDOP (horizontal dilution of precision) tokens = strtok_r(NULL, delimiter, &last);//HDOP, nein gebraucht // next field: altitude // get altitude (in meters mm.m) tokens = strtok_r(NULL, delimiter, &last); //strcpy(GpsInfo.PosLLA.alt.c,tokens); //reuse variables for alt deg=strtol (tokens,&pEnd,10); desim=strtol (pEnd+1,NULL,10); GpsInfo.PosLLA.alt.i=deg*10+desim; // next field: altitude units, always 'M' // next field: geoid seperation // next field: seperation units // next field: DGPS age // next field: DGPS station ID // next field: checksum } /** * Prosesses NMEA GPVTG sentences * \param[in] Buffer for parsed nmea GPVTG sentence */ void nmeaProcessGPVTG(char* packet) { char *tokens; //*p, *tokens[MAXTOKENS]; char *last; char *delimiter = ","; char *pEnd; uint8_t i=0; #ifdef NMEA_DEBUG_VTG //PIOS_COM_SendFormattedStringNonBlocking(COM_USART1,"NMEA: %s\r\n",packet); #endif // start parsing just after "GPVTG," // attempt to reject empty packets right away if(packet[6]==',' && packet[7]==',') return; // tokenizer for nmea sentence /*for ((p = strtok_r(packet, ",", &last)); p; (p = strtok_r(NULL, ",", &last)), i++) { if (i < MAXTOKENS - 1) tokens[i] = p; }*/ //GPVTG header tokens = strtok_r(packet, delimiter, &last); // get course (true north ref) in degrees [ddd.dd] tokens = strtok_r(NULL, delimiter, &last); strcpy(GpsInfo.VelHS.heading.c,tokens); // next field: 'T' tokens = strtok_r(NULL, delimiter, &last); // next field: course (magnetic north) // get course (magnetic north ref) in degrees [ddd.dd] tokens = strtok_r(NULL, delimiter, &last); //strcpy(GpsInfo.VelHS.heading.c,tokens[1]); // next field: 'M' tokens = strtok_r(NULL, delimiter, &last); // next field: speed (knots) // get speed in knots tokens = strtok_r(NULL, delimiter, &last); //strcpy(GpsInfo.VelHS.speed.f,tokens[5]); // next field: 'N' tokens = strtok_r(NULL, delimiter, &last); // next field: speed (km/h) // get speed in km/h tokens = strtok_r(NULL, delimiter, &last); strcpy(GpsInfo.VelHS.speed.c,tokens); // next field: 'K' // next field: checksum GpsInfo.VelHS.updates++; }