« 今度はチャタリング除去、その考え方 | トップページ | CR2032バックアップ用リチウム電池・・・0V »

2020年9月16日 (水)

ロータリーエンコーダーの2相パルスをタイマー割り込みで

2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
2020年9月14日:「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ
2020年9月15日:今度はチャタリング除去、その考え方

これらの続きということで、2kHzのタイマー割り込み(ATmega328Pのタイマー2)で
ロータリーエンコーダの入力を処理してみました。
   ※無理やりチャタリングを入れてます
そのスケッチを示します。

A相入力がA0、B相がA1。
D8出力とD9出力にチャタリング除去して平滑化したA相とB相を出力。
D10出力はA相B相の↑↓エッジ検出タイミング。
D13出力は(LED)は2kHzタイマー割り込み処理でパルスを出力しています。

/*****  ロータリーエンコーダーテスト    *****/
// A相+B相の4エッジを使って4逓倍
// タイマー2割り込みでチャタリング除去とともにエッジ検出
// ポート使用
// A0 PC0 A相入力 (pull up)
// A1 PC1 B相入力 (pull up)
// D8 PB0 A相入力安定モニター出力
// D9 PB1 B相入力安定モニター出力
// D10 PB2 エッジ検出出力
// D13 PB5 LED 割り込みタイミング出力
// タイマー2周波数設定 → OCR2A = 125 - 1; // 2kHz(0.5mS) ★1
// ポート制御
#define D8_H (PORTB |= (1 << PB0)) // PB0 A相入力安定モニター出力
#define D8_L (PORTB &= ~(1 << PB0)) //
#define D9_H (PORTB |= (1 << PB1)) // PB1 B相入力安定モニター出力
#define D9_L (PORTB &= ~(1 << PB1)) //
#define D10_H (PORTB |= (1 << PB2)) // PB2 エッジ検出出力
#define D10_L (PORTB &= ~(1 << PB2)) //
#define D13_H (PORTB |= (1 << PB5)) // PB5 (LED)割り込みタイミング
#define D13_L (PORTB &= ~(1 << PB5)) //
// 配列のデータ数を返すマクロ
#define DIMSIZ(a) (sizeof(a)/sizeof(*a))

/******************************/
/* エンコーダ入力 */
/******************************/
// データ
#define ENC_TOP 359 // 360度回転 0~359
volatile word enc_cnt; // 0~359カウンタ
volatile byte f_cnt; // カウンタup/down検出フラグ
// A相 B相入力 on,offチェック (Hで1)
#define INP_A (PINC & (1 << PC0)) // A相入力チェック
#define INP_B (PINC & (1 << PC1)) // B相
// ↑↓エッジのビット位置
#define ENC_A 0 // A相↑
#define ENC_B 1 // B相↓
// bit0,1にエンコーダ入力データが入る
volatile byte enc_sft[4]; // チャタリング除去用シフトデータ
// 4回ともH/L同じなら安定
volatile byte enc_inp; // 入力のon/off安定状態
// エッジをチェック
/***** エンコーダカウントアップ *****/
// 359度なら0に
void cntup(void)
{
if(enc_cnt >= ENC_TOP) enc_cnt = 0;
else enc_cnt++;
}
/***** エンコーダカウントダウン *****/
// 0度なら359に
void cntdn(void)
{
if(enc_cnt == 0) enc_cnt = ENC_TOP;
else enc_cnt--;
}
/***** カウント値読み出し *****/
// いったん割り込み禁止にして
word readcnt(void)
{
word d;
cli(); // いったん割り込み禁止して
d = enc_cnt; // 0~359度カウンタ読み出し
sei(); // 割り込み有効に戻す
return d;
}
/***** エンコーダ入力スキャン *****/
// タイマー割り込みで呼出
// 4サイクルでチャタリング除去
// ↑↓エッジを見つけてup/downカウント
// CW : A↑ B=L , A↓ B=H , B↑ A=H , B↓ A=L
// CCW : A↑ B=H , A↓ B=L , B↑ A=L , B↓ A=H
void encscan(void)
{
static byte p = 0; // 最新入力書き込み位置
static byte q1 = 0; // 安定したエンコーダ入力
byte d0, d1; // L安定,H安定 ↓↑エッジ
byte n, q2; // n:一時データ、q2:エッジ検出用
// エンコーダA,B相入力
n = 0;
if(INP_A) n |= 0b00000001; // 0:A相
if(INP_B) n |= 0b00000010; // 1:B相
enc_sft[p] = n; // 最新エンコーダ入力状態
// シフトデータをANDとORして変化点を見つける ★2
d0 = d1 = enc_sft[0]; // d0:全L / d1:全Hチェック
d0 |= enc_sft[1]; // [0]~[3]の4シフトデータ
d1 &= enc_sft[1];
d0 |= enc_sft[2];
d1 &= enc_sft[2];
d0 |= enc_sft[3];
d1 &= enc_sft[3];
n = (~d0) | d1; // 1のところが安定データ
q2 = q1; // 前の状態
q1 = (q2 & (~n)) | (enc_sft[3] & n); // 新入力状態確定
// シフトデータのポインタ+1
p++; // 次書き込み位置
if(p >= DIMSIZ(enc_sft)) p = 0; // ポインタ一周
// A相/B相安定状態をモニター出力
if(q1 & (1 << ENC_A)) D8_H; // A相 D8
else D8_L;
if(q1 & (1 << ENC_B)) D9_H; // B相 D9
else D9_L;
// エッジチェック
d1 = (~q2) & ( q1); // ↑エッジをチェック ★3
d0 = ( q2) & (~q1); // ↓エッジ
if(d0 | d1){ // エッジあり
D10_H; // A相かB相に↑↓エッジあり
f_cnt = 1; // カウントup/down検出
if(d1 & (1 << ENC_A)){ // A相↑ ★4
if(!(q1 & (1 << ENC_B))) cntup(); // B=L CW, +1
else cntdn(); // B=H CCW,-1
}
if(d1 & (1 << ENC_B)){ // B相↑
if( q1 & (1 << ENC_A)) cntup(); // A=H CW, +1
else cntdn(); // A=L CCW,-1
}
if(d0 & (1 << ENC_A)){ // A相↓
if( q1 & (1 << ENC_B)) cntup(); // B=H CW, +1
else cntdn(); // B=L CCW,-1
}
if(d0 & (1 << ENC_B)){ // B相↓
if(!(q1 & (1 << ENC_A))) cntup(); // A=L CW, +1
else cntdn(); // A=H CCW,-1
}
}
else{
D10_L; // エッジなし
}
}
/*******************************/
/* タイマー2割り込み */
/*******************************/
/***** タイマー2コンペアマッチA割込み *****/
// 2kHz(0.5mS)で割り込み
ISR(TIMER2_COMPA_vect)
{
D13_H; // (!!!)
encscan(); // エンコーダ入力処理
D13_L; // (!!!)
}
/*******************************/
/* SETUP */
/*******************************/
/***** SETUP *****/
void setup()
{
// ポートI/O指定
PORTB = 0b00000000; // data/pull up
DDRB = 0b00111111; // I/O (0:in 1:out)
// |||||+---- PB0 IO8 out A相入力安定モニター出力
// ||||+----- PB1 IO9 out B相入力安定モニター出力
// |||+------ PB2 IO10 out ↑↓エッジありモニター出力
// ||+------- PB3 IO11 out
// |+-------- PB4 IO12 out
// +--------- PB5 IO13 out (LED) 割り込みタイミング
PORTC = 0b00111111; // data/pull up
DDRC = 0b00000000; // I/O (0:in 1:out)
// |||||+---- PC0 AD0 in A相入力(pull up)
// ||||+----- PC1 AD1 in B相入力
// |||+------ PC2 AD2 in
// ||+------- PC3 AD3 in
// |+-------- PC4 AD4 in
// +--------- PC5 AD5 in
PORTD = 0b11111111; // data/pull up
DDRD = 0b00000010; // I/O (0:in 1:out)
// |||||||+---- PD0 IO0 in RXD
// ||||||+----- PD1 IO1 out TXD
// |||||+------ PD2 IO2 in
// ||||+------- PD3 IO3 in
// |||+-------- PD4 IO4 in
// ||+--------- PD5 IO5 in
// |+---------- PD6 IO6 in
// +----------- PD7 IO7 in
// タイマー2
TCCR2A = 0b00000010; // モード設定
// |||| ++--- WGM: CTCモード
// ||++------- COM0B
// ++--------- COM0A
TCCR2B = 0b00000100;
// |+++--- CS:1/64 : 250kHz
// +------ WGM02
OCR2A = 125 - 1; // 2kHz(0.5mS) ★1
TIMSK2 = 0b00000010;
// +----- OCIE2A コンペアマッチA割り込み有効
// シリアル
Serial.begin(9600);
// 最初の表示
delay(100); // 入力安定にちょい時間待ち(timer0使う)
cli(); // いったん割り込み禁止して
enc_cnt = 0; // カウンタをゼロクリア
sei(); // 割り込み有効に戻す
f_cnt = 1; // カウント値表示フラグon
}
/*******************************/
/* LOOP */
/*******************************/
/***** LOOP *****/
void loop()
{
if(f_cnt){ // カウンタup/downした
f_cnt = 0;
Serial.println(readcnt()); // カウント値をシリアル出力
}
}
/*===== end of "test_enc2.ino" =====*/

★1でタイマー2割り込みの周波数を設定。
2kHz(0.5ms)にしています。
チャタリング除去用のシフトレジスタが4段ですので、入力した信号が2mS遅れます。
これで2相パルスに対する応答速度が決まります。
ダイヤルを早く回すと追いつかないという状態に。

★2がチャタリング除去の処理。
ANDとORを重ねながらシフトレジスタを読み出しています。

★3がエッジ検出処理。
チャタリング除去されて確定したq1とq2からエッジを得ます。

★4で、A相B相の↑↓エッジ(4つある)から、相対する相の
状態を見て、カウントアップとカウントダウンの処理を行っています。


その状態。 オシロスコープで。
A000

一番下の波形、このタイミングでカウンタのup/downが
行われて、カウント値をシリアル出力しています。

エンコーダのぐるぐる回しをちょいと早くすると、
こんな感じに。
A003

模擬的なチャタリングをうまく取り除いている様子が見えます。
しかし、応答速度はこのあたりが限界。
対策は、タイマー割り込みの周期をもっと短くっという方法に
なるのですが、毎割り込みで取られる時間がちょいもったいな
いかと。
パルスが入ってカウント処理されると10usくらいです。
待機時は7~8usくらい。

割り込み機能の無い入力ピンを使っての2相パルスカウント、
高速応答はできませんが、手動で操作するエンコーダだと
こんなものでしょう。

シフトレジスタは8bitです。
今は2bitしか使っていないんで、エンコーダは4系統まで
拡張できるかと。


※試行錯誤

シフトレジスタのAND、OR処理、Cの書き方を
ちょいと変えると、出てくるコードが変わりました。
これが最初の。
enc_sft[0]~enc_sft[3]の並びをうまくやってくれるかと、
コンパイラに期待しましたが・・・

//  シフトデータをANDとORして変化点を見つける ★2
d0 = d1 = enc_sft[0]; // d0:全L / d1:全Hチェック
d0 |= enc_sft[1]; // [0]~[3]の4シフトデータ
d1 &= enc_sft[1];
d0 |= enc_sft[2];
d1 &= enc_sft[2];
d0 |= enc_sft[3];
d1 &= enc_sft[3];
n = (~d0) | d1; // 1のところが安定データ
q2 = q1; // 前の状態
q1 = (q2 & (~n)) | (enc_sft[3] & n); // 新入力状態確定

500: 90 91 18 01 lds r25, 0x0118 ;enc_sft[0]
504: 20 91 19 01 lds r18, 0x0119 ;enc_sft[1]
508: 29 2b or r18, r25
50a: 80 91 19 01 lds r24, 0x0119 ;enc_sft[1]
50e: 98 23 and r25, r24
510: 80 91 1a 01 lds r24, 0x011A ;enc_sft[2]
514: 28 2b or r18, r24
516: 80 91 1a 01 lds r24, 0x011A ;enc_sft[2]
51a: 98 23 and r25, r24
51c: 40 91 1b 01 lds r20, 0x011B ;enc_sft[3]
520: 80 91 1b 01 lds r24, 0x011B ;enc_sft[3]
524: 24 2b or r18, r20
526: 20 95 com r18
528: 98 23 and r25, r24
52a: 92 2b or r25, r18
52c: 80 91 17 01 lds r24, 0x0117 ;q1
530: 20 91 1b 01 lds r18, 0x011B ;enc_sft[3]
534: 49 2f mov r20, r25
536: 40 95 com r20
538: 48 23 and r20, r24
53a: 92 23 and r25, r18
53c: 94 2b or r25, r20
53e: 90 93 17 01 sts 0x0117, r25 ;q1

別の書き方で。
こっちのほうがメモリの読み出しを省いている
分、ちょいだけと早そう。


// シフトデータをANDとORして変化点を見つける ★2
d0 = d1 = enc_sft[0]; // d0:全Lチェック,d1:全Hチェック
n = enc_sft[1]; // [0]~[3]の4シフトデータ
d0 |= n;
d1 &= n;
n = enc_sft[2];
d0 |= n;
d1 &= n;
n = enc_sft[3];
d0 |= n;
d1 &= n;
n = (~d0) | d1; // 1のところが安定データ
q2 = q1; // 前の状態
q1 = (q2 & (~n)) | (enc_sft[3] & n); // 新入力状態確定


500: 90 91 18 01 lds r25, 0x0118 ;enc_sft[0]
504: 80 91 19 01 lds r24, 0x0119 ;enc_sft[1]
508: 29 2f mov r18, r25
50a: 28 2b or r18, r24
50c: 89 23 and r24, r25
50e: 40 91 1a 01 lds r20, 0x011A ;enc_sft[2]
512: 24 2b or r18, r20
514: 98 2f mov r25, r24
516: 94 23 and r25, r20
518: 80 91 1b 01 lds r24, 0x011B ;enc_sft[3]
51c: 28 2b or r18, r24
51e: 20 95 com r18
520: 89 23 and r24, r25
522: 92 2f mov r25, r18
524: 98 2b or r25, r24
526: 80 91 17 01 lds r24, 0x0117 ;q1
52a: 20 91 1b 01 lds r18, 0x011B ;enc_sft[3]
52e: 49 2f mov r20, r25
530: 40 95 com r20
532: 48 23 and r20, r24
534: 92 23 and r25, r18
536: 94 2b or r25, r20
538: 90 93 17 01 sts 0x0117, r25 ;q1

 

 

|

« 今度はチャタリング除去、その考え方 | トップページ | CR2032バックアップ用リチウム電池・・・0V »

Arduino」カテゴリの記事

割り込み処理」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« 今度はチャタリング除去、その考え方 | トップページ | CR2032バックアップ用リチウム電池・・・0V »