#ESP32にはパルスカウンタ機能が付いてる
ハードウェアでのパルスカウンタが実装されているのでロータリーエンコーダとか楽ちんに使える
しかも8系統も同時入力可能(!?)
つくづくなぜこんなマイコンが700円前後なのか理解が出来ない…
#arduino coreでも使えました
公式サンプルにはESP-IDF向けのものしかパルスカウンタのサンプルが見つかりませんでした。
https://github.com/espressif/esp-idf/tree/master/examples/peripherals/pcnt
ググってもほぼヒットせず、唯一使用例を見つけられたのがこちら。
https://qiita.com/kentegrate/items/7f2431121b782fae63fa
ESP32はArduino IDEでもプログラミングできますが、そのためのライブラリは未実装なところも多く、実装されていない機能(パルスカウンタなど)を使いたかったため、ESP-IDFを使用しました。サンプルコードが全然なかったのですが、このリポジトリは様々なコードがあり、大変参考になりました。
やはりESP-IDFでしかダメなのか…しかしずっとarduinoで書いてきたビギナープログラマーには敷居が高い
…と思ってましたがアップデートされたのかarduinoでも問題なく使えたので備忘録代わりに書いておきます。
#部品の接続
マウスのホイール部分に使われているような二相式ロータリーエンコーダが使いやすいと思います。
エンコーダのA相をGPIO4に、B相をGPIO5に繋いでください。
#コード
基本的には上記のESP-IDF向けの公式サンプルをarduino coreに修正した感じです
#include "driver/pcnt.h"
#define PULSE_INPUT_PIN 4 //パルスの入力ピン 今回はエンコーダのA相を接続
#define PULSE_CTRL_PIN 5 //制御ピン 今回はエンコーダのB相を接続
#define PCNT_H_LIM_VAL 10 //カウンタの上限 今回は使ってない
#define PCNT_L_LIM_VAL -10 //カウンタの下限 今回は使ってない
int16_t count = 0; //カウント数
void setup() {
pcnt_config_t pcnt_config;//設定用の構造体の宣言
pcnt_config.pulse_gpio_num = PULSE_INPUT_PIN;
pcnt_config.ctrl_gpio_num = PULSE_CTRL_PIN;
pcnt_config.lctrl_mode = PCNT_MODE_REVERSE;
pcnt_config.hctrl_mode = PCNT_MODE_KEEP;
pcnt_config.channel = PCNT_CHANNEL_0;
pcnt_config.unit = PCNT_UNIT_0;
pcnt_config.pos_mode = PCNT_COUNT_INC;
pcnt_config.neg_mode = PCNT_COUNT_DEC;
pcnt_config.counter_h_lim = PCNT_H_LIM_VAL;
pcnt_config.counter_l_lim = PCNT_L_LIM_VAL;
pcnt_unit_config(&pcnt_config);//ユニット初期化
pcnt_counter_pause(PCNT_UNIT_0);//カウンタ一時停止
pcnt_counter_clear(PCNT_UNIT_0);//カウンタ初期化
Serial.begin(115200);
pcnt_counter_resume(PCNT_UNIT_0);//カウント開始
}
void loop() {
pcnt_get_counter_value(PCNT_UNIT_0, &count);
if(count > 100) pcnt_counter_clear(PCNT_UNIT_0);
if(count < -100) pcnt_counter_clear(PCNT_UNIT_0);
Serial.print("Counter value: ");
Serial.println(count);
delay(1000);
}
#動作
1秒ごとにカウントした値をシリアルで吐き出します。
-100または100を超えたら0にリセットします。
#設定用の構造体について
##.pulse_gpio_num
信号を受けるIOピンを指定。
##.ctrl_gpio_num
制御IOピンを指定。指定したピンがHIGHかLOWかで次の.hctrl_modeと.lctrl_modeに影響が出る。
エンコーダだと回転方向を判断してインクリメントorデクリメントするみたいな使い方かな。
一方向しか回転しないからいらないよ!って人はPCNT_PIN_NOT_USEDを指定してやればOK。
##.ctrl_lctrl_mode
制御ピンがLOWの時カウンタの振る舞いの選択。以下から選ぶ。
(1)PCNT_MODE_KEEP → 変更しない
(2)PCNT_MODE_REVERSE → インクリメントならデクリメントに、デクリメントならインクリメントに反転
(3)PCNT_MODE_DISABLE → カウントしない
##.ctrl_hctrl_mode
制御ピンがHIGHの時どうするかの選択。パラメータは上と一緒。
##.channel
これがわからないので誰か教えてください…パラメータは以下の二種類
PCNT_CHANNEL_0
PCNT_CHANNEL_1
2018/08/13追記
どうやらChannel_0にA相を、Channel_1にB相を割り当て、4逓倍するというような使い方ができるっぽい。
今まで4逓倍する時に2つユニット作って、取得した値を合算するといった使い方をしていたが、1つのユニットでできるみたい。
その場合のコードはこんな感じです。
#include "driver/pcnt.h"
#define PULSE_INPUT_PIN 4 //パルスの入力ピン 今回はエンコーダのA相を接続
#define PULSE_CTRL_PIN 5 //制御ピン 今回はエンコーダのB相を接続
#define PCNT_H_LIM_VAL 10 //カウンタの上限 今回は使ってない
#define PCNT_L_LIM_VAL -10 //カウンタの下限 今回は使ってない
int16_t count = 0; //カウント数
void setup() {
pcnt_config_t pcnt_config_A;//設定用の構造体の宣言(A相)
pcnt_config_A.pulse_gpio_num = PULSE_INPUT_PIN;
pcnt_config_A.ctrl_gpio_num = PULSE_CTRL_PIN;
pcnt_config_A.lctrl_mode = PCNT_MODE_REVERSE;
pcnt_config_A.hctrl_mode = PCNT_MODE_KEEP;
pcnt_config_A.channel = PCNT_CHANNEL_0;
pcnt_config_A.unit = PCNT_UNIT_0;
pcnt_config_A.pos_mode = PCNT_COUNT_INC;
pcnt_config_A.neg_mode = PCNT_COUNT_DEC;
pcnt_config_A.counter_h_lim = PCNT_H_LIM_VAL;
pcnt_config_A.counter_l_lim = PCNT_L_LIM_VAL;
pcnt_config_t pcnt_config_B;//設定用の構造体の宣言(B相)
pcnt_config_B.pulse_gpio_num = PULSE_CTRL_PIN;//↓A相用のconfigで設定したのと入れ替える
pcnt_config_B.ctrl_gpio_num = PULSE_INPUT_PIN;//↑A相用のconfigで設定したのと入れ替える
pcnt_config_B.lctrl_mode = PCNT_MODE_KEEP;//↓A相用のconfigで設定したのと入れ替える
pcnt_config_B.hctrl_mode = PCNT_MODE_REVERSE;//↑A相用のconfigで設定したのと入れ替える
pcnt_config_B.channel = PCNT_CHANNEL_1;//チャンネルを1に設定
pcnt_config_B.unit = PCNT_UNIT_0;//UNITはA相と同じものに
pcnt_config_B.pos_mode = PCNT_COUNT_INC;
pcnt_config_B.neg_mode = PCNT_COUNT_DEC;
pcnt_config_B.counter_h_lim = PCNT_H_LIM_VAL;
pcnt_config_B.counter_l_lim = PCNT_L_LIM_VAL;
pcnt_unit_config(&pcnt_config_A);//ユニット初期化A相
pcnt_unit_config(&pcnt_config_B);//ユニット初期化B相
pcnt_counter_pause(PCNT_UNIT_0);//カウンタ一時停止
pcnt_counter_clear(PCNT_UNIT_0);//カウンタ初期化
Serial.begin(115200);
pcnt_counter_resume(PCNT_UNIT_0);//カウント開始
}
void loop() {
pcnt_get_counter_value(PCNT_UNIT_0, &count);
if(count > 100) pcnt_counter_clear(PCNT_UNIT_0);
if(count < -100) pcnt_counter_clear(PCNT_UNIT_0);
Serial.print("Counter value: ");
Serial.println(count);
delay(1000);
}
##.unit
パルスカウンタのユニット。以下の通り8個まで使える。
PCNT_UNIT_0 ~ PCNT_UNIT_7
##.pos_mode
信号ピンに立ち上がりパルス(LOW→HIGH)が入った時どうするかの選択。以下から選ぶ。
(1)PCNT_COUNT_DIS → カウントしない。
(2)PCNT_COUNT_INC → インクリメント。カウントアップ。
(1)PCNT_COUNT_DEC → デクリメント。カウントダウン。
##.neg_mode
信号ピンに立ち下がりパルス(HIGH→LOW)が入った時どうするかの選択。パラメータは上と一緒。
##.counter_h_lim
カウンタの上限。この値を超えたら何か関数を割り込みとか設定出来るみたい?
##.counter_l_lim
カウンタの下限。この値を下回ったら何か関数を割り込みとか設定出来るみたい?
#その他の機能
pcnt_set_filter_valueで入力信号にフィルターをかけることが出来るようです。
チャタリングのような、指定値より短いパルスを無視するみたいな使い方かな?
引数のAPB_CLK cyclesという単位がわからないので詳しい人お願いします(丸投げ)
2018/5/10追記
1 APB_CLK cycles = 12.5nsらしい
このフィルターの設定の有無に関わらず12.5nsより短いパルスは認識できないそうな。