mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 03:01:27 +00:00
Added Seeed Wio Tracker L2 Pro variant
https://github.com/meshcore-dev/MeshCore/pull/3381
This commit is contained in:
1 parent
78e134b2ba
commit
6c7b751eb8
11 files changed
+1142
No files matched your search
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi",
|
||||
"partitions": "default_16MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "opi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Seeed Wio Tracker L2 (16 MB flash, 8 MB OPI PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.seeedstudio.com/",
|
||||
"vendor": "Seeed Studio"
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Transparent wrapper around the GNSS UART. MicroNMEA ignores GSV sentences,
|
||||
// so this watches the byte stream for them and keeps a running count of
|
||||
// satellites in view, strong satellites and best SNR, which separates RF
|
||||
// faults from a receiver that only lacks sky view. It also records the
|
||||
// fix flags straight from GGA/RMC so a parser problem upstream can't hide a
|
||||
// real fix, and can hand every complete line to a logger (SD card).
|
||||
class GpsTapStream : public Stream {
|
||||
Stream& _src;
|
||||
char _line[120];
|
||||
uint8_t _len = 0;
|
||||
struct Talker { char id[3]; uint8_t in_view; uint8_t strong; uint8_t strong_acc; unsigned long at; };
|
||||
Talker _talkers[6] = {};
|
||||
int _best_snr = 0;
|
||||
unsigned long _best_at = 0;
|
||||
unsigned long _last_gsv = 0;
|
||||
int _gga_fix = -1; // GGA fix quality (0 = no fix)
|
||||
int _gga_used = 0; // GGA satellites used
|
||||
char _rmc_status = '?'; // RMC status: A = valid, V = void
|
||||
unsigned long _last_fix_sentence = 0;
|
||||
|
||||
static const unsigned long FRESH_MS = 6000;
|
||||
static const int STRONG_DB = 30;
|
||||
|
||||
// pointer to comma-separated field n (0 = sentence id), NULL if absent
|
||||
static const char* field(const char* s, int n) {
|
||||
const char* p = s;
|
||||
for (int i = 0; i < n; i++) {
|
||||
p = strchr(p, ',');
|
||||
if (p == NULL) return NULL;
|
||||
p++;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void parseGsv(const char* s) {
|
||||
// $GPGSV,total,msg,in_view,prn,elev,az,snr,...*cs
|
||||
char talker[3] = {s[1], s[2], 0};
|
||||
const char* f;
|
||||
int total = (f = field(s, 1)) ? atoi(f) : 0;
|
||||
int msg = (f = field(s, 2)) ? atoi(f) : 0;
|
||||
int in_view = (f = field(s, 3)) ? atoi(f) : -1;
|
||||
if (in_view < 0) return;
|
||||
|
||||
int strong_here = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
const char* snr = field(s, 7 + 4 * k);
|
||||
if (snr == NULL || *snr == ',' || *snr == '*' || *snr == 0) continue;
|
||||
int v = atoi(snr);
|
||||
if (v >= STRONG_DB) strong_here++;
|
||||
if (v > _best_snr || millis() - _best_at > FRESH_MS) { _best_snr = v; _best_at = millis(); }
|
||||
}
|
||||
|
||||
int slot = -1;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (strcmp(_talkers[i].id, talker) == 0) { slot = i; break; }
|
||||
if (slot < 0 && _talkers[i].id[0] == 0) slot = i;
|
||||
}
|
||||
if (slot < 0) return;
|
||||
Talker& t = _talkers[slot];
|
||||
memcpy(t.id, talker, 3);
|
||||
t.in_view = (uint8_t) in_view;
|
||||
if (msg <= 1) t.strong_acc = 0;
|
||||
t.strong_acc += strong_here;
|
||||
if (msg >= total) t.strong = t.strong_acc;
|
||||
t.at = millis();
|
||||
_last_gsv = millis();
|
||||
}
|
||||
|
||||
void parseFixFlags(const char* s) {
|
||||
const char* type = s + 3;
|
||||
if (strncmp(type, "GGA", 3) == 0) {
|
||||
const char* f;
|
||||
_gga_fix = (f = field(s, 6)) && *f != ',' ? atoi(f) : 0;
|
||||
_gga_used = (f = field(s, 7)) && *f != ',' ? atoi(f) : 0;
|
||||
_last_fix_sentence = millis();
|
||||
} else if (strncmp(type, "RMC", 3) == 0) {
|
||||
const char* f = field(s, 2);
|
||||
_rmc_status = (f != NULL && *f != ',' && *f != 0) ? *f : '?';
|
||||
_last_fix_sentence = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void feed(int c) {
|
||||
if (echo) Serial.write((uint8_t) c);
|
||||
if (c == '\n' || c == '\r') {
|
||||
if (_len > 6 && _line[0] == '$') {
|
||||
_line[_len] = 0;
|
||||
if (on_line != NULL) on_line(_line);
|
||||
if (strncmp(_line + 3, "GSV", 3) == 0) parseGsv(_line);
|
||||
else parseFixFlags(_line);
|
||||
}
|
||||
_len = 0;
|
||||
} else if (_len < sizeof(_line) - 1) {
|
||||
_line[_len++] = (char) c;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
bool echo = false; // mirror raw NMEA to USB Serial
|
||||
void (*on_line)(const char* line) = NULL; // complete-sentence hook (e.g. SD log)
|
||||
|
||||
explicit GpsTapStream(Stream& src) : _src(src) {}
|
||||
|
||||
int satsInView() const {
|
||||
int n = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_talkers[i].id[0] != 0 && millis() - _talkers[i].at < FRESH_MS) n += _talkers[i].in_view;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
int strongSats() const {
|
||||
int n = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_talkers[i].id[0] != 0 && millis() - _talkers[i].at < FRESH_MS) n += _talkers[i].strong;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
int bestSnr() const { return millis() - _best_at < FRESH_MS ? _best_snr : 0; }
|
||||
bool streaming() const { return millis() - _last_gsv < FRESH_MS; }
|
||||
int ggaFix() const { return _gga_fix; }
|
||||
int ggaUsed() const { return _gga_used; }
|
||||
char rmcStatus() const { return _rmc_status; }
|
||||
|
||||
// Stream interface: pass-through with a tap on read()
|
||||
int available() override { return _src.available(); }
|
||||
int peek() override { return _src.peek(); }
|
||||
int read() override {
|
||||
int c = _src.read();
|
||||
if (c >= 0) feed(c);
|
||||
return c;
|
||||
}
|
||||
void flush() override { _src.flush(); }
|
||||
size_t write(uint8_t b) override { return _src.write(b); }
|
||||
size_t write(const uint8_t* buf, size_t n) override { return _src.write(buf, n); }
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
# MeshCore for Seeed Wio Tracker L2 Pro
|
||||
|
||||
MeshCore port for the **Seeed Studio Wio Tracker L2 Pro** (pre-release hardware;
|
||||
pin map may change on production units).
|
||||
|
||||
## Hardware
|
||||
|
||||
| Component | Detail |
|
||||
|-----------|--------|
|
||||
| MCU module | Wio-S3: ESP32-S3, 16 MB flash (QIO), 8 MB PSRAM (OPI) |
|
||||
| LoRa | SX1262, 862-930 MHz (SCK 4, MISO 5, MOSI 6, CS 21, RST 7, BUSY 8, DIO1 9, DIO2 = RF switch, TCXO 1.8 V) |
|
||||
| GNSS | Quectel L76K (GPS/BeiDou/GLONASS/QZSS), UART: module TX to GPIO 18, module RX from GPIO 17, 9600 baud |
|
||||
| Display | 3.2" 320x240 NV3031B on quad-SPI (SCLK 42, IO0-3 = 41/40/39/38, CS 46), 75 MHz |
|
||||
| Touch | GT911 capacitive, I2C 0x5D |
|
||||
| Backlight | LP5814 LED driver, I2C 0x2C |
|
||||
| I/O expander | TCA9535, I2C 0x21 - gates power/reset for GNSS, LCD, touch, SD, speaker amp, battery ADC, Grove |
|
||||
| Battery ADC | ADS1115, I2C 0x48, AIN0 through x2 divider |
|
||||
| USB-C detect | AW35615 CC controller, I2C 0x22 (charge detection) |
|
||||
| Audio | ES8311 codec (speaker, alert tones) + ES7243E mic ADC (unused) on I2S |
|
||||
| I2C bus | SDA 47, SCL 48 |
|
||||
| Buttons | User/Boot = GPIO 0; Wake/lock button on expander P00 |
|
||||
| SD card | SDIO 1-bit: CLK 2, CMD 3, D0 1 - offline map tiles, logs |
|
||||
|
||||
## Firmware flavors
|
||||
|
||||
| Env | What it does |
|
||||
|-----|--------------|
|
||||
| `Wio_Tracker_L2_companion_radio_ble` | **Phone companion.** Standard display UI, MeshCore app over BLE (pairing PIN `123456`), on-screen Bluetooth toggle page. |
|
||||
| `Wio_Tracker_L2_companion_radio_usb` | Standard display companion over USB-C serial. |
|
||||
| `Wio_Tracker_L2_repeater` | Standalone mesh repeater. |
|
||||
| `Wio_Tracker_L2_room_server` | Standalone room/BBS server. |
|
||||
|
||||
## Build & flash
|
||||
|
||||
```bash
|
||||
# from the MeshCore repo root
|
||||
pio run -e Wio_Tracker_L2_companion_radio_ble
|
||||
|
||||
# bootloader mode if needed: hold User/Boot, tap RST, release - then:
|
||||
pio run -e Wio_Tracker_L2_companion_radio_ble -t upload
|
||||
```
|
||||
|
||||
The board enumerates as a native USB-CDC port (auto-reset via 1200-bps touch
|
||||
is enabled). For the BLE companion build, pair from the MeshCore app with the
|
||||
PIN shown on the device screen.
|
||||
|
||||
## Port notes
|
||||
|
||||
- **Everything hangs off the TCA9535 expander.** `WioTrackerL2Board::begin()`
|
||||
replays Seeed's power-up sequence; order and delays matter (especially the
|
||||
500 ms LCD reset settle). If the expander probe fails, the firmware still
|
||||
runs but GPS/display/battery reads are dead.
|
||||
- **Battery** is read via the ADS1115 at +/-4.096 V FSR and mapped through a
|
||||
LiPo discharge curve.
|
||||
- **GNSS**: GPS+BeiDou+GLONASS are enabled at start (`$PCAS04,7`). A watchdog
|
||||
resets the module if it tracks 4+ strong satellites for minutes without
|
||||
producing a fix. Raw NMEA can be logged to SD for diagnosis.
|
||||
- **Power**: CPU drops to 80 MHz while the screen sleeps; the speaker amp and
|
||||
I2S clocks run only while a tone plays; the GNSS and Grove rails are gated;
|
||||
idle Bluetooth switches to slow advertising.
|
||||
- **Display orientation**: `offset_rotation=1` (landscape). If a unit shows
|
||||
the UI upside-down, use 3 in `WioTrackerL2Display.h`.
|
||||
- Pin map source: `meshtastic/firmware` [PR #10909](https://github.com/meshtastic/firmware/pull/10909),
|
||||
since merged as [`variants/esp32s3/seeed_wio_tracker_L2`](https://github.com/meshtastic/firmware/tree/master/variants/esp32s3/seeed_wio_tracker_L2),
|
||||
and the `meshtastic/device-ui` [`wio-l2` branch](https://github.com/meshtastic/device-ui/tree/wio-l2).
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "Sound.h"
|
||||
|
||||
#include <MeshCore.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <math.h>
|
||||
#include "AudioBoard.h" // pschatzmann/arduino-audio-driver
|
||||
#include <driver/i2s.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
// Wio-S3 audio wiring: ES8311 codec on I2C (0x18), I2S MCLK 10 / BCK 11 /
|
||||
// WS 12 / DOUT 16; speaker amp power (PA EN) is raised by the board init.
|
||||
#define SND_I2S_MCLK 10
|
||||
#define SND_I2S_BCK 11
|
||||
#define SND_I2S_WS 12
|
||||
#define SND_I2S_DOUT 16
|
||||
#define SND_SAMPLE_RATE 44100
|
||||
|
||||
static DriverPins snd_pins;
|
||||
static AudioBoard snd_codec(AudioDriverES8311, snd_pins);
|
||||
static bool snd_ready = false;
|
||||
static void (*snd_amp_ctl)(bool) = NULL; // board hook: speaker amp power
|
||||
|
||||
void soundSetAmpControl(void (*fn)(bool on)) { snd_amp_ctl = fn; }
|
||||
|
||||
void soundInit() {
|
||||
// i2s_driver_install crashes inside IDF's cleanup if its DMA allocation
|
||||
// fails (LoadProhibited boot loop) - refuse to try without clear headroom
|
||||
if (heap_caps_get_free_size(MALLOC_CAP_DMA) < 60000) {
|
||||
MESH_DEBUG_PRINTLN("sound: low DMA heap - sound disabled this boot");
|
||||
return;
|
||||
}
|
||||
|
||||
snd_pins.addI2C(PinFunction::CODEC, Wire);
|
||||
snd_pins.addI2S(PinFunction::CODEC, SND_I2S_MCLK, SND_I2S_BCK, SND_I2S_WS, SND_I2S_DOUT, -1);
|
||||
|
||||
CodecConfig cfg;
|
||||
cfg.input_device = ADC_INPUT_NONE;
|
||||
cfg.output_device = DAC_OUTPUT_ALL;
|
||||
cfg.i2s.bits = BIT_LENGTH_16BITS;
|
||||
cfg.i2s.rate = RATE_44K;
|
||||
if (!snd_codec.begin(cfg)) {
|
||||
MESH_DEBUG_PRINTLN("sound: ES8311 init failed - sound disabled");
|
||||
return;
|
||||
}
|
||||
snd_codec.setVolume(70);
|
||||
|
||||
i2s_config_t i2s_cfg = {};
|
||||
i2s_cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
|
||||
i2s_cfg.sample_rate = SND_SAMPLE_RATE;
|
||||
i2s_cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
|
||||
i2s_cfg.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
i2s_cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
||||
i2s_cfg.intr_alloc_flags = 0;
|
||||
i2s_cfg.dma_buf_count = 2; // keep the DMA footprint minimal
|
||||
i2s_cfg.dma_buf_len = 256;
|
||||
i2s_cfg.tx_desc_auto_clear = true;
|
||||
if (i2s_driver_install(I2S_NUM_0, &i2s_cfg, 0, NULL) != ESP_OK) {
|
||||
MESH_DEBUG_PRINTLN("sound: i2s driver install failed");
|
||||
return;
|
||||
}
|
||||
i2s_pin_config_t pin_cfg = {};
|
||||
pin_cfg.mck_io_num = SND_I2S_MCLK;
|
||||
pin_cfg.bck_io_num = SND_I2S_BCK;
|
||||
pin_cfg.ws_io_num = SND_I2S_WS;
|
||||
pin_cfg.data_out_num = SND_I2S_DOUT;
|
||||
pin_cfg.data_in_num = I2S_PIN_NO_CHANGE;
|
||||
if (i2s_set_pin(I2S_NUM_0, &pin_cfg) != ESP_OK) {
|
||||
MESH_DEBUG_PRINTLN("sound: i2s pin config failed");
|
||||
return;
|
||||
}
|
||||
i2s_stop(I2S_NUM_0); // clocks off until a tone plays (GNSS-adjacent EMI, battery)
|
||||
snd_ready = true;
|
||||
MESH_DEBUG_PRINTLN("sound: ES8311 + I2S ready");
|
||||
}
|
||||
|
||||
bool soundReady() { return snd_ready; }
|
||||
|
||||
void soundBeep(int freq_hz, int duration_ms) {
|
||||
if (!snd_ready) return;
|
||||
if (snd_amp_ctl) snd_amp_ctl(true);
|
||||
i2s_start(I2S_NUM_0);
|
||||
delay(3); // amp settle before the first samples
|
||||
const int total = SND_SAMPLE_RATE * duration_ms / 1000;
|
||||
const float amp = 6000.0f; // gentle level; codec volume does the rest
|
||||
static int16_t buf[256 * 2]; // 256 stereo frames per chunk
|
||||
|
||||
int written_frames = 0;
|
||||
while (written_frames < total) {
|
||||
int chunk = total - written_frames;
|
||||
if (chunk > 256) chunk = 256;
|
||||
for (int i = 0; i < chunk; i++) {
|
||||
float t = (float)(written_frames + i) / SND_SAMPLE_RATE;
|
||||
// short attack/decay envelope so the beep doesn't click
|
||||
float env = 1.0f;
|
||||
int pos = written_frames + i;
|
||||
if (pos < 220) env = pos / 220.0f;
|
||||
else if (pos > total - 220) env = (total - pos) / 220.0f;
|
||||
int16_t s = (int16_t)(amp * env * sinf(2.0f * (float)M_PI * freq_hz * t));
|
||||
buf[i * 2] = s;
|
||||
buf[i * 2 + 1] = s;
|
||||
}
|
||||
size_t written_bytes = 0;
|
||||
i2s_write(I2S_NUM_0, buf, chunk * 2 * sizeof(int16_t), &written_bytes, portMAX_DELAY);
|
||||
written_frames += chunk;
|
||||
}
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
i2s_stop(I2S_NUM_0);
|
||||
if (snd_amp_ctl) snd_amp_ctl(false);
|
||||
}
|
||||
|
||||
// selectable alert melodies; {0,0} terminates a style early
|
||||
struct ToneNote { uint16_t freq; uint16_t ms; };
|
||||
static const ToneNote TONE_STYLES[SOUND_TONE_STYLES][5] = {
|
||||
{{880, 90}, {1175, 120}, {0, 0}}, // Classic
|
||||
{{1400, 40}, {1800, 40}, {2200, 60}, {0, 0}}, // Chirp
|
||||
{{1319, 140}, {988, 180}, {0, 0}}, // Ding dong
|
||||
{{1568, 45}, {1245, 45}, {1568, 45}, {1245, 90}, {0, 0}}, // Trill
|
||||
{{659, 70}, {880, 70}, {1175, 70}, {1568, 110}, {0, 0}}, // Rise
|
||||
{{1047, 120}, {523, 120}, {1047, 120}, {0, 0}}, // Alarm
|
||||
};
|
||||
static int tone_style = 0;
|
||||
|
||||
void soundLoadTonePref() {
|
||||
File f = SPIFFS.open("/tone", "r");
|
||||
if (f) {
|
||||
int v = f.parseInt();
|
||||
f.close();
|
||||
if (v >= 0 && v < SOUND_TONE_STYLES) tone_style = v;
|
||||
}
|
||||
}
|
||||
|
||||
void soundSetToneStyle(int style) {
|
||||
if (style < 0 || style >= SOUND_TONE_STYLES) return;
|
||||
tone_style = style;
|
||||
File f = SPIFFS.open("/tone", "w");
|
||||
if (f) {
|
||||
f.print(style);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
int soundGetToneStyle() { return tone_style; }
|
||||
|
||||
void soundMessageTone() {
|
||||
const ToneNote* n = TONE_STYLES[tone_style];
|
||||
for (int i = 0; i < 5 && n[i].freq != 0; i++) soundBeep(n[i].freq, n[i].ms);
|
||||
}
|
||||
|
||||
void soundChannelTone() {
|
||||
// softer cousin of the chosen alert: first note, dropped a fourth
|
||||
soundBeep(TONE_STYLES[tone_style][0].freq * 3 / 4, 100);
|
||||
}
|
||||
|
||||
void soundAckTone() { soundBeep(1568, 50); }
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// Board audio backend for the Wio Tracker L2 Pro (ES8311 codec + speaker).
|
||||
// UI code includes "Sound.h" and gets whatever the variant provides; all
|
||||
// functions are safe no-ops if the codec failed to initialize.
|
||||
|
||||
#define SOUND_TONE_STYLES 6 // Classic, Chirp, Ding dong, Trill, Rise, Alarm
|
||||
|
||||
void soundInit(); // call once after Wire is up (board.begin done)
|
||||
void soundSetAmpControl(void (*fn)(bool on)); // board hook to gate the speaker amp power
|
||||
bool soundReady();
|
||||
void soundBeep(int freq_hz, int duration_ms); // short blocking tone
|
||||
void soundMessageTone(); // incoming DM (plays the selected alert style)
|
||||
void soundChannelTone(); // incoming channel message (softer variant)
|
||||
void soundAckTone(); // delivery ack / confirm
|
||||
|
||||
void soundLoadTonePref(); // read /tone from SPIFFS (safe before soundInit)
|
||||
void soundSetToneStyle(int style); // select + persist an alert style
|
||||
int soundGetToneStyle();
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "WioTrackerL2Board.h"
|
||||
|
||||
// TCA9535 register map
|
||||
static const uint8_t TCA_REG_OUTPUT0 = 0x02;
|
||||
static const uint8_t TCA_REG_OUTPUT1 = 0x03;
|
||||
static const uint8_t TCA_REG_CONFIG0 = 0x06; // 1 = input, 0 = output
|
||||
static const uint8_t TCA_REG_CONFIG1 = 0x07;
|
||||
|
||||
// ADS1115 registers / config bits
|
||||
static const uint8_t ADS_REG_CONVERSION = 0x00;
|
||||
static const uint8_t ADS_REG_CONFIG = 0x01;
|
||||
// OS=1 (start single shot) | MUX=100 (AIN0 vs GND) | PGA=001 (+/-4.096V)
|
||||
// | MODE=1 (single shot) | DR=100 (128 SPS) | COMP_QUE=11 (disabled)
|
||||
static const uint16_t ADS_CFG_BATT = 0x8000 | 0x4000 | 0x0200 | 0x0100 | 0x0080 | 0x0003;
|
||||
// +/-4.096V FSR -> 125uV/LSB; battery is behind a x2 divider -> 0.25 mV/LSB
|
||||
static const float ADS_MV_PER_LSB = 0.125f * 2.0f;
|
||||
|
||||
bool WioTrackerL2Board::expWriteReg(uint8_t reg, uint8_t val) {
|
||||
Wire.beginTransmission(TCA9535_ADDR);
|
||||
Wire.write(reg);
|
||||
Wire.write(val);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
void WioTrackerL2Board::expSetOutput(uint8_t pin, bool initial_level) {
|
||||
uint8_t port = pin >> 3, bit = pin & 7;
|
||||
// set desired level first so the pin doesn't glitch when direction flips
|
||||
if (initial_level) out_shadow[port] |= (1 << bit);
|
||||
else out_shadow[port] &= ~(1 << bit);
|
||||
expWriteReg(port ? TCA_REG_OUTPUT1 : TCA_REG_OUTPUT0, out_shadow[port]);
|
||||
|
||||
cfg_shadow[port] &= ~(1 << bit); // 0 = output
|
||||
expWriteReg(port ? TCA_REG_CONFIG1 : TCA_REG_CONFIG0, cfg_shadow[port]);
|
||||
}
|
||||
|
||||
void WioTrackerL2Board::expSetInput(uint8_t pin) {
|
||||
uint8_t port = pin >> 3, bit = pin & 7;
|
||||
cfg_shadow[port] |= (1 << bit);
|
||||
expWriteReg(port ? TCA_REG_CONFIG1 : TCA_REG_CONFIG0, cfg_shadow[port]);
|
||||
}
|
||||
|
||||
void WioTrackerL2Board::expWritePin(uint8_t pin, bool level) {
|
||||
uint8_t port = pin >> 3, bit = pin & 7;
|
||||
if (level) out_shadow[port] |= (1 << bit);
|
||||
else out_shadow[port] &= ~(1 << bit);
|
||||
expWriteReg(port ? TCA_REG_OUTPUT1 : TCA_REG_OUTPUT0, out_shadow[port]);
|
||||
}
|
||||
|
||||
// Power-up sequence mirrors Seeed's reference firmware for this board
|
||||
// (order and delays matter, especially the LCD reset settle time).
|
||||
bool WioTrackerL2Board::initExpander() {
|
||||
Wire.beginTransmission(TCA9535_ADDR);
|
||||
if (Wire.endTransmission() != 0) {
|
||||
return false; // expander not responding
|
||||
}
|
||||
|
||||
expSetInput(EXP_PIN_WAKE_BTN);
|
||||
expSetInput(EXP_PIN_I2C_IRQ);
|
||||
expSetInput(EXP_PIN_SD_DETECT);
|
||||
|
||||
expSetOutput(EXP_PIN_OTG_EN, LOW);
|
||||
delay(10);
|
||||
expSetOutput(EXP_PIN_PA_EN, LOW); // amp only powered while a tone plays
|
||||
delay(10);
|
||||
expSetOutput(EXP_PIN_TF_EN, HIGH);
|
||||
delay(10);
|
||||
expSetOutput(EXP_PIN_BAT_ADC_EN, HIGH);
|
||||
delay(10);
|
||||
expSetOutput(EXP_PIN_GNSS_EN, HIGH);
|
||||
delay(10);
|
||||
// GNSS reset is active HIGH; hold 10ms then release LOW so the L76K runs
|
||||
expSetOutput(EXP_PIN_GNSS_RST, HIGH);
|
||||
delay(10);
|
||||
expWritePin(EXP_PIN_GNSS_RST, LOW);
|
||||
// user LED is driven active-low through the expander; idle level HIGH = off
|
||||
expSetOutput(EXP_PIN_USER_LED, HIGH);
|
||||
delay(10);
|
||||
expSetOutput(EXP_PIN_GROVE_EN, HIGH);
|
||||
delay(10);
|
||||
|
||||
expSetOutput(EXP_PIN_LCD_EN, HIGH);
|
||||
delay(50);
|
||||
expSetOutput(EXP_PIN_LCD_RST, HIGH);
|
||||
delay(5);
|
||||
expWritePin(EXP_PIN_LCD_RST, LOW);
|
||||
delay(10);
|
||||
expWritePin(EXP_PIN_LCD_RST, HIGH);
|
||||
delay(500); // NV3031B needs a long settle after reset before init commands
|
||||
expSetOutput(EXP_PIN_LCD_CS, HIGH);
|
||||
delay(10);
|
||||
|
||||
// GT911 touch reset sequence: INT low during reset selects I2C addr 0x5D
|
||||
expSetOutput(EXP_PIN_TP_RST, LOW);
|
||||
expSetOutput(EXP_PIN_TP_INT, LOW);
|
||||
delay(10);
|
||||
expWritePin(EXP_PIN_TP_RST, HIGH);
|
||||
delay(60);
|
||||
|
||||
// capture WAKE button idle level (polarity unknown on alpha hardware)
|
||||
int inputs = expReadInputs();
|
||||
wake_btn_baseline = inputs >= 0 ? (uint8_t)(inputs & 1) : 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WioTrackerL2Board::begin() {
|
||||
// GNSS UART: NMEA arrives at ~500 B/s and the main loop can stall for
|
||||
// hundreds of ms on SD tile decodes; the 256-byte default overflowed
|
||||
Serial1.setRxBufferSize(1024);
|
||||
|
||||
uint32_t t0 = millis();
|
||||
while (!Serial && millis() - t0 < 3000) { delay(50); }
|
||||
MESH_DEBUG_PRINTLN("WioTrackerL2Board: ESP32Board init");
|
||||
|
||||
ESP32Board::begin(); // starts Wire on PIN_BOARD_SDA/PIN_BOARD_SCL (47/48)
|
||||
|
||||
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||
pinMode(P_LORA_MISO, INPUT_PULLUP);
|
||||
|
||||
MESH_DEBUG_PRINTLN("WioTrackerL2Board: TCA9535 expander init");
|
||||
expander_ok = initExpander();
|
||||
if (!expander_ok) {
|
||||
Serial.println("ERROR: TCA9535 IO expander not found - peripherals unpowered!");
|
||||
}
|
||||
|
||||
Wire.beginTransmission(0x22);
|
||||
aw_ok = Wire.endTransmission() == 0;
|
||||
MESH_DEBUG_PRINTLN("WioTrackerL2Board: init done");
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_source & (1 << P_LORA_DIO_1)) {
|
||||
startup_reason = BD_STARTUP_RX_PACKET; // LoRa packet woke us from deep sleep
|
||||
}
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
}
|
||||
|
||||
void WioTrackerL2Board::setLed(bool on) {
|
||||
// P10 doubles as GNSS wakeup and idles HIGH; blink = brief LOW pulses so
|
||||
// the GPS never sees a sustained low level
|
||||
if (expander_ok) {
|
||||
expWritePin(EXP_PIN_USER_LED, !on);
|
||||
}
|
||||
}
|
||||
|
||||
int WioTrackerL2Board::expReadInputs() {
|
||||
Wire.beginTransmission(TCA9535_ADDR);
|
||||
Wire.write((uint8_t)0x00); // input port 0 register
|
||||
if (Wire.endTransmission() != 0) return -1;
|
||||
if (Wire.requestFrom((int)TCA9535_ADDR, 2) != 2) return -1;
|
||||
int lo = Wire.read();
|
||||
int hi = Wire.read();
|
||||
return lo | (hi << 8);
|
||||
}
|
||||
|
||||
bool WioTrackerL2Board::isExternalPowered() {
|
||||
if (!aw_ok) return false;
|
||||
Wire.beginTransmission(0x22);
|
||||
Wire.write((uint8_t)0x40); // STATUS0
|
||||
if (Wire.endTransmission() != 0) return false;
|
||||
if (Wire.requestFrom(0x22, 1) != 1) return false;
|
||||
return (Wire.read() & 0x80) != 0; // VBUSOK bit
|
||||
}
|
||||
|
||||
bool WioTrackerL2Board::readWakeButton() {
|
||||
if (!expander_ok) return false;
|
||||
int v = expReadInputs();
|
||||
if (v < 0) return false;
|
||||
return ((uint8_t)(v & 1)) != wake_btn_baseline;
|
||||
}
|
||||
|
||||
int16_t WioTrackerL2Board::adsReadRaw() {
|
||||
Wire.beginTransmission(ADS1115_ADDR);
|
||||
Wire.write(ADS_REG_CONFIG);
|
||||
Wire.write((uint8_t)(ADS_CFG_BATT >> 8));
|
||||
Wire.write((uint8_t)(ADS_CFG_BATT & 0xFF));
|
||||
if (Wire.endTransmission() != 0) return -1;
|
||||
|
||||
delay(10); // 128 SPS -> ~8ms conversion time
|
||||
|
||||
Wire.beginTransmission(ADS1115_ADDR);
|
||||
Wire.write(ADS_REG_CONVERSION);
|
||||
if (Wire.endTransmission() != 0) return -1;
|
||||
if (Wire.requestFrom((int)ADS1115_ADDR, 2) != 2) return -1;
|
||||
|
||||
// sequence the two reads explicitly: operand evaluation order of <<|
|
||||
// is unspecified and each read dequeues a byte
|
||||
uint8_t hi = Wire.read();
|
||||
uint8_t lo = Wire.read();
|
||||
int16_t raw = ((int16_t)hi << 8) | lo;
|
||||
return raw < 0 ? 0 : raw;
|
||||
}
|
||||
|
||||
uint16_t WioTrackerL2Board::getBattMilliVolts() {
|
||||
if (!expander_ok) return 0; // BAT_ADC_EN rail never came up
|
||||
|
||||
int16_t raw = adsReadRaw();
|
||||
if (raw < 0) return 0;
|
||||
|
||||
return (uint16_t)(raw * ADS_MV_PER_LSB);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include "helpers/ESP32Board.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Seeed Wio Tracker L2 Pro (Wio-S3 module: ESP32-S3 + SX1262)
|
||||
//
|
||||
// Nearly all peripheral power rails and reset lines are behind a TCA9535
|
||||
// 16-bit I2C IO expander at address 0x21. Nothing (GNSS, LCD, touch, SD,
|
||||
// battery ADC, speaker amp) works until the expander is configured, so
|
||||
// begin() must run before any display / GPS / sensor init.
|
||||
//
|
||||
// Battery voltage is read through an ADS1115 16-bit I2C ADC at 0x48,
|
||||
// channel AIN0, behind a x2 resistive divider (rail gated by expander).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define TCA9535_ADDR 0x21
|
||||
#define ADS1115_ADDR 0x48
|
||||
|
||||
// TCA9535 pin numbering: 0..7 = port 0 (P00..P07), 8..15 = port 1 (P10..P17)
|
||||
#define EXP_PIN_WAKE_BTN 0 // input - side WAKE button
|
||||
#define EXP_PIN_I2C_IRQ 1 // input - shared I2C IRQ
|
||||
#define EXP_PIN_SD_DETECT 2 // input - microSD card detect
|
||||
#define EXP_PIN_TP_INT 3 // output - touch panel interrupt (driven for reset seq)
|
||||
#define EXP_PIN_LCD_CS 4 // output - LCD chip select (idle high)
|
||||
#define EXP_PIN_LCD_EN 5 // output - LCD power enable
|
||||
#define EXP_PIN_LCD_RST 6 // output - LCD reset
|
||||
#define EXP_PIN_GROVE_EN 7 // output - Grove port power
|
||||
#define EXP_PIN_TP_RST 8 // output - touch panel reset
|
||||
#define EXP_PIN_GNSS_RST 9 // output - GNSS reset (active HIGH)
|
||||
#define EXP_PIN_USER_LED 10 // output - mesh/user LED (also GNSS wakeup)
|
||||
#define EXP_PIN_OTG_EN 11 // output - USB OTG power
|
||||
#define EXP_PIN_PA_EN 12 // output - speaker amp power
|
||||
#define EXP_PIN_GNSS_EN 13 // output - GNSS power
|
||||
#define EXP_PIN_TF_EN 14 // output - microSD power
|
||||
#define EXP_PIN_BAT_ADC_EN 15 // output - battery ADC divider enable
|
||||
|
||||
class WioTrackerL2Board : public ESP32Board {
|
||||
public:
|
||||
void begin();
|
||||
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Seeed Wio Tracker L2 Pro";
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override { setLed(true); }
|
||||
void onAfterTransmit() override { setLed(false); }
|
||||
|
||||
// Mesh/user LED lives on the IO expander, not a GPIO
|
||||
void setLed(bool on);
|
||||
|
||||
// speaker amplifier power (expander P12); off when idle to keep the GNSS
|
||||
// antenna away from class-D switching noise and to save battery
|
||||
void setSpeakerAmp(bool on) { expWritePin(EXP_PIN_PA_EN, on); }
|
||||
|
||||
// hardware reset pulse to the L76K GNSS (active HIGH on this board); the
|
||||
// module keeps almanac/ephemeris across it, so this is a warm restart
|
||||
// GNSS rail: off saves the whole receiver when GPS is disabled; on re-runs
|
||||
// the power-up reset so the module comes back cleanly
|
||||
void setGnssPower(bool on) {
|
||||
if (on) {
|
||||
expWritePin(EXP_PIN_GNSS_EN, HIGH);
|
||||
delay(10);
|
||||
gnssReset();
|
||||
} else {
|
||||
expWritePin(EXP_PIN_GNSS_EN, LOW);
|
||||
}
|
||||
}
|
||||
// Grove expansion port rail (nothing on-board depends on it)
|
||||
void setGrovePower(bool on) { expWritePin(EXP_PIN_GROVE_EN, on); }
|
||||
|
||||
void gnssReset() {
|
||||
expWritePin(EXP_PIN_GNSS_RST, HIGH);
|
||||
delay(10);
|
||||
expWritePin(EXP_PIN_GNSS_RST, LOW);
|
||||
}
|
||||
|
||||
// WAKE button on expander P00: pressed = level differs from boot baseline
|
||||
bool readWakeButton();
|
||||
|
||||
// VBUS presence via the AW35615 USB-C controller (I2C 0x22)
|
||||
bool isExternalPowered() override;
|
||||
|
||||
bool expanderOK() const { return expander_ok; }
|
||||
|
||||
private:
|
||||
uint8_t out_shadow[2] = { 0xFF, 0xFF }; // TCA9535 output regs default high
|
||||
uint8_t cfg_shadow[2] = { 0xFF, 0xFF }; // 1 = input (power-on default)
|
||||
bool expander_ok = false;
|
||||
bool aw_ok = false; // AW35615 USB-C controller responded at probe
|
||||
uint8_t wake_btn_baseline = 0; // idle level of P00, captured at init
|
||||
|
||||
int expReadInputs(); // 16-bit input register pair, -1 on error
|
||||
|
||||
bool expWriteReg(uint8_t reg, uint8_t val);
|
||||
void expSetOutput(uint8_t pin, bool initial_level);
|
||||
void expSetInput(uint8_t pin);
|
||||
void expWritePin(uint8_t pin, bool level);
|
||||
bool initExpander();
|
||||
|
||||
int16_t adsReadRaw();
|
||||
};
|
||||
@@ -0,0 +1,210 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/LGFXDisplay.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#define LGFX_USE_V1
|
||||
#include <LovyanGFX.hpp>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Wio Tracker L2 Pro display stack:
|
||||
// - NV3031B 320x240 panel on quad-SPI (SCLK 42, IO0-3 = 41/40/39/38, CS 46)
|
||||
// - GT911 capacitive touch on I2C 0x5D (SDA 47 / SCL 48)
|
||||
// - LP5814 4-channel LED driver on I2C 0x2C used as backlight
|
||||
// Panel power/reset lines are on the TCA9535 expander and are already
|
||||
// sequenced by WioTrackerL2Board::begin() before this driver initializes.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#ifndef L2_SPI_FREQUENCY
|
||||
#define L2_SPI_FREQUENCY 75000000
|
||||
#endif
|
||||
|
||||
#define LP5814_I2C_ADDR 0x2C
|
||||
|
||||
// LP5814 used as backlight controller (all 4 channels in parallel)
|
||||
class WioTrackerL2Backlight : public lgfx::v1::ILight {
|
||||
static constexpr uint8_t REG_DEVICE_CONFIG0 = 0x00;
|
||||
static constexpr uint8_t REG_MAX_CURRENT = 0x01;
|
||||
static constexpr uint8_t REG_ENABLE_CONTROL = 0x02;
|
||||
static constexpr uint8_t REG_DIM_MODE = 0x04;
|
||||
static constexpr uint8_t REG_ENGINE_MODE = 0x05;
|
||||
static constexpr uint8_t REG_UPDATE = 0x0F;
|
||||
static constexpr uint8_t REG_LED0_DC = 0x14;
|
||||
static constexpr uint8_t REG_LED0_PWM = 0x18;
|
||||
|
||||
uint8_t _brightness = 153; // 60%
|
||||
|
||||
void writeReg(uint8_t reg, uint8_t value) {
|
||||
Wire.beginTransmission(LP5814_I2C_ADDR);
|
||||
Wire.write(reg);
|
||||
Wire.write(value);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
public:
|
||||
bool init(uint8_t brightness) override {
|
||||
Wire.beginTransmission(LP5814_I2C_ADDR);
|
||||
if (Wire.endTransmission() != 0) {
|
||||
return false; // LP5814 not found
|
||||
}
|
||||
|
||||
writeReg(REG_DEVICE_CONFIG0, 0x01); // chip enable
|
||||
writeReg(REG_MAX_CURRENT, 0x01); // 51 mA max current
|
||||
writeReg(REG_ENABLE_CONTROL, 0x00); // outputs off while configuring
|
||||
writeReg(REG_DIM_MODE, 0x4E);
|
||||
writeReg(REG_ENGINE_MODE, 0xF0);
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
writeReg(REG_LED0_DC + i, 200);
|
||||
}
|
||||
writeReg(REG_ENABLE_CONTROL, 0x0F); // enable all 4 channels
|
||||
writeReg(REG_UPDATE, 0x55); // latch (LP5814 requires 0x55)
|
||||
delay(5);
|
||||
|
||||
setBrightness(brightness);
|
||||
return true;
|
||||
}
|
||||
|
||||
void setBrightness(uint8_t brightness) override {
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
writeReg(REG_LED0_PWM + i, brightness);
|
||||
}
|
||||
_brightness = brightness;
|
||||
}
|
||||
|
||||
uint8_t getBrightness() const { return _brightness; }
|
||||
|
||||
virtual ~WioTrackerL2Backlight() = default;
|
||||
};
|
||||
|
||||
class LGFX_WioTrackerL2 : public lgfx::LGFX_Device {
|
||||
lgfx::Panel_NV3031B _panel_instance;
|
||||
lgfx::Bus_SPI _bus_instance;
|
||||
lgfx::Touch_GT911 _touch_instance;
|
||||
WioTrackerL2Backlight _light_instance;
|
||||
|
||||
public:
|
||||
bool init_impl(bool use_reset, bool use_clear) override {
|
||||
// bring up backlight controller while the I2C bus is still clean
|
||||
_light_instance.init(_light_instance.getBrightness());
|
||||
|
||||
bool result = LGFX_Device::init_impl(use_reset, use_clear);
|
||||
|
||||
// GT911 probe can leave the ESP32 I2C peripheral with a stuck BUSY flag;
|
||||
// cycling Wire resets it so later LP5814/sensor traffic doesn't time out
|
||||
Wire.end();
|
||||
Wire.begin(47, 48);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LGFX_WioTrackerL2(void) {
|
||||
{
|
||||
auto cfg = _bus_instance.config();
|
||||
cfg.spi_host = SPI3_HOST;
|
||||
cfg.spi_mode = 3;
|
||||
cfg.freq_write = L2_SPI_FREQUENCY;
|
||||
cfg.freq_read = 16000000;
|
||||
cfg.pin_sclk = 42;
|
||||
// quad SPI data pins
|
||||
cfg.pin_io0 = 41;
|
||||
cfg.pin_io1 = 40;
|
||||
cfg.pin_io2 = 39;
|
||||
cfg.pin_io3 = 38;
|
||||
_bus_instance.config(cfg);
|
||||
_panel_instance.setBus(&_bus_instance);
|
||||
}
|
||||
|
||||
{
|
||||
auto cfg = _panel_instance.config();
|
||||
cfg.pin_cs = 46;
|
||||
cfg.pin_rst = -1; // reset is on the IO expander, done in board init
|
||||
cfg.pin_busy = -1;
|
||||
cfg.panel_width = 240; // native portrait orientation
|
||||
cfg.panel_height = 320;
|
||||
cfg.memory_width = 240;
|
||||
cfg.memory_height = 320;
|
||||
cfg.offset_x = 0;
|
||||
cfg.offset_y = 0;
|
||||
cfg.offset_rotation = 1; // panel mounted landscape (320x240)
|
||||
cfg.invert = true;
|
||||
cfg.rgb_order = true;
|
||||
cfg.dlen_16bit = false;
|
||||
cfg.bus_shared = false;
|
||||
_panel_instance.config(cfg);
|
||||
}
|
||||
|
||||
{
|
||||
auto cfg = _touch_instance.config();
|
||||
cfg.pin_cs = -1;
|
||||
cfg.x_min = 0;
|
||||
cfg.x_max = 239;
|
||||
cfg.y_min = 0;
|
||||
cfg.y_max = 319;
|
||||
cfg.pin_int = -1; // INT is on the IO expander
|
||||
cfg.offset_rotation = 2;
|
||||
cfg.i2c_port = 0;
|
||||
cfg.i2c_addr = 0x5D;
|
||||
cfg.pin_sda = 47;
|
||||
cfg.pin_scl = 48;
|
||||
cfg.bus_shared = false;
|
||||
cfg.freq = 400000;
|
||||
_touch_instance.config(cfg);
|
||||
_panel_instance.setTouch(&_touch_instance);
|
||||
}
|
||||
|
||||
_panel_instance.setLight(&_light_instance);
|
||||
setPanel(&_panel_instance);
|
||||
}
|
||||
};
|
||||
|
||||
class WioTrackerL2Display : public LGFXDisplay {
|
||||
LGFX_WioTrackerL2 disp;
|
||||
public:
|
||||
WioTrackerL2Display() : LGFXDisplay(320, 240, disp) {}
|
||||
|
||||
// direct access to the LGFX device (LVGL flush/touch glue)
|
||||
lgfx::LGFX_Device* lgfxDevice() { return &disp; }
|
||||
|
||||
// reliable touch read in logical (UI_ZOOM-scaled) coords; returns false when
|
||||
// not touched (LGFXDisplay::getTouch reads an uninitialized point on release)
|
||||
bool readTouch(int& x, int& y) {
|
||||
lgfx::touch_point_t tp;
|
||||
if (disp.getTouch(&tp, 1) == 0) return false;
|
||||
x = tp.x / UI_ZOOM;
|
||||
y = tp.y / UI_ZOOM;
|
||||
return true;
|
||||
}
|
||||
|
||||
// shadows LGFXDisplay::begin() (non-virtual, called on the concrete type in
|
||||
// main.cpp) - identical except rotation: our panel config already carries
|
||||
// offset_rotation=1 for the landscape mounting, so no extra rotation here
|
||||
bool begin() {
|
||||
// dark theme: deep navy ground, MeshCore blue accents (RGB565)
|
||||
UIColor::window_bkg = 0x0885; // dark navy
|
||||
UIColor::title_bkg = 0x1299; // meshcore blue
|
||||
UIColor::title_txt = 0xFFFF;
|
||||
UIColor::primary_txt = 0xE73C; // near-white
|
||||
UIColor::secondary_txt = 0x8D59; // muted blue-gray
|
||||
UIColor::warning_txt = 0xFD20; // orange
|
||||
UIColor::popup_bkg = 0x1299;
|
||||
UIColor::popup_txt = 0xFFFF;
|
||||
UIColor::corp_blue = 0x5DBF; // sky blue (own messages, icons)
|
||||
|
||||
turnOn();
|
||||
display->init();
|
||||
display->setRotation(0);
|
||||
display->setColorDepth(8);
|
||||
// The zoomed sprite blit stops a pixel short of the panel edge, so those
|
||||
// pixels keep whatever the panel powered up with (they showed as coloured
|
||||
// lines down the right side and along the bottom). Clear the panel once.
|
||||
display->fillScreen(UIColor::window_bkg);
|
||||
display->setBrightness(153);
|
||||
display->setTextColor(TFT_WHITE);
|
||||
|
||||
buffer.setColorDepth(8);
|
||||
buffer.setPsram(true);
|
||||
buffer.createSprite(width(), height());
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
; Seeed Wio Tracker L2 Pro (alpha) - Wio-S3 module: ESP32-S3R8 + SX1262
|
||||
; Pin map derived from Seeed's Meshtastic support branch (meshtastic/firmware
|
||||
; PR #10909 + device-ui `wio-l2` branch).
|
||||
[Wio_Tracker_L2]
|
||||
extends = esp32_base
|
||||
board = seeed_wio_tracker_l2
|
||||
build_flags = ${esp32_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/wio-tracker-l2
|
||||
-D SEEED_WIO_TRACKER_L2
|
||||
-D PIN_BOARD_SDA=47
|
||||
-D PIN_BOARD_SCL=48
|
||||
-D PIN_USER_BTN=0
|
||||
-D USE_SX1262
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D P_LORA_NSS=21
|
||||
-D P_LORA_RESET=7
|
||||
-D P_LORA_BUSY=8
|
||||
-D P_LORA_DIO_1=9
|
||||
-D P_LORA_SCLK=4
|
||||
-D P_LORA_MISO=5
|
||||
-D P_LORA_MOSI=6
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
-D PIN_GPS_TX=18 ; L76K TX -> ESP32 RX
|
||||
-D PIN_GPS_RX=17 ; L76K RX <- ESP32 TX
|
||||
-D GPS_BAUD_RATE=9600
|
||||
-D ENV_SKIP_GPS_DETECT=1 ; GPS is always present, powered via IO expander
|
||||
-D DISPLAY_CLASS=WioTrackerL2Display
|
||||
-D UI_ZOOM=2
|
||||
; disable ALL env sensor I2C probes: this bus carries six onboard chips
|
||||
; (TCA9535 0x21, AW35615 0x22, LP5814 0x2C, ADS1115 0x48, GT911 0x5D,
|
||||
; ES8311 0x18) and stray probe matches can hang sensors.begin()
|
||||
-D ENV_INCLUDE_AHTX0=0
|
||||
-D ENV_INCLUDE_BME280=0
|
||||
-D ENV_INCLUDE_BMP280=0
|
||||
-D ENV_INCLUDE_SHTC3=0
|
||||
-D ENV_INCLUDE_SHT4X=0
|
||||
-D ENV_INCLUDE_INA3221=0
|
||||
-D ENV_INCLUDE_INA219=0
|
||||
-D ENV_INCLUDE_INA226=0
|
||||
-D ENV_INCLUDE_INA260=0
|
||||
-D ENV_INCLUDE_MLX90614=0
|
||||
-D ENV_INCLUDE_VL53L0X=0
|
||||
-D ENV_INCLUDE_BME680=0
|
||||
-D ENV_INCLUDE_BMP085=0
|
||||
-D ENV_INCLUDE_LPS22HB=0
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/wio-tracker-l2>
|
||||
+<helpers/sensors/*.cpp>
|
||||
+<helpers/ui/LGFXDisplay.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
lovyan03/LovyanGFX @ 1.2.30 ; pinned: first release that links NV3031B under gnu++14
|
||||
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip
|
||||
|
||||
; -------- Companion: BLE phone app, standard display UI --------------------
|
||||
[env:Wio_Tracker_L2_companion_radio_ble]
|
||||
extends = Wio_Tracker_L2
|
||||
build_flags =
|
||||
${Wio_Tracker_L2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D HAS_TOUCH=1
|
||||
-D AUTO_OFF_MILLIS=60000 ; LCD, no burn-in worry; 1 min screen timeout
|
||||
build_src_filter = ${Wio_Tracker_L2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Wio_Tracker_L2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; -------- Companion: USB serial link, standard display UI ------------------
|
||||
[env:Wio_Tracker_L2_companion_radio_usb]
|
||||
extends = Wio_Tracker_L2
|
||||
build_flags =
|
||||
${Wio_Tracker_L2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D ENABLE_USB_INTERFACE
|
||||
-D HAS_TOUCH=1
|
||||
-D AUTO_OFF_MILLIS=60000
|
||||
build_src_filter = ${Wio_Tracker_L2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Wio_Tracker_L2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; -------- Standalone repeater: extends mesh coverage, no phone needed -------
|
||||
[env:Wio_Tracker_L2_repeater]
|
||||
extends = Wio_Tracker_L2
|
||||
build_flags =
|
||||
${Wio_Tracker_L2.build_flags}
|
||||
-D ADVERT_NAME='"WioL2 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${Wio_Tracker_L2.build_src_filter}
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
lib_deps =
|
||||
${Wio_Tracker_L2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; -------- Standalone room server: shared message board / BBS ----------------
|
||||
[env:Wio_Tracker_L2_room_server]
|
||||
extends = Wio_Tracker_L2
|
||||
build_flags =
|
||||
${Wio_Tracker_L2.build_flags}
|
||||
-D ADVERT_NAME='"WioL2 Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
build_src_filter = ${Wio_Tracker_L2.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${Wio_Tracker_L2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
WioTrackerL2Board board;
|
||||
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
GpsTapStream gps_tap(Serial1); // GSV sats-in-view / SNR watcher
|
||||
MicroNMEALocationProvider gps(gps_tap, &rtc_clock);
|
||||
EnvironmentSensorManager sensors(gps);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
MESH_DEBUG_PRINTLN("radio_init: rtc + sx1262 init");
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire); // Wire already running on 47/48 from board.begin()
|
||||
|
||||
bool ok = radio.std_init(&spi);
|
||||
MESH_DEBUG_PRINTLN("radio_init: SX1262 %s", ok ? "OK" : "FAILED");
|
||||
return ok;
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include "WioTrackerL2Board.h"
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
#include "GpsTap.h"
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include "WioTrackerL2Display.h"
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern WioTrackerL2Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
extern GpsTapStream gps_tap;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
Reference in new issue
Block a user