From 54d5ae7e16a49226848e33f42892e48dc9e5c4ee Mon Sep 17 00:00:00 2001 From: MaxineMuster <146550015+MaxineMuster@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:30:01 +0100 Subject: [PATCH] Driver for NEO6M GPS module (#1890) * Driver vor NEO6m GPS receiver Tested on W800 * Some tweaks: empty RX buffer betewwn readings on several unsuccessful reads, try enabling the NMEA RMC messages again * empty buffer every second remove commented out old code * fix broken log if no lat/long NS or EW is present * Add "savecfg" argument to save config to NEO after configuring - so next time we might use it without sending commands to NEO module this way, only NEOs Tx is needed (and OpenBekens Rx) --------- Co-authored-by: openshwprojects <85486843+openshwprojects@users.noreply.github.com> --- openBeken_win32_mvsc2017.vcxproj | 3 +- platforms/obk_main.cmake | 1 + platforms/obk_main.mk | 1 + src/driver/drv_main.c | 17 ++ src/driver/drv_neo6m.c | 425 +++++++++++++++++++++++++++++++ src/driver/drv_neo6m.h | 5 + src/hal/bl602/hal_uart_bl602.c | 4 +- src/obk_config.h | 7 +- 8 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 src/driver/drv_neo6m.c create mode 100644 src/driver/drv_neo6m.h diff --git a/openBeken_win32_mvsc2017.vcxproj b/openBeken_win32_mvsc2017.vcxproj index 47bbf57fc..a2f392436 100644 --- a/openBeken_win32_mvsc2017.vcxproj +++ b/openBeken_win32_mvsc2017.vcxproj @@ -248,6 +248,7 @@ + @@ -1321,4 +1322,4 @@ - \ No newline at end of file + diff --git a/platforms/obk_main.cmake b/platforms/obk_main.cmake index bfc5d5855..a2f00a464 100644 --- a/platforms/obk_main.cmake +++ b/platforms/obk_main.cmake @@ -101,6 +101,7 @@ set(OBKM_SRC ${OBK_SRCS}driver/drv_ntp.c ${OBK_SRCS}driver/drv_deviceclock.c ${OBK_SRCS}driver/drv_ds3231.c + ${OBK_SRCS}driver/drv_neo6m.c ${OBK_SRCS}libraries/obktime/obktime.c ${OBK_SRCS}driver/drv_timed_events.c ${OBK_SRCS}driver/drv_openWeatherMap.c diff --git a/platforms/obk_main.mk b/platforms/obk_main.mk index fc32ae59f..23aabaee2 100644 --- a/platforms/obk_main.mk +++ b/platforms/obk_main.mk @@ -121,6 +121,7 @@ OBKM_SRC += $(OBK_SRCS)driver/drv_multiPinI2CScanner.c OBKM_SRC += $(OBK_SRCS)driver/drv_ntp.c OBKM_SRC += $(OBK_SRCS)driver/drv_deviceclock.c OBKM_SRC += $(OBK_SRCS)driver/drv_ds3231.c +OBKM_SRC += $(OBK_SRCS)driver/drv_neo6m.c OBKM_SRC += $(OBK_SRCS)libraries/obktime/obktime.c OBKM_SRC += $(OBK_SRCS)driver/drv_timed_events.c OBKM_SRC += $(OBK_SRCS)driver/drv_openWeatherMap.c diff --git a/src/driver/drv_main.c b/src/driver/drv_main.c index 217ec6f74..be60259d0 100644 --- a/src/driver/drv_main.c +++ b/src/driver/drv_main.c @@ -3,6 +3,7 @@ #include "drv_bl0937.h" #include "drv_bl0942.h" #include "drv_bl_shared.h" +#include "drv_neo6m.h" #include "drv_cse7766.h" #include "drv_ir.h" #include "drv_rc.h" @@ -1368,6 +1369,22 @@ static driver_t g_drivers[] = { NULL, // onHassDiscovery false, // loaded }, +#endif +#if ENABLE_DRIVER_NEO6M + //drvdetail:{"name":"NEO6M", + //drvdetail:"title":"TODO", + //drvdetail:"descr":"NEO6M is a GPS chip which uses UART protocol for communication. By default, it uses 9600 baud, but you can also enable it with other baud rates by using 'startDriver NEO6M '.", + //drvdetail:"requires":""} + { "NEO6M", // Driver Name + NEO6M_UART_Init, // Init + NEO6M_UART_RunEverySecond, // onEverySecond + NEO6M_AppendInformationToHTTPIndexPage, // appendInformationToHTTPIndexPage + NULL, // runQuickTick + NULL, // stopFunction + NULL, // onChannelChanged + NULL, // onHassDiscovery + false, // loaded + }, #endif //{ "", NULL, NULL, NULL, NULL, NULL, NULL, NULL, false }, }; diff --git a/src/driver/drv_neo6m.c b/src/driver/drv_neo6m.c new file mode 100644 index 000000000..cfa32940b --- /dev/null +++ b/src/driver/drv_neo6m.c @@ -0,0 +1,425 @@ +#include "../obk_config.h" +#if ENABLE_DRIVER_NEO6M +#include +#include +#include + +#include "../logging/logging.h" +#include "../new_pins.h" +#include "../cmnds/cmd_public.h" +#include "drv_uart.h" + +#include "../httpserver/new_http.h" + +static unsigned short NEO6M_baudRate = 9600; + +#define NEO6M_UART_RECEIVE_BUFFER_SIZE 512 + + +static char fakelat[12]={0}; +static char fakelong[12]={0}; +static char tempstr[50]; + +#include "drv_deviceclock.h" +static bool setclock2gps=false; +#if ENABLE_TIME_SUNRISE_SUNSET +static bool setlatlong2gps=false; +#endif + + + +static int H,M,S,SS,DD,MM,YY; +static float Lat_f,Long_f; +static char NS,EW; +static bool gpslocked=false; +static uint8_t failedTries = 0; + + +enum { +NMEA_TIME, +NMEA_LOCK, +NMEA_LAT, +NMEA_LAT_DIR, +NMEA_LONG, +NMEA_LONG_DIR, +NMEA_SPEED, +NMEA_COURSE, +NMEA_DATE, +NMEA_MAGVAR, +NMEA_MAGVAR_DIR, +NMEA_MODE, +// parse will stop at *, so not needed during parsing +//NMEA_STAR, +//NMEA_CHECKSUM, +NMEA_WORDS +}; + +#define LEAP_YEAR(Y) ((!(Y%4) && (Y%100)) || !(Y%400)) +static const uint8_t DaysMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +const uint32_t SECS_PER_MIN = 60UL; +const uint32_t SECS_PER_HOUR = 3600UL; +const uint32_t SECS_PER_DAY = 3600UL * 24UL; +const uint32_t MINS_PER_HOUR = 60UL; + +//simple "mktime" replacement to calculate epoch from a date +// note: as mktime, month are 0-11 (not 1 - 12) +uint32_t obkmktime(int yr, uint8_t month, uint8_t day, uint8_t hr, uint8_t min, uint8_t sec) { + // to avoid mktime - this enlarges the image especially for BL602! + // so calculate seconds from epoch locally + // we start by calculating the number of days since 1970 + int i; + uint16_t days; + uint32_t t; + days = (yr - 1970) * 365; // days per full years + // add one day every leap year - first leap after 1970 is 1972, possible leap years every 4 years + for (i=1972; i < yr; i+=4) days += LEAP_YEAR(i); + for (i=0; i=3); + dateread = (sscanf (Nvalue[NMEA_DATE],"%2d%2d%2d",&DD,&MM,&YY) ==3); + YY+=2000; + + gpslocked=(Nvalue[NMEA_LOCK][0]=='A'); + if (!gpslocked) { + ADDLOG_WARN(LOG_FEATURE_DRV, "parseGPS: no GPS lock! %s\r\n", timeread && dateread ? "Date/time might be valid." :""); + + }; + + NS='?'; // otherwise the string will be cut here + if (Nvalue[NMEA_LAT_DIR][0]) NS=Nvalue[NMEA_LAT_DIR][0]; + + // NMEA returns degrees as degree and "minutes" of latitude/longitude + // we want it as a decimal representation + // e.g. from input value + // 5213.00212 = 52.216702 + // ddmm.mmmmm + // we would need the minutes 13.00212 to be converted + // the result is value / 60 + // here: 1.03272 / 60 = 0.216702 + + float frac; + int whole=0; // need to get tw digits as %2d, using "%2f" will take the whole value as float?!? + + ADDLOG_DEBUG(LOG_FEATURE_DRV,"NEO6M: fakelat=%s Nvalue[NMEA_LAT]=%s ",fakelat,Nvalue[NMEA_LAT]); + if (*fakelat && strlen(fakelat) <= strlen(Nvalue[NMEA_LAT])) memcpy(Nvalue[NMEA_LAT],fakelat,strlen(fakelat)); + Lat_f=atof(Nvalue[NMEA_LAT]); + whole=(int)(Lat_f/100); + Lat_f=(float)whole + (Lat_f - 100*whole)/60; + + + + EW='?'; // otherwise the string will be cut here + if (Nvalue[NMEA_LONG_DIR][0]) EW=Nvalue[NMEA_LONG_DIR][0]; + + + ADDLOG_DEBUG(LOG_FEATURE_DRV,"NEO6M: fakelong=%s Nvalue[NMEA_LONG]=%s ",fakelong,Nvalue[NMEA_LONG]); + + if (*fakelong && strlen(fakelong) <= strlen(Nvalue[NMEA_LONG])) { + memcpy(Nvalue[NMEA_LONG],fakelong,strlen(fakelong)); // note: we will not copy "\0" but ony overwrite (some) numbers... + } + Long_f = atof(Nvalue[NMEA_LONG]); + whole = (int)(Long_f/100); + Long_f=(float)whole + (Long_f - 100*whole)/60; + uint32_t epoch_time=obkmktime(YY,MM-1,DD,H,M,S); + tempstr[0]='\0'; + if (setclock2gps && gpslocked && timeread && dateread){ + TIME_setDeviceTime(epoch_time); +// ADDLOG_INFO(LOG_FEATURE_DRV,"local clock set to UTC time read"); + strcat(tempstr, "(clock "); + } +#if ENABLE_TIME_SUNRISE_SUNSET + if( setlatlong2gps && gpslocked){ + TIME_setLatitude(Lat_f); + TIME_setLongitude(Long_f); +// ADDLOG_INFO(LOG_FEATURE_DRV,"latitude set to %f, longitude set to %f",Lat_f, Long_f); + strcat(tempstr,tempstr[0]? "and lat/long " : "(lat/log "); + } +#endif + if (tempstr[0]) strcat(tempstr,"set to GPS data)"); + ADDLOG_INFO(LOG_FEATURE_DRV, + "Read GPS DATA:%02i.%02i.%i - %02i:%02i:%02i.%02i (epoch=%u) LAT=%f%c - LONG=%f%c %s\r\n", DD,MM,YY,H,M,S,SS,epoch_time,Lat_f,NS,Long_f,EW,tempstr); + + p = strstr(++p, "$GPRMC,"); + + } else{ + ADDLOG_INFO(LOG_FEATURE_DRV, "parseGPS: p is NULL -- data=%s .... ",data); + failedTries++; + } +//ADDLOG_INFO(LOG_FEATURE_DRV, "... end of parseGPS "); + +} + + +static int UART_GetNextPacket(void) { +//ADDLOG_INFO(LOG_FEATURE_DRV, "UART_GetNextPacket - start \r\n"); + int cs; + char data[NEO6M_UART_RECEIVE_BUFFER_SIZE+5]={0}; + cs = UART_GetDataSize(); +//ADDLOG_INFO(LOG_FEATURE_DRV, "UART_GetNextPacket - cs=%i \r\n",cs); + + int i=0; + for (i=0; i< cs; i++){ + data[i]=UART_GetByte(i); + } + data[i]=0; +//ADDLOG_INFO(LOG_FEATURE_DRV, "UART_GetNextPacket i=%i - data=%s\r\n",i,data); + UART_ConsumeBytes(cs-1); + parseGPS(data); + return 0; +} + + + +// send "$EIGPQ,RMC*3A\r\n" to request/poll DATA +static void UART_WritePollReq(void) { + uint8_t send[]="$EIGPQ,RMC*3A\r\n$EIGPQ,RMC*3A\r\n"; + + for (int i = 0; i < sizeof(send); i++) { + UART_SendByte(send[i]); + } +} + + +/* + # Disabling all NMEA sentences +$PUBX,40,GGA,0,0,0,0*5A // Disable GGA +$PUBX,40,GLL,0,0,0,0*5C // Disable GLL +$PUBX,40,GSA,0,0,0,0*4E // Disable GSA +$PUBX,40,GSV,0,0,0,0*59 // Disable GSV +$PUBX,40,RMC,0,0,0,0*47 // Disable RMC +$PUBX,40,VTG,0,0,0,0*5E // Disable VTG +$PUBX,40,ZDA,0,0,0,0*44 // Disable ZDA +*/ +static void UART_WriteDisableNMEA(void) { + char send[][26]={ + "$PUBX,40,GGA,0,0,0,0*5A\r\n", + "$PUBX,40,GGA,0,0,0,0*5A\r\n", +// "$PUBX,40,GGA,0,1,0,0*5B\r\n", // Enable GGA + "$PUBX,40,GLL,0,0,0,0*5C\r\n", + "$PUBX,40,GSA,0,0,0,0*4E\r\n", + "$PUBX,40,GSV,0,0,0,0*59\r\n", +// "$PUBX,40,RMC,0,0,0,0*47\r\n", // Disable RMC + "$PUBX,40,RMC,0,1,0,0*46\r\n", // Enable RMC + "$PUBX,40,VTG,0,0,0,0*5E\r\n", + "$PUBX,40,ZDA,0,0,0,0*44\r\n" + }; + byte b; + for (int i = 0; i < 7; i++) { + for (int j = 0; j < sizeof(send[i]); j++) { + b = (byte)send[i][j]; + UART_SendByte(b); + } + } +} + +static void UART_Write_SAVE(void) { +uint8_t cfg_cfg_save_all[] ={ 0xB5,0x62,0x06,0x09,0x0D,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1D,0xAB }; + byte b; + for (int j = 0; j < sizeof(cfg_cfg_save_all); j++) { + b = (byte)cfg_cfg_save_all[j]; + UART_SendByte(b); + } +} + + + + +static void UART_WriteEnableRMC(void) { + char send[][26]={ + "$PUBX,40,RMC,0,1,0,0*46\r\n", // Enable RMC + "$PUBX,40,RMC,0,1,0,0*46\r\n" // Enable RMC + }; + byte b; + for (int i = 0; i < 7; i++) { + for (int j = 0; j < sizeof(send[i]); j++) { + b = (byte)send[i][j]; + UART_SendByte(b); + } + } +} + +static void Init(void) { + +} + +// THIS IS called by 'startDriver NEO6M' command +// You can set alternate baud with 'startDriver NEO6M ' syntax +void NEO6M_UART_Init(void) { + Init(); + uint8_t temp=Tokenizer_GetArgsCount()-1; + const char* arg; + const char* fake=NULL; + NEO6M_baudRate = 9600; // default value +#if ENABLE_TIME_SUNRISE_SUNSET + setlatlong2gps = false; +#endif + setclock2gps = false; + fakelat[0]='\0'; + fakelong[0]='\0'; + bool savecfg=0; + for (int i=1; i<=temp; i++) { + arg = Tokenizer_GetArg(i); + + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: argument %i/%i is %s",i,temp,arg); + + if ( arg && !stricmp(arg,"setclock")) { + setclock2gps=true; + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: setting local clock to UTC time read if GPS is synched"); + } + if ( arg && !stricmp(arg,"setlatlong")) { +#if ENABLE_TIME_SUNRISE_SUNSET + setlatlong2gps=true; + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: setting lat/long to values read by GPS"); +#else + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: local clock and/or SUNRISE_SUNSET not enabled - ignoring \"setlatlong\""); +#endif + } + fake=strstr(arg, "fakelat="); + if ( arg && fake ) { + int i=0; + fake += 8; +// ADDLOG_INFO(LOG_FEATURE_DRV,"fake=%s fakelat=%s",fake,fakelat); + while(fake[i]){ + fakelat[i]=fake[i]; + i++; + }; + fakelat[i]='\0'; + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: fakelat=%s",fakelat); + } + fake=NULL; + fake=strstr(arg, "fakelong="); + if ( arg && (fake=strstr(arg, "fakelong=")) ) { + int i=0; + fake +=9; +// ADDLOG_INFO(LOG_FEATURE_DRV,"fake=%s fakelong=%s",fake,fakelong); + while(fake[i]){ + fakelong[i]=fake[i]; + i++; + }; + fakelong[i]='\0'; + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: fakelong=%s",fakelong); + } + + fake=NULL; + fake=strstr(arg, "savecfg"); + if ( arg && fake ) { + savecfg=1; + } + + if (Tokenizer_IsArgInteger(i)){ + NEO6M_baudRate = Tokenizer_GetArgInteger(i); + ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M: baudrate set to %i",NEO6M_baudRate); + } + } + UART_InitUART(NEO6M_baudRate, 0, 0); + UART_InitReceiveRingBuffer(NEO6M_UART_RECEIVE_BUFFER_SIZE); + UART_WriteDisableNMEA(); + if (savecfg) UART_Write_SAVE(); +} + + + + + +void NEO6M_requestData(void) { +//ADDLOG_INFO(LOG_FEATURE_DRV, "NEO6M_requestData \r\n"); + UART_GetNextPacket(); +} + + + +void NEO6M_UART_RunEverySecond(void) { + + int cs= UART_GetDataSize(); + +//ADDLOG_INFO(LOG_FEATURE_DRV,"NEO6M_UART_RunEverySecond: UART_GetDataSize() = %i \r\n",cs); + + if (g_secondsElapsed % 5 == 0) { // every 5 seconds + NEO6M_requestData(); + } + else { + if (cs > 1){ +//ADDLOG_INFO(LOG_FEATURE_DRV, "EO6M_UART_RunEverySecond: UART_ConsumeBytes(%i); \r\n",cs -1); + UART_ConsumeBytes(cs -1); // empty buffer so the old values are not read + } + } + if (g_secondsElapsed % 5 == 4) { // every 5 seconds, one second before requesting data + if (failedTries >=5){ + UART_WriteEnableRMC(); // try to enable NMEA RMC messages, just in case + failedTries = 0; + } +// we could also disable all NMEA outpot and only poll the data +// this will be less serial "traffic", but time is available only on next cycle, so one additional second later +//ADDLOG_INFO(LOG_FEATURE_DRV, "EO6M_UART_RunEverySecond: calling UART_WritePollReq \r\n"); +// UART_WritePollReq(); + } + +} + + +void NEO6M_AppendInformationToHTTPIndexPage(http_request_t *request, int bPreState) +{ + if (bPreState) + return; + if (gpslocked) hprintf255(request, "
GPS: %i-%02i-%02iT%02i:%02i:%02i Lat: %f%c Long: %f%c
", YY,MM,DD,H,M,S,Lat_f,NS,Long_f,EW); +} +#endif // ENABLE_DRIVER_NEO6M diff --git a/src/driver/drv_neo6m.h b/src/driver/drv_neo6m.h new file mode 100644 index 000000000..d6e5566c3 --- /dev/null +++ b/src/driver/drv_neo6m.h @@ -0,0 +1,5 @@ +#pragma once + +void NEO6M_UART_Init(void); +void NEO6M_UART_RunEverySecond(void); +void NEO6M_AppendInformationToHTTPIndexPage(http_request_t *request); diff --git a/src/hal/bl602/hal_uart_bl602.c b/src/hal/bl602/hal_uart_bl602.c index 6e85846f8..3a045100a 100644 --- a/src/hal/bl602/hal_uart_bl602.c +++ b/src/hal/bl602/hal_uart_bl602.c @@ -47,7 +47,7 @@ static void console_cb_read(int fd, void* param) { fd_console = fd; buffer[ret] = 0; - addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "BL602 received: %s\n", buffer); + addLogAdv(LOG_DEBUG, LOG_FEATURE_ENERGYMETER, "BL602 received: %s\n", buffer); for(i = 0; i < ret; i++) { UART_AppendByteToReceiveRingBuffer(buffer[i]); @@ -78,6 +78,8 @@ int HAL_UART_Init(int baud, int parity, bool hwflowc, int txOverride, int rxOver //bl_irq_register(UART1_IRQn, MY_UART1_IRQHandler); //bl_irq_enable(UART1_IRQn); //vfs_uart_init_simple_mode(0, 7, 16, baud, "/dev/ttyS0"); + // Info: serial 1: RX_pin=3 TX_pin=4 + // dev-Board: IO3=Pin 7 IO4=Pin 26 if(CFG_HasFlag(OBK_FLAG_USE_SECONDARY_UART)) { diff --git a/src/obk_config.h b/src/obk_config.h index 815af035f..9f1e6bbf9 100644 --- a/src/obk_config.h +++ b/src/obk_config.h @@ -108,7 +108,9 @@ #define ENABLE_ADVANCED_CHANNELTYPES_DISCOVERY 1 #define ENABLE_LITTLEFS 1 #define NEW_TCP_SERVER 1 - +#define ENABLE_DRIVER_NEO6M 1 +#define ENABLE_TIME_SUNRISE_SUNSET 1 +#define ENABLE_TIME_DST 1 #elif WINDOWS #if LINUX @@ -230,6 +232,7 @@ //#undef ENABLE_DRIVER_BL0942 #define ENABLE_DRIVER_IRREMOTEESP 1 //#endif +#define ENABLE_DRIVER_NEO6M 1 #elif PLATFORM_BEKEN @@ -300,6 +303,7 @@ #if PLATFORM_BEKEN_NEW #define NEW_TCP_SERVER 1 #endif +#define ENABLE_DRIVER_NEO6M 1 // ENABLE_I2C_ is a syntax for // our I2C system defines for drv_i2c_main.c @@ -558,6 +562,7 @@ #define ENABLE_DRIVER_TUYAMCU 1 #define ENABLE_DRIVER_DS1820 1 #define ENABLE_DRIVER_BMPI2C 1 +#define ENABLE_DRIVER_NEO6M 1 // #define ENABLE_OBK_BERRY 1