naoki86star

インターネットの片隅でなにかしら書いてみる

PICと 非接触ICカードリーダーつないでみる

 USBのデバイスで 非接触ICカードリーダー、SONYのRC-S380という型番*1のを持っている。PICのUSB-HOST機能でつないでどこまでできるかやってみた。
 自分のSUICAカードをカードリーダーにおいて、製造ID/製造パラメータ/システムコード*2というのを読み取れたであろうところまでできた。*3

 今回もPIC24FJ64GB002。MCCでMLA USB-HOST LITE有効でコード生成。これと別に追加で、github.com上のMLAからusb_host_generic.c(.h)を拝借。*4

  • usb_host_generic.cを利用することで恩恵にあずかったのはUSBHostGenericRead()/USBHostGenericWrite()、それからGENERIC_DEVICE構造体*5。usb_host_generic.c全般にもとののままでは、通信時に渡したり参照するエンドポイントの番号が固定値になっていて、残念ながら今回のデバイスとあってない。今回のトライではusb_host_generic.cの中で固定値修正。
  • RC-S380君とのやりとりの仕方は後述の通り、raspberry pi母艦でカードを読み取りして調べた。USB通信としてはBULK転送を使っている模様。usb_host_config.hに#define USB_SUPPORT_BULK_TRANSFERSを追加*6
  • usb_host_config.c#usbClientDrvTable[]に、usb_host_generic.cのUSBHostGenericInitialize, USBHostGenericEventHandler を列挙、DataEventHandlerはNULL*7
  • usb_host_config.c#usbTPLに受け入れるデバイスを追記。(usb_host_config.h)NUM_TPL_ENTRIES の数を合わせておく。


 RC-S380君とのインタフェースを調べるにあたり、raspberry pi3にカードリーダをつなぎ、nfcpyというpythonモジュールを利用して、どういう通信をしているかを観察することができた。example/sense.pyコマンドにデバッグオプションを渡して実行するとUSB通信層のログを出力してくれる。sense.pyではsrc/nfc/clf/__init__.py, src/nfc/clf/rcs387.pyが中心的なコードになる。
 カードを乗せずに実行した結果はこれ。引数の212Fというのはカードリーダシステムのタイプを指定するようで、212が通信速度Fはテクノロジータイプ(カードシステム F)elica?)を意図して命名したのかも。

