USBを使ってみよう その1

USB対応しているPICが増えてきたのでUSBを使う実験をします。C言語を使用した例は数多くあってつまらないのでPICのプログラム(ファームウェア)もパソコン側のプログラムもBASICを使用してみることにします。当方、C言語の方が得意なのですがBASICの方が好きなのです。このページでは実用になる物を作るのではなく雛形として役に立つ物を作ります。現時点考えているテーマは以下の3つです。

1.パソコン側のプログラムでPICに接続されたLEDをON,OFFするLEDリモート操作装置
2.パソコンから1文字送ると、それと全く同じ文字をそのまま返送してくるエコーサーバー
3.大量の2バイトデータを送り込むと、その合計値を返送してくる足し算装置 <-これ没(2011/09/21)

最終目標はPICkit2の様に自分自身のファームウェアをアップデート可能な装置の開発ですが、その前段階としてこの3つを実験してみます。3つめのテーマは大量データの送受信の実験です。

使用予定の環境は以下の通りです
 ・MPU:PIC18F2550
 ・ファームウェア開発言語:mikroBASIC V6(2009年10月現在 2つ前の旧バージョン)
 ・パソコン側プログラム:VisualBASIC 2005 Express Edition

mikroBASICは有料ソフトです。無料版もあるのですが、USB機器の開発は無料版の制限を超えてしまいます。


2008-03-12

先ずは、mikroBASICのヘルプにある回路とサンプルコードを動かしてみることにします。PIC18F4550はUSB接続するときの定番の様ですが、秋月電子で売ってないので18F2550を使用することにします。

PICT6483.jpg

mikroBASICのヘルプにある回路

2008-03-14

USBを使用した電子工作の書籍はたくさんありますが「うんちく系」や「わかっている人が解っている人のために書いた」ものが多く、その仕組みを機器を開発する上でどのように使用するのかが殆ど解りません。7、8冊の本を書店で立ち読みしましたが唯一この本だけが参考になりそうだったので購入しました。この本のページ数ではUSBを理解するのに十分とは言えませんが著者のホームページにサンプルが多数あるのでそれを読み解くと理解が深まります。この本以外のUSB電子工作本はハードウェアーに偏りすぎですが、この本はソフトウェア寄りです。

USBを使うためにはハードの開発、ファームウェアの開発、パソコン側プログラムの開発が必要になりますが、ハードウェアー部分はmikroBASICのサンプル回路の通りPICなどのMPUにUSBコネクターを接続すれば、USB部分のハード開発はそれでもうおしまいです。あとは全部ソフトの開発です。ソフトの方はどんなAPI(=サブルーチン群)をどんな順番で実行すればいいのかを理解することが重要です。

PICT6487.jpg

¥4600もするが翻訳の程度が良くないのでちょっと読みづらい。助詞の使い方が間違っているので書いてある内容は理解できる。著者のホームページ http://www.lvr.com/ には多数サンプルがあるので、印刷物に出来なかった部分を補間してくれる。

2008-03-17

8MHzのセラロックがないので工作は全く進みません。「周波数が違うとダメです」とサンプルプログラムに書いてあるので部品を入手してから実験に取りかかることにします。その前にパソコン側のソフトについてまとめておきます。

パソコン側はHIDというインターフェースAPIを使います。ヒューマン・インターフェース・デバイスの略でマウスやキーボードがこのインターフェースを使用しています。キーボードやマウス用のインターフェースと言うとUSB装置->パソコンという方向の通信だけしか出来ないようにイメージしてしまいますが、パソコン->USB装置の通信も可能です。接続するとデバイスドライバーのインストールを要求されるUSB装置がありますが、HIDはWindowsに標準的に組み込まれているのでデバイスドライバーのインストールは必要ありません。HIDを使用する理由はとても単純です。mikroBASICがHIDインターフェース用の関数しか備えていないためです。

HIDインターフェースを使うときに必要なAPIは以下の10個のようです。この10個を決められたお作法に従って呼び出せばUSB装置と通信できるはずです。実際に試してみていないので他に必要な物が出てくるかもしれません。

 HidD_GetHidGuid
 HidD_GetAttribute
 
 SetupDiGetClassDevs
 SetupDiEnumDeviceInterface
 SetupDiGetDeviceInterfaceDetail
 SetupDiDestroyDeviceInfoList
 
 CreateFile
 CloseHandle
 WriteFile
 ReadFile

2008-03-21

8MHzのセラロックを購入したので実験開始。回路はとてもシンプル。緑のLEDは電源間に入っているのでUSBケーブルをパソコンに挿した段階で点灯します。それ以外に何かを表示する物は一切付いていません。

PICT6512.jpg

ブレッドボードで組んだ実験回路。ブレッドボードも寿命があるみたいで一部接触抵抗が大きくなっている箇所があります。特に電源は配線が数珠つなぎになっているので抵抗値が50Ωにもなっていました。少しグリグリやって誤魔化してます。

mikroBASICは何度かバージョンアップしたため旧バージョンのサンプルが残っていてどれがまともに動く物だかよくわからなくなっています。最新版をインストールし直せばいいのですが面倒くさかったので適当に見当を付けてHEXファイルを書き込み接続して見るも動かず。

PICT6510.jpg

いろいろ出てくれます

PICT6511.jpg

本当は出ないはずなのですが...

ベンダーID:1234 プロダクトID:0001 です。

PICT6508.jpg

認識したみたいです

早速、PC側と通信してみます。mikroBASICにはHIDターミナルというテストプログラムが付いていて、基本的な動作をこのソフトで確認できます。Communication欄に何か入力してsendボタンを押すと、それがUSB装置に送信され、結果がその下の欄のちょっと広い部分に表示されます。書き込んだサンプルファームウェアは送信した文字列をそのまま送り返してくる物なので(エコーサーバーって言います)入力と出力に同じ物が表示されることになります。

echoserver.gif

mikroBASICに付属してくるHIDターミナルというソフト

実験台ができあがったので、先ずファームウェア側を変更してみます。あるコードを送信するとそれに反応してLEDを点灯させてみることにします。コード0(0x00)を送ると消灯、コード1(0x01)を送ると点灯するようにしてみます。

2008-03-22

ファームウェアのみを数行変更してLEDのON/OFFを実験してみました。
ファイル ファイルタイプ 添付ファイルの解説
ledonoff.wmv MOVIE LEDをON/OFFする実験

2008-03-26

今度はPC側のソフトです。mikroBASICのHIDターミナルはソースが公開されていないので、USBコンプリートの著者のホームページからサンプルを入手して、それを加工してシンプルなプログラムを作りました。

PICT6523.jpg

シンプルなボタン2つだけのプログラム

mikroBASICのHIDターミナルでやったように、LEDのON/OFFの実験
ファイル ファイルタイプ 添付ファイルの解説
ledonoff2.wmv MOVIE LEDをON/OFFする実験

2008-04-07

USB装置の着脱を認識させる仕組みにしてみました。丸印の色が「赤」<->「緑」に変わるようにしてあります。
ファイル ファイルタイプ 添付ファイルの解説
usbattdet.wmv MOVIE USB装置の着脱実験

