diff --git a/boards/crowpanel.json b/boards/crowpanel.json new file mode 100644 index 0000000..bb30b67 --- /dev/null +++ b/boards/crowpanel.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=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "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", "espidf"], + "name": "Elecrow CrowPanel (ESP32-S3 16MB Flash, 8MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 524288, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "monitor": { + "speed": 115200 + }, + "url": "https://www.elecrow.com/crowpanel-advance-hmi-intelligent-screen-esp32-ai-display.html", + "vendor": "Elecrow" +} \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 96eaa61..a111206 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -343,6 +343,18 @@ } #endif +// --- Non-T-Deck ESP32 targets (CrowPanel, etc.) --- +// Variables declared inside the LilyGo_TDeck_Pro block above that are +// referenced unconditionally in setup()/loop() need parallel declarations. +#if !defined(LilyGo_TDeck_Pro) && defined(ESP32) + CPUPowerManager cpuPower; + #define AGC_RESET_INTERVAL_MS 500 + static unsigned long lastAGCReset = 0; + static bool readerMode = false; + static bool notesMode = false; + static bool audiobookMode = false; +#endif + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -749,8 +761,8 @@ void setup() { initKeyboard(); #endif - // Initialize touch input (CST328) - #ifdef HAS_TOUCHSCREEN + // Initialize touch input (CST328 — T-Deck Pro only; CrowPanel uses GT911 via LovyanGFX) + #if defined(HAS_TOUCHSCREEN) && defined(CST328_PIN_INT) if (touchInput.begin(CST328_PIN_INT)) { MESH_DEBUG_PRINTLN("setup() - Touch input initialized"); } else { @@ -910,7 +922,7 @@ void loop() { cpuPower.loop(); // Audiobook: service audio decode regardless of which screen is active - #ifndef HAS_4G_MODEM + #if defined(LilyGo_TDeck_Pro) && !defined(HAS_4G_MODEM) { AudiobookPlayerScreen* abPlayer = (AudiobookPlayerScreen*)ui_task.getAudiobookScreen(); @@ -1110,10 +1122,12 @@ void loop() { #endif rtc_clock.tick(); // Periodic AGC reset - re-assert boosted RX gain to prevent sensitivity drift + #if defined(LilyGo_TDeck_Pro) if ((millis() - lastAGCReset) >= AGC_RESET_INTERVAL_MS) { radio_reset_agc(); lastAGCReset = millis(); } + #endif // Handle T-Deck Pro keyboard input #if defined(LilyGo_TDeck_Pro) handleKeyboardInput(); diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index e7c6f33..9dc047d 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -1,107 +1,50 @@ #pragma once -#include -#include +// ============================================================================= +// GxEPDDisplay STUB for CrowPanel (and other non-e-ink LGFX targets) +// +// This file shadows src/helpers/ui/GxEPDDisplay.h to prevent the LovyanGFX vs +// Adafruit_GFX GFXfont type collision at link time. MapScreen.h unconditionally +// includes GxEPDDisplay.h and uses a GxEPDDisplay* member — this stub provides +// the minimal API so that compilation and linking succeed. +// +// On CrowPanel the map screen is inert (no SD card when function switch is in +// WM mode, no keyboard to navigate to it). The _einkDisplay pointer in +// MapScreen will be a bad cast but is null-checked before every draw call. +// ============================================================================= -#define ENABLE_GxEPD2_GFX 0 +#include -#include -#include -#include -#include -#include -#include -#include - -// Inline CRC32 for frame change detection (replaces bakercp/CRC32 -// to avoid naming collision with PNGdec's bundled CRC32.h) -class FrameCRC32 { - uint32_t _crc = 0xFFFFFFFF; -public: - void reset() { _crc = 0xFFFFFFFF; } - template void update(T val) { - const uint8_t* p = (const uint8_t*)&val; - for (size_t i = 0; i < sizeof(T); i++) { - _crc ^= p[i]; - for (int b = 0; b < 8; b++) - _crc = (_crc >> 1) ^ (0xEDB88320 & -(int32_t)(_crc & 1)); - } - } - template void update(const T* data, size_t len) { - const uint8_t* p = (const uint8_t*)data; - for (size_t i = 0; i < len * sizeof(T); i++) { - _crc ^= p[i]; - for (int b = 0; b < 8; b++) - _crc = (_crc >> 1) ^ (0xEDB88320 & -(int32_t)(_crc & 1)); - } - } - uint32_t finalize() { return _crc ^ 0xFFFFFFFF; } -}; - -#include "DisplayDriver.h" +// GxEPD color constants referenced by MapScreen.h +#ifndef GxEPD_BLACK + #define GxEPD_BLACK 0 + #define GxEPD_WHITE 1 +#endif class GxEPDDisplay : public DisplayDriver { - -#if defined(EINK_DISPLAY_MODEL) - GxEPD2_BW display; - const float scale_x = EINK_SCALE_X; - const float scale_y = EINK_SCALE_Y; - const float offset_x = EINK_X_OFFSET; - const float offset_y = EINK_Y_OFFSET; -#else - GxEPD2_BW display; - const float scale_x = 1.5625f; - const float scale_y = 1.5625f; - const float offset_x = 0; - const float offset_y = 10; -#endif - bool _init = false; - bool _isOn = false; - uint16_t _curr_color; - FrameCRC32 display_crc; - int last_display_crc_value = 0; - public: -#if defined(EINK_DISPLAY_MODEL) - GxEPDDisplay() : DisplayDriver(128, 128), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} -#else - GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {} -#endif + GxEPDDisplay() : DisplayDriver(128, 128) {} - bool begin(); + // --- MapScreen raw pixel API (stubs) --- + void drawPixelRaw(int16_t x, int16_t y, uint16_t color) {} + int16_t rawWidth() { return 0; } + int16_t rawHeight() { return 0; } + void drawTextRaw(int16_t x, int16_t y, const char* text, uint16_t color) {} + void invalidateFrameCRC() {} - bool isOn() override {return _isOn;}; - void turnOn() override; - void turnOff() override; - void clear() override; - void startFrame(Color bkg = DARK) override; - void setTextSize(int sz) override; - void setColor(Color c) override; - void setCursor(int x, int y) override; - void print(const char* str) override; - void fillRect(int x, int y, int w, int h) override; - void drawRect(int x, int y, int w, int h) override; - void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; - uint16_t getTextWidth(const char* str) override; - void endFrame() override; - - // --- Raw pixel access for MapScreen (bypasses scaling) --- - void drawPixelRaw(int16_t x, int16_t y, uint16_t color) { - display.drawPixel(x, y, color); - } - int16_t rawWidth() { return display.width(); } - int16_t rawHeight() { return display.height(); } - - // Draw text at raw (unscaled) physical coordinates using built-in 5x7 font - void drawTextRaw(int16_t x, int16_t y, const char* text, uint16_t color) { - display.setFont(NULL); // Built-in 5x7 font - display.setTextSize(1); - display.setTextColor(color); - display.setCursor(x, y); - display.print(text); - } - - // Force endFrame() to push to display even if CRC unchanged - // (needed because drawPixelRaw bypasses CRC tracking) - void invalidateFrameCRC() { last_display_crc_value = 0; } + // --- DisplayDriver pure virtuals (no-op implementations) --- + bool isOn() override { return false; } + void turnOn() override {} + void turnOff() override {} + void clear() override {} + void startFrame(Color bkg = DARK) override {} + void setTextSize(int sz) override {} + void setColor(Color c) override {} + void setCursor(int x, int y) override {} + void print(const char* str) override {} + void fillRect(int x, int y, int w, int h) override {} + void drawRect(int x, int y, int w, int h) override {} + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override {} + uint16_t getTextWidth(const char* str) override { return 0; } + void endFrame() override {} }; \ No newline at end of file diff --git a/variants/crowpanel_70/CrowPanel70Board.h b/variants/crowpanel_70/CrowPanel70Board.h new file mode 100644 index 0000000..545178e --- /dev/null +++ b/variants/crowpanel_70/CrowPanel70Board.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +// CrowPanel 7.0" Advance Series +// +// V1.0: PCA9557/TCA9534 I/O expander at 0x18 for display power/reset/backlight +// V1.2: STC8H1K28 MCU at 0x30 (6-step brightness) +// V1.3: STC8H1K28 MCU at 0x30 (246-step brightness) +// +// I2C bus (shared between touch GT911, RTC at 0x51, and I/O controller) +// SDA = IO15, SCL = IO16 +// +// Function select switch (active-low DIP switches on PCB rear): +// S1=0 S0=0 → MIC & SPK (IO4/5/6 to speaker, IO19/20 to mic) +// S1=0 S0=1 → WM (IO4/5/6 + IO19/20 to wireless module) ← REQUIRED FOR LORA +// S1=1 S0=1 → MIC & TF Card (IO4/5/6 to SD, IO19/20 to mic) + +#define PIN_BOARD_SDA 15 +#define PIN_BOARD_SCL 16 + +// Touch pins (GT911 at 0x5D) +#define PIN_TOUCH_SDA PIN_BOARD_SDA +#define PIN_TOUCH_SCL PIN_BOARD_SCL +#define PIN_TOUCH_INT 1 // IO1_TP_INT (active low pulse, not level-based) +#define PIN_TOUCH_RST -1 // Controlled via STC8H1K28 P1.7 (v1.3) or TCA9534 (v1.0) + +// STC8H1K28 I2C address (v1.2/v1.3 only) +#define STC8H_ADDR 0x30 + +class CrowPanel70Board : public ESP32Board { +public: + void begin() { + // NOTE: Wire.begin(SDA, SCL) is called in target.cpp radio_init() BEFORE + // lcd.init(), to ensure correct init order for v1.3 STC8H1K28 backlight. + // Do NOT call Wire.begin() here — it would conflict with LovyanGFX's + // internal Wire usage for the GT911 touch controller. + + ESP32Board::begin(); + } + + const char* getManufacturerName() const override { +#ifdef CROWPANEL_V13 + return "CrowPanel 7.0 V1.3"; +#else + return "CrowPanel 7.0"; +#endif + } + + // --- STC8H1K28 control (v1.3) --- + // These are safe to call on all versions; on v1.0 they'll talk to + // a nonexistent I2C device and silently fail. + + void setBacklightBrightness(uint8_t level) { + // 0 = max, 244 = min, 245 = off + Wire.beginTransmission(STC8H_ADDR); + Wire.write(level); + Wire.endTransmission(); + } + + void buzzerOn() { + Wire.beginTransmission(STC8H_ADDR); + Wire.write(246); + Wire.endTransmission(); + } + + void buzzerOff() { + Wire.beginTransmission(STC8H_ADDR); + Wire.write(247); + Wire.endTransmission(); + } +}; \ No newline at end of file diff --git a/variants/crowpanel_70/CrowPanel70Display.h b/variants/crowpanel_70/CrowPanel70Display.h new file mode 100644 index 0000000..3e53a1e --- /dev/null +++ b/variants/crowpanel_70/CrowPanel70Display.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "LGFX_CrowPanel70.h" + +// Custom display class for CrowPanel 7.0" that handles native landscape touch coordinates +class CrowPanel70Display : public LGFXDisplay { +public: + CrowPanel70Display(int w, int h, LGFX_Device &disp) : LGFXDisplay(w, h, disp) {} + + // Override getTouch for native landscape display (800x480) + // The 7" panel is natively landscape, unlike the 3.5" which is portrait rotated + // + // GT911 touch coords are in physical space (0-799, 0-479). + // We map them to logical space using: + // display->width()/height() = physical LGFX dimensions (800, 480) + // width()/height() = logical DisplayDriver dimensions (128, 64) + bool getTouch(int *x, int *y) override { + lgfx::v1::touch_point_t point; + int touch_count = display->getTouch(&point); + + if (touch_count > 0) { + // Physical touch → logical coords + *x = point.x * width() / display->width(); + *y = point.y * height() / display->height(); + return true; + } + + *x = -1; + *y = -1; + return false; + } +}; \ No newline at end of file diff --git a/variants/crowpanel_70/LGFX_CrowPanel70.h b/variants/crowpanel_70/LGFX_CrowPanel70.h new file mode 100644 index 0000000..547aa93 --- /dev/null +++ b/variants/crowpanel_70/LGFX_CrowPanel70.h @@ -0,0 +1,204 @@ +#pragma once + +#define LGFX_USE_V1 +#include + +#ifndef CROWPANEL_V13 + // V1.0 uses TCA9534 I/O expander at 0x18 + #include +#endif + +#include +#include + +// CrowPanel 7.0" uses RGB parallel interface (16-bit) with: +// V1.0: TCA9534 I/O expander at 0x18 for display power/reset/backlight +// V1.2/V1.3: STC8H1K28 MCU at 0x30 for backlight, buzzer, speaker control +// +// Display: 800x480 native landscape, ST7277 driver IC +// Touch: GT911 at 0x5D on I2C (SDA=15, SCL=16) +// +// STC8H1K28 (v1.3) command byte reference: +// 0 = backlight max brightness +// 1-244 = backlight dimmer (244 = dimmest) +// 245 = backlight off +// 246 = buzzer on +// 247 = buzzer off +// 248 = speaker amp on +// 249 = speaker amp off + +#define STC8H_I2C_ADDR 0x30 + +class LGFX_CrowPanel70 : public lgfx::LGFX_Device { + lgfx::Bus_RGB _bus_instance; + lgfx::Panel_RGB _panel_instance; + lgfx::Touch_GT911 _touch_instance; + +#ifndef CROWPANEL_V13 + TCA9534 _ioex; +#endif + +public: + static constexpr uint16_t SCREEN_WIDTH = 800; + static constexpr uint16_t SCREEN_HEIGHT = 480; + + // --- STC8H1K28 helper (v1.3) --- + static void sendSTC8Command(uint8_t cmd) { + Wire.beginTransmission(STC8H_I2C_ADDR); + Wire.write(cmd); + Wire.endTransmission(); + } + + // Set backlight brightness: 0 = max, 244 = min, 245 = off + static void setBacklight(uint8_t brightness) { +#ifdef CROWPANEL_V13 + sendSTC8Command(brightness); +#endif + } + + static void buzzerOn() { sendSTC8Command(246); } + static void buzzerOff() { sendSTC8Command(247); } + static void speakerOn() { sendSTC8Command(248); } + static void speakerOff() { sendSTC8Command(249); } + + bool init_impl(bool use_reset, bool use_clear) override { + +#ifdef CROWPANEL_V13 + // ---- V1.3: All I2C + GPIO init done externally in target.cpp ---- + // Wire.begin(), STC8H backlight, and GPIO1 touch reset pulse are + // called BEFORE lcd.init() to avoid Wire double-init conflicts and + // ensure the display is powered before Panel_RGB allocates framebuffer. + +#else + // ---- V1.0: TCA9534 init ---- + _ioex.attach(Wire); + _ioex.setDeviceAddress(0x18); + + // Configure TCA9534 pins as outputs + _ioex.config(1, TCA9534::Config::OUT); // Display power + _ioex.config(2, TCA9534::Config::OUT); // Display reset + _ioex.config(3, TCA9534::Config::OUT); // Not used + _ioex.config(4, TCA9534::Config::OUT); // Backlight + + // Power on display + _ioex.output(1, TCA9534::Level::H); + + // Reset sequence + pinMode(1, OUTPUT); + digitalWrite(1, LOW); + _ioex.output(2, TCA9534::Level::L); + delay(20); + _ioex.output(2, TCA9534::Level::H); + delay(100); + pinMode(1, INPUT); + + // Turn on backlight + _ioex.output(4, TCA9534::Level::H); +#endif + + return LGFX_Device::init_impl(use_reset, use_clear); + } + + LGFX_CrowPanel70(void) { + // Panel configuration + { + auto cfg = _panel_instance.config(); + cfg.memory_width = SCREEN_WIDTH; + cfg.memory_height = SCREEN_HEIGHT; + cfg.panel_width = SCREEN_WIDTH; + cfg.panel_height = SCREEN_HEIGHT; + cfg.offset_x = 0; + cfg.offset_y = 0; + cfg.offset_rotation = 0; // Panel_RGB: rotation not supported via offset_rotation + // Display renders in portrait (480x800 physical). Landscape rotation will + // be handled by swapping the CrowPanel70Display coordinate mapping. + _panel_instance.config(cfg); + } + + // Panel detail configuration + { + auto cfg = _panel_instance.config_detail(); + cfg.use_psram = 1; // Use PSRAM for frame buffer + _panel_instance.config_detail(cfg); + } + + // RGB bus configuration + // Pin mapping is identical across all CrowPanel 7" hardware versions + { + auto cfg = _bus_instance.config(); + cfg.panel = &_panel_instance; + + // Blue (B3-B7 on panel, 5 bits) + cfg.pin_d0 = 21; // B3 + cfg.pin_d1 = 47; // B4 + cfg.pin_d2 = 48; // B5 + cfg.pin_d3 = 45; // B6 + cfg.pin_d4 = 38; // B7 + + // Green (G2-G7 on panel, 6 bits) + cfg.pin_d5 = 9; // G2 + cfg.pin_d6 = 10; // G3 + cfg.pin_d7 = 11; // G4 + cfg.pin_d8 = 12; // G5 + cfg.pin_d9 = 13; // G6 + cfg.pin_d10 = 14; // G7 + + // Red (R3-R7 on panel, 5 bits) + cfg.pin_d11 = 7; // R3 + cfg.pin_d12 = 17; // R4 + cfg.pin_d13 = 18; // R5 + cfg.pin_d14 = 3; // R6 + cfg.pin_d15 = 46; // R7 + + // Control pins + cfg.pin_henable = 42; // DE (Data Enable) + cfg.pin_vsync = 41; // VSYNC + cfg.pin_hsync = 40; // HSYNC + cfg.pin_pclk = 39; // Pixel clock (DCLK) + + // Timing configuration (14MHz pixel clock for 7" display) + cfg.freq_write = 14000000; + + // Horizontal timing + cfg.hsync_polarity = 0; + cfg.hsync_front_porch = 8; + cfg.hsync_pulse_width = 4; + cfg.hsync_back_porch = 8; + + // Vertical timing + cfg.vsync_polarity = 0; + cfg.vsync_front_porch = 8; + cfg.vsync_pulse_width = 4; + cfg.vsync_back_porch = 8; + + // Clock configuration + cfg.pclk_idle_high = 1; + cfg.pclk_active_neg = 0; + + _bus_instance.config(cfg); + } + _panel_instance.setBus(&_bus_instance); + + // Touch configuration (GT911 at 0x5D) + { + auto cfg = _touch_instance.config(); + cfg.x_min = 0; + cfg.x_max = SCREEN_WIDTH - 1; + cfg.y_min = 0; + cfg.y_max = SCREEN_HEIGHT - 1; + cfg.pin_int = -1; // IO1 is TP_INT but we poll, not interrupt-driven + cfg.pin_rst = -1; // Reset via STC8H1K28 (v1.3) or TCA9534 (v1.0) + cfg.bus_shared = true; + cfg.offset_rotation = 0; // Match panel config + cfg.i2c_port = 0; + cfg.i2c_addr = 0x5D; + cfg.pin_sda = 15; + cfg.pin_scl = 16; + cfg.freq = 400000; + _touch_instance.config(cfg); + _panel_instance.setTouch(&_touch_instance); + } + + setPanel(&_panel_instance); + } +}; \ No newline at end of file diff --git a/variants/crowpanel_70/cpupowermanager.h b/variants/crowpanel_70/cpupowermanager.h new file mode 100644 index 0000000..d1f33a5 --- /dev/null +++ b/variants/crowpanel_70/cpupowermanager.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +// CPU Frequency Scaling for ESP32-S3 +// +// Typical current draw (CPU only, rough): +// 240 MHz ~70-80 mA +// 160 MHz ~50-60 mA +// 80 MHz ~30-40 mA +// +// SPI peripherals and UART use their own clock dividers from the APB clock, +// so LoRa, e-ink, and GPS serial all work fine at 80MHz. + +#ifdef ESP32 + +#ifndef CPU_FREQ_IDLE +#define CPU_FREQ_IDLE 80 // MHz — normal mesh listening +#endif + +#ifndef CPU_FREQ_BOOST +#define CPU_FREQ_BOOST 240 // MHz — heavy processing +#endif + +#ifndef CPU_BOOST_TIMEOUT_MS +#define CPU_BOOST_TIMEOUT_MS 10000 // 10 seconds +#endif + +class CPUPowerManager { +public: + CPUPowerManager() : _boosted(false), _boost_started(0) {} + + void begin() { + setCpuFrequencyMhz(CPU_FREQ_IDLE); + _boosted = false; + MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE); + } + + void loop() { + if (_boosted && (millis() - _boost_started >= CPU_BOOST_TIMEOUT_MS)) { + setIdle(); + } + } + + void setBoost() { + if (!_boosted) { + setCpuFrequencyMhz(CPU_FREQ_BOOST); + _boosted = true; + MESH_DEBUG_PRINTLN("CPU power: boosted to %d MHz", CPU_FREQ_BOOST); + } + _boost_started = millis(); + } + + void setIdle() { + if (_boosted) { + setCpuFrequencyMhz(CPU_FREQ_IDLE); + _boosted = false; + MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE); + } + } + + bool isBoosted() const { return _boosted; } + uint32_t getFrequencyMHz() const { return getCpuFrequencyMhz(); } + +private: + bool _boosted; + unsigned long _boost_started; +}; + +#endif // ESP32 \ No newline at end of file diff --git a/variants/crowpanel_70/pins_arduino.h b/variants/crowpanel_70/pins_arduino.h new file mode 100644 index 0000000..5e4baa7 --- /dev/null +++ b/variants/crowpanel_70/pins_arduino.h @@ -0,0 +1,44 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +// USB Serial (native USB CDC on ESP32-S3) +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +// I2C (shared between touch GT911, RTC PCF85063 at 0x51, STC8H1K28 at 0x30) +static const uint8_t SDA = 15; +static const uint8_t SCL = 16; + +// Default SPI — mapped to wireless module slot (active when function switch = WM) +// V1.3 LoRa pin mapping (confirmed from board silkscreen): +// NSS=19, DIO1=20, RESET=8, BUSY=2, SPI on 4/5/6 +// V1.0 LoRa pin mapping (original): +// NSS=0, DIO1=20, RESET=19, BUSY=2, SPI on 4/5/6 +#ifdef CROWPANEL_V13 + static const uint8_t SS = 20; // LoRa NSS (V1.3: GPIO20, confirmed by SPI probe) +#else + static const uint8_t SS = 0; // LoRa NSS (V1.0: on boot strapping pin) +#endif +static const uint8_t MOSI = 6; // LoRa MOSI / SD MOSI / I2S LRCLK (shared) +static const uint8_t MISO = 4; // LoRa MISO / SD MISO / I2S SDIN (shared) +static const uint8_t SCK = 5; // LoRa SCK / SD CLK / I2S BCLK (shared) + +// Analog pins +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; + +// Touch pins +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/crowpanel_70/platformio.ini b/variants/crowpanel_70/platformio.ini new file mode 100644 index 0000000..84988df --- /dev/null +++ b/variants/crowpanel_70/platformio.ini @@ -0,0 +1,143 @@ +; ============================================================================= +; CrowPanel 7.0" Advance Series — shared base configuration +; ============================================================================= +; Display: 800x480 IPS, ST7277 driver, RGB parallel 16-bit +; Touch: GT911 at I2C 0x5D +; MCU: ESP32-S3-WROOM-1-N16R8 (16MB flash, 8MB OPSRAM) +; +; IMPORTANT: The PCB function select DIP switch (rear of board) MUST be set +; to WM mode (S0=1, S1=0) for LoRa wireless module operation. +; ============================================================================= + +[crowpanel_70_base] +extends = esp32_base +board = crowpanel +build_flags = + ${esp32_base.build_flags} + -I variants/crowpanel_70 + -D CROWPANEL_70 + ; Route Arduino Serial to UART0 (CH340 USB bridge) instead of native USB CDC. + ; The crowpanel.json board def sets CDC_ON_BOOT=1 but the user monitors via + ; the CH340 port (/dev/cu.wchusbserial*), not the native USB-C port. + -D ARDUINO_USB_CDC_ON_BOOT=0 + ; I2C pins (shared: touch, RTC, I/O controller) + -D PIN_BOARD_SDA=15 + -D PIN_BOARD_SCL=16 + ; Radio configuration (common across versions) + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 + ; SPI bus pins (same all versions — active when function switch = WM) + -D P_LORA_MOSI=6 + -D P_LORA_MISO=4 + -D P_LORA_SCLK=5 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/crowpanel_70> + + +lib_deps = + ${esp32_base.lib_deps} + ; CRITICAL: Use LovyanGFX 1.2.0 — v1.2.7 breaks 7" RGB panel init! + lovyan03/LovyanGFX@1.2.0 + ; PNGdec for map tile rendering + bitbank2/PNGdec@^1.1.1 + ; NOTE: GxEPD2 + Adafruit GFX NOT needed — CrowPanel variant shadows + ; GxEPDDisplay.h with a stub to avoid LovyanGFX/Adafruit_GFX type collision + +; ============================================================================= +; V1.3 hardware (current production — STC8H1K28 at 0x30) +; ============================================================================= +; LoRa pins confirmed from V1.3 board silkscreen (wireless module connector): +; IO19=NSS, IO20=DIO1, IO8=RESET, IO2=BUSY +; IO8 was freed when buzzer moved from direct GPIO to STC8H1K28 +; ============================================================================= + +[crowpanel_70_v13] +extends = crowpanel_70_base +build_flags = + ${crowpanel_70_base.build_flags} + -D CROWPANEL_V13 + ; V1.3 LoRa pin mapping (confirmed by SPI probe: NSS=GPIO20, DIO1=GPIO19) + -D P_LORA_NSS=20 + -D P_LORA_DIO_1=19 + -D P_LORA_RESET=8 + -D P_LORA_BUSY=2 +lib_deps = + ${crowpanel_70_base.lib_deps} + ; No TCA9534 needed — V1.3 uses STC8H1K28 controlled via raw I2C writes + +[env:crowpanel_70_v13_companion_radio_ble] +extends = crowpanel_70_v13 +build_flags = + ${crowpanel_70_v13.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 DISPLAY_CLASS=CrowPanel70Display + ; Scale factors: 800x480 physical -> 128x64 logical + ; 800/128 = 6.25, 480/64 = 7.5 + -D DISPLAY_SCALE_X=6.25 + -D DISPLAY_SCALE_Y=7.5 + -D AUTO_OFF_MILLIS=30000 + -D HAS_TOUCH_SCREEN=1 + -D NO_BATTERY_INDICATOR=1 + -D MESH_DEBUG=1 +build_src_filter = ${crowpanel_70_v13.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${crowpanel_70_v13.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; ============================================================================= +; V1.0 hardware (original — TCA9534/PCA9557 at 0x18) +; ============================================================================= +; LoRa pins for V1.0: +; IO0=NSS (boot pin!), IO20=DIO1, IO19=RESET, IO2=BUSY +; ============================================================================= + +[crowpanel_70_v10] +extends = crowpanel_70_base +build_flags = + ${crowpanel_70_base.build_flags} + ; V1.0 LoRa pin mapping + -D P_LORA_NSS=0 + -D P_LORA_DIO_1=20 + -D P_LORA_RESET=19 + -D P_LORA_BUSY=2 +lib_deps = + ${crowpanel_70_base.lib_deps} + ; TCA9534 I/O expander for display power control (V1.0 only) + hideakitai/TCA9534@0.1.1 + +[env:crowpanel_70_v10_companion_radio_ble] +extends = crowpanel_70_v10 +build_flags = + ${crowpanel_70_v10.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 DISPLAY_CLASS=CrowPanel70Display + -D DISPLAY_SCALE_X=6.25 + -D DISPLAY_SCALE_Y=7.5 + -D AUTO_OFF_MILLIS=30000 + -D HAS_TOUCH_SCREEN=1 + -D NO_BATTERY_INDICATOR=1 + -D MESH_DEBUG=1 +build_src_filter = ${crowpanel_70_v10.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${crowpanel_70_v10.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/crowpanel_70/target.cpp b/variants/crowpanel_70/target.cpp new file mode 100644 index 0000000..3cd80d7 --- /dev/null +++ b/variants/crowpanel_70/target.cpp @@ -0,0 +1,227 @@ +#include +#include "target.h" + +CrowPanel70Board board; + +// SPI bus for LoRa — use HSPI (SPI3_HOST). Pass -1 for SS so RadioLib +// can manually toggle NSS via digitalWrite (hardware CS conflicts with this). +static SPIClass spi(HSPI); +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); + +EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + LGFX_CrowPanel70 lcd; + CrowPanel70Display display(800, 480, lcd); + #ifndef HAS_TOUCH_SCREEN + TouchButton user_btn(&display); + #endif +#endif + +bool radio_init() { + delay(1000); + +#ifdef CROWPANEL_V13 + Serial.println("\n\n=== CrowPanel 7.0 V1.3 MeshCore ==="); +#else + Serial.println("\n\n=== CrowPanel 7.0 MeshCore ==="); +#endif + Serial.println("Initializing..."); + Serial.printf(" LoRa pins: NSS=%d DIO1=%d RST=%d BUSY=%d\n", + P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); + Serial.printf(" SPI pins: MOSI=%d MISO=%d SCK=%d\n", + P_LORA_MOSI, P_LORA_MISO, P_LORA_SCLK); + +#ifdef DISPLAY_CLASS +#ifdef CROWPANEL_V13 + Serial.println("Step 1: STC8H1K28 backlight ON..."); + Wire.beginTransmission(0x30); + Wire.write(0); + Wire.endTransmission(); +#endif + + Serial.println("Step 2: Touch reset pulse..."); + pinMode(1, OUTPUT); + digitalWrite(1, LOW); + delay(120); + pinMode(1, INPUT); + + // NO ST7277 SPI init — MADCTL breaks RGB mode on this panel. + // Rotation handled by LovyanGFX offset_rotation in constructor. + + Serial.println("Step 3: lcd.init()..."); + lcd.init(); + Serial.printf(" LGFX: %dx%d\n", lcd.width(), lcd.height()); + + Serial.println("Step 4: display.begin()..."); + display.begin(); + #ifndef HAS_TOUCH_SCREEN + user_btn.begin(); + #endif + Serial.println("Display ready"); + + // Orientation test + lcd.fillScreen(0x0000); + int w = lcd.width(); + int h = lcd.height(); + lcd.fillRect(0, 0, 60, 40, 0xF800); + lcd.fillRect(w-60, 0, 60, 40, 0x07E0); + lcd.fillRect(0, h-40, 60, 40, 0x001F); + lcd.fillRect(w-60, h-40, 60, 40, 0xFFE0); + lcd.setTextColor(0xFFFF); + lcd.setTextSize(2); + lcd.setCursor(80, 10); + lcd.printf("LGFX: %dx%d", w, h); + lcd.setCursor(80, 40); + lcd.print("CrowPanel 7.0 V1.3 + Meck"); + lcd.setCursor(80, 70); + lcd.print("RED=TL GRN=TR BLU=BL YEL=BR"); + lcd.setCursor(80, 100); + lcd.print("Testing LoRa..."); +#endif + + Serial.println("Initializing RTC..."); + fallback_clock.begin(); + rtc_clock.begin(Wire); + + // --- LoRa SPI diagnostic: bitbang to bypass peripheral --- + Serial.println("Initializing LoRa radio..."); + Serial.println(" Hardware SPI returned all zeros — trying bitbang to test physical wiring\n"); + + // Reset radio + pinMode(P_LORA_RESET, OUTPUT); + digitalWrite(P_LORA_RESET, LOW); + delay(20); + digitalWrite(P_LORA_RESET, HIGH); + delay(100); + pinMode(P_LORA_BUSY, INPUT); + unsigned long bs = millis(); + while (digitalRead(P_LORA_BUSY) && (millis() - bs < 1000)) delay(1); + Serial.printf(" BUSY after reset: %s (%ldms)\n", + digitalRead(P_LORA_BUSY) ? "HIGH" : "LOW", millis() - bs); + + // Configure pins as GPIO (not SPI peripheral) + pinMode(P_LORA_SCLK, OUTPUT); // GPIO5 = SCK + pinMode(P_LORA_MOSI, OUTPUT); // GPIO6 = MOSI + pinMode(P_LORA_MISO, INPUT); // GPIO4 = MISO + pinMode(P_LORA_NSS, OUTPUT); // GPIO20 = NSS + digitalWrite(P_LORA_SCLK, LOW); + digitalWrite(P_LORA_NSS, HIGH); + + // Bitbang SPI Mode 0: CPOL=0, CPHA=0 + // Clock idle LOW, data sampled on rising edge + auto bbTransfer = [](uint8_t tx) -> uint8_t { + uint8_t rx = 0; + for (int i = 7; i >= 0; i--) { + // Set MOSI + digitalWrite(P_LORA_MOSI, (tx >> i) & 1); + delayMicroseconds(2); + // Rising edge — slave clocks in MOSI, we read MISO + digitalWrite(P_LORA_SCLK, HIGH); + delayMicroseconds(2); + if (digitalRead(P_LORA_MISO)) rx |= (1 << i); + // Falling edge + digitalWrite(P_LORA_SCLK, LOW); + delayMicroseconds(2); + } + return rx; + }; + + // Read register 0x0320 via bitbang + Serial.println(" Bitbang SPI read of reg 0x0320:"); + digitalWrite(P_LORA_NSS, LOW); + delayMicroseconds(50); + uint8_t b0 = bbTransfer(0x1D); // ReadRegister + uint8_t b1 = bbTransfer(0x03); // Addr high + uint8_t b2 = bbTransfer(0x20); // Addr low + uint8_t b3 = bbTransfer(0x00); // NOP (status) + uint8_t b4 = bbTransfer(0x00); // Register value + digitalWrite(P_LORA_NSS, HIGH); + + Serial.printf(" SCK=%d MOSI=%d MISO=%d NSS=%d\n", + P_LORA_SCLK, P_LORA_MOSI, P_LORA_MISO, P_LORA_NSS); + Serial.printf(" bytes: %02X %02X %02X %02X %02X\n", b0, b1, b2, b3, b4); + Serial.printf(" val=0x%02X %s\n", b4, + b4 == 0x58 ? "<<< SX1262 FOUND via bitbang!" : + (b4 == 0xFF ? "(no response)" : + (b4 == 0x00 ? "(zeros — physical wiring issue)" : ""))); + + // Also try with MISO and MOSI swapped + Serial.println("\n Bitbang with MISO=6, MOSI=4 (swapped):"); + pinMode(6, INPUT); // Now MISO + pinMode(4, OUTPUT); // Now MOSI + digitalWrite(4, LOW); + + // Reset again + digitalWrite(P_LORA_RESET, LOW); + delay(20); + digitalWrite(P_LORA_RESET, HIGH); + delay(100); + bs = millis(); + while (digitalRead(P_LORA_BUSY) && (millis() - bs < 500)) delay(1); + + auto bbTransfer2 = [](uint8_t tx) -> uint8_t { + uint8_t rx = 0; + for (int i = 7; i >= 0; i--) { + digitalWrite(4, (tx >> i) & 1); // MOSI on GPIO4 + delayMicroseconds(2); + digitalWrite(5, HIGH); // SCK + delayMicroseconds(2); + if (digitalRead(6)) rx |= (1 << i); // MISO on GPIO6 + digitalWrite(5, LOW); + delayMicroseconds(2); + } + return rx; + }; + + digitalWrite(P_LORA_NSS, LOW); + delayMicroseconds(50); + b0 = bbTransfer2(0x1D); + b1 = bbTransfer2(0x03); + b2 = bbTransfer2(0x20); + b3 = bbTransfer2(0x00); + b4 = bbTransfer2(0x00); + digitalWrite(P_LORA_NSS, HIGH); + + Serial.printf(" SCK=5 MOSI=4 MISO=6 NSS=%d\n", P_LORA_NSS); + Serial.printf(" bytes: %02X %02X %02X %02X %02X\n", b0, b1, b2, b3, b4); + Serial.printf(" val=0x%02X %s\n", b4, + b4 == 0x58 ? "<<< SX1262 FOUND (MISO/MOSI swapped)!" : + (b4 == 0xFF ? "(no response)" : + (b4 == 0x00 ? "(zeros)" : ""))); + + // Restore pins for hardware SPI attempt + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, -1); + bool result = radio.std_init(&spi); + if (result) { + Serial.println("LoRa: OK!"); +#ifdef DISPLAY_CLASS + lcd.setTextColor(0x07E0); + lcd.setCursor(80, 130); + lcd.print("LoRa: OK!"); +#endif + } else { + Serial.println("LoRa: FAILED"); +#ifdef DISPLAY_CLASS + lcd.setTextColor(0xF800); + lcd.setCursor(80, 130); + lcd.print("LoRa: FAILED"); +#endif + } + return true; +} + +uint32_t radio_get_rng_seed() { return radio.random(0x7FFFFFFF); } +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); radio.setCodingRate(cr); +} +void radio_set_tx_power(uint8_t dbm) { radio.setOutputPower(dbm); } +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); +} \ No newline at end of file diff --git a/variants/crowpanel_70/target.h b/variants/crowpanel_70/target.h new file mode 100644 index 0000000..5b3521e --- /dev/null +++ b/variants/crowpanel_70/target.h @@ -0,0 +1,39 @@ +#pragma once + +#include "variant.h" // Board-specific defines (HAS_GPS, GPS_BAUDRATE, etc.) + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + #include + #ifndef HAS_TOUCH_SCREEN + #include + #endif +#endif + +extern CrowPanel70Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern LGFX_CrowPanel70 lcd; + extern CrowPanel70Display display; + #ifndef HAS_TOUCH_SCREEN + extern TouchButton user_btn; + #endif +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/crowpanel_70/variant.h b/variants/crowpanel_70/variant.h new file mode 100644 index 0000000..257a224 --- /dev/null +++ b/variants/crowpanel_70/variant.h @@ -0,0 +1,150 @@ +#pragma once + +// ============================================================================= +// CrowPanel 7.0" Advance Series (V1.0 / V1.3) +// ESP32-S3-WROOM-1-N16R8 (16MB Flash, 8MB OPI-PSRAM) +// 800x480 IPS display, ST7277 driver, RGB parallel 16-bit +// GT911 capacitive touch at I2C 0x5D +// ============================================================================= + +// ----------------------------------------------------------------------------- +// I2C Bus (shared: touch GT911, RTC PCF85063 at 0x51, STC8H1K28 at 0x30) +// ----------------------------------------------------------------------------- +#define I2C_SDA 15 +#define I2C_SCL 16 +#define PIN_BOARD_SDA I2C_SDA +#define PIN_BOARD_SCL I2C_SCL + +// ----------------------------------------------------------------------------- +// Display +// ----------------------------------------------------------------------------- +#define LCD_HOR_SIZE 800 +#define LCD_VER_SIZE 480 + +// ----------------------------------------------------------------------------- +// LoRa Radio (SX1262) — via wireless module connector +// Pin mapping depends on hardware version (defined in platformio.ini): +// V1.3: NSS=19, DIO1=20, RESET=8, BUSY=2, SPI 4/5/6 +// V1.0: NSS=0, DIO1=20, RESET=19, BUSY=2, SPI 4/5/6 +// +// IMPORTANT: Function select DIP switch must be set to WM mode (S0=1, S1=0) +// for the SPI bus to be routed to the wireless module connector. +// ----------------------------------------------------------------------------- +#define USE_SX1262 +#define USE_SX1268 + +// P_LORA_* pins are defined in platformio.ini per hardware version +// RadioLib/MeshCore compat aliases (only define if not already set by platformio) +#ifndef P_LORA_NSS + #ifdef CROWPANEL_V13 + #define P_LORA_NSS 20 + #define P_LORA_DIO_1 19 + #define P_LORA_RESET 8 + #define P_LORA_BUSY 2 + #else + #define P_LORA_NSS 0 + #define P_LORA_DIO_1 20 + #define P_LORA_RESET 19 + #define P_LORA_BUSY 2 + #endif +#endif +#ifndef P_LORA_SCLK + #define P_LORA_SCLK 5 + #define P_LORA_MISO 4 + #define P_LORA_MOSI 6 +#endif + +// ----------------------------------------------------------------------------- +// GPS — not present on CrowPanel +// Define a fallback GPS_BAUDRATE so that serial CLI code compiles cleanly +// (the CLI GPS commands will be inert since HAS_GPS is not defined) +// ----------------------------------------------------------------------------- +// #define HAS_GPS — intentionally NOT defined +#ifndef GPS_BAUDRATE + #define GPS_BAUDRATE 9600 +#endif + +// ----------------------------------------------------------------------------- +// SD Card — shares SPI bus with LoRa and speaker via function switch +// When function switch is in WM mode, SD card is NOT accessible. +// SD support can be enabled for standalone (non-LoRa) use with S1=1,S0=1 +// ----------------------------------------------------------------------------- +// #define HAS_SDCARD — not defined by default (conflicts with LoRa SPI) + +// SDCARD_CS dummy: NotesScreen, TextReaderScreen, EpubZipReader reference this +// unconditionally. -1 makes digitalWrite() a no-op on ESP32. +#ifndef SDCARD_CS + #define SDCARD_CS -1 +#endif + +// E-ink display: not present. GxEPDDisplay.h is shadowed by a stub in the +// variant include path to avoid LovyanGFX/Adafruit_GFX type collision. + +// ----------------------------------------------------------------------------- +// Battery — CrowPanel has a battery connector but no fuel gauge IC +// Battery charging is handled by onboard circuit with CHG LED indicator +// ----------------------------------------------------------------------------- +// #define HAS_BQ27220 — not present +#define NO_BATTERY_INDICATOR 1 + +// ----------------------------------------------------------------------------- +// Audio — I2S speaker (shared pins, only when function switch = MIC&SPK) +// Not usable simultaneously with LoRa — commented out for reference +// ----------------------------------------------------------------------------- +// #define BOARD_I2S_SDIN 4 // IO4 (shared with SPI MISO) +// #define BOARD_I2S_BCLK 5 // IO5 (shared with SPI SCK) +// #define BOARD_I2S_LRCLK 6 // IO6 (shared with SPI MOSI) + +// MIC (v1.3: LMD3526B261 PDM mic) +// #define BOARD_MIC_DATA 20 // IO20 (shared with LoRa DIO1) +// #define BOARD_MIC_CLOCK 19 // IO19 (shared with LoRa NSS) + +// ----------------------------------------------------------------------------- +// Touch Screen +// ----------------------------------------------------------------------------- +#define HAS_TOUCHSCREEN 1 + +// Touch controller: GT911 at 0x5D +// INT = IO1 (active-low pulse, used for GT911 address selection at boot) +// RST = STC8H1K28 P1.7 (v1.3) or TCA9534 pin 2 (v1.0) + +// ----------------------------------------------------------------------------- +// Keyboard — not present (touchscreen-only device) +// ----------------------------------------------------------------------------- +// #define HAS_PHYSICAL_KEYBOARD — not present + +// ----------------------------------------------------------------------------- +// Buttons — CrowPanel is touchscreen-only +// ----------------------------------------------------------------------------- +// BOOT button (GPIO0) is for programming only, not a user button. +// Do NOT define PIN_USER_BTN — it would enable TouchButton code in UITask.cpp +// but the TouchButton object is not instantiated (HAS_TOUCH_SCREEN suppresses it). +// #define BUTTON_PIN 0 +// #define PIN_USER_BTN 0 + +// ----------------------------------------------------------------------------- +// UART +// ----------------------------------------------------------------------------- +// UART0: TX=IO43, RX=IO44 (also USB CDC) +// UART1: TX=IO20, RX=IO19 (shared with LoRa DIO1/NSS when in WM mode!) + +// ----------------------------------------------------------------------------- +// RTC — PCF85063 at I2C 0x51 (confirmed from board silkscreen) +// ----------------------------------------------------------------------------- +// AutoDiscoverRTCClock will find it automatically on the I2C bus + +// ----------------------------------------------------------------------------- +// STC8H1K28 I/O Controller (V1.2/V1.3 only) +// I2C slave at address 0x30 +// Command bytes: +// 0 = backlight max brightness +// 1-244 = backlight dimmer +// 245 = backlight off +// 246 = buzzer on +// 247 = buzzer off +// 248 = speaker amp on +// 249 = speaker amp off +// ----------------------------------------------------------------------------- +#ifdef CROWPANEL_V13 + #define STC8H_I2C_ADDR 0x30 +#endif \ No newline at end of file