備忘錄_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圖
印刷電路板的實品照
敬請期待
// 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;
}
}
}
}