というわけで前回作ったボードにGPSを繋いでみた。
/*
============================================================
ESP32-S3 Hardware Test Menu
Arduino-ESP32
============================================================
OLED
SH1107 128x128
SDA GPIO1
SCL GPIO2
Button ADC
GPIO4
GPS
ATGM336H
RX GPIO15
TX GPIO16
9600 baud
ICM-42670-P
SCK GPIO12
MISO GPIO13
MOSI GPIO11
CS GPIO10
============================================================
GPS
============================================================
GGA:
Fix Quality
Used satellites
GSA:
Fix Type
1 = No Fix
2 = 2D Fix
3 = 3D Fix
GSV:
Visible satellites
QZSS:
PRN 193-199
============================================================
*/
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <WiFi.h>
#include <U8g2lib.h>
#include <HardwareSerial.h>
#include <time.h>
#include <sys/time.h>
// ============================================================
// DEBUG
// ============================================================
#define DEBUG_SERIAL 0
#if DEBUG_SERIAL
#define DBG_BEGIN(x) Serial.begin(x)
#define DBG_PRINT(x) Serial.print(x)
#define DBG_PRINTLN(x) Serial.println(x)
#define DBG_PRINTF(...) Serial.printf(__VA_ARGS__)
#else
#define DBG_BEGIN(x)
#define DBG_PRINT(x)
#define DBG_PRINTLN(x)
#define DBG_PRINTF(...)
#endif
// ============================================================
// OLED
// ============================================================
#define OLED_SDA 1
#define OLED_SCL 2
U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(
U8G2_R0,
U8X8_PIN_NONE
);
// ============================================================
// BUTTON
// ============================================================
#define BUTTON_ADC_PIN 4
enum Button {
BTN_NONE,
BTN_UP,
BTN_DOWN,
BTN_PUSH
};
Button readButtonRaw() {
int raw =
analogRead(
BUTTON_ADC_PIN
);
if (
raw < 1000
) {
return BTN_PUSH;
}
if (
raw < 1850
) {
return BTN_DOWN;
}
if (
raw < 2800
) {
return BTN_UP;
}
return BTN_NONE;
}
Button getButton() {
static Button lastButton =
BTN_NONE;
Button current =
readButtonRaw();
if (
current != BTN_NONE &&
lastButton == BTN_NONE
) {
delay(20);
Button check =
readButtonRaw();
if (
check == current
) {
lastButton =
current;
return current;
}
}
if (
current == BTN_NONE
) {
lastButton =
BTN_NONE;
}
return BTN_NONE;
}
// ============================================================
// GPS
// ============================================================
#define GPS_RX_PIN 15
#define GPS_TX_PIN 16
#define GPS_BAUD 9600
HardwareSerial GPS(1);
String gpsLine;
// ============================================================
// LAST NMEA
// ============================================================
String gpsLastLines[6];
void addGPSLine(
const String &line
) {
for (
int i = 5;
i > 0;
i--
) {
gpsLastLines[i] =
gpsLastLines[i - 1];
}
gpsLastLines[0] =
line;
}
// ============================================================
// GPS TIME
// ============================================================
bool gpsTimeValid =
false;
int gpsHour = 0;
int gpsMinute = 0;
int gpsSecond = 0;
int gpsDay = 0;
int gpsMonth = 0;
int gpsYear = 0;
bool rtcSyncedFromGPS =
false;
time_t gpsSyncEpoch = 0;
// ============================================================
// GPS FIX
// ============================================================
//
// GGA Fix Quality:
//
// 0 = invalid
// 1 = GPS fix
// 2 = DGPS
// 4 = RTK Fixed
// 5 = RTK Float
//
// GSA Fix Type:
//
// 1 = No Fix
// 2 = 2D
// 3 = 3D
// ============================================================
int gpsFixQuality = 0;
int gpsFixType = 1;
// ============================================================
// FIX TEXT
// ============================================================
const char* getFixTypeText() {
switch (
gpsFixType
) {
case 1:
return "NOFIX";
case 2:
return "2D";
case 3:
return "3D";
default:
return "--";
}
}
// ============================================================
// GGA FIX TEXT
// ============================================================
const char* getFixQualityText() {
switch (
gpsFixQuality
) {
case 0:
return "NOFIX";
case 1:
return "GPS";
case 2:
return "DGPS";
case 4:
return "RTK";
case 5:
return "FLOAT";
default:
return "FIX";
}
}
// ============================================================
// SATELLITE COUNTERS
// ============================================================
// GGA
int gpsUsedTotal = 0;
// GSV confirmed values
int gpsVisible = 0;
int qzssVisible = 0;
int beidouVisible = 0;
int glonassVisible = 0;
int galileoVisible = 0;
int gpsGpgsvTotal = 0;
// ============================================================
// USED SATELLITES
// ============================================================
int gpsUsedGPS = 0;
int gpsUsedQZSS = 0;
int gpsUsedBeiDou = 0;
int gpsUsedGLONASS = 0;
int gpsUsedGalileo = 0;
// ============================================================
// QZSS
// ============================================================
#define MAX_QZSS 10
struct QZSSInfo {
int prn;
int elevation;
int azimuth;
int snr;
bool used;
};
QZSSInfo qzss[MAX_QZSS];
int qzssCount = 0;
// ============================================================
// GSV TEMP
// ============================================================
int gpgsvTempGPS = 0;
int gpgsvTempQZSS = 0;
int bdgsvTemp = 0;
int glgsvTemp = 0;
int gagsvTemp = 0;
QZSSInfo qzssTemp[MAX_QZSS];
int qzssTempCount = 0;
// ============================================================
// QZSS PRN
// ============================================================
bool isQZSSPRN(
int prn
) {
return (
prn >= 193 &&
prn <= 199
);
}
// ============================================================
// NMEA FIELD
// ============================================================
String nmeaField(
const String &line,
int field
) {
int current = 0;
int start = 0;
if (
line.startsWith("$")
) {
start = 1;
}
while (true) {
int comma =
line.indexOf(
',',
start
);
int star =
line.indexOf(
'*',
start
);
int end;
if (
comma >= 0 &&
(
star < 0 ||
comma < star
)
) {
end = comma;
} else if (
star >= 0
) {
end = star;
} else {
end =
line.length();
}
if (
current == field
) {
return line.substring(
start,
end
);
}
if (
end >=
line.length()
) {
break;
}
start =
end + 1;
current++;
}
return "";
}
// ============================================================
// GPS UTC -> EPOCH
// ============================================================
time_t gpsToEpoch(
int year,
int month,
int day,
int hour,
int minute,
int second
) {
int64_t days = 0;
for (
int y = 1970;
y < year;
y++
) {
bool leap =
(
(y % 4 == 0 && y % 100 != 0) ||
(y % 400 == 0)
);
days +=
leap ? 366 : 365;
}
static const int daysBeforeMonth[] = {
0,
31,
59,
90,
120,
151,
181,
212,
243,
273,
304,
334
};
days +=
daysBeforeMonth[
month - 1
];
days +=
day - 1;
bool leap =
(
(year % 4 == 0 && year % 100 != 0) ||
(year % 400 == 0)
);
if (
leap &&
month > 2
) {
days++;
}
int64_t seconds =
days * 86400LL;
seconds +=
hour * 3600LL;
seconds +=
minute * 60LL;
seconds +=
second;
return (time_t)seconds;
}
// ============================================================
// GNZDA
// ============================================================
void parseGNZDA(
const String &line
) {
if (
!line.startsWith("$GNZDA")
) {
return;
}
String utc =
nmeaField(
line,
1
);
String day =
nmeaField(
line,
2
);
String month =
nmeaField(
line,
3
);
String year =
nmeaField(
line,
4
);
if (
utc.length() < 6 ||
day.length() == 0 ||
month.length() == 0 ||
year.length() == 0
) {
return;
}
int h =
utc.substring(
0,
2
).toInt();
int m =
utc.substring(
2,
4
).toInt();
int s =
utc.substring(
4,
6
).toInt();
int d =
day.toInt();
int mo =
month.toInt();
int y =
year.toInt();
if (
h < 0 || h > 23 ||
m < 0 || m > 59 ||
s < 0 || s > 60 ||
d < 1 || d > 31 ||
mo < 1 || mo > 12 ||
y < 2000
) {
return;
}
gpsHour = h;
gpsMinute = m;
gpsSecond = s;
gpsDay = d;
gpsMonth = mo;
gpsYear = y;
gpsTimeValid =
true;
// 初回だけESP32のシステム時刻を設定
if (
!rtcSyncedFromGPS
) {
time_t epoch =
gpsToEpoch(
y,
mo,
d,
h,
m,
s
);
struct timeval tv;
tv.tv_sec =
epoch;
tv.tv_usec =
0;
settimeofday(
&tv,
nullptr
);
gpsSyncEpoch =
epoch;
rtcSyncedFromGPS =
true;
DBG_PRINTF(
"GPS time set: %04d/%02d/%02d %02d:%02d:%02d UTC\n",
y,
mo,
d,
h,
m,
s
);
}
}
// ============================================================
// GGA
// ============================================================
void parseGGA(
const String &line
) {
if (
!line.startsWith("$GNGGA") &&
!line.startsWith("$GPGGA")
) {
return;
}
int fixQuality =
nmeaField(
line,
6
).toInt();
int satellites =
nmeaField(
line,
7
).toInt();
gpsFixQuality =
fixQuality;
if (
fixQuality > 0
) {
gpsUsedTotal =
satellites;
} else {
gpsUsedTotal =
0;
}
}
// ============================================================
// ADD QZSS TEMP
// ============================================================
void addQZSSTemp(
int prn,
int elevation,
int azimuth,
int snr
) {
for (
int i = 0;
i < qzssTempCount;
i++
) {
if (
qzssTemp[i].prn ==
prn
) {
qzssTemp[i].elevation =
elevation;
qzssTemp[i].azimuth =
azimuth;
qzssTemp[i].snr =
snr;
return;
}
}
if (
qzssTempCount <
MAX_QZSS
) {
qzssTemp[
qzssTempCount
].prn =
prn;
qzssTemp[
qzssTempCount
].elevation =
elevation;
qzssTemp[
qzssTempCount
].azimuth =
azimuth;
qzssTemp[
qzssTempCount
].snr =
snr;
qzssTemp[
qzssTempCount
].used =
false;
qzssTempCount++;
}
}
// ============================================================
// PUBLISH GPGSV
// ============================================================
void publishGPGSV() {
gpsVisible =
gpgsvTempGPS;
qzssVisible =
gpgsvTempQZSS;
gpsGpgsvTotal =
gpgsvTempGPS +
gpgsvTempQZSS;
qzssCount =
qzssTempCount;
for (
int i = 0;
i < qzssCount;
i++
) {
qzss[i] =
qzssTemp[i];
}
DBG_PRINTF(
"GPGSV complete: GPS=%d QZSS=%d TOTAL=%d\n",
gpsVisible,
qzssVisible,
gpsGpgsvTotal
);
}
// ============================================================
// PUBLISH OTHER GSV
// ============================================================
void publishBDGSV() {
beidouVisible =
bdgsvTemp;
}
void publishGLGSV() {
glonassVisible =
glgsvTemp;
}
void publishGAGSV() {
galileoVisible =
gagsvTemp;
}
// ============================================================
// GSV
// ============================================================
void parseGSV(
const String &line
) {
bool isGPS =
line.startsWith("$GPGSV");
bool isBeiDou =
line.startsWith("$BDGSV");
bool isGLONASS =
line.startsWith("$GLGSV");
bool isGalileo =
line.startsWith("$GAGSV");
bool isQZSS =
line.startsWith("$QZGSV");
if (
!isGPS &&
!isBeiDou &&
!isGLONASS &&
!isGalileo &&
!isQZSS
) {
return;
}
int totalMessages =
nmeaField(
line,
1
).toInt();
int messageNumber =
nmeaField(
line,
2
).toInt();
// ==========================================================
// GPS / QZSS
// ==========================================================
if (
isGPS
) {
if (
messageNumber == 1
) {
gpgsvTempGPS = 0;
gpgsvTempQZSS = 0;
qzssTempCount = 0;
}
for (
int field = 4;
field <= 20;
field += 4
) {
String prnString =
nmeaField(
line,
field
);
if (
prnString.length() == 0
) {
continue;
}
int prn =
prnString.toInt();
if (
prn <= 0
) {
continue;
}
int elevation =
nmeaField(
line,
field + 1
).toInt();
int azimuth =
nmeaField(
line,
field + 2
).toInt();
int snr =
nmeaField(
line,
field + 3
).toInt();
if (
isQZSSPRN(prn)
) {
gpgsvTempQZSS++;
addQZSSTemp(
prn,
elevation,
azimuth,
snr
);
} else {
gpgsvTempGPS++;
}
}
if (
messageNumber ==
totalMessages
) {
publishGPGSV();
}
return;
}
// ==========================================================
// BeiDou
// ==========================================================
if (
isBeiDou
) {
if (
messageNumber == 1
) {
bdgsvTemp = 0;
}
for (
int field = 4;
field <= 20;
field += 4
) {
int prn =
nmeaField(
line,
field
).toInt();
if (
prn > 0
) {
bdgsvTemp++;
}
}
if (
messageNumber ==
totalMessages
) {
publishBDGSV();
}
return;
}
// ==========================================================
// GLONASS
// ==========================================================
if (
isGLONASS
) {
if (
messageNumber == 1
) {
glgsvTemp = 0;
}
for (
int field = 4;
field <= 20;
field += 4
) {
int prn =
nmeaField(
line,
field
).toInt();
if (
prn > 0
) {
glgsvTemp++;
}
}
if (
messageNumber ==
totalMessages
) {
publishGLGSV();
}
return;
}
// ==========================================================
// Galileo
// ==========================================================
if (
isGalileo
) {
if (
messageNumber == 1
) {
gagsvTemp = 0;
}
for (
int field = 4;
field <= 20;
field += 4
) {
int prn =
nmeaField(
line,
field
).toInt();
if (
prn > 0
) {
gagsvTemp++;
}
}
if (
messageNumber ==
totalMessages
) {
publishGAGSV();
}
return;
}
// ==========================================================
// QZSS GSV
// ==========================================================
if (
isQZSS
) {
if (
messageNumber == 1
) {
qzssTempCount = 0;
}
for (
int field = 4;
field <= 20;
field += 4
) {
int prn =
nmeaField(
line,
field
).toInt();
if (
prn <= 0
) {
continue;
}
int elevation =
nmeaField(
line,
field + 1
).toInt();
int azimuth =
nmeaField(
line,
field + 2
).toInt();
int snr =
nmeaField(
line,
field + 3
).toInt();
addQZSSTemp(
prn,
elevation,
azimuth,
snr
);
}
if (
messageNumber ==
totalMessages
) {
qzssCount =
qzssTempCount;
qzssVisible =
qzssTempCount;
for (
int i = 0;
i < qzssCount;
i++
) {
qzss[i] =
qzssTemp[i];
}
}
}
}
// ============================================================
// GSA
// ============================================================
//
// 重要:
//
// GSAを受信するたびに ++ しない。
// 1つのGSA文を全部解析してから、その結果で置換する。
// ============================================================
void parseGSA(
const String &line
) {
if (
!line.startsWith("$GNGSA")
) {
return;
}
// ----------------------------------------------------------
// GSA:
//
// $GNGSA,A,3,...,1
// ↑
// Fix Type
//
// 最後:
// 1 = GPS
// 2 = GLONASS
// 3 = Galileo
// 4 = BeiDou
// ----------------------------------------------------------
int fixType =
nmeaField(
line,
2
).toInt();
int systemId =
nmeaField(
line,
18
).toInt();
// ----------------------------------------------------------
// GPS/QZSSのFix Typeを保存
// ----------------------------------------------------------
if (
systemId == 1
) {
gpsFixType =
fixType;
}
// ----------------------------------------------------------
// このGSA文専用の一時カウンタ
// ----------------------------------------------------------
int usedGPS = 0;
int usedQZSS = 0;
int usedBeiDou = 0;
int usedGLONASS = 0;
int usedGalileo = 0;
// ----------------------------------------------------------
// GPS/QZSS USEフラグを一旦クリア
// ----------------------------------------------------------
if (
systemId == 1
) {
for (
int i = 0;
i < qzssCount;
i++
) {
qzss[i].used =
false;
}
}
// ----------------------------------------------------------
// GSA satellite IDs
//
// field 3~14
// ----------------------------------------------------------
for (
int field = 3;
field <= 14;
field++
) {
String sv =
nmeaField(
line,
field
);
if (
sv.length() == 0
) {
continue;
}
int prn =
sv.toInt();
if (
prn <= 0
) {
continue;
}
// GPS / QZSS
if (
systemId == 1
) {
if (
isQZSSPRN(prn)
) {
usedQZSS++;
for (
int i = 0;
i < qzssCount;
i++
) {
if (
qzss[i].prn ==
prn
) {
qzss[i].used =
true;
break;
}
}
} else {
usedGPS++;
}
// GLONASS
} else if (
systemId == 2
) {
usedGLONASS++;
// Galileo
} else if (
systemId == 3
) {
usedGalileo++;
// BeiDou
} else if (
systemId == 4
) {
usedBeiDou++;
}
}
// ----------------------------------------------------------
// 結果を一括反映
// ----------------------------------------------------------
if (
systemId == 1
) {
gpsUsedGPS =
usedGPS;
gpsUsedQZSS =
usedQZSS;
} else if (
systemId == 2
) {
gpsUsedGLONASS =
usedGLONASS;
} else if (
systemId == 3
) {
gpsUsedGalileo =
usedGalileo;
} else if (
systemId == 4
) {
gpsUsedBeiDou =
usedBeiDou;
}
}
// ============================================================
// GPS UPDATE
// ============================================================
void updateGPS() {
while (
GPS.available()
) {
char c =
GPS.read();
if (
c == '\n'
) {
gpsLine.trim();
if (
gpsLine.length() > 0
) {
addGPSLine(
gpsLine
);
#if DEBUG_SERIAL
Serial.println(
gpsLine
);
#endif
parseGNZDA(
gpsLine
);
parseGGA(
gpsLine
);
parseGSV(
gpsLine
);
parseGSA(
gpsLine
);
}
gpsLine = "";
} else if (
c != '\r'
) {
if (
gpsLine.length() < 200
) {
gpsLine += c;
}
}
}
}
// ============================================================
// ICM-42670-P
// ============================================================
#define ICM_SCK 12
#define ICM_MISO 13
#define ICM_MOSI 11
#define ICM_CS 10
#define ICM_SPI_FREQ 1000000
#define ICM_REG_TEMP_DATA1 0x09
#define ICM_REG_ACCEL_DATA_X1 0x0B
#define ICM_REG_GYRO_DATA_X1 0x11
#define ICM_REG_PWR_MGMT0 0x1F
#define ICM_REG_WHO_AM_I 0x75
#define ICM_WHO_AM_I_VALUE 0x67
SPIClass ICM_SPI(
FSPI
);
bool icmDetected =
false;
struct ICMData {
int16_t temp;
int16_t ax;
int16_t ay;
int16_t az;
int16_t gx;
int16_t gy;
int16_t gz;
float temperature;
float gyroX_dps;
float gyroY_dps;
float gyroZ_dps;
float gyroAngleX;
float gyroAngleY;
float gyroAngleZ;
float accelRoll;
float accelPitch;
};
ICMData icmData = {};
uint32_t icmLastMicros = 0;
// ============================================================
// ICM READ REG
// ============================================================
uint8_t icmReadReg(
uint8_t reg
) {
ICM_SPI.beginTransaction(
SPISettings(
ICM_SPI_FREQ,
MSBFIRST,
SPI_MODE0
)
);
digitalWrite(
ICM_CS,
LOW
);
ICM_SPI.transfer(
reg | 0x80
);
uint8_t value =
ICM_SPI.transfer(
0
);
digitalWrite(
ICM_CS,
HIGH
);
ICM_SPI.endTransaction();
return value;
}
// ============================================================
// ICM WRITE
// ============================================================
void icmWriteReg(
uint8_t reg,
uint8_t value
) {
ICM_SPI.beginTransaction(
SPISettings(
ICM_SPI_FREQ,
MSBFIRST,
SPI_MODE0
)
);
digitalWrite(
ICM_CS,
LOW
);
ICM_SPI.transfer(
reg & 0x7F
);
ICM_SPI.transfer(
value
);
digitalWrite(
ICM_CS,
HIGH
);
ICM_SPI.endTransaction();
}
// ============================================================
// ICM BURST
// ============================================================
void icmReadRegs(
uint8_t reg,
uint8_t *data,
size_t len
) {
ICM_SPI.beginTransaction(
SPISettings(
ICM_SPI_FREQ,
MSBFIRST,
SPI_MODE0
)
);
digitalWrite(
ICM_CS,
LOW
);
ICM_SPI.transfer(
reg | 0x80
);
for (
size_t i = 0;
i < len;
i++
) {
data[i] =
ICM_SPI.transfer(
0
);
}
digitalWrite(
ICM_CS,
HIGH
);
ICM_SPI.endTransaction();
}
// ============================================================
// INT16
// ============================================================
int16_t makeInt16(
uint8_t hi,
uint8_t lo
) {
return (int16_t)(
((uint16_t)hi << 8) |
lo
);
}
// ============================================================
// ICM INIT
// ============================================================
void initICM() {
pinMode(
ICM_CS,
OUTPUT
);
digitalWrite(
ICM_CS,
HIGH
);
ICM_SPI.begin(
ICM_SCK,
ICM_MISO,
ICM_MOSI,
ICM_CS
);
delay(20);
uint8_t who =
icmReadReg(
ICM_REG_WHO_AM_I
);
DBG_PRINTF(
"ICM WHO_AM_I = 0x%02X\n",
who
);
if (
who ==
ICM_WHO_AM_I_VALUE
) {
icmDetected =
true;
icmWriteReg(
ICM_REG_PWR_MGMT0,
0x0F
);
delay(50);
} else {
icmDetected =
false;
}
}
// ============================================================
// ICM DATA
// ============================================================
void readICMData() {
if (
!icmDetected
) {
return;
}
uint8_t data[14];
icmReadRegs(
ICM_REG_TEMP_DATA1,
data,
sizeof(data)
);
icmData.temp =
makeInt16(
data[0],
data[1]
);
icmData.ax =
makeInt16(
data[2],
data[3]
);
icmData.ay =
makeInt16(
data[4],
data[5]
);
icmData.az =
makeInt16(
data[6],
data[7]
);
icmData.gx =
makeInt16(
data[8],
data[9]
);
icmData.gy =
makeInt16(
data[10],
data[11]
);
icmData.gz =
makeInt16(
data[12],
data[13]
);
icmData.temperature =
((float)icmData.temp / 128.0f)
+ 25.0f;
icmData.gyroX_dps =
(float)icmData.gx /
16.4f;
icmData.gyroY_dps =
(float)icmData.gy /
16.4f;
icmData.gyroZ_dps =
(float)icmData.gz /
16.4f;
uint32_t now =
micros();
if (
icmLastMicros != 0
) {
float dt =
(float)(
now -
icmLastMicros
) / 1000000.0f;
if (
dt > 0.0f &&
dt < 0.2f
) {
icmData.gyroAngleX +=
icmData.gyroX_dps *
dt;
icmData.gyroAngleY +=
icmData.gyroY_dps *
dt;
icmData.gyroAngleZ +=
icmData.gyroZ_dps *
dt;
}
}
icmLastMicros =
now;
float ax =
(float)icmData.ax;
float ay =
(float)icmData.ay;
float az =
(float)icmData.az;
icmData.accelRoll =
atan2f(
ay,
az
) *
180.0f /
PI;
icmData.accelPitch =
atan2f(
-ax,
sqrtf(
ay * ay +
az * az
)
) *
180.0f /
PI;
}
// ============================================================
// WIFI
// ============================================================
int wifiCount = 0;
int wifiSelected = 0;
void startWiFiScan() {
WiFi.mode(
WIFI_STA
);
WiFi.disconnect();
delay(50);
wifiCount =
WiFi.scanNetworks(
false,
true
);
if (
wifiCount < 0
) {
wifiCount = 0;
}
wifiSelected = 0;
}
// ============================================================
// SCREEN
// ============================================================
enum Screen {
SCREEN_MENU,
SCREEN_GPS,
SCREEN_TIME,
SCREEN_WIFI,
SCREEN_ICM
};
Screen screen =
SCREEN_MENU;
// ============================================================
// MENU
// ============================================================
const char *menuItems[] = {
"GPS Monitor",
"Current Time",
"WiFi Scan",
"ICM-42670",
"Camera",
"Resolution",
"Brightness",
"Settings",
"About"
};
const int MENU_COUNT =
sizeof(menuItems) /
sizeof(menuItems[0]);
int menuIndex = 0;
// ============================================================
// TIME PAGE
// ============================================================
int timePage = 0;
#define TIME_PAGE_COUNT 3
// ============================================================
// HEADER
// ============================================================
void drawHeader(
const char *title
) {
u8g2.setFont(
u8g2_font_6x10_tf
);
u8g2.drawStr(
0,
10,
title
);
u8g2.drawHLine(
0,
12,
128
);
}
// ============================================================
// MENU
// ============================================================
void drawMenu() {
u8g2.clearBuffer();
drawHeader(
"TEST MENU"
);
u8g2.setFont(
u8g2_font_6x10_tf
);
int first =
menuIndex - 2;
if (
first < 0
) {
first = 0;
}
if (
first >
MENU_COUNT - 6
) {
first =
max(
0,
MENU_COUNT - 6
);
}
for (
int i = 0;
i < 6;
i++
) {
int index =
first + i;
if (
index >= MENU_COUNT
) {
break;
}
int y =
25 + i * 15;
if (
index == menuIndex
) {
u8g2.drawBox(
0,
y - 8,
128,
11
);
u8g2.setDrawColor(
0
);
u8g2.drawStr(
3,
y,
menuItems[index]
);
u8g2.setDrawColor(
1
);
} else {
u8g2.drawStr(
3,
y,
menuItems[index]
);
}
}
u8g2.sendBuffer();
}
// ============================================================
// GPS SCREEN
// ============================================================
void drawGPSScreen() {
u8g2.clearBuffer();
drawHeader(
"GPS MONITOR"
);
u8g2.setFont(
u8g2_font_5x8_tf
);
for (
int i = 0;
i < 6;
i++
) {
if (
gpsLastLines[i].length() == 0
) {
continue;
}
String s =
gpsLastLines[i];
if (
s.length() > 21
) {
s =
s.substring(
0,
21
);
}
u8g2.drawStr(
0,
24 + i * 15,
s.c_str()
);
}
u8g2.sendBuffer();
}
// ============================================================
// TIME PAGE 1
// ============================================================
void drawTimeClockPage() {
u8g2.setFont(
u8g2_font_5x8_tf
);
time_t now =
time(nullptr);
if (
!rtcSyncedFromGPS ||
now < 1000000000
) {
u8g2.drawStr(
0,
35,
"RTC NOT SET"
);
u8g2.drawStr(
0,
52,
"Waiting GPS..."
);
return;
}
time_t jstEpoch =
now +
9 * 3600;
struct tm jst;
gmtime_r(
&jstEpoch,
&jst
);
char dateBuf[32];
snprintf(
dateBuf,
sizeof(dateBuf),
"%04d/%02d/%02d",
jst.tm_year + 1900,
jst.tm_mon + 1,
jst.tm_mday
);
u8g2.drawStr(
5,
37,
dateBuf
);
char timeBuf[32];
snprintf(
timeBuf,
sizeof(timeBuf),
"%02d:%02d:%02d",
jst.tm_hour,
jst.tm_min,
jst.tm_sec
);
u8g2.setFont(
u8g2_font_10x20_tf
);
u8g2.drawStr(
8,
71,
timeBuf
);
u8g2.setFont(
u8g2_font_5x8_tf
);
u8g2.drawStr(
5,
90,
"JST UTC+9"
);
u8g2.drawStr(
5,
104,
"GPS -> RTC OK"
);
}
// ============================================================
// TIME PAGE 2
// ============================================================
void drawTimeGPSPage() {
u8g2.setFont(
u8g2_font_5x8_tf
);
char line[40];
snprintf(
line,
sizeof(line),
"USED TOTAL %2d",
gpsUsedTotal
);
u8g2.drawStr(
0,
25,
line
);
snprintf(
line,
sizeof(line),
"GPS VIEW %2d",
gpsVisible
);
u8g2.drawStr(
0,
39,
line
);
snprintf(
line,
sizeof(line),
"QZSS VIEW %2d",
qzssVisible
);
u8g2.drawStr(
0,
53,
line
);
snprintf(
line,
sizeof(line),
"BDS VIEW %2d",
beidouVisible
);
u8g2.drawStr(
0,
67,
line
);
snprintf(
line,
sizeof(line),
"GLO VIEW %2d",
glonassVisible
);
u8g2.drawStr(
0,
81,
line
);
snprintf(
line,
sizeof(line),
"GAL VIEW %2d",
galileoVisible
);
u8g2.drawStr(
0,
95,
line
);
snprintf(
line,
sizeof(line),
"USE G%02d Q%02d B%02d",
gpsUsedGPS,
gpsUsedQZSS,
gpsUsedBeiDou
);
u8g2.drawStr(
0,
109,
line
);
}
// ============================================================
// TIME PAGE 3
// ============================================================
void drawTimeQZSSPage() {
u8g2.setFont(
u8g2_font_5x8_tf
);
u8g2.drawStr(
0,
21,
"QZSS / MICHIBIKI"
);
if (
qzssCount == 0
) {
u8g2.drawStr(
0,
42,
"No QZSS"
);
} else {
for (
int i = 0;
i < qzssCount &&
i < 5;
i++
) {
char line[40];
snprintf(
line,
sizeof(line),
"%3d EL%02d SNR%02d %s",
qzss[i].prn,
qzss[i].elevation,
qzss[i].snr,
qzss[i].used
? "USE"
: ""
);
u8g2.drawStr(
0,
38 + i * 14,
line
);
}
}
u8g2.drawStr(
0,
108,
"193-199 = QZSS"
);
}
// ============================================================
// TIME SCREEN
// ============================================================
void drawTimeScreen() {
u8g2.clearBuffer();
char title[32];
// ==========================================================
// ページ番号 + 2D/3D FIX
// ==========================================================
snprintf(
title,
sizeof(title),
"TIME %d/3 %s",
timePage + 1,
getFixTypeText()
);
drawHeader(
title
);
if (
timePage == 0
) {
drawTimeClockPage();
} else if (
timePage == 1
) {
drawTimeGPSPage();
} else {
drawTimeQZSSPage();
}
u8g2.setFont(
u8g2_font_5x8_tf
);
u8g2.drawStr(
0,
119,
"UP/DN Page PUSH Back"
);
u8g2.sendBuffer();
}
// ============================================================
// WIFI SCREEN
// ============================================================
void drawWiFiScreen() {
u8g2.clearBuffer();
drawHeader(
"WIFI SCAN"
);
u8g2.setFont(
u8g2_font_5x8_tf
);
if (
wifiCount == 0
) {
u8g2.drawStr(
0,
35,
"No networks"
);
} else {
int first =
wifiSelected;
if (
first >
wifiCount - 4
) {
first =
max(
0,
wifiCount - 4
);
}
for (
int i = 0;
i < 4;
i++
) {
int index =
first + i;
if (
index >= wifiCount
) {
break;
}
int y =
27 + i * 20;
String ssid =
WiFi.SSID(
index
);
if (
ssid.length() == 0
) {
ssid =
"<hidden>";
}
if (
ssid.length() > 15
) {
ssid =
ssid.substring(
0,
15
);
}
char line[32];
snprintf(
line,
sizeof(line),
"%s %ld",
ssid.c_str(),
(long)WiFi.RSSI(index)
);
if (
index == wifiSelected
) {
u8g2.drawBox(
0,
y - 8,
128,
17
);
u8g2.setDrawColor(
0
);
u8g2.drawStr(
2,
y,
line
);
u8g2.setDrawColor(
1
);
} else {
u8g2.drawStr(
2,
y,
line
);
}
char ch[12];
snprintf(
ch,
sizeof(ch),
"C%ld",
(long)WiFi.channel(index)
);
u8g2.drawStr(
105,
y,
ch
);
}
}
u8g2.drawStr(
0,
119,
"UP/DN Scroll PUSH Back"
);
u8g2.sendBuffer();
}
// ============================================================
// ICM SCREEN
// ============================================================
void drawICMScreen() {
u8g2.clearBuffer();
drawHeader(
"ICM-42670-P"
);
u8g2.setFont(
u8g2_font_5x8_tf
);
uint8_t who =
icmReadReg(
ICM_REG_WHO_AM_I
);
char line[40];
snprintf(
line,
sizeof(line),
"WHO_AM_I: %02X",
who
);
u8g2.drawStr(
0,
23,
line
);
if (
who !=
ICM_WHO_AM_I_VALUE
) {
u8g2.drawStr(
0,
42,
"NOT DETECTED"
);
} else {
readICMData();
snprintf(
line,
sizeof(line),
"TEMP %5.1f C",
icmData.temperature
);
u8g2.drawStr(
65,
23,
line
);
snprintf(
line,
sizeof(line),
"R %7.1f",
icmData.accelRoll
);
u8g2.drawStr(
0,
39,
line
);
snprintf(
line,
sizeof(line),
"P %7.1f",
icmData.accelPitch
);
u8g2.drawStr(
0,
51,
line
);
snprintf(
line,
sizeof(line),
"GX %6.1f d/s",
icmData.gyroX_dps
);
u8g2.drawStr(
0,
65,
line
);
snprintf(
line,
sizeof(line),
"GY %6.1f d/s",
icmData.gyroY_dps
);
u8g2.drawStr(
0,
77,
line
);
snprintf(
line,
sizeof(line),
"GZ %6.1f d/s",
icmData.gyroZ_dps
);
u8g2.drawStr(
0,
89,
line
);
snprintf(
line,
sizeof(line),
"A %.1f %.1f %.1f",
icmData.gyroAngleX,
icmData.gyroAngleY,
icmData.gyroAngleZ
);
u8g2.drawStr(
0,
103,
line
);
}
u8g2.drawStr(
0,
119,
"PUSH: Back"
);
u8g2.sendBuffer();
}
// ============================================================
// MENU ENTER
// ============================================================
void enterMenuItem() {
switch (
menuIndex
) {
case 0:
screen =
SCREEN_GPS;
break;
case 1:
timePage =
0;
screen =
SCREEN_TIME;
break;
case 2:
startWiFiScan();
screen =
SCREEN_WIFI;
break;
case 3:
screen =
SCREEN_ICM;
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
}
}
// ============================================================
// MENU HANDLER
// ============================================================
void handleMenu() {
Button b =
getButton();
if (
b == BTN_UP
) {
menuIndex--;
if (
menuIndex < 0
) {
menuIndex =
MENU_COUNT - 1;
}
}
if (
b == BTN_DOWN
) {
menuIndex++;
if (
menuIndex >=
MENU_COUNT
) {
menuIndex = 0;
}
}
if (
b == BTN_PUSH
) {
enterMenuItem();
}
}
// ============================================================
// GPS HANDLER
// ============================================================
void handleGPSScreen() {
Button b =
getButton();
if (
b == BTN_PUSH
) {
screen =
SCREEN_MENU;
}
}
// ============================================================
// TIME HANDLER
// ============================================================
void handleTimeScreen() {
Button b =
getButton();
if (
b == BTN_UP
) {
timePage--;
if (
timePage < 0
) {
timePage =
TIME_PAGE_COUNT - 1;
}
}
if (
b == BTN_DOWN
) {
timePage++;
if (
timePage >=
TIME_PAGE_COUNT
) {
timePage = 0;
}
}
if (
b == BTN_PUSH
) {
screen =
SCREEN_MENU;
}
}
// ============================================================
// WIFI HANDLER
// ============================================================
void handleWiFiScreen() {
Button b =
getButton();
if (
b == BTN_UP
) {
wifiSelected--;
if (
wifiSelected < 0
) {
wifiSelected = 0;
}
}
if (
b == BTN_DOWN
) {
wifiSelected++;
if (
wifiSelected >=
wifiCount
) {
wifiSelected =
max(
0,
wifiCount - 1
);
}
}
if (
b == BTN_PUSH
) {
WiFi.scanDelete();
screen =
SCREEN_MENU;
}
}
// ============================================================
// ICM HANDLER
// ============================================================
void handleICMScreen() {
Button b =
getButton();
if (
b == BTN_PUSH
) {
screen =
SCREEN_MENU;
}
}
// ============================================================
// SETUP
// ============================================================
void setup() {
DBG_BEGIN(115200);
#if DEBUG_SERIAL
Serial.setTxTimeoutMs(0);
#endif
delay(100);
// ----------------------------------------------------------
// ADC
// ----------------------------------------------------------
analogReadResolution(
12
);
analogSetPinAttenuation(
BUTTON_ADC_PIN,
ADC_11db
);
// ----------------------------------------------------------
// OLED
// ----------------------------------------------------------
Wire.setPins(
OLED_SDA,
OLED_SCL
);
Wire.begin();
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(
u8g2_font_6x10_tf
);
u8g2.drawStr(
0,
30,
"ESP32-S3"
);
u8g2.drawStr(
0,
48,
"Hardware Test"
);
u8g2.drawStr(
0,
66,
"ATGM336H GPS"
);
u8g2.sendBuffer();
delay(800);
// ----------------------------------------------------------
// GPS
// ----------------------------------------------------------
GPS.begin(
GPS_BAUD,
SERIAL_8N1,
GPS_RX_PIN,
GPS_TX_PIN
);
// ----------------------------------------------------------
// ICM
// ----------------------------------------------------------
initICM();
// ----------------------------------------------------------
// WiFi
// ----------------------------------------------------------
WiFi.mode(
WIFI_STA
);
WiFi.disconnect();
}
// ============================================================
// LOOP
// ============================================================
void loop() {
// GPSは画面に関係なく常時処理
updateGPS();
switch (
screen
) {
case SCREEN_MENU:
handleMenu();
drawMenu();
break;
case SCREEN_GPS:
handleGPSScreen();
drawGPSScreen();
break;
case SCREEN_TIME:
handleTimeScreen();
drawTimeScreen();
break;
case SCREEN_WIFI:
handleWiFiScreen();
drawWiFiScreen();
break;
case SCREEN_ICM:
handleICMScreen();
drawICMScreen();
break;
}
delay(30);
}書き込み中