PICでUSBを!(知識ゼロからのスタートです)
PIC18F14K50のUSB機能を100%自前のソフトで制御する試みです。しかもアセンブラで!
当記事は2009年12月から「TTLでCPUをつくろう!」というタイトルの もとにほとんど毎日連載をしてきたものを再編集したものです。 2011.7.10

前へ
次へ
目次へ戻る
ホームページトップへ戻る
☆C++でUSB(HID)アクセスプログラムを作成

PIC側のプログラムはC18コンパイラを使わずPICアセンブラで作成しますが、パソコン側のUSB(HID)アクセスプログラムはBorland C++コンパイラで作成します。
しかしこれがまた難物で悪戦苦闘の連続でありました。

[第48回]

●HIDテストプログラム(HIDデバイス検出)のまとめ

前回までで、やっとUSBに接続されているHIDデバイスのProductIDとVenderIDが検出できるようになりました。
そのテストでは秋月のPICプログラマとともに、私がつくった試作回路(PIC18F4550回路)も接続して、ともに検出できることを確認しました。
ここから先は、ProductIDとVenderIDの検出に使ったhandleをそのまま使って、ターゲットデバイスに対してWriteFile関数とReadFile関数によって、データの送受信を行う部分の作業をしていくことになります。

ですけれど、やっとHIDデバイスの検出ができた、とは言っても、それはWindowsマシンの側のソフトができた、と言う話で、相手側のPICのプログラムについては、何もまだ説明をしておりません。
Windowsマシンの側のプログラムについてだけ、どんどん先に進んでいっても、相手側のプログラムをどう作っていくのかも説明しなければ、実際のところ、何もできないのと同じです。

ということで、やっとHIDデバイスの検出ができて、一区切りつきましたので、これから先はしばらくの間、いよいよPICの側のプログラムについて説明をしていきたいと思います。

その前に、今まで説明をしてきましたHIDテストプログラム(HIDデバイス検出)を簡単にまとめて整理しておくことにします(そうしておきませんと、また全部すっかりきれいに忘れてしまいます)。

ソースプログラムは、[第39回]にあります。
ですが、その後にWindowsXPでUSBマウスが検出できないことがわかり、そのための対策を書き加えました([第47回])ので、一部が変更になりました。
ですので、前回の修正を加えた、現在までのところの最終リストをまずお見せしたうえで、簡単な説明をしていくことにいたします。

●HIDテストプログラム(HIDデバイス検出)のソースリストです

//HID sample
//
//10/1/18 10/1/19 1/20 1/29
//2/6 2/7 4/5 4/7 4/9
//from hidtest1_8_b
//
#include <windows.h>
#include <setupapi.h>
extern "C" {
#include "hidsdi.h"
}
#include <iostream.h>
#pragma comment(lib, "hid.lib")

