作成日:2013年2月24日
最終更新日: 2016年3月11日

Arduino Leonardoでポインタの絶対位置を指定してみる

Arduino Leonardoはマウスとして振る舞うことができます。ポインタの位置はマウスの移動距離…つまり現在のポインタの位置に対する相対的な位置を指定することになります。しかし人間が普通のマウスを操作する時のように現在のポインタの位置を確認しながら動かすというわけにもいかないので、絶対的な位置が指定できたほうが便利なことがあります。ということでやってみました。

注:Arduino LLC(https://www.arduino.cc)よりリリースされている Arduino IDE Ver1.66 以降、USBデバイスに関するインプリメントが大きく変更されおり、本ドキュメントはそのままでは現状と異なりますのでご注意ください。このページからダウンロードできるファイルやこのドキュメントはそれより前のバージョンのIDEを前提としており、現在のIDEではそのまま使用できません。
また、Arduino SRL(http://www.arduino.org)がリリースしているIDEでは検証していません。

どうやるか

Arduino LeonardoのスケッチをコンパイルしてPCに接続すると、USBのHID(Human Interface Device)としてマウスとキーボードとしての機能があるという情報がホスト(つまりPC)に伝わるようになっています。もっと厳密に言うと、Arudino LeonardoがPCに対して送信するHIDレポートディスクリプタにはマウスとキーボードの情報が含まれているということです。ここに、座標の絶対位置を指定できるデバイス、つまりデジタイザとしての情報を追加し、さらに実際にデジタイザとしての情報を送信する処理を追加すればできるはずです。
ということで、単にスケッチを作成するだけでなく、Arduinoのアプリケーション内に記載されているソースに追加する必用があります。
なお、ここでの情報は Arduino IDE Ver1.5.1を元に作成しています。それ以外のバージョンでは変更する必用がある可能性があります。

レポートディスクリプタの追加

まず必用なのは、自分がデジタイザでもあるという事をPCに伝えるための情報をHIDレポートディスクリプタに追加することです。
HIDレポートディスクリプタはhardware/arduino/avr/cores/arduino/HID.cppの中に_hidReportDescriptor[]という変数名で記載されています。ここのキーボードの情報の後にデジタイザとしての情報を追加します。どの情報か(キーボードか、マウスか等)を識別するための情報であるREPORT IDとしては「3」にしました(ちなみにArduino Leonardoではマウスが1、キーボードが2となっている)。

HID.cpp
…前略
const u8 _hidReportDescriptor[] = {
 …中略
 //	Keyboard
 …中略
  0xc0,                          // END_COLLECTION

  // Digitizer
  0x05, 0x0d,                         // USAGE_PAGE (Digitizers) 
  0x09, 0x02,                         // USAGE (Pen) 
  0xa1, 0x01,                         // COLLECTION (Application) 
  0x85, 0x03,                         //   REPORT_ID (3) 
  0x09, 0x20,                         //   USAGE (Stylus) 
  0xa1, 0x00,                         //   COLLECTION (Physical)
  0x09, 0x42,                         //     USAGE (Tip Switch)
  0x09, 0x44,                         //     USAGE (Barrel Switch)
  0x09, 0x45,                         //     USAGE (Eraser Switch) 
  0x09, 0x3c,                         //     USAGE (Invert) 
  0x09, 0x32,                         //     USAGE (In Range) 
  0x15, 0x00,                         //     LOGICAL_MINIMUM (0)
  0x25, 0x01,                         //     LOGICAL_MAXIMUM (1) 
  0x75, 0x01,                         //     REPORT_SIZE (1) 
  0x95, 0x05,                         //     REPORT_COUNT (5)
  0x81, 0x02,                         //     INPUT (Data,Var,Abs) 
  0x95, 0x0b,                         //     REPORT_COUNT (11)
  0x81, 0x03,                         //     INPUT (Cnst,Var,Abs)
  0x05, 0x01,                         //     USAGE_PAGE (Generic Desktop)
  0x75, 0x10,                         //     REPORT_SIZE (16) 
  0x95, 0x01,                         //     REPORT_COUNT (1) 
  0x55, 0x0d,                         //     UNIT_EXPONENT (-3)
  0x65, 0x33,                         //     UNIT (Inch,EngLinear)
  0x15, 0x00,                         //     LOGICAL_MINIMUM (0)
  0x26, 0xff, 0x7f,                   //     LOGICAL_MAXIMUM (32767)
  0x09, 0x30,                         //     USAGE (X)
  0x81, 0x02,                         //     INPUT (Data,Var,Abs) 
  0x09, 0x31,                         //     USAGE (Y) 
  0x81, 0x02,                         //     INPUT (Data,Var,Abs) 
  0xc0,                               //   END_COLLECTION
  0xc0,                               // END_COLLECTION 

#if RAWHID_ENABLED
 …以下略

このレポートディスクリプタの作成にはMicrosoftのサイトを参考にしました。
このディスクリプタに対し、送信する情報は以下の6バイトです。このうちbyte0のbit1〜3は0固定、bit4は1固定としました。bit0はクリック(デジタイザのボタンを押している時)に1とします。

byte bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0 予約(0) In Range Invert Erase Sw Barrel Sw Tip Sw
1 予約(0)
2 X(下位8bit)
3 X(上位8bit)
4 Y(下位8bit)
5 Y(上位8bit)

実際にこれらの情報を送信するためのクラスの定義とインプリメンテーション。
クラスの名称は「Digitizer_」にしました。これは同フォルダのUSBAPI.hファイルに追加します。インプリメンテーションはHID.cppファイルに追加します。さらにインスタンスとしてDigitizerという変数もHID.cppに宣言しておきます。

USBAPI.h
class Digitizer_
{
private:
  int screenX0,screenY0,screenX1,screenY1;
  int logicalMinX,logicalMinY,logicalMaxX,logicalMaxY;
  int logicalX, logicalY;
  uint8_t _switch;
  void send();
public:
  Digitizer_(void);
  void begin(void);
  void end(void);
  void setDisplayResolution(int x0, int y0, int x1, int y1);
  void setLogicalResolution(int x0, int y0, int x1, int y1);
  void press();
  void release();
  void click();
  void move(int x, int y);	
};
extern Digitizer_ Digitizer;
HID.cpp
…前略
Digitizer_ Digitizer; ←インスタンスを追加
…中略
Digitizer_::Digitizer_(void)
{
  logicalMinX = logicalMinY = screenX0 = screenY0 = 0;
  logicalMaxX = logicalMaxY = screenY0 = screenY1 = 32767;
  _switch = 0;
}

void Digitizer_::begin(void) 
{
}

void Digitizer_::end(void) 
{
}

void Digitizer_::setDisplayResolution(int param_x0,int param_y0,int param_x1,int param_y1) {
  screenX0 = param_x0;
  screenX1 = param_x1;
  screenY0 = param_y0;
  screenY1 = param_y1;
}

void Digitizer_::setLogicalResolution(int param_x0,int param_y0,int param_x1,int param_y1) {
  logicalMinX = param_x0;
  logicalMinY = param_y0;
  logicalMaxX = param_x1;
  logicalMaxY = param_y1;
}

void Digitizer_::send() {
  u8 m[6];
  m[0] = 0x10 | _switch;
  m[1] = 0;
  m[2] = LSB(logicalX);
  m[3] = MSB(logicalX);
  m[4] = LSB(logicalY);
  m[5] = MSB(logicalY);
  HID_SendReport(3,m,6);
}

void Digitizer_::move(int paramX, int paramY)
{
  logicalX = map(paramX,screenX0,screenX1,logicalMinX,logicalMaxX);
  logicalY = map(paramY,screenY0,screenY1,logicalMinY,logicalMaxY);
  send();	
}

void Digitizer_::press() {
  _switch = 1;
  send();
}

void Digitizer_::release() {
  _switch = 0;
  send();
}

void Digitizer_::click() {
  press();
  release();
}

これらを追加済みのUSBAPI.h、HID.cppファイルは以下です。

USB_Digitizer_forLeonardo.zip

API

APIとして以下を用意しましたが、使いやすいようにDigitizer_クラスを作ればいいかと思います。

Digitizer.move(int x, int y)
ポインタを座標x,yに移動させます。
Digitizer.press()
ポインタの位置でボタンを押したことにします。ここでの「ポインタの位置」とは、現在のポインタの位置ではなく、Digitizer.move()で最後に移動させた位置です。以下のAPIも同様です。
Digitizer.release()
ポインタの位置でボタンを離したことにします。
Digitizer.click()
ポインタの位置でボタンを一回押して離した事にします。Digitizer.press()とDigitizer.release()が連続して実行されるのと同じです。
Digitizer.setDisplayResolution(int x0, int y0, int x1, int y1)
Digitizer.setLogicalResolution(int x0, int y0, int x1, int y1)
スケッチにて指定する座標と画面上のポインタの位置の関連を指定します。

setDisplayResolutionでは、スケッチで指定するディスプレイ上での端(左および上)から端(右および下)までの値を指定します。例えば1280×1024ドットのディスプレイを使用し、スケッチでも横方向を0〜1279、縦方向を0〜1023で指定したい場合には、
Digitizer.setDisplayResolution(0, 0, 1279, 1023);
と指定します。
他の例として、例えばディスプレイのドット数に関係なく、
Digitizer.setDisplayResolution(0, 0, 100, 100);
と指定すれば、縦横とも画面全体を0〜100の範囲で指定するようになります。例えばこの設定の場合に
Digitizer.move(50, 50);
と指定すればポインタは画面中央に移動します。

setLogicalResolutuinは、Arduinoから送信するデータの最小・最大値を指定します。これはよくわかりませんが、環境によって画面上のピクセル数と一致する場合や0〜32767になる場合があるようです(同じ機器でも表示するモニタによって異なったりするようです)。いろいろ試してみて意図する位置にポインタを移動できる値に設定してください。以下、経験上の値です。
Windowsの場合は、
Digitizer.setLogicalResolution(0, 0, <画面の横方向ドット数-1>, <画面の縦方向ドット数-1>);
または、
Digitizer.setLogicalResolution(0, 0, 32767, 32767);
のいずれかを指定してみてください。
Macの場合は実際の画面よりも広い大きさの仮想的なサイズを持っているようで、私の環境では、
Digitizer.setLogicalResolution(2460, 2460, 30306, 30306);
と指定し、あとはsetDisplayResolutionで実際の画面のドット数を指定することで、move(x,y)で指定したピクセル位置にポインタを移動させることができました。

Windowsの場合、ポインタを移動させるとポインタの形状がデジタイザ用のものに変わってしまうようです。通常の矢印のポインタに戻したいようであれば、移動等の後に、
Mouse.move(1,0);
Mouse.move(-1,0);
等を追加して、マウスを微妙に動かした事にすれば戻るようです。



home | Elec top

inserted by FC2 system