pi@Goliath:~/github.com/nfcpy $ sudo python3 examples/sense.py -d -v 212F
284 ms [nfc.clf] searching for reader on path usb
285 ms [nfc.clf.transport] using libusb-1.0.22
288 ms [nfc.clf.transport] path matches '^(usb|)$'
295 ms [nfc.clf.device] loading rcs380 driver for usb:054c:06c3
306 ms [nfc.clf.transport] >>> 0000ff00ff00
318 ms [nfc.clf.rcs380] SetCommandType 01
318 ms [nfc.clf.transport] >>> 0000ffffff0300fdd62a01ff00
320 ms [nfc.clf.transport] <<< 0000ff00ff00
320 ms [nfc.clf.transport] <<< 0000ffffff0300fdd72b00fe00
321 ms [nfc.clf.rcs380] GetFirmwareVersion
321 ms [nfc.clf.transport] >>> 0000ffffff0200fed6200a00
322 ms [nfc.clf.transport] <<< 0000ff00ff00
323 ms [nfc.clf.transport] <<< 0000ffffff0400fcd7211101f600
323 ms [nfc.clf.rcs380] firmware version 1.11
323 ms [nfc.clf.rcs380] GetPDDataVersion
323 ms [nfc.clf.transport] >>> 0000ffffff0200fed6220800
324 ms [nfc.clf.transport] <<< 0000ff00ff00
325 ms [nfc.clf.transport] <<< 0000ffffff0400fcd72300010500
325 ms [nfc.clf.rcs380] package data format 1.00
326 ms [nfc.clf.rcs380] SwitchRF 00
326 ms [nfc.clf.transport] >>> 0000ffffff0300fdd606002400
327 ms [nfc.clf.transport] <<< 0000ff00ff00
327 ms [nfc.clf.transport] <<< 0000ffffff0300fdd707002200
327 ms [nfc.clf.rcs380] GetFirmwareVersion
328 ms [nfc.clf.transport] >>> 0000ffffff0200fed6200a00
328 ms [nfc.clf.transport] <<< 0000ff00ff00
329 ms [nfc.clf.transport] <<< 0000ffffff0400fcd7211101f600
329 ms [nfc.clf.rcs380] firmware version 1.11
330 ms [nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:001:038
330 ms [nfc.clf.rcs380] SwitchRF 00
330 ms [nfc.clf.transport] >>> 0000ffffff0300fdd606002400
331 ms [nfc.clf.transport] <<< 0000ff00ff00
331 ms [nfc.clf.transport] <<< 0000ffffff0300fdd707002200
23:45:56 None
332 ms [nfc.clf.rcs380] SwitchRF 00
333 ms [nfc.clf.transport] >>> 0000ffffff0300fdd606002400
333 ms [nfc.clf.transport] <<< 0000ff00ff00
334 ms [nfc.clf.transport] <<< 0000ffffff0300fdd707002200
334 ms [nfc.clf.transport] >>> 0000ff00ff00

 clfの実装を見ると、カードリーダーに対して、1)コマンド送信し、 2)コマンドに対するACKを受信し、3)コマンドに対するデータを受信する、のパターンで通信をしている。いくつかの準備コマンドを経て"InCommRF"というコマンド(0x04)でID読み出しが行われると理解。”コマンド”は1バイト+パラメータで構成されていて、固定ヘッダ、テイルと間にチェックサム?を付加している。PICで実装の場合は、送信ではUSBHostGenericWrite()、受信はUSBHostGenericRead()で動作してくれた。仕様としては全然調べていないけれどもACKの値判定などrcs380.pyの判定をそのまま参考にした。

 率直に書くと、INA219のときと同じように、上記実行出力のテキストを得たらあとはバイト配列をコピペしつつ、src/nfc/clf/rcs387.pyを参考にしてPICのコードを書き進めた。

 時間を費やしたのは、PICからBULK転送をさせるためにusb_host_generic.cをどう使えばいいか、みたいなところの実装部分。MCCが出力するUSBコードはBULK転送も用意してくれていたのだけれどもdefine追加で有効にする必要あった。usb_host.cの範囲含めて色々デバッグプリント差し込んで試行錯誤の末できるようになった。

 example/sense.pyは、カードリーダとの通信で、最初にSoftResetということでこのデバイスとしてのACKパケットを一つ送っている。これの応答はUSB通信上NACKとして戻るぽいのだが、clfのpythonではexceptionで処理されてる。自分今回example/sense.pyと同じように送ってみているが、このACK送信に対するreadをしていない。NACKが戻ってきたの扱いをうまくできてなく*8、その辺実装はもっとがんばらないといけないっぽい。


f:id:naoki86star:20210421143829j:plain

 やったものはこんな雰囲気・プロトタイプレベル。普段使いのSUICAに加えて、あとすごく前にJR東「東京駅開業100周年記念Suica」1枚購入していた*9のを思い出したので探し出してかざしてみたら別のIDがちゃんと(?!*10)表示された。

 まぁ、PICでICカードを識別できて、その次にやらせたいアクションを考えたとき、PICならではの用途はなかなか思いつかない、かな。。

*1:マイナンバーカードに対応とあるがそれの有用なユースケースまだ知らない

*2:ここの「FeliCa技術方式の各種コードについて」の説明にある情報に相当するはず

*3:デバイスはリーダー機能のみの機種と思う、なにかカードをおかしくてしてしまうことはなかろうが、なにか過剰にコマンド飛ばすとかカードかざし続けるとかしないよう注意する

*4:ダウンロードインストール版(v2018_11_26)にusb_host_generic.hのほうが存在しなかった

*5:どんな用途でも修正が必須と思うが、自分にはこれだけでもありがたい。

*6:USB_SUPPORT_INTERRUPT_TRANSFERSはコメントアウト。今回の範囲内ではRC-S380君はINTERRUPT転送を使ってないようなので。PICワールド、コードメモリ節約大事。

*7:#include "usb_host_generic.h"入れるのがちょっと気持ち悪い

*8:usb_host_generic.cでさらにやるべきことがありそう

*9:追加販売の通販版だったと思う

*10:sense.pyでみるのと同じ値が得られてる