送受信とバッファ

シリアル通信に限らず、データをやり取りする場合には、バッファを設けることが必要になる。(場合が多い)
なぜ、バッファが必要なのかというと、

  • 通信を行う相手との処理速度の差
  • 相手から送られてくるデータを常に監視すると、他の処理に支障が出る

などの問題を解決する手段として、バッファの設置が有効となる。
(バッファとは、ある大きさの連続したメモリのことで、通常は配列を使って実装することが多い。)

 シリアルポート(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単位で)
// この関数はイベントループ(もしくは割り込み)から呼び出される。
void getch()  {
     // シリアルポートへのアクセス等 (シリアルポート(UDR1)からデータの読み取り)
     // リングバッファへのアクセス等 (シリアルポートから読み取ったデータを、受信リングバッファに書き込み)
}

// リングバッファから読み取ったデータを、シリアルポートに書き込む。(1byte単位で)
// この関数はイベントループ(もしくは割り込み)から呼び出される。
void putch()
{
     // リングバッファへのアクセス等 (送信リングバッファからデータを読み取り)
    // シリアルポートへのアクセス等 (送信リングバッファから読み取ったデータをシリアルポート(UDR1)に書き込み)
}

// イベントループの定義。各種のイベントに対応
void EventLoop()
{
    if (シリアルポートに受信データあり?) {
        getch();
    }
    if (シリアルポートが送信可能?) {
        putch();
    }

   ........
}

// 受信リングバッファから文字列を読み取る
// 標準ライブラリ関数のgets()と同じ動作。ただし、gets()は定義済みなので、名前を変える
void get_s(char *str)
{
    // 受信リングバッファからデータを読み取る
    // 改行文字を受け取るまでreturnしない。
    //         ※ループ内でEventLoop()の呼び出しを忘れると、データ受信ができないことに注意!
}

//  送信リングバッファに文字列を書き込む
// 標準ライブラリ関数のputs()と同じ動作。ただし、puts()は定義済みなので、名前を変える
void put_s(char *str)
{
    // 送信リングバッファに文字列を書き込む
   // 送信バッファがあふれそうな場合は、バッファへの書き込みを待って、EventLoop()を呼び出し、バッファが溢れないように!
}

int main()
{
    // 初期化処理等
   .....

    while (1) {
         // 必要な処理等
        put_s("abcdefg");
        get_s(&buf);
        .......
        EventLoop();
    }
}

 

最終更新:2016年02月22日 15:49