thinknode m9 stuff + temp remove from build.sh

This commit is contained in:
liquidraver
2026-07-23 08:40:31 +02:00
parent 8ddea4603c
commit 63155b4d44
7 changed files with 95 additions and 38 deletions
-1
View File
@@ -45,7 +45,6 @@ ESP32_boards=(
heltec_wifi_lora32_v43/esp32s3/procpu
heltec_wireless_tracker/esp32s3/procpu
heltec_wireless_tracker_v2/esp32s3/procpu
thinknode_m9/esp32s3/procpu
ttgo_tbeam/esp32/procpu
)
+4 -1
View File
@@ -163,7 +163,10 @@ BOARDS = [
dict(stem="heltec_wifi_lora32_v4-esp32s3-procpu", kind="esp32", device="Heltec v4"),
dict(stem="heltec_wireless_tracker-esp32s3-procpu", kind="esp32", device="Heltec Wireless Tracker"),
dict(stem="heltec_wireless_tracker_v2-esp32s3-procpu", kind="esp32", device="Heltec Wireless Tracker v2"),
dict(stem="thinknode_m9-esp32s3-procpu", kind="esp32", device="Elecrow ThinkNode M9"),
# ThinkNode M9 pulled from the build.sh release matrix 2026-07-22 (bring-up
# still in progress — keypad/GPS/battery unverified on hardware). Restore
# this line together with the build.sh entry once it's release-ready.
# dict(stem="thinknode_m9-esp32s3-procpu", kind="esp32", device="Elecrow ThinkNode M9"),
# Classic ESP32 T-Beam: ships -merged.bin (full-flash, 0x0) too.
dict(stem="ttgo_tbeam-esp32-procpu", kind="esp32", device="LilyGo T-Beam (SX1262)"),
@@ -240,7 +240,10 @@
/* LittleFS auto-mount — standard /lfs mount point */
#include "../../common/filesystem.dtsi"
/* USB OTG companion transport. thinknode_m9 consoles over USB Serial/JTAG,
* which shares D+/D- with USB OTG, so the console is rerouted to uart0. */
/* Console + shell on uart0 — the USB-C port is a CH9102-class UART bridge
* into UART0 (GPIO43/44), verified on hardware. No USB OTG transport on this
* board: the S3's native D+/D- pads (GPIO19/20) are not bonded to the
* connector, and GPIO20/21 carry the STC8H keypad I2C bus — enabling the OTG
* or USB Serial/JTAG PHY would claim the keypad's SDA pin. BLE is the
* companion transport. */
#include "../../common/esp32s3_console_uart0.dtsi"
#include "../../common/esp32s3_usb_otg.dtsi"
@@ -30,10 +30,15 @@
model = "Elecrow ThinkNode M9";
compatible = "elecrow,thinknode-m9";
/* No zephyr,console here: the M9's USB-C data lines ride a CH9102-class
* USB-UART bridge into UART0 (verified on hardware — the native USB
* Serial/JTAG console produced no output). The S3's native USB pads
* (GPIO19/20) are not wired to the connector at all; GPIO20/21 carry the
* STC8H keypad I2C bus instead. Every build of this board (app overlay
* and lr1110_updater overlay alike) routes console + shell to uart0 via
* boards/common/esp32s3_console_uart0.dtsi. */
chosen {
zephyr,sram = &sram1;
zephyr,console = &usb_serial;
zephyr,shell-uart = &usb_serial;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,bt-hci = &esp32_bt_hci;
@@ -69,9 +74,11 @@
clock-frequency = <DT_FREQ_M(240)>;
};
&usb_serial {
status = "okay";
};
/* USB Serial/JTAG stays disabled (SoC default): its PHY pads are GPIO19/20,
* and on this board GPIO20 is the keypad's I2C1 SDA. The USJ driver's init
* re-attaches the USB pad (usb_serial_jtag_ll_phy_enable_pad) and enables the
* D+ pull-up, fighting the keypad bus. Native USB is unbonded from the
* connector anyway — see the chosen-node comment. */
&i2c0 {
status = "okay";
@@ -8,13 +8,15 @@ description: |
over I2C. Used by the Elecrow ThinkNode M9, whose keypad is its only input
device — there is no user button on that board.
Protocol:
reg 0x01..0x04 battery millivolts, little-endian
reg 0x05 code of the key currently pressed (0 = none)
Protocol (as recovered from the stock keypad firmware — note the key
register is 0x01, not the 0x05 "matrix key" register that the reference
header documents; that define is dead code and 0x05 reads as 0xFF):
reg 0x01 code of the last key pressed (latched; 0x88 = invalid)
reg 0x01..0x04 battery millivolts, little-endian (same base register)
reg 0x06 state; writing 0x01 requests sleep
The MCU raises its interrupt line (idle-low, rising edge) when a key is
pressed. The driver reads register 0x05 on that edge and reports the result
pressed. The driver reads register 0x01 on that edge and reports the result
through Zephyr's input subsystem. If no interrupt GPIO is supplied the
driver falls back to polling.
+63 -21
View File
@@ -22,13 +22,12 @@
* helpers/ui-joystick/joystick_defs.h). Everything else is a named key and is
* translated here.
*
* The named codes below are the ones confirmed from two independent firmwares
* for this keypad. Arrow keys are NOT among them, and no reachable reference
* documents them — so any code this driver does not recognise is reported at
* INFO level with its hex value. Press the arrow keys once on real hardware
* and the log names them; add them to key_map[] and navigation is complete.
* That is deliberate: guessing arrow codes would produce a driver that looks
* finished and silently does the wrong thing.
* The named codes below (arrows included) come from the stock keypad
* firmware's protocol, as recovered from a linked reference-firmware ELF by a
* second independent firmware — the register bodies there are the shipped
* hardware truth, not header guesses. Any code this driver still does not
* recognise is reported at INFO level with its hex value so it can be added
* to key_map[].
*/
#define DT_DRV_COMPAT zephcore_stc8h_keypad
@@ -45,13 +44,23 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(stc8h_keypad, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
/* Register map — see dts/bindings/input/zephcore,stc8h-keypad.yaml */
/* Register map — see dts/bindings/input/zephcore,stc8h-keypad.yaml.
*
* The key register is 0x01, NOT the 0x05 "matrix key" register that the
* reference header documents: the recovered implementation bodies read the
* key from 0x01, and the 0x05 define is dead code nothing consumes. Reading
* 0x05 returns constant 0xFF — that was this driver's original bug (keys
* fully dead, phantom 0xFF at init). The overlap with the battery registers
* (0x01..0x04 little-endian) is odd but is what the shipped firmware does;
* it is also why key reads are IRQ-gated below. */
#define STC8H_REG_BATTERY 0x01 /* 0x01..0x04, little-endian millivolts */
#define STC8H_REG_KEY 0x05
#define STC8H_REG_KEY 0x01
#define STC8H_REG_STATE 0x06
#define STC8H_STATE_SLEEP 0x01
#define STC8H_KEY_NONE 0x00
#define STC8H_KEY_NONE 0x00
#define STC8H_KEY_INVALID 0x88 /* helper MCU's "invalid key" marker */
#define STC8H_KEY_ALL_ONES 0xFF /* error sentinel / floating-bus read */
struct stc8h_config {
struct i2c_dt_spec i2c;
@@ -63,22 +72,27 @@ struct stc8h_data {
const struct device *dev;
struct gpio_callback irq_cb;
struct k_work_delayable work;
atomic_t irq_pending;
uint8_t last_key;
};
/*
* Named keys, as reported by the helper MCU.
*
* Only codes corroborated by two independent firmwares for this keypad are
* listed. The two function keys are mapped to page navigation because on a
* board with no other input they are the only way to move between screens;
* the UI treats them as prev/next.
* Named keys, as reported by the helper MCU. Codes from the recovered stock
* keypad protocol (see file header). The two function keys are mapped to page
* navigation because on a board with no other input they are the only way to
* move between screens; the UI treats them as prev/next.
*/
static const struct {
uint8_t raw;
uint16_t code;
} key_map[] = {
{ 0x0D, INPUT_KEY_ENTER }, /* enter / confirm */
{ 0xB5, INPUT_KEY_UP }, /* arrow up */
{ 0xB6, INPUT_KEY_DOWN }, /* arrow down */
{ 0xB4, INPUT_KEY_LEFT }, /* arrow left */
{ 0xB7, INPUT_KEY_RIGHT }, /* arrow right */
{ 0x08, INPUT_KEY_BACK }, /* del -> back / cancel */
{ 0x89, INPUT_KEY_BACK }, /* del long-press -> back / cancel */
{ 0x86, INPUT_KEY_ESC }, /* back */
{ 0x82, INPUT_KEY_HOME }, /* home */
{ 0x83, INPUT_KEY_MENU }, /* context menu */
@@ -148,19 +162,44 @@ static void stc8h_report(const struct device *dev, uint8_t raw)
LOG_INF("unmapped keypad code 0x%02X — add it to key_map[] to bind it", raw);
}
static bool stc8h_key_valid(uint8_t raw)
{
return raw != STC8H_KEY_NONE && raw != STC8H_KEY_INVALID &&
raw != STC8H_KEY_ALL_ONES;
}
static void stc8h_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct stc8h_data *data = CONTAINER_OF(dwork, struct stc8h_data, work);
const struct device *dev = data->dev;
const struct stc8h_config *cfg = dev->config;
bool irq_event = atomic_cas(&data->irq_pending, 1, 0);
uint8_t key = STC8H_KEY_NONE;
if (stc8h_read_reg(cfg, STC8H_REG_KEY, &key) == 0) {
/* Report on the transition only — the register reads the key
* that is *currently down*, so a held key would otherwise
* repeat at the poll rate. */
if (key != STC8H_KEY_NONE && key != data->last_key) {
if (irq_event) {
/* KB_INT edge latched: this read is a fresh keypress —
* report unconditionally. The register latches the
* *last* key (it does not return to 0 on release), so
* comparing against last_key here would swallow every
* repeated press of the same key. This mirrors the
* reference driver, which only ever reads the key
* register in response to the interrupt. */
if (stc8h_key_valid(key)) {
stc8h_report(dev, key);
}
} else if (cfg->irq.port == NULL) {
/* No IRQ line: transition polling is all we have. */
if (stc8h_key_valid(key) && key != data->last_key) {
stc8h_report(dev, key);
}
} else if (stc8h_key_valid(key) && key != data->last_key &&
gpio_pin_get_dt(&cfg->irq) > 0) {
/* Poll with an IRQ line = missed-edge safety net.
* Only report while the key is still held (INT level
* active) — the latched register would otherwise
* re-report the stale last key forever. */
stc8h_report(dev, key);
}
data->last_key = key;
@@ -177,7 +216,10 @@ static void stc8h_irq_handler(const struct device *port, struct gpio_callback *c
ARG_UNUSED(port);
ARG_UNUSED(pins);
/* I2C cannot be touched from an ISR — hand off to the work queue. */
/* I2C cannot be touched from an ISR — hand off to the work queue.
* The flag tells the handler this run is a real key edge, so it must
* report even when the latched register still holds the same code. */
atomic_set(&data->irq_pending, 1);
k_work_reschedule(&data->work, K_NO_WAIT);
}
@@ -1,8 +1,9 @@
# ThinkNode M9 (ESP32-S3) LR1110 updater — overrides for the nRF-flavored prj.conf
#
# Console is the native USB Serial/JTAG (base DTS chosen) — no USB device
# stack, no CDC ACM, no UF2, no DT code partition (ESP Simple Boot links the
# image itself and west flash writes it from 0x0).
# Console rides uart0 (CH9102-class USB-C bridge; see board.overlay — native
# USB Serial/JTAG is unbonded and its pads carry the keypad I2C bus) — no USB
# device stack, no CDC ACM, no UF2, no DT code partition (ESP Simple Boot
# links the image itself and west flash writes it from 0x0).
CONFIG_BUILD_OUTPUT_UF2=n
CONFIG_USE_DT_CODE_PARTITION=n