PR

Arduino環境でのタむマ管理ずDIのチャタリング防止の方法

組み蟌み゚ンゞニア
本蚘事はプロモヌションが含たれおいたす。

こんにちは、ENGかぎです。

Arduino環境で゜フト開発しおいるずDIに信号を入力しお凊理を刀定させるこずがよくありたす。DI信号がスむッチなどであった堎合チャタリングによっおDI信号が安定しないこずがありたす。察策ずしおフィルタを䜜る方法を蚘事にしたした。

Arduino UNOを察象ずしたすが、ArduinoのラむブラリであるMsTimer2を䜿甚しおタむマを起動しメむン関数内でベヌスタむマを䜜っおタむマの管理を行いたす。

Arduino UNOを䜿っお動䜜確認を行ったこずを䞋蚘リンクにたずめおいたす。

Arduinoで孊べるマむコンの゜フト開発ず暙準ラむブラリの䜿い方

Arduinoでタむマを動䜜させる

ArduinoのIDEの初期ではタむマ動䜜をさせるラむブラリがありたせん。あるのかもしれたせんが分かりたせんでした。Arduino環境でタむマ2を動䜜させるラむブラリずしおよく䜿甚されおいるMsTimer2のラむブラリをむンストヌルしお䜿甚したす。

MsTimer2のむンストヌル

Arduino-ラむブラリマネヌゞャ
Arduino-ラむブラリマネヌゞャ

Arduino IDEのツヌル内のラむブラリを管理を遞択するずラむブラリマネヌゞャ画面が衚瀺されたす。怜玢をフィルタに「mstimer2」倧文字小文字はどちらでもよいを入力するず衚瀺される「MsTimer2」䞭の右䞋のむンストヌルボタンを抌すずむンストヌルされたす。

䞊蚘の䟋は既にむンストヌル枈みなのでむンストヌルボタンが衚瀺されおいたせん。

タむマの管理の考え方

タむマ管理の考え方のむメヌゞ
タむマ管理の考え方のむメヌゞ

タむマを初期蚭定する際に蚭定した割り蟌み呚期がベヌスタむマずなりたす。割り蟌みが入る毎にカりントを+1しお曎新しおいき、芏定回数゜フトりェアタむマの分呚比を決める倀以䞊になったずき゜フトりェアタむマを曎新したす。

䟋ベヌスタむマを1msずし、芏定回数を10ずした堎合
1ms間隔でタむマの割り蟌みが発生しベヌスタむマ倀を曎新したす。芏定回数以䞊になったずき1msが10回経過したこずになるので10msのタむミングを぀くるこずができたす。

欠点はメむン関数の1呚の時間に巊右されるこずですが、通垞メむン関数のルヌプは1msを超えるこずが少ないため問題にならないこずが倚いです。メむン関数の1呚に時間を芁しおいる堎合は誀差が倧きくなる可胜性もありたすので誀差が問題になるような堎合は泚意が必芁です。

意図しおメむンルヌプ1呚が遅れおいる堎合は問題ありたせんが、割り蟌みを含めお遅くなりすぎる堎合はプログラムの構成を考え盎した方が良いかもしれたせん。

広告

タむマ管理を実装する

MsTimer2の初期化ずタむマ管理に぀いお䞀䟋を瀺したす。

#include <MsTimer2.h>

#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベヌスタむマずなる
#define LED_ONOFF 50

// application use
int8_t timled; //管理したいタむマ
int8_t cnt1ms; //1ms毎にカりントアップさせる
/*** Local function prototypes */
void TimerCnt();

MsTimer2を䜿甚するためにむンクルヌドしたす。手打ちしおもよいですがスケッチ内のラむブラリのむンクルヌドからMsTimer2を遞択するこずで衚瀺を远加するこずができたす。

管理したいタむマを笊号付の倉数ずしお宣蚀したす。-1をTIME_OFFず定矩しタむマを䜿甚しない時にセットしたす。MsTimer2のコヌルバック甚の関数ずしおTimerCnt()を宣蚀しおいたす。

void setup() {

    MsTimer2::set(1,TimerCnt); //1msごずに関数ぞ遷移 TimerCnt関数がコヌルバックされる
    MsTimer2::start();
    timled = LED_ONOFF; //゜フトりェアタむマを起動倀をセット
}
/* Timer2 callback function add */
void TimerCnt(){
    ++cnt1ms; //ベヌスカりントを曎新
}

MsTimer2割り蟌みが発生するずTimerCnt()がコヌルバックずしお呌び出されたす。TimerCnt()でベヌスカりントをカりントしお割り蟌みが発生した回数をカりントしおいたす。

void loop() {
 
    if( cnt1ms >= BASE_CNT ){//10msごずにここに遷移する
        cnt1ms -=BASE_CNT;
    
        if( timled > TIME_UP ){ //タむマ倀を曎新
            timled--;
        }
    }

    if( timled == TIME_UP ){
        //タむムアップた時に凊理したい内容を入れる
        //timled = TIME_OFF; タむマを䜿甚しない時はOFFにするず曎新停止ずなる
    }
}

ベヌスカりントが芏定倀10になったずきif文の条件を満たすためタむマ倀の曎新凊理ずなりたす。

MsTimer2の割り蟌みタむミングずBASE_CNTを組み合わせるこずで任意のタむマを䜜るこずができたす。

MsTimer2を䜿甚しない堎合参考

Arduino環境では32ビットでmsec管理できる関数ずしおmillis()がラむブラリずしお搭茉されおいたす。

#define BASE_CNT 10
uint32_t basemillis;    

if( millis() - basemillis > BASE_CNT ){
    basemillis = millis(); //条件を満たしたずきのmsecを保管
    
    if( timled > TIME_UP ){ //タむマ倀を曎新
        timled--;
    }
}

millis()では経過したmsecをカりントし続けるためカりント倀がBASE_CNT以䞊の差になったずきが10ms経過した時間になるためMsTImer2を䜿甚したずきず同じ動きになりたす。

欠点ずしおは倀がオヌバヌフロヌしお0xFFFFFFFFから0に戻ったずきにカりント倀の刀定が䞀床だけずれおしたうこずです。オヌバヌフロヌするのは電源ONから1193時間埌なので垞にONしおおくこずがなれば問題になりたせん。

スポンサヌリンク

チャタリング防止を考える

マむコンのDIはデゞタル入力で入力された電圧倀からHighレベル1かLowレベル0かの刀定を行いたす。スむッチのチャタリングなどが原因でマむコンが誀動䜜する可胜性があるため泚意が必芁です。

DI信号のチャタリング

チャタリング防止の考え方
チャタリング防止の考え方

スむッチがONからOFFになったずきスむッチの接觊抵抗などによっお電圧レベルが安定しない瞬間がありたす。数ms経過するずDI信号が安定するため、安定埌にDI情報を取り蟌む察策が必芁です。

僅か数ms間ではありたすがず0が繰り返されるこずからフィルタを入れおいなかった堎合誀動䜜の可胜性がありたす。リレヌの接点などは容量が倧きくチャタリングが倧きく出るこずがありたす。

呚蟺回路でコンデンサを入れたりするこずでチャタリングの高呚波成分を枛衰させるこずができたすが、完党に陀去できるわけではありたせん。

゜フトでDI信号が安定したこずを確認しお入力ずしお採甚するこずでチャタリングの圱響を抑えるこずができたす。

DIフィルタを実装する

DIフィルタはDIのチャタリング察策のために実装するものです。DIフィルタの方法は様々ですが以䞋の通りが䞻になりたす。

  1. タむマを䜿っお耇数回䞀臎した倀を採甚する方法
  2. メむンルヌプ呚回ごずにDI情報を比范しお芏定回数䞀臎を確認する方法

今回はのタむマを䜿った方法を実装したす。

#define DI_FILT_MAX 4

enum DI_NO{
    DI1 = 0,
    DI2,
    DI_MAX
};

struct DIFILT_TYP{
    uint8_t wp;
    uint8_t buf[DI_MAX][DI_FILT_MAX];
    uint8_t di1;
    uint8_t di2;
};

DIFILT_TYP difilt;
void DiFilter();

䞊蚘のタむマ管理の方法を䜿っお10ms毎にDIの情報を確認し4回䞀臎したずきDI倀ずしお採甚する方法をDiFilter()関数の凊理ずしお実装したす。

void DiFilter(){

    if( timdifilt == TIME_UP ){
        difilt.buf[DI1][difilt.wp] = digitalRead(PIN_DI1);
        difilt.buf[DI2][difilt.wp] = digitalRead(PIN_DI2);
    
        if( difilt.buf[DI1][0] == difilt.buf[DI1][1] &&
            difilt.buf[DI1][1] == difilt.buf[DI1][2] &&
            difilt.buf[DI1][2] == difilt.buf[DI1][3] ){
            difilt.di1 = difilt.buf[DI1][0];
        }
    
       if( difilt.buf[DI2][0] == difilt.buf[DI2][1] &&
           difilt.buf[DI2][1] == difilt.buf[DI2][2] &&
           difilt.buf[DI2][2] == difilt.buf[DI2][3] ){
           difilt.di2 = difilt.buf[DI2][0];
        }
        
       if( ++difilt.wp >= DI_FILT_MAX ){
           difilt.wp = 0;
       }

       timdifilt = FILT_MIN;
    }
}