2008-04-12

エコーサーバーが完成したので掲載しておきます。パソコン側から8バイト送信すると、それをそっくりそのまま返送してくる装置です。処理速度は約5000バイト/秒です。USBの転送能力を全く使い切っていませんが、殆どの装置はこの程度の速度が出れば十分なはずです。

PICT6744.jpg

実験回路の全貌

回路図。X1は8MHzのセラロックです。

usbhidsch.GIF

仕様:

・送信文字欄に8文字入れてSENDボタンを押すと8バイトの文字がUSB装置に送信されます
・送受信は送信回数分だけ繰り返します。例えば送信回数を100とすると「8バイト送信して、8バイト受信する」を100回繰り返します
・受信した文字は右側の四角い欄に表示されます。Clearボタンを押すとこの欄の表示をクリヤできます。
・送信回数の下にある白い物はプログレスパーです。
・装置を着脱すると左上の●の色が変わります。緑は装置が接続されていることを意味し、赤は取り外されていることを意味します。

・USB装置がデータを送信するとき赤いLEDが点灯します。
・黄色いLEDは電源間に入っているのでUSBケーブルを挿せば点灯します。
・緑のLEDはmikroBASICの HID_read が実行されている間点灯します。
 HID_Readはパソコンからデータが送られてこないとゼロバイトを受信して直ぐに返ってきます。
 そのため緑ランプは常に点灯しているように見えます。

PICT6746.jpg

パソコン側プログラムの画面

【エコーサーバープログラム】 プログラムと動いているところ
ファイル ファイルタイプ 添付ファイルの解説
USB_HID_test.zip OTHER ファームウェアプログラム。mikroBASIC(V6.0.0.0)のソース&HEXファイル
USB_SAMPLE.zip OTHER パソコン側プログラム。Visual Basic 2005 Express Editionのソース&実行ファイル一式
usbhidecho.wmv MOVIE パソコン側のプログラムが動いているところ
ファームウェア エコーサーバーメインプログラムの解説
0001  '******************************************************************************
0002 ' microcontroller as 18F2550
0003 '
0004 ' Project as USB_HID_test
0005 ' This project is designed to work with PIC 18F2550
0006 ' You should not make adjustments to device flags and to device clock.
0007 '
0008 ' This code demonstrates how to send/receive a byte via USB.
0009 ' You should disconnect any resistors from PORTC. Connect PIC to the USB on
0010 ' you PC using the following configuration as
0011 ' D+ --- RC5
0012 ' D- --- RC4
0013 ' In order to generate the device descriptor you can use HID terminal
0014 ' provided with mikroBasic compiler. Save the USBdsc.ppas file in the
0015 ' same folder with this project and make sure it is included in the project
0016 ' by means of the keyword "include", as demonstrated below.
0017 '******************************************************************************
0018 program USB_HID_test
0019
0020 include "usbgenhid"
0021 include "USBdsc"
0022
0023 symbol PORT_OUTPUT = 0
0024 symbol PORT_INPUT = 1
0025
0026 dim
0027 k, i, ch as byte
0028 userWR_buffer as byte[64]
0029 userRD_buffer as byte[64]
0030 df as word absolute 15
0031 wr_adr as word
0032 ' , rd_adr
0033 'const text as array[29] of byte = "mikroElektronika Compilers ER"
0034 ' text as byte[31]
0035
0036
0037 '******************************************************************************
0038 ' Main Interrupt Routine
0039 '******************************************************************************
0040 sub procedure interrupt
0041 HID_InterruptProc
0042 end sub
0043 '******************************************************************************
0044
0045
0046
0047 '******************************************************************************
0048 ' Initialization Routine
0049 '******************************************************************************
0050 sub procedure Init_Main
0051 '--------------------------------------
0052 ' Disable interrupts
0053 '--------------------------------------
0054 INTCON = 0 ' Disable GIE, PEIE, TMR0IE,INT0IE,RBIE
0055 INTCON2 = 0xF5
0056 INTCON3 = 0xC0
0057 RCON.IPEN = 0 ' Disable Priority Levels on interrupts
0058 PIE1 = 0
0059 PIE2 = 0
0060 PIR1 = 0
0061 PIR2 = 0
0062
0063 ADCON1 = ADCON1 or 0x0F ' Configure all ports with analog function as digital
0064 '--------------------------------------
0065 ' Ports Configuration
0066 '--------------------------------------
0067 TRISA = 0xFF
0068 TRISB = 0xFF
0069 TRISC = 0xFF
0070
0071 TRISB.0 = PORT_OUTPUT
0072 TRISB.1 = PORT_OUTPUT
0073 PORTB.0 = 0
0074 PORTB.1 = 0
0075
0076 LATA = 0
0077 LATB = 0
0078 LATC = 0
0079 '--------------------------------------
0080 ' Clear user RAM
0081 ' Banks [00 .. 07] ( 8 x 256 = 2048 Bytes )
0082 '--------------------------------------
0083 end sub
0084
0085 '******************************************************************************
0086 ' Main Program Routine
0087 '******************************************************************************
0088
0089 main:
0090 df=0
0091 Init_Main
0092
0093 HID_Enable(@userRD_buffer, @userWR_buffer)
0094 Delay_mS(1000)
0095 Delay_mS(1000)
0096
0097 while true
0098 PORTB.1 = 1 ' LED Green ON
0099 k = HID_Read
0100 PORTB.1 = 0 ' LED Green OFF
0101 if k > 0 then
0102 for i = 0 to k - 1
0103 userWR_buffer[i] = userRD_buffer[i]
0104 next i
0105 PORTB.0 = 1 ' LED Red ON
0106 HID_Write(@userWR_buffer, k)
0107 PORTB.0 = 0 ' LED Red OFF
0108 end if
0109 wend
0110 HID_Disable
0111
0112 '** This code is necessary in order to make linker load the variables
0113 ' and HID_InterruptProc routine.
0114 '** In real time it will never execute.
0115 if false then
0116 HID_InterruptProc
0117 wr_adr = @DeviceDescr
0118 wr_adr = @ConfigDescr
0119 wr_adr = @InterfaceDescr
0120 wr_adr = @HID_Descriptor
0121 wr_adr = @EP1_RXDescr
0122 wr_adr = @EP1_TXDescr
0123 wr_adr = @HID_ReportDesc
0124 wr_adr = @LangIDDescr
0125 wr_adr = @ManufacturerDescr
0126 wr_adr = @ProductDescr
0127 wr_adr = @StrUnknownDescr
0128 end if
0129 end.
0130
行番号
解説
97~109行目
実際に書いたのはこの部分だけです、それ以外はmikroBASICのサンプルそのままです。
99行目
パソコンから送られてきたデータを受信します。変数kはゼロか8のどちらかになります。パソコンから送られてきたデータが無い場合、送られてくるのを待つのではなくkがゼロになります。
99行目
受信した文字を送信バッファーにCOPYします
106行目
送信バッファーにセットした文字をパソコンに向けて送信します。
110行目
このサンプルプログラムでは、この行は永久に実行されません。97行目のwhileが無限ループになっているからです。
ファームウェア デバイスデスクリプタソース プログラムの解説
0001  module USBdsc
0002
0003 include "HIDconstant"
0004
0005 '******************************************************************************
0006 ' The number of bytes in each report,
0007 ' calculated from Report Size and Report Count in the report descriptor
0008 '******************************************************************************
0009 const HID_INPUT_REPORT_BYTES = 8
0010 const HID_OUTPUT_REPORT_BYTES = 8
0011
0012 const HID_FEATURE_REPORT_BYTES = 2
0013 '******************************************************************************
0014 ' Byte constants
0015 '******************************************************************************
0016
0017 以下略
行番号
解説
9~10行目
これが送受信するデータのサイズです。このソースはmikroBASICのHIDターミナルで生成します。この値をパソコン側のソフトとあわせなければなりません。mikroBASICのHIDターミナルはこの値が何であっても動作しますが、それはパソコン側から、このUSB装置のこの値(=レポートサイズ)を調べることが出来るからです。

