/* * Copyright (C) 2021-2025 Roger Clark, VK3KYY / G4KYF * Colin Durbridge, G4EML * Daniel Caujolle-Bert, F1RMB * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "main.h" #include "user_interface/uiGlobals.h" #include "user_interface/uiUtilities.h" #include "interfaces/gps.h" #include "user_interface/uiLocalisation.h" #include "usb/usb_com.h" #if defined(PLATFORM_MD9600) #include "interfaces/remoteHead.h" #endif #if defined(HAS_GPS) #define GPS_RX_BUFFERS_MAX 3U #if defined(LOG_GPS_DATA) #define LOG_RAM_BUF_SIZE 4096U static #if ! (CPU_MK22FN512VLL12) // Doesn't fit on DM1801 __attribute__((section(".data.$RAM2"))) #endif uint8_t NMEARecordingBuffer[LOG_RAM_BUF_SIZE]; #define LOG_FLASH_16MB_START_ADDRESS (14 * 1024 * 1024) // Last 2MB #define LOG_FLASH_16MB_MEM_SIZE (2 * 1024 * 1024) #if defined(CPU_MK22FN512VLL12) #define LOG_FLASH_2MB_START_ADDRESS (1 * 1024 * 1024) // Last 1MB #define LOG_FLASH_2MB_MEM_SIZE (1 * 1024 * 1024) #define LOG_FLASH_1MB_START_ADDRESS DMRID_MEMORY_LOCATION_2 // DMRId section 2 #define LOG_FLASH_1MB_MEM_SIZE ((1 * 1024 * 1024) - DMRID_MEMORY_LOCATION_2) // Last 288k #endif // CPU_MK22FN512VLL12 static uint32_t gpsLogFlashStartAddress = 0U; static uint32_t gpsLogFlashMemSize = 0U; static uint32_t gpsLogMemOffset = 0U; static bool gpsIsLogging = false; #endif // LOG_GPS_DATA typedef struct { uint8_t data[GPS_LINE_LENGTH]; uint8_t length; } gpsReceiveBuffer_t; typedef struct { gpsReceiveBuffer_t rxBuffers[GPS_RX_BUFFERS_MAX]; uint8_t linesCount; uint8_t bufferIndex; uint8_t charPosition; } gpsReceiveData_t; gpsData_t gpsData = { .Status = (GPS_STATUS_FIX_UPDATED | GPS_STATUS_FIXTYPE_UPDATED), .Latitude = 0U, .LatitudeHiRes = 0.0, .Longitude = 0U, .LongitudeHiRes = 0.0, .SatsInViewGP = 0U, .SatsInViewBD = 0U, .currentGPSIndex = 0U, .currentBDIndex = 0U, .Time = 0U, .AccuracyInCm = 0U, .HeightInM = 0U #if defined(STM32F405xx) , .baudRateIndex = 0U #endif }; #if defined(CPU_MK22FN512VLL12) static volatile gpsReceiveData_t gpsRxData; #else static gpsReceiveData_t gpsRxData; static bool detectingBaudRate = true; static uint32_t gpsBaudDetectionDelayCounter = 0U; #endif static uint8_t gpsBufferIndexProcessing = 0U; #if defined(STM32F405xx) static uint8_t gpsDMABuf[GPS_DMA_BUFFER_SIZE]; // double buffer (two halves) for GPS UART DMA receive #endif // // Grace number of GSV invalid frames (in GSV) before setting the fix lost in the Status. // On the MD-9600, from time to time a bunch if invalid frames ('V') are send by the GPS module. // This prevents to get some "Fix lost" -> "Fix acquired" sequences. #define GPS_FIX_GRACE_MAX 15U static uint8_t gpsFixGraceCount = 0U; //#define GNSS_MULTI_GSV 1 // Uncomment this to support GB, GA and GL GSVs sentences (not with genuine GPS modules). //#define USE_DUMMY_GPS_DATA #ifdef USE_DUMMY_GPS_DATA const char *DUMMY_GPS_DATA[] = { "$GNZDA,074101.000,20,09,2022,,*42", "$GNGGA,074102.000,3858.1,N,14602.1,E,1,03,4.38,51.4,M,-1.5,M,,*4F", "$GPGSA,A,2,29,25,12,,,,,,,,,,4.49,4.38,1.00,1*16", "$BDGSA,A,2,,,,,,,,,,,,,4.49,4.38,1.00,4*0D", "$GPGSV,2,1,07,29,63,203,24,2,62,148,,25,61,339,24,20,33,129,*48", "$GPGSV,2,2,07,12,32,18,25,26,7,220,,23,1,340,*76", "$BDGSV,1,1,04,1,47,0,,4,44,22,,3,34,313,,2,12,289,*5D", "$GNRMC,074102.000,A,3758.10000,N,14502.10000,E,5.000,275.00,200922,,,A*53", "$GNZDA,074101.000,20,09,2022,,*42", "$GNGGA,074102.000,3858.10000,S,14502.10000,E,1,03,4.38,51.4,M,-1.5,M,,*4F", "$GPGSA,A,2,29,25,12,,,,,,,,,,4.49,4.38,1.00,1*16", "$BDGSA,A,2,,,,,,,,,,,,,4.49,4.38,1.00,4*0D", "$GPGSV,2,1,07,29,63,203,24,2,62,148,,25,61,339,24,20,33,129,*48", "$GPGSV,2,2,07,12,32,18,25,26,7,220,,23,1,340,*76", "$BDGSV,1,1,04,1,47,0,,4,44,22,,3,34,313,,2,12,289,*5D", "$GNRMC,074102.000,A,3758.00000,N,14502.00000,E,5.000,275.00,200922,,,A*53" }; uint32_t dummyGpsDataIndex = 0; #endif #if defined(CPU_MK22FN512VLL12) #include "fsl_uart.h" static bool gpsIrqIsEnabled = false; extern double round(double); // implementation in aprs.c #endif #if defined(LOG_GPS_DATA) static void gpsLogNMEAData(const char *nmea, uint8_t length); #endif void gpsInit(void) { memset((gpsReceiveData_t *)&gpsRxData, 0, sizeof(gpsReceiveData_t)); gpsBufferIndexProcessing = 0U; #if defined(LOG_GPS_DATA) #if defined(STM32F405xx) gpsLogFlashStartAddress = LOG_FLASH_16MB_START_ADDRESS; gpsLogFlashMemSize = LOG_FLASH_16MB_MEM_SIZE; #else switch (flashChipPartNumber) { case 0x4015: // 4015 25Q16 16M-bits 2M-bytes, used in the Baofeng DM-1801 ? case 0x4017: // 4017 25Q64 64M-bits 8M-bytes, used in Roger's special GD-77 radios modified on the TYT production line. gpsLogFlashStartAddress = LOG_FLASH_2MB_START_ADDRESS; gpsLogFlashMemSize = LOG_FLASH_2MB_MEM_SIZE; break; case 0x4018: // 4018 25Q128 128M-bits 16M-bytes, used in MD9600 and Daniel's modified GD-77. case 0x7018: // 7018 25Q128JV 128M-bits 16M-bytes, KI5GZK's modified GD-77. gpsLogFlashStartAddress = LOG_FLASH_16MB_START_ADDRESS; gpsLogFlashMemSize = LOG_FLASH_16MB_MEM_SIZE; break; case 0x4014: // 4014 25Q80 8M-bits 1M-bytes, used in the GD-77. default: if (voicePromptDataIsLoaded) { gpsLogFlashStartAddress = LOG_FLASH_1MB_START_ADDRESS; gpsLogFlashMemSize = LOG_FLASH_1MB_MEM_SIZE; } else { gpsLogFlashStartAddress = DMRID_MEMORY_LOCATION_1; gpsLogFlashMemSize = ((1 * 1024 * 1024) - DMRID_MEMORY_LOCATION_1); // last 832k } break; } #endif #endif // LOG_GPS_DATA #if defined(CPU_MK22FN512VLL12) uart_config_t config; UART_GetDefaultConfig(&config); config.baudRate_Bps = 9600; config.enableTx = false; config.enableRx = true; UART_Init(UART0, &config, CLOCK_GetFreq(SYS_CLK)); UART_EnableRxFIFO(UART0, true); UART_EnableInterrupts(UART0, kUART_RxDataRegFullInterruptEnable | kUART_RxOverrunInterruptEnable); gpsOff(); #endif } #if defined(CPU_MK22FN512VLL12) void UART0_RX_TX_IRQHandler (void) { uint32_t uartFlags = UART_GetStatusFlags(UART0); while ((kUART_RxDataRegFullFlag | kUART_RxOverrunFlag) & uartFlags) { gpsProcessChar(UART_ReadByte(UART0)); uartFlags = UART_GetStatusFlags(UART0); }; SDK_ISR_EXIT_BARRIER; } #endif // Converts the GPS data from the format DDDmm.mmmm into our custom format Int<<23 + frac*1000 static uint32_t gpsLatLongConvert(const char *input, double *dValue) { double value = strtod(input, NULL); double degrees = (int32_t)(value * 1E-2); // get degrees value -= (degrees * 1E2); degrees += (value / 60.0); *dValue = degrees; return ((((uint32_t)degrees) << 23) + ((uint32_t)(round(((degrees - (uint32_t)degrees) * 1E5))))); } static time_t_custom gpsTimeConvert(const char *time, const char *date) { struct tm gpsDateTime; uint8_t Date; uint8_t Month; uint8_t Year; uint8_t Hours; uint8_t Minutes; uint8_t Seconds; if (strlen(date) == 6) { Year = ((date[4] - '0') * 10) + (date[5] - '0'); Month = ((date[2] - '0') * 10) + (date[3] - '0'); Date = ((date[0] - '0') * 10) + (date[1] - '0'); } else { Year = 21; Month = 12; Date = 25; } if (Year < 70) { Year = Year + 100; } if (strlen(time) >= 6) { Seconds = ((time[4] - '0') * 10) + (time[5] - '0'); Minutes = ((time[2] - '0') * 10) + (time[3] - '0'); Hours = ((time[0] - '0') * 10) + (time[1] - '0'); } else { Seconds = 0; Minutes = 0; Hours = 0; } memset(&gpsDateTime, 0x00, sizeof(struct tm)); // clear entire struct gpsDateTime.tm_mday = Date; /* day of the month, 1 to 31 */ gpsDateTime.tm_mon = Month - 1; /* months since January, 0 to 11 */ gpsDateTime.tm_year = Year; /* years since 1900 */ gpsDateTime.tm_hour = Hours; gpsDateTime.tm_min = Minutes; gpsDateTime.tm_sec = Seconds; return mktime_custom(&gpsDateTime); } static void getParam(const char *line, char *result, int entryno, int reslen) { int count = 0; int respoint = 0; for (size_t i = 0; i < strlen(line); i++) { if (line[i] == ',') { count++; if (count == entryno) { result[respoint] = 0; return; } else { respoint = 0; } } else { result[respoint++] = line[i]; if (respoint > (reslen - 1)) { result[0] = 0; return; } } } result[0] = 0; } #if !(defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)) static #endif void gpsProcessChar(uint8_t rxchar) { if ((rxchar != '\r') && (gpsRxData.charPosition < GPS_LINE_LENGTH)) { if (rxchar >= '!') // Ignore '\n' { gpsRxData.rxBuffers[gpsRxData.bufferIndex].data[gpsRxData.charPosition++] = rxchar; } } else { gpsRxData.rxBuffers[gpsRxData.bufferIndex].data[gpsRxData.charPosition] = 0; gpsRxData.rxBuffers[gpsRxData.bufferIndex].length = gpsRxData.charPosition; gpsRxData.linesCount++; gpsRxData.bufferIndex = (gpsRxData.bufferIndex + 1) % GPS_RX_BUFFERS_MAX; gpsRxData.rxBuffers[gpsRxData.bufferIndex].length = 0U; gpsRxData.charPosition = 0U; } } void gpsDataInputStartStop(bool enable) { #if defined(STM32F405xx) if (enable) { HAL_UART_Receive_DMA( #if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) &huart1 #elif defined(PLATFORM_MD380) &huart3 #endif , gpsDMABuf, GPS_DMA_BUFFER_SIZE); } else { HAL_UART_DMAStop( #if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) &huart1 #elif defined(PLATFORM_MD380) &huart3 #endif ); } #elif defined(CPU_MK22FN512VLL12) if (enable) { EnableIRQ(UART0_RX_TX_IRQn); } else { DisableIRQ(UART0_RX_TX_IRQn); } gpsIrqIsEnabled = enable; #endif // STM32F405xx memset((gpsReceiveData_t *)&gpsRxData, 0, sizeof(gpsReceiveData_t)); gpsBufferIndexProcessing = 0U; } void gpsOn(void) { memset((uint8_t *)&gpsData, 0x00, sizeof(gpsData_t));// reset everything gpsFixGraceCount = 0U; gpsData.Status |= (GPS_STATUS_FIX_UPDATED | GPS_STATUS_FIXTYPE_UPDATED); #if ! defined(PLATFORM_MD9600) gpsPower(true); //Turn on the power to the GPS module, except on MD9600 where it also powers the mic so is turned on all the time in application main #endif #if defined(PLATFORM_MD9600) if(remoteHeadActive) { remoteHeadGpsDataInputStartStop(true); } else #endif { gpsDataInputStartStop(true); } } void gpsOff(void) { #if ! defined(PLATFORM_MD9600) gpsPower(false); //Turn off the power to the GPS module except on MD9600 where the mic and GPS are powered at the same time. #endif #if defined(PLATFORM_MD9600) if(remoteHeadActive) { remoteHeadGpsDataInputStartStop(false); } else #endif { gpsDataInputStartStop(false); } memset((uint8_t *)&gpsData, 0x00, sizeof(gpsData_t));// reset everything gpsFixGraceCount = 0U; gpsData.Status |= (GPS_STATUS_FIX_UPDATED | GPS_STATUS_FIXTYPE_UPDATED); } #if defined(STM32F405xx) void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { #if defined(PLATFORM_MD9600) if(huart == &huart3) { remoteHead_UART_RxHalfCpltCallback(); } else #endif { #if defined(PLATFORM_MD9600) // GPS module power is never turned off on this platform, so we must ignore any data from it. if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) == GPS_MODE_OFF) { return; } #endif for (size_t i = 0; i < 32; i++) { gpsProcessChar(gpsDMABuf[i]); } } } #endif // STM32F405xx void gpsOnUsingQuickKey(bool on) { uiEvent_t e = { .buttons = BUTTON_NONE, .keys = NO_KEYCODE, .rotary = 0, .events = FUNCTION_EVENT, .hasEvent = true, .time = ticksGetMillis() }; bool sendEvent = false; // Use QuickKey to change GPS power status if (on && (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_NOT_DETECTED) && (SETTINGS_GPS_MODE_GET(nonVolatileSettings) < (NUM_GPS_MODES - 1))) { e.function = QUICKKEY_MENUVALUE(MENU_GENERAL, MENU_GENERAL_OPTIONS_GPS_ENTRY_NUMBER, FUNC_RIGHT); sendEvent = true; } else if((on == false) && (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)) { e.function = QUICKKEY_MENUVALUE(MENU_GENERAL, MENU_GENERAL_OPTIONS_GPS_ENTRY_NUMBER, FUNC_LEFT); sendEvent = true; } if (sendEvent) { menuSystemPushNewMenu(MENU_GENERAL); menuSystemCallCurrentMenuTick(&e); } } #if defined(STM32F405xx) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { #if defined(PLATFORM_MD9600) if(huart == &huart3) { remoteHead_UART_RxCpltCallback(); } else #endif { /* Debugging only HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin); */ #if defined(PLATFORM_MD9600) // GPS module power is never turned off on this platform, so we must ignore any data from it. if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) == GPS_MODE_OFF) { return; } #endif for (size_t i = 0; i < 32; i++) { gpsProcessChar(gpsDMABuf[(GPS_DMA_BUFFER_SIZE / 2) + i]); } } } #endif // STM32F405xx #if defined(PLATFORM_MD9600) //turn the GPS on and off. Note this also turns the Microphone power on and off, so the GPS must always be On when the radio is in use. void gpsAndMicPower(bool on) { if (on) { GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9_Msk) | GPIO_MODER_MODER9_0; //Set the GPIOA Pin 9 to GPIO mode HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET); //set it high } else { GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9_Msk) | GPIO_MODER_MODER9_0; // Set the GPIOA Pin 9 to GPIO mode HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET); //set it Low } } #endif void gpsPower(bool on) { #if defined(STM32F405xx) if (on) { #if defined(PLATFORM_MD380) GPIOD->MODER = (GPIOD->MODER & ~GPIO_MODER_MODER8_Msk) | GPIO_MODER_MODER8_0; //Set the GPIOD Pin 8 to GPIO mode HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_SET); //set it high #else // PLATFORM_MD380 #if defined(PLATFORM_MD9600) if(remoteHeadActive) { remoteHeadGpsPower(true); //G4EML... if remote head is fitted then send command to power on the GPS/Mic } //G4EML... always also use the hardware power control as the mic might be locally connected. #endif // PLATFORM_MD9600 GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9_Msk) | GPIO_MODER_MODER9_0; //Set the GPIOA Pin 9 to GPIO mode HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET); //set it high #endif // PLATFORM_MD380 } else { #if defined(PLATFORM_MD380) GPIOD->MODER = (GPIOD->MODER & ~GPIO_MODER_MODER8_Msk) | GPIO_MODER_MODER8_0; // Set the GPIOD Pin 8 to GPIO mode HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_RESET); //set it Low #else // PLATFORM_MD380 #if defined(PLATFORM_MD9600) if(remoteHeadActive) { remoteHeadGpsPower(false); //G4EML... if remote head is fitted then send command to power on the GPS/Mic } //G4EML... always also use the hardware power control as the mic might be locally connected. #endif // PLATFORM_MD9600 GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9_Msk) | GPIO_MODER_MODER9_0; // Set the GPIOA Pin 9 to GPIO mode HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET); //set it Low #endif // PLATFORM_MD380 } #elif defined(CPU_MK22FN512VLL12) && ! defined(PLATFORM_RD5R) // On RD5R, this pin is connected to the flash light LED GPIO_PinWrite(GPIO_GPS_Power, Pin_GPS_Power, (on ? 1U : 0U)); #endif // STM32F405xx } static bool jumpToNextField(char **pField, uint8_t *curLength) { // Searching end of the current field. while (**pField != '\0' && (*curLength > 0)) { (*pField)++; (*curLength)--; } // Another field exists, position *pField on it. if (*curLength > 0) { (*pField)++; (*curLength)--; return true; } return false; } static void convertCommasToDelimiters(char *line, uint8_t len) { for (uint8_t i = 0; i < len ; i++) { if ((line[i] == ',') || (line[i] == '*')) { line[i] = '\0'; } } } static int getNmeaInt(const char *str) { if (*str != '\0') { return atoi(str); } return -1; } #if defined(GNSS_MULTI_GSV) static bool populateSatelliteData(char **NMEA, uint8_t *curLength, gpsSatellitesData_t *sat, uint8_t *counter, bool *isDifferent, int8_t sub) #else static bool populateSatelliteData(char **NMEA, uint8_t *curLength, gpsSatellitesData_t *sat, uint8_t *counter, bool *isDifferent) #endif { gpsSatellitesData_t pSat; int prn; memcpy(&pSat, sat, sizeof(gpsSatellitesData_t)); prn = getNmeaInt(*NMEA); sat->Number = #if defined(GNSS_MULTI_GSV) ((sub && (prn > sub)) ? (prn - sub) : prn); #else prn; #endif jumpToNextField(NMEA, curLength); sat->El = getNmeaInt(*NMEA); jumpToNextField(NMEA, curLength); sat->Az = getNmeaInt(*NMEA); jumpToNextField(NMEA, curLength); sat->RSSI = getNmeaInt(*NMEA); (*counter)++; #if defined(USE_DUMMY_GPS_DATA) *isDifferent = true; #else *isDifferent = (memcmp(&pSat, sat, sizeof(gpsSatellitesData_t)) != 0); #endif return ((*counter < GPS_STORAGE_MAX) && jumpToNextField(NMEA, curLength)); } #if defined(GNSS_MULTI_GSV) static uint16_t processGSV(char *line, uint8_t lineLength, gpsSatellitesData_t *satsStorage, uint8_t *counter, bool *satsAreDifferents, int8_t sub) #else static uint16_t processGSV(char *line, uint8_t lineLength, gpsSatellitesData_t *satsStorage, uint8_t *counter, bool *satsAreDifferents) #endif { uint16_t totalSatsInView = 0; if (lineLength > 7) { int msgNumber; bool satIsDifferent; char *pos = &line[7]; // skip message header convertCommasToDelimiters(line, lineLength); lineLength -= 7; // take care of header skipping // Skip total messages jumpToNextField(&pos, &lineLength); msgNumber = atoi(pos); if (msgNumber == 1) { // Reset storage counter *counter = 0; } jumpToNextField(&pos, &lineLength); totalSatsInView = atoi(pos); jumpToNextField(&pos, &lineLength); while(*counter < GPS_STORAGE_MAX) { #if defined(GNSS_MULTI_GSV) bool res = (populateSatelliteData(&pos, &lineLength, (satsStorage + *counter), counter, &satIsDifferent, sub) && (lineLength > 4)); #else bool res = (populateSatelliteData(&pos, &lineLength, (satsStorage + *counter), counter, &satIsDifferent) && (lineLength > 4)); #endif *satsAreDifferents |= satIsDifferent; if (!res) // we have to stop the parsing here { break; } } } return totalSatsInView; } #if 0 #if USE_CHECKSUM // https://en.wikipedia.org/wiki/Fletcher%27s_checksum // Slightly modified, as length will never be > GPS_LINE_LENGTH static uint16_t fletcher16(const uint8_t *data, uint8_t len) { uint32_t c0 = 0; uint32_t c1 = 0; do { c0 = c0 + *data++; c1 = c1 + c0; } while (--len); c0 = c0 % 255; c1 = c1 % 255; return (c1 << 8 | c0); } #endif #endif #if defined(STM32F405xx) void gpsStartBaudRateAutodetection(void) { gpsData.baudRateIndex = 0U; detectingBaudRate = true; gpsBaudDetectionDelayCounter = 0U; } void gpsStopBaudRateAutodetection(void) { detectingBaudRate = false; gpsBaudDetectionDelayCounter = 0U; } void gpsSetBaudRateByIndex(uint8_t baudRateIndex) { const uint32_t baudRateArray[] = { 9600, 19200, 38400, 57600, 115200, 230400, 460800 }; huart1.Instance = USART1; huart1.Init.BaudRate = baudRateArray[(baudRateIndex % GPS_BAUDRATES_ARRAY_MAX)]; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } #endif void gpsTick(void) { char gpsLine[GPS_LINE_LENGTH] = { 0 }; char param[6][20]; char line[20]; char *p; uint8_t lineLength = 0U; if ((menuSystemGetCurrentMenuNumber() != UI_TX_SCREEN) && (SETTINGS_GPS_MODE_GET(nonVolatileSettings) >= GPS_MODE_OFF) && ((ticksGetMillis() % 500) == 0) #if defined(STM32F405xx) && (HAL_DMA_GetState(&hdma_usart1_rx) != HAL_DMA_STATE_BUSY) #elif defined(CPU_MK22FN512VLL12) && (gpsIrqIsEnabled == false) #endif ) { gpsDataInputStartStop(true); } #if defined(STM32F405xx) if (detectingBaudRate) { gpsBaudDetectionDelayCounter++; if ((SETTINGS_GPS_MODE_GET(nonVolatileSettings) != GPS_MODE_OFF) && (gpsRxData.linesCount == 0U) && ((gpsBaudDetectionDelayCounter % 500) == 0U)) { gpsData.baudRateIndex = (gpsData.baudRateIndex + 1U) % GPS_BAUDRATES_ARRAY_MAX; gpsSetBaudRateByIndex(gpsData.baudRateIndex); gpsDataInputStartStop(true); } } #endif #ifdef USE_DUMMY_GPS_DATA if (true) { strcpy(gpsLine, DUMMY_GPS_DATA[dummyGpsDataIndex % (sizeof(DUMMY_GPS_DATA) / sizeof(DUMMY_GPS_DATA[0]))]); dummyGpsDataIndex++; lineLength = strlen(gpsLine); #else // USE_DUMMY_GPS_DATA if (gpsRxData.linesCount > 0U) { #if defined(STM32F405xx) if (detectingBaudRate) { settingsSet(nonVolatileSettings.gpsModeAndBaudsIndex, SETTINGS_GPS_BAUDRATE_SET(nonVolatileSettings, (gpsData.baudRateIndex + 1U))); detectingBaudRate = false; } #endif if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) == GPS_NOT_DETECTED) { settingsSet(nonVolatileSettings.gpsModeAndBaudsIndex, SETTINGS_GPS_MODE_SET(nonVolatileSettings, GPS_MODE_OFF)); gpsOff(); return; } lineLength = gpsRxData.rxBuffers[gpsBufferIndexProcessing].length; memcpy(gpsLine, (uint8_t *)&gpsRxData.rxBuffers[gpsBufferIndexProcessing].data[0], (lineLength + 1)); gpsRxData.linesCount--; gpsBufferIndexProcessing = (gpsBufferIndexProcessing + 1) % GPS_RX_BUFFERS_MAX; #endif // USE_DUMMY_GPS_DATA if (gpsLine[0] == '$') { if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) >= GPS_MODE_ON_NMEA) { USB_DEBUG_printf("%s\r\n", gpsLine);// Note. NMEA protocol requires CR LF #if defined(LOG_GPS_DATA) // log everything once per minute if ((gpsData.Time % 60) == 0) { gpsLogNMEAData(gpsLine, lineLength); } #endif } if (memcmp(&gpsLine[3], "GGA", 3) == 0)// message that contains accuracy (HDOP) and altitude { getParam(gpsLine, line, 9, 20);// get accuracy (HDOP) uint16_t hdop = (uint16_t)((strtod(line, NULL)) * 1E2); if (hdop != gpsData.AccuracyInCm) { gpsData.AccuracyInCm = hdop; gpsData.Status |= (GPS_STATUS_HDOP_UPDATED | GPS_STATUS_HAS_HDOP); } getParam(gpsLine, line, 10, 20);// get height p = strchr(line, '.'); if (p != NULL) { *p = '\0'; } int16_t height = atoi(line); if (height != gpsData.HeightInM) { gpsData.HeightInM = height; gpsData.Status |= (GPS_STATUS_HEIGHT_UPDATED | GPS_STATUS_HAS_HEIGHT); } } else if (memcmp(&gpsLine[3], "RMC", 3) == 0) //is this the LAT Long and Time Message? { char statusLetter[20]; int currentMenu = menuSystemGetCurrentMenuNumber(); #if defined(LOG_GPS_DATA) if ((gpsData.Time % 60) != 0) { gpsLogNMEAData(gpsLine, lineLength); } #endif // check if it has the date and time. getParam(gpsLine, param[0], 2, 20); //get parameter 2 which is GMT Time as hhmmss.sss getParam(gpsLine, statusLetter, 3, 20); getParam(gpsLine, param[5], 10, 20); //get parameter 10 which is Date as ddmmyy if ((param[0][0] != 0) && (param[5][0] != 0)) { gpsData.Time = gpsTimeConvert(param[0], param[5]); // Clock skew ? if (((gpsData.Status & (GPS_STATUS_HAS_FIX | GPS_STATUS_3D_FIX)) == (GPS_STATUS_HAS_FIX | GPS_STATUS_3D_FIX)) && (abs(uiDataGlobal.dateTimeSecs - gpsData.Time) > 5)) { uiSetUTCDateTimeInSecs(gpsData.Time); #if defined(STM32F405xx) setRtc_custom(uiDataGlobal.dateTimeSecs); #endif // Update Satellite screen (re-enter) bool restartSatMenu = (currentMenu == MENU_SATELLITE); if (restartSatMenu) { menuDataGlobal.currentItemIndex = 0; // will restart in prediction list menuSystemPopPreviousMenu(); menuSatelliteSetFullReload(); } menuSatelliteScreenClearPredictions(false); if (restartSatMenu) { menuSystemPushNewMenu(MENU_SATELLITE); } } gpsData.Status |= (GPS_STATUS_TIME_UPDATED | GPS_STATUS_HAS_TIME); } // Have a fix // if (statusLetter[0] == 'A') { gpsFixGraceCount = GPS_FIX_GRACE_MAX; if ((gpsData.Status & GPS_STATUS_HAS_FIX) == 0) { gpsData.Status |= (GPS_STATUS_HAS_FIX | GPS_STATUS_FIX_UPDATED); } } else // Have no fix { if (gpsFixGraceCount > 0U) { gpsFixGraceCount--; } else { // Clear fix type status if (gpsData.Status & (GPS_STATUS_2D_FIX | GPS_STATUS_3D_FIX)) { gpsData.Status &= ~(GPS_STATUS_2D_FIX | GPS_STATUS_3D_FIX); gpsData.Status |= GPS_STATUS_FIXTYPE_UPDATED; } // Loosing fix status if (gpsData.Status & GPS_STATUS_HAS_FIX) { gpsData.Status &= ~(GPS_STATUS_HAS_FIX | GPS_STATUS_HAS_POSITION | GPS_STATUS_HAS_HDOP | GPS_STATUS_HAS_COURSE | GPS_STATUS_HAS_SPEED | GPS_STATUS_HAS_HEIGHT | GPS_STATUS_HAS_TIME); gpsData.Status |= GPS_STATUS_FIX_UPDATED; } } return; } getParam(gpsLine, param[1] , 4, 20); //get parameter 4 which is Latitude a ddmm.mmmm getParam(gpsLine, param[2] , 5, 20); //get parameter 5 which is N/S getParam(gpsLine, param[3] , 6, 20); //get parameter 6 which is Longitude a dddmm.mmmm getParam(gpsLine, param[4] , 7, 20); //get parameter 7 which is E/W gpsData.Latitude = gpsLatLongConvert(param[1], &gpsData.LatitudeHiRes); if (param[2][0] == 'S') { gpsData.Latitude = gpsData.Latitude | 0x80000000; gpsData.LatitudeHiRes = -gpsData.LatitudeHiRes; } gpsData.Longitude = gpsLatLongConvert(param[3], &gpsData.LongitudeHiRes); if (param[4][0] == 'W') { gpsData.Longitude = gpsData.Longitude | 0x80000000; gpsData.LongitudeHiRes = -gpsData.LongitudeHiRes; } if (((currentMenu != UI_TX_SCREEN) && (currentMenu != MENU_SATELLITE)) && ((nonVolatileSettings.location.lat != gpsData.Latitude) || (nonVolatileSettings.location.lon != gpsData.Longitude))) { settingsSet(nonVolatileSettings.location.lat, gpsData.Latitude); settingsSet(nonVolatileSettings.location.lon, gpsData.Longitude); menuSatelliteScreenClearPredictions(false); gpsData.Status |= (GPS_STATUS_POSITION_UPDATED | GPS_STATUS_HAS_POSITION); } getParam(gpsLine, line, 8, 20); if (strchr(line, '.') != NULL) // There is a value { uint16_t v = (uint16_t)((strtod(line, NULL)) * 1E2); if (v != gpsData.SpeedInHundredthKn) { gpsData.SpeedInHundredthKn = v; gpsData.Status |= (GPS_STATUS_SPEED_UPDATED | GPS_STATUS_HAS_SPEED); } } else if (gpsData.Status & GPS_STATUS_HAS_SPEED) // Value cleared { gpsData.SpeedInHundredthKn = 0U; gpsData.Status &= ~GPS_STATUS_HAS_SPEED; gpsData.Status |= GPS_STATUS_SPEED_UPDATED; } getParam(gpsLine, line, 9, 20); if (strchr(line, '.') != NULL) // There is a value { uint16_t v = (uint16_t)((strtod(line, NULL)) * 1E2); if (v != gpsData.CourseInHundredthDeg) { gpsData.CourseInHundredthDeg = v; gpsData.Status |= (GPS_STATUS_COURSE_UPDATED | GPS_STATUS_HAS_COURSE); } } else if (gpsData.Status & GPS_STATUS_HAS_COURSE) // Value cleared { gpsData.CourseInHundredthDeg = 0U; gpsData.Status &= ~GPS_STATUS_HAS_COURSE; gpsData.Status |= GPS_STATUS_COURSE_UPDATED; } } else if (memcmp(&gpsLine[3], "GSA", 3) == 0) // DOP and active satellites { getParam(gpsLine, param[0], 2, 20); // get parameter 2 which is mode 'A' or 'M' getParam(gpsLine, param[1], 3, 20); // get parameter 3 which is Fix type if (param[0][0] == 'A') { if (gpsData.Status & GPS_STATUS_HAS_FIX) { // We just got a 3D fix if ((param[1][0] == '3') && ((gpsData.Status & GPS_STATUS_3D_FIX) == 0)) { gpsData.Status &= ~GPS_STATUS_2D_FIX; gpsData.Status |= (GPS_STATUS_3D_FIX | GPS_STATUS_FIXTYPE_UPDATED); } // We just got a 2D fix else if ((param[1][0] == '2') && ((gpsData.Status & GPS_STATUS_2D_FIX) == 0)) { gpsData.Status &= ~GPS_STATUS_3D_FIX; gpsData.Status |= (GPS_STATUS_2D_FIX | GPS_STATUS_FIXTYPE_UPDATED); } } } else { // Clear 2D and 3D fix, if any sets if (gpsData.Status & (GPS_STATUS_2D_FIX | GPS_STATUS_3D_FIX)) { gpsData.Status &= ~(GPS_STATUS_2D_FIX | GPS_STATUS_3D_FIX); gpsData.Status |= GPS_STATUS_FIXTYPE_UPDATED; } } } else if (memcmp(&gpsLine[3], "GSV", 3) == 0) { uint16_t *pSatsInView = NULL; gpsSatellitesData_t *pSats = NULL; uint8_t *pCurrentGPSIndex = NULL; uint16_t prevSatsInView = 0; uint32_t gpsStatus; #if defined(GNSS_MULTI_GSV) int8_t prnSub = 0; bool gbSatSub = false; bool glSatSub = false; #endif if ((gpsLine[1] == 'G') && (gpsLine[2] == 'P')) // GPS GSV { pSatsInView = &gpsData.SatsInViewGP; pSats = &gpsData.GPSatellites[0]; pCurrentGPSIndex = &gpsData.currentGPSIndex; gpsStatus = GPS_STATUS_GPS_SATS_UPDATED; } else if((gpsLine[1] == 'B') // (BD) BeiDou GSV` #if defined(GNSS_MULTI_GSV) || ((gpsLine[1] == 'G') && ((gbSatSub = (gpsLine[2] == 'B')) // (GB) BeiDou GSV (100 should be subtracted to the PRN number to determine the BeiDou PRN number) || (gpsLine[2] == 'A') // (GA) Galileo GSV || ((glSatSub = (gpsLine[2] == 'L'))) // (GL) GLONASS GSV (64 should be subtracted to the PRN number to determine the GLONASS PRN number) ) ) #endif ) { pSatsInView = &gpsData.SatsInViewBD; pSats = &gpsData.BDSatellites[0]; pCurrentGPSIndex = &gpsData.currentBDIndex; gpsStatus = GPS_STATUS_BD_SATS_UPDATED; #if defined(GNSS_MULTI_GSV) prnSub = (gbSatSub ? 100 : (glSatSub ? 64 : 0)); #endif } if (pSatsInView && pSats && pCurrentGPSIndex) { bool satsAreDifferents = false; prevSatsInView = *pSatsInView; #if defined(GNSS_MULTI_GSV) *pSatsInView = processGSV(gpsLine, lineLength, pSats, pCurrentGPSIndex, &satsAreDifferents, prnSub); #else *pSatsInView = processGSV(gpsLine, lineLength, pSats, pCurrentGPSIndex, &satsAreDifferents); #endif if ((*pSatsInView != prevSatsInView) || satsAreDifferents) { gpsData.Status |= gpsStatus; } } } } } #if defined(STM32F405xx) else { // Stops trying to detect the GPS baudrate (no GPS available), after // 10 complete runs. if (detectingBaudRate && (gpsBaudDetectionDelayCounter % ((500 * GPS_BAUDRATES_ARRAY_MAX) * 10U) == 0U)) { detectingBaudRate = false; } } #endif } #if defined(LOG_GPS_DATA) static void gpsLogByte(char data) { NMEARecordingBuffer[gpsLogMemOffset % LOG_RAM_BUF_SIZE] = data; gpsLogMemOffset++; if ((gpsLogMemOffset % LOG_RAM_BUF_SIZE) == 0) { SPI_Flash_write((gpsLogFlashStartAddress + (((gpsLogMemOffset / LOG_RAM_BUF_SIZE) - 1) * LOG_RAM_BUF_SIZE)), NMEARecordingBuffer, LOG_RAM_BUF_SIZE); gpsLogMemOffset %= gpsLogFlashMemSize; } } static void gpsLogNMEAData(const char *nmea, uint8_t length) { if ((SETTINGS_GPS_MODE_GET(nonVolatileSettings) == GPS_MODE_ON_LOG) && gpsIsLogging && ((gpsData.Status & GPS_STATUS_HAS_FIX) != 0)) { for(uint8_t i = 0; i < length; i++) { gpsLogByte(nmea[i]); } } } void gpsLoggingStart(void) { if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) == GPS_MODE_ON_LOG) { if (gpsIsLogging == false) { #if defined(CPU_MK22FN512VLL12) // Invalidate the whole DMRIDs, if exist, on MK22. // Except the ones with 16Mb flash chip if ((gpsLogFlashStartAddress != LOG_FLASH_16MB_START_ADDRESS) && (dmrIDCacheGetCount() > 0)) { dmrIDCacheClear(); // Ensure dmrIDLookup() fails memset(NMEARecordingBuffer, 0x00, DMRID_HEADER_LENGTH); SPI_Flash_write(DMRID_MEMORY_LOCATION_1, NMEARecordingBuffer, DMRID_HEADER_LENGTH); } #endif // Load the last stored flash block gpsLogMemOffset = nonVolatileSettings.gpsLogMemOffset; SPI_Flash_read((((gpsLogFlashStartAddress + gpsLogMemOffset) / LOG_RAM_BUF_SIZE) * LOG_RAM_BUF_SIZE), NMEARecordingBuffer, LOG_RAM_BUF_SIZE); // write start marker that can be read as text gpsLogByte('A'); gpsLogByte('A'); gpsLogByte('A'); gpsLogByte('A'); gpsIsLogging = true; } } } void gpsLoggingStop(void) { if (gpsIsLogging) { gpsIsLogging = false; // write end marker which can be read as text gpsLogByte('Z'); gpsLogByte('Z'); if (gpsLogMemOffset % LOG_RAM_BUF_SIZE) { SPI_Flash_write((gpsLogFlashStartAddress + ((gpsLogMemOffset / LOG_RAM_BUF_SIZE) * LOG_RAM_BUF_SIZE)), NMEARecordingBuffer, (gpsLogMemOffset % LOG_RAM_BUF_SIZE)); } settingsSet(nonVolatileSettings.gpsLogMemOffset, gpsLogMemOffset); } } void gpsLoggingClear(void) { uint32_t numFlashPages = gpsLogFlashMemSize / LOG_RAM_BUF_SIZE; uint32_t addr; watchdogRun(false); for(uint32_t i = 0; i < numFlashPages; i++) { addr = gpsLogFlashStartAddress + (i * LOG_RAM_BUF_SIZE); SPI_Flash_eraseSector(addr); } watchdogRun(true); } #endif // LOG_GPS_DATA #endif // HAS_GPS