timdifiltタむマがTIME_UPするず内郚の凊理を行いたす。DI情報を栌玍するバッファを曎新しながら入れおいき4぀のバッファの倀が䞀臎したずきDI情報ずしお採甚しおいたす。

4回䞀臎でDI情報ずしお採甚するためチャタリングによる䞍安定な郚分を陀いお倀が採甚できるようになりたす。

timdifiltにセットする倀を倧きくするずDI情報の安定を刀定する時間は長くなりたすが、チャタリング防止による誀動䜜は防ぎやすくなりたす。

䟋では4぀のバッファで構成しおいたすが、タむマの倀を短くしたりバッファの倀を増やす方法を遞択したりするこずで任意のフィルタを実装するこずができたす。

void setup() {
   
    MsTimer2::set(1,TimerCnt); //1msごずに関数ぞ遷移
    MsTimer2::start();
    timdifilt = FILT_MIN;

    for( uint8_t i=0; i < 10; i++ ){
       //メむンに行く前にDI情報を確定しおおく
        mainTimer();
        DiFilter();
        delay(10);
    }
}

メむンルヌプに遷移する前にDI情報を確定しおおきたい堎合はsetup()関数から抜ける前にDIフィルタ凊理を入れおおくずよいです。

補品の動䜜トリガヌになっおいる堎合やDI情報で凊理を分岐させる堎合は、誀動䜜防止のためDIフィルタの初期化はしおおいたほうが良いでしょう。

スポンサヌリンク

タむマ管理ずDIフィルタの動䜜を確認する

タむマ管理ずDIフィルタの動䜜確認
タむマ管理ずDIフィルタの動䜜確認

11番をDOにしおタむマ管理の方法でLEDを点灯/消灯しおいたす。同じタむミングだずタむマ管理ができおいるこずの確認がしにくいため点灯の時間を短くしお消灯の時間を短くするようにしたす。

番をDIピンのマむコンプルアップに蚭定しSW1を抌したずきLOWレベルになりシリアルモニタに「di1–ok」を衚瀺するようにしたす。同様に7番もDIピンのマむコンプルアップにしSW2を抌したずきLOWレベルにしシリアルモニタに「di2–ok」を衚瀺するようにしたす。

10msのタむマ管理で4回分の倀が䞀臎しおから出力が倉曎されるこずから応答時間が最小で玄40msずなりたす。

タむマヌの時限を10msずしおいるのでボタンを抌すタむミングによっおは最倧で10msの誀差が出たす。これらを考慮するず応答時間は40ms50msになりたす。

シリアルモニタでの確認
シリアルモニタでの確認

SW1ずSW2を抌したずきにシリアルモニタに察象の文字が衚瀺されたす。10msのタむマで確認しおいるため効果が分かりにくいのですが、DIフィルタをタむマ管理を長くずるこずで倉化が分かりやすくなりたす。timdifiltにセットする倀を増やすずSWに察する反応が遅くなるため効果が分かりやくなりたす。

䟋えばtimdifiltに50をセットするず50×10msで1回DIの状態を確認し、4回䞀臎しおDI情報ずするので2秒経過しないず応答しないフィルタになりたす。応答が遅すぎるのは問題なのでほどほどがよさそうです。

スポンサヌリンク

゜ヌスコヌド党䜓

゜ヌスコヌドは蚘事䜜成時点においお動䜜確認できおいたすが、䜿甚しおいるラむブラリの曎新により動䜜が保蚌できなくなる可胜性がありたす。たた、゜ヌスコヌドを䜿甚したこずによっお生じた䞍利益などの䞀切の責任を負いかねたす。参考資料ずしおお䜿いください。

#include <MsTimer2.h>

#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベヌスタむマずなる
#define LED_ONOFF 50
#define LED_ONOFF2 100
#define FILT_MIN 1

#define PIN_DO1 11
#define PIN_DI1 6
#define PIN_DI2 7
#define DI_FILT_MAX 4

