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

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

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

[第54回]

●WriteFile()関数の続きです(HIDの怪を追求)

正直なところ、こんなことにこだわっている暇はないはずなのですけれど…。
若いころならば、「あー、無視。無視。次行きましょー」だったのですが、それがトシをとってきますと、「お若いの。そんなに急いでどこへ行きなさる?ほれ。野辺は花盛りというに。しばし花などを愛でてみてはいかがかな?」という気分になってまいります。
君子忙中有閑。

で。なんとなく気になるHIDの「65バイトルール」について、貴重な1日を割くことにしまして、いま少し追求を試みることといたしました。

まずは前回([第53回])のテスト結果を読み直してみて、気が付いた点について確認をしてみました。
前回、PIC18F4550のHIDレポートディスクリプタの設定を、40(=64)バイトから20(=32)バイトに変更してのテストで、HID送信プログラムの送信バッファの第1バイトwbf[0]の値が00ではなくてFFにしたままであったところを、念の為に00に直して、再度テストしてみました。
しかし、結果は前回の結果と何も変わりませんでした。

もうひとつ。ひょっとしたら、HIDレポートディスクリプタの設定を、40(=64)バイトから20(=32)バイトに変更したのですから、WriteFile()関数の第3パラメータ(nwrite)の値を21(=33)にしても、今回はエラーにはならないのではないか、と考えました。
で、そのようにHID送信プログラムを書き換えてテストしてみたのですが…。
結果はやっぱりエラーになってしまい、送信できませんでした。
やはりHIDレポートディスクリプタの設定とは関係なく、HIDでのWriteFile()関数の第3パラメータはつねに41(=65)を指定しなければならない、ようです。

次にもうひとつ。
受信側(当社ZB25K)なのですが、簡易的なプログラムでテストをしたものですから、事実を誤認しているところがあるかもしれません。
ZB25K側の受信プログラムを変更して、再度テストをしてみました。
すると、意外な事実が判明したのであります。

前回、「…/mogamまでの32バイトが送信された」と書きました。
受信側の表示では、確かに/mogamの後ろは意味不明の文字列になっています。
私はこれを見て単純に「送信されたデータは32バイト」だと早合点してしまったのです。
事実はそうではありませんでした。

受信側(ZB25K)のプログラムを変更して、再度テストしてみた結果です。



前回のテストでは見えていなかった、終わりの部分「gawa」が現れました。
でも中間にゴミが挟まっています。
そして1文字「i」が落ちています。
むむ。
なんだか推理小説の謎解きめいてきましたよ。

おおお。
これは?
ひょっとしたら…!

こちらは同時にチェックしたオシロスコープの波形です。

下側(CH2)がRS232C(RS485)の送信データ(PIC18F4550のTX出力)です。
上側は(CH1)MAX485の送信/受信を切り換えるための信号です。
送信開始でH、送信終了でLになります。

●PIC18F4550回路図

参考までに、PIC18F4550HIDテスト基板の回路図をお見せします。


さきほどのオシロスコープのCH1は、PIC18F4550の2pinの出力波形で、CH2は25pinの出力波形です。

CH1の波形をよく見ると、途中約40msecのあたりにL信号が確認できます。
これは、RS232Cの送信が2回行われたことを示しているのです。
でも最初の送信は約40msecなのに、次の送信は約70msecほどもあります。
これは?

ボーレートは9600ボーです。1バイトの送信ごとにスタートビットとストップビットが付加されますから、転送速度は960バイト/秒になります。
ということは、約40msecの間に送信された文字数は、960×0.04≒38文字です。
これは、Windowsマシンから送信されたはずの文字列「samidare…mogamigawa(および0D0A)」の文字数38バイト(26H)にぴったり一致します。
PIC18F4550はどこからこの送信文字数を得たのでしょうか?

そうです。
Windowsマシンからは、送信する文字列の先頭に、文字列の総バイト数を挿入して送る、というのが今回のHID送信のルールでした([第52回])。
このページのトップでお見せした送信テストの実行結果の画面では、右のDOS窓(HID送信プログラムを実行)で、データの先頭に[26]が表示されていることがわかります。

PIC18F4550は、Windowsマシンから送られたデータの先頭につけられた、送信データ数情報の26(=38)にしたがって、RS232Cで38バイトの文字列を送出したのです。
もう一度、このページのトップでお見せした送信テストの実行結果の画面で、今度は左側のDOS窓、ZB25Kの受信データを見てみましょう。

samidareo/atsumetehayashi/mogamォSツj[p6
ここまでで38文字です。
ここで一旦送信が切れて、そのあと
gawa
が送信されたことが、さきほどのオシロスコープの波形から推測されます。
画面からはわからないのですが、実はgawaのあとに改行コード0D・0Aがあって、そのあとに00が続いていることがわかっています。

ああ。
それもお見せすることにいたしましょう。

ZB25KがPIC18F4550から受信したデータはZB25Kの配列DD$に格納されます。
DD$の領域はダイレクトにはちょっとアクセスしにくいので、固定領域のA$〜D$に移してから、メモリダンプしてみました。

ZBK・BASICの文字変数のサイズは40バイトの固定サイズです。
先頭に1バイトの文字数情報が入りますから、文字型の1変数に格納できる文字数は最大で39文字です。
いま、受信データはF300〜F327、F328〜F34F、F350〜F377、F378〜F39Fの順に4つの変数エリア(A$、B$、C$、D$)に納められています。

ここからがZBKの側の仕様でちょっとややこしくなるのですが、水色で囲んだ4つの数値は各変数エリアの先頭にあって、そこに格納されている文字数を示しています。
最初のブロックを見ますと、格納文字数は27(=39)です。
そのうち38文字をピンクで囲いました。
この38文字がPIC18F4550から最初に送信されたグループなのです。
ZBKBASICは改行コード0D0Aを見つけるまでは、連続したデータ列とみなすので、途中で送信が途切れてもそのまま待ち続けます。
そのために、受信した結果からは送信の切れ目はわかりませんが、先に推論した結果から、ここに送信の切れ目があったと考えられます。

ああ。そうでした。
これも確認できることに気が付きました。
さきほどのオシロスコープの波形を拡大してみました。

最初の送信データの最後のところです。
001101100と読めます。最初の0はスタートビットです。RS232Cのデータはビット0からビット7の順に送られます。
これを普通のバイナリ(2進数)の表記に直すと、00110110ですから、16進数の36です。これは文字”6”のASCIIコードです。

こちらは次の送信ブロックの最初のデータです。

011100110と読めます。
さきほどと同様に、普通のバイナリ(2進数)の表記に直すと、01100111ですから、16進数の67です。これは文字”g”のASCIIコードです。

これで文字コード36(文字”6”)が最初の送信ブロックの最後で、文字コード67(文字”g”)が次の送信ブロックの最初のデータであることが確認されました。

一体それがどういうことを意味しているのか、まだおわかりいただけないかもしれません。
私としても、うまく説明できないもどかしさを感じています。
ええ。
私は、何が起きたのか、今ははっきりわかっていると思います。

こういうことなのです。
PIC18F4550は、ホストからの着信があると(その受信データはオートマチックに受信バッファに格納されます)、その受信したデータを最初の1バイトの値(ホストからの送信バイト数を示す)に従ってRS232Cで送出するようにプログラムされています。

[2010.4.20注記]
PIC18F4550に、そのような機能がある、ということではありません。
もちろん、「そのようにプログラムした」のは、この私です。
PIC18F4550の機能としては、USBとRS232Cの機能は完全に独立して存在していますし、それらを連動させるような、都合のよい仕組みは用意されていません。
いずれそのプログラムの内容についても説明をする時があると思います。
独立して存在しているUSB(ここではHID)の受信データを、RS232Cで送信するように「私がPICアセンブラでプログラムしました」という意味です。
また下の文で、「受信バッファは64バイト」としているところも、「私がそのようにプログラムしたから」です。
PIC18F4550ではUSBのバッファは任意のバイト数で任意の個数を用意することができますが、そのようにプログラムしなければ、PIC18F4550は何もしてくれません。
もちろん「最初の1バイトが送信文字数を示している」ことも、「私がそのようにプログラムしたから」です。
それからずっと下の方で出てきますが、「RS232Cの送信カウンタに値をセットして」というところも、PIC18F4550に「送信カウンタ」や「バッファのデータをRS232Cで送信してくれる」機能がもとから備わっている、という意味ではありません。
それも「そのようにアセンブラでプログラムしたから」です。
[注記ここまで]

PIC18F4550の受信バッファは64バイトで、実際に何バイトがホストから送られたのかを知ることはできませんが、最初の1バイトが送信データ数を示しているとの約束に従って、受信バッファの2番目以下の文字列をRS232Cで送出します。
ホストから送られた最初のデータはおそらく26(=38)のはずなので、PIC18F4550からはRS232C経由でZB25Kに38文字のデータが送られた、と考えられます。

それではなぜ、ZB25Kが受信した38文字のデータのうち、本来含まれているはずのmogamより後ろの7文字がでたらめな文字列に置き換わってしまっていたのでしょうか?
ううう。実にミステリーです。

合理的な答えはただひとつです。
それは、Windowsマシンからは32文字しか送られてこなかったから、なのです(その32バイトという数値は、HIDレポートディスクリプタで指定した値です)。
そうとしか考えられません。

最初の1バイトは文字数情報ですから、データの本体は31文字です。
だから、PIC18F4550の受信バッファには、31文字のデータsamidare〜mogamまでしか入っていなかったのです。
しかしPIC18F4550は第1バイトの示す文字データ数情報に従って受信バッファから(31文字ではなくて)38文字を取り出して、それをZB25Kに送信してしまった、というのがことの真相です(と思います)。

では、その次のデータブロックの「g」+「awa」は何を意味しているのでしょうか。
さきほども説明しました通り、「g」はRS232CでPIC18F4550からZB25Kに送信された、2番目のデータブロックの最初の文字です。
その後に続く文字列は、ZB25Kの次の文字変数B$に格納されています。
アドレスはF328〜F34Fです。
さきほどお見せしたZB25Kの変数エリアデータの表示画面を見てください。
F328には、変数B$に格納されている文字数が入っています。
03が入っていますから、ここに格納されているデータは3文字、つまり「awa」であることを示しています。

オシロスコープの波形では、第2ブロックの送信データは約70msecですから、960×0.07≒67バイトていどはあるはずなのに、ここが3文字であることには理由があります。
上の方でも書いたと思いますが、ZBKBASICでは改行コード0D0Aを受信するとそこが文字列の区切りだと判断して、そこで格納する文字変数を次の変数に切り換える仕組みになっています。
このとき、0D0Aコードそのものは、文字変数に格納されません。
つまり、ZB25Kのメモリダンプ結果からは、awaの後ろに0D0Aコードがあったことがわかります。
以上のことをまとめてみると、どうなるでしょうか。

こういうことがおきた、と考えられます。
HIDにおけるWriteFile()関数の制約から(と思われます)、送信バイト数は41(=65)を指定することしか許されませんが、それにもかかわらずHIDレポートディスクリプタが指定した20(=32)バイトに従って、実際には、先頭の文字列データ数情報の1バイトと、それに続く31文字のデータ(合計32バイト)がPIC18F4550に送られた、と考えられます。

これは推測するしかないのですが、おそらくWriteFile()関数はHIDデバイスに対するハンドルを使った場合、第3パラメータ(送信バイト数)は41(=65)しか受け付けないようにガードがかかっているのではないでしょうか。
しかしその下層にHID送信ルーチンがあって、そこではレポートディスクリプタで指定されたバイト数だけを送信する、のだと考えられます。

するとここで、上層のWriteFile()関数が定義している40(=64)バイトの送信データと、下層のHID送信ルーチンが従っている20(=32)バイトの間でずれが生じます。
下層のHID送信ルーチンは、まだ後半の20(=32)バイトを送信していませんから、そのために、1回の送信では完了せずに、続いて2回目の送信が行われた、のではないでしょうか。

当然、2回目に送信されたデータは、1回目に送信されたデータの続き、のはずです。
1回目は、…mogamで終わっていましたから、2回目は、その続きのigawa+0D+0Aになるはずです。
しかし、実際にPIC18F4550からZB25Kに送信されたのは、gawa+0D+0Aでした。

いったい「i」はどこに行ってしまったのでしょうか?
また、2回目にPIC18F4550からZB25Kにどうして約67バイトものデータが送られたのでしょうか。

そこからも、興味深い意外な事実が浮かび上がってきます。

実は、2回目に送信されたデータバイト数は、およそではなくて、正確に求めることができます。
もう一度、さきほどのZB25Kの変数エリアデータの表示画面を見てください。
さきほどまでの説明で、ZB25Kのメモリダンプの結果から、2回目の受信データは、「g」+「awa」+0D+0Aである、というところまでは明らかになりました。

では、そのあと、どれだけのデータがPIC18F4550から送られたのでしょうか。
それは、その次の変数C$とD$の格納データ数情報を見ることでわかります。
C$の格納データ数情報はアドレスF350にあります。水色で囲った27(=39)バイトです。
D$の格納データ数情報はアドレスF378にあります。水色で囲った12(=18)バイトです。
以上の情報から、2回目にPIC18F4550からZB25Kに送信されたデータは、
1+3+1+1+39+18=63バイトであることが確認できます。

その数字の意味についてはちょっと置くことにしまして、まずは、2回目に送信されたデータの中身について確認してみたいと思います。
送信されたデータの改行コード0D0Aの後ろのデータは変数C$に格納されています。
アドレスF351から後ろです。
このデータを見ますと、00が25個あります。その後ろはその前の変数にも見える「ゴミデータ」と同じです。
00が25個、ということに何か意味があるのでしょうか?

大有りなのです。
HID送信テストプログラムでは送信バッファとしてwbf[]を用意しました。65バイトのバッファです。
wbf[]を定義するときに、同時に値として”¥0”を定義しました。
つまり、初期値はオール00です。

そこに、wbf[1]から(wbf[0]からではありません)、先頭の文字数情報1バイト+「samidare…mogamigawa」36バイトおよび0D、0Aコードの合わせて39バイトの文字列が代入されます。
64−39=25ですから、0D0Aの後ろには25個の00データがあるはずです。

ああ。そうでした。
そんな計算をしなくても、最初にお見せした、送信テストの実行結果の画面を見れば、右のDOS窓の送信データの表示で、[00]が25個表示されていることがわかります。

ということは、さきほどの推論に従えば、この00データの終わりが、ホストからPIC18F2550に2回目に送信されたデータの終わりである、と考えられます。
仮に2回目に送信されたデータの先頭が、「g」+「awa」+0D+0Aである、と考えて、送信データ数を計算してみると、
1+3+1+1+25=31バイトになります。
64バイトの送信バッファwbf[]から、32バイトずつちょうど2回に分けて送信された、と考えられるのですが…。
うむむ。
なんとなく合いそうで、どこかがずれているようです。
何なのでしょうか?

さきほども書きましたように、最終的にZB25Kに送信されたデータをつなぎ合わせてみると、確かに64バイトのデータがホストからPIC18F4550に32バイトずつ2回に分けて送信された、ように見えるのですが、しかし、後半が1バイト足りません。
どうも「i」の文字が1文字落ちてしまっているようです。

そしてまた、ZB25KがPIC18F4550から受信したデータは1回目が38バイトで、2回目はなんと63バイトである、という事実は何を物語っているのでしょうか?

実は皆様にお知らせしていない事実がひとつありました。
それは、PIC18F4550がZB25Kに受信バッファのデータを送信するとき、そのバッファの先頭のデータを送信バイト数としてカウンタにセットするのですが、例外が2つあるのです。
その第1は、送信データ数として受信した値が00であったときは、RS232C送信を行わない、ということです。
その第2は、もしも送信データとして受信した値が、受信バッファの最大データ容量(63バイト)を超える場合には、63バイトを送出して、そこでRS232Cの送信を打ち切る、というルールです。


以上で、謎を解く鍵は、包み隠すことなく全て読者の皆様に公開いたしました。
おそらく、聡明なる読者の皆様におかれましては、すでにことの真相を突き止められたことでありましょう。

以下は私が推理して到達した、ことの真相です(と思います)。

すでに説明いたしましたとおり、おそらくWindowsマシンからは64バイトのデータが32バイトずつ2回に分けて送信されたのだ、と思います。
ではなぜ、2回目の送信データの先頭にあったはずの「i」の文字が、PIC18F4550からZB25Kに送信されたデータからは消えてしまい、またなぜPIC18F4550からZB25Kへの2回目の送信データ数が63バイトだったのでしょうか。

ここで皆様方にぜひとも思い出していただきたいのは、PIC18F4550がホストから受信した第1番目のデータをどう扱うか、ということです。

ホストから1回目に送信されたデータの第1番目には送信文字数情報の26(=38)が入っていました。
ですからPIC18F4550は、実際に受信した文字データの31文字に、その後ろのバッファにもとからあった無意味なデータを含めた38文字を、RS232Cで送出してしまったのです。

ではホストから2回目に送られたはずのデータの、第1番目は何だったのでしょうか?
そうです。
おそらく、1回目に送信された、…mogamに続く文字「i」だったはずです。

2回目には、ホストからは、「igawa0D0A…」という文字列が送信されてきたはずです。
しかしPIC18F4550は、ルールに従ってその先頭の文字「i」を文字としてではなく、「送信文字数」として、そのコードである69(=105)を送信カウンタに入れてしまったのです。
そして、その次の文字から後ろの「gawa0D0A…」をRS232Cで送出したのです。

しかし、69(=105)はPIC18F4550の受信バッファの最大容量(63バイト)を超える値です。
そのためPIC18F4550はバッファの最初のデータである「g」からバッファの最後までの63バイトを送出したところで、送信を打ち切ってしまったのだと考えられます。
CPUをつくろう!第481回(2010.4.19upload)を再編集

PICでUSBを![第54回]
2011.7.11upload

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