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