From 6f51e8ff83b349d7c446cd7e05494c88544ca122 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 29 Mar 2026 06:43:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(travelers):=20=E7=9F=A9=E9=98=B5=E6=8C=89?= =?UTF-8?q?=E9=94=AE=E6=98=A0=E5=B0=84=E6=94=B9=E4=B8=BA=20key3/7/11/15=20?= =?UTF-8?q?=E6=96=B9=E5=90=91=E9=94=AE,=20P1.5=20=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E9=97=AA=E7=83=81,=20=E4=BF=AE=E5=A4=8D=20cols=20=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E6=8F=90=E5=8D=87=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | Bin 0 -> 28 bytes CHANGELOG.md | 12 ++- PCB/Moonshine_travelers.eprj2 | Bin 2306048 -> 2318336 bytes code/firmware-2.7.15.567b8ea/src/Led.cpp | 2 + code/firmware-2.7.15.567b8ea/src/Power.cpp | 12 +++ .../src/input/TCA9535ButtonThread.cpp | 76 +++++++++--------- .../src/input/TCA9535ButtonThread.h | 33 ++++++++ code/firmware-2.7.15.567b8ea/src/main.cpp | 7 ++ .../diy/esp32c3_moonshine_travelers/variant.h | 43 ++++++---- 9 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..58275e28107f6fefb7d2ac2322b168e2cf6d8dee GIT binary patch literal 28 hcmezWPmiITA)ld$A)6tIp_Cy72rC)%8F(4E7yx?S22B6} literal 0 HcmV?d00001 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2cd69..f453f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,10 +55,20 @@ ### Changed -- 无 +#### 按键映射更新(key3/key7/key11/key15 = 方向键) +- 矩阵按键映射从 `key1=UP, key2=DOWN, key3=LEFT, key4=RIGHT` 改为 `key3=UP, key7=DOWN, key11=LEFT, key15=RIGHT` +- 方向键全部位于 COL3 列(key3=ROW0·COL3, key7=ROW1·COL3, key11=ROW2·COL3, key15=ROW3·COL3) +- SELECT 由 GPIO9 短按处理,CANCEL 由 POWER_BOOT(P1.3) 短按处理 + +#### P1.5 状态灯改为独立 1 秒闪烁 +- P1.5 状态灯从 `GpioSplitter` + `LED_PIN` 同步驱动改为独立驱动 +- 在 `TCA9535ButtonThread::runOnce()` 中每 500ms 翻转(亮 500ms + 灭 500ms = 1 秒周期) +- 移除 `Led.cpp` 中的 `GpioTca9535LedPin` 类及相关 `#ifdef TCA9535_LORA_RST_VIRTUAL_PIN` 代码 ### Fixed +- **矩阵扫描 cols 整数提升 bug**:`~` 运算符对 `uint8_t` 提升为 `int`,导致 `cols` 高 4 位被污染,key4~key15 永远无法触发 + - 修复:`((~(p0In & 0xF0)) >> 4) & 0x0F` — 显式截断到 4 bit - **LTO 链接错误**:编译时 `-flto` 导致 `undefined reference to TCA9535ButtonThread::*` - 原因:.h/.cpp 中的 `#if defined(HAS_TCA9535_BUTTON)` 守卫导致部分编译单元中符号被丢弃 - 修复:去掉 .h/.cpp 中的条件守卫,类定义和实现始终编译;main.cpp 中的实例化仍由 `#ifdef` 控制 diff --git a/PCB/Moonshine_travelers.eprj2 b/PCB/Moonshine_travelers.eprj2 index 913a8e01ca32000a75f0d5826e2139026ea5bed6..e9898ab38b7ba9d2546da932d35be7945d225bef 100644 GIT binary patch delta 27047 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 V*ZyBmTA;*VQQT2tzNr4w{|kj;Tl4?` delta 14156 zcmbW8OKcle6ox%xkK@O^@x*z!c3zWIg#ZE)r!I96Bp8*pK)?wKrL;hjs-=ZKLaI`g z5bzy|fK*UoI?)Jq(GUril}p|0yC4uD1qsBW3);0M#6FvJgOHd8A+enEOR* zx#!*)e{yO43$0ZDTw7{4?gj$-VA&eh?%tR@xu^|X`DL*$5o21gQ=6WhI(zKo@$>qs zX{}o3_D}!6zkFwD=BHY}=Dl?96|8Cfp~{c(fxML+7|P{_a(Q>`#jq8co1dF^Z`g(< zH$(GXZvFd=wf&FUTBEUlEFzf^@UEY&u}R~!cC)GIYPG~4#=S_*IBhKctp9eYHkMAk zro{&H*@67t?C#xp*F0cckNo$iJ5n%gw?7yDiSS;<^hh8m_~_*`q?87vq#Ox5I_WYE zz-1H*Ln#ADNm&d#v%Zlwh`oscB`FpKQc~Wgfb46aEiw+2q*x=6k_*KKS&TTg@21>| z&{&c{Rf;tMDY;OO$a>Tn_sx=v(OBAmsuXJlQr_^#2u;}_f@FY_6iWjsZ&G5;s_%|m zh|@aK1(c*%CyTT0XeJNXdnzX1Ubj^!jeftri-~37{&)jsq$0 zF`$Lel!;av$U8tuik$*d-tp5)c%E4%f}90PQtS+nl5(Y$uyNf#9?k=oQEV1Uy$7Vc z$tNMT(H7bJKuL;S08-uuWNm~;n++n!2S7=RT>?_xq0&a!zqW`V9|0vPHV>p^HC6V1 zIC{gvMG&}*7lRxO7np>xQssj0(`B0qvM^jg8iotZ!_1`0{y%Zrrix>|f}9K&n3Soc z%G0!7B`_*PwRj5`@j6mkJE*;C#;71w!v*AOkhQ6%shdSR z1_cQmE--^rOqY9UW7UmOK{kgAQ0JsWR`95P)ooZwAFPe9q<899tYTm7>T3GlFb#Jn zh6_0#E+FY6H`C>D8r6ds6=Z<8z!Xq1LmjwBFe*p~ae?`ul}ve-)~m-cD##6S0m&h0 zwNuZrL5vDAMO=WYsGYh3pTMXfZNvrUjVfd+NE~qinIkSBbyV%3wrUt_6=adPz%){^ zgL*nYi%~&Ji3`jrl{%@=IJmf~nd`-2q>~pdi=81tyzDI;jirWsC}PNL)Y? zNzRaa^$JD>86_?-rBoqP9m1#}y~G9Pmo~{%M=&bLHF1H-rv5JKarG8P1&JpvF!NOE zqCSTl!>Aw&#Ra6HWSva)Hbw>cC@wG|HPTHzuinL|AUVYaW~WMIs&g0>WU9EpRMmPn zb+>?A6&H}K;sUc(rbXQ?E@Hic%oP`yx|*@5y_&SEmV4Br8i_o^k^Z#zK#~W7Ve2A7LM5eb5$4petS%D8*)|O-tL|J E2NYwotpET3 diff --git a/code/firmware-2.7.15.567b8ea/src/Led.cpp b/code/firmware-2.7.15.567b8ea/src/Led.cpp index 6406cd2..aa022e0 100644 --- a/code/firmware-2.7.15.567b8ea/src/Led.cpp +++ b/code/firmware-2.7.15.567b8ea/src/Led.cpp @@ -41,6 +41,8 @@ static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin); static GpioPin &ledFinalPin = ledHwPin; #endif +// P1.5 状态灯由 TCA9535ButtonThread 独立驱动 1 秒闪烁,不再跟随 LED_PIN + #ifdef USE_POWERMON /** * We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff. diff --git a/code/firmware-2.7.15.567b8ea/src/Power.cpp b/code/firmware-2.7.15.567b8ea/src/Power.cpp index fa8661d..cb1d5c6 100644 --- a/code/firmware-2.7.15.567b8ea/src/Power.cpp +++ b/code/firmware-2.7.15.567b8ea/src/Power.cpp @@ -20,6 +20,10 @@ #include "meshUtils.h" #include "sleep.h" +#ifdef TCA9535_LORA_RST_VIRTUAL_PIN +#include "input/TCA9535ButtonThread.h" +#endif + #if defined(ARCH_PORTDUINO) #include "api/WiFiServerAPI.h" #include "input/LinuxInputImpl.h" @@ -779,6 +783,14 @@ void Power::shutdown() #ifdef PIN_LED3 ledOff(PIN_LED3); #endif + +#ifdef TCA9535_LORA_RST_VIRTUAL_PIN + // TCA9535 电源管理板卡:拉低 POWER_EN 切断硬件供电 + // 按键仍按着时 MOS 还导通,屏幕 "Shutting Down..." 已显示完毕 + tca9535PowerEn(false); + delay(500); // 等待 I²C 写入完成和屏幕显示稳定 +#endif + doDeepSleep(DELAY_FOREVER, true, true); #elif defined(ARCH_PORTDUINO) exit(EXIT_SUCCESS); diff --git a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp index 3cc03c5..5c7a864 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp @@ -4,29 +4,29 @@ using namespace concurrency; -// ----------------------------------------------------------------------- // 默认按键映射(4×4 矩阵,行优先:KEY[0]=ROW0·COL0 ... KEY[15]=ROW3·COL3) +// 仅保留方向键,SELECT/CANCEL 由其他按键处理 // variant.h 中可用 #define TCA9535_KEY_MAP { ... } 覆盖 // ----------------------------------------------------------------------- #ifndef TCA9535_KEY_MAP #define TCA9535_KEY_MAP \ { \ - INPUT_BROKER_SELECT, /* ROW0·COL0 */ \ - INPUT_BROKER_UP, /* ROW0·COL1 */ \ - INPUT_BROKER_DOWN, /* ROW0·COL2 */ \ - INPUT_BROKER_LEFT, /* ROW0·COL3 */ \ - INPUT_BROKER_RIGHT, /* ROW1·COL0 */ \ - INPUT_BROKER_CANCEL, /* ROW1·COL1 */ \ - INPUT_BROKER_NONE, /* ROW1·COL2 */ \ - INPUT_BROKER_NONE, /* ROW1·COL3 */ \ - INPUT_BROKER_NONE, /* ROW2·COL0 */ \ - INPUT_BROKER_NONE, /* ROW2·COL1 */ \ - INPUT_BROKER_NONE, /* ROW2·COL2 */ \ - INPUT_BROKER_NONE, /* ROW2·COL3 */ \ - INPUT_BROKER_NONE, /* ROW3·COL0 */ \ - INPUT_BROKER_NONE, /* ROW3·COL1 */ \ - INPUT_BROKER_NONE, /* ROW3·COL2 */ \ - INPUT_BROKER_NONE, /* ROW3·COL3 */ \ + INPUT_BROKER_NONE, /* key0 = ROW0·COL0 */ \ + INPUT_BROKER_NONE, /* key1 = ROW0·COL1 */ \ + INPUT_BROKER_NONE, /* key2 = ROW0·COL2 */ \ + INPUT_BROKER_UP, /* key3 = ROW0·COL3 */ \ + INPUT_BROKER_NONE, /* key4 = ROW1·COL0 */ \ + INPUT_BROKER_NONE, /* key5 = ROW1·COL1 */ \ + INPUT_BROKER_NONE, /* key6 = ROW1·COL2 */ \ + INPUT_BROKER_DOWN, /* key7 = ROW1·COL3 */ \ + INPUT_BROKER_NONE, /* key8 = ROW2·COL0 */ \ + INPUT_BROKER_NONE, /* key9 = ROW2·COL1 */ \ + INPUT_BROKER_NONE, /* key10 = ROW2·COL2 */ \ + INPUT_BROKER_LEFT, /* key11 = ROW2·COL3 */ \ + INPUT_BROKER_NONE, /* key12 = ROW3·COL0 */ \ + INPUT_BROKER_NONE, /* key13 = ROW3·COL1 */ \ + INPUT_BROKER_NONE, /* key14 = ROW3·COL2 */ \ + INPUT_BROKER_RIGHT, /* key15 = ROW3·COL3 */ \ } #endif @@ -58,11 +58,11 @@ bool TCA9535ButtonThread::init() { // =================================================================== // 第一步:配置 P1 口方向 - // P1.2 = 输出(POWER_EN),P1.3 = 输入(POWER_BOOT),P1.4 = 输出(LoRa RST) + // P1.2 = 输出(POWER_EN),P1.3 = 输入(POWER_BOOT),P1.4 = 输出(LoRa RST),P1.5 = 输出(状态灯) // Configuration 寄存器:1=input, 0=output - // P1.2=bit2=0, P1.3=bit3=1, P1.4=bit4=0 → 0xEB (1110 1011) + // P1.2=bit2=0, P1.3=bit3=1, P1.4=bit4=0, P1.5=bit5=0 → 0xCB (1100 1011) // =================================================================== - if (!writeReg(TCA9535_REG_CONFIG_P1, 0xEB)) { + if (!writeReg(TCA9535_REG_CONFIG_P1, 0xCB)) { LOG_WARN("TCA9535: P1 config write failed"); return false; } @@ -71,6 +71,9 @@ bool TCA9535ButtonThread::init() // 注意:此时不拉高 POWER_EN,等开机确认后再拉高 tca9535LoraReset(true); + // P1.5 状态灯默认熄灭(高电平) + tca9535StatusLed(false); + // =================================================================== // 第二步:开机检测 — 等待用户持续按住 P1.3 达 2 秒 // 物理按键已使 MOS 导通(ESP32 得电),但 POWER_EN 尚未拉高 @@ -174,26 +177,28 @@ int32_t TCA9535ButtonThread::runOnce() // 持续按住中 uint32_t held = millis() - _powerBtnPressStart; if (held >= TCA9535_POWER_BOOT_HOLD_MS) { - LOG_WARN("TCA9535: Shutdown button held %lu ms -> POWERING OFF", held); - _powerState = TCA9535PowerState::SHUTDOWN_PENDING; - - // 清空屏幕,给用户"即将关机"的视觉反馈 - // 按键仍在按着所以 MOS 还导通,系统不会立刻断电 - if (screen) - screen->setOn(false); - - tca9535PowerEn(false); - // 用户松手后 MOS 断开,系统断电 - return INT32_MAX; // 不再调度 + LOG_WARN("TCA9535: Power button held %lu ms -> SHUTDOWN", held); + _powerBtnPressStart = 0; + dispatchEvent(INPUT_BROKER_SHUTDOWN); } } else if (!pressed && _powerBtnPressStart != 0) { - // 按键松开,未达关机时间 + // 按键松开,未达关机时间 → 短按 = CANCEL 事件 uint32_t held = millis() - _powerBtnPressStart; - LOG_DEBUG("TCA9535: Shutdown button released after %lu ms (no action)", held); + LOG_DEBUG("TCA9535: Power button short press (%lu ms) -> CANCEL", held); _powerBtnPressStart = 0; + dispatchEvent(INPUT_BROKER_CANCEL); } } + // =================================================================== + // P1.5 状态灯闪烁:500ms 亮 + 500ms 灭 = 1 秒周期 + // =================================================================== + if (millis() - _statusLedToggleMs >= 500) { + _statusLedToggleMs = millis(); + _statusLedOn = !_statusLedOn; + tca9535StatusLed(_statusLedOn); + } + // =================================================================== // 矩阵键盘扫描(仅 RUNNING 状态) // =================================================================== @@ -221,8 +226,6 @@ int32_t TCA9535ButtonThread::runOnce() if (pressed & (1u << i)) { input_broker_event evt = tca9535KeyMap[i]; if (evt != INPUT_BROKER_NONE) { - LOG_DEBUG("TCA9535: key[%u] (R%uC%u) pressed -> event %d", i, i / TCA9535_COLS, i % TCA9535_COLS, - (int)evt); dispatchEvent(evt); } } @@ -262,7 +265,8 @@ bool TCA9535ButtonThread::scanMatrix(uint16_t &keys) // 提取列位(P0.4~P0.7),低电平=按下 // 将列状态从高4位移到低位,便于索引 - uint8_t cols = (~(p0In & TCA9535_COL_MASK)) >> 4; // bit0=COL0, bit3=COL3 + // 注意:~ 运算会将 uint8_t 提升为 int,必须 & 0x0F 截断到 4 bit + uint8_t cols = ((~(p0In & TCA9535_COL_MASK)) >> 4) & 0x0F; // bit0=COL0, bit3=COL3 // 组装到 keys(每行 4 列) keys |= ((uint16_t)cols << (row * TCA9535_COLS)); diff --git a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h index 9ffffc0..192f192 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h @@ -10,6 +10,7 @@ * - P1.2:电源使能(POWER_EN),高电平有效,驱动 MOS 管维持供电 * - P1.3:电源开机按钮(POWER_BOOT),输入,低电平有效(按键按下接地) * - P1.4:LoRa RST 输出(通过 I²C 控制 RadioLib 复位序列) + * - P1.5:状态指示灯,低电平点亮 * * 电源管理逻辑: * 开机:物理按键按下 → MOS 导通 → ESP32/TCA9535 得电 @@ -83,6 +84,7 @@ #define TCA9535_BIT_P12 (1u << 2) // POWER_EN 输出 #define TCA9535_BIT_P13 (1u << 3) // POWER_BOOT 输入 #define TCA9535_BIT_P14 (1u << 4) // LoRa RST 输出 +#define TCA9535_BIT_P15 (1u << 5) // 状态指示灯输出(低电平点亮) /** * 通过 I²C 控制 TCA9535 P1.2 上的电源使能(POWER_EN)。 @@ -160,6 +162,33 @@ static inline bool tca9535LoraReset(bool high) return (Wire.endTransmission() == 0); } +/** + * 通过 I²C 控制 TCA9535 P1.5 上的状态指示灯。 + * 低电平点亮,高电平熄灭。 + * @param on true=点亮(低电平),false=熄灭(高电平) + */ +static inline bool tca9535StatusLed(bool on) +{ + Wire.beginTransmission(TCA9535_I2C_ADDR); + Wire.write(TCA9535_REG_OUTPUT_P1); + if (Wire.endTransmission(false) != 0) + return false; + if (Wire.requestFrom((uint8_t)TCA9535_I2C_ADDR, (uint8_t)1) != 1) + return false; + uint8_t p1Out = Wire.read(); + + // 修改 P1.5 位:低电平点亮 + if (on) + p1Out &= ~TCA9535_BIT_P15; // 拉低 = 点亮 + else + p1Out |= TCA9535_BIT_P15; // 拉高 = 熄灭 + + Wire.beginTransmission(TCA9535_I2C_ADDR); + Wire.write(TCA9535_REG_OUTPUT_P1); + Wire.write(p1Out); + return (Wire.endTransmission() == 0); +} + /** * 电源管理状态机 */ @@ -202,6 +231,10 @@ class TCA9535ButtonThread : public Observable, public concur // 上次扫描结果(16 位,每 bit 对应 row*4+col),用于边沿检测 uint16_t _lastKeys = 0x0000; + // P1.5 状态灯闪烁控制 + bool _statusLedOn = false; + uint32_t _statusLedToggleMs = 0; + // 写寄存器 bool writeReg(uint8_t reg, uint8_t val); diff --git a/code/firmware-2.7.15.567b8ea/src/main.cpp b/code/firmware-2.7.15.567b8ea/src/main.cpp index 1d67306..ecbcc6d 100644 --- a/code/firmware-2.7.15.567b8ea/src/main.cpp +++ b/code/firmware-2.7.15.567b8ea/src/main.cpp @@ -1151,6 +1151,13 @@ void setup() userConfig.longPress = INPUT_BROKER_SELECT; userConfig.longPressTime = 500; userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; +#ifdef BUTTON_SINGLE_PRESS_EVENT + userConfig.singlePress = BUTTON_SINGLE_PRESS_EVENT; +#endif +#ifdef BUTTON_DISABLE_LONG_PRESS + userConfig.longPress = INPUT_BROKER_NONE; + userConfig.longLongPress = INPUT_BROKER_NONE; +#endif UserButtonThread->initButton(userConfig); } else { ButtonConfig userConfigNoScreen; diff --git a/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h b/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h index 12259bd..dbed402 100644 --- a/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h +++ b/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h @@ -1,5 +1,12 @@ #define BUTTON_PIN 9 +// GPIO9 短按直接触发 SELECT(而非默认的 USER_PRESS 切换) +// 长按无功能(关机由 POWER_BOOT 长按处理) +#ifndef BUTTON_SINGLE_PRESS_EVENT +#define BUTTON_SINGLE_PRESS_EVENT INPUT_BROKER_SELECT +#endif +#define BUTTON_DISABLE_LONG_PRESS 1 + #define HAS_SCREEN 1 #define USE_SH1106 @@ -36,25 +43,27 @@ // 按键映射:4×4 矩阵,行优先排列 // KEY[0]=ROW0·COL0, KEY[1]=ROW0·COL1, ..., KEY[15]=ROW3·COL3 -// 低电平有效(按下接地,列读取到低电平=按下) +// 低电平有效(按下接地,列读取到低电平=按下) +// SELECT 由 GPIO9 短按触发,CANCEL 由 POWER_BOOT(P1.3) 短按触发 +// 矩阵仅保留方向键 #define TCA9535_KEY_MAP \ { \ - INPUT_BROKER_SELECT, /* ROW0·COL0 */ \ - INPUT_BROKER_UP, /* ROW0·COL1 */ \ - INPUT_BROKER_DOWN, /* ROW0·COL2 */ \ - INPUT_BROKER_LEFT, /* ROW0·COL3 */ \ - INPUT_BROKER_RIGHT, /* ROW1·COL0 */ \ - INPUT_BROKER_CANCEL, /* ROW1·COL1 */ \ - INPUT_BROKER_NONE, /* ROW1·COL2 */ \ - INPUT_BROKER_NONE, /* ROW1·COL3 */ \ - INPUT_BROKER_NONE, /* ROW2·COL0 */ \ - INPUT_BROKER_NONE, /* ROW2·COL1 */ \ - INPUT_BROKER_NONE, /* ROW2·COL2 */ \ - INPUT_BROKER_NONE, /* ROW2·COL3 */ \ - INPUT_BROKER_NONE, /* ROW3·COL0 */ \ - INPUT_BROKER_NONE, /* ROW3·COL1 */ \ - INPUT_BROKER_NONE, /* ROW3·COL2 */ \ - INPUT_BROKER_NONE, /* ROW3·COL3 */ \ + INPUT_BROKER_NONE, /* key0 = ROW0·COL0 */ \ + INPUT_BROKER_NONE, /* key1 = ROW0·COL1 */ \ + INPUT_BROKER_NONE, /* key2 = ROW0·COL2 */ \ + INPUT_BROKER_UP, /* key3 = ROW0·COL3 */ \ + INPUT_BROKER_NONE, /* key4 = ROW1·COL0 */ \ + INPUT_BROKER_NONE, /* key5 = ROW1·COL1 */ \ + INPUT_BROKER_NONE, /* key6 = ROW1·COL2 */ \ + INPUT_BROKER_DOWN, /* key7 = ROW1·COL3 */ \ + INPUT_BROKER_NONE, /* key8 = ROW2·COL0 */ \ + INPUT_BROKER_NONE, /* key9 = ROW2·COL1 */ \ + INPUT_BROKER_NONE, /* key10 = ROW2·COL2 */ \ + INPUT_BROKER_LEFT, /* key11 = ROW2·COL3 */ \ + INPUT_BROKER_NONE, /* key12 = ROW3·COL0 */ \ + INPUT_BROKER_NONE, /* key13 = ROW3·COL1 */ \ + INPUT_BROKER_NONE, /* key14 = ROW3·COL2 */ \ + INPUT_BROKER_RIGHT, /* key15 = ROW3·COL3 */ \ }