void main( void )
{
        cout << hex;
//
//   HIDclass識別子取得
//
        GUID hidGuid;
        HidD_GetHidGuid(&hidGuid);
//
//      HIDデバイス情報セット取得
//
        HDEVINFO devinf;             
        devinf = SetupDiGetClassDevs(&hidGuid, NULL, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
        cout <<"devinf="<<devinf<<endl;
//
//   個別デバイス情報の構造体の取得
//
        SP_DEVICE_INTERFACE_DATA spid;
        spid.cbSize = sizeof(spid);
//
        for( int index = 0; ; index++ )
  {
        cout<<"search"<<endl;
        if(!SetupDiEnumDeviceInterfaces(devinf, NULL, &hidGuid, index, &spid))
                {
                cout<<"end"<<endl;
                break;
                }
        cout<<"index="<<index<<endl;
//
//      デバイスインターフェイスの詳細情報の取得
//
        unsigned long size;
        SetupDiGetDeviceInterfaceDetail( devinf, &spid, NULL, 0, &size, 0 );
        PSP_INTERFACE_DEVICE_DETAIL_DATA dev_det = PSP_INTERFACE_DEVICE_DETAIL_DATA( new char[size] );
        dev_det->cbSize=sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
//
        SetupDiGetDeviceInterfaceDetail( devinf, &spid, dev_det, size, &size, 0 );
        cout << dev_det->DevicePath << endl;
//
//      ファイルハンドルの取得
//
        HANDLE handle = CreateFile( dev_det->DevicePath, GENERIC_READ|GENERIC_WRITE,
 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        if(handle==INVALID_HANDLE_VALUE){cout<<"INVALID_HADLE"<<endl;continue;}
//
//      VenderIDとProductIDの取得
//
        HIDD_ATTRIBUTES attr;
        HidD_GetAttributes( handle, &attr ) ;
        cout<<"VenderID: " <<attr.VendorID <<endl;
        cout<<"ProductID: " <<attr.ProductID<<endl;
        cout<<endl;
//
        CloseHandle(handle);
   }
}

このプログラムは、ema log(http://emaame.com/20061227.html)様のサイトにありましたソースリストをもとに作成させていただきました。ema様には感謝申し上げます。

●MSDNのHIDclass関数情報

WindowsでHIDデバイスを扱うプログラムを書くためには、マイクロソフトが提供しているHIDclassAPIを利用することになります。
HIDclassAPIは、HID.DLL(HIDダイナミックリンクライブラリ)として¥windows¥systemフォルダに含まれています。
WindowsAPIの使い方などの情報は、マイクロソフトのMSDN情報サイトにあるはずなのですが、HIDclassAPIに関しては、どういうわけか、検索しても出てきません。

んなわけはないだろーよ、ということでしつこく検索してみましたら、どうやらHIDclassAPIについては、日本語のMSDNサイトには無い、ということのようです。
まーなんでしょうか、日頃先進国だの技術大国だのといばっていましても、所詮日本語などは、ハバ、なのですよねえ。
あ。ハバはナゴヤ弁でした。
ナゴヤ弁では、「仲間はずれ」にする、ということを、ハバにする、と申します。

んではということで、そのハバ、ではない、英語のMSDNサイト(http://msdn.microsoft.com/en-us/library/ff538865.aspx)を訪問いたしました。




●HIDclass識別子取得

さてでは、MSDNを参照しながら説明をしていくことにいたします。
最初はhidGuidの取得です。
HidD_GetHidGuid関数を使います。

        GUID hidGuid;
        HidD_GetHidGuid(&hidGuid);

Guidとはなんぞや、ということなのですが、ひとつしか存在しない識別子のことなのだそうです。
ああ。識別子か、とわかったような気になってしまいますが、その実なんにもわかりません。
ただ、名前が代わっただけです。
よくはわからないのですけれど、USBに接続されているHIDデバイスの情報を得るためには、まずこのhidGuidなる識別子を取得しなさい、ということのようです。
hidGuidは、HIDデバイス情報倉庫の扉を開けるキーのようなもののようです。
HidD_GetHidGuid関数をCallすると、hidGuidバッファへのポインタが返されます。

●HIDデバイス情報セット取得

上で取得したhidGuidを使って、HIDデバイス情報セットを取得します。
SetupDiGetClassDevs関数をCallします。

        HDEVINFO devinf;             
        devinf = SetupDiGetClassDevs(&hidGuid, NULL, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
        cout <<"devinf="<<devinf<<endl;

この関数は日本語のMSDNサイトにあります。
でも、その説明を読んでもよくわかりません。
第1パラメータに&hidGuidを置くことと、第4パラメータ(フラグ)に、DIGCF_PRESENT | DIGCF_DEVICEINTERFACEを指定する、ということだけは、まあなんとかわかったような、です。
DIGCF_PRESENTを指定すると、現在存在しているデバイス情報だけが返されます。
またDIGCF_DEVICEINTERFACEを指定したときは、第1パラメータはインターフェイスクラスGuidであることを示す、とあります。
おそらくhidGuidはインターフェイスクラスGuidなのでありましょう。
第2パラメータのNULL、第3パラメータの0については、全くもってわかりませんが、あちこち参照しますと、ここはこうしておきなさいよ、ということらしいので、そのようにしてあります。
SetupDiGetClassDevs関数をCallすると、HIDデバイス情報セットのハンドルdevinfoが得られます。
このハンドルを使って、HIDデバイス情報セットの中から、個々のデバイス情報を読み出します。

●個別デバイス情報の構造体の取得

個々のデバイス情報を取得するには、SetupDiEnumDeviceInterfaces関数をCallします。

        SP_DEVICE_INTERFACE_DATA spid;
        spid.cbSize = sizeof(spid);
//
        for( int index = 0; ; index++ )
  {
        cout<<"search"<<endl;
        if(!SetupDiEnumDeviceInterfaces(devinf, NULL, &hidGuid, index, &spid))
                {
                cout<<"end"<<endl;
                break;
                }
        cout<<"index="<<index<<endl;

この関数も日本語のMSDNサイトにあります。
この説明も、なんだか、わかったような、わからないような、です。
この関数は、与えられたデバイス情報セットから、指定したindexにしたがって、個別デバイス情報の構造体を返します。
index=0からスタートし、この関数をcallして、デバイス情報体を得ながら、indexをインクリメントして、それを続けます。
デバイス情報がなくなって関数のCallが失敗するまで、この作業を続けます。

第1パラメータは、上で得たHIDデバイス情報セットのハンドルdevinfoです。
第2パラメータはNULLにします。
第3パラメータには最初に取得したHIDクラスGuidへのポインタ&hidGuidを指定します。
なぜここにまたHIDクラスGuidが必要なのか、わかりません。
第4パラメータは0から始まるindexを指定します。
第5パラメータには、関数が成功すると、個別のデバイス情報の構造体が書き込まれたバッファSP_DEVICE_INTERFACE_DATA spidへのポインタが与えられます。

よくわからないのですけれど、この関数をCallする前に、cbSizeメンバをsizeof(SP_DEVICE_INTERFACE_DATA)に設定しなければならない、のだそうです。
それで、
spid.cbSize = sizeof(spid);
としています。

●デバイスインターフェイスの詳細情報の取得

上のSetupDiGetClassDevs関数をCallすることで、デバイスの詳細情報は得られた、と思いましたが、そうではないようです。
うむむ。
よくわからんなあ。
SetupDiGetClassDevs関数によって得られたデバイス情報の構造体、つうのを利用するのに、また関数が必要なのだそうです。
今度はSetupDiGetDeviceInterfaceDetail関数をCallします。

        unsigned long size;
        SetupDiGetDeviceInterfaceDetail( devinf, &spid, NULL, 0, &size, 0 );
        PSP_INTERFACE_DEVICE_DETAIL_DATA dev_det = PSP_INTERFACE_DEVICE_DETAIL_DATA( new char[size] );
        dev_det->cbSize=sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
//
        SetupDiGetDeviceInterfaceDetail( devinf, &spid, dev_det, size, &size, 0 );
        cout << dev_det->DevicePath << endl;

この関数も日本語のMSDNサイトにあります。
この関数もよくわかりません。
しかも、この関数は2回Callしなければならない、のだそうです。
最初のCallで必要なバッファのサイズが得られ、次のCallで、その得られたサイズのバッファに必要な情報が書き込まれる、ということなのだそうですが、そのあたりのテクニックはまったくもって理解不能で、ひとさまに説明申し上げるレベルではありません。
で、ここはこのように書くのだそうです、とだけ書きまして、次に行ってしまうことといたします。
とにかく、そのようにいたしますと、デバイス情報の詳細が得られる、のだそうであります。
ではそこで得られたデバイスの詳細情報をさっそく読んでみましょう、と言いたいところなのですが、なぜか、まだそのままでは読むことができないようであります。
ここでは、得られた詳細情報なるものの中から、その先に進むためのデバイスパスなるもの(dev_det->DevicePath)だけを利用します。
むむむ。
まったくわかりませんが、なぜこんなややこしいことになっておるのでありましょう。
ほんと、疲れてしまいます。

●ファイルハンドルの取得

デバイスパスを使って、やっとHIDファイルをアクセスするためのハンドルが手に入ります。
前回([第47回])でも紹介しました、CreateFile関数を使います。

        HANDLE handle = CreateFile( dev_det->DevicePath, GENERIC_READ|GENERIC_WRITE,
 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        if(handle==INVALID_HANDLE_VALUE){cout<<"INVALID_HADLE"<<endl;continue;}

今回の最初のところでも書きましたように、CreateFile関数を使って、やっとのことで得られたファイルハンドルを使って、WriteFile関数でデータを送信し、ReadFile関数でデータを受信することができます。
でも、それはまだまだ先の話です。

まずは、そのようにして得られたファイルハンドルが、ターゲットデバイスのものかどうかを確認しなければなりません。
つまり、まずはVenderIDとProductIDを取得して、確認する必要があります。

●VenderIDとProductIDの取得

VenderIDとProductIDは、ファイルハンドルを使って、HidD_GetAttributes関数をCallすることで得られます。

        HIDD_ATTRIBUTES attr;
        HidD_GetAttributes( handle, &attr ) ;
        cout<<"VenderID: " <<attr.VendorID <<endl;
        cout<<"ProductID: " <<attr.ProductID<<endl;
        cout<<endl;

HidD_GetAttributes関数は日本語のMSDNサイトにはありません。
上で紹介しました英語のMSDNサイト(http://msdn.microsoft.com/en-us/library/ff538865.aspx)にあります。
HidD_GetAttributes関数をCallすると、HIDD_ATTRIBUTES構造体attrのポインタが得られます。
その構造体のメンバattr.VendorIDとattr.ProductIDが、目的のVenderIDとProductIDです。

ちょいと簡単に、というつもりで書き始めたのですけれど、なかなかそんな具合にはいきませんでした。
CPUをつくろう!第475回(2010.4.11upload)を再編集

PICでUSBを![第48回]
2011.7.10upload

前へ
次へ
目次へ戻る
ホームページトップへ戻る