Raspberry Pi

Raspberry Pi 4のLinux ガジェットでマウスを実現する(USB OTG)

Raspberry Pi 4のLinuxガジェット機能を使ってHID(Human Interface Device)マウスを実現します。ガジェットモードを動作させるに、USB OTG(USB On-The-Go)にRaspberry Piが対応している必要があります。

現在、USB OTGに対応しているのは、Raspberry Pi Zero(W/WH)、Raspberry Pi 4になります。

今回の手順はRaspberry Pi 4を元に説明を作っていますが、Raspberry Pi Zero(W/WH)でも動作します。

USB On-The-Go(USB OTG)とは

USB OTGは、USB IF(USB Implementers Forum)が定めた規格です。

もともとUSBは、パソコン(ホスト)と、USB機器「キーボード、マウス、プリンター、デジカメなど」(デバイス)が接続して、パソコンがUSB機器を制御していました。

USB OTGの規格が誕生して、USB機器どうしを直接接続して使えるようになりました。例としてデジカメとプリンターを接続して直接印刷したり、スマホとプリンターを接続して印刷したり出来るのは、USB OTGに対応しているからです。

USB OTGに対応している機器は、状況役割によってUSBホストになったり、USBデバイスになったりします。

接続方法

Raspberry Pi 4は、下の画像にあるようにUSB Type-Cの電源コネクタとパソコンを接続します。

パソコンにUSB Type-Cがある場合は、USB Type-C to Type-Cケーブルで接続します。

USB Type-C接続

パソコンにUSB Type-Cが無い場合には、USB Type-C to Type-A変換ケーブルを使って接続します。

電源はこのUSBケーブルから供給されるため、ある程度安定した電源供給が必要です。

USB Type-C to Type-Aで接続する場合、状況によってはセルフパワー付きのUSB HUBを使って接続するのも良いと思います。

USB Type-C to Type-A変換とセルフパワーUSB HUB

Raspberry Pi Zero WHの場合、USB Micro-Bコネクタ(電源「PWR IN」ではないコネクタ「USB」の方)に接続します。

パソコンとは、USB Type-A to Micro-Bケーブルで接続されて、電源供給もこのUSBケーブル経由で供給されます。

USB Micro-B接続

dwc2 USBドライバの設定

/boot/config.txt」ファイルに「dtoverlay=dwc2」を、「/etc/modules」ファイルに「dwc2」を一番最後にviやnanoなどのエディタで追記して起動時にドライバが自動でロードされるように設定します。

$ sudo nano /boot/config.txt
$ sudo nano /etc/modules

または、コマンドでターミナルから「tee」コマンドでファイルに追記してください。

$ echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
$ echo "dwc2" | sudo tee -a /etc/modules

追記後、Raspberry Piを再起動します。

HIDマウスの設定

今回作成する、USBマウスのUSB HID report descriptorは次のようなパラメーターになっています。

パラメーターの詳細の確認は、USB Implementers Forumの「Device Class Definition for HID」、「HID Usage Tables」ドキュメントを参照してください。

USBマウスのHID report descriptor情報

USAGE_PAGE (Generic Desktop)            05 01
USAGE (Mouse)                           09 02
    COLLECTION (Application)            A1 01
    USAGE (Pointer)                     09 01
    COLLECTION (Physical)               A1 00
        USAGE_PAGE (Button)             05 09
        USAGE_MINIMUM (Button 1)        19 01
        USAGE_MAXIMUM (Button 5)        29 05
        LOGICAL_MINIMUM (0)             15 00
        LOGICAL_MAXIMUM (1)             25 01
        REPORT_COUNT (5)                95 05
        REPORT_SIZE (1)                 75 01
        INPUT (Data,Var,Abs)            81 02
        REPORT_COUNT (1)                95 01
        REPORT_SIZE (3)                 75 03
        INPUT (Cnst,Var,Abs)            81 03
        USAGE_PAGE (Generic Desktop)    05 01
        USAGE (X)                       09 30
        USAGE (Y)                       09 31
        USAGE (Wheel)                   09 38
        LOGICAL_MINIMUM (-32767)        16 01 80
        LOGICAL_MAXIMUM (32767)         26 FF 7F
        REPORT_SIZE (16)                75 10
        REPORT_COUNT (3)                95 03
        INPUT (Data,Var,Rel)            81 06
    END_COLLECTION                      C0
END_COLLECTION                          C0

上記のHID report descriptor情報を使用して、次のシェルスクリプト「gadget_mouse.sh」を作成して、Raspberry Pi 4をUSBマウス「Generic USB Mouse」として動作するようにします。

作成したシェルスクリプトはUSBガジェットの作成「start」と、USBガジェットの停止「stop」の機能があります。

