Release 2017.10.22 / Update 2018.9.25

ロータリーエンコーダを使うpart 3 : “Dual Encoder”

part 1part 2と、Arduinoでロータリエンコーダを使うためのスケッチを試行錯誤してきましたが、2つの記事で得た知恵を統合して、新たなスケッチを書いてみました。

名づけて「Dual Encoder」。

今回書いたスケッチで目指したのは、

  • デュアルエンコーダ(複数のエンコーダ値を読み取る)
  • 「外部割込み」ではなく「タイマー割り込み」で
  • クリック有り・無し対応

です。

前回の問題点解決に試行錯誤した結果、精度が程よく改善されたかと思います。特にクリックなしタイプに対しては、自分的に満足出来るレベルになりました。

ただし、完全にチャタリングを排除できたわけではないし、状況によっては他のタイマーと競合してスケッチ全体のレスポンスに影響を与える可能性もあります。また、クリックタイプへの対応は4の倍数で強制しているだけなので、バタつきも出ます。

なので、クリックタイプ&外部割込みで事足りるのであれば、そっちを使ったほうが安定しているかな、と(part 1 参照)。まあ、一度試して判断してもらえれば…。

本来ならライブラリにしたほうが便利なんでしょうけど、技量・時間不足なのでスケッチファイルです。コードを直接書き換えて使ってください。悪しからず。

回路図

このスケッチを試すのに必要なパーツは、

  • ArduinoUNO
  • ロータリーエンコーダ×2
  • (手元にあれば)128*64 OLED

ロータリーエンコーダは、2相とGNDの3ピンになっている安いタイプで大丈夫です。可能ならpart 2でやっていた「クリックなし」を用意できると、細かいエンコーダ操作が出来るので面白いか、と。

OLEDとu8glibの設定部分はこちらを参考にしてください。

あるいは、今回のスケッチはシリアルプリントにも返しているので、OLEDは用意しなくてもシリアルモニタでエンコーダ値を確認できます。

スケッチ

プロジェクトファイルをダウンロード、又は下記のコードをコピペするかして、Arduinoに書き込んでください。

Download – 「Dual encoder ver 1.0

注意点

タイマー割り込み

このエンコーダ読み取りスケッチは「MsTimer2」というタイマー割り込み用のライブラリを使っています。(part 2を参考に)IDEへ組み込んでください。

もし、他のタイマーを使いたい場合、スケッチ内を書き換えれば対応できるとは思います。

ポート操作

今回のスケッチはdigitalRead関数ではなく、直接ポートを読みに行くようにしました。この方法だと割り込みに割く時間を節約できるからです。

なので、エンコーダに接続しているピン設定は基本変更出来ないと考えてください。また、ArduinoUNO系統(ATmega328)以外のマイコン、つまり、ポート配置が違うタイプも、このままでは動かない場合があると思います。

ピンの設定を変更したい場合は、自分で調べて、ポート読み取りの部分を書き換えてください。

PIN# & _BV(#)の部分です。

参考 ArduinoUNOのポート配置

ちなみに、「ポート読み取り」にリンクしてませんが、最初に#define定義しているピンはpinModeの設定用なので、同様に書き換える必要があります。要注意。

使い方

諸々の関数は下記に詳細を書いておきますが、基本はENC_COUNT()の返り値を変更したい変数に投げるだけです。タイマー開始後なら好きなところに呼び出せます。

スケッチ内の仕組みや構造はこの記事では触れないので、気になる方はpart 1 / part 2を参考にしてください。

もしモサモサ感が気になるなら、それはOLEDを使うu8glibに起因するものなので、define定義した「OLED_DRAW」を0にするか、u8glibに絡む行をコメントアウトして、シリアルモニタだけの確認にしてください。

Dual Encoderの関数

void SET_ENC_RES (byte encoder num, bool resolution);

使うロータリーエンコーダの分解能を設定します。返り値はありません。

クリックのある一般的なエンコーダは、4パターンを1クリック(1カウント)として扱うので、trueにすると、それに倣ったカウントをします。

encoder num エンコーダ番号の指定。1個目は(0)、2個目は(1)になります。
resolution 分解能の指定。
0(false) ノンクリックタイプの高分解能
1(true) クリックタイプ

例えば、1個目のエンコーダをクリックタイプとして扱いたい場合は、

SET_ENC_RES(0, true);

デフォルトの使用はノンクリックタイプを想定しているので、クリックタイプのロータリーエンコーダを使わない場合は、この関数で設定する必要はありません。

boolean ENC_CHECK (); / ENC_CHECK (byte encoder num);

エンコーダの増減があったかをチェックしにいきます。変化があれば1(true)、なければ0(false)を返してきます。

encoder num エンコーダ番号の指定。1個目は(0)、2個目は(1)になります。

ENC_CHECK()場合、全部のエンコーダの中から1つでも増減があればtrueを返します。

ENC_CHECK(#)の場合は、指定したエンコーダのみの増減有無を返します。

この返り値は、ENC_COUNT()関数を呼ぶことでリセットされます。よって、リセットしたくない時、この関数で「増減有無の確認」だけすることができます。

int ENC_COUNT (byte encoder num);

実際のエンコーダ値を取得。前回読んでから増減した+-差分を返り値として返します。

encoder num エンコーダ番号の指定。1個目は(0)、2個目は(1)になります。

例えば、「val」という変数へ1個目のエンコーダ増減を足したいなら、

val += ENC_COUNT(0);

今回のスケッチではu8glibを組み込んでいますが、その中のdo~while分が全体のもたつきを作っていて、思うように更新されていないように見えます。ただ、(チャタリングによる取りこぼしがなければ)現実にロータリーエンコーダが回った分は拾っているハズなので、最終的な変更値はちゃんと得られると思います。

この関数を呼ぶことで、ENC_CHECK()の返り値はリセットされます。

void ENC_RESET()

エンコーダを読み取る上で、実際は「enc_count[]」という変数が裏で連続カウント値を記録しています。そのenc_count[]変数を0にリセットします。この変数はint型にしているので、よほどのことがなければオーバーフローしないと思いますが、使い方によってはその可能性もあるので、そういった危険がありそうな場合は、この関数を適宜組み込んでください。

参考 ) ArduinoUnoでint型が扱える数値範囲 -32768 ~ 32767

define定義値

ENC_NUM

ロータリーエンコーダの個数を指定をします。

1個しか使用しないのであれば、数を減らすことでタイマー割り込みに割く時間を節約できます。 ただし、その場合、ピン配列等も書き換えてください。

ENC_TOLERANCE

part 2に同じく、このスケッチは「ゲージ判定」でチャタリング対策しています。その閾値です。

この値はbyte型にしているので、255以上に設定しないでください。

TIMER_INTVAL

タイマー割り込みのインターバル時間(ms)です。

値を小さくするほど、正確な読み取りができるようになると思いますが、その分他のタスクを圧迫することになります。逆に、メインのタスクが上手く動かない場合に大きくすると、負荷が減るかと思います。ただし、だからと言って、大き過ぎてもエンコーダの読み取りが上手くいかなくなります。

ENC_REPEAT

1度のタイマー割り込みで1回のゲージ判定だと割り出しは難しいので、1回割り込んだら何回もピン状態を確認し、ゲージ分量を確保しています。つまり、1回の割り込み当たりに繰り返す「読み取り回数」の数値です。

数値を増やすことで、よりエンコーダの変化を細かく見れるようになりますが、その分他のタスクを圧迫することになります。

こちらも、255以上に設定しないでください。

 

ENC_TOLERANCE、TIMER_INTVAL、ENC_REPEAT、この3つの数値バランスでエンコーダの読み取り精度が変わってくるので、自分のプロジェクトに合わせ、上手く調整してください。

最後に

このスケッチは2つのエンコーダを同時に読めるよう目指したものですが、実は少し書き換えるだけで扱える個数を増やせます。

とりあえず、エンコーダ4個まで試しました。

ただし、割り込みに割く時間も増えるので、他のタスクに影響が出たり、取りこぼしが増えたりするかもしれません。特にPWMを扱うピンとのカブりが出てくると、憂慮することが増えるので厄介です。それでもよければ、と言う感じです。

例えば、3つ目のエンコーダをArduinoのA0、A1ピンに追加する場合、下記のように書き足します。

ピン定義

ポート操作

後は上述に倣い、

int val += ENC_COUNT(2);

というように値を取得していくだけです。

 

ロータリーエンコーダを複数扱うようなプロジェクトは中々ないかもしれませんが、1個だけでも、充分機能してくれると思うので、是非お役立てください。

参考リンク

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください