AVRのピン変化割り込みでDCCの受信

今回はATtiny2313でDCCのパケットを受信する手順を公開します。
ついでにDCCのパケットはピン変化割り込みを利用して受信してみます。

タイマー1を時間(パケットのパルス幅)計測に使うためタイマー1を他の事に利用する場合は修正が必要です。

回路図

電源周りのコンデンサ等は省略しています。別途確認用のLEDやシリアルを接続して動作確認してください。
またレールとの接続はフォトカプラで電気的に絶縁することも可能です。

AVRでDCCパケットを受信

AVRでDCCパケットを受信


プログラム

//AVRのピン変化割り込みでDCCパケットの受信
//dcc_poll関数は割り込み専用ではないのでポーリング動作も可能
// Copyright chibiegg 2009 All rights reserved.
// https://www.chibiegg.net/
#include <avr/io.h>
#include <avr/interrupt.h>

//パケットを受信したときにコールされる関数
//割り込みの中でコールされることに注意!
//時間のかかる処理はここでフラグを立ててメインで実行することをおすすめします
void dcc_exec(uint8_t*,uint8_t){

	//ここで受信時の動作を行う

}

//ピン変化割り込み起動時の動作
ISR(PCINT_vect){
uint8_t sreg_tmp = SREG; //多重割り込みできるようにSREGの内容を退避
uint8_t bit1,bit2;
		bit1 = (PINB & 0x01);
		bit2 = 1-((PINB & 0x02)>>1);
		dcc_poll(bit1,bit2); //DCCパケット処理関数のコール
SREG = sreg_tmp; //SREGの内容を戻す
}


//メイン関数でやることは初期化だけ
//DCCパケットの受信については割り込みで全て処理される
//なので、その他の処理は時間がかかる物でもメインで自由にできます
int main(){
	//ピン変化割り込み
	PCMSK = 0x03; //レールに接続された端子のみ割り込み許可
	GIMSK = 0x20; //ピン変化割り込みの許可

	sei();//割り込み許可
	while(1){
		//何もしない
		//その他の処理が自由にできます
	}
}


//これより先DCCパケット受信用関数
//別ファイルにするほうがすっきりするかも
//その場合は
//				extern void dcc_exec(uint8_t*,uint8_t);
//と宣言しておく。

volatile uint16_t _dcc_timer=0;
uint8_t _dcc_portbefore[2]={0,0};
uint8_t _dcc_now=0,_dcc_before=0,_dcc_precount=0,_dcc_bitcount=0,_dcc_bytecount=0;
uint8_t _dcc_packet[12],_dcc_errbyte;

void _dcc_timer_start(){
	//カウンタをクリアし、計測を開始する
	TCCR1B = 0;
	TCNT1 = 0;
	TCCR1B = 0x02;
	//8分周
	//ワンカウント 0.4us@20MHz
}
uint16_t _dcc_timer_stop(){
	//タイマーを停止し計測したカウントを返す
	TCCR1B = 0;
	return TCNT1;
}

void dcc_poll(uint8_t bit1,uint8_t bit2){
	//入力変化検知
	//どちらも同じ変化の場合
	if(bit1!=_dcc_portbefore[0] && bit1==bit2){
		_dcc_portbefore[0] = bit1;
		_dcc_portbefore[1] = bit2;
	}else{
		return; //変化なしあるいは片側だけの変化
	}


	if(!bit1){	//立ち上がりエッジ
		//ビット幅計測開始
		_dcc_timer_start();
	}else{		//立ち下がりエッジ
		//計測終了
		_dcc_timer = _dcc_timer_stop();
		//ワンカウント 0.4us
		if(_dcc_timer>=110&&_dcc_timer<=200){ //ビット1  55us - 61us
			_dcc_now=1;//今処理しているビットの種類「1」(以降の処理で利用するため)
			_dcc_precount++; //プリアンプル数計測用
			//現在受信しているバイトにデータを追加
			_dcc_packet[_dcc_bytecount]=(_dcc_packet[_dcc_bytecount]<<1)+1;
		}else if(_dcc_timer>=225&&_dcc_timer<=25000){ //ビット0 95us - 9900us
			_dcc_now=0;//今処理しているビットの種類「0」(以降の処理で利用するため)
			if(_dcc_precount>=14){ //スタートビット(14回以上の「1」の後の「0」)
				//変数たちを初期化
				_dcc_bitcount=1;
				_dcc_packet[0]=0;
				_dcc_bytecount=0;
				_dcc_errbyte=0;
				_dcc_precount=0;
				return;
			}
			_dcc_precount=0;//プリアンプル数計測リセット
			//現在受信しているバイトにデータを追加
			_dcc_packet[_dcc_bytecount]=(_dcc_packet[_dcc_bytecount]<<1);
		}else{return;} //規格外の長さ

		if(_dcc_bytecount>=10){	return;}//今受信しているパケットを無視する場合
		_dcc_bitcount++;
		if(_dcc_bitcount==1){
			if(_dcc_now==1){//ストップビット
				if(_dcc_bytecount!=10){ //無視する予定のパケットで無い場合
					//ここでエラーバイトの確認し、実際の動作にうつる
					if(_dcc_errbyte==0){
						dcc_exec(_dcc_packet,_dcc_bytecount); //受信完了時の関数のコール
					}
					_dcc_bytecount=10; //エラーバイトが一致しないので今回のパケットは無視
				}
			}
		}
		if(_dcc_bitcount==9){//バイトの終わり
			//今受信が終わったバイトが一バイト目で0xffならアイドルパケットなので
			//今受信しているパケットは無視する(_dcc_bytecountを10にすることにより無視化)
			if(_dcc_bytecount==0&&_dcc_packet[0]==0xff){_dcc_bytecount=10;return;}
			_dcc_errbyte^=_dcc_packet[_dcc_bytecount]; //エラーバイト比較用の計算
			_dcc_bitcount=0;
			_dcc_bytecount++;
		}

	}
}

説明

ピン変化割り込みによってDCCパケット解析(dcc_poll)関数を呼び出します。
この関数では立ち上がりエッジから立ち下がりエッジまでのパルス幅を計測し、立ち下がりエッジで計測が終わるごとに、プリアンプル部の検出、バイトの受信等の処理を行います。
さらに、ストップビットを受信し、エラーバイトとの比較も一致すると、dcc_exec関数をパケットのバイト列へのポインタとパケットのバイト数を引数としてコールします。
実際にDCCパケットについて処理したい内容はこのdcc_exec関数に記述することによって実現できます。
ただし、dcc_exec関数は割り込みの動作中でコールされるので、長い時間のかかる処理などはできません。そのような処理が必要であればこの関数でフラグを立ててメインのスレッドで処理をするべきです。

ちなみに、dcc_poll関数さえ遅滞なくコールできるのであればピン変化割り込みを利用しなくてもポーリング動作させることができます。

この記事はDCC機器の自作に投稿されました タグ: , , , , , , . このパーマリンクをブックマークする。 コメントを投稿するか、トラックバックをどうぞ: トラックバック URL.

3 コメント