こんにちは、ENGかぴです。
マイコンのソフト開発しているとDIに信号を入力しては処理を判定させることがよくあります。DI信号がスイッチなどであった場合チャタリングによってDI信号が安定しないことがあります。対策としてフィルタを作る方法をまとめました。
この考え方はPICマイコンだけでなく他のマイコンでも応用できます。メインループを周回するごとにDIピンの情報を確認して規定回数一致すると採用する考え方もできますが、今回はタイマ機能を使ってフィルタを作っています。
PIC12F675を使ってマイコンの動きを勉強するためにPIC12F675の機能でできることについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PIC12F675のDIのチャタリング防止
マイコンのDIはデジタル入力です。入力された電圧値からHighレベル(1)かLowレベル(0)かの判定を行います。スイッチのチャタリングなどが原因でマイコンが誤動作する可能性があるため注意が必要です。
DI信号のチャタリング
スイッチがONからOFFになったときスイッチの接触抵抗などによって電圧レベルが安定しない瞬間があります。
数ms経過するとDI信号は安定するので安定した状態をDI情報として取り込む必要があります。
僅か数ms間ではありますが1と0が繰り返されるためフィルタを入れていなかった場合誤動作の可能性があります。リレーの接点などは容量が大きくチャタリングが大きく出ることがあります。
周辺回路にコンデンサを入れたりすることでチャタリングの高周波成分を減衰させることができますが完全に除去できるわけではありません。
DIフィルタの効果
DIフィルタはソフトで変化をコントロールする処理を作って実現したものです。DIフィルタの考え方は様々ですが、例の一つとして10ms毎にDIを確認して数回一致した場合ときのDI値を採用する方法を考えていきます。
TMR1を10msでオーバーフローするように実装し10ms毎にDI値を準備したバッファ(値を格納する変数)に入れていきます。buf[0]が初めに格納するバッファとしたとき10ms後にはbuf[1]にDI値を格納します。これを繰り返してbuf[0]からbuf[3]までの4つの値が一致した時にDI信号の値として採用します。
これによってチャタリングしてDI信号が安定しない部分を除いて値が採用できるようになります。
例では4つのバッファで構成していますが、TMR1の間隔を短くしてバッファの値を増やす方法を選択したり、TMR1の間隔を長くしてバッファの数を減らすなど使い方によって調整することによって任意のフィルタを実装することができます。
PIC12F675の初期設定
DIフィルタはTMR1割り込みを使って実装することができます。TMR1を10msでオーバーフローするように設定しDI信号を格納するバッファは4つ(40ms程度のフィルタ)とします。
TMR1を実装する
TMR1の初期化と使い方については下記記事を参考にしてください。
PICマイコン(PIC12F675)の割り込みの設定と使い方
TMR1はDI値を格納するタイミングとして使用します。
初期部分を実装する
typedef struct{
char wp;
char buf[ DI_NUM ];
}FILT_DATA;
//--------------変数定義----------------------------
FILT_DATA DiData; //FILT_DATAの型で宣言
//-----------------関数定義---------------------------
void DiFilter(void);
void main(void) {
//各種初期化
//DIフィルタ初期化
DiData.wp = 0;
filtflg = BIT_CLR;
//DIフィルタ初期起動
CntInit = CNT_INIT_MAX;
while(CntInit > 0){ //0になるまでフィルタを実施
CLRWDT();
DiFilter(); //DIフィルタ処理
__delay_ms(10); //10ms遅延させてDIフィルタ処理
CntInit--;
filtflg = BIT_SET;
}
//タイマの初期化
while(1){//メインの無限ループ
CLRWDT();
DiFilter(); //DIフィルタ
}
DIフィルタのデータを格納するための構造体を定義して変数定義を行っています。DI値を格納する変数DiDataの初期化を行います。
メイン関数のループの前にDIフィルタの初期化を行っているのはメイン関数に入る前にDI情報を確定するためです。
TMR1を起動する前にDIフィルタを行う場合のタイミングをdelay関数で作っています。10msのウェイトを10回繰り返してwhileから抜けるようにしています。TMR1を起動して10回割り込みによる処理が終わったらループを抜ける作りでもよいと思います。
例えばプログラム開始時は変数は0になっているため1が入力されていた場合に初期化をしておくことで1を確定してからメイン動作に移ることができます。
DIフィルタを実装する
DIフィルタを実装するためにDIピンにスイッチのON/OFFが入力できるようにして動作確認します。
動作確認用の回路図
SW1を押すとGP3の入力に1が入ります。10msの経過の割り込みの4回分の値が1となり一致した時LED2が点灯します。ONからOFFにしたときも同様に10msの経過割り込みが4回分0で一致した時にLED2が消灯します。
DIフィルタを実装例
//割り込み関数
void __interrupt() intr( void){
if( TMR1IE == BIT_SET && TMR1IF == BIT_SET){ //タイマ1の割り込みであるか
TMR1IF = BIT_CLR; //割り込みフラグをクリア
TMR1ON = BIT_CLR;
TMR1H = TIME_START_H; //タイマ初期化
TMR1L = TIME_START_L; //タイマ初期化
TMR1ON = BIT_SET; //タイマ1ON
filtflg = BIT_SET;
}
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){
if( filtflg ){
filtflg = BIT_CLR;
DiData.buf[ DiData.wp ] = GPIO3;
if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
//比較して一致したら値を採用
GPIO2 = DiData.buf[ 0 ];
}
if( ++DiData.wp >= DI_NUM){
DiData.wp = 0;
}
}
}
メイン処理の中にDiFilter()が毎回処理されますが、TMR1がオーバーフローしたときのみDIフィルタの処理をするようにオーバーフローしたときにfiltflgをセットしてDiFilter()の内部処理を行うようにしています。
今回は10msのタイマを作って4回分の値が一致することを確認して一致した場合にGP2の出力として採用するようにしています。
4回分の値が一致してから出力が変更されることから応答時間が最小で約40msとなります。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = ON
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF
#include <xc.h>
//--------------定数定義----------------------------
#define _XTAL_FREQ 4000000
#define TIME_START_H 0xD8 //タイマスタート位置 65536- 55536 = 10000
#define TIME_START_L 0xF0 //タイマスタート位置 65536- 55536 = 10000
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define TIME_START_H 0xD8 //TMR1Hの初期値
#define TIME_START_L 0xF0 //TMR1Lの初期値
#define TIME_BASE_MAX 1 //ベースタイマカウント値
#define CNT_INIT_MAX 10 //10ms×10 = 100ms
#define DI_NUM 4 //DIフィルタのサンプリング数
#define BIT_SET 1
#define BIT_CLR 0
typedef struct{
char wp;
char buf[ DI_NUM ];
}FILT_DATA;
//--------------変数定義----------------------------
unsigned char cnt10ms;
char filtflg; //DIフィルタ起動
char CntInit; //初期化時のみ使用
FILT_DATA DiData;
//-----------------関数定義---------------------------
void DiFilter(void);
void main(void) {
//各種初期化
ADCON0 = 0x00; //ADC使用しない
ANSEL = 0x00; //アナログモードは使用しない
CMCON = 0x07; //コンパレータ使用しない
TRISIO = 0x08; //GP3はDI・その他はDO
GPIO = 0x00; //ポートの設定 1:High 0:Low
OPTION_REG = 0x0F; //プリスケーラ128 WDTで使用
//割り込み設定
INTCON = 0xC0; //割り込み許可とPIE1を許可
PIE1 = 0x01; //タイマ1割り込みを許可
//DIフィルタ初期化
DiData.wp = 0;
filtflg = BIT_CLR;
//DIフィルタ初期起動
CntInit = CNT_INIT_MAX;
while(CntInit > 0){ //0になるまでフィルタを実施
CLRWDT();
DiFilter(); //DIフィルタ処理
__delay_ms(10); //10ms遅延させてDIフィルタ処理
CntInit--;
filtflg = BIT_SET;
}
//タイマ起動
T1CON = 0;
TMR1H = TIME_START_H;//タイマ初期化
TMR1L = TIME_START_L;//タイマ初期化
T1CON = 1;//タイマ1ON
TMR1IF = 0;//割り込みクリア
while(1){//メインの無限ループ
CLRWDT();
DiFilter(); //DIフィルタ
}
}
//--------割り込み関数---------------------------------------
void __interrupt() intr( void){
if( TMR1IE == BIT_SET && TMR1IF == BIT_SET){ //タイマ1の割り込みであるか
TMR1IF = BIT_CLR; //割り込みフラグをクリア
TMR1ON = BIT_CLR;
TMR1H = TIME_START_H; //タイマ初期化
TMR1L = TIME_START_L; //タイマ初期化
TMR1ON = BIT_SET; //タイマ1ON
filtflg = BIT_SET;
}
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){
if( filtflg ){
filtflg = BIT_CLR;
DiData.buf[ DiData.wp ] = GPIO3;
if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
//比較して一致したら値を採用
GPIO2 = DiData.buf[ 0 ];
}
if( ++DiData.wp >= DI_NUM){
DiData.wp = 0;
}
}
}
//---------------------end file----------------------------
関連リンク
PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
最後まで、読んでいただきありがとうございました。