備忘錄_20160105(定位) 修改 回首頁

程式 2026-05-05 11:46:57 1777952817 100
u-blox neo-m8n (gps) + esp32 c3 supermini

u-blox neo-m8n (gps) + esp32 c3 supermini

gpio圖
easyeda設計pcb
洞洞板的實品照
印刷電路板的實品照
敬請期待
// RX, TX 用 pin4,5 才不會跟 arduino 使用中的 UART0 相衝 // neo-m8n 的 VCC -- esp32 c3 supermini 的 3.3V // neo-m8n 的 RX -- esp32 c3 supermini 的 TX (gpio5) // neo-m8n 的 TX -- esp32 c3 supermini 的 RX (gpio4) // neo-m8n 的 GND -- esp32 c3 supermini 的 GND // 目前是 led 燈亮,代表有抓到可用的 gps 訊號 #include <WiFi.h> #include <HTTPClient.h> #include <HardwareSerial.h> #include <string.h> // gpio #define iLedPin 8 #define iGpsTx 5 #define iGpsRx 4 // NMEA 0183 - $GPRMC, $GNRMC #define iIdxMessageType 0 #define iIdxUTCTime 1 #define iIdxStatus 2 #define iIdxLatVal 3 #define iIdxLatDir 4 #define iIdxLonVal 5 #define iIdxLonDir 6 #define iSpeedOverGround 7 #define iTrackAngle 8 #define iDDMMYY 9 #define iMagneticVariationVal 10 #define iMagneticVariationDir 11 #define iModeAndChecksum 12 // wifi information #define iMaxSsidLen 64 #define iMaxPassLen 64 struct stcSSIDPASS { char caSSID[iMaxSsidLen]; char caPASS[iMaxPassLen]; }; stcSSIDPASS arySSIDPASS[]= { {"godroad", "66666666"}, {"ssid2", "pass2"}, {"ssid3", "pass3"} }; int iSSIDPASSAmount=sizeof(arySSIDPASS)/sizeof(arySSIDPASS[0]); int iSSIDPASSIndex = -1; int iLastSSIDPASSIndex=-1; unsigned long ulLastTicks = 0; // neo-6m / neo-m8n #define iGpsBufMax 512 HardwareSerial oHSGps(1); bool booGpsBuf=false; char caGpsBuf[iGpsBufMax]; int iGpsBufIdx=0; char caLastGpsBuf[iGpsBufMax]="initial"; // CMT+8 bool getBooIsAllDigit(const char caIn[], int iLength) { for(int i=0; i<iLength; i++) { if(caIn[i]<'0' || caIn[i]>'9') { return false; } } return true; } void calcGMTp8(const char caDDMMYY[], const char caUTCTime[], char caGMTp8[]) { /* 傳入: DDMMYY = "ddmmyy" UTCTime = "hhmmss" 傳出: "yyyymmddhhmmss" */ if(strlen(caDDMMYY)<6 || strlen(caUTCTime)<6) { strcpy(caGMTp8, "00000000000000"); return; } if(getBooIsAllDigit(caDDMMYY, 6)==false || getBooIsAllDigit(caUTCTime, 6)==false) { strcpy(caGMTp8, "00000000000000"); return; } int iYear = 2000 + (caDDMMYY[4]-'0')*10 + (caDDMMYY[5]-'0'); int iMonth = (caDDMMYY[2]-'0')*10 + (caDDMMYY[3]-'0'); int iDay = (caDDMMYY[0]-'0')*10 + (caDDMMYY[1]-'0'); int iHour = (caUTCTime[0]-'0')*10 + (caUTCTime[1]-'0'); int iMinute = (caUTCTime[2]-'0')*10 + (caUTCTime[3]-'0'); int iSecond = (caUTCTime[4]-'0')*10 + (caUTCTime[5]-'0'); iHour+=8; if(iHour>=24) { iHour-=24; iDay+=1; } int iaDaysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31}; bool booLeap=(iYear%4==0 && iYear%100!=0) || (iYear%400==0); if(booLeap==true) { iaDaysInMonth[1]=29; } if(iDay>iaDaysInMonth[iMonth-1]) { iDay=1; iMonth+=1; if(iMonth>12) { iMonth=1; iYear+=1; } } sprintf(caGMTp8, "%04d%02d%02d%02d%02d%02d", iYear, iMonth, iDay, iHour, iMinute, iSecond); } float getFRealDeg(const char caVal[], char cDir, bool &booSuccess) { /* 經度 dddmm.mmmm E,W 緯度 ddmm.mmmm N,S 成功:return degree, booSuccess=true 失敗:return 0, booSuccess=false */ booSuccess=false; if(!caVal || caVal[0]=='\0') { return 0; } if(!(cDir=='N' || cDir=='S' || cDir=='E' || cDir=='W')) { return 0; } int iDegLen = (cDir=='N' || cDir=='S') ? 2 : 3; int iLen=strlen(caVal); if (iLen<=iDegLen) { return 0; } for (int i=0; i<iLen; i++) { char c = caVal[i]; if (!((c>='0' && c<='9') || c == '.')) { return 0; } } int iDeg=0; for(int i=0; i<iDegLen; i++) { iDeg=iDeg*10+(caVal[i]-'0'); } float fMin = atof(caVal+iDegLen); if (fMin < 0 || fMin >= 60) return 0; float fFinalDeg = iDeg + fMin / 60.0; if (cDir=='S' || cDir=='W') { fFinalDeg = -fFinalDeg; } booSuccess=true; return fFinalDeg; } bool getBooIsConnected() { if (WiFi.status()==WL_CONNECTED) { return true; } WiFi.disconnect(true); delay(200); iSSIDPASSIndex = (iSSIDPASSIndex + 1) % iSSIDPASSAmount; iLastSSIDPASSIndex=iSSIDPASSIndex; WiFi.begin(arySSIDPASS[iSSIDPASSIndex].caSSID, arySSIDPASS[iSSIDPASSIndex].caPASS); WiFi.setTxPower(WIFI_POWER_8_5dBm); // c3 硬體問題,要將功率調小。 /* WIFI_POWER_19_5dBm // 最強 WIFI_POWER_19dBm WIFI_POWER_18_5dBm WIFI_POWER_17dBm WIFI_POWER_15dBm WIFI_POWER_13dBm WIFI_POWER_11dBm WIFI_POWER_8_5dBm WIFI_POWER_7dBm WIFI_POWER_5dBm WIFI_POWER_2dBm WIFI_POWER_MINUS_1dBm // 最弱 */ int iTimeout = 100; while (WiFi.status()!=WL_CONNECTED && iTimeout>0) { delay(100); iTimeout--; } if (WiFi.status()!=WL_CONNECTED) { return false; } Serial.print(arySSIDPASS[iLastSSIDPASSIndex].caSSID); Serial.println(" connected"); return true; } void sendData(const char caDDMMYY[], const char caUTCTime[], float fLat, float fLon) { if(isnan(fLat) || isnan(fLon)) { return; } if(getBooIsConnected()==false) { return; } char caGMTp8[16]; calcGMTp8(caDDMMYY, caUTCTime, caGMTp8); if(strcmp(caGMTp8, "00000000000000")==0) { return; } char caUrl[600]; int iCode; { HTTPClient oHttpClient; snprintf( caUrl, sizeof(caUrl), "https://liujiaje.com/resources/20260408_esp32_lolin_d32_neo-6m-gps/api/gps.php" "?lat=%.6f&lon=%.6f&gmtp8=%s&ssid=%s", fLat, fLon, caGMTp8, arySSIDPASS[iLastSSIDPASSIndex].caSSID); oHttpClient.begin(caUrl); oHttpClient.setTimeout(5000); iCode=oHttpClient.GET(); if(iCode > 0) { ulLastTicks = millis(); } oHttpClient.end(); } { HTTPClient oHttpClient; char caRecTime[30]; snprintf( caRecTime, sizeof(caRecTime), "20%c%c/%c%c/%c%c%%20%c%c:%c%c:%c%c", caDDMMYY[4], caDDMMYY[5], caDDMMYY[2], caDDMMYY[3], caDDMMYY[0], caDDMMYY[1], caUTCTime[0], caUTCTime[1], caUTCTime[2], caUTCTime[3], caUTCTime[4], caUTCTime[5]); snprintf( caUrl, sizeof(caUrl), "https://web.godroad.tw/api5/getgpstest.aspx" "?lat=%.6f&lng=%.6f&rectime=%s&cid=5554", fLat, fLon, caRecTime); oHttpClient.begin(caUrl); oHttpClient.setTimeout(5000); iCode=oHttpClient.GET(); if(iCode>0) { ulLastTicks = millis(); } oHttpClient.end(); } } void keepAlive() { HTTPClient oHttpClient; if (getBooIsConnected()==false) { return; } char caUrl[300]; snprintf( caUrl, sizeof(caUrl), "https://liujiaje.com/resources/20260408_esp32_lolin_d32_neo-6m-gps/api/keepalive.php" "?ssid=%s&lastgpsbuffer=%s", arySSIDPASS[iLastSSIDPASSIndex].caSSID, caLastGpsBuf); oHttpClient.begin(caUrl); oHttpClient.setTimeout(5000); int iCode = oHttpClient.GET(); if(iCode>0) { ulLastTicks = millis(); } oHttpClient.end(); } void setup() { Serial.begin(9600); delay(1000); WiFi.mode(WIFI_STA); delay(200); pinMode(iLedPin, OUTPUT); digitalWrite(iLedPin, HIGH); // dark oHSGps.begin(9600, SERIAL_8N1, iGpsRx, iGpsTx); getBooIsConnected(); // pre-connect } int getIValFromHex(char c1) { if(c1>='0' && c1<='9') { return c1-'0'; } if(c1>='A' && c1<='F') { return c1-'A'+10; } if(c1>='a' && c1<='f') { return c1-'a'+10; } return -1; } bool getBooCheckNMEAChecksum(const char caGpsBuf[]) { if(!caGpsBuf || caGpsBuf[0]!='$') { return false; } const char *cPtr=caGpsBuf+1; uint8_t uiCheckSum=0; // 計算 xor(從 $ 之後(不含)到 * 之前(不含)) int iCount=0; while (*cPtr && *cPtr!='*' && iCount<200) { uiCheckSum ^= (uint8_t)(*cPtr); cPtr++; iCount++; } // 沒找到 '*' if(*cPtr != '*') { return false; } // 需要兩個 hex 字元 if(*(cPtr+1)=='\0' || *(cPtr+2)=='\0') { return false; } int iHI=getIValFromHex(*(cPtr+1)); int iLO=getIValFromHex(*(cPtr+2)); if(iHI<0 || iLO<0) { return false; } uint8_t uiGiven=(iHI<<4) | iLO; return uiCheckSum==uiGiven; } void dealWith_RMC(char caGpsBuf[]) { const int iMaxItem=16; const int iMaxLen=132; char caItem[iMaxItem][iMaxLen]={}; int iItemIdx=0; int iItemLen=0; for(int i=0, iMax=strlen(caGpsBuf); i<iMax; i++) { if(caGpsBuf[i]==',') { caItem[iItemIdx][iItemLen]='\0'; iItemIdx++; iItemLen=0; if(iItemIdx>=iMaxItem) { break; } } else { if(iItemLen<(iMaxLen-1)) { caItem[iItemIdx][iItemLen]=caGpsBuf[i]; iItemLen++; } } } if(iItemLen!=0) { caItem[iItemIdx][iItemLen]='\0'; iItemIdx++; iItemLen=0; } if(iItemIdx<=12) { return; } if(caItem[iIdxStatus][0]!='A') { return; } bool booSuccess=false; if(caItem[iIdxLatVal][0]=='\0') { return; } float fLat=getFRealDeg(caItem[iIdxLatVal], caItem[iIdxLatDir][0], booSuccess); if(booSuccess==false) { return; } if(caItem[iIdxLonVal][0]=='\0') { return; } float fLon=getFRealDeg(caItem[iIdxLonVal], caItem[iIdxLonDir][0], booSuccess); if(booSuccess==false) { return; } digitalWrite(iLedPin, LOW); // lights on sendData(caItem[iDDMMYY], caItem[iIdxUTCTime], fLat, fLon); } void loop() { if((millis()-ulLastTicks)>30000) { keepAlive(); } // 網路傳輸每隔30秒送一次封包,確保行動網路不被關閉 int iChar=oHSGps.read(); if(iChar!=-1) { digitalWrite(iLedPin, HIGH); // turn dark char chr1=(char)iChar; if(chr1=='\r') { } else if(chr1=='\n') { caGpsBuf[iGpsBufIdx]='\0'; strcpy(caLastGpsBuf, caGpsBuf); if(iGpsBufIdx>10 && (strncmp(caGpsBuf, "$GPRMC", 6)==0 || strncmp(caGpsBuf, "$GNRMC", 6)==0)) { if(getBooCheckNMEAChecksum(caGpsBuf)==true) { // Serial.println(caGpsBuf); dealWith_RMC(caGpsBuf); digitalWrite(iLedPin, LOW); // lights on } } booGpsBuf=false; iGpsBufIdx=0; } else if(chr1=='$') { booGpsBuf=true; caGpsBuf[0]=chr1; iGpsBufIdx=1; } else if(booGpsBuf==true) { if(iGpsBufIdx<(iGpsBufMax-2)) { caGpsBuf[iGpsBufIdx]=chr1; iGpsBufIdx++; } else { // drop data booGpsBuf=false; iGpsBufIdx=0; } } } }