【mcu 微控制器】
mcu 比較
esp32 lolin d32 雙核記憶體大,可用 thonny [micropython] 開發
esp32 c3 supermini 單核記憶體小,使用 arduino [c++] 開發
esp32 c3 supermini 的燒錄問題
按住 boot 不放
再按 reset
放開 reset
再放開 boot
這時候可以 erase flash 、 upload programs
esp32 c3 supermini 的 wifi 問題
最後用 WiFi.setTxPower(WIFI_POWER_8_5dBm); 解決
【電腦與微控制器之間的溝通-序列埠】
USB:Universal Serial Bus
UART:Universal Asynchronous Receiver/Transmitter 通用非同步收發傳輸器
(e.q.RS232[COM1, COM2, ......], RS449, ......)
通訊埠
Windows 用 COM (COMunication)
MacOS X, Linux 用 TTY (teletypewriter)
傳輸速率(Baud rate)
e.q.9600bps, 115200bps
CDC:Communications Device Class 通訊裝置
ACM:Abstract Control Model 抽象控制模型
JTAG:Joint Test Action Group 聯合測試工作群組
USB TO UART:USB→CH340/CP2102晶片→UART TX/RX→MCU
Win7─COM1, ......
Win11─COM1, ......
Ras~PiOS(rpi4,rpi5)─/dev/ttyUSB0, ......
USB CDC ACM:USB→MCU內建的USB Peripheral(虛擬UART,但已非bit傳輸,而是packet傳輸)
Win7─不支援
Win11─COM1, ......
Ras~PiOS(rpi4,rpi5)─/dev/ttyACM0, ......
USB JTAG:透過USB直接對MCU偵錯
e.q.breakpoint, single step, watch variable
※rx, tx 衝突問題
內建序列埠 是 PC 跟 MCU 溝通的管道,thonny與arduino都需要。
當 gps 模組也要用到 rx, tx 時,就得閃過內建的序列埠。
【gps 模組(neo-6m, neo-7m, neo-m8n, ......)】
neo-6m 抓到衛星時,會閃藍燈。
neo-m8n 電源導通會有固定紅燈,抓到衛星時,另一個紅燈會閃爍。
添購時,一定要選有附天線的版本。(通常是陶瓷天線)
陶瓷天線要遠離雜訊 (mcu, 行動電源, ......)
【整合開發環境 ide】
thonny https://thonny.org/
使用 micropython 語言
工具小而美。寫入的 *.py 檔案可以讀取出來。
win7/win11:到官網下載安裝程式
Ras~PiOS(rpi4,rpi5):sudo apt install thonny
需要先將 micropython 韌體燒錄到 mcu 中。
https://micropython.org/
要選對應的 mcu 型號
esp32 lolin d32 -- 韌體可用
esp32 c3 supermini -- 韌體可下載,可燒錄,但無法正常使用。
arduino https://www.arduino.cc/ (Products/Arduino IDE)
使用 c++ 語言
工具大很多,但功能較強。
win7:下載舊版來使用 (e.q.arduino 1.8.19)
win11:下載最新版本並安裝 (e.q.arduino-ide_2.3.8_Windows_64bit.exe)
Ras~PiOS(rpi4,rpi5):sudo apr install arduino
Arduino IDE
Board
(需要用 Boards Manager 下載安裝 Espressif Systems 的 esp32 相關資訊)
esp32 lolin d32 要選擇 LOLIN D32
esp32 c3 supermini 要選擇 ESP32C3 Dev Module
Port
選擇 COMx 或 ttyUSBx/ttyACMx
【硬體配置】
底板選擇
1.麵包板+杜邦線
2.麵包板+短導線
3.萬用板(洞洞板)+銅線 (電腦纖維萬用板-用這個)(電木PC板-錫黏不住)
4.PCB(印刷電路板)(EASYEDA+JLCPCB)
外殼挑選
可到大創、無印良品挑選適合的外殼
銲接
電烙鐵 (e.q.日製100W, 臺製40W)
烙鐵頭 (筆型,刀型)(跟瓦數對應,不能亂買)
烙鐵架
錫筆 (含銀錫筆、錫筆)
鍍錫銅線
剪刀、小刀
鑷子
吸錫線
燈光架+手機架 (老花小幫手)
麵包板
排針 (一短一長,兩邊等長)
固定用的硬紙盒
挖洞器
螺絲、螺帽
三用電表 (測量有無導通、有無虛銲)
【購買管道-mcu,gps,pcb】
shopee 蝦皮 (mcu、gps模組)
taobao 淘寶 (mcu、gps模組)
tlcpcb 嘉立創 (pcb板)
easyway (若是境外產品,需要授權,可能要付稅金)
#define iLED_esp32_c3_supermini 8
#define iLED_esp32_lolin_d32 5
int iLED=iLED_esp32_c3_supermini;
void setup()
{
pinMode(iLED, OUTPUT);
}
void loop()
{
digitalWrite(iLED, HIGH);
delay(500);
digitalWrite(iLED, LOW);
delay(500);
}
// 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;
}
}
}
}
要讓 windows 能夠透過 ch340 晶片,達成 usb to serial 功能,要安裝此晶片的驅動程式。
pip install esptool
Micropython fimeware(ESP32) 下載 ESP32_GENERIC-20260406-v1.28.0.bin
esptool --port COM4 erase_flash (COM的部份,要找一下,別找錯了)
esptool --chip esp32 --port COM4 --baud 460800 write_flash -z 0x1000 ESP32_GENERIC-20260406-v1.28.0.bin
raspberry pi 5 與 esp32 lolin d32 的相遇
[terminal]
lsusb
(看到 Bus 003 Device 003: ID 1a86:7523 QinHeng Electronics CH340 serial converter)
dmesg | grep -i tty
(看到 [14885.442504] usb 3-1: ch341-uart converter now attached to ttyUSB0)
ls /dev/ttyUSB*
(看到 /dev/ttyUSB0)
dmesg -w
(熱插拔 usb 線會看到設備動作的描述)
[Thonny]
看右下方,選單選擇
MicroPython (ESP32) ● USB Serial @ /dev/ttyUSB0
[terminal]
sudo apt update
sudo apt upgrade -y
sudo apt install pipx
pipx ensurepath
(關掉terminal,重新打開)
pipx --version
pipx install esptool
esptool.py --help
到 https://micropython.org/download/ESP32_GENERIC/ 下載 ESP32_GENERIC-20260406-v1.28.0.bin
esptool erase_flash
或
esptool --port /dev/ttyUSB0 erase_flash
esptool --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 ESP32_GENERIC-20260406-v1.28.0.bin
[Thonny]
進入 Thonny ,看右下方,選單選擇
MicroPython (ESP32) ● USB Serial @ /dev/ttyUSB0
從 Shell 方框可以看到韌體已更新
是 MicroPython v1.28.0 on 2026-04-06; Generic ESP32 module with ESP32



