2017年07月02日

ジョグシャトルで遊ぶ その3


 今回は、ジョグモードとシャトルモードの切り替えの説明と、Arduinoで動くサンプルプログラムを示します。

arduino.JPG

■ジョグとシャトルの切り替え

 ツマミを上から押すことで、ノック式ボールペンのような機構的な仕組みによりジョグモードとシャトルモードが切り替わります。この変化は、軸の上下を検出するフォトインタラプタから得られるモード信号を調べることで検出できます。
 これとは別に、シャトルモードで使うための、シャトルセンター位置を示す信号があります。これは羽が1枚だけ付いた回転する板が、シャトルのセンター位置の時に、フォトインタラプタの光を遮るという形で信号を生成します。
 この部品はシャトルモードの時はツマミ軸に噛み合って回転し、回転範囲を制限し、クリック位置を定めます。ジョグモードの時は軸から切り離され、スプリングでセンター位置に移動します。したがって、ジョグモードからシャトルモードに移行した時は、常にセンター位置から始まることになります。
 このような機械的な動作に合わせて、プログラムを作ります。動作は次のようになります。

・シャトルモードでは、回転パルスによってnShuttle変数を増減させる。
・センター信号の検出で、nShuttle変数を0にリセットする。
・ジョグモードでは、回転パルスによってJogFwd()関数かJogRev()関数を呼び出す。
・シャトルモードに移行した時は、センター信号を検出してから動作を始める。
・ジョグモードに切り替えた直後は、ツマミの回転検出を一定時間行わない(切り替え時に意図しない移動をしないようにするため)。


 これを実現するために、ジョグ/シャトルのモードの状態は、過渡的なものも含めて以下のようになります。

・状態1: ジョグモードに移行(回転検出を行わない)
・状態2: ジョグモード(回転検出を行う)
・状態3: シャトルモードに移行(センター未確認)
・状態4: シャトルモード(センター確認済み、回転検出を行う)

 これらの状態は、時間経過やジョグ/シャトルの動作によって別の状態に移動します。定常状態は状態2状態4で、ここでは回転の検出を行い、そして必要に応じて、状態2ではJogFwd()関数かJogRev()関数の呼び出しを行い、状態4ではnShuttleの更新を行います。
 状態1から状態2へは時間経過で、状態3から状態4はセンター検出で移行します。またすべての状態において、モード切替の検出を行います。

state.jpg

 状態は以下の定数で表します。


enum {
JsJogOn = 0, // シャトルからジョグに移行
kJsJog, // 現在、ジョグモードである
kJsShOn, // ジョグからシャトルに移行(センター未確定)
kJsSh // 現在、シャトルモードである(センター確定)
};


 状態はグローバル変数jsModeに保持し、この値で場合分けして、各状態での処理、別の状態への遷移を行います。kJsJogOnkJsShOnは過渡的な状態を示すもので、実際のジョグ動作はkJsJog状態で、シャトル動作はkJsSh状態で処理を行います。
 状態の遷移は、モード信号(GetMode()関数)でジョグモード(kJsJogOn)かシャトルモード(kJsShOn)かを判定します。シャトルモードに移行した場合、その後、センター位置をGetCenter()関数で判断してkJsShに遷移します。
 ジョグモード(kJsJogOn)に遷移した時は、ツマミを押す操作で多少ツマミが回転してしまうため、それをカウントしないように500ミリ秒ほど待機してから、実際にジョグ操作を行う状態(kJsJog)に移行します。
 シャトル処理、ジョグ処理は、ChkRot()関数で回転を検出します。この関数はGetClk()関数で現在のΦ1、Φ2信号を読み込み、以前の信号の状態(prev変数)を組み合わせてテーブルを引き、動作内容を返します。この値に応じて、ジョグモードの場合はJogFwd()関数かJogRev()関数を呼び出し、シャトルモードではnShuttle変数を増減します。またnShuttle変数が変化した時は、JogShuttle()関数は非0を返します。
 シャトルモードでの処理は、常にセンター信号を監視し、センターであればnShuttle変数を0にリセットします。実はここでちょっと問題があります。このジョグ/シャトルユニットは、センターとして認識される角度範囲が意外と広く、この範囲中でカウントが進むことがあるのです。そのためカウントミスが発生し、変数の増減で0になったにも関わらず、センター信号が検出ず、さらに進んでセンターと判断されてしまいます。具体的には、2、1、0、-1、0(センター検出)という出力になってしまいます。このような動作はまずいので、カウントが0になるのにセンター信号が検出されない時は、カウントを0にせず、前の値を維持し、0になるのはセンター信号を検出した時だけとしています。
 JogShuttle()関数を呼び出す前に、変数の初期化が必要です。これは、prev変数に最初の状態を読み込む、jsModeに最初の状態を読み込むといった処理を行います。

 これらの処理を行うプログラム例(Arduino用)を以下に示します。関数JogShuttle()がジョグ/シャトルのためのポーリングルーチンです。この関数はジョグ/シャトルの信号線を読み込み、以前の状態と組み合わせて動作を判定し、必要な作業を行うという処理を1回だけ行います。なので、実際にジョグ/シャトルを使う際は、この関数を繰り返し呼び出す必要があります。呼び出し間隔が長く、その間にツマミが何ステップ分も回されると、データの取りこぼしとなります。ジョグをすばやく回した時でもパルスの取りこぼしがないようにするには、1秒間に数百回程度は呼び出す必要があるでしょう。
 また、この関数を呼び出す前に、最初に使う以前の状態データ、ジョグ/シャトルの状態データを初期化しておく必要があります(setup()関数中で実施)。

 以下のプログラム例は、ATmega328のArduino用のもので、信号入力はすべて別関数として定義しています(GetClk()GetMode()GetCenter())。初期化はsetup()で、メイン部分のloop()は繰り返し呼び出されます。


