Elite-Cや一部のPro Micro互換機における挙動の問題と対策

はじめに

多くの自作キーボードにおいて、そのMCUにPro Microおよびその互換機が使用されており、多くの場合がPro Micro互換機だと思います。当店においても互換機を取り扱っております。その互換機ですが、最近流通しているいくつか製造ロットにおいてVCCでの出力電圧が5Vきっちりに出力されるために、ファームウェア側で対応が必要になる場合があります。またElite-Cにおいても同様の問題が報告されております。

対応が必要になるのは分離型キーボードのみです。下記症状にあたった場合には、ご確認いただければと思います。

  • ファームウェア書き込み後、片手ずつでは動作する
  • 両手をTRRS/TRSケーブルで接続し、片手(primary側)をUSBケーブルにてPCにつなぐと、もう片手(secondary側)がまったく反応しない*1

TL;DR

QMKファームウェアにて keymap.c あるいは config.h 等にて下記のように SPLIT_USB_DETECT を定義してファームウェアを焼き直してください。

#define SPLIT_USB_DETECT

詳細

この症状に関しての詳細は @kaoriya さんの記事に解説されています。

zenn.dev

以下、この SPLIT_USB_DETECT のフラグを立てることでどういった影響があるのか、QMK Firmware内での動作に関する簡単な解説です*2

このフラグを立てると、split_util.c というファイル内の次のセクションの最初のケースが呼ばれます。

#if defined(SPLIT_USB_DETECT)
#    if defined(PROTOCOL_LUFA)
static inline bool usbHasActiveConnection(void) { return USB_Device_IsAddressSet(); }
static inline void usbDisable(void) {
    USB_Disable();
    USB_DeviceState = DEVICE_STATE_Unattached;
}
#    elif defined(PROTOCOL_CHIBIOS)
static inline bool usbHasActiveConnection(void) { return usbGetDriverStateI(&USBD1) == USB_ACTIVE; }
static inline void usbDisable(void) { usbStop(&USBD1); }
#    elif defined(PROTOCOL_VUSB)
static inline bool usbHasActiveConnection(void) {
    usbPoll();
    return usbConfiguration;
}
static inline void usbDisable(void) { usbDeviceDisconnect(); }
#    else
static inline bool usbHasActiveConnection(void) { return true; }
static inline void usbDisable(void) {}
#    endif

bool usbIsActive(void) {
    for (uint8_t i = 0; i < (SPLIT_USB_TIMEOUT / SPLIT_USB_TIMEOUT_POLL); i++) {
        // This will return true if a USB connection has been established
        if (usbHasActiveConnection()) {
            return true;
        }
        wait_ms(SPLIT_USB_TIMEOUT_POLL);
    }

    // Avoid NO_USB_STARTUP_CHECK - Disable USB as the previous checks seem to enable it somehow
    usbDisable();

    return false;
}
#elif defined(PROTOCOL_LUFA) && defined(OTGPADE)
static inline bool usbIsActive(void) {
    USB_OTGPAD_On();  // enables VBUS pad
    wait_us(5);

    return USB_VBUS_GetStatus();  // checks state of VBUS
}
#else
static inline bool usbIsActive(void) { return true; }
#endif

ここで実装されている usbIsActive() は、同ファイル下部の is_keyboard_master() という関数内で呼ばれています。*3

__attribute__((weak)) bool is_keyboard_master(void) {
    static enum { UNKNOWN, MASTER, SLAVE } usbstate = UNKNOWN;

    // only check once, as this is called often
    if (usbstate == UNKNOWN) {
        usbstate = usbIsActive() ? MASTER : SLAVE;
    }

    return (usbstate == MASTER);
}

ここで usbstateUNKNOWN の場合にのみ usbIsActive() が呼ばれ、その結果で usbstateMASTER および SLAVE となります。この usbstate はstaticローカル変数のため、関数の実装を見る限り、UNKNOWNの状態なるのは一度だけであとは必ず MASTERSLAVE の値が設定されています。(コメントにも「 is_keyboard_master() は頻繁に呼ばれるので、UNKNOWN に関する処理は一度しか行われないようにする」と記載があります)

つまり、 SPLIT_USB_DETECT のフラグを設定して影響が起きるのは、キーボードをPCにつないで初めて左右のprimaryの判定をするときだけ、ということになります。私もこのフラグをオンにして使用してみていますが、キーボードの使用中にパフォーマンスが劣化する、といった症状は一切見られていません。

(2021.03.14 00:00 追記)@kaoriya さんが追加の記事を書いてくださっています。キーボードとしての動作自体には影響がないものの、装飾のLEDなどに対して注意するべきポイントを解説してくださっています。

zenn.dev

参照

*1:QMK Firmwareなどではmaster/slaveと記載されていますが、ここではprimary/secondaryとします

*2:実装はrev 6ea4b06 at masterのもの

*3:雑にgrepした結果、そこでしか呼ばれていませんでした。