# rx,tx 用 pin16,17 才不會跟 thonny 所需要的 uart0 相衝
# NEO-6M的 VCC -- esp32的 3.3V
# NEO-6M的 RX -- esp32的 TX (gpio17)
# NEO-6M的 TX -- esp32的 RX (gpio16)
# NEO-6M的 GND -- esp32的 GND
# 目前是 led 燈亮,代表有抓到可用的 gps 訊號
from machine import Pin
from machine import UART
import time
import network
import urequests
import gc
def getStrGMTp8(strDDMMYY, strUTCTime):
'''
傳入從 gps 模組得到的 日月年,時分秒
傳出 年(4碼)月(2碼)日(2碼)時(2碼)分(2碼)秒(2碼)
'''
strGmtp8="00000000000000"
try:
iYear=int("20"+strDDMMYY[4:6])
iMonth=int(strDDMMYY[2:4])
iDay=int(strDDMMYY[0:2])
iHour=int(strUTCTime[0:2])
iMinute=int(strUTCTime[2:4])
iSecond=int(strUTCTime[4:6])
oUtcTime=(iYear,iMonth,iDay,iHour,iMinute,iSecond,0,0)
oUtcTimestamp=time.mktime(oUtcTime)
oGmtp8Time=time.localtime(oUtcTimestamp+8*3600)
strGmtp8="{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}".format(
oGmtp8Time[0], oGmtp8Time[1], oGmtp8Time[2],
oGmtp8Time[3], oGmtp8Time[4], oGmtp8Time[5])
except Exception as oE:
#print(oE)
pass
return strGmtp8
def getFRealDeg(strVal, strDir):
"""
把經緯度從文字轉成浮點數,若有任何錯誤,傳回None。
經度 dddmm.mmmm E,W
緯度 ddmm.mmmm N,S
"""
if not strVal or strDir not in ['N','S','E','W']:
return None
try:
if strDir in ['N','S']:
iDegLen=2
else:
iDegLen=3
iDeg=int(strVal[:iDegLen])
fMin=float(strVal[iDegLen:])
fFinalDeg=iDeg+fMin/60
if strDir in ['S','W']:
fFinalDeg=-fFinalDeg
return fFinalDeg
except:
return None
def getBooIsConnected():
global oWlan, lstSSIDPASS, iSSIDPASSIndex, strSSID, strPASS
if oWlan.isconnected()==False:
oWlan.disconnect() # 清除殘餘狀態
time.sleep_ms(200)
try:
iSSIDPASSIndex=(iSSIDPASSIndex+1) % len(lstSSIDPASS)
strSSID=lstSSIDPASS[iSSIDPASSIndex][0]
strPASS=lstSSIDPASS[iSSIDPASSIndex][1]
oWlan.connect(strSSID, strPASS)
except:
return False # 連線錯誤,本次退出。
iTimeout=20
while oWlan.isconnected()==False and iTimeout>0:
time.sleep(1)
iTimeout=iTimeout-1
if oWlan.isconnected()==False:
return False # 網路連不上,本次退出。
return True
def sendData(strDDMMYY, strUTCTime, fLat, fLon):
global oWlan, iLastTicks, strSSID
if fLat is None or fLon is None:
return # 數據有問題,本次退出。
if getBooIsConnected()==False:
return # 連線失敗,本次退出。
strGmtp8=getStrGMTp8(strDDMMYY, strUTCTime)
if strGmtp8=="00000000000000":
return # 計算失敗,本次退出。
oResponse=None
try:
strRecTime="20"+strDDMMYY[4:6]+"/"+strDDMMYY[2:4]+"/"+strDDMMYY[0:2]+"%20"+strUTCTime[0:2]+":"+strUTCTime[2:4]+":"+strUTCTime[4:6]
strUrl=(
"https://web.godroad.tw/api5/getgpstest.aspx?"
"lat="+str(fLat)+
"&lng="+str(fLon)+
"&rectime="+strRecTime+
"&cid=5554"
)
oResponse=urequests.get(strUrl, timeout=5)
iLastTicks=time.ticks_ms()
except:
pass
finally:
if oResponse:
oResponse.close()
oResponse=None
try:
# GET 方法,可用 http
strUrl=(
"http://liujiaje.com/resources/20260408_esp32_lolin_d32_neo-6m-gps/api/gps.php?"
"lat="+str(fLat)+
"&lon="+str(fLon)+
"&gmtp8="+strGmtp8+
"&ssid="+strSSID
)
oResponse=urequests.get(strUrl, timeout=5)
iLastTicks=time.ticks_ms()
except:
pass
finally:
if oResponse:
oResponse.close()
'''
oResponse=None
try:
# POST 方法,最好用 https
strUrl="https://liujiaje.com/temp/20260408_gps/index.php"
dictHeader={"Content-Type": "application/x-www-form-urlencoded"}
strData="lat="+str(fLat)+"&lon="+str(fLon)+"&ddmmyy="+strDDMMYY+"&utc="+strUTCTime
oResponse=urequests.post(strUrl, data=strData, headers=dictHeader, timeout=5)
iLastTicks=time.ticks_ms()
except:
pass
finally:
if oResponse:
oResponse.close()
'''
gc.collect()
def keepAlive():
global oWlan, iLastTicks, strSSID
if getBooIsConnected()==False:
return # 連線失敗,本次退出。
oResponse=None
try:
strUrl=(
"http://liujiaje.com/resources/20260408_esp32_lolin_d32_neo-6m-gps/api/keepalive.php?"
"&ssid="+strSSID
)
oResponse=urequests.get(strUrl, timeout=5)
iLastTicks=time.ticks_ms()
except:
pass
finally:
if oResponse:
oResponse.close()
gc.collect()
def blink(iTimes, fDelayMs=50):
global oLed
if not oLed:
return
try:
for _ in range(iTimes):
oLed.value(0) # 亮燈
time.sleep_ms(fDelayMs)
oLed.value(1) # 暗燈
time.sleep_ms(fDelayMs)
except:
pass
def send_cmd(strHex):
global oUart
try:
oUart.write(bytes.fromhex(strHex))
time.sleep(0.1)
except:
pass
# 連接 wifi 參數
lstSSIDPASS=[
("godroad", "66666666"),
("ssid2", "pass2"),
("ssid3", "pass3")]
iSSIDPASSIndex=-1
strSSID=""
strPASS=""
iLastTicks=0 # 記錄最後一次送出網路封包的時間
oWlan=network.WLAN(network.STA_IF)
oWlan.active(True)
# 使用 led 燈號
oLed=Pin(5, Pin.OUT)
oLed.value(1)
# 與 GPS 模組(neo-6m) 有關
oUart = UART(2, baudrate=9600, tx=17, rx=16, rxbuf=2048)
baBuf=b""
# 關掉多餘
send_cmd("B5 62 06 01 08 00 F0 03 00 00 00 00 00 00 02 38") # GSV
send_cmd("B5 62 06 01 08 00 F0 02 00 00 00 00 00 00 01 31") # GSA
send_cmd("B5 62 06 01 08 00 F0 05 00 00 00 00 00 00 05 47") # VTG
send_cmd("B5 62 06 01 08 00 F0 41 00 00 00 00 00 00 49 8B") # TXT
# 保留
send_cmd("B5 62 06 01 08 00 F0 00 01 00 00 00 00 00 00 2A") # GGA
send_cmd("B5 62 06 01 08 00 F0 04 01 00 00 00 00 00 00 36") # RMC
while True:
#blink(iTimes=1, fDelayMs=10)
if time.ticks_diff(time.ticks_ms(), iLastTicks)>30000: # 網路傳輸每隔30秒送一次封包,確保行動網路不被關閉
keepAlive()
baData=oUart.read()
if baData:
baBuf+=baData
if len(baBuf)>2048:
baBuf=baBuf[-1024:]
oLed.value(1)
iPos=baBuf.find(b"\n")
while iPos!=-1:
strLine=baBuf[:iPos]
baBuf=baBuf[iPos+1:]
iPos=baBuf.find(b"\n")
#blink(iTimes=1, fDelayMs=2)
strLine=strLine.strip()
try:
strLine=strLine.decode()
except:
strLine=""
if strLine.startswith('$GPRMC') or strLine.startswith('$GNRMC'):
lstPart=strLine.split(',')
if len(lstPart)>12:
strMessageType=lstPart[0]
strUTCTime=lstPart[1]
strStatus=lstPart[2] # Void/Active
strLatVal=lstPart[3] #
strLatDir=lstPart[4] # North/South
strLonVal=lstPart[5] #
strLonDir=lstPart[6] # East/West
if strStatus=="A":
fLat=getFRealDeg(strLatVal, strLatDir)
fLon=getFRealDeg(strLonVal, strLonDir)
oLed.value(0)
else:
fLat=None
fLon=None
strSpeedOverGround=lstPart[7] # knots
strTrackAngle=lstPart[8]
strDDMMYY=lstPart[9]
strMagneticVariationVal=lstPart[10]
strMagneticVariationDir=lstPart[11]
strMode=lstPart[12] # Autonomous/Differential/Estimated
try:
strChecksum=strMode[1:]
strMode=strMode[0:1]
except:
strChecksum=""
strMode=""
sendData(strDDMMYY, strUTCTime, fLat, fLon)
try:
time.sleep_ms(50)
except KeyboardInterrupt:
break
若要防水,可用熱熔膠封住瓶口。







