Monthly Archives: 11月 2012

ArduinoでRELET(FeliCa電子マネー残高照会機)モドキを作ろう

作ろうと考えるようになったいきさつ

以前、家電量販店を歩いてるとSuicaやEdyなどの電子マネーの残高を確認できる電子マネービュアー「リレット」EV10 | KING JIMが販売されており思わず『買いたい』と思いましたが、値札を見たらお値段が・・・・。度々家電量販店に行くも今度買おうと自分に言い聞かせてたらいつの間にか生産中止になってしまたようで・・・・・。仕方ないから自分で自由に機能を拡張できるFeliCa電子マネー残高照会機を作ろうと思い立ちました。

初代残高照会機(Arduino UNO + PaSoRi)

実は、以前にも同様の目的でFeliCaの残高照会機を作った事があったのですが、想像よしていたものより大きく据え置きにするしかなかったため今回これよりも遥かに小さい物を作成しようと考えました。

道具の準備

・ハンダ線
・ハンダ吸い取り線
・フラックス
・熱収縮チューブ
・ハンダこて
・こて台
・こて拭き
・精密ピンセット
・カッターナイフ

電子工作をする上でとくに特別な物はないですが、結構細かい部品をハンダ付けするため精密ピンセットとフラックスがあるといいかもしれません。

部品の準備

・FeliCa リーダー・ライター RC-S620S
・FeliCa RC-S620S ピッチ変換基板のセット(フラットケーブル付き)
・Arduino Pro mini 5V
・SB0802GN
・AS1322A/V2
・Polymer Lithium Ion Battery – 110mAh
・JST-PHオスコネクター
・タクトスイッチ
・1μF積層セラミックチップコンデンサ 2個
・1μF積層セラミックコンデンサ 1個
・10kΩチップ抵抗 2個
・ビニール線

・FT232RL搭載小型USB-シリアルアダプタ 5V
・LiPo Charger Basic – Micro-USB

 今回手軽に持ち運べるよう小型化するために、Arduino Pro miniを選択しましたが、通常8Mhzな3.3V版では115200bpsの転送レートに対応していないためRC-S620/Sと通信することが出来ないので5V版を準備してください。
 なお、同じく軽量化小型化のためにLCD液晶にStrawberryLinux製のミニI2C液晶モジュールを使用しました。このパーツを使用すると一部チップ部品のハンダ付けが必要となりますが、I2C通信を利用する事により使用するピンを劇的に減らすことが可能になります。

ハードウェアの作成

Arduinoにシリアル通信用の足をハンダ付けし、裏面にプルアップ抵抗用にチップ抵抗(10kΩ)を2個ハンダ付けします。

StrawberryLinux製のミニI2C液晶モジュールはコンデンサを3個ハンダ付けしなければならないので、表面にチップコンデンサ(1μF)を2個、裏にコンデンサ(1μF)を1個ハンダ付けします。

StrawberryLinux製の昇圧型DC-DCコンバータモジュールは5V引き出す場合ハンダジャンパーが必要になるのでハンダでショートさせます。

最後にこのような配線になるように各種パーツをハンダ付けしていきます。バッテリーは使い切ると外して充電器に接続し充電します。
※ 当初、CR2032で動かす予定でしたが大電流放電が苦手なようでうまく動きませんでした。
※ RC-S620/Sは配線を間違うと一瞬で壊れます。私は試作の時にうっかり2台も壊してしまいまいした・・・・
※ 今回何となく不安でRAWに昇圧モジュールのOUTを繋いでいますが電圧を5Vに調整しているのでVCCに繋いでも一応動いたりします。

これでハードは完成です。

ライブラリの準備

Arduino向けRC-S620/S制御ライブラリ
I2C液晶ライブラリ – Okiraku Programming

それぞれのライブラリをダウンロードしlibrariesフォルダーに入れ、RC-S620/S制御ライブラリ“RCS620S.cpp”を開き#include “WProgram.h”を#include “Arduino.h”に書き換えて上書き保存します。I2C液晶ライブラリも“LCD_I2C.h”の中身を下記から次のように通り書き換えます。
※ 実は、“LCD_I2C.h”の書き換えは#include “Arduino.h”じゃなくて#include <wiring_private.h>でもよかったり?

書き換え前

#ifndef LCD_I2C_h
#define LCD_I2C_h
extern "C" {
#include <math.h>
#include <wiring.h>
}
#include <Wire.h>

書き換え後

#ifndef LCD_I2C_h
#define LCD_I2C_h
#include <math.h>
#include "Arduino.h"
#include <Wire.h>

ソフトウエアの作成

アトリエのどか様の公開されているスケッチを参考に実際にSuica、PASMO、Edy、nanaco、waonなどのFeliCa電子マネーの残高を表示させるように記述しました。

※ Arduino Pro miniではRC-S620/Sを接続しているとスケッチをArduinoに書き込めないという問題が発生しました。スケッチを書き込む場合は一度ArduinoとRC-S620/Sを繋ぐフレキケーブルを外しておく必要があります。

#include <Wire.h>
#include <LCD_I2C.h>
#include <RCS620S.h>

// Circuit
// RESET    - 1 RST
// Analog 5 - 2 SCL (internally pulled up)
// Analog 4 - 3 SDA (internally pulled up)
// GND      - 4 VSS
// 5V       - 5 VDD

// RCS620S
#define COMMAND_TIMEOUT               400
#define POLLING_INTERVAL              500
#define RCS620S_MAX_CARD_RESPONSE_LEN 30

// FeliCa Service/System Code
#define CYBERNE_SYSTEM_CODE           0x0003
#define COMMON_SYSTEM_CODE            0xFE00
#define PASSNET_SERVICE_CODE          0x090F
#define EDY_SERVICE_CODE              0x170F
#define NANACO_SERVICE_CODE           0x564F
#define WAON_SERVICE_CODE             0x680B

RCS620S rcs620s;

void setup(){
  LCD.begin(5);
  Serial.begin(115200);
}

void loop(){
  uint32_t balance;
  uint8_t buf[RCS620S_MAX_CARD_RESPONSE_LEN];
  
  rcs620s.timeout = COMMAND_TIMEOUT;
  
  // サイバネ領域
  if(rcs620s.polling(CYBERNE_SYSTEM_CODE)){
    // Suica PASMO
    if(requestService(PASSNET_SERVICE_CODE)){
      if(readEncryption(PASSNET_SERVICE_CODE, 0, buf)){
        // Little Endianで入っているPASSNETの残高を取り出す
        balance = buf[23];                  // 11 byte目
        balance = (balance << 8) + buf[22]; // 10 byte目
        // 残高表示
        printBalanceLCD("PASSNET", &balance);
      }
    }
  }
  
  // 共通領域
  else if(rcs620s.polling(COMMON_SYSTEM_CODE)){
    // Edy
    if(requestService(EDY_SERVICE_CODE)){
      if(readEncryption(EDY_SERVICE_CODE, 0, buf)){
        // Big Endianで入っているEdyの残高を取り出す
        balance = buf[26];                  // 14 byte目
        balance = (balance << 8) + buf[27]; // 15 byte目
        // 残高表示
        printBalanceLCD("Edy", &balance);
      }
    }
    
    // nanaco
    if(requestService(NANACO_SERVICE_CODE)){
      if(readEncryption(NANACO_SERVICE_CODE, 0, buf)){
        // Big Endianで入っているNanacoの残高を取り出す
        balance = buf[17];                  // 5 byte目
        balance = (balance << 8) + buf[18]; // 6 byte目
        balance = (balance << 8) + buf[19]; // 7 byte目
        balance = (balance << 8) + buf[20]; // 8 byte目
        // 残高表示
        printBalanceLCD("nanaco", &balance);
      }
    }
    
    // waon
    if(requestService(WAON_SERVICE_CODE)){
      if(readEncryption(WAON_SERVICE_CODE, 1, buf)){
        // Big Endianで入っているWaonの残高を取り出す
        balance = buf[17];                  // 21 byte目
        balance = (balance << 8) + buf[18]; // 22 byte目
        balance = (balance << 8) + buf[19]; // 23 byte目
        balance = balance & 0x7FFFE0;       // 残高18bit分のみ論理積で取り出す
        balance = balance >> 5;             // 5bit分ビットシフト
        // 残高表示
        printBalanceLCD("waon", &balance);
      }
    }
  }
  
  // デフォルト表示
  else{
    LCD.clear();
    LCD.move(0);
    LCD.print("Touch");
    LCD.move(0x44);
    LCD.print("Card");
  }
  
  rcs620s.rfOff();
  delay(POLLING_INTERVAL);
}

