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>