Arduino+RC-S620/SでSoftwareSerial通信が上手くいかない

2012-04-25   treby   IDm Snatcher  , , このエントリーをはてなブックマークに追加

昨日試したサンプルプログラムのソフトウェアシリアル通信対応版の作成に挑戦している。

Arduinoでソフトウェアシリアル通信を行うためには、Arduino標準ライブラリのSoftwareSerialを使うと良いと思われる。この標準ライブラリ、かつては機能制限が多くNewSoftSerialというものが代替としてよく利用されていたようだ。その実状もあってか、Arduino 1.0になってそのNewSoftSerialライブラリが本家のSoftwareSerialに置き換わった。つまり、かつてのNewSoftSerialはArduino 1.0からのSoftwareSerialと同等であるとみなして問題ない(多分)。

ソースコードの改造はほぼ機械的で、ハードウェアシリアル通信を表すSerialを、ソフトウェアシリアル通信のものに置き換えるだけだ。修正にあたっては「なんでも作っちゃう、かも。 Arduinoで遊ぼう – JPEGカメラモジュールで写真を撮る」を参考にした。

手を入れたのは、Arduino用RC-S620/SライブラリであるRCS620S.cpp とRCS620S.h、そして付属するサンプルスケッチFeliCaPush.inoである(つまり全部)。次にコードの修正部分を示す。

RCS620S.h

SoftwareSerialクラスの宣言

/* --------------------------------
 * Class Declaration
 * -------------------------------- */

class SoftwareSerial;				// Add
class RCS620S
{

コンストラクタの引数追加

public:
    RCS620S(SoftwareSerial *softSerial = NULL);		// original -> RCS620S();

    int initDevice(void);
    int polling(uint16_t systemCode = 0xffff);
    ...

ソフトウェアシリアルポートへの参照変数の定義

private:
	SoftwareSerial *serialPort;		// Add

    int rwCommand(
        const uint8_t* command,
        uint16_t commandLen,
        uint8_t response[RCS620S_MAX_RW_RESPONSE_LEN],
        uint16_t* responseLen);
        ...

RCS620S.cpp

include部分

#include "RCS620S.h"
#include 
#include 
#include 
#include "Arduino.h"			// WProgram.h -> Arduino.h
#include "Print.h"
#include "SoftwareSerial.h"		// HardwareSerial.h -> SoftwareSerial.h

コンストラクタの修正

/* ------------------------
 * public
 * ------------------------ */

RCS620S::RCS620S(SoftwareSerial *softSerial)		// Original -> RCS620S::RCS620S()
{
    this->serialPort = softSerial;			// Add
    this->timeout = RCS620S_DEFAULT_TIMEOUT;
}

シリアル通信部分の変更

void RCS620S::writeSerial(			// Modified
    const uint8_t* data,
    uint16_t len)
{
    // debug code begin
    uint16_t nwrite = 0;
    Serial.print("Sending Data : ");
    while(nwrite < len) {
       Serial.print(data[nwrite],HEX);
       if(nwrite+1 < len)Serial.print("-");
       nwrite++;
    }
    Serial.println("");
    // debug code end

    if (serialPort) {
        serialPort->write(data, len);
    } else {
        Serial.write(data, len);
    }
}

int RCS620S::readSerial(			// Modified
    uint8_t* data,
    uint16_t len)
{
    uint16_t nread = 0;
    unsigned long t0 = millis();

	Serial.print("received : ");		// debug code

    while (nread < len) {
        if (checkTimeout(t0)) {
            return 0;
        }

        if (serialPort)	{
			if (serialPort->available() > 0) {
				data[nread] = serialPort->read();
				Serial.print(data[nread], HEX);		// debug code
				if(nread+1 < len)Serial.print("-");			// debug code
				nread++;
			}
		} else {
			if (Serial.available() > 0) {
				data[nread] = Serial.read();
				nread++;
			}
		}
    }

    Serial.println("");			// debug code

    return 1;
}

void RCS620S::flushSerial(void)		// Modified
{
	if (serialPort) {
		serialPort->flush();
	} else {
		Serial.flush();
	}
}

FeliCaPush.ino

宣言、setup関数

#include 
#include 
#include 
#include      // Add

#define COMMAND_TIMEOUT  400
#define PUSH_TIMEOUT    2100
#define POLLING_INTERVAL 500

#define LED_PIN 13

SoftwareSerial softSerial(2,3);    // Add
RCS620S rcs620s(&softSerial);          // Modified
int waitCardReleased = 0;

void setup()
{
  int ret;
  
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  softSerial.begin(115200);      // Modified
  Serial.begin(9600);           // for Serial Monitor
//  Serial.begin(115200);

  ret = rcs620s.initDevice();
  while (!ret) {}
}

問題解決に当たり、せっかくなので、ソフトウェアシリアルポート(今回は2ピン、3ピン)から送受信されているデータをPCにハードウェアシリアル送信してみた。それをシリアルモニタで観察した図を次に示す。

通信の様子

Arduino – RC-S620/S間のジャンパ線を引っこ抜くと、受信文字列が出ないことが確認できた。このことからちゃんとソフトウェア送信ポート(3ピン)からデータをシリアル送信して、受信ポート(2ピン)で受信することはできていると思われる。

ただ、問題はその受信データである。ACKフレームを示すバイト列「00-00-FF-00-FF-00」が返って来なければならないのに、実際には上図に示すように「00-80-00-FF-80-00」が返ってきている。この結果は何回やっても変わらず、それが私の頭を悩ませている。

解決に向けて

同様のロジックで、かつハードウェアのシリアル通信機能を利用している時は問題なく動くことから、ロジックは正しいと考えられる。問題はおそらくSoftwareSerial にあるのだろう。

実は、前出のソースコード中でさりげなく使っているSoftwareSerial::flush()関数は、Arduino標準リファレンスのドキュメントに載っていない。ただし実装としてはあるために一応使うことはできる。フォーラムでの議論を流し読みした感じ、そもそもハードウェア版シリアルポートであるSerial.flush()の実装ですら途中で仕様が変わっているようなことが書いてある。

「なんでも作っちゃう、かも。 Arduino 1.0 リリース」によると、Serial送信の非同期化と題して以下の記述が見える。

Serial.print()などデータの送信はバックグラウンドで行われるようになりました。Serial.flush()は受信データを破棄する処理から、送信データがすべて送信されるまで待つ処理に変更されました。

ハードウェアシリアル通信のflush関数(送信データがすべて送信されるまで待つ処理)で上手く行っている以上、ソフトウェアシリアル通信のflush関数でも同等の処理をするように変更する必要があると思われる。

のだが……これはなかなかに骨が折れそうである。しばらく保留するか。