PICマイコン(PIC16F1827)のDIのチャタリング防止

組み込みエンジニア

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

マイコンにおいて外部機器の状態をDIに取り込んで判定させることがあります。スイッチやリレーなどで遮断容量が大きいほど瞬時に信号が切り替わらず信号が安定しない期間(チャタリング)があります。ソフトで対策を行う方法をまとめました。

DIのチャタリング防止したフィルタをDIフィルタと表現します。DI情報のサンプリングタイミングはTMR0を使ってソフトウェアタイマを構成して生成します。

PIC16F1827でDIフィルタをつくる

マイコンのDI(デジタル入力)は入力信号の電圧によってHighレベル(1)かLowレベル(0)かの判定を行います。DI信号を取り込む際に注意する必要があるチャタリングとDIフィルタについて説明します。

DI信号のチャタリング

チャタリングの説明図

スイッチがONからOFF(又はOFFからON)になるとスイッチの接触抵抗などによって電圧レベルが安定しない瞬間があります。

数ms経過するとDI信号は安定するので安定した状態をDI情報として取り込む必要があります。

僅か数ms間ではありますが1と0が繰り返されるためフィルタを入れていなかった場合誤動作の可能性があります。リレーの接点など遮断容量が大きなものほどチャタリングが大きく出る傾向があります。

周辺回路にコンデンサを入れたりすることでチャタリングの高周波成分を減衰させることができますが完全に除去できるわけではありません。

ソフトでDI信号が安定したことを確認して入力として採用することでチャタリングの影響を抑えることができます。

DIフィルタの例

DIフィルタの説明
DIフィルタの説明

wpはbuf[0]からbuf[3]においてデータを格納する位置を示すポインタです。buf[0]から順番にwpを更新しながらbuf[1]・・・buf[3]までデータを格納します。buf[3]にデータを格納するとwpをクリアして次のデータをbuf[0]に格納するようにします。

void DiFilter(void){
    uint8_t i;
    bool    boo = true;

    DiData.buf[ DiData.wp ] = IO_RB0_GetValue();

    for( i = 1; i < DI_NUM; i++){
        if( DiData.buf[i-1] != DiData.buf[i] ){
            boo = false;
            break;
        }
    }

    if( boo ){ //比較して一致したら値を採用
         IO_RA3_LAT = DiData.buf[ 0 ]; 
    }

    if( ++DiData.wp >= DI_NUM){ //wpを更新
        DiData.wp = 0; wpをクリア
    }
}

例ではRB0のDI情報を4回分の格納したデータが一致したときにDIデータとして採用するようにフィルタを構成したものです。ソフトウェアタイマによって10ms毎にDiFilter()の処理を行うようにします。

buf[0]にデータを格納すると10ms後にはbuf[1]にDI値を格納します。これを繰り返してbuf[0]からbuf[3]までの4つの値が一致した時にDI信号の値として採用します。

4回分の値が一致してから出力が変更されることから応答時間が最小で約40msになりますが、チャタリングしてDI信号が安定しない数msの区間を除いて値が採用できるようになります。

タイマーの時限を10msとしているのでボタンを押すタイミングによっては最大で10msの誤差が出ます。これらを考慮して応答時間は40ms~50msになります。

ソフトウェアタイマの構成によってDiFilter()をコールするタイミングが長くなるほどチャタリング防止による誤動作は防ぎやすくなりますが、応答時間が長くなってしまいます。

ソフトウェアタイマの間隔を短くしたりバッファの値を増やしたりなど、仕様によって調整することによって任意のフィルタを実装することができます。

DIフィルタを実装する

MCCが作成した関数をmain.cに組み込んでTMR0オーバーフロー割り込みを発生させタイマ管理を行いDIフィルタ処理を行います。

MCCによる設定

MCCを使ってレジスタ設定を行っています。タイマ管理の方法と同様の設定でDIフィルタを構成しています。MCCの設定については下記記事を参考にしてください。

PICマイコン(PIC16F1827)でタイマーを管理する方法

動作確認用の回路

DIフィルタの動作確認用の回路
DIフィルタの動作確認用の回路

タイマ管理によって10ms毎にRB0のDI情報を取得します。DIフィルタによってDIが確定した値をもとにLED3を点灯/消灯します。SW1を押しDIフィルタでLOWが確定できるとLED3を消灯します。

タイマ管理によって250ms毎にRB6をトグル出力(反転しながら出力)させるためLED2を点灯/消灯します。LED1はDIフィルタにおいて状態が不一致の区間を検出して2秒間点灯させます。SW1がONからOFF(OFFからON)に状態変化したとき点灯します。

RB0はウィークプルアップしているため信号レベルが浮くことはありません。ピンの配置は以下の通りです。

PIC16F1827のピン設定(MCC)
PIC16F1827のピン設定(MCC)

PB0はINPUTでウィークプルアップ(WPU)設定しています。WPUを有効にするためにはRegistersのnWPUENをenabledにする必要があります。