//
// ジョグ/シャトルのテストプログラム (Arduino ATmega328)
//

// 回転を検出するテーブル
enum {
kRotNotMove = 0,
kRotFwd,
kRotRev,
kRotMiss
};

// クロックの変化パターンから回転を判定
const unsigned char jsPhPat[16] = {
kRotNotMove, // 00 -> 00 Not move
kRotFwd, // 00 -> 01 +
kRotRev, // 00 -> 10 -
kRotMiss, // 00 -> 11 Miss
kRotRev, // 01 -> 00 -
kRotNotMove, // 01 -> 01 Not move
kRotMiss, // 01 -> 10 Miss
kRotFwd, // 01 -> 11 +
kRotFwd, // 10 -> 00 +
kRotMiss, // 10 -> 01 Miss
kRotNotMove, // 10 -> 10 Not move
kRotRev, // 10 -> 11 -
kRotMiss, // 11 -> 00 Miss
kRotRev, // 11 -> 01 -
kRotFwd, // 11 -> 10 +
kRotNotMove // 11 -> 11 Not move
};

// ジョグモードとシャトルモードの遷移
enum {
kJsJogOn = 0, // ジョグモードに移行
kJsJog, // ジョグモード確定
kJsShuttleOn, // シャトルモードに移行
kJsShuttle // シャトルモード確定
};

// 使用するグローバル変数
int jsMode;
unsigned long tMode;
int nShuttle;
unsigned char prev;

//
// セットアップ
//

// ピンの割り当て
const int Clk1 = 2;
const int Clk2 = 3;
const int Center = 4;
const int Mode = 5;

void setup()
{
// ピンの動作を定義
pinMode(LED_BUILTIN, OUTPUT);

pinMode(Clk1, INPUT_PULLUP);
pinMode(Clk2, INPUT_PULLUP);
pinMode(Center, INPUT_PULLUP);
pinMode(Mode, INPUT_PULLUP);
Serial.begin(9600);

// 初期状態を取得
jsMode = (GetMode() == HIGH) ? kJsJogOn : kJsShuttleOn;
prev = GetClk();
nShuttle = 0;
tMode = millis(); // 現在時刻を取得

return;
}

//
// メインループ
//
void loop() {
// ひたすら繰り返し呼び出す
if (JogShuttle()) {
// シャトル値が更新された
Serial.print("S=");
Serial.println(nShuttle);
}

return;
}

//
// ジョグ/シャトルの状態をスキャンし、必要に応じて適切な処理を行う
// シャトル値が変化した時は、非0を返す
//

int JogShuttle()
{
unsigned char act;
int ret = 0; // シャトル値に変化があった場合、1を返す

// 状態の変化の処理、現在の状態での処理など
switch (jsMode) {
case kJsJogOn: // ジョグモードに切り替わった
// モード変更の判定
if (GetMode() == LOW) { // シャトルモードに移行
jsMode = kJsShuttleOn;
break;
}
// 一定時間待ってからジョグモードに移行(回転検出はしない)
if (millis() >= (tMode + 500)) {
jsMode = kJsJog;
prev = GetClk();
}
break;

case kJsJog: // ジョグモード
// モード変更の判定
if (GetMode() == LOW) { // シャトルモードに移行
jsMode = kJsShuttleOn;
break;
}
// ジョグの回転の検出
act = ChkRot();
if (act == kRotFwd) {
JogFwd();
} else if (act == kRotRev) {
JogRev();
}
break;

case kJsShuttleOn: // シャトルモードに切り替わった
// モード変更の判定
if (GetMode() == HIGH) { // ジョグモードに移行
jsMode = kJsJogOn;
tMode = millis();
nShuttle = 0;
ret = 1;
break;
}
// センターを確認してシャトルモードに移行
if (GetCenter() == HIGH) { // シャトルがセンター位置
jsMode = kJsShuttle;
nShuttle = 0;
ret = 1;
prev = GetClk();
}
break;

case kJsShuttle: // シャトルモード
// モード変更の判定
if (GetMode() == HIGH) { // ジョグモードに移行
jsMode = kJsJogOn;
tMode = millis();
nShuttle = 0;
ret = 1;
break;
}
if (GetCenter() == HIGH) { // シャトルがセンター位置
if (nShuttle != 0) {
ret = 1;
}
prev = GetClk();
nShuttle = 0;
} else {
// シャトル回転の検出
act = ChkRot();
if (act == kRotFwd) {
if (nShuttle != -1) { // センターの例外処理
nShuttle++;
ret = 1;
}
} else if (act == kRotRev) {
if (nShuttle != 1) { // センターの例外処理
nShuttle--;
ret = 1;
}
}
}
}
return ret;
}

// ジョグ操作に対応する処理
void JogFwd()
{
Serial.print(">");
return;
}

void JogRev()
{
Serial.print("<");
return;
}

// 回転の検出
int ChkRot()
{
unsigned char t;
unsigned char pat;
unsigned char action;

t = GetClk();
pat = (prev << 2) | t;
action = jsPhPat[pat];
prev = t;

if (action == kRotMiss) {
Serial.print("*");
}

return action;
}

// 2相クロックの読み込み (Arduino用コード)
unsigned char GetClk()
{
unsigned char val;

val = (digitalRead(Clk1) == HIGH) ? 2 : 0;
if (digitalRead(Clk2) == HIGH) {
val |= 1;
}
return val;
}

// モード信号の読み込み (Arduino用コード)
int GetMode()
{
return digitalRead(Mode);
}

// センター信号の読み込み (Arduino用コード)
int GetCenter()
{
return digitalRead(Center);
}


posted by masa at 21:07| 電子工作