#!/bin/bash
GADGET="/sys/kernel/config/usb_gadget"
VID="0x1d6b"                        # Linux Foundation
PID="0x0104"                        # Multifunction Composite Gadget
DEVICE="0x0100"                     # v1.0.0
USB_TYPE="0x0200"                   # USB2
SERIAL_NUMBER="abcd1234"            # serial number
MANUFACTURER="toki-blog.com"        # manufacturer code
PRODUCT_NAME="Generic USB Mouse"    # product name
PROTOCOL="1"                        # USB protocol
SUBCLASS="1"                        # USB subclass
REPORT_LENGTH="7"                   # USB report length
REPORT_DESCRIPTOR="\\x05\\x01\\x09\\x02\\xA1\\x01\\x09\\x01\\xA1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x03\\x05\\x01\\x09\\x30\\x09\\x31\\x09\\x38\\x16\\x01\\x80\\x26\\xFF\\x7F\\x75\\x10\\x95\\x03\\x81\\x06\\xC0\\xC0"
DEVICE_NO="usb0"
CONFIG_NO="1"
MAX_POWER="250"

case "$1" in
    start)
        echo "Creating the USB gadget"
        modprobe libcomposite       # Loading composite module

        echo "Creating gadget directory"
        cd $GADGET
        mkdir -p g1
        cd g1

        echo "Setting ID's"
        echo $VID > idVendor
        echo $PID > idProduct
        echo $DEVICE > bcdDevice
        echo $USB_TYPE > bcdUSB

        echo "Creating strings"
        mkdir -p strings/0x409
        echo $SERIAL_NUMBER > strings/0x409/serialnumber
        echo $MANUFACTURER > strings/0x409/manufacturer
        echo $PRODUCT_NAME > strings/0x409/product

        echo "Creating the functions"
        mkdir -p functions/hid.$DEVICE_NO
        echo $PROTOCOL > functions/hid.$DEVICE_NO/protocol
        echo $SUBCLASS > functions/hid.$DEVICE_NO/subclass
        echo $REPORT_LENGTH > functions/hid.$DEVICE_NO/report_length
        echo -ne $REPORT_DESCRIPTOR > functions/hid.$DEVICE_NO/report_desc

        echo "Creating the configurations"
        mkdir -p configs/c.$CONFIG_NO/strings/0x409
        echo "Config $CONFIG_NO: ECM network" > configs/c.$CONFIG_NO/strings/0x409/configuration
        echo $MAX_POWER > configs/c.$CONFIG_NO/MaxPower

        echo "Associating the functions with their configurations"
        ln -s functions/hid.$DEVICE_NO configs/c.$CONFIG_NO/

        echo "Enabling the USB gadget"
        ls /sys/class/udc > UDC
        echo "OK"
        
        ;;
    stop)
        echo "Stopping the USB gadget"

        echo "Disabling the USB gadget"
        cd $GADGET/g1
        echo "" > UDC

        echo "Cleaning up"
        rm configs/c.$CONFIG_NO/hid.$DEVICE_NO
        rmdir functions/hid.$DEVICE_NO

        echo "Cleaning up configuration"
        rmdir configs/c.$CONFIG_NO/strings/0x409
        rmdir configs/c.$CONFIG_NO

        echo "Clearing strings"
        rmdir strings/0x409

        echo "Removing gadget directory"
        cd $GADGET
        rmdir g1
        cd /

        # modprobe -r libcomposite    # Remove composite module
        echo "OK"

        ;;
    *)
        echo "Usage : $0 {start|stop}"
        ;;
esac

「gadget_mouse.sh」を作成したら、次のコマンドを実行して、USBガジェットの作成「start」します。

パラメーターの意味は、Linux USB gadget configured through configfsを参照してください。

$ chmod 777 gadget_mouse.sh
$ sudo ./gadget_mouse.sh start

実行時にエラーが出る場合、作成したファイルの改行コードが「LF」になっていているか確認しましょう。

「CRLF」の場合は、改行コードを「LF」に変更して保存します。

HIDマウスの動作確認

lsコマンドで「/dev/hidg0」を表示すると次のように表示されます。

$ ls -l /dev/hidg0
crw------- 1 root root 235, 0  5月 25 01:01 /dev/hidg0

「/dev/hidg0」のデバイスが作成されると、Windows側では「Generic USB Mouse」接続を検知してドライバーのインストールが実行されます。

正しく、Windowsで認識、動作しているかをデバイスマネージャーにて確認します。

「マウスとそのほかのポインティングデバイス」を展開すると、「HID準拠マウス」があります。下の画像の例の場合、通常のマウスと今回追加したガジェットのマウスの2つが表示されます。

デバイスマネージャー

「HID準拠マウス」を「右クリック」して「プロパティ」で、「HID準拠マウスのプロパティ」を表示します。

「イベント」タブを表示して、「親デバイス」の項目で今回追加した、「VID:1D6B」、「PID:0104」、「シリアル:abcd1234」の表示があれば、このデバイスが正常に動いています。

HID準拠マウスのプロパティ

パラメーターに不備があって設定に失敗すると、下記のようにエクスクラメーションマークが表示されます。

デバイスマネージャー、失敗例

USB機器の情報を詳しく確認する

USBView、USBDeviewでもう少し詳細に確認します。

使い方については、こちらの記事で紹介しています。

PCに接続したUSB機器の情報を確認する

USB機器の情報を取得するツールを紹介します。「USBView」ツールは、Windows PCに接続されているすべてのUSBデバイス情報を参照できるWindows SDKに含まれるツールです。 また、 ...

続きを見る

パラメーターの説明

今回作成したHIDマウスは、次のパラメーターで制御します。

byte bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0 padding padding padding 進むボタン 戻るボタン 中ボタン 右ボタン 左ボタン
1 カーソル横移動量(LSB)
2 カーソル横移動量(MSB)
3 カーソル縦移動量(LSB)
4 カーソル縦移動量(MSB)
5 ホイール縦スクロール移動量(LSB)
6 ホイール縦スクロール移動量(MSB)
  • カーソル横移動量 ・・・ -32767~32767の範囲(マイナスが左移動、プラスが右移動)
  • カーソル縦移動量 ・・・ -32767~32767の範囲(マイナスが上移動、プラスが下移動)
  • ホイール縦スクロール移動量 ・・・ -32767~32767の範囲(マイナスが下スクロール、プラスが上スクロール)

コマンドでの動作確認

次のコマンドを入力して、パソコン側のマウスカーソルが動作するか確認します。

実行するとマウスカーソルが右に移動します。

$ sudo su
# echo -ne "\x00\xff\x00\x00\x00\x00\x00" > /dev/hidg0

Pythonのサンプルプログラム

Pythonにて制御するサンプルプログラム「mouse_sample.py」を作成します。

サンプルプログラムでは、「左クリック1回」カーソルの位置で実行して、「右にカーソル移動」をします。

from enum import IntEnum

CLICK_OFF = 0x00        # Click off
CLICK_LEFT = 0x01       # 左クリック
CLICK_RIGHT = 0x02      # 右クリック
CLICK_MIDDLE = 0x04     # 真ん中クリック
CLICK_BACK = 0x08       # 戻る
CLICK_FORWARD = 0x10    # 進む

class MouseIndex(IntEnum):
    TIP_SW = 0
    X_LSB = 1
    X_MSB = 2
    Y_LSB = 3
    Y_MSB = 4
    WHEEL_LSB = 5
    WHEEL_MSB = 6
    MAX = 7

def send_command(buffer):
    cmd = bytes(buffer)
    with open('/dev/hidg0', 'rb+') as fd:
        fd.write(cmd)

def mouse_control(tip, x, y, wheel):
    data = [0] * MouseIndex.MAX
    data[MouseIndex.TIP_SW] = tip
    data[MouseIndex.X_LSB] = (x & 0xff)
    data[MouseIndex.X_MSB] = (x >> 8)
    data[MouseIndex.Y_LSB] = (y & 0xff)
    data[MouseIndex.Y_MSB] = (y >> 8)
    data[MouseIndex.WHEEL_LSB] = (wheel & 0xff)
    data[MouseIndex.WHEEL_MSB] = (wheel >> 8)
    send_command(data)

def main():
    print("Mouse sample.")

    x = 0
    y = 0
    wheel = 0
    # 左クリック
    mouse_control(CLICK_LEFT, x, y, wheel)
    mouse_control(CLICK_OFF, x, y, wheel)
    # カーソルX移動
    x = 100
    mouse_control(CLICK_OFF, x, y, wheel)

if __name__ == "__main__":
    main()

作成した「mouse_sample.py」を次のコマンドにて実行します。

$ sudo python3 mouse_sample.py

Raspberry Pi起動時にガジェットを有効にする方法

Raspberry Piを起動した時に、ガジェットを有効にして作成したUSBマウスとして起動するようにします。また、一般ユーザーでも作成したデバイスの使用ができるよう許可します。

例として「/home/pi/Desktop/gadgets」フォルダに、作成し「gadget_mouse.sh」ファイルを置きます。

「vi」や「nano」のエディタで「/etc/rc.local」ファイルを開いて次のスクリプトを書き込みます。

/home/pi/Desktop/gadgets/gadget_mouse.sh start
chmod -R 777 /dev/hidg0

exit 0

exit 0」の手前にスクリプトを書き込みましょう。

まとめ

Raspberry PiのLinuxガジェット機能では色々な事ができ、今回はUSBマウスを実現しました。マウスが再現できると自動でアプリやブラウザの制御ができたりして色々アイデアが出てきます。

今後も、他のLinuxガジェット機能も試して行きます。

以上、「Raspberry Pi 4のLinux ガジェットでマウスを実現する(USB OTG)」の内容でした。

-Raspberry Pi

© 2021 Toki Blog(トキブログ)