// request service
int requestService(uint16_t serviceCode){
  int ret;
  uint8_t buf[RCS620S_MAX_CARD_RESPONSE_LEN];
  uint8_t responseLen = 0;
  
  buf[0] = 0x02;
  memcpy(buf + 1, rcs620s.idm, 8);
  buf[9] = 0x01;
  buf[10] = (uint8_t)((serviceCode >> 0) & 0xff);
  buf[11] = (uint8_t)((serviceCode >> 8) & 0xff);

  ret = rcs620s.cardCommand(buf, 12, buf, &responseLen);
  
  if(!ret || (responseLen != 12) || (buf[0] != 0x03) ||
      (memcmp(buf + 1, rcs620s.idm, 8) != 0) || ((buf[10] == 0xff) && (buf[11] == 0xff))) {
    return 0;
  }

  return 1;
}

int readEncryption(uint16_t serviceCode, uint8_t blockNumber, uint8_t *buf){
  int ret;
  uint8_t responseLen = 0;
  
  buf[0] = 0x06;
  memcpy(buf + 1, rcs620s.idm, 8);
  buf[9] = 0x01; // サービス数
  buf[10] = (uint8_t)((serviceCode >> 0) & 0xff);
  buf[11] = (uint8_t)((serviceCode >> 8) & 0xff);
  buf[12] = 0x01; // ブロック数
  buf[13] = 0x80;
  buf[14] = blockNumber;

  ret = rcs620s.cardCommand(buf, 15, buf, &responseLen);

  if (!ret || (responseLen != 28) || (buf[0] != 0x07) ||
      (memcmp(buf + 1, rcs620s.idm, 8) != 0)) {
    return 0;
  }

  return 1;
}

void printBalanceLCD(char *card_name, uint32_t *balance){
  char result[8];
  sprintf(result, "%u", *balance);
  LCD.clear();
  LCD.move(0);
  LCD.print(card_name);
  LCD.move(0x40);
  LCD.print(0xE6);
  LCD.print(0x20);
  LCD.print(result);
  return;
}

作成過程

Arduino Pro miniとチップ抵抗のハンダ付けが終わった写真

ミニI2C液晶モジュールとチップコンデンサのハンダ付けが終わった写真

ミニI2C液晶モジュールとコンデンサのハンダ付けが終わった写真

全ての組立終わり、スケッチの書き込みの準備中の写真

作成例

作成したハードにスケッチを書き込み、調度よさそうな大きさのケースに格納してみました。



機能拡張のヒント

地方の各種交通機関のFeliCaカードに対応させても良いかもしれません。
残高と一緒に経由駅を表示させたり購入履歴を表示できるようにしても良いかもしれません。
おさいふケータイなどの三者間通信に対応しても面白いかもしれません。

各種パーツの図の配布

今回Arduino Pro miniやミニI2C液晶モジュールなどの図を作成しましたのでpng形式で提供させて頂きます。
FeliCa残高照会機 パーツ一覧

参考

かお(・v・)もじ SF Checker – アトリエのどか
第四章 低電圧I2C液晶ディスプレイ
felicalib プロジェクト Wiki
ICカードのフォーマット解析
I2C液晶ライブラリ – Okiraku Programming