enum DI_NO{
    DI1 = 0,
    DI2,
    DI_MAX
};

struct DIFILT_TYP{
    uint8_t wp;
    uint8_t buf[DI_MAX][DI_FILT_MAX];
    uint8_t di1;
    uint8_t di2;
};

// application use
int8_t timled = LED_ONOFF;
int8_t timled2 = TIME_OFF;
int8_t timdifilt = TIME_OFF;
int8_t cnt10ms;
bool btnflg1;
bool btnflg2;
DIFILT_TYP difilt;

/*** Local function prototypes */
void TimerCnt();
void mainTimer();
void DiFilter();

void setup() {
  
    pinMode( PIN_DO1, OUTPUT);
    pinMode( PIN_DI1, INPUT_PULLUP );
    pinMode( PIN_DI2, INPUT_PULLUP );
  
    MsTimer2::set(1,TimerCnt); //1msごずに関数ぞ遷移
    MsTimer2::start();
    Serial.begin(115200);
    timdifilt = FILT_MIN;

    for( uint8_t i=0; i < 10; i++ ){
        mainTimer();
        DiFilter();
        delay(10);
    }
}

void loop() {
 
    mainTimer();
    DiFilter();

    if( timled == TIME_UP ){ //timled2ず亀互に䜿甚しおLEDをONOFFしおいる
        timled = TIME_OFF;
        timled2 = LED_ONOFF2; //LED_ONOFF2を長くするずON期間が長くなる
        digitalWrite(PIN_DO1, HIGH);
    }

    if( timled2 == TIME_UP ){ 
        timled2 = TIME_OFF;
        timled = LED_ONOFF; //LED_ONOFFを長くするずOFF期間が長くなる
        digitalWrite(PIN_DO1, LOW);
    }

    if(difilt.di1 == 0){
        if(btnflg1){
            btnflg1 = false;
            Serial.println("di1--ok");
        }
    }else{
        btnflg1 = true;
    }
  
    if(difilt.di2 == 0){
        if(btnflg2){
            btnflg2 = false;
            Serial.println("di2--ok");
        }
    }else{
        btnflg2 = true;
    }
}
/* callback function add */
void TimerCnt(){
    ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){

    if( cnt10ms >= BASE_CNT ){
        cnt10ms -=BASE_CNT;
        //10msごずにここに遷移する
        if( timled > TIME_UP ){
            timled--;
        }

        if( timled2 > TIME_UP ){
            timled2--;
        }

        if( timdifilt > TIME_UP ){
            timdifilt--;
        }
    }
}
/* DiFilter function add */
void DiFilter(){

    if( timdifilt == TIME_UP ){
        difilt.buf[DI1][difilt.wp] = digitalRead(PIN_DI1);
        difilt.buf[DI2][difilt.wp] = digitalRead(PIN_DI2);
    
        if( difilt.buf[DI1][0] == difilt.buf[DI1][1] &&
            difilt.buf[DI1][1] == difilt.buf[DI1][2] &&
            difilt.buf[DI1][2] == difilt.buf[DI1][3] ){ //4回䞀臎を確認
            difilt.di1 = difilt.buf[DI1][0];
        }
    
        if( difilt.buf[DI2][0] == difilt.buf[DI2][1] &&
            difilt.buf[DI2][1] == difilt.buf[DI2][2] &&
            difilt.buf[DI2][2] == difilt.buf[DI2][3] ){ //4回䞀臎を確認
            difilt.di2 = difilt.buf[DI2][0];
        }
        
        if( ++difilt.wp >= DI_FILT_MAX ){
            difilt.wp = 0;
        }

        timdifilt = FILT_MIN;
    }
}

メむン関数であるloop()に遷移する前にsetup()関数内でDIフィルタの倀を確定するためにfor文でdelay(10)で遅延させながらDI倀を確定しおいたす。

関連リンク

Arduinoのラむブラリを䜿っお動䜜確認を行ったこずを䞋蚘リンクにたずめおいたす。

Arduinoで孊べるマむコンの゜フト開発ず暙準ラむブラリの䜿い方

Seeeduino XIAOで孊べる゜フト開発ず暙準ラむブラリの䜿い方

ESP32-WROOM-32Eで孊べる゜フト開発ず暙準ラむブラリの䜿い方

Raspberry Pi Picoで孊べる゜フト開発

PRキャリア圢成でお悩みのあなたぞ初回無料盞談で盞談できる[coachee]

最埌たで、読んでいただきありがずうございたした。

タむトルずURLをコピヌしたした