diff --git a/boards/seeed_meshpager_x2.json b/boards/seeed_meshpager_x2.json new file mode 100644 index 000000000..94a8f1861 --- /dev/null +++ b/boards/seeed_meshpager_x2.json @@ -0,0 +1,45 @@ +{ + "build": { + "arduino": { + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_USB_CDC_ON_BOOT=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0X303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3box" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "SenseCAP MeshPager X2 (16 MB FLASH, 8 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.seeedstudio.com", + "vendor": "Seeed Studio" +} diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index e9e8f7d34..fdefb6e50 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1007,7 +1007,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \ - defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(RAK_WISMESH_TAP_V2)) && \ + defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(RAK_WISMESH_TAP_V2) || \ + defined(SEEED_MESHPAGER_X2)) && \ HAS_TFT // switch BT off by default; use TFT programming mode or hotkey to enable config.bluetooth.enabled = false; diff --git a/variants/esp32s3/seeed_mesh_pager_x2/pins_arduino.h b/variants/esp32s3/seeed_mesh_pager_x2/pins_arduino.h new file mode 100644 index 000000000..506bda92e --- /dev/null +++ b/variants/esp32s3/seeed_mesh_pager_x2/pins_arduino.h @@ -0,0 +1,34 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 17; +static const uint8_t SCL = 18; + +static const uint8_t SS = -1; +static const uint8_t MOSI = 14; +static const uint8_t MISO = 16; +static const uint8_t SCK = 15; + +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; + +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/seeed_mesh_pager_x2/platformio.ini b/variants/esp32s3/seeed_mesh_pager_x2/platformio.ini new file mode 100644 index 000000000..011fe9271 --- /dev/null +++ b/variants/esp32s3/seeed_mesh_pager_x2/platformio.ini @@ -0,0 +1,105 @@ +[env:seeed_meshpager_x2] +custom_meshtastic_hw_model = 100 ; TODO: assign a unique number for this hardware model +custom_meshtastic_hw_model_slug = SEEED_MESHPAGER_X2 +custom_meshtastic_architecture = esp32s3 +custom_meshtastic_actively_supported = true +custom_meshtastic_support_level = 1 +custom_meshtastic_display_name = Seeed MeshPager X2 +custom_meshtastic_images = meshpager_x2.svg +custom_meshtastic_tags = Seeed +custom_meshtastic_requires_dfu = true +custom_meshtastic_partition_scheme = 16MB + +board = seeed_meshpager_x2 +board_level = pr +extends = esp32s3_base +board_check = true +upload_protocol = esptool +board_build.partitions = default_16MB.csv + +; Build flags for MeshPager X2 +build_flags = ${esp32s3_base.build_flags} + -I variants/esp32s3/seeed_mesh_pager_x2 + -D SEEED_MESHPAGER_X2 + -D CONFIG_ARDUHAL_ESP_LOG + -D CONFIG_ARDUHAL_LOG_COLORS=1 + -D CONFIG_DISABLE_HAL_LOCKS=1 + -D MESHTASTIC_EXCLUDE_WEBSERVER=1 + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE + -D INPUTDRIVER_ENCODER_TYPE=4 + -D INPUTDRIVER_ENCODER_UP=0x40 + -D INPUTDRIVER_ENCODER_DOWN=0x41 + -D INPUTDRIVER_ENCODER_LEFT=0x42 + -D INPUTDRIVER_ENCODER_RIGHT=0x43 + -D INPUTDRIVER_ENCODER_ENT=0x44 + -D INPUTDRIVER_ENCODER_ESC=0x45 + -D INPUTDRIVER_BUTTON_TYPE=9 + -D LV_USE_LOG=0 + -D LV_BUILD_TEST=0 + -D USE_LOG_DEBUG + -D LOG_DEBUG_INC=\"DebugConfiguration.h\" + -D RADIOLIB_SPI_PARANOID=0 + ; Display Configuration + -D HAS_SCREEN=1 + -D HAS_TFT=1 + -D USE_I2S_BUZZER + ; SD Card - 1-bit SDIO mode + -D HAS_SDCARD + -D HAS_SD_MMC + -D SD_SCLK_PIN=15 + -D SD_MOSI_PIN=14 + -D SD_MISO_PIN=16 + -D SDCARD_CS=-1 ; control via IO Expander pin 13 + -D SD_SPI_FREQUENCY=40000000 + ; Memory Configuration + -D RAM_SIZE=5120 + -D LGFX_BUFSIZE=153600 + -D LV_CACHE_DEF_SIZE=2097152 + ; Graphics/UI Configuration + -D LGFX_DRIVER_TEMPLATE + -D DISPLAY_SIZE=320x240 + -D LGFX_DRIVER=LGFX_GENERIC + -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_GENERIC.h\" + -D LGFX_PANEL=ST7789P3 + -D LGFX_SCREEN_WIDTH=240 + -D LGFX_SCREEN_HEIGHT=320 + -D LGFX_INVERT_COLOR=true + -D LGFX_RGB_ORDER=true + -D LGFX_CFG_HOST=SPI2_HOST + -D LGFX_PIN_SCK=12 + -D LGFX_PIN_MOSI=11 + -D LGFX_PIN_MISO=-1 + -D LGFX_PIN_DC=13 + -D LGFX_PIN_CS=10 + -D LGFX_PIN_RST=-1 + -D LGFX_PIN_BL=46 + -D LGFX_ROTATION=1 + -D SPI_FREQUENCY=80000000 + -D VIEW_320x240 + -D MAP_FULL_REDRAW + -D USE_PACKET_API + +build_src_filter = ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/seeed_mesh_pager_x2> + +lib_deps = + ${esp32s3_base.lib_deps} + ${device-ui_base.lib_deps} + ; Audio processing and synthesis + # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix + https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip + # renovate: datasource=pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM + earlephilhower/ESP8266SAM@1.1.0 + # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver + https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip + ; IO Expander (TCA6424) + # renovate: datasource=pio depName=TCA9555 packageName=RobTillaart/library/TCA9555 + RobTillaart/TCA9555@0.4.4 + ; Display driver (ST7789) + # renovate: datasource=pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX + lovyan03/LovyanGFX@1.2.26 + ; Sensors: ADS1115 (if used), BMM350, SHT4x, LSM6DSO, etc. + # renovate: datasource=github-tags depName=Adafruit_ADS1X15 packageName=adafruit/Adafruit_ADS1X15 + https://github.com/adafruit/Adafruit_ADS1X15/archive/refs/tags/2.6.2.zip diff --git a/variants/esp32s3/seeed_mesh_pager_x2/rfswitch.h b/variants/esp32s3/seeed_mesh_pager_x2/rfswitch.h new file mode 100644 index 000000000..42d63ce60 --- /dev/null +++ b/variants/esp32s3/seeed_mesh_pager_x2/rfswitch.h @@ -0,0 +1,24 @@ +#include "RadioLib.h" + +// MeshPager X2 RF switch matrix for the Semtech LR2021 chip (Wio-LR2021 module). +// Derived from SenseCAP BSP components/lr20xx/src/ral_lr20xx_bsp.c: +// DIO5: active during RX_LF and TX_HF +// DIO6: active during all RF states (RX_LF, TX_LF, RX_HF, TX_HF) + +static const uint32_t lr20x0_rfswitch_dio_pins[] = { + RADIOLIB_LR2021_DIO5, + RADIOLIB_LR2021_DIO6, + RADIOLIB_NC, + RADIOLIB_NC, + RADIOLIB_NC, +}; + +static const Module::RfSwitchMode_t lr20x0_rfswitch_table[] = { + // mode DIO5 DIO6 + {LR2021::MODE_STBY, {LOW, LOW}}, // standby: both off + {LR2021::MODE_RX, {HIGH, HIGH}}, // RX sub-GHz (LF): DIO5 on, DIO6 on + {LR2021::MODE_TX, {LOW, HIGH}}, // TX sub-GHz (LF): DIO5 off, DIO6 on + {LR2021::MODE_RX_HF, {LOW, HIGH}}, // RX 2.4 GHz (HF): DIO5 off, DIO6 on + {LR2021::MODE_TX_HF, {HIGH, HIGH}}, // TX 2.4 GHz (HF): DIO5 on, DIO6 on + END_OF_MODE_TABLE, +}; diff --git a/variants/esp32s3/seeed_mesh_pager_x2/variant.cpp b/variants/esp32s3/seeed_mesh_pager_x2/variant.cpp new file mode 100644 index 000000000..24e72cdee --- /dev/null +++ b/variants/esp32s3/seeed_mesh_pager_x2/variant.cpp @@ -0,0 +1,225 @@ +#include "variant.h" +#include "Arduino.h" + +#include +#include "AudioBoard.h" + +namespace { + +// TCA6424 register map +constexpr uint8_t TCA6424_INPUT_PORT0 = 0x00; +constexpr uint8_t TCA6424_OUTPUT_PORT0 = 0x04; +constexpr uint8_t TCA6424_CONFIG_PORT0 = 0x0C; + +constexpr uint8_t EXP_PIN_BUTTON_UP = 0; +constexpr uint8_t EXP_PIN_BUTTON_DOWN = 1; +constexpr uint8_t EXP_PIN_BUTTON_LEFT = 2; +constexpr uint8_t EXP_PIN_BUTTON_RIGHT = 3; +constexpr uint8_t EXP_PIN_BUTTON_CONFIRM = 4; +constexpr uint8_t EXP_PIN_BUTTON_RETURN = 5; +constexpr uint8_t EXP_PIN_BUTTON_PWR = 6; +constexpr uint8_t EXP_PIN_SD_DETECT = 7; +constexpr uint8_t EXP_PIN_GNSS_RTC_INT = 8; +constexpr uint8_t EXP_PIN_GNSS_SLEEP_INT = 9; +constexpr uint8_t EXP_PIN_IMU_INT1 = 10; + +constexpr uint8_t EXP_PIN_SEN_EN = 12; +constexpr uint8_t EXP_PIN_SD_PWR_EN = 13; +constexpr uint8_t EXP_PIN_LCD_PWR_EN = 14; +constexpr uint8_t EXP_PIN_GNSS_VRTC_EN = 15; +constexpr uint8_t EXP_PIN_GNSS_PWR_EN = 16; +constexpr uint8_t EXP_PIN_BAT_ADC_EN = 17; +constexpr uint8_t EXP_PIN_PA_PWR_EN = 18; +constexpr uint8_t EXP_PIN_PWR_HOLD = 20; +constexpr uint8_t EXP_PIN_LCD_RST = 21; +constexpr uint8_t EXP_PIN_LORA_RESET = 22; +constexpr uint8_t EXP_PIN_GNSS_RST = 23; + +class Tca6424 { + public: + explicit Tca6424(uint8_t address) : address(address) {} + + bool begin(TwoWire &wire) + { + this->wire = &wire; + + // Default all pins as inputs and all outputs low in our local shadow. + config[0] = 0xFF; + config[1] = 0xFF; + config[2] = 0xFF; + out[0] = 0x00; + out[1] = 0x00; + out[2] = 0x00; + + return writeReg(TCA6424_OUTPUT_PORT0 + 0, out[0]) && writeReg(TCA6424_OUTPUT_PORT0 + 1, out[1]) && + writeReg(TCA6424_OUTPUT_PORT0 + 2, out[2]) && writeReg(TCA6424_CONFIG_PORT0 + 0, config[0]) && + writeReg(TCA6424_CONFIG_PORT0 + 1, config[1]) && writeReg(TCA6424_CONFIG_PORT0 + 2, config[2]); + } + + bool pinMode(uint8_t pin, bool output) + { + const uint8_t port = pin / 8; + const uint8_t bit = pin % 8; + if (port > 2) { + return false; + } + + if (output) { + config[port] &= ~(1U << bit); // 0 = output + } else { + config[port] |= (1U << bit); // 1 = input + } + return writeReg(TCA6424_CONFIG_PORT0 + port, config[port]); + } + + bool digitalWrite(uint8_t pin, bool high) + { + const uint8_t port = pin / 8; + const uint8_t bit = pin % 8; + if (port > 2) { + return false; + } + + if (high) { + out[port] |= (1U << bit); + } else { + out[port] &= ~(1U << bit); + } + return writeReg(TCA6424_OUTPUT_PORT0 + port, out[port]); + } + + private: + bool writeReg(uint8_t reg, uint8_t value) + { + wire->beginTransmission(address); + wire->write(reg); + wire->write(value); + return wire->endTransmission() == 0; + } + + uint8_t address; + TwoWire *wire = nullptr; + uint8_t config[3] = {0xFF, 0xFF, 0xFF}; + uint8_t out[3] = {0x00, 0x00, 0x00}; +}; + +Tca6424 ioExpander(IO_EXPANDER_I2C_ADDR); +DriverPins pinsAudioBoardES8311; +AudioBoard board(AudioDriverES8311, pinsAudioBoardES8311); +bool expanderReady = false; +bool earlyInitFailed = false; + +bool configureInput(uint8_t pin) +{ + return ioExpander.pinMode(pin, false); +} + +bool configureOutput(uint8_t pin, bool level) +{ + return ioExpander.pinMode(pin, true) && ioExpander.digitalWrite(pin, level); +} + +} // namespace + +void earlyInitVariant() +{ + // Use explicit pins to avoid accidental macro drift during early startup. + Wire.begin(17, 18); + + if (!ioExpander.begin(Wire)) { + earlyInitFailed = true; + return; + } + + // Buttons + SD detect + IMU interrupt are inputs. + bool ok = true; + ok &= configureInput(EXP_PIN_BUTTON_UP); + ok &= configureInput(EXP_PIN_BUTTON_DOWN); + ok &= configureInput(EXP_PIN_BUTTON_LEFT); + ok &= configureInput(EXP_PIN_BUTTON_RIGHT); + ok &= configureInput(EXP_PIN_BUTTON_CONFIRM); + ok &= configureInput(EXP_PIN_BUTTON_RETURN); + ok &= configureInput(EXP_PIN_BUTTON_PWR); + ok &= configureInput(EXP_PIN_SD_DETECT); + ok &= configureInput(EXP_PIN_IMU_INT1); + + // Keep board alive and power domains in known state. + ok &= configureOutput(EXP_PIN_PWR_HOLD, true); + ok &= configureOutput(EXP_PIN_BAT_ADC_EN, false); + ok &= configureOutput(EXP_PIN_SEN_EN, true); + ok &= configureOutput(EXP_PIN_SD_PWR_EN, true); + ok &= configureOutput(EXP_PIN_PA_PWR_EN, true); + ok &= configureOutput(EXP_PIN_LORA_RESET, true); + + // ST7789P3 power-on and reset sequence (mirrors bsp_lcd_hard_reset in BSP demo). + ok &= configureOutput(EXP_PIN_LCD_PWR_EN, true); + delay(20); + ok &= configureOutput(EXP_PIN_LCD_RST, false); // assert reset (active low) + delay(20); + ok &= ioExpander.digitalWrite(EXP_PIN_LCD_RST, true); // release reset + delay(120); // allow ST7789P3 to complete internal initialization + + // Match demo LR20xx reset sequence on expander pin 22. + ok &= ioExpander.digitalWrite(EXP_PIN_LORA_RESET, false); + delay(10); + ok &= ioExpander.digitalWrite(EXP_PIN_LORA_RESET, true); + delay(10); + + // GNSS sequence from demo firmware. + ok &= configureOutput(EXP_PIN_GNSS_PWR_EN, true); + delay(10); + ok &= configureOutput(EXP_PIN_GNSS_VRTC_EN, true); + delay(10); + ok &= configureOutput(EXP_PIN_GNSS_RST, true); + delay(10); + ok &= ioExpander.digitalWrite(EXP_PIN_GNSS_RST, false); + delay(10); + ok &= configureOutput(EXP_PIN_GNSS_SLEEP_INT, true); + delay(10); + ok &= configureOutput(EXP_PIN_GNSS_RTC_INT, false); + + expanderReady = ok; + earlyInitFailed = !ok; +} + +void lateInitVariant() +{ + if (!expanderReady) { + if (earlyInitFailed) { + //LOG_ERROR("Skipping ES8311 init because TCA6424 init/config failed"); + } else { + //LOG_ERROR("Skipping ES8311 init because TCA6424 is not ready"); + } + return; + } + + Wire1.begin(I2C_SDA1, I2C_SCL1); + + // I2C: function, bus + pinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire1); + // I2S: function, mclk, bck, ws, data_out, data_in + pinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN); + + CodecConfig cfg; + cfg.input_device = ADC_INPUT_LINE1; + cfg.output_device = DAC_OUTPUT_ALL; + cfg.i2s.bits = BIT_LENGTH_16BITS; + cfg.i2s.rate = RATE_44K; + + board.begin(cfg); + board.setVolume(75); + //LOG_INFO("ES8311 audio board initialized on I2C1"); +} + +extern "C" void meshtastic_variant_pre_radio_reset(void) +{ + if (!expanderReady) { + return; + } + + // Match SenseCAP demo: pulse LR20xx reset through expander right before radio init. + ioExpander.digitalWrite(EXP_PIN_LORA_RESET, false); + delay(10); + ioExpander.digitalWrite(EXP_PIN_LORA_RESET, true); + delay(10); +} diff --git a/variants/esp32s3/seeed_mesh_pager_x2/variant.h b/variants/esp32s3/seeed_mesh_pager_x2/variant.h new file mode 100644 index 000000000..3ff6e671a --- /dev/null +++ b/variants/esp32s3/seeed_mesh_pager_x2/variant.h @@ -0,0 +1,161 @@ +#pragma once + +/** + * SenseCAP MeshPager X2 - ESP32-S3 LoRa/GNSS/Audio Pager + * + * Target: ESP32-S3 + * Display: ST7789P3 LCD 240×320 RGB565 (SPI) + * Radio: LR20xx wideband (LR1120/LR1121) + * Audio: ES8311 DAC + ES7243E ADC + * GNSS: L76K UART + * Storage: SD card (1-bit SDIO mode) + * IO Expander: TCA6424 + */ + +// Buttons - via IO Expander (TCA6424) +#define BUTTON_UP GPIO_NUM_NC // EXP pin 0, active low +#define BUTTON_DOWN GPIO_NUM_NC // EXP pin 1, active low +#define BUTTON_LEFT GPIO_NUM_NC // EXP pin 2, active low +#define BUTTON_RIGHT GPIO_NUM_NC // EXP pin 3, active low +#define BUTTON_CONFIRM GPIO_NUM_NC // EXP pin 4, active low +#define BUTTON_RETURN GPIO_NUM_NC // EXP pin 5, active low +#define BUTTON_PIN 9 // Power/wake button (GPIO 9), active high + +// I2C Buses +#define I2C_SDA 17 // I2C0: SDA (IO Expander, RTC) +#define I2C_SCL 18 // I2C0: SCL + +#define I2C_SDA1 47 // I2C1: SDA (Sensors, Audio codecs) +#define I2C_SCL1 48 // I2C1: SCL + +// I2C0 devices: TCA6424 IO expander, YSN8900E RTC +// I2C1 devices: ES8311, ES7243E, SHT4X, LSM6DSO, BMM350 + +// IO Expander (TCA6424) +#define HAS_IO_EXPANDER +#define IO_EXPANDER_I2C_PORT 0 +#define IO_EXPANDER_I2C_ADDR 0x22 +#define IO_EXPANDER_INT 2 // GPIO 2 interrupt from expander + +// Battery & Power +#define BATTERY_PIN 1 // GPIO 1, ADC for VBAT with 2:1 divider +#define ADC_CHANNEL ADC_CHANNEL_0 // ADC1_GPIO1_CHANNEL +#define ADC_MULTIPLIER 2.0 * 1.045 // 2.0 divider + 4.5% correction +#define ADC_ATTEN ADC_ATTEN_DB_12 + +#define USE_POWERSAVE +#define SLEEP_TIME 120 + +#if 0 +#define TFT_CS 10 // Chip select (GPIO 10, SPI2) +#define TFT_SDA 11 // MOSI (GPIO 11) +#define TFT_SCK 12 // Clock (GPIO 12) +#define TFT_MISO -1 // Not used for display +#define TFT_A0 13 // DC / Data-Command (GPIO 13) +#define TFT_BL 46 // Backlight PWM (GPIO 46, LEDC) +#define TFT_RESET -1 // Reset via expander (EXP pin 21), managed separately + +// TFT uses SPI bus (SPI2_HOST on ESP32-S3) +#define ST7789_CS TFT_CS +#define ST7789_RS TFT_A0 +#define ST7789_SDA TFT_SDA +#define ST7789_SCK TFT_SCK +#define ST7789_MISO TFT_MISO +#define ST7789_RESET TFT_RESET +#define ST7789_BUSY -1 +#define ST7789_BL TFT_BL +#define ST7789_SPI_HOST SPI2_HOST + +//#define SPI_FREQUENCY 80000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 320 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 + +#define BRIGHTNESS_DEFAULT 130 // Medium-low brightness (0-255) +#define SCREEN_TRANSITION_FRAMERATE 5 +#endif + +// LoRa Radio - Semtech LR2021 (Wio-LR2021 module) +#define USE_LR2021 // Wideband radio supporting sub-GHz and 2.4 GHz + +// LoRa uses SPI1 bus +#define HW_SPI1_DEVICE + +#define LORA_SCK 3 // GPIO 3 +#define LORA_MOSI 4 // GPIO 4 +#define LORA_MISO 5 // GPIO 5 +#define LORA_CS 6 // GPIO 6 + +#define LORA_DIO0 -1 // Not connected +#define LORA_DIO1 7 // GPIO 7 (IRQ) +#define LORA_DIO2 8 // GPIO 8 (BUSY) +#define LORA_DIO3 -1 // Not connected (managed via IO expander pin 22 for reset) + +#define LORA_RESET -1 // Reset via IO Expander (TCA6424 pin 22) + +#define LR2021_IRQ_PIN LORA_DIO1 +#define LR2021_NRESET_PIN LORA_RESET +#define LR2021_BUSY_PIN LORA_DIO2 +#define LR2021_SPI_NSS_PIN LORA_CS +#define LR2021_SPI_SCK_PIN LORA_SCK +#define LR2021_SPI_MOSI_PIN LORA_MOSI +#define LR2021_SPI_MISO_PIN LORA_MISO + +// LR2021 IRQ is on chip DIO8 (not the default DIO5). +#define LR2021_IRQ_DIO_NUM 8 + +// LR2021 TCXO supply voltage on DIO3 (1.8 V). +#define LR2021_DIO3_TCXO_VOLTAGE 1.8 +#define LR2021_DIO_AS_RF_SWITCH + +// GPS/GNSS - L76K (UART) +#define HAS_GPS 1 +#define GPS_BAUDRATE 115200 +#define GPS_TX_PIN 43 // UART1 TX +#define GPS_RX_PIN 44 // UART1 RX +#define GPS_THREAD_INTERVAL 50 // 50 ms update interval + +// Audio - I2S with ES8311 DAC + ES7243E ADC +#define HAS_I2S +#define DAC_I2S_BCK 39 // SCLK (GPIO 39) +#define DAC_I2S_WS 40 // LRLK (GPIO 40) +#define DAC_I2S_DOUT 41 // SDOUT/Speaker (GPIO 41) +#define DAC_I2S_DIN -1 // Not used for DAC +#define DAC_I2S_MCLK 38 // MCLK (GPIO 38) + +#define ADC_I2S_BCK 39 // SCLK (GPIO 39, shared with DAC) +#define ADC_I2S_WS 40 // LRLK (GPIO 40, shared with DAC) +#define ADC_I2S_DOUT -1 // Not used for ADC +#define ADC_I2S_DIN 42 // SDIN/Microphone (GPIO 42) +#define ADC_I2S_MCLK 38 // MCLK (GPIO 38, shared with DAC) + +// Audio Codec Addresses +#define HAS_ES8311 +#define ES8311_I2C_ADDR 0x18 // I2C1 address for ES8311 + +#define HAS_ES7243E +#define ES7243E_I2C_ADDR 0x14 /* I2C1 address for ES7243E ADC + Note: May conflict with BMM350 sensor in some configs */ + +// Sensors - I2C1 +#define HAS_BMM350 /* Magnetometer on I2C1 */ +#define HAS_SHT4X /* Temperature/Humidity on I2C1 */ +#define HAS_LSM6DSO /* IMU (Accel/Gyro) on I2C1 */ + +// RTC - via I2C0 +#define RTC_INT 45 // GPIO 45 (RTC interrupt input) + +// Debug UART +#define SERIAL_TX_PIN 43 // Shared with GPS TX +#define SERIAL_RX_PIN 44 // Shared with GPS RX + +// Display backlight control via LEDC +#define LEDC_TIMER LEDC_TIMER_0 +#define LEDC_MODE LEDC_LOW_SPEED_MODE +#define LEDC_OUTPUT_IO TFT_BL +#define LEDC_CHANNEL LEDC_CHANNEL_0 +#define LEDC_DUTY_RES LEDC_TIMER_13_BIT +#define LEDC_FREQUENCY 5000 // 5 kHz PWM