もっと軽くて使いやすいanalogRead()用のライブラリを作ってみたの続きです。
ArduinoでMIDIスイッチを作って使っているのですが、もうちょっと反応が速くならないかなあと思っていたのです。
チャタリングを防ぐにはデバウンス処理がどうしても必要で、その分反応が鈍くなってしまう...
Exponential Filterのやりかたを使えばなんとかなるかも?という気がしてきたのでやってみました。
DebFuncs for Arduino
そういえばSchmitt triggerなんてのがあったな...
何に使うかピンとこなくて今までスルーしてたけど、あらためて調べてみたら今回の用途にドンピシャ。ソフト的にまねします。
やってることは単純で
- 入力を平滑化(指数移動平均をとる)して
- 現在値がHIGH(=1)の場合は閾値
0.250.1未満でLOWに変化 - 現在値がLOW(=0)の場合は閾値
0.750.9超えでHIGHに変化
0か1のノイズ込みの入力を平滑化すれば0と1の間をふらふら行ったり来たりする信号になるのですが、中央(0.5)付近では反応せず、両端(0または1)に寄ったときのみ値が変化する、という戦略です。
指数移動平均はデフォルトで rate=25で、新しい値がおおむね四つ続いたら閾値に到達して値が変化するようにしました。 その後テストしたら自分の環境では結構誤動作があったので、 ~~5まで下げました。~~25に戻してスレッショルドの方をいじりました。
#ifndef DEB_FUNCS_H
#define DEB_FUNCS_H
#include "Arduino.h"
namespace DF{
class Debounce{
private:
const long RATE;
const long THRESHOLD_L;
const long THRESHOLD_H;
long recentValueMul100;
bool debouncedValue;
public:
Debounce();
Debounce(long rate);
Debounce(long rate, long thresholdL, long thresholdH);
bool operator()(const bool& currentValue);
};
class IsDROPPED{
private:
bool recentValue;
public:
IsDROPPED();
bool operator()(const bool& currentValue);
};
class IsRAISED{
private:
bool recentValue;
public:
IsRAISED();
bool operator()(const bool& currentValue);
};
class IsCHANGED{
private:
bool recentValue;
public:
IsCHANGED();
bool operator()(const bool& currentValue);
};
}
#endif
#include "DebFuncs.h"
DF::Debounce::Debounce()
: RATE( 25 )
, THRESHOLD_L( 10 )
, THRESHOLD_H( 90 )
, recentValueMul100( 100 )
, debouncedValue( true )
{
}
DF::Debounce::Debounce( long rate )
: RATE(rate)
, THRESHOLD_L(10)
, THRESHOLD_H(90)
, recentValueMul100(100)
, debouncedValue(true)
{
}
DF::Debounce::Debounce( long rate
, long thresholdL
, long thresholdH
)
: RATE( rate )
, THRESHOLD_L( thresholdL )
, THRESHOLD_H( thresholdH )
, recentValueMul100( 100 )
, debouncedValue( true )
{
}
bool DF::Debounce::operator()( const bool& currentValue ){
const long SMOOTHED_VALUE_MUL100 ( RATE * currentValue
+ (100 - RATE) * recentValueMul100 / 100
);
recentValueMul100 = SMOOTHED_VALUE_MUL100;
return debouncedValue ?
( SMOOTHED_VALUE_MUL100 < THRESHOLD_L )?(debouncedValue = false):true
:
( SMOOTHED_VALUE_MUL100 > THRESHOLD_H )?(debouncedValue = true):false
;
}
DF::IsDROPPED::IsDROPPED():recentValue( false ){
}
bool DF::IsDROPPED::operator()( const bool& currentValue ){
const bool RESULT ( recentValue && !currentValue );
recentValue = currentValue;
return RESULT;
}
DF::IsRAISED::IsRAISED():recentValue( false ){
}
bool DF::IsRAISED::operator()( const bool& currentValue ){
const bool RESULT ( !recentValue && currentValue );
recentValue = currentValue;
return RESULT;
}
DF::IsCHANGED::IsCHANGED():recentValue( false ){
}
bool DF::IsCHANGED::operator()( const bool& currentValue ){
const bool RESULT ( recentValue != currentValue );
recentValue = currentValue;
return RESULT;
}
以前に作ったDebounceFunc for Arduinoとの大きな違いは、以前はそれぞれの関数内で独自に値を更新していたので関数同士の整合性がとれない可能性があったのに対し、今度のは、デバウンスはloop()内で一回だけして、その値を使いまわせるようにしたので、関数間の整合性がとれるようになった点です。
改めて見直してみると、一個の関数でなんとか済ませられるようにと思ったんでしょう、関数の内部に状態を保存するオブジェクトを隠しもたせてメソッドで更新しつつ返すというようなことをやってました。何やってたんだろ、俺。
使い方
ライブラリをダウンロードして、スケッチ>ライブラリをインクルード>.ZIP形式のライブラリをインストール... でダウンロードしたzipファイルまたはフォルダを指定してインストールします。
関数名を変えたしデバウンスを明示的に計算するひと手間が増えましたが、あとはほぼ同じです。
#include <DebFuncs.h>
// Instantiate a set of function objects.
DF::Debounce debounceOne;
// or debounceOne(10),for example.
// You can set reaction rate from 0(no reaction) up to 100(no debouncing).
// default value is 25.
DF::IsDROPPED isDroppedOne;
// Instantiate a set of arrays.
DF::Debounce debounceArray[4];
// or debounceArray[4]={10,10,10,10},for example.
// You can set each reaction rate from 0(no reaction) up to 100(no debouncing).
// default value is 25.
DF::IsDROPPED isDroppedArray[4];
const int PIN_ONE = 2;
const int PIN_ARRAY[4] = {3,4,5,6};
void setup() {
pinMode( PIN_ONE, INPUT_PULLUP );
for (int i = 0; i < 4; i++){
pinMode( PIN_ARRAY[i], INPUT_PULLUP );
}
}
void loop() {
// regular pattern :
const bool DEBOUNCED_DATA = debounceOne( digitalRead( PIN_ONE ) );
if ( isDroppedOne( DEBOUNCED_DATA ) ){
// Do anything you want only when the debounced data is dropped.
}
// regular pattern for array :
for (int i = 0; i < 4; i++){
const bool DEBOUNCED_DATA = debounceArray[i]( digitalRead( PIN_ARRAY[i] ) );
if ( isDroppedArray[i]( DEBOUNCED_DATA ) ){
// Do anything you want only when the debounced data is dropped.
}
}
delay(1);
}
使用例
以前の記事と同じAbleton LIVE用のトランスポートです。
#include <MIDIUSB.h>
#include <DebFuncs.h>
const int LEDPIN = 13;
const int INPIN[] = {3,4,5,6};
const int CC_NUM[] = {25,26,27,28};
DF::Debounce debounce[4];
DF::IsDROPPED isDropped[4];
DF::IsRAISED isRaised[4];
void setup() {
for (int i = 0; i < 4; i++ ) {
pinMode( INPIN[i], INPUT_PULLUP );
}
pinMode( LEDPIN, OUTPUT );
Serial.begin( 115200 );
}
void loop() {
for (int i = 0; i < 4; i++ ) {
const int DEBOUNCED_DATA = debounce[i]( digitalRead( INPIN[i] ) );
if ( isDropped[i]( DEBOUNCED_DATA ) ) {
controlChange( 0, CC_NUM[i], 127 );
MidiUSB.flush();
digitalWrite( LEDPIN, HIGH );
}
if ( isRaised[i]( DEBOUNCED_DATA ) ) {
controlChange( 0, CC_NUM[i], 0 );
MidiUSB.flush();
digitalWrite( LEDPIN, LOW );
}
}
delay(1);
}
void controlChange( byte channel, byte control, byte value) {
midiEventPacket_t event = { 0x0B, 0xB0 | channel, control, value };
MidiUSB.sendMIDI( event );
}
#シリアルプロッタで比べてみました
青がdigitalRead()した素の値、黄色がDebFuncs、赤がDebounceFuncでデバウンスした値です。
設定は双方デフォルト値で。
ON(HIGH -> LOW)時のチャタリングに対する反応:
OFF(LOW -> HIGH)時のチャタリングに対する反応:
コードはこちら。有り物を使った関係でピン4と5の間にスイッチがつながっています。
#include <DebFuncs.h>
#include <DebounceFunc.h>
const int inPin = 4;
const int gnd_pin = 5;
DF::Debounce debounce;
DebounceFunc debounceFunc(1,30);
void setup() {
pinMode(inPin,INPUT_PULLUP);
pinMode(gnd_pin,OUTPUT);
digitalWrite(gnd_pin, LOW);
Serial.begin(115200);
}
void loop() {
const bool RAW_DATA = digitalRead(inPin);
const bool DEBOUNCED_DATA = debounce(RAW_DATA);
const bool NEW_STATE = debounceFunc.getDebouncedState(RAW_DATA);
Serial.print(RAW_DATA);
Serial.print(" ");
Serial.print(DEBOUNCED_DATA);
Serial.print(" ");
Serial.print(NEW_STATE);
Serial.println(" ");
}