Easy SetupでWPUにチェックするのみでは有効にならないので注意が必要です。Notificationsにワーニングで通知されます。

DIフィルタの初期化

DIフィルタを使用すると初期起動時に状態変化を検出することがあります。RB0をウィークプルアップを使用していますが初期化時はDIフィルタを構成するバッファがクリアされた状態であるため状態の不一致が発生する期間があります。

メイン処理に遷移したときにDIフィルタが確定していないために誤動作を検出してしまう可能性があります。

void main(void)
{
    DiFilterInit(); //メイン処理前にDIを確定する

    while (1){
        //メイン処理
    }
}
// DIフィルタの初期化 //
void DiFilterInit(void){
	
    CntInit = CNT_INIT_MAX;
    while(CntInit > 0){  //0になるまでフィルタを実施
	DiFilter();      //DIフィルタ処理
	__delay_ms(10);  //10ms遅延させてDIフィルタ処理
        CntInit--;
    }    
}

例のようにメイン処理においてwhite(1)に遷移する前にDiFilterInit()関数によってDI情報を確定しておく必要があります。

DiFilterInit()はメイン処理に遷移する前なのでdelay_ms(10)を使用して10msウェイトを置いています。規定回数(CNT_INIT_MAX)のDIフィルタの処理を行うことでDIが確定した状態でメイン処理に遷移することができます。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include "mcc_generated_files/mcc.h"

#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define TIME_BASE_MAX 10     //ベースタイマカウント値
#define	CNT_INIT_MAX 10     //10ms×10 = 100ms
#define TIME_FILTER_MAX 1    //ベースタイマカウント値
#define TIME_LED_MAX 25     //250ms
#define TIME_LED_OFF 200    //2s
#define DI_NUM 4            //DIフィルタのサンプリング数

typedef struct{
	uint8_t    wp;
	uint8_t    buf[ DI_NUM ];
}FILT_DATA;
//--------------変数定義----------------------------
int16_t cnt10ms;
int16_t timfilter;   //DIフィルタ起動
int16_t timled;      //動作確認用LED
int16_t timledoff;  //チャタリング発生通知LEDをOFF
uint8_t CntInit;	//初期化時のみ使用
FILT_DATA DiData;
//--------------関数プロトタイプ宣言----------------------------
void mainApp(void);     //メイン関数内での処理
void TMR0_INT(void);    //タイマー0割り込みでの処理
void mainTimer(void);
void DiFilter(void);
void DiFilterInit(void);

/* Main application */
void main(void)
{
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(TMR0_INT);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();
    DiFilterInit(); //メイン処理前にDIを確定する

    while (1)
    {
        mainApp();
        mainTimer();
        DiFilter();
    }
}
/* main処理  */
void mainApp(void){
    
    if( timled == TIME_UP ){ //タイムアップしたか
        timled = TIME_LED_MAX;
        IO_RB6_Toggle();
    }
    
    if( timledoff == TIME_UP ){
        timledoff = TIME_OFF;
        IO_RA7_SetHigh(); //チャタリングが発生(OFF))
    }
}
/* TMR0オーバーフロー割り込みでの処理  */
void TMR0_INT(void){  
 
    ++cnt10ms;
}
/* タイマ管理関数 */
void mainTimer(void){
 
    if( cnt10ms >= TIME_BASE_MAX){ //規定回数オーバーフローしたか
        cnt10ms -= TIME_BASE_MAX;
        
        if( timfilter > TIME_UP ){
            --timfilter; //タイマを更新
        }
        
        if( timled > TIME_UP){
            --timled; //タイマを更新
        }
        
        if( timledoff > TIME_UP){
            --timledoff; //タイマを更新
        }
    }
}
// DIフィルタの初期化 //
void DiFilterInit(void){
	
    CntInit = CNT_INIT_MAX;
    while(CntInit > 0){  //0になるまでフィルタを実施
	DiFilter();         //DIフィルタ処理
	__delay_ms(10);     //10ms遅延させてDIフィルタ処理
        CntInit--;
	timfilter = TIME_UP;
    }    
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){
    uint8_t i;
    bool boo = true;

    if( timfilter == TIME_UP ){
        timfilter = TIME_FILTER_MAX;

        DiData.buf[ DiData.wp ] = IO_RB0_GetValue();

        for( i = 1; i < DI_NUM; i++){
            if( DiData.buf[i-1] != DiData.buf[i] ){
                boo = false;
                IO_RA7_SetLow(); //チャタリングが発生
                timledoff = TIME_LED_OFF;
                break;
            }
        }

        if( boo ){ ////比較して一致したら値を採用
             IO_RA3_LAT = DiData.buf[ 0 ]; 
        }

        if( ++DiData.wp >= DI_NUM){
                DiData.wp = 0;
        }
    }
}
/**
 End of File
*/

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていないと使用できません。

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

RUNTEQ-プログラミングで自由を手に入れる

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました