B級なHello Kittyのパソコンフレンズの研究
[rev.1.2]
Sorry, this page is written in Japanese(ISO-2022-JP).
 
Hello Kittyのパソコンフレンズ
Hello KittyのLink
■ 日本のページ
┗ Hello Kittyのパソコンフレンズ
 
Hello Kittyのパソコンフレンズというロボット(?)について研究してみました. いやぁ,内心バカにしていたんですが,声が出るというのは結構面白いです. なお,研究員は別にKittyが好きなわけではありません. (タッチおじさんの方がどちらかといえば好きかも)

 
更新履歴
Rev. 1.0
初公開(2000/08/06)
Rev. 1.1
serialキティデバイスドライバを1.0として更新,PPC(BeBox)にも対応(2000/10/20)
Rev. 1.2
USBのプロトコル解析情報を追加(2001/11/16)
 

目次

はじめに
セット内容
ハードウェアの研究
コマンドの解説
実践研究
例題1: serialキティを赤面させる
例題2: serialキティデバイスドライバ
おわりに

はじめに

ふと店で気になるものを見つけ,安かったのとUSBの研究もしてみたかったので 「Hello Kittyのパソコンフレンズ」(3,980円也)を購入してみました. Windows 95/98のみ対応だったので,BeOSでも動かせるようにしてみました.
こういうくだらんこともB級のニオイぷんぷんですので,けっこう好きだったりします.;-p


セット内容

箱の中身は次のようになっています.

  • キティ本体(以下キティ)
  • コネクタはRS232CとUSB typeAの2つが付属.
  • ソフトウェア(Hello Kitty Station)を含む8cm CD-ROM

コネクタはRS232CとUSB type Aの2種類あって,いずれも片方の端はキティ本体と 接続される専用コネクタとなっています.


ハードウェアの研究

中身を解剖してみると,メインのチップは2つあります. 1つは富士通製の8ビットマイコン(MB89191)と, もう1つはCYPRESS製のUSBマイコン(CY7C63001A)が使われていました. 富士通製のマイコンはRS232C制御用,CYPRESS製マイコンはUSB用のようです. (これほどはっきり分けているのも珍しい気がします.1つのマイコンでRS232C I/Fと USB I/Fを持ってるヤツにしなかった理由は何なのだろう? やっぱり価格なのかな? それとも富士通製品を使わなければならなかった台所事情なのか...)

さて,富士通製のマイコンは4kBのマスクROMと128バイトのRAMを内部に持ち, CYPRESS製のマイコンは4kBのEPROMと128バイトのRAMを内部に持っています.

双方とも限られたメモリしか持っていないので,キティの発する音声データは これらのマイコンの中には入っておらず,アヤシゲな樹脂封入されたチップを 実装した小さな別基板に持っていると考えられます. (これはちょっと残念.別のデータを使うことができれば別の筐体の中に回路のみを 入れて{動く|しゃべる}マスコットとして使えたのですが...)


次にコネクタのI/Fについて調べてみました. (専用コネクタに関してはどちらが1番ピンか分からなかったので,勝手に 番号をつけました)

専用コネクタとRS232C,USBとの接続
専用コネクタ RS232C側(ピン番号) USB側(ピン番号)
1 GND GND, シールド
2 RxD(2) NC
3 NC D-
4 RTS(7) NC
5 NC D+
6 TxD(3) NC
7 NC Vcc
8 DTR(4) NC

こうして見ると,RS232CとUSBはGNDこそ共用しているものの,他の信号線は まったく排他的に利用しています. となると,RS232Cでしか使わない信号線(RTSやDTR)をプルアップすることで, 本体側のRS232CとUSBのどちらにつながっているかを判別しているのかと 思っていたのですが. 結局RTSはRS232CとUSBの自動判定用途に使っていないことが判明しました. 実際,RS232Cで接続したまま,RTSをLにして,コマンド送信すると キティから発声させることはできました. ただし,RTSをLにしていると,キティはコマンドのACKやボタンをクリックしたときの データを送ってくれなくなります. すなわち,ハードウェアフロー制御にもしっかり対応していたのでした. まぁまともなRS232C用マイコンですので,当たり前といえば当たり前でしたね.

さて,このRS232C/USB自動判別機能については,調査しても見返りとして得られる 利益が少なそうなので調査を棚上げにしておきます. (USBの研究を進めていくと自然に解決されるかもしれません)


次にキティのロボとしての動作ですが,以下の動作をします(マニュアルより抜粋).

  1. 首を横にかしげる(かしげ)
  2. 右手を振る(腕ふり)
  3. 赤面する(赤面)

また,キティの特筆すべき点は音声でしゃべることです. 次の発声・発音をします.

  1. ハロー
  2. こんにちは
  3. またね
  4. ちゅーせ〜ん
  5. 大当たり
  6. すてきすてき
  7. ラッキー
  8. あれ〜?
  9. タイマー
  10. 時間よ
  11. ピロリ
  12. ピピピ
  13. ピロリピロリピロリ
  14. ピピピピピピ...
  15. ピロリピロリピロリ...

余談ですが,研究員は「またね」と「あれ〜?」と「すてきすてき」の声が とても気に入っています.(^^;

なお,WindowsソフトであるHello Kitty Stationをインストールすると, PCの音源を使って以下の発声をさせることができます.

  1. おはよう(kitty01.wav)
  2. 今日もいい日でありますように!(kitty02.wav)
  3. がんばろ!(kitty03.wav)
  4. おつかれさま(kitty04.wav)
  5. メール来てるかしら(kitty05.wav)
  6. まだみたい(kitty06.wav)
  7. メールが来てるわ(kitty07.wav)
  8. メールよ(kitty08.wav)
  9. 大事なメールがあるわ(kitty09.wav)
  10. メール来ちゃった(kitty10.wav)
  11. お友達からよ(kitty11.wav)
  12. お仕事のメールよ(kitty12.wav)
  13. お誕生日おめでとう!(kitty13.wav)
  14. 今日は大事な日よ!(kitty14.wav)
  15. 予定があるわ(kitty15.wav)
  16. 会議の時間よ!(kitty16.wav)
  17. もう時間だぁ(kitty17.wav)
  18. お休みしましょ(kitty18.wav)
  19. お知らせでーす!(kitty19.wav)
  20. 今日はおしまい〜(kitty20.wav)
  21. ひとやすみ(kitty21.wav)
  22. すごーい!(kitty22.wav)
  23. 何してたの?(kitty23.wav)
  24. ねむくなっちゃった(kitty24.wav)
  25. ヒ・ミ・ツ(kitty25.wav)
  26. OK!(kitty26.wav)
  27. らららん(kitty27.wav)
  28. ふふふん(kitty28.wav)
  29. にこにこ(kitty29.wav)
  30. ざんねん(kitty30.wav)

これらの音のデータはすばらしいことにwav形式で C:\Program Files\MascotRobot\Kitty\Data\Soundにあります. また,グラフィックのデータもbmp形式で C:\Program Files\MascotRobot\Kitty\Graphic\Soundにあります. これらのデータをBeOSで使って良いものかどうかは不明です. (個人の利用の範囲であれば...という気もしますが,データの著作権は 富士通パソコンシステムズさんにありますので,気になる方はそちらに 問い合わせてみてください.研究員は問い合わせておりません.(^^;)


さて,研究員はUSBについては勉強が不足しているので,今回はRS232C側について 研究してみました.

2001/05/12: USBの解析は一部完了しました.
2001/11/16: USBの解析は完了しました.

コマンドの解説

キティをRS232CでWindows 98ノートPC(COMPAQ ARMADA M300)に接続し, どのようなデータが送受信されているのか調べてみました.

調査には以前購入してお蔵入りしていた秋月電子製RS232Cラインモニタ (AKIRS232Cラインモニター)のキットを蔵から引きずり出してせっせと半田づけして 利用しました. ただし,このラインモニタの作り方が悪かったのか,電源用のACアダプタとして 利用しているのがCOMAQ Contura AERO用のACアダプタ(17V)が悪いのか, うまくデータが取れないときがあるようです. したがって,以下のデータの中にもウソが混じっている場合も考えられますので, 十分ご注意ください.(ウソ発見は大歓迎です)

まず,データ以外の信号線についてですが, Windows98で動作するソフトウェアであるHello Kitty Stationを起動した後では, RTSはHにアサートされ,DTRはLにネゲートされて固定されています. (RTSは元々半二重通信のデータ送受信制御(いわゆるハードウェアフロー制御)の 目的で使います.Hに固定してていいんだろうか. まぁ所詮キティからは1バイトずつしか送られてこないし, ハードウェアフロー制御使う必要はキティにはないといえばないですね(笑))

ハードウェアフローの話が出たので,ここでRS232Cの送受信に利用する パラメータについて説明します. キティ本体との通信はB技研調べでは,以下のようになっています.
(これはウソの可能性も高いです.しかし,このパラメータで通信できている ので,OKとしています.)

  • データ転送レート: 9600bps
  • データ: 8ビット
  • ストップビット: 1ビット
  • パリティ: なし
  • フロー制御: なし
  • RTS: H
  • DTR: L

次にコマンドの送受信です.

Hello Kitty Stationを起動した際に,serialキティ本体に対して 何らかのコンフィギュレーションを行なうようです. ラインモニタを用いて,Hello Kitty Station起動の際のデータ送受信を 観察してみると,次のようなシーケンスでした.
(送信: PC→serialキティ,受信: PC←serialキティ) なお,PCからserialキティにコマンド(1バイト)を送信すると,ACKとしてデータが 返ってくるようです. どうやら,コマンドが成功するとコマンドと同じACKを返し,コマンドが失敗すると 別のデータをACKとして返すようです.

送受信シーケンス
時刻 送信 受信
1 0x00
2 0x00
3 0xFF
4 0x00
5 0x20
6 0x20
7 0x31
8 0x32
9 0x28
10 0x28

むぅ,これを見るとPC側からデータを送信する前にserialキティからデータが 送られてきています.ということは,serialキティはRS232Cポートに接続されたことを 自動判別して,データを送信するのかもしれません. また,RTSがHになったのを検出してデータを送信しているのかもしれません. しかし,残念ながらB技研にはそのあたりを詳しく調べる手立てがありません. もしまっとうなRS232Cラインモニタで調べていただける方がいましたら, 教えてくださいませ.(_o_)


さて,上記のHello Kitty Station起動時シーケンスや,Hello Kitty Stationの キーボード/マウス設定での動作確認時のシーケンスを細かく調べ, 以下のコマンド表を得ました.なお,コマンド名は適当に付けました.

コマンド名 コマンド
初期化 0x20
ボタンイネーブル 0x28
不明 0x31
かしげ(戻し),腕ふり(腕おろし) 0x40
かしげ 0x44
腕ふり(腕あげ) 0x48
赤面(消灯) 0x50
赤面(点灯) 0x58
ハロー 0x60
こんにちは 0x61
またね 0x62
ちゅーせ〜ん 0x63
大当たり 0x64
すてきすてき 0x65
ラッキー 0x66
あれ〜? 0x67
タイマー 0x68
時間よ 0x69
0x6A
ピロリ 0x6B
ピピピ 0x6C
ピロリピロリピロリ 0x6D
ピピピピピピ... 0x6E
ピロリピロリピロリ... 0x6F

これらのコマンドを組み合わせてPC側から送信することで,serialキティは 例えば「あれー?」と発声しながら首をかしげて,さらに首を元に戻す,という 動作をすることができます. (コマンド送信シーケンスは0x67 0x44 0x40)

ここで,発声はかなり時間のかかる動作ですので,適当にwaitを入れないと, 発声途中で次の発声コマンドを送信すると,発声が替わってしまいます. BeOSでwaitを入れるにはsnooze()を用います.

なお,発声途中で腕振りなどの動作をさせても発声とは関係ないので, waitを入れる必要はありません.

また,ボタンイネーブルコマンドを送信した後で,serialキティの2つのボタンを押下すると serialキティからデータが送信されます. 送信されるデータは以下のとおり.

  1. 右クリック: 0x34 0x30
  2. 右クリック: 0x38 0x30
  3. 左右同時クリック: 0x3C 0x30



USBプロトコルの解析

さて,USBプロトコルを解析してみました. BeOSのusbkitのinfoでディスクリプタを覗いてみると次のようになっています.

[Device]
Class .................. 0
Subclass ............... 0
Protocol ............... 0
Vendor ID .............. 0x04c5
Product ID ............. 0x1010
Version ................ 0x0100
Manufacturer String .... "Fujitsu"
Product String ......... "Mascot Robot"
Serial Number .......... ""
  [Configuration 0]
    [Interface 0]
    Class .............. 0
    Subclass ........... 0
    Protocol ........... 0
      [Endpoint 0]
      MaxPacketSize .... 8
      Interval ......... 10
      Type ............. Interrupt
      Direction ........ Input
ここで注目すべきはデフォルトのコントロールエンドポイントを除いて エンドポイントが1つ(Endpoint 0)しかないということです. しかもこれはInput方向(デバイス(USBキティ)→ホスト(PC))です. これはキティのボタンクリックに対応してUSBキティからPCにデータを 送る(実際は[Endpoint 0]ディスクリプタに記述されているとおりにPCが インターバル10(msec)でポーリングして受信する)ためのものだと考えられます.

で,秘密兵器を某所よりお借りして,キティのUSBプロトコルをsnoopし, USBキティを動かしたり,顔を赤らめさせたりボタンクリックを受信したりする プロトコルを解析しました.
以下,USBインタフェースを介してアクセスする場合のキティのことをUSBキティと 表記することにします.

まず,USBキティを認識します. 通常のUSBデバイスと同様に,USBデバイスとして認識するには Control InエンドポイントにGET_DESCRIPTOR標準リクエスト(デバイスディスクリプタ)を送ってやり, デバイスディスクリプタの最低バイト数分(8バイト)要求します(Windows 2000などでは バイト数が64バイトとか255バイトとか多めの数を要求したりしています). すなわち,Control Inエンドポイントに80 06 00 01 00 00 08 00を送ります. (このデータの意味の詳細はUSB 1.1仕様書,9.3 USB Device Requestを参照のこと)

offset field データ 意味
0 bmRequestType 0x80 データ転送方向: Device→Host
Type : Standard
Recipient : Device
1 bRequest 0x06 GET_DESCRIPTOR
2 wValue 0x0001 ディスクリプタ型はDevice
4 wIndex 0x0000 インデクスは0
6 wLength 0x0800 データステージで転送するデータバイト数は8
ちなみにこれはBeOSではusbdが勝手にやってくれます. すると,デバイスに関するディスクリプタ(上記BeOSのusbkitのinfoで覗いた結果)が8バイト(12 01 10 00 00 00 00 08)返ってきます. これは次のようなフォーマットになっています. (ディスクリプタのフォーマットに関しては,USB 1.1仕様書,9.6.1 Deviceを参照のこと)
offset field データ 意味
0 bLength 0x12 Deviceディスクリプタは0x12=18バイト
1 bDescriptorType 0x01 ディスクリプタ型はDevice型
2 bcdUSB 0x1000 USB 1.00準拠のデバイス
4 bDeviceClass 0x00 デバイスクラス=0
5 bDeviceSubClass 0x00 デバイスサブクラス=0
6 bDeviceProtocol 0x00 デバイスプロトコル=0
7 bMaxPacketSize 0x08 最大パケットサイズ=8バイト
ここでは,usbkitのinfoで読んだディスクリプタの
[Device]
Class .................. 0
Subclass ............... 0
Protocol ............... 0
Vendor ID .............. 0x04c5
Product ID ............. 0x1010
Version ................ 0x0100
にあたる部分が読み出せています.

次にデバイスにSET_ADDRESSします. 例えば,使っていないアドレスとしてUSBキティに対して,2をSET_ADDRESSします. すなわち,Control Inエンドポイントに00 05 02 00 00 00 00を送ります. (SET_ADDRESSに関しては,USB 1.1仕様書,9.4.6 Set Addressを参照のこと)

offset field データ 意味
0 bmRequestType 0x00 データ転送方向: Host→Device
Type : Standard
Recipient : Device
1 bRequest 0x05 SET_ADDRESS
2 wValue 0x0200 アドレスは2
4 wIndex 0x0000 インデクスは0
6 wLength 0x0000 データステージで転送するデータバイト数は0


1つ目のGET_DESCRIPTORで,デバイスディスクリプタが18バイトあることがわかっているので, 次に,再度デバイスディスクリプタ全部(0x12=18バイト)を取得します. すなわち,Control Inエンドポイントに80 06 00 01 00 00 12 00を送ります.

offset field データ 意味
0 bmRequestType 0x80 データ転送方向: Device→Host
Type : Standard
Recipient : Device
1 bRequest 0x06 GET_DESCRIPTOR
2 wValue 0x0001 ディスクリプタ型はDevice
4 wIndex 0x0000 インデクスは0
6 wLength 0x1200 データステージで転送するデータバイト数は18(=0x12)
そうすると,デバイスに関するディスクリプタがすべて返ってきます. これは次のようなフォーマットになっています. (はじめの8バイトは1つ目のGET_DESCRIPTORと同じ)
offset field データ 意味
0 bLength 0x12 Deviceディスクリプタは18バイト
1 bDescriptorType 0x01 ディスクリプタ型はDevice型
2 bcdUSB 0x1000 USB 1.00準拠のデバイス
4 bDeviceClass 0x00 デバイスクラス=0
5 bDeviceSubClass 0x00 デバイスサブクラス=0
6 bDeviceProtocol 0x00 デバイスプロトコル=0
7 bMaxPacketSize 0x08 エンドポイント0の最大パケットサイズ=8バイト
8 idVendor 0x04C5 ベンダID=0x04C5(富士通パーソナル?)
10 idProduct 0x1010 プロダクトID=0x1010
12 bDeviceProtocol 0x00 デバイスプロトコル=0
14 bcdDevice 0x0100 デバイス番号=1.00
15 iManufactuarer 0x01 メーカーを示す文字列ディスクリプタ(Manufacturer String)のインデクスは1
16 iProduct 0x02 製品名を示す文字列ディスクリプタ(Product String)のインデクスは2
17 iSerialNumber 0x00 シリアル番号を示す文字列ディスクリプタはこの製品には存在しない
18 bNumConfigurations 0x01 コンフィギュレーションの数は1つ
ここで得られたデバイスに関する情報から,製品名や,メーカー,コンフィギュレーションの数などがわかります. (BeOSのusbdでは,idVendorやidProductを用いて,デバイスを判別し,ロードすべき ドライバを決定していたりします) ここでは, usbkitのinfoで読んだディスクリプタの
[Device]
Class .................. 0
Subclass ............... 0
Protocol ............... 0
Vendor ID .............. 0x04c5
Product ID ............. 0x1010
Version ................ 0x0100
が読み出せています.(前半部分は重複あり)

次に,コンフィギュレーションディスクリプタを取得します. コンフィギュレーションディスクリプタは全部で何バイトあるか現時点ではわかっていないので, Control InエンドポイントにGET_DESCRIPTOR標準リクエスト(コンフィギュレーションディスクリプタ)を送ってやり, 最小限の9バイトを要求します. すなわち,Control Inエンドポイントに80 06 00 02 00 00 09 00を送ります. (GET_DESCRIPTORに関しては,USB 1.1仕様書,9.4.3 Get Descriptorを参照のこと)

offset field データ 意味
0 bmRequestType 0x80 データ転送方向: Device→Host
Type : Standard
Recipient : Device
1 bRequest 0x06 GET_DESCRIPTOR
2 wValue 0x0002 ディスクリプタ型はConfiguration
4 wIndex 0x0000 インデクスは0
6 wLength 0x0900 データステージで転送するデータバイト数は9
そうすると,コンフィギュレーションに関するディスクリプタが返ってきます. これは次のようなフォーマットになっています.
offset field データ 意味
0 bLength 0x09 コンフィギュレーションディスクリプタは9バイト
1 bDescriptorType 0x02 ディスクリプタ型はConfiguration型
2 wTotalLength 0x0019 本コンフィギュレーションの長さは0x19(=25)バイト
4 bNumInterface 0x01 インタフェースの数は1つ
5 bConfigurationValue 0x01 本コンフィギュレーションを活かすためには0x01を設定する
6 iConfiguration 0x04 コンフィギュレーションを示す文字列ディスクリプタのインデクスは4
7 bmAttributes 0x80 アトリビュートを示す.USBキティはselfpoweredデバイスではなく,Remote wakeupに対応していない.
8 MaxPower 0x32 最大消費電力は100mA
ここでは, usbkitのinfoで読んだディスクリプタに関する項目は読み出していないように見えますが, インタフェースの数は1つであることや, このコンフィギュレーションを使うために1を設定することなど,ドライバを書く上では必要なことが 出てきています.

一旦最小限のコンフィギュレーションディスクリプタを取得した後は, コンフィギュレーションを示すディスクリプタをすべて取得します. 上のGET_DESCRIPTORでwTotalLength=0x19(25バイト)という情報が取得できているので, 次に再度GET_DESCRIPTORで25バイトの取得を要求します. すなわち,Control Inエンドポイントに80 06 00 02 00 00 19 00を送ります.

offset field データ 意味
0 bmRequestType 0x80 データ転送方向: Device→Host
Type : Standard
Recipient : Device
1 bRequest 0x06 GET_DESCRIPTOR
2 wValue 0x0002 ディスクリプタ型はConfiguration
4 wIndex 0x0000 インデクスは0
6 wLength 0x1900 データステージで転送するデータバイト数は19
そうすると,コンフィギュレーションに関するディスクリプタが返ってきます. これは次のようなフォーマットになっています.(はじめの9バイトは1つ前のGET_DESCRIPTORと同じデータです) ここで取得されるディスクリプタは全部で3つあります. (コンフィギュレーションディスクリプタが9バイト,インタフェースディスクリプタが9バイト,エンドポイントディスクリプタが7バイト)
offset field データ 意味
0 bLength 0x09 コンフィギュレーションディスクリプタは9バイト
1 bDescriptorType 0x02 ディスクリプタ型はConfiguration型
2 wTotalLength 0x0019 本コンフィギュレーションの長さは0x19(=25)バイト
4 bNumInterface 0x01 インタフェースの数は1つ
5 bConfigurationValue 0x01 本コンフィギュレーションを活かすためには0x01を設定する
6 iConfiguration 0x04 コンフィギュレーションを示す文字列ディスクリプタのインデクスは4
7 bmAttributes 0x80 アトリビュートを示す.USBキティはselfpoweredデバイスではなく,Remote wakeupに対応していない.
8 MaxPower 0x32 最大消費電力は100mA
9 bLength 0x09 ディスクリプタは9バイト
10 bDescriptorType 0x04 ディスクリプタ型はInterface型
11 bInterfaceNumber 0x00 Interfaceの数は0(0から始まるので実際はInterfaceは1つという意味)
12 bAlternateSetting 0x00 Alternate設定を使うために設定する値は0
13 bNumEndpoints 0x01 (エンドポイント0を除いた)エンドポイントの数は1つ
14 bInterfaceClass 0x00 Interfaceクラスは0(reserved for future use)
15 bInterfaceSubClass 0x00 Interfaceサブクラスは0(reserved for future use)
16 bInterfaceProtocol 0x00 Interfaceプロトコルは0
17 iInterface 0x05 Interfaceを示す文字列ディスクリプタのインデクスは5
18 bLength 0x07 ディスクリプタは7バイト
19 bDescriptorType 0x05 ディスクリプタ型はエンドポイント型
20 bEndpointAddress 0x81 エンドポイントはInエンドポイント,アドレスは1
21 bmAttribute 0x03 control転送を行なうエンドポイントである
22 wMaxPacketSize 0x0800 最大パケットサイズは8バイト(リトルエンディアンのため,0x0008と読む)
24 bInterval 0x0A 転送間隔は10msec(control転送エンドポイントに関しては無視される)
ここでは,usbkitのinfoで読んだディスクリプタの
  [Configuration 0]
    [Interface 0]
    Class .............. 0
    Subclass ........... 0
    Protocol ........... 0
      [Endpoint 0]
      MaxPacketSize .... 8
      Interval ......... 10
      Type ............. Interrupt
      Direction ........ Input
の部分が読み出せています.

usbkitのinfoで読める情報として,Manufacuturer StringやProduct Stringがありますが, これらは文字列ディスクリプタを読み出してやれば取得できます. 文字列ディスクリプタに関しては, Manufacturer Stringはインデクス1を指定し, Product Stringはインデクス2を指定し, Control Inエンドポイントに対してGET_DESCRIPTOR標準リクエスト(文字列ディスクリプタ)を送ってやることで取得できます (ここでは詳細な説明は省略します).

さて,キモのUSBキティを動かすための情報ですが, これにはControl Inエンドポイントに対して,ベンダリクエストを送ってやり, USBキティから返ってくるデータを取得することで実行されます. 具体的には,Control Inエンドポイントに対して, C2 03 2C 00 xx 00 08 00を送ってやることで 実行されます(xxに入る部分が以下に示すコマンドです).

offset field データ 意味
0 bmRequestType 0xC2 データ転送方向: Device→Host
Type : Vendor
Recipient : Endpoint
1 bRequest 0x03 SET_FEATURE?
2 wValue 0x2C00 意味は不明だが,決まっている
4 wIndex 0x???? コマンド部分
6 wLength 0x0800 データステージで転送するデータバイト数は8
コマンド部分はシリアルのときに使ったコマンドとほぼ同じです.
コマンド名 コマンド
初期化 0x20
ボタンイネーブル 0x28
不明 0x31
ボタンデータ読み取り 0x32
かしげ(戻し),腕ふり(腕おろし) 0x40
かしげ 0x44
腕ふり(腕あげ) 0x48
赤面(消灯) 0x50
赤面(点灯) 0x58
ハロー 0x60
こんにちは 0x61
またね 0x62
ちゅーせ〜ん 0x63
大当たり 0x64
すてきすてき 0x65
ラッキー 0x66
あれ〜? 0x67
タイマー 0x68
時間よ 0x69
0x6A
ピロリ 0x6B
ピピピ 0x6C
ピロリピロリピロリ 0x6D
ピピピピピピ... 0x6E
ピロリピロリピロリ... 0x6F


コマンドを送信した後に, データステージで受信する8バイトのデータのフォーマットを解析すると 次のようになっていました(解析した結果ですので,研究員の想像の部分があります).

offset データ 意味
0 0x42 不明.
1 0x?? ボタンデータ,またはコマンドに対応
2 0x2C00 wValueで送った0x2C00に対応
4 0x??00 wIndexで送ったコマンドに対応
6 0x0800 データステージで転送するデータバイト数(=8)に対応?


キティのボタンクリックに関する情報は, 上のコマンドで0x32(ボタンデータ読み取り)を実行し, 返ってくるデータから判断すればいいようです. (取りこぼすことがまれにあります)

解析した結果では,ボタンデータ読み取りを実行したときに, 返ってくるデータには以下のものがありました.

  • 0x30: ボタンは押下されていない
  • 0x34: 右ボタンが押下されている
  • 0x38: 左ボタンが押下されている
  • 0x3C: 両ボタンが押下されている
  • 0x61: 約80回毎に出るが,意味は不明
  • 0x70: 約80回毎に出るが,意味は不明
  • 0x71: まれに出るが,意味は不明
  • 0x01: とてもまれに出るが,意味は不明
  • 0x08: とてもまれに出るが,意味は不明
  • 0x09: とてもまれに出るが,意味は不明
  • 0x26: とてもまれに出るが,意味は不明
  • 0x4C: とてもまれに出るが,意味は不明
  • 0x69: とてもまれに出るが,意味は不明
ということで,USBキティドライバ中ではこれらのボタン読み取りに関しては adhocに対応しています.


実践研究

さて,お待たせしました.実践研究です.

2001/11/16: USBキティを追加したので,serialと混同しないようにUSBはUSBキティ,serialはserialキティと区別しました

例題1: serialキティを赤面させる

Hello Kitty Stationで赤面動作を確認したときのコマンドシーケンスは

  1. 0x40 [かしげ(戻し),腕ふり(腕おろし)]
  2. 0x58 [赤面(点灯)]
  3. 0x40 [かしげ(戻し),腕ふり(腕おろし)]
  4. 0x50 [赤面(消灯)]
  5. 0x40 [かしげ(戻し),腕ふり(腕おろし)]
  6. 0x50 [赤面(消灯)]

でした. なぜか[かしげ(戻し),腕ふり(腕おろし)]と[赤面(消灯)]を2回繰り返していますね. ここらへんは適当に(もしくはやたら保守的に)実装されているような気がします.

さて,実際に試してみましょう. 初期化シーケンスとして,以下のコマンドを送信しますが,初期化の前に 1回以上のデータ読み込みを行なうとよいようです.

■初期化シーケンス

  1. 1バイトReadする
  2. 初期化コマンド(0x20)送信
  3. ACK受信
  4. ボタンイネーブル(0x28)送信
  5. ACK受信

あぁ,何て楽勝な初期化シーケンスでしょう♪

では,まずはserial1のみに対応したでっちあげソフトで試してみましょう. ここでは,初期化シーケンスを叩いて,赤面させっぱなしにしてみます. コマンドとコマンドの間にsnooze()を入れていますが,この値は適当です.



/*
 Hello Kitty Example 1
 */
#include <DeviceKit.h>
#include <stdio.h>

int main(void)
{
  BSerialPort  serial1;
  int          seq = 0;
  char         buf;

  serial1.SetBlocking(false);
  serial1.SetDataRate(B_9600_BPS);
  serial1.SetDataBits(B_DATA_BITS_8);
  serial1.SetStopBits(B_STOP_BITS_1);
  serial1.SetFlowControl(B_NOFLOW_CONTROL);
  serial1.SetParityMode(B_NO_PARITY);
  serial1.SetTimeout(1000);
  serial1.ClearInput();
  serial1.ClearOutput();
  serial1.SetRTS(true);
  serial1.SetDTR(false);
  
  if ( serial1.Open("serial1") <=0 ) {
    printf("serial1 Open Error\n");
    return -1;
  }
  
  serial1.SetRTS(true);
  serial1.SetDTR(false);
  serial1.ClearInput();
  serial1.ClearOutput();
  
  if (serial1.Read(buf,1) == 1) {
    seq++;
    printf("%02d:                   KITTY 0x%02x\n", seq, buf & 0xff);
  }

  serial1.SetBlocking(true);

  // read 1 byte before intialize
  if (serial1.Read(buf,1) == 1) {
    printf("%02d:                   KITTY 0x%02x\n", seq, buf & 0xff);
    seq++;
  }

  snooze(1000000);
  // initialize command send
  buf = 0x20; serial1.Write(buf, 1);
  printf("%02d:  PC 0x%02x\n", seq, buf & 0xff);
  seq++;

  // read ACK
  if (serial1.Read(buf,1) == 1) {
    printf("%02d:                   KITTY 0x%02x\n", seq, buf & 0xff);
    seq++;
  }
  // button enable command send
  buf = 0x28; serial1.Write(buf, 1);
  printf("%02d:  PC 0x%02x\n", seq, buf & 0xff);
  seq++;

  // read ACK
  if (serial1.Read(buf,1) == 1) {
    printf("%02d:                   KITTY 0x%02x\n", seq, buf & 0xff);
    seq++;

  }
  
  // sekimen OFF
  buf = 0x50; serial1.Write(buf, 1);
  printf("%02d:  PC 0x%02x\n", seq, buf & 0xff);
  seq++;

  // read ACK  
  if (serial1.Read(buf,1) == 1) {
    printf("%02d:                   KITTY 0x%02x\n", seq, buf & 0xff);
    seq++;

  }
  // sekimen ON
  buf = 0x58; serial1.Write(buf, 1);
  printf("%02d:  PC 0x%02x\n", seq, buf & 0xff);
  seq++;

  // read ACK  
  if (serial1.Read(buf,1) == 1) {
    printf("%02d:                   KITTY 0x%02x\n", seq, buf & 0xff);
    seq++;

  }
  serial1.Close();
}

うーん,書いていてあまりにも簡単なので,これ以上書くのがバカらしく なってきました. ということで,例題1はここまでです.


例題2: serialキティデバイスドライバ

次に,デバイスドライバとして,serialキティをopen(), ioctl()で制御することを 考えました.これも適当にでっちあげてみました.

手動でインストールするには,/boot/home/config/add-ons/kernel/drivers/binに ドライバ本体(kitty_driver)をコピーし,/boot/home/config/add-ons/kernel/drivers/dev/misc/にドライバ本体へのシンボリックリンクを置いてください. (この説明でわからない人はすなおにバイナリをインストールしてください)
[2000/10/20 デバイスドライバ1.0から本体へのシンボリックリンクを/dev/miscへ変更しました.]

さて,要はシリアルですので,serial1ならI/Oポート0x3f8とかserial2なら I/Oポート0x2f8とかでアクセスできるわけです. で,9600bps, データビット8ビット,ストップビット1ビット,ノンパリに 強引に設定してから,コマンドを送信してやればよいわけですね.

で,設計としては,serial1のみに対応し,

  1. init_hardware(), uninit_driver(), init_driver()では何もせず,
  2. kitty_open()でシリアルポートの初期化,およびserialキティの初期化シーケンスを実行します.
  3. kitty_read()では何も実行しません.
  4. kitty_write()では何も実行しません.
  5. kitty_ioctl()で制御するようにします.
  6. kitty_close(), kitty_free()では何も実行しません.
というような手抜き設計です.
[2000/10/20 ドライバ1.0にて更新] で,設計としては,serial1のみに対応(ソース変更により対応可)し,
  1. init_hardware()でISAモジュールからisaポインタをゲット
  2. init_driver()ではロック変数is_openを初期化します.
  3. kitty_open()でシリアルポートの状態保存,初期化,およびキティ本体の初期化シーケンスを実行します.
  4. kitty_read()では何も実行しません.
  5. kitty_write()では何も実行しません.
  6. kitty_ioctl()で制御するようにします.
  7. uninit_driver()ではISAモジュールのisaポインタをプットします.
  8. kitty_close()ではシリアルポートをopenする前の状態に(できるだけ)戻します.
  9. kitty_free()ではロック変数is_openを再設定します.
というような手抜き設計です. serail2以降に対応させたい方は,ソースコード中のkitty.hの#define COM_BASEを serial1なら0x3F0に,serial2なら0x2F0に, serial3なら0x370に,serial4なら0x270に変更してください.

確認はしていませんが,デバッグ出力をONにしていると まともに動かない可能性があります. また,ver.1.0から2重起動をチェックするようになりましたので, キティデバイスドライバをopenしているアプリがある場合,2つ目以降の アプリがキティデバイスドライバをopenしようとした場合,openに失敗します.

さて,ドライバの動作ですが,結局,ioctl()での制御ONLYで, プリミティブなコマンドの送信のみできます.
ioctl()のmsgは以下のとおりとしました. (もうベタベタのキティに送信するコマンドのとおり(笑))

#define B_KITTY_FACE_LED_ON     0x58  // ASCII 'X'
#define B_KITTY_FACE_LED_OFF    0x50  // ASCII 'P'
#define B_KITTY_BUTTON_ENABLE   0x28  // ASCII '('
#define B_KITTY_BUTTON_DISABLE  0x20  // ASCII ' ' (SPACE)
#define B_KITTY_STEADY_POSITION 0x40  // ASCII '@'
#define B_KITTY_QUESTION        0x44  // ASCII 'D'
#define B_KITTY_HAND_UP         0x48  // ASCII 'H'
#define B_KITTY_HELLO           0x60  // ASCII 'a'
#define B_KITTY_JAPANESE_HELLO  0x61  // ASCII 'a'
#define B_KITTY_SEE_YA          0x62  // ASCII 'b'
#define B_KITTY_LOTO            0x63  // ASCII 'c'
#define B_KITTY_BINGO           0x64  // ASCII 'd'
#define B_KITTY_FANTASTIC       0x65  // ASCII 'e'
#define B_KITTY_LUCKY           0x66  // ASCII 'f'
#define B_KITTY_OH_NO           0x67  // ASCII 'g'
#define B_KITTY_TIMER           0x68  // ASCII 'h'
#define B_KITTY_ITS_TIME        0x69  // ASCII 'i'
#define B_KITTY_PI              0x6A  // ASCII 'j'
#define B_KITTY_PIRORI          0x6B  // ASCII 'k'
#define B_KITTY_PIPIPI          0x6C  // ASCII 'l'
#define B_KITTY_PIRORI2         0x6D  // ASCII 'm'
#define B_KITTY_PIPIPI2         0x6E  // ASCII 'n'
#define B_KITTY_PIRORI3         0x6F  // ASCII 'o'
で,こちらがserialキティドライバを利用して,ioctlで制御するアプリのサンプルです.
  1. 例題2のアプリ(ソースコードつき)
  2. 例題3のアプリ(ソースコードつき)

例題2は「あれ〜?」と発声させるだけのシンプルなアプリです.

例題3は標準入力(キーボード)からキー入力を1文字読み込んで,それに対応した コマンドでserialキティを動かすだけのアプリです. キー入力は上記ASCII文字に対応しています. (というか,どんな入力でもserialキティに送りつけてしまう極悪アプリです)

あまりの簡単さなので,もはや,説明など不要でしょう.

例題4: USBキティデバイスドライバ

さて,USBの解析も終ったので,USBキティを制御するドライバをでっちあげました. ioctl()による制御コマンドは同じですが,open()するデバイス名が違っています. (serialキティは/dev/misc/kittyに,USBキティは/dev/misc/usb_kittyになります)

設計としては,USBのどのポートでもOKで対応し(このへんはusbdが管理してくれている),

  1. init_hardware()では何もしません.
  2. init_driver()ではusbバスマネージャと通信する際に必要なモジュール情報の保存, usbdへのドライバ名の登録,挿抜時のコールバック関数の登録を行ないます.
  3. uninit_driver()ではinit_driver()で登録したモジュール情報や デバイス名文字列などをfreeします.
  4. mydev_open()でUSBキティの初期化を行ないます.
  5. mydev_read()ではUSBキティのボタンクリックデータを読みとります.
  6. mydev_write()では何も実行しません.
  7. mydev_ioctl()でUSBキティの動作を制御するようにします.
  8. mydev_close(), mydev_free()では何も実行しません.
というような本格設計です(今回は手抜きとはあえて言いません(^^). 処理の詳細に関してはドライバのソースを参照してください.

おわりに

キティデバイスドライバを利用するのはしごく簡単です. open()して,ioctl()するだけですので, BeWare作者の方,キティデバイスドライバを使って 新たなユーザインタフェースの境地を開拓してみませんか?

個人的には,biffとかのユーザ通知型のアプリとか,メーラとかの ユーザインタフェースに適していると思います.

 
[B技研TOPへ戻る] [B級なBeOSの研究へ戻る]
 
Copyright (c) 1998, 1999 Shuichi NAKAMURA All rights reserevd.
You can link this page freely.
counter(since 2000/08/09)