、シリアル通信などデバイス間の通信でデータの送受信を行う場合、バッファにデータを蓄積をしておき、データを取得するタイミングでバッファから取り出します。
このバッファに格納できるデータ量は決まっており、通信データ量が多くなるとバッファに格納できるデータ量を超えてしまいます。このため、データの取りこぼし等による不具合が発生する可能性があります。
この不具合でデータが紛失してしまうことを避けるために、送受信のステータスをポーリングしてデータを監視したり、リングバッファを使ってデータを蓄積したりする対応方法があります。
ポーリングは以前サンプルを使って学習したのである程度理解できましたが、リングバッファはどうやったら実装できるか調べたので、備忘録として記事にまとめてみました。
今回はリングバッファを使ってUART通信する方法について紹介をしますので、参考にしてみてください。
リングバッファについて
リングバッファはバッファの構造の一種で、先頭と末尾が連結され、データをリング状に蓄えられるようにしたものです。
リング状のバッファにすることで通常の配列とは違って、先頭・終端を任意の位置にすることができるため、使用済みの領域を何度も再利用できます。
リングバッファの制御にはリングバッファからデータを読み出すためのポインタ(g_rx_rdpt)と格納されているデータ数(g_rx_dtno)の2つの変数を使用します。この2つの変数の初期状態は0です。
リングバッファに 0x10 / 0x11 / 0x12 の3バイトが格納された場合、格納されているデータ数(g_rx_dtno)が3になります。
格納されているbuffer[0]=0x10を取り出します。格納されているデータ数(g_rx_dtno)が2になり、データを読み出すポインタは1つ移動するので1になります。
リングバッファに 0x13 / 0x14 の2バイトを格納します。格納されているデータ数(g_rx_dtno)が4になります。
このようにデータを格納したり読み出したりする度に、読み出しポインタ(g_rx_rdpt)と格納データ数(g_rx_dtno)の2種類のデータを動かして、リングバッファを制御しています。
ソースコードについて
16バイトのUART通信受信用のリングバッファを実装します。(参考)
コード生成したコードに追加で記述
まずはプロジェクトを作成してコード生成をします。コード生成の方法についてはここから確認できます。
次にプロジェクト・ツリーのコード生成タブを展開して、r_cg_serial_user.cを開きます。
/* Start user code for global. Do not edit comment generated here */
extern volatile uint8_t g_rx_buff[16]; // 受信用リングバッファ
extern volatile uint8_t g_rx_rdpt; // データの読み出しポインタ
extern volatile uint8_t g_rx_dtno; // 格納されているデータ数
extern volatile uint8_t g_rx_status; // 受信ステータスフラグ
/* End user code. Do not edit comment generated here */
自動生成されているr_uart0_callback_softwareoverrunという関数を探します。この関数は、ソフトウェアによるデータのオーバーフローを検出した場合に実行されます。
static void r_uart0_callback_softwareoverrun(uint16_t rx_data)
{
/* Start user code. Do not edit comment generated here */
uint8_t setptr; // 設定用ポインタ
if ( g_rx_dtno < 16 )
{
// バッファに空きがある場合
setptr = ((g_rx_rdpt + g_rx_dtno) & 0x0F); // 書き込みポインタ
g_rx_buff[setptr] = rx_data; // 受信データをバッファに格納
g_rx_dtno++; // 格納データ数をインクリメント
}
else
{
// バッファがフルの場合
g_rx_status = 0x80; // オーバフロー・フラグをセット
}
/* End user code. Do not edit comment generated here */
}
16バイトまで格納できるバッファなので、16以上のデータが既に格納されている場合はオーバーフロー・フラグをセットします。
バッファに空き(16バイト未満)があれば、リングバッファに受信データを格納します。例えば、読み出しポインタ(g_rx_rdpt)が1と格納データ数(g_rx_dtno)が4だったとすると、受信データを格納先はg_rx_rdptとg_rx_dtnoを合計した5番目になります。データを格納したらg_rx_dtnoをインクリメントして、格納数を更新します。
g_rx_rdptとg_rx_dtnoを合計して16なら0番目が格納先になるので先頭に戻ります。この仕組みによって、バッファを再利用することができます。
リングバッファの制御処理を記述
次にプロジェクト・ツリーのファイルタブを右クリックして、UART0.cという名前で新規ファイルを作成します。
必要なヘッダのインクルード
#include "r_cg_macrodriver.h"
#include "UART0.h"
#include "r_cg_userdefine.h"
//グローバル変数定義
volatile uint8_t g_rx_buff[16]; // 受信用リングバッファ
volatile uint8_t g_rx_rdpt; // データの読み出しポインタ
volatile uint8_t g_rx_dtno; // 格納されているデータ数
volatile uint8_t g_rx_status; // 受信ステータスフラグ
リングバッファからデータを1つだけ取り出しする関数を作成します。関数内の最初でリングバッファにデータが格納されているかチェックします。
void get_data(uint8_t * const buff)
{
uint8_t work;
uint8_t * gp_buff; // 転送用ポインタ
gp_buff = buff; // 転送先ポインタを設定
if ( 0 == g_rx_dtno )
{
*gp_buff = 0x00; // データがなければ,0x00
}
else
{
*gp_buff = g_rx_buff[g_rx_rdpt]; // リングバッファから転送
SRMK0 = 1; // INTSR0との排他制御
g_rx_rdpt++; // 読み出しポインタを更新
g_rx_rdpt &= 0x0F;
g_rx_dtno--; // データ数を-1
SRMK0 = 0; // INTSR0との排他制御終了
}
return;
}
データを取り出した後は、データの格納数をデクリメントし、リードポインタをインクリメントしてリングバッファを更新します。この時、16以上になるようであれば、リードポインタを0にします。
次にリングバッファの状態を確認する関数を作成します。
受信ステータスとデータ数を読み出して、変数に代入します。ビット7でオーバーフローフラグ、ビット0〜4でデータ数をチェックすることができます。
uint8_t chk_status(void)
{
uint8_t work;
SRMK0 = 1; // INTSR0との排他制御
work = g_rx_status; // ステータス読み出し
g_rx_status = 0x00; // ステータスをクリア
work |= g_rx_dtno; // データ数読み出し
SRMK0 = 0; // 排他制御完了
return (work);
}
void init_bf(void)
{
SRMK0 = 1; // INTSR0との排他制御
g_rx_rdpt = 0x00; // 読み出しポインタ初期化
g_rx_dtno = 0x00; // データ数をクリア
g_rx_status = 0x00; // ステータスをクリア
SRMK0 = 0; // 排他制御完了
}
void init_bf(void); // リングバッファの初期化
uint8_t chk_status(void); // リングバッファの状態確認
void get_data(uint8_t * const buff); // リングバッファから1データ読み出し
メイン処理を記述
リングバッファに格納されているデータを取り出して、そのデータをそのまま送信する処理にします。
void main(void)
{
R_MAIN_UserInit();
/* Start user code. Do not edit comment generated here */
{
while(1U)
{
uint8_t buff[16]; // 受信データ
while ( 0x00 == chk_status() ); //受信するまでループ
NOP();
get_data((uint8_t *)buff); //リングバッファから1データ読み出し
R_UART0_Send((uint8_t *)buff, 1); // データ送信処理開始
}
}
/* End user code. Do not edit comment generated here */
}
chk_status関数を用いて、リングバッファへのデータ受信待ちをします。while文でループさせて戻り値が0以外になればループから抜けます。リングバッファにデータが格納されれば、単純に1バイトのデータを取り出します。あとはデータをR_UART0_Sendで送信するだけです。
さて、デバックしてみましょう。
うまく動作していますね。TeraTermに入力した文字がそのまま戻ってくればOKです。(TeraTerm上には同じ文字が2文字表示されます。)