mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-10-15 17:00:26 +00:00
New board: ThinkNode_M9_companion_radio_touch env, variants/thinknode_m9/ (board glue, LR1110 target, keyboard driver, partitions, M9_PORT.md pin reference), boards/thinknode_m9.json, device_caps HAS_THINKNODE_M9 block, HAS_M9_KEYBOARD input path in the touch UI (d-pad focus nav, tab jump keys, QWERTY typing). Based on a contributed bring-up patch by ded (SalishMesh), who also remote- tested every build on a preproduction unit. Hard-won fixes on top of the patch, each verified on hardware: - LR1110 radio init -706: preprod chips run transceiver FW 0x0303, which rejects DriveDiosInSleepMode (0x012A, added in FW 0x0308) that RadioLib 7.x sends unconditionally during begin(). New build-time patch scripts/build/patch_radiolib_lr11x0.py tolerates it on old FW; radio_init() prints the chip's device/FW/errors either way. - TCXO: DIO3 at 3.3 V (the patch's active-oscillator/tcxo=0 theory gave -707 on hardware). - Display: rotation 1 (not 3), plus the missing landscape s_ui_rotation override so LVGL renders 320x240 instead of portrait-into-landscape. - Keyboard: the controller is a separate ESP32-S2 I2C slave (0x6C) with addressed registers - reads select register 0x01 first (bare reads return the HW-version register forever). Boot probe logs the controller's HW/FW; backlight duty register wired. - USB pad release: the keyboard bus lives on GPIO20/21 = the S3's native USB D-/D+ pads, owned from reset by the ROM USB-Serial-JTAG (D+ pullup on SDA). M9Board::begin() releases them before any bus init. - GPIO35-37 are octal-PSRAM pins on the S3R8 - never drive them (the patch's "SD CS = 36" misread package pin 36 = GPIO48; SD is on the shared SPI bus, support returns with PIN_SD_CS=48 later). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
111 lines
4.9 KiB
C++
111 lines
4.9 KiB
C++
#include <Arduino.h>
|
|
#include <SPI.h>
|
|
#include "M9Board.h"
|
|
#include "soc/usb_serial_jtag_reg.h"
|
|
|
|
void ThinkNodeM9Board::begin() {
|
|
// Release the S3's native USB pads. GPIO19/20 are USB D-/D+ and the ROM's
|
|
// USB-Serial-JTAG peripheral owns those pads from reset, including a D+
|
|
// pull-up ON GPIO20. The M9's console goes through an external UART bridge
|
|
// (U3) instead, and the schematic reuses GPIO20/21 as the KEYBOARD I2C bus
|
|
// (nets ESP32-2_SDA/SCL) and GPIO19 as LCD_TE — so without this the
|
|
// keyboard SDA line is clamped by the USB PHY and the controller never
|
|
// answers (bring-up #8: probe found nothing on a correctly-wired bus).
|
|
REG_CLR_BIT(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE);
|
|
|
|
ESP32Board::begin(); // attaches PIN_VBAT_READ, brings up Wire on PIN_BOARD_SDA/SCL (7/6)
|
|
|
|
// The one place this board's init genuinely has to differ from T-Deck/
|
|
// Heltec V4's pattern: their displays use ST7789LCDDisplay's dedicated-
|
|
// instance branch (LILYGO_TDECK / HELTEC_LORA_V4_TFT), which begins its
|
|
// own SPI bus internally — so neither board's board.begin() touches SPI
|
|
// at all, and the radio's bus only gets begun later, inside radio_init().
|
|
// M9's display is on the class's "default" branch (display(&SPI, ...)) —
|
|
// the only safe branch available without borrowing another board's
|
|
// identity macro (HELTEC_LORA_V4_TFT is also checked in shared UITask.cpp
|
|
// against HeltecV4Board-only methods, which would be a compile error here)
|
|
// — and that branch does NOT begin the bus itself, it assumes the caller
|
|
// already has. So a single bare SPI.begin() has to happen here, before
|
|
// display.begin() runs (next, in main.cpp). Everything else in this
|
|
// function mirrors TDeckBoard::begin()/HeltecV4Board::begin() as closely
|
|
// as M9's actual hardware allows — no CS-deselect dance, no NSS pre-drive:
|
|
// neither working board does either, and they don't need it.
|
|
SPI.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, PIN_TFT_CS);
|
|
|
|
// Shared-SPI bus discipline: park the bus devices' chip-selects HIGH before
|
|
// any traffic flows (LR1110 NSS=39, TFT CS=16). NOTE: do NOT touch GPIO35-37
|
|
// on this board — the S3R8's octal PSRAM owns them (driving GPIO36 as a GPIO
|
|
// wedged the PSRAM bus and hung boot right after prefs init, bring-up #3).
|
|
// SCHEMATIC TRUTH (read from the V1.0 sheet, bring-up #9): the microSD *is*
|
|
// on this shared SPI bus with CS = GPIO48 — the patch's "SD CS = 36" misread
|
|
// the S3's PACKAGE pin 36 (SPICLK_N = GPIO48) as GPIO36. SD support returns
|
|
// with PIN_SD_CS=48 once the keyboard/radio bring-up settles (M9_PORT.md).
|
|
pinMode(PIN_TFT_CS, OUTPUT);
|
|
digitalWrite(PIN_TFT_CS, HIGH);
|
|
pinMode(P_LORA_NSS, OUTPUT);
|
|
digitalWrite(P_LORA_NSS, HIGH);
|
|
|
|
// Bring up both rails at boot and leave them claimed for now. Display
|
|
// power-down (turnOff()) releases periph_power via the pointer passed into
|
|
// ST7789LCDDisplay's constructor in target.cpp; the backlight is ours to
|
|
// manage explicitly until UI-side sleep/backlight-timeout wiring exists for
|
|
// this board (TODO — see M9_PORT.md).
|
|
periph_power.begin();
|
|
backlight.begin();
|
|
periph_power.claim();
|
|
backlight.claim();
|
|
|
|
pinMode(PIN_USER_BTN, INPUT_PULLUP); // BOOT button, GPIO0, active LOW
|
|
|
|
esp_reset_reason_t reason = esp_reset_reason();
|
|
if (reason == ESP_RST_DEEPSLEEP) {
|
|
uint64_t wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
|
if (wakeup_source & (1ULL << P_LORA_DIO_1)) {
|
|
startup_reason = BD_STARTUP_RX_PACKET; // woke from an incoming LoRa packet
|
|
}
|
|
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
|
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
|
}
|
|
}
|
|
|
|
void ThinkNodeM9Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
|
|
|
// Hold DIO1 / NSS at their required levels through sleep (LR1110 IRQ wake).
|
|
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
|
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
|
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
|
|
|
if (pin_wake_btn < 0) {
|
|
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);
|
|
} else {
|
|
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1) | (1ULL << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
|
|
}
|
|
|
|
if (secs > 0) {
|
|
esp_sleep_enable_timer_wakeup(secs * 1000000ULL);
|
|
}
|
|
|
|
esp_deep_sleep_start(); // CPU halts here and never returns
|
|
}
|
|
|
|
void ThinkNodeM9Board::powerOff() {
|
|
enterDeepSleep(0);
|
|
}
|
|
|
|
uint16_t ThinkNodeM9Board::getBattMilliVolts() {
|
|
#if defined(PIN_VBAT_READ)
|
|
// 1:1 divider (manufacturer-confirmed) — unlike the ESP32Board base class's
|
|
// hardcoded `2 * raw`, the pin voltage IS the battery voltage here.
|
|
analogReadResolution(12);
|
|
uint32_t sum = 0;
|
|
const int kSamples = 8;
|
|
for (int i = 0; i < kSamples; i++) {
|
|
sum += analogReadMilliVolts(PIN_VBAT_READ);
|
|
}
|
|
return (uint16_t)(sum / kSamples);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|