送受信するデータ構造はHIDターミナルのHIDデスクリプタ生成画面を使います。全項目を埋めてCreateボタンを押すとUSBdsc.pbasという名前のmikroBASICのソースファイルが生成されます。これをメインプログラムからincludeします。

VID:
  ベンダーIDです。USBを仕切っている団体がUSB装置メーカーに割り当てます。
  自作装置の場合、自分の好きな4桁16進数を入力します
PID:
  プロダクトIDです。ベンダーIDとプロダクトIDでUSB装置を識別します
  これも自分の好きな番号を割り当てます
  必ずしもベンダーIDとプロダクトIDで識別する必要はありませんが、これが一番簡単な方法です
  1台のパソコンに複数同じUSB装置が接続出来る場合、この方式では不足ですが自作機器なら問題ないでしょう。
Report Length Input:
  USB装置が受信するデータのサイズです
  パソコン側プログラムとサイズ合わせをする必要があります
  あってないとパソコン側プログラムのReadFile関数がエラーを返してきます
Report Length Output:
  USB装置が送信するデータのサイズです
  サイズ合わせやエラーに関してはInputと同様です
Bus power:
  パソコンがUSB装置に供給しなければいけない電力を50mA単位で指定します
  既にUSBハブにたくさんUSB装置が接続されていて、ここで指定した電力供給能力が確保できないときは
  ハブ側が電力供給不可であるとしてUSB装置への電力供給を却下してきます
Endpoints pooling int:
  USBはパソコン側が一定間隔で常にUSB装置に送受信するデータが有るか無いか問い合わせてくる仕組みになっています
  その問い合わせ時間間隔をここで指定します。
  初期値として1msが入力されていますが、通常初期値のままでかまいません。
Strings Vendor Name:
  調べてませんが、たぶんこのベンダー名はパソコン側から読み出すことが出来ます
Strings Product Name:
  同上です

hidterm2.gif

HIDターミナルのデスクリプタ生成画面

