google-site-verification: google3bd66dd162ef54c7.html

ピンチェンジ割込みを使ってロータリーエンコーダーを読む (Arduino)

 少し前の記事でウォッチドッグタイマー割込みを使ってロータリーエンコーダーの読取りをやってみました。これは、ウォッチドッグタイマーの使い方を試す事例として悪くないと思います。でも、割り込み周期を15msまでしか短く出来ないので、ロータリーエンコーダーをすばやく廻した時に取りこぼしが出てしまうという問題がありました。

 タイマー割込みを使ってロータリーエンコーダーの読取りを行えばこんなことにはならないのですが、タイマーの割り込みが余っていない場合はそうもいきません。

 ということで困っていたのですが、ピンチェンジ割り込み (Pin Change Interrupt) を使えば何とかなりそうなことに気付きました。

 ピンチェンジ割り込みはCPUの機能として存在していますが、Arduinoの言語仕様には出てこないので自分で制御レジスタを設定してやる必要があります。なお、これはArduinoの言語仕様に出てくる外部割込み(INT0, INT1)とは別の割込みになります。

 CPU(ATmega328P)のデーターシートを読むと、ピンチェンジ割込みの詳しい使い方が書かれていますが、物理ピンや割込み信号名の関係がややこしいので一覧表に整理してみました。

▼ピンチェンジ割込み関係ピン、信号対応表
ピンチェンジ割込み関係信号対応表
 今回読もうとしているロータリーエンコーダーは、この表の黄色で示すD9とD10に接続されています。その割込み信号名は PCINT1と PCINT2 で、PCMSK0 レジスタで指定するということが判ります。

 ピンチェンジ割り込みでは、まとまったグループ単位でどこかのピンの状態に変化があった時に割り込みが掛かります。実際にどのピンが変化したのかを特定するためには、プログラムで調べる必要があります。でもロータリーエンコーダーの読取りを行う場合、「二つのピンのどちらかに状態の変化があった」ということが判れば大丈夫なので処理はかなり簡単になります。

 ということで、実際に動作確認するためのプログラムを書いてみました。

▼ピンチェンジ割り込みでロータリーエンコーダーを読むスケッチ
/* ピンチェンジ割込みを使ってロータリーエンコーダーを読む
* A相 = D9, PB1, PCINT1, PCIE0
* B相 = D10, PB2, PCINT2, PCIE0
* 2016/4/8 ラジオペンチ http://radiopench.blog96.fc2.com/
*/
#define ECA 9 // エンコーダーA相 = D9
#define ECB 10 //       B相 = D10

volatile int X, nIRQ;
volatile unsigned long lastMicros;
int data;

void setup() {
pinMode(ECA, INPUT_PULLUP);
pinMode(ECB, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("start");

PCMSK0 |= ((1 << PCINT1) | (1 << PCINT2)); // D9,D10ピンからのピンチェンジ割込みを使う
PCICR |= (1 << PCIE0); // PCIE0グループからの割込み許可
}

void loop() {
if (X != 0) { // エンコーダーの値が変化していたら
data += X;
X = 0;
Serial.print(data); Serial.print(", "); Serial.print(nIRQ); // 動作確認表示
Serial.print(", "); Serial.println(lastMicros);
}
}

ISR(PCINT0_vect) { // PCIINT0グループからの割込み処理
static byte bp = 0; // ビットパターン記録バッファ
static int n = 0;

n++; // 割込み発生回数カウント
bp = bp << 1; // ピンの状態変化をbpに右詰めで記録 0b00ABABAB
if (digitalRead(ECA) == HIGH) bp |= 0x01;
bp = bp << 1;
if (digitalRead(ECB) == HIGH) bp |= 0x01;

bp &= 0x0F; // 下位4ビット残して上位を消す 0b0000ABAB
if (bp == 0b0111) { // このパターンに
X++; // 一致していればインクリメント
nIRQ = n; // 割込み回数を記録し
n = 0; // カウンタをリセット
}

if (bp == 0b1011) {
X--; // 一致していればデクリメント
nIRQ = n;
n = 0;
}

if (n == 0) { // 値の変化があって
if ((micros() - lastMicros) < 50000) { // 50ms以内の更新だったら
X *= 10; // 早送り(10倍速)
}
lastMicros = micros();
}
}
 19、20行目がピンチェンジ割込みを起動するためのレジスタ設定です。ここで設定しておけば、割り込みが掛かるたびに割込み処理ルーチンである32行目の ISR(PCINT0_vect) が実行されます。

 割込み処理ルーチンではパターン一致方式でロータリーエンコーダーの読取りを行っていますが、ちょっとひねりを加えて、素早く廻した時は早送りでカウントが進むようなルーチンを追加してあります。

 実行するとシリアルモニタに状態を表示します。

▼出力例
実行結果
 エンコーダーのカウント値、割込み発生回数、経過時間(μs)の順に表示します。10カウントの時点ですばやく廻したのでカウントが10ステップ間隔で増えています。

 割込み回数は理論的には4になるはずですが、チャッタリングの影響で多少変化しています。なお、スイッチのチャッタリング消しをプログラムで行いたかったのですが、うまい手を思い付かなかったので、回路にチャッタ消しのコンデンサを入れています(下図のC1,C2)。コンデンサの容量は割込み回数を見ながら調整すればいいのですが、今回はそこまではやっていません。

▼回路図
回路図

 ということで、ピンチェンジ割り込みを使ったロータリーエンコーダー読み取りはうまくいったようです。このテクニックはロータリーエンコーダーに限らずArduinoでより複雑な物を作りたい場合に役立つと思います。

 この記事の作成には以下のサイトを参考にさせていただきました。ありがとうございます。
・FIRMLOGICSさん、Arduino のスリープと、Pin Change 割込
・wsnakさん、LEDシーリングライト用赤外線リモコンの製作(9)アイリスオーヤマ用 スリープ対応
・橋本商会さん、ATmega168でピン変化割り込み
関連記事

コメントの投稿

管理者にだけ表示を許可する

カレンダー
02 | 2017/03 | 03
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード