目次

リングバッファ処理

 通信でデータを受信する場合、リングバッファを
 使います。

 リングバッファを利用すると、取りこぼしがないのと
 データをある程度の時間の履歴を残しておけます。

 コマンドインタプリタでは、1レコードが到着したなら
 即座に反応しなければならない場面が多いので、次の
 ように受信バッファを操作してきました。

char sbuf[16] ;
byte sindex ;

void serialEvent()
{
  char ch;
  if ( Serial.available() > 0 ) {
    /* get 1 character */
    ch = Serial.read();
    /* store */
    *(sbuf+sindex) = ch ;
    /* increment */
    sindex++ ;
    /* judge */
    if ( ch == '\r' ) {
      sindex = 0 ; 
      uflag  = ON ;
    }
  }
}

 受信バッファに1レコードが到着したなら、フラグを利用して
 コマンドインタプリタ側に通知しています。

 イベント通知フラグの値で、コマンドを処理。

  if ( uflag == ON ) {
    /* clear flag */
    uflag = OFF ;
    /* get command */
    cmd = *(sbuf+0) ;
    /* help */
    if ( cmd == '?' ) { show_help(); }
    /* initialilze buffer */
    if ( cmd == 'I' ) { init_rbuf(); }
    /* display */
    if ( cmd == 'D' ) { display_line(OFF); }
    /* shift */
    if ( cmd == 'S' ) { display_line(ON) ; }
    /* write 1 byte */
    if ( cmd == 'W' ) {
      for ( ii = 1 ; ii < 11 ; ii++ ) {
        /* get 1 byte from receive buffer */
        tmp = *(sbuf+ii) ;
        /* judge */
        if ( tmp == '\r' ) break ;
        /* store it to ring buffer */
        store_rbuf( tmp ); 
      }
    }
  }

 リングバッファを扱うために、配列を宣言し
 読出しポインタ、書込みポインタを使っての
 1文字入力と複数文字表示を考えます。

 リングバッファのイメージは、以下。



 上図では、rdp、wdpを扱うときに、次の規則を適用します。

 リングバッファにデータを書き込むときは、wdpが示す位置に
 格納。データを格納後、wdpを+1して、サイズで割った剰余
 をwdpに代入する。
 リングバッファからデータを読み出すときは、rdpが示す位置の
 データを取り出す。データを取出後、rdpを+1して、サイズで
 割った剰余をrdpに代入する。

 言葉での説明は、回りくどい表現になりますが、コードに変換
 してみると簡単。

 リングバッファに1文字書き込む

void store_rbuf(byte x)
{
  *(rbuf+wdp) = x ;
  wdp++ ;
  wdp %= RSIZE ;
}

 リングバッファから1文字取り出す

byte load_rbuf()
{
  byte result ;

  result = *(rbuf+rdp) ;
  rdp++ ;
  rdp %= RSIZE ;

  return result
}


 リングバッファのサイズを20バイトとし
 10バイトだけを表示してみます。

 動作イメージは、以下。



 端末操作で処理したいので、コマンドを用意します。

 各コマンドを定義していきます。

 help

  使えるコマンドのリストを表示すればよいので
  文字列表示関数を使って、端末に文字を送信。

void show_help()
{
  rs_puts("? help")               ; crlf();
  rs_puts("I initialilze buffer") ; crlf();
  rs_puts("D display")            ; crlf();
  rs_puts("S shift")              ; crlf();
  rs_puts("W write 1 byte")       ; crlf();
}

 initialilze buffer

  リングバッファに割り当てる配列に、スペースを格納。

void init_rbuf()
{
  byte i ;
  for ( i = 0 ; i < RSIZE ; i++ ) {
    *(rbuf+i) = ' ' ;
  }
  rdpb = 0 ;
  wdp = 0 ;
}

 display(10 bytes)

  リングバッファから10バイトを取り出して、表示。

void display_line()
{
  byte i ;
  byte j ;
  /* delimiter */
  *(lineU+10) = '\0' ;
  /* copy */
  j = rdpb ;
  for ( i = 0 ; i < 10 ; i++ ) {
    *(lineU+i) = *(rbuf+j) ;
    j++ ;
    j %= RSIZE ;
  }
  /* show */
  rs_puts( lineU );
  crlf();
}

  rdpbは、別途処理すると仮定。

 display(10 bytes) and shift

  リングバッファから10バイトを取り出して、表示するのは
  ひとつ前と同じ。先頭情報の位置が更新されるように工夫。

void display_line_shift()
{
  byte i ;
  byte j ;
  /* delimiter */
  *(lineU+10) = '\0' ;
  /* copy */
  j = rdpb ;
  for ( i = 0 ; i < 10 ; i++ ) {
    *(lineU+i) = *(rbuf+j) ;
    j++ ;
    j %= RSIZE ;
  }
  /* show */
  rs_puts( lineU );
  crlf();
  /* update */
  rdpb++ ;
  rdpb %= RSIZE ;
}

  ひとつ前の関数との差異は、先頭情報の位置が更新されるだけ
  なので、選択可能にします。

void display_line(byte x)
{
  byte i ;
  byte j ;
  /* delimiter */
  *(lineU+10) = '\0' ;
  /* copy */
  j = rdpb ;
  for ( i = 0 ; i < 10 ; i++ ) {
    *(lineU+i) = *(rbuf+j) ;
    j++ ;
    j %= RSIZE ;
  }
  /* show */
  rs_puts( lineU );
  crlf();
  /* update */
  if ( x ) { rdpb++ ; rdpb %= RSIZE ; }
}

 write 1 byte

  1バイトの書き込みは、次の関数で対応。

void store_rbuf(byte x)
{
  *(rbuf+wdp) = x ;
  wdp++ ;
  wdp %= RSIZE ;
}

  1バイト書き込み処理を元にし、コマンドインタプリタでは
  複数バイトの入力ができるようにします。

    /* write 1 byte */
    if ( cmd == 'W' ) {
      for ( ii = 1 ; ii < 11 ; ii++ ) {
        /* get 1 byte from receive buffer */
        tmp = *(sbuf+ii) ;
        /* judge */
        if ( tmp == '\r' ) break ;
        /* store it to ring buffer */
        store_rbuf( tmp ); 
      }
    }
  }

  受信バッファからのデータ転送と複数バイト書き込みを
  1レコードが10バイト以内として処理。

 ここまでで必要な関数、手続きを用意したので、まとめます。

#include <MsTimer2.h>

#define OFF 0
#define ON  OFF+1

/* pin assignment */
#define LED_BIT 5

/* ring buffer size */
#define RSIZE     20

#define XINTERVAL 1500

/* function prototype */
void update_trigger();
void show_help();
void rs_putchar(char x);
void rs_puts(char *ptr);
void crlf();

/* variables */
boolean tflag ;
boolean eflag ;
boolean uflag ;

char cmd ;
char sbuf[16] ;
byte sindex ;

byte xcnt ;

/* ring buffer */
byte rdpb ;
byte wdp ;
byte rbuf[RSIZE];

char lineU[11] ;

void init_rbuf();
void store_rbuf(byte x);

void display_line(byte x);

void setup()
{
  /* initialize serial */
  Serial.begin(9600);
  sindex = 0 ;
  rs_puts("Hello !");
  crlf();
  show_help();
  /* clear flags */
  tflag = OFF ;
  eflag = OFF ;
  uflag = OFF ;
  /* initialize port values */
  PORTB = 0x00 ;
  PORTC = 0x00 ;
  PORTD = 0x00 ;
  /* initialize port direction */
  DDRB = 0xff ;
  DDRC = 0xff ;
  DDRD = 0xfe ;
  /* others */
  xcnt = 0 ;
  init_rbuf();
  /* 1500ms period */
  MsTimer2::set(XINTERVAL,update_trigger);
  /* enable */ 
  MsTimer2::start();
}

void loop()
{
  byte tmp ;
  byte ii ;
  /* command interpreter */
  if ( uflag == ON ) {
    /* clear flag */
    uflag = OFF ;
    /* get command */
    cmd = *(sbuf+0) ;
    /* help */
    if ( cmd == '?' ) { show_help(); }
    /* initialilze buffer */
    if ( cmd == 'I' ) { init_rbuf(); }
    /* display */
    if ( cmd == 'D' ) { display_line(OFF); }
    /* shift */
    if ( cmd == 'S' ) { display_line(ON) ; }
    /* write 1 byte */
    if ( cmd == 'W' ) {
      for ( ii = 1 ; ii < 11 ; ii++ ) {
        /* get 1 byte from receive buffer */
        tmp = *(sbuf+ii) ;
        /* judge */
        if ( tmp == '\r' ) break ;
        /* store it to ring buffer */
        store_rbuf( tmp ); 
      }
    }
  }
  /* timer handling */
  if ( tflag == ON ) {
    /* clear flag */
    tflag = OFF ;
    /* flash LED */
    PORTB &= ~(1 << LED_BIT);
    if ( xcnt & ON ) { PORTB |= (1 << LED_BIT); }
    /* increment */
    xcnt++ ;
  }
}

void update_trigger()
{
  /* set flag */
  tflag = ON ;
}

void show_help()
{
  rs_puts("? help")               ; crlf();
  rs_puts("I initialilze buffer") ; crlf();
  rs_puts("D display")            ; crlf();
  rs_puts("S shift")              ; crlf();
  rs_puts("W write 1 byte")       ; crlf();
}

void rs_putchar(char x)
{
  Serial.write(x);
}

void rs_puts(char *ptr)
{
  while ( *ptr ) {
    rs_putchar( *ptr );
    ptr++;
  }
}

void crlf()
{
  rs_putchar('\r');
  rs_putchar('\n');
}

void display_line(byte x)
{
  byte i ;
  byte j ;
  /* delimiter */
  *(lineU+10) = '\0' ;
  /* copy */
  j = rdpb ;
  for ( i = 0 ; i < 10 ; i++ ) {
    *(lineU+i) = *(rbuf+j) ;
    j++ ;
    j %= RSIZE ;
  }
  /* show */
  rs_puts( lineU );
  crlf();
  /* update */
  if ( x ) { rdpb++ ; rdpb %= RSIZE ; }
}

void init_rbuf()
{
  byte i ;
  for ( i = 0 ; i < RSIZE ; i++ ) {
    *(rbuf+i) = ' ' ;
  }
  rdpb = 0 ;
  wdp = 0 ;
}

void store_rbuf(byte x)
{
  *(rbuf+wdp) = x ;
  wdp++ ;
  wdp %= RSIZE ;
}

/* receive interrupt */
void serialEvent()
{
  char ch;
  if ( Serial.available() > 0 ) {
    /* get 1 character */
    ch = Serial.read();
    /* store */
    *(sbuf+sindex) = ch ;
    /* increment */
    sindex++ ;
    /* judge */
    if ( ch == '\r' ) {
      sindex = 0 ; 
      uflag  = ON ;
    }
  }
}

 Teratermを利用して、スケッチを動かしてみます。

 接続した状態は、以下。



 現在のリングバッファの内容を表示。



 起動時の初期化で、スペースが出力されてます。

 複数バイトを書き込んで、表示。



 表示後、シフトをしていってみます。



 表示コマンドの'D'と'S'には、違いがあるのが
 わかります。

 電光掲示板や電車の中にある情報表示装置では
 リングバッファを利用した左にシフトしていく
 表示が見られます。

 リングバッファを使っていることがわかります。

目次

inserted by FC2 system