パソコン側 エコーサーバー メインプログラムの解説
0001  '---------------------------------------------------------------------------- 2008-04-07 ------------
0002 ' USB HIDインターフェースサンプルプログラム
0003 '----------------------------------------------------------------------------------------------------
0004 Option Strict On ' 型の定義が厳密になるようにする
0005 Option Explicit On ' 変数を定義無しに使用できないようにする宣言
0006
0007 '----------------------------------------------------------------------------------------------------
0008 ' プログラムメイン
0009 '----------------------------------------------------------------------------------------------------
0010 Public Class Form1
0011 Public Const MY_VENDER_ID As Short = &H1234
0012 Public Const MY_PRODUCT_ID As Short = &H1
0013
0014 Public Const MY_REPORT_LENGTH As Integer = 8
0015
0016 Dim HIDHandle As Integer
0017 Dim Send_Buffer(MY_REPORT_LENGTH) As Byte
0018 Dim Recv_Buffer(MY_REPORT_LENGTH) As Byte
0019
0020 '-------------------------------------------------------------------
0021 ' プログラムを終了したときの処理
0022 '-------------------------------------------------------------------
0023 Private Sub Form1_FormClosed( _
0024 ByVal sender As Object, _
0025 ByVal e As System.Windows.Forms.FormClosedEventArgs) _
0026 Handles Me.FormClosed
0027
0028 hid_unregister_notification()
0029 hid_close(HIDHandle)
0030 End Sub
0031
0032 '-------------------------------------------------------------------
0033 ' プログラムを開始したときの処理
0034 ' USB装置をOPENしている。OPENできなかったときは
0035 ' フォーム上の●を赤くして、装置が繋がっていない旨の表示をしている
0036 '-------------------------------------------------------------------
0037 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
0038 hid_register_notification(Me.Handle)
0039 HIDHandle = hid_init(MY_VENDER_ID, MY_PRODUCT_ID)
0040
0041 If HIDHandle <> INVALID_HANDLE_VALUE Then
0042 Label1.ForeColor = Color.Green
0043 Else
0044 Label1.ForeColor = Color.Red
0045 End If
0046 End Sub
0047
0048 '-------------------------------------------------------------------
0049 ' SENDボタンを押したときの処理
0050 ' ファームウェアは8バイト受信すると、それをそっくりそのまま返送する
0051 ' プログラムになっている。
0052 '-------------------------------------------------------------------
0053 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
0054 Dim count As Integer
0055 Dim i As Integer
0056 Dim str As String
0057
0058 count = Integer.Parse(TextBox2.Text)
0059 ProgressBar1.Maximum = count
0060 For i = 1 To count
0061 Send_Buffer = System.Text.Encoding.UTF8.GetBytes(TextBox1.Text)
0062 hid_puts(HIDHandle, Send_Buffer, MY_REPORT_LENGTH)
0063 hid_gets(HIDHandle, Recv_Buffer, MY_REPORT_LENGTH)
0064 str = System.Text.Encoding.UTF8.GetString(Recv_Buffer)
0065 ListBox1.Items.Insert(0, str)
0066 ProgressBar1.Value = i
0067 Next
0068 End Sub
0069
0070 '-------------------------------------------------------------------
0071 ' USB装置が着脱されるとこれが実行される
0072 '-------------------------------------------------------------------
0073 Protected Overrides Sub WndProc(ByRef m As Message)
0074 Try
0075 If m.Msg = WM_DEVICECHANGE Then
0076 OnDeviceChange(m)
0077 End If
0078 MyBase.WndProc(m)
0079 Catch ex As Exception
0080 MsgBox("exception : device change")
0081 End Try
0082 End Sub
0083
0084 '-------------------------------------------------------------------
0085 ' USB装置が着脱されるとこれが実行される
0086 ' WndProc経由で間接的に呼び出される
0087 ' このサンプルでは フォーム上のテキスト「●」の色をUSB装置の
0088 ' 着脱状態に応じて変更している。
0089 '  赤いとき:装置なし
0090 '  緑のとき:装置あり
0091 '-------------------------------------------------------------------
0092 Private Sub OnDeviceChange(ByVal m As Message)
0093 Try
0094 If (m.WParam.ToInt32 = DBT_DEVICEARRIVAL) Then
0095 If HIDHandle = INVALID_HANDLE_VALUE Then
0096 HIDHandle = hid_init(MY_VENDER_ID, MY_PRODUCT_ID)
0097 If HIDHandle <> INVALID_HANDLE_VALUE Then
0098 Label1.ForeColor = Color.Green
0099 End If
0100 End If
0101 ElseIf (m.WParam.ToInt32 = DBT_DEVICEREMOVECOMPLETE) Then
0102 If hid_mydevice_notification(m) Then
0103 If hid_close(HIDHandle) Then
0104 Label1.ForeColor = Color.Red
0105 HIDHandle = INVALID_HANDLE_VALUE
0106 End If
0107 End If
0108 End If
0109 Catch ex As Exception
0110 MsgBox("exception : On device change")
0111 End Try
0112 End Sub
0113
0114 Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
0115 ListBox1.Items.Clear()
0116 End Sub
0117 End Class
0118
0119 '----------- E N D O F P R O G R A M --------------------------------------------------------
行番号
解説
1~最終行
コメントを読んでください。
パソコン側 HID共通関数群 の解説
0001  '---------------------------------------------------------------------------- 2008-04-07 ------------
0002 ' USB HIDインターフェース汎用関数郡
0003 '
0004 ' この汎用関数郡はUSBを使用するプログラムに取り込んで使用します
0005 ' 取り込み方は「ソリューションエクスプローラー」で「追加」-「新しい項目」-「モジュール」で新しい
0006 ' モジュールを作成してこのソースを全て上書きすればOK
0007 ' (自動生成される Module ModuleX .... End Module の部分も含め上書きして良い)
0008 '
0009 ' 概要:プログラムは以下6つの関数郡を使用してUSB装置(HID装置)と通信をします
0010 ' hid_init :HID装置を初期化する
0011 ' hid_close :HID装置を使用を終了する
0012 ' hid_puts :HID装置に文字列を送信する
0013 ' hid_gets :HID装置から文字列を受信する
0014 ' hid_register_notification :HID装置の着脱通知を受け取れるようにする
0015 ' hid_mydevice_notification :受け取ったの着脱通知が自分のOPENしているUSB装置であるか否かを調べる
0016 ' hid_unregister_notification:HID装置の着脱通知を解除する
0017 '
0018 ' 主なお作法(こんなお作法でプログラムを書いてください Form1.vbはそのサンプルです)
0019 '   1.プログラムを起動したら hid_register_notification でUSB装置の着脱を検知できるようにする
0020 '   2.hid_init で目的のUSB装置をOPENする
0021 '   3.hid_puts と hid_gets でUSB装置とデータの送受信をする
0022 '   4.通信が不要になったら hid_close でUSB装置をCLOSEする
0023 '   5.プログラムを終了するときに hid_unregister_notification で着脱検知を解除する
0024 '  この標準的な処理の流れに加え
0025 '   もし、装置が取り外されたら...
0026 '      hid_mydevice_notification で自分の装置であるかを調べてから hid_close する
0027 '   もし、装置が取り付けられたら...
0028 '      すでに装置がOPENしていないことをHIDhandleなどで確認してから hid_init する
0029 '
0030 ' この共通関数郡の制約事項:
0031 ' 1.プログラムは同時に2つ以上のUSB装置を使用できません
0032 '   デバイスパス名をグローバル変数で保持しているということから来るものです
0033 '   きれいにクラスを作ればこの制約は容易に解除できますが、その必要性は薄いでしょう
0034 ' 2.データは最大でも64バイト単位に細切れにして送信する必要があります
0035 '----------------------------------------------------------------------------------------------------
0036 Option Strict On ' 型の定義が厳密になるようにする
0037 Option Explicit On ' 変数を定義無しに使用できないようにする宣言
0038
0039 ' Marshal を使用できるようにする宣言
0040 ' マーシャルというのは.Netプログラム(このプログラムがそれだ)と
0041 ' 非.Net関数(この行以下に DllImport... で定義してあるものがそれだ)間で構造体や配列データの
0042 ' 受け渡しをするときに必要になる。
0043 Imports System.Runtime.InteropServices
0044
0045 '----------------------------------------------------------------------------------------------------
0046 ' 低レベルファイル入出力の定義
0047 ' 低レベルとは「次元が低い」とか「おバカ」とかいう意味ではなく、よりハードウェアに近いという意味だ
0048 ' この部分は定義なので Module ~ End Module までは決まり文句だ、つまり使うときはこれを
0049 ' コピペすれば良い
0050 '----------------------------------------------------------------------------------------------------
0051 Module FileIOApiDeclarations
0052 Public Const FILE_FLAG_OVERLAPPED As Integer = &H40000000
0053 Public Const FILE_SHARE_READ As Short = &H1S
0054 Public Const FILE_SHARE_WRITE As Short = &H2S
0055 Public Const GENERIC_READ As Integer = &H80000000
0056 Public Const GENERIC_WRITE As Integer = &H40000000
0057 Public Const INVALID_HANDLE_VALUE As Integer = -1
0058 Public Const OPEN_EXISTING As Short = 3
0059 Public Const WAIT_TIMEOUT As Integer = &H102
0060 Public Const WAIT_OBJECT_0 As Short = 0
0061
0062 <StructLayout(LayoutKind.Sequential)> _
0063 Public Structure OVERLAPPED
0064 Dim Internal As Integer
0065 Dim InternalHigh As Integer
0066 Dim Offset As Integer
0067 Dim OffsetHigh As Integer
0068 Dim hEvent As Integer
0069 End Structure
0070
0071 <StructLayout(LayoutKind.Sequential)> _
0072 Public Structure SECURITY_ATTRIBUTES
0073 Dim nLength As Integer
0074 Dim lpSecurityDescriptor As Integer
0075 Dim bInheritHandle As Integer
0076 End Structure
0077
0078 '--------------------------------------------------------------------------------------
0079 ' プログラムを終了させるときにこれを呼び出してUSB装置との通信を終了する
0080 '--------------------------------------------------------------------------------------
0081 <DllImport("kernel32.dll")> _
0082 Function CloseHandle( _
0083 ByVal hObject As Integer) _
0084 As Boolean
0085 End Function
0086
0087 '--------------------------------------------------------------------------------------
0088 ' USB装置をOPENする
0089 ' 最初の引数USB装置のパス名で以下のようにとても長い名前だ。
0090 ' これが最初から解っていれば(デバイスマネージャで調べれば解る)直接これを指定してOPENできる
0091 ' lpFileName引数、ELECOMのUSBマウスの例
0092 '   \\?\hid#vid_056e&pid_000d#6&3b56028e&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
0093 '--------------------------------------------------------------------------------------
0094 <DllImport("kernel32.dll", CharSet:=CharSet.Auto)> _
0095 Function CreateFile( _
0096 ByVal lpFileName As String, _
0097 ByVal dwDesiredAccess As Integer, _
0098 ByVal dwShareMode As Integer, _
0099 ByRef lpSecurityAttributes As SECURITY_ATTRIBUTES, _
0100 ByVal dwCreationDisposition As Integer, _
0101 ByVal dwFlagsAndAttributes As Integer, _
0102 ByVal hTemplateFile As Integer) _
0103 As Integer
0104 End Function
0105
0106 '--------------------------------------------------------------------------------------
0107 ' USB装置からデータを読み出す。
0108 ' 実際の読み出しはWindowsのデバイスドライバーが勝手に読み出してくれるので
0109 ' 読みだしてくれたバッファーに溜まっているデータを取りにいっているだけだ
0110 ' もしバッファーが空なら(データが無ければ)データが溜まるまで待たされる
0111 ' 3番目の引数nNumberOfBytesToRead はファームウェア側に合わせないといけないが
0112 ' パソコン側からHidP_GetCaps 関数を使って調べることも出来る、関数が返した構造体の
0113 ' InputReportByteLengthメンバーがそれだ
0114 ' このサンプルにはその構造体は使用していないので定義していない
0115 '--------------------------------------------------------------------------------------
0116 ' <DllImport("kernel32.dll")> _
0117 ' Function ReadFile( _
0118 ' ByVal hFile As Integer, _
0119 ' ByRef lpBuffer As Byte, _
0120 ' ByVal nNumberOfBytesToRead As Integer, _
0121 ' ByRef lpNumberOfBytesRead As Integer, _
0122 ' ByRef lpOverlapped As Integer) _
0123 ' As Boolean
0124 'End Function
0125 <DllImport("kernel32.dll")> _
0126 Function ReadFile( _
0127 ByVal hFile As Integer, _
0128 ByRef lpBuffer As Byte, _
0129 ByVal nNumberOfBytesToRead As Integer, _
0130 ByRef lpNumberOfBytesRead As Integer, _
0131 ByRef lpOverlapped As OVERLAPPED) _
0132 As Boolean
0133 End Function
0134
0135 '--------------------------------------------------------------------------------------
0136 ' USB装置にデータを書き込む。
0137 '--------------------------------------------------------------------------------------
0138 <DllImport("kernel32.dll")> _
0139 Function WriteFile( _
0140 ByVal hFile As Integer, _
0141 ByRef lpBuffer As Byte, _
0142 ByVal nNumberOfBytesToWrite As Integer, _
0143 ByRef lpNumberOfBytesWritten As Integer, _
0144 ByVal lpOverlapped As Integer) _
0145 As Boolean
0146 End Function
0147
0148 '--------------------------------------------------------------------------------------
0149 ' ReadFileやWriteFileの返り値はBooleanなので、うまくいったか否かしか解らない
0150 ' うまくいかなかった理由をもうちょっと詳しく知りたいときはReadFileやWriteFileに続けてこの関数を
0151 ' 呼び出してそのコードから判断する必要がある。
0152 '--------------------------------------------------------------------------------------
0153 <DllImport("kernel32.dll")> _
0154 Function GetLastError() As Integer
0155 End Function
0156 End Module
0157
0158
0159 '----------------------------------------------------------------------------------------------------
0160 ' HIDインターフェースの定義
0161 ' これらの関数を駆使して通信したいUSB装置のパス名(CreateFileの一つ目の引数)を求めたり
0162 ' USB装置の属性なんかを入手する。
0163 '----------------------------------------------------------------------------------------------------
0164 Module HidApiDeclarations
0165 Public Const HidP_Input As Short = 0
0166 Public Const HidP_Output As Short = 1
0167 Public Const HidP_Feature As Short = 2
0168
0169 <StructLayout(LayoutKind.Sequential)> _
0170 Public Structure HIDD_ATTRIBUTES
0171 Dim Size As Integer
0172 Dim VendorID As Short
0173 Dim ProductID As Short
0174 Dim VersionNumber As Short
0175 End Structure
0176
0177 '--------------------------------------------------------------------------------------
0178 ' バッファー上の全てのInputレポート(USB装置から送信されてきたデータ)を削除する
0179 '--------------------------------------------------------------------------------------
0180 <DllImport("hid.dll")> _
0181 Function HidD_FlushQueue( _
0182 ByVal HidDeviceObject As Integer) _
0183 As Boolean
0184 End Function
0185
0186 '--------------------------------------------------------------------------------------
0187 ' USB装置のベンダーID、プロダクトIDを調べるためにはこの関数を使う
0188 ' 1番目の引数はCreateFileの返り値である HIDHandle だ
0189 '--------------------------------------------------------------------------------------
0190 <DllImport("hid.dll")> _
0191 Function HidD_GetAttributes( _
0192 ByVal HidDeviceObject As Integer, _
0193 ByRef Attributes As HIDD_ATTRIBUTES) _
0194 As Boolean
0195 End Function
0196
0197 '--------------------------------------------------------------------------------------
0198 ' USBの HidGuid なるものを取得するサブルーチンだが WindowsXPなら
0199 ' 毎回同じ文字列の 4d1e55b2-f16f-11cf-88cb-001111000030 が引数にセットされて帰ってくる
0200 ' 呼び出すときは引数には Empty を指定しておく 詳しくはサンプル(プログラムメイン)を見てくれたまえ
0201 ' 俺はWindowsXPしか使わない!!...と断言できる人はこの関数を使わなくてもいいぞ
0202 '--------------------------------------------------------------------------------------
0203 <DllImport("hid.dll")> _
0204 Sub HidD_GetHidGuid( _
0205 ByRef HidGuid As System.Guid)
0206 End Sub
0207 End Module
0208
0209
0210 '----------------------------------------------------------------------------------------------------
0211 ' USB装置は、それを使うプログラムが動いているときでもケーブルを引っこ抜かれる可能性がある
0212 ' これらの定数、構造体定義、および関数は、USB装置の取り外し、取り付けを検知するために使用される
0213 ' また、setupapi.dll の関数はUSB装置を探し出すために利用される
0214 '----------------------------------------------------------------------------------------------------
0215 Module DeviceManagementApiDeclarations
0216 Public Const DBT_DEVICEARRIVAL As Integer = &H8000
0217 Public Const DBT_DEVICEREMOVECOMPLETE As Integer = &H8004
0218 Public Const DBT_DEVTYP_DEVICEINTERFACE As Integer = 5
0219 Public Const DEVICE_NOTIFY_WINDOW_HANDLE As Integer = 0
0220 Public Const WM_DEVICECHANGE As Integer = &H219
0221
0222 Public Const DIGCF_PRESENT As Short = &H2S
0223 Public Const DIGCF_DEVICEINTERFACE As Short = &H10S
0224
0225 <StructLayout(LayoutKind.Sequential)> _
0226 Public Class DEV_BROADCAST_DEVICEINTERFACE
0227 Public dbcc_size As Integer
0228 Public dbcc_devicetype As Integer
0229 Public dbcc_reserved As Integer
0230 Public dbcc_classguid As Guid
0231 Public dbcc_name As Short
0232 End Class
0233
0234 <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
0235 Public Class DEV_BROADCAST_DEVICEINTERFACE_1
0236 Public dbcc_size As Integer
0237 Public dbcc_devicetype As Integer
0238 Public dbcc_reserved As Integer
0239 <MarshalAs(UnmanagedType.ByValArray, ArraySubType:=UnmanagedType.U1, SizeConst:=16)> _
0240 Public dbcc_classguid() As Byte
0241 <MarshalAs(UnmanagedType.ByValArray, sizeconst:=255)> _
0242 Public dbcc_name() As Char
0243 End Class
0244
0245 <StructLayout(LayoutKind.Sequential)> _
0246 Public Class DEV_BROADCAST_HDR
0247 Public dbch_size As Integer
0248 Public dbch_devicetype As Integer
0249 Public dbch_reserved As Integer
0250 End Class
0251
0252 <StructLayout(LayoutKind.Sequential)> _
0253 Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
0254 Dim cbSize As Integer
0255 Dim DevicePath As String
0256 End Structure
0257
0258 <StructLayout(LayoutKind.Sequential)> _
0259 Public Structure SP_DEVICE_INTERFACE_DATA
0260 Dim cbSize As Integer
0261 Dim InterfaceClassGuid As System.Guid
0262 Dim Flags As Integer
0263 Dim Reserved As Integer
0264 End Structure
0265
0266 '--------------------------------------------------------------------------------------
0267 ' 装置が着脱されたことがプログラムに通知されるようにします
0268 ' USB装置が着脱されるとプログラムにメッセージが飛んできますが
0269 ' アプリケーションが必要とするUSB装置であるかまではわからない
0270 '--------------------------------------------------------------------------------------
0271 <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
0272 Function RegisterDeviceNotification( _
0273 ByVal hRecipient As IntPtr, _
0274 ByVal NotificationFilter As IntPtr, _
0275 ByVal Flags As Int32) _
0276 As IntPtr
0277 End Function
0278
0279 '--------------------------------------------------------------------------------------
0280 ' RegisterDeviceNotification で設定した通知要求を解除する
0281 ' 通常この関数はアプリケーションを終了するときに呼び出す
0282 '--------------------------------------------------------------------------------------
0283 <DllImport("user32.dll")> _
0284 Function UnregisterDeviceNotification( _
0285 ByVal Handle As IntPtr) _
0286 As Boolean
0287 End Function
0288
0289 '--------------------------------------------------------------------------------------
0290 ' 現在接続されているUSB装置の一覧を入手する
0291 '--------------------------------------------------------------------------------------
0292 <DllImport("setupapi.dll", CharSet:=CharSet.Auto)> _
0293 Function SetupDiGetClassDevs( _
0294 ByRef ClassGuid As System.Guid, _
0295 ByVal Enumerator As String, _
0296 ByVal hwndParent As Integer, _
0297 ByVal Flags As Integer) _
0298 As IntPtr
0299 End Function
0300
0301 '--------------------------------------------------------------------------------------
0302 ' SetupDiGetClassDevsで取得した情報を開放する
0303 '--------------------------------------------------------------------------------------
0304 <DllImport("setupapi.dll")> _
0305 Function SetupDiDestroyDeviceInfoList( _
0306 ByVal DeviceInfoSet As IntPtr) _
0307 As Integer
0308 End Function
0309
0310 '--------------------------------------------------------------------------------------
0311 ' 現在接続されているUSB装置の一覧から特定のUSB装置の情報を入手する
0312 '--------------------------------------------------------------------------------------
0313 <DllImport("setupapi.dll")> _
0314 Function SetupDiEnumDeviceInterfaces( _
0315 ByVal DeviceInfoSet As IntPtr, _
0316 ByVal DeviceInfoData As Integer, _
0317 ByRef InterfaceClassGuid As System.Guid, _
0318 ByVal MemberIndex As Integer, _
0319 ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) _
0320 As Boolean
0321 End Function
0322
0323 '--------------------------------------------------------------------------------------
0324 ' デバイスパス名を入手する。この関数を使用して得たデバイスパス名を使用して
0325 ' USB装置をCreateFile関数でOPENします。
0326 ' この関数は2回呼び出さないと目的のパス名を入手できません
0327 '--------------------------------------------------------------------------------------
0328 <DllImport("setupapi.dll", CharSet:=CharSet.Auto)> _
0329 Function SetupDiGetDeviceInterfaceDetail( _
0330 ByVal DeviceInfoSet As IntPtr, _
0331 ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, _
0332 ByVal DeviceInterfaceDetailData As IntPtr, _
0333 ByVal DeviceInterfaceDetailDataSize As Integer, _
0334 ByRef RequiredSize As Integer, _
0335 ByVal DeviceInfoData As IntPtr) _
0336 As Boolean
0337 End Function
0338 End Module
0339
0340
0341 '----------------------------------------------------------------------------------------------------
0342 ' HIDインターフェースをシリアルポートのように簡単に使えるようにした関数郡
0343 '----------------------------------------------------------------------------------------------------
0344 Module usb_hid
0345 Dim DeviceNotificationHandle As IntPtr
0346 Dim DevicePathName As String
0347
0348 '---------------------------------------------------------------------------------------
0349 ' USB装置に文字列を送信する
0350 '
0351 ' HIDにはレポートという概念があり、それは簡単に言うと送受信する一塊のデータです
0352 ' レポートにはレポート長、つまり送るデータのサイズがあり、異なる長さのレポートを使用したい場合
0353 ' 例えばレポート番号1は長さ3バイト、レポート番号2は長さ5バイトといったように取り決めて
0354 ' USBのケーブルを流れるデータ量を節約します
0355 ' 多くの自作回路では、これは面倒なことなのでレポートを1種(レポート番号は常にゼロ)にして
0356 ' そのデータの中身で何が送られてきたのかを判断するプログラムを書いたほうがシンプルです
0357 ' 例えば...
0358 '  1バイト目が1だったらUSB装置が検知した温度でそれは1バイトであるとか
0359 '  2バイト目が2だったら時刻でそれは8バイトであるとかいった具合です
0360 '  このような場合はレポート長を8バイトとしてしまい、温度を送るときは残り7バイトを
0361 '  読み捨てるようにします
0362 '
0363 ' 引数1:USB装置のハンドル
0364 ' 引数2:送信する文字列
0365 ' 引数3:送信する長さ(固定長、つまり送信するたびに毎回同じ長さを指定する)
0366 ' 返り値:受信がうまくいったか否か
0367 ' 制約事項:64バイト以下のデータしか送信できません
0368 '---------------------------------------------------------------------------------------
0369 Public Function hid_puts( _
0370 ByVal handle As Integer, _
0371 ByVal buffer() As Byte, _
0372 ByVal len As Integer) _
0373 As Boolean
0374
0375 Dim OutputReportBuffer(64) As Byte
0376 Dim Result As Boolean
0377 Dim NumberOfBytesWritten As Integer
0378 Dim i As Integer
0379
0380 OutputReportBuffer(0) = &H0 ' レポートIDは常にゼロ
0381 For i = 1 To len
0382 OutputReportBuffer(i) = buffer(i - 1)
0383 Next
0384 Result = WriteFile(handle, OutputReportBuffer(0), len + 1, NumberOfBytesWritten, 0)
0385 If Result = False Then
0386 MsgBox("Last Error Code = " & GetLastError() & " written " & NumberOfBytesWritten & " bytes")
0387 End If
0388
0389 Return Result
0390 End Function
0391
0392 '---------------------------------------------------------------------------------------
0393 ' USB装置から文字列を受信する
0394 '
0395 ' 引数1:USB装置のハンドル
0396 ' 引数2:受信した文字列
0397 '     ここで指定した変数に文字が埋め込まれて返される
0398 ' 引数3:受信する文字数
0399 '     受信した文字数ではない。「XXバイトよこせ」という意味であり
0400 '     ファームウェア側もここで指定したバイト数を送信しなければならない
0401 ' 返り値:受信がうまくいったか否か
0402 ' 制約事項:64バイト以下のデータしか受信できません
0403 '---------------------------------------------------------------------------------------
0404 Public Function hid_gets( _
0405 ByVal handle As Integer, _
0406 ByVal buffer() As Byte, _
0407 ByVal len As Integer) _
0408 As Boolean
0409
0410 Dim InputReportBuffer(64) As Byte
0411 Dim Result As Boolean
0412 Dim NumberOfBytesRead As Integer
0413 Dim i As Integer
0414 Dim HIDOverlapped As OVERLAPPED
0415
0416 Result = ReadFile(handle, InputReportBuffer(0), len + 1, NumberOfBytesRead, HIDOverlapped)
0417 If Result = False Then
0418 MsgBox("Last Error Code = " & GetLastError() & " read " & NumberOfBytesRead & " bytes")
0419 End If
0420 For i = 0 To len - 1
0421 buffer(i) = InputReportBuffer(i + 1)
0422 Next
0423
0424 Return Result
0425 End Function
0426
0427 '---------------------------------------------------------------------------------------
0428 ' USB装置を初期化する
0429 '   目的のUSB装置はベンダーIDとプロダクトIDで識別する
0430 '
0431 ' 引数1:OPENしたいUSB装置のベンダーID  4桁の16進数
0432 ' 引数2:OPENしたいUSB装置のプロダクトID 4桁の16進数
0433 ' 返り値:USB装置のハンドル
0434 ' 該当するIDのUSB装置が無ければ INVALID_HANDLE_VALUE
0435 '---------------------------------------------------------------------------------------
0436 Public Function hid_init( _
0437 ByVal vendor_id As Short, _
0438 ByVal product_id As Short) _
0439 As Integer
0440
0441 Dim HidGuid As System.Guid
0442 Dim HIDHandle As Integer
0443 Dim DeviceAttributes As HIDD_ATTRIBUTES
0444 Dim Result As Boolean
0445 Dim Success As Boolean
0446 Dim Security As SECURITY_ATTRIBUTES
0447 Dim MyDeviceDetected As Boolean
0448
0449 Dim MemberIndex As Integer
0450
0451 Dim DeviceInfoSet As IntPtr
0452 Dim LastDevice As Boolean
0453 Dim BufferSize As Integer
0454 Dim MyDeviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA
0455 Dim MyDeviceInterfaceData As SP_DEVICE_INTERFACE_DATA
0456
0457 MyDeviceDetected = False
0458
0459 HidGuid = Guid.Empty
0460 HidD_GetHidGuid(HidGuid)
0461
0462 Security.lpSecurityDescriptor = 0
0463 Security.bInheritHandle = CInt(True)
0464 Security.nLength = Len(Security)
0465
0466 Try
0467 DeviceInfoSet = SetupDiGetClassDevs(HidGuid, vbNullString, 0, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
0468 MemberIndex = 0
0469 Do
0470 MyDeviceInterfaceData.cbSize = Marshal.SizeOf(MyDeviceInterfaceData)
0471 Result = SetupDiEnumDeviceInterfaces(DeviceInfoSet, 0, HidGuid, MemberIndex, MyDeviceInterfaceData)
0472 If (Result = False) Then
0473 LastDevice = True
0474 Else
0475 MyDeviceInterfaceDetailData = Nothing
0476 '----- SetupDiGetDeviceInterfaceDetail 1回目の呼び出し --------------------
0477 Success = SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, MyDeviceInterfaceData, _
0478 IntPtr.Zero, 0, BufferSize, IntPtr.Zero)
0479 MyDeviceInterfaceDetailData.cbSize = Marshal.SizeOf(MyDeviceInterfaceDetailData)
0480 Dim DetailDataBuffer As IntPtr = Marshal.AllocHGlobal(BufferSize)
0481 Marshal.WriteInt32(DetailDataBuffer, 4 + Marshal.SystemDefaultCharSize)
0482 '----- SetupDiGetDeviceInterfaceDetail 2回目の呼び出し --------------------
0483 Success = SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, MyDeviceInterfaceData, _
0484 DetailDataBuffer, BufferSize, BufferSize, IntPtr.Zero)
0485 Dim pdevicePathName As IntPtr = New IntPtr(DetailDataBuffer.ToInt32 + 4)
0486 DevicePathName = Marshal.PtrToStringAuto(pdevicePathName)
0487 Marshal.FreeHGlobal(DetailDataBuffer)
0488
0489 HIDHandle = CreateFile(devicePathName, GENERIC_READ Or GENERIC_WRITE, _
0490 FILE_SHARE_READ Or FILE_SHARE_WRITE, Security, OPEN_EXISTING, 0, 0)
0491 If (HIDHandle <> INVALID_HANDLE_VALUE) Then
0492 DeviceAttributes.Size = Marshal.SizeOf(DeviceAttributes)
0493 Result = HidD_GetAttributes(HIDHandle, DeviceAttributes)
0494 If Result Then
0495 If (DeviceAttributes.VendorID = vendor_id) And (DeviceAttributes.ProductID = product_id) Then
0496 MyDeviceDetected = True
0497 Exit Do
0498 Else
0499 MyDeviceDetected = False
0500 Result = CloseHandle(HIDHandle)
0501 End If
0502 Else
0503 MyDeviceDetected = False
0504 Result = CloseHandle(HIDHandle)
0505 End If
0506 End If
0507 End If
0508 MemberIndex = MemberIndex + 1
0509 Loop Until (LastDevice = True)
0510
0511 SetupDiDestroyDeviceInfoList(DeviceInfoSet)
0512 Catch ex As Exception
0513 MsgBox("exception : hid open")
0514 End Try
0515
0516 If MyDeviceDetected = True Then
0517 Return HIDHandle
0518 Else
0519 Return INVALID_HANDLE_VALUE
0520 End If
0521 End Function
0522
0523 '---------------------------------------------------------------------------------------
0524 ' USB装置をCLOSEする
0525 '---------------------------------------------------------------------------------------
0526 Public Function hid_close(ByVal hidhandle As Integer) As Boolean
0527 Try
0528 If (hidhandle <> INVALID_HANDLE_VALUE) Then
0529 CloseHandle(hidhandle)
0530 Return True
0531 Else
0532 Return False
0533 End If
0534 Catch ex As Exception
0535 MsgBox("exception : hid_close")
0536 End Try
0537 End Function
0538
0539 '---------------------------------------------------------------------------------------
0540 ' USB装置の着脱通知を登録する
0541 '
0542 ' USB装置が取り外されたとき、そのお知らせがこのプログラム宛に来るように登録する
0543 ' プログラムを起動したときに(適当な場所としてはForm_Load)これを呼び出して着脱通知をセットしておけばよい
0544 ' 「ケーブルを引っこ抜いたりしないとか、USB装置を接続する前にプログラムを起動したりしない」
0545 ' と約束できる人はこんな面倒なものを書かなくてもよい。
0546 '     ...でもそんな人に限って約束を破るのだ。だからそれに備えてこれは必要になる。
0547 ' この通知はHIDデバイスを着脱したときに受け取ることが出来る。HIDデバイスでない
0548 ' USBメモリーなどを抜き差ししても通知は来ない。逆にHIDデバイスなら全て通知が来るため
0549 ' 接続したい目的の装置であるかまでは通知を受け取った後にデバイスパス名を調べる必要がある
0550 ' つまり、「もし、ベンダーID=1234 プロダクトID=0001の装置が着脱されたら知らせてくれ」
0551 ' などということは出来ない。出来ることは「HID装置が着脱されたらそれを知らせてくれ」である
0552 ' そんなわけで hid_mydevice_notification という関数を用意した
0553 '
0554 ' 引数1:USB装置がハブに取り付けられたりケーブルを引っこ抜かれたりしたときに
0555 '     どのクラスにそれを知らせるのか。
0556 ' 通常はメインフォームでよいので Me.Handle を渡せばよい
0557 ' 返り値:登録がうまくいったか否か
0558 '---------------------------------------------------------------------------------------
0559 Public Function hid_register_notification(ByVal win_handle As IntPtr) As Boolean
0560 Dim DevBroadcastDeviceInterface As DEV_BROADCAST_DEVICEINTERFACE = New DEV_BROADCAST_DEVICEINTERFACE()
0561 Dim DevBroadcastDeviceInterfaceBuffer As IntPtr
0562 Dim size As Integer
0563 Dim HidGuid As System.Guid
0564
0565 HidGuid = Guid.Empty
0566 HidD_GetHidGuid(HidGuid)
0567
0568 Try
0569 size = Marshal.SizeOf(DevBroadcastDeviceInterface)
0570 DevBroadcastDeviceInterface.dbcc_size = size
0571 DevBroadcastDeviceInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
0572 DevBroadcastDeviceInterface.dbcc_reserved = 0
0573 DevBroadcastDeviceInterface.dbcc_classguid = HidGuid
0574 DevBroadcastDeviceInterfaceBuffer = Marshal.AllocHGlobal(size)
0575
0576 Marshal.StructureToPtr(DevBroadcastDeviceInterface, DevBroadcastDeviceInterfaceBuffer, True)
0577
0578 DeviceNotificationHandle = RegisterDeviceNotification( _
0579 win_handle, DevBroadcastDeviceInterfaceBuffer, DEVICE_NOTIFY_WINDOW_HANDLE)
0580
0581 Marshal.PtrToStructure(DevBroadcastDeviceInterfaceBuffer, DevBroadcastDeviceInterface)
0582 Marshal.FreeHGlobal(DevBroadcastDeviceInterfaceBuffer)
0583
0584 If (DeviceNotificationHandle.ToInt32 = IntPtr.Zero.ToInt32) Then
0585 Return False
0586 Else
0587 Return True
0588 End If
0589 Catch ex As Exception
0590 MsgBox("exception : RegisterDeviceNotification")
0591 End Try
0592
0593 End Function
0594
0595 '---------------------------------------------------------------------------------------
0596 ' USB装置の着脱通知を解除する
0597 '
0598 ' 通常はプログラムを終了させるときに呼び出す
0599 ' USB装置を使用するプログラムがUSB装置の有無に興味がなくなるのはたぶん
0600 ' プログラムを終了するときでしょう
0601 '---------------------------------------------------------------------------------------
0602 Public Sub hid_unregister_notification()
0603 Try
0604 UnregisterDeviceNotification(DeviceNotificationHandle)
0605 Catch ex As Exception
0606 MsgBox("exception : UnregisterDeviceNotification")
0607 End Try
0608 End Sub
0609
0610 '---------------------------------------------------------------------------------------
0611 ' 着脱されたUSB装置が自分の使用しているUSB装置であるかを調べる
0612 '
0613 ' この関数はUSB装置をCLOSEするときのみに使用する
0614 ' 通知メッセージが来たらこの関数で自分の使用しているUSB装置であるか否かを調べてから
0615 ' 装置をCLOSEする。
0616 ' 装置をOPENするときは通知メッセージに頼ることが出来ないのでこれを使用する必要は無い
0617 ' なぜならプログラムを起動する前にUSB装置は接続されていることが多いからだ。
0618 ' 既に接続されている装置の着脱のメッセージを受け取ることは出来ない。
0619 '
0620 ' 引数1:通知メッセージ
0621 ' 返り値:True ->自分の使用しているUSB装置である
0622 '    False->自分の使用しているUSB装置では無い
0623 '---------------------------------------------------------------------------------------
0624 Public Function hid_mydevice_notification(ByVal m As Message) As Boolean
0625 Try
0626 Dim DevBroadcastDeviceInterface As New DEV_BROADCAST_DEVICEINTERFACE_1()
0627 Dim DevBroadcastHeader As New DEV_BROADCAST_HDR()
0628
0629 Marshal.PtrToStructure(m.LParam, DevBroadcastHeader)
0630 If (DevBroadcastHeader.dbch_devicetype = DBT_DEVTYP_DEVICEINTERFACE) Then
0631 Dim StringSize As Integer = CInt((DevBroadcastHeader.dbch_size - 32) / 2)
0632 ReDim DevBroadcastDeviceInterface.dbcc_name(StringSize)
0633 Marshal.PtrToStructure(m.LParam, DevBroadcastDeviceInterface)
0634 Dim DeviceNameString As New String(DevBroadcastDeviceInterface.dbcc_name, 0, StringSize)
0635 If (String.Compare(DeviceNameString, DevicePathName, True) = 0) Then
0636 Return True
0637 Else
0638 Return False
0639 End If
0640 End If
0641 Catch ex As Exception
0642 MsgBox("exception : hid_mydevice_notification")
0643 End Try
0644 End Function
0645
0646 End Module
0647 '-------------- HID汎用関数 --------- E N D O F F I L E ------------------------------------
行番号
解説
1~最終行
このソースは無変更で使用できるようにしてあります、HIDで通信をする場合いじらずに使えます。RS-232に比べると装置の着脱認識機能があるため、少し複雑ですが、RS-232と同程度のシンプルさでUSBを使えるようにしたものです。そのお作法についてはこのリストの一番上のコメントを見てください。

2009-10-31

現在バージョンのmikroBASICではすでにこのプログラムはコンパイル不能になっています。PIC18F4550は秋月電子で販売が始まっているので、mikroBASICに付属のサンプルUSBプログラム+HIDターミナルの組み合わせで動作確認が出来ます。

USBの波形上がD-,下がD+の波形

usbhid100mhz.PNG

USB-HIDのポーリングの様子。データもこの狭い帯域幅を使用して送受信されるので高速大量データの転送は不可。

hid_usb25mhzpoll.PNG