プッシュボタンスイッチ内蔵のロータリーエンコーダ

今回はプシュボタン内蔵のロータリーエンコーダです。以下は購入したモジュール

裏面奥に有る3ピンがエンコーダ用、手前2ピンがスイッチ用です。

ESP32との接続

このエンコーダは2相式でエンコーダが回転するとA,B端子に90度位相のずれたパルスが発生します。各相とESP32を以下の様に接続しました。

  • エンコーダの3ピン
    • A: パルス用。ESP32の入力専用ピンGPIO34に接続
    • B: コントロール用。ESP32の入力専用ピンGPIO35に接続。
    • C: GND。 GNDに接続。
  • スイッチの2ピン
    • 片側をGNDに他方をGPIO27に接続。チャタリング防止にコンデンサを入れています。

動作確認プログラム

ESP32は内部にエンコーダ用ドライバを持っていてそれを使えば比較的簡単にこのエンコーダを管理出来ます。Pulse Counter (PCNT)を参考にArduinoでプログラムを書いて行きます。

コンフィグの設定

内部のドライバーを使用する前にコンフィグを設定する必要が有ります。設定項目は“driver/pcnt.h”の以下の箇所です。

typedef struct {
    int pulse_gpio_num;             /*!< Pulse input gpio_num,if you want to use gpio16, 
                   pulse_gpio_num = 16, a negative value will be ignored */
    int ctrl_gpio_num;              /*!< Contol signal input gpio_num, a negative value 
                   will be ignored*/
    pcnt_ctrl_mode_t lctrl_mode;    /*!< PCNT low control mode*/
    pcnt_ctrl_mode_t hctrl_mode;    /*!< PCNT high control mode*/
    pcnt_count_mode_t pos_mode;     /*!< PCNT positive edge count mode*/
    pcnt_count_mode_t neg_mode;     /*!< PCNT negative edge count mode*/
    int16_t counter_h_lim;          /*!< Maximum counter value */
    int16_t counter_l_lim;          /*!< Minimum counter value */
    pcnt_unit_t unit;               /*!< PCNT unit number */
    pcnt_channel_t channel;         /*!< the PCNT channel */
} pcnt_config_t
  • pulse_gpio_num、 ctrl_gpio_num: 
    • パルス入力用GPIOピン、コントロール入力用GPIOピンを指定。
    • 例えば GPIO16をpulse_gpio_numに指定したい時は、pulse_gpio_num = 16とする。
  • pcnt_ctrl_mode_t lctrl_mode、pcnt_ctrl_mode_t hctrl_mode:
    • 回転方向の判別を指定します。指定出来る条件は以下の3つ
      • PCNT_MODE_KEEP → 変更しない
      • PCNT_MODE_REVERSE → インクリメントならデクリメントに、デクリメントならインクリメントに反転
      • PCNT_MODE_DISABLE → カウントしない
  • pcnt_count_mode_t pos_mode、pcnt_count_mode_t neg_mode
    • パルスの立ち上がりと立ち下がりでどの様にカウントするか下記の3つで指定
      • PCNT_COUNT_DIS → カウントしない。
      • PCNT_COUNT_INC → インクリメント。カウントアップ。
      • PCNT_COUNT_DEC → デクリメント。カウントダウン。
  • counter_l_lim、counter_l_lim
    • カウンタの上限値と下限値を指定。符号付き16ビット(±32767)で指定。
    • 指定範囲を超えるとカウンタが0に成ります。
  • unit、channel
    • ユニットはPCNT_UNIT_0 ~ PCNT_UNIT_7までの8つ
    • チャンネルはPCNT_CHANNEL_0とPCNT_CHANNEL_1の2つを指定出来ます。

プログラム

以上を踏まえて以下のプログラムを書きました。

sample.ino

#include "driver/pcnt.h"

#define   PULSE_PIN   34
#define   CTRL_PIN    35
#define   SW_PIN      27

void setup() {
  Serial.begin(115200);
  delay(1000);

  pcnt_config_t pcnt_config = {};
  pcnt_config.pulse_gpio_num  = PULSE_PIN;
  pcnt_config.ctrl_gpio_num   = CTRL_PIN;
  pcnt_config.lctrl_mode      = PCNT_MODE_REVERSE;
  pcnt_config.hctrl_mode      = PCNT_MODE_KEEP;
  pcnt_config.pos_mode        = PCNT_COUNT_INC;
  pcnt_config.neg_mode        = PCNT_COUNT_DEC;
  pcnt_config.counter_h_lim   = 50;
  pcnt_config.counter_l_lim   = -50;
  pcnt_config.unit            = PCNT_UNIT_0;
  pcnt_config.channel         = PCNT_CHANNEL_0;

  pcnt_unit_config(&pcnt_config);
  
  pcnt_set_filter_value(PCNT_UNIT_0, 100);
  pcnt_filter_enable(PCNT_UNIT_0);

  pcnt_counter_pause(PCNT_UNIT_0);
  pcnt_counter_clear(PCNT_UNIT_0);
  pcnt_counter_resume(PCNT_UNIT_0);
  
  pinMode(SW_PIN, INPUT_PULLUP);
}

void loop() {
  int16_t cnt = 0;

  while(1){
    if (digitalRead(SW_PIN) == LOW) 
      pcnt_counter_clear(PCNT_UNIT_0);
    
    pcnt_get_counter_value(PCNT_UNIT_0, &cnt);
    Serial.println("Count: " + String(cnt));
    delay(500);
  }
}
  • 14行から17行で回転方向とカウント方法を指定します。まずはこの様に指定して下さい。
  • カウンタの最大値50,最小値−50に設定。
  • ユニット0のチャンネル0を使用。
  • 23行:pcnt_unit_config(&pcnt_config); 設定の実行。
  • 25行:pcnt_set_filter_value(PCNT_UNIT_0, 100);
    • チャタリング防止用にフィルターを設定します。
    • 第二引数の値を変えると効果の調整が出来ます。今回は100位がベストでした。
  • 26行:pcnt_filter_enable(PCNT_UNIT_0); フィルターを起動
  • 28行:pcnt_counter_pause(PCNT_UNIT_0); カウンタを停止
  • 29行:pcnt_counter_clear(PCNT_UNIT_0); カウンタの値をクリア
  • 30行:pcnt_counter_resume(PCNT_UNIT_0); カウント開始。
  • 32行:pinMode(SW_PIN, INPUT_PULLUP); スイッチのモードを入力(PULL_UP有り)に設定。
  • 39行:ボタンが押されたらカウンターをクリアー。pcnt_counter_clear(PCNT_UNIT_0);
  • 42行:pcnt_get_counter_value(PCNT_UNIT_0, &cnt); カウンター値の読み込み
  • 43行:値の表示

プログラムを実行するとシリアルモニタにカウンタの値が表示されます。右左に回すとカウンタの値が増減しますが、ワンクリックで値が2つ変わってしまいます。このプログラムでは各ポートの設定を以下の様にしています

  1. pcnt_config.lctrl_mode = PCNT_MODE_REVERSE; <= 逆の計算
  2. pcnt_config.hctrl_mode = PCNT_MODE_KEEP;   <= 規定の計算
  3. pcnt_config.pos_mode = PCNT_COUNT_INC;   <= 加算
  4. pcnt_config.neg_mode = PCNT_COUNT_DEC;   <= 減算

上記1,2ですが、コントロール信号がHIGHの時パルスに変化が有った時は規定通り、LOWの時は逆の計算を行うという設定に成ります。同様に3,4ですが、パルスの立ち上がりでカウンタの加算。立ち下がりで減算をしなさいと言う設定に成ります。以下はA相をコントロール、B相をパルスとし、2クリック分有る方向に回転した場合の各相の出力を書いたものです。

上の表でワンクリックが上表1から始まったと仮定すると終了は4と成ります。この間で最初のパルスの変化は2です。ここでは

  • コントロール: HIGH
  • パルス:    立ち上がり

となるので 規定の計算 + 加算 となり カウンタの値が加算されます。

次にパルスが変化するのは4で、

  • コントロール: LOW
  • パルス:    立ち下がり

となるので 逆の計算 + 減算 => 通常の計算 + 加算 となり カウンタの値が加算されます。この様にワンクリックで2回計算するケースが有るので2つカウンタの値が増減するのだと思います。この方向に回転する場合パルスに変化が有るとカウンタの計算は常に加算に成ります。

今度は同じConfigで逆にエンコーダを回転させた場合を考えます。上記と逆ですから、4番から始まり1番までと成ります。最初のパルスの変化は4で

  • コントロール: LOW
  • パルス:    立ち上がり (上記は立ち下がりになっていますが逆回転なので逆に見て下さい)

となるので 逆の計算 + 加算 => 通常の計算 + 減算 となり カウンタの値が減算されます。次の2のケースも同様に減算となり、この方向に回す場合カウンタの値は常に減算されます。

カウンタの加減が予定していた回転方向と逆の場合、下記のどれかを行えば変える事が出来ます。

  • pcnt_config.pulse_gpio_num と pcnt_config.ctrl_gpio_num の値を変える
    • このエンコーダの2ピンにパルス、コントロールの区別が有りません。
    • よってお互いを入れ替えれば条件が反転します。
  • pcnt_config.lctrl_mode と pcnt_config.hctrl_modeの値を変える
    • 演算方法を逆にするので当然カウンタの計算方法が逆に成ります。
  • pcnt_config.pos_mode と pcnt_config.neg_modeの値を変える
    • 条件を逆にする事になるのでカウンタ計算方法が逆に成ります。

またワンクリックでカウンタの値を1つ変化させたい場合は、pcnt_config.pos_mode またはpcnt_config.neg_mode のどちらかを PCNT_COUNT_DIS と設定すれば、ワンクリック1カウントと成ります。

この関数を使うと呼ぶたけでカウント値が得られるので非常に便利です。

追記

今回エンコーダの各相をESP32の入力専用ポートに接続したのですが、当初正常に動きませんでした。PCNTのソースを確認したら各相のポートはソフトでPULL_UPされていました。入力専用ポートにはPULL_UP機能が無くそれが原因で正常に機能しなかった様です。100KΩの抵抗でPULL_UPしたら正常に動く様に成りました。