シリアル通信に限らず、データをやり取りする場合には、バッファを設けることが必要になる。(場合が多い)
なぜ、バッファが必要なのかというと、
などの問題を解決する手段として、バッファの設置が有効となる。
(バッファとは、ある大きさの連続したメモリのことで、通常は配列を使って実装することが多い。)
シリアルポート(USART)のシフトレジスタやUDR1などのレジスタも、一種のバッファと考えられるが、両方合わせて2byteしかないため、通信データ量が多くなると、取りこぼしなどの問題が発生する。(データが送られてくるより短い間隔でUDR1を参照しないと、データが上書きされて紛失する)
データの紛失を避けるためには、ユーザプログラムで着信データを常にループ(ポーリング)や割り込みなどを使って監視する必要がある。ただし、データが着信する度に処理を行うと、ユーザプログラムが煩雑になって厄介である。
解決策として、着信したデータをバッファに記録する専用のプログラムを作成し、ユーザプログラムはバッファに記録されたデータを参照するように構成すれば、データの取りこぼしが無くなり、ユーザプログラムの負担も軽減する。(と思われる)
printf()やscanf()を使っているイメージとほぼ同じ。 これらの関数は、マイコンのプログラミング環境には用意されていないので、自分で作るしかない。(面倒な仕事は一度、ライブラリを作成して、それを再利用すれば、後で楽ができる。)
リングバッファ
バッファの構成手法には様々なものがあるが、ここではリングバッファを取り上げる。
リングバッファとは、文字通りデータをリング状に記録するバッファのことで、データを無限に連続して記憶することができる。
この時、データは、もっとも古いものから上書きされていくことになる。
シリアル通信のデータの様に、連続して沢山のデータをやり取りする場合に有効な手法と言えるが、当然のことながら、
古いデータは勝手に上書きされていくので、上書きされる前にデータを処理する必要がある。
リングバッファの動作は以下の通り。データを順番に書き込んで行くと、書き込みの開始位置がリング上を周回しながら移動してゆく。
データの読み込み位置についても同様である。
こうすることで、効率良く、データの保存ができる。
リングバッファを実装する方法は、様々だが一般的には配列を用いることが多い。
例えば、下図の様に、一定の大きさ(ここでは16byte)の配列を用意する。
読み出し位置、書き込み位置の添え字を記憶する変数を用意し、それぞれの変数が取りうる範囲を0~0xFとする。0xFを超えた場合は、0に戻す。
以上の処理を行えば、リングバッファを実装することができる。
このような添え字の処理は、剰余演算、もしくはビットマスクを使えば、簡単に実装が出来る。ただし剰余演算はコストが高い(処理時間がかかる)ので、一般的にはビットマスクを用いる方が良い。
int read_pos; // 読み出し位置の添え字を記憶 read_data = data[ read_pos & 0xF ]; // read_pos & 0xF で、添え字の範囲は0~0xFとなる。 |
実装に当たっては、これに加えてバッファ内に残っているデータ数を示す変数を用意すると良い。
以上、リングバッファとイベントループを使ってシリアル通信を行うプログラムのスケルトン(骨格)を、以下に示す。
// シリアルポートから読み取ったデータを、リングバッファに書き込む。(1byte単位で) // イベントループの定義。各種のイベントに対応 |