« 明るすぎた~ | トップページ | 「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ »

2020年9月12日 (土)

ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む

2020年8月11日:14pinのAVRマイコン、ATtiny24が動かん!
2019年9月29日:360度グルグル回したろ
に絡むんですが、1kHzのPWM波発生回路を作ろうとしています。

旭化成のAK7401 、PWMで角度を出力してくれます。
その回路検証に使います。
周波数が1kHzで、検出角によりPWMのデューティを10%から90%
まで可変。
デューティーの10%が0度に対応して、50%が180度で
359.9度が90%という仕様です。
このパルスを模擬的に出そうという回路です。

この角度の設定にロータリーエンコーダを使おうとしたのです。
右に左にぐるぐる回せば0~359度自在に設定という目論見です。

14pinのAVRマイコン、ATtiny24が動かん! では、ロータリーDIP SW
を使ったんですが、これは「リニア」に設定できません。
角度の設定ならやはりグルグル回しでしょう。

「確かどこかに残ってたはずや」っとパーツボックスを
探すと出てきました。
BOURNSのECW1J-B23-BC0024L

11_20200912143701

1回転が24クリック。
360度回そうとすると15回転。
   ちょっとクリクリするのがたいへんか・・・

1クリックでこんなパルスが出てきます。
右回しと左回しで波形がちょいと異なります。
A2_20200912143801

A1_20200912143801
A相B相とも途中では止まりません。
両方信号オフ状態で止まります。
これ、2相パルスには違いないのですが、クリック機構があると
途中で止められないので、4逓倍は無理。

そこで・・・「つぶしてもエエやん」っと、基板部を固定してある
ポッチリを削ると、中身が出てきました。
A相・B相のコード板と接点。
13_20200912143901

そしてクリックはこんな機構。
14_20200912144001

スプリング部に出たチョッポリで回転軸のギザギザを押さえ
ています。
このスプリングを外せば、クリック無しで自由回転するんですが、
接点端子部をコード板に押さえているのもこの機構です。
スプリングを取ると接触が安定しません。

そこで、「ポッチリを反対にしてみたろ」っとスプリングを裏返したと
ころ、「まぁエエやん」っとクリック感無しで360度クルクル回転でき
るようになりました。

これで4逓倍すれば、3.75回転で360度の角度をセットできます。
回すとこんなパルスが出てきます。
A3_20200912144201
ほんとは各パルス90度位相差なんですが、コード板の構造で
しょう、1箇所周期が長いところがあります。

接点のチャタリング、こんな様子です。
A4_20200912144301
いつも出るわけじゃなく、「たまに出る」というヒゲですが、
CRでのチャタリング吸収回路は必須です。
ちなみにBOURNSのカタログにはこんな回路が。

・単純にCRで
 10k+0.01uFですんで時定数0.1mSくらい。
B1_20200912144901

・そして・・・あらま懐かしいMC14490 が出てきました。
B2
チャタリング除去専用のICです。
大昔、使ったことあります。
C-MOS4000番シリーズの「けったいなIC」系列に登場する
石ですなぁ。

まずはArduino UNOでエンコーダーの計数を試してみます。
ネットを探すと・・・INT0・INT1エッジ割り込みを使った
例題ばかり。

面白くないんで、こんな接続に。
ピン変化割り込み」を使ってみます。

B5

ピン変化割り込みは、ポートB群、ポートC群、ポートD群、
3つの割り込みが独立して使えます。
ただし、それぞれのポート群の中での入力ピンは一つだけ。
  (どのピンが変化したかのチェックを省略する処理では)
INT0とINT1はPD2、PD3でポートD群ですんでこれは除外。
PB0とPC0に2相パルスを入れることにします。

エンコーダ入力のテストはこんなスケッチ。
  ※PWM出力制御と設定角度の表示はこれから
   とりあえずシリアル出力で。

/*****  ロータリーエンコーダーテスト    *****/
// A相+B相の4エッジを使って4逓倍
// ポート使用
// A0 PC0 A相入力 PCINT8
// D8 PB0 B相入力 PCINT0
// D13 PB5 LED テスト出力
// データ
#define ENC_TOP 359 // 360度回転 0~359
volatile word enc_cnt; // 0~359カウンタ
volatile byte f_cnt; // カウンタup/downフラグ
// ポート制御
#define INP_A0 (PINC & (1 << PC0)) // A相 A0入力 PC0 PCINT8
#define INP_D8 (PINB & (1 << PB0)) // B相 D8入力 PB0 PCINT0
#define D13_H (PORTB |= (1 << PB5)) // PB5 D13 H/L
#define D13_L (PORTB &= ~(1 << PB5)) // テストパルス

/***** エンコーダカウントアップ *****/
// 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;
}

/***** A相入力 ↑↓ PC0 A0 *****/
// CW : A↑ B=L / A↓ B=H
// CCW : A↑ B=H / A↓ B=L
ISR(PCINT1_vect)
{
D13_H; // (!!!)
if(INP_A0){ // A↑
if(!INP_D8) cntup(); // B=L CW, +1
else cntdn(); // B=H CCW,-1
}
else{ // A↓
if(INP_D8) cntup(); // B=H CW, +1
else cntdn(); // B=L CCW,-1
}
f_cnt = 1; // カウントup/down
D13_L; // (!!!)
}
/***** B相入力 ↑↓ PB0 D8 *****/
// CW : B↑ A=H / B↓ A=L
// CCW : B↑ A=L / B↓ A=H
ISR(PCINT0_vect)
{
D13_H; // (!!!)
if(INP_D8){ // B↑
if(INP_A0) cntup(); // A=H CW, +1
else cntdn(); // A=L CCW,-1
}
else{ // B↓
if(!INP_A0) cntup(); // A=L CW, +1
else cntdn(); // A=H CCW,-1
}
f_cnt = 1; // カウントup/down
D13_L; // (!!!)
}

