diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index cebf0cfe5c..af848b7011 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -46,6 +46,8 @@ jobs: - wio_wm1110_repeater # SX1276 (ESP32) - Tbeam_SX1276_repeater + # Seeed Wio Tracker L2 Pro (ESP32-S3, LovyanGFX display) + - Wio_Tracker_L2_companion_radio_ble steps: - name: Clone Repo diff --git a/boards/seeed_wio_tracker_l2.json b/boards/seeed_wio_tracker_l2.json new file mode 100644 index 0000000000..706ed4c170 --- /dev/null +++ b/boards/seeed_wio_tracker_l2.json @@ -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" +} diff --git a/variants/wio-tracker-l2/GpsTap.h b/variants/wio-tracker-l2/GpsTap.h new file mode 100644 index 0000000000..020f5e9b18 --- /dev/null +++ b/variants/wio-tracker-l2/GpsTap.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +// 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); } +}; diff --git a/variants/wio-tracker-l2/README.md b/variants/wio-tracker-l2/README.md new file mode 100644 index 0000000000..29fbece6fb --- /dev/null +++ b/variants/wio-tracker-l2/README.md @@ -0,0 +1,64 @@ +# 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 and `meshtastic/device-ui` + (`wio-l2` branches). diff --git a/variants/wio-tracker-l2/Sound.cpp b/variants/wio-tracker-l2/Sound.cpp new file mode 100644 index 0000000000..b84f4a7719 --- /dev/null +++ b/variants/wio-tracker-l2/Sound.cpp @@ -0,0 +1,157 @@ +#include "Sound.h" + +#include + +#include +#include +#include +#include +#include "AudioBoard.h" // pschatzmann/arduino-audio-driver +#include +#include + +// 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); } diff --git a/variants/wio-tracker-l2/Sound.h b/variants/wio-tracker-l2/Sound.h new file mode 100644 index 0000000000..9e5f633c0b --- /dev/null +++ b/variants/wio-tracker-l2/Sound.h @@ -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(); diff --git a/variants/wio-tracker-l2/WioTrackerL2Board.cpp b/variants/wio-tracker-l2/WioTrackerL2Board.cpp new file mode 100644 index 0000000000..edeaacce52 --- /dev/null +++ b/variants/wio-tracker-l2/WioTrackerL2Board.cpp @@ -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); +} diff --git a/variants/wio-tracker-l2/WioTrackerL2Board.h b/variants/wio-tracker-l2/WioTrackerL2Board.h new file mode 100644 index 0000000000..c5644e2f20 --- /dev/null +++ b/variants/wio-tracker-l2/WioTrackerL2Board.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#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(); +}; diff --git a/variants/wio-tracker-l2/WioTrackerL2Display.h b/variants/wio-tracker-l2/WioTrackerL2Display.h new file mode 100644 index 0000000000..d9089a64d4 --- /dev/null +++ b/variants/wio-tracker-l2/WioTrackerL2Display.h @@ -0,0 +1,210 @@ +#pragma once + +#include +#include + +#define LGFX_USE_V1 +#include + +// --------------------------------------------------------------------------- +// 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; + } +}; diff --git a/variants/wio-tracker-l2/platformio.ini b/variants/wio-tracker-l2/platformio.ini new file mode 100644 index 0000000000..9434d08187 --- /dev/null +++ b/variants/wio-tracker-l2/platformio.ini @@ -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> + + + + + + +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} + + + +<../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} + + + +<../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} diff --git a/variants/wio-tracker-l2/target.cpp b/variants/wio-tracker-l2/target.cpp new file mode 100644 index 0000000000..a87da39249 --- /dev/null +++ b/variants/wio-tracker-l2/target.cpp @@ -0,0 +1,35 @@ +#include +#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 +} diff --git a/variants/wio-tracker-l2/target.h b/variants/wio-tracker-l2/target.h new file mode 100644 index 0000000000..11a66e4ec1 --- /dev/null +++ b/variants/wio-tracker-l2/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include "WioTrackerL2Board.h" +#include +#include +#include +#include +#include "GpsTap.h" +#ifdef DISPLAY_CLASS + #include "WioTrackerL2Display.h" + #include +#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();