ソーマ&るいのマイコン備忘録

マイコン関連の備忘録的なこと

ロータリーエンコーダーを使う

c++で利用可能な機械式のロータリーエンコーダーでカウントをするクラスを作成して見ました。

機械式ロータリーエンコーダー以下(RE)は構造が簡単なため、手ごろな価格で入手できます、よくあるプログラムでは前回と今回のビットパターンから4ビットのテーブルを作成してINC/DEC/STOP/ERR(実際には+1,-1,0)のデータを加算するという手法が使われますが、安価なREではチャタリングが多く上手く検出できないケースも多いです。

今回はタイマーによる定期的な割り込み、またはある程度速度が見込めるポーリングで利用可能なREの検出クラスを紹介します。

 

使い方は RENC XXX;

インスタンスを作成し

XXX,Init( stbs );

で使用前に初期化します、stbsでサンプリング中に接点の状態を安定化させる定数を設定し、内部の状態をリセットします、この初期化は何度呼んでも大丈夫です。

 

XXX.Samp( unsigned char pa,unsigned char pb );

をタイマーなりポーリングなりで定期的に呼び出します。

pa pb は REのそれぞれの相の接点の状態を伝えます、(0でONです)、内部では0かそうでないかを聞いているだけなので、特定のビットパターンでなくともOKです。

paが逆転、pbが正転です。

 

XXX.Get( *buf );

この関数が1を返したら検出動作があったことを示します、それまでにカウントされた値が(*buf)に代入され、クラス中のカウントはクリアされます、ただし取り込みタイミングによってはカウント数差し引き0が入ることもあります(バグではありません)。

サンプルがポーリングで行われる場合は、この関数をサンプルと同じ時系列で呼び出すと効率が悪いので、stbsで指定した回数*2回以上サンプルが呼ばれたらGetを一回呼び出すようにすると無駄がありません(理由は下記のとおり)。

 

初期値のstbsの役割ですが、サンプリングの際に以下の動作が行われます。

初期状態では接点OFFとします、ここからONに変更するためにはONが連続してstbs回続くことが条件となります、要するに一度でも現在と同じ状態が入力されたら”チャタリングしている”とみなされ、現状維持となります、ONからOFFも同じです、言い方を変えれば極端なヒステリシス動作を行っています。

自分の持っているREは某秋葉の愛のある激安ショップのもので、割り込み1meseに対しstbs5~10でそこそこ動作しています。

 

検出の手順ですが、チャタリングをとった後のビットパターンが前回と変わっていたら検出動作を実行します、パターンが同じなら何もせず戻るのでCPU時間を有効に使うことが出来ます。

検出状態(ステート)は ウエイト、方向決定、加算、減算、の四つです。

 

ウエイト

接点パターンが0になったらステートを方向決定にします、また途中で例外が起きた場合もこの状態に戻ります。

方向決定

接点パターンが逆方向のみONなら減算、順方向のみなら加算、どちらもONなら例外でウエイトに戻します。

加算

このステートに入った後はシーケンスをチェックします。

同時ON、減算のみON、どちらもOFF、この三つの動作が順に行われれば正しい動作とみなし内部のcntをincし、ステートを方向決定に戻します。

途中で例外が発生すればアイドルに戻します。

減算

加算とは逆のシーケンスをチェックします。

 

少しでも速度を稼ぐためにswitch ~case の代わりに関数のジャンプテーブルを使いましたが、例外(ありえないですが)の除去があるため、それほど速度は変わらないかもしれません、速度が必要ならif聞かないでstateでそのまま呼び出しても問題が無いかと思います。

 

↓ヘッダファイルです↓

//
// 機械式ロータリーエンコーダーライブラリ
//

 
//
// チャタリング除去クラス
//
class RECON {
private:
int tmax;
int tcnt;
int level;
public:
void Reset( int stb );
int Get( unsigned char con ); // con = 0で接点ON
};

//
// エンコーダー
//
class RENC {
private:
long cnt; // REの操作で回転でカウントされた値
int flg; // カウント動作が行われたらこのフラグを立てる、Getによる取得があればクリア
int state; // 検出状態フラグ
int ptn; // チャタリング除去された後の現在の接点のパターン
int lastptn; // 前回の接点パターン
int step; // INC/DECのシーケンス監視カウンタ

RECON CON1;
RECON CON2;
// 状態変移により呼ばれる関数郡
void ReWait( void );
void ReCheck( void );
void ReInc( void );
void ReDec( void );
static void ( RENC::*fncp[ ] )( void );

public:
//
// stbsはチャタリング除去のサンプル回数
//
void Init( int stbs );

//
// paが先で逆転,pbが先で正転 (0でON)
//
void Samp( unsigned char pa,unsigned char pb );
int Get( long *buf );
};

 

↓ソースファイルです↓

//
// 機械式ロータリーエンコーダーライブラリ
//
#include "ReLib.h"

#define RE_STATE_WAIT (0)
#define RE_STATE_CHECK (1)
#define RE_STATE_INC (2)
#define RE_STATE_DEC (3)
#define RE_POLE_A (2)
#define RE_POLE_B (1)
#define RE_SEQ_END (2)

//
// 検出シーケンスのパターンテーブル
//
static int inc_seq = { (RE_POLE_A | RE_POLE_B),RE_POLE_B,0 };
static int dec_seq
= { (RE_POLE_A | RE_POLE_B),RE_POLE_A,0 };


void RECON::Reset( int stbs ) {
tmax = stbs;
tcnt = 0;
level = 0;
}

int RECON::Get( unsigned char con ) {

if ( level ) {
if ( !con ) {
tcnt = tmax;
} else {
if ( tcnt ) {
tcnt --;
} else {
level = 0;
}
}
} else {
if ( con ) {
tcnt = tmax;
} else {
if ( tcnt ) {
tcnt --;
} else {
level = 1;
}
}
}

return level;

}


//
// 分岐関数郡
//
// 開始直後or例外発生時(接点の組み合わせがニュートラルになるまで待機)
void RENC::ReWait( void ) {

step = 0;
if ( !ptn ) state = RE_STATE_CHECK;

}

void RENC::ReCheck( void ) {
if ( RE_POLE_A == ptn ) {
state = RE_STATE_INC;
} else if ( RE_POLE_B == ptn ) {
state = RE_STATE_DEC;
} else if ( ( RE_POLE_A | RE_POLE_B ) == ptn ) {
state = RE_STATE_WAIT; // 異常パターン検出に付き待機へ
}

}
void RENC::ReInc( void ) {
if ( ptn == inc_seq[ step ] ) {
step ++;
if ( step >= RE_SEQ_END ) {
step = 0;
state = RE_STATE_CHECK;
cnt ++;
flg = 1;
}
} else {
// 異常値検出
state = RE_STATE_WAIT;
}

}
void RENC::ReDec( void ) {
if ( ptn == dec_seq[ step ] ) {
step ++;
if ( step >= RE_SEQ_END ) {
step = 0;
state = RE_STATE_CHECK;
cnt --;
flg = 1;
}
} else {
// 異常値検出
state = RE_STATE_WAIT;
}
}


//
// 関数テーブル
//
void (RENC::*RENC::fncp[ ])(void) = {
&RENC::ReWait,
&RENC::ReCheck,
&RENC::ReInc,
&RENC::ReDec
};
//
// 初期化
//
void RENC::Init( int stbs ) {

cnt = 0L;
flg = 0;
step = 0;
state = RE_STATE_WAIT;
lastptn = -1;
CON1.Reset( stbs );
CON2.Reset( stbs );

}
//
// サンプリング
//
void RENC::Samp( unsigned char pa,unsigned char pb ) {

ptn = 0;

// チャタリング除去
if ( CON1.Get( pa ) ) ptn |= RE_POLE_A;
if ( CON2.Get( pb ) ) ptn |= RE_POLE_B;

// ポート状態変化の検出
if ( ptn != lastptn ) {
if ( state >= 0 && state <= 3 ) {
( this->*fncp[ state ] )();
}
lastptn = ptn;
}

}
//
// カウント値を得る
//
int RENC::Get( long *buf ) {

if ( flg ) {
*buf = cnt;
cnt = 0;
flg = 0;
return 1;
}
*buf = 0;
return 0;
}