/***** SETUP *****/
void setup()
{
pinMode(8, INPUT); // D8 PB0 PCINT0 B相入力
pinMode(A0, INPUT); // A0 PC0 PCINT8 A相入力
pinMode(13, OUTPUT); // D13 PB5 (test LED port)
// ピン変化割り込み
PCMSK0 = 0b00000001; // 1で許可
// +---- PCINT0 PB0 D8 B入力
PCMSK1 = 0b00000001; // 1で許可
// +---- PCINT8 PC0 A0 A入力
PCICR = 0b00000011; // 1で割り込み許可
// ||+---- PCIE0 PCINT0~7 (PB)
// |+----- PCIE1 PCINT8~14 (PC)
// +------ PCIE2 PCINT16~23 (PD)
// シリアル
Serial.begin(9600);
// 最初の表示
f_cnt = 1; // カウント値表示フラグon
}
/***** LOOP *****/
void loop()
{
if(f_cnt){ // カウンタup/downした
f_cnt = 0;
Serial.println(readcnt()); // カウント値をシリアル出力
}
}


INT0・INT1以外にエンコーダをもう一つ付けたいという時の
参考になりますでしょうか。

・カウントアップ・ダウンの処理
B11_20200912150301
   ※おっと、2カ所ミスあり。
    B相↓のとき「B相=」というところ「A相=」です。

・逆転する様子
B12_20200912150301
入力のA相・B相パルスと、割り込み処理の関係、こんな具合です。
割り込みでD13・PB5ポートをon/offしています。
各エッジで割り込みが入っていることが見えます。

A6_20200912145501

※注意点
0~359という16bit値を扱っていますが、Arduino-UNOは
8bitマイコン。
割り込みで処理される2バイト以上のデータを扱う時は、
メイン側での読み書きは必ず割込禁止状態でしなくちゃ
なりません。
ネットで見かけるロータリーエンコーダの処理、これを
していない例題が
多数です。

2019年3月25日:割り込みで処理させるwordデータの扱い
2016年02月19日:Arduinoのタイマー処理
2018年10月11日:魔法の言葉「volatile」

※2相パルス、ロータリーエンコーダー絡みの記事
2014年07月01日:高速2相パルス発生回路
2017年9月12日:C-MOS 2相パルスカウンタの入力部回路
2010年01月30日:U/Dカウンタ
2018年5月25日:アナログ入力で2相パルスの周波数と正転逆転を制御する発振器
2018年6月17日:RX220マイコンで2相パルスカウント:グルグル回る角度を


※問題のある割り込み処理内データの読み出し例。

いずれも、読み書きのタイミングが「ゆっくり」だから
割り込みとの競合が露呈しないのでしょう。

割り込み内で下位桁からの桁上がりで上位桁が変化する
時などが問題。
割込を禁止しないで2バイト以上のデータを読み出すと、
そのタイミングで、割り込みによるカウントアップや
カウントダウンが起こると、誤ったデータを読み出
してしまいます。
下位バイトと上位バイトが処理される隙間に割り込みが
入るとアウトかも!という現象です。
16bitマイコンや32bitマイコンを触っていると、意識
しなくても正しい処理をしてくれるんですが、低機能
な8bitマイコンではしかたありません。

めったにないことでも、起こる可能性があるものは確実に
起こります。
それがプログラム(のバグ)というものです。

割り込みで処理される2バイト以上のデータを扱う時は、
めんどうがらずに「割込禁止」と「割込許可」を挿入のこと。
C言語のコンパイラ、変数にvolatileを付けていても、割り込み
との競合は防いでくれません。
プログラマの責任です。

ラジオペンチ Arduinoでロータリーエンコーダーを使う
  intのカウンター。
ラジオペンチ ピンチェンジ割込みを使ってロータリーエンコーダーを読む (Arduino)
  Xの変化は小さそうなんで8bit→16bitへの桁上がりはなさそうだけど・・・
   if (X != 0) {   // エンコーダーの値が変化していたら
    data += X; ←★1
    X = 0;   ←★2
    :
    ★1と★2の間に割り込みがあってXが増減したら、
    ★2でクリアされてしまいその増減が無視されてしまう・・・
    この間でも,割込禁止・割込許可が必要。

第二十一項 ロータリーエンコーダとノイズ対策・割り込み kusamura
  longのカウンターを処理。
ロータリーエンコーダを使う part 1 : 外部割込みとチャタリング対策 jumbleat
  intのカウンター。
  割り込み内でSerial.printを使うのは気持ち悪い。
【Arduino】マウスホイール(ロータリーエンコーダ)の回転量を取得する - おもちゃラボ
  intのカウンター。
迷走の果て・Tiny Objects Arduinoでロータリーエンコーダを試す(2)
  intのカウンター。
ロータリーエンコーダテスト 割り込みを使う場合 ・ GitHub
  intのカウンター。

|

« 明るすぎた~ | トップページ | 「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ »

Arduino」カテゴリの記事

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

コメント

コメントを書く



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




« 明るすぎた~ | トップページ | 「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ »