目次

SPI通信スケッチ

 2つのArduinoをSPIで接続して、マスターとスレーブで通信
 しながら、データ交換する実験をしてみました。



 SPIは、マスターとスレーブが個々にもっているシフトレジスタ
 を接続し、クロックに同期して「トコロテン」方式でデータ交換
 します。



 マスターとスレーブの役割は、次のように単純です。

 クロック同期のデータ交換なので、マスターとスレーブの間で
 シリアルインターフェースと同様なプロトコルを決めてから
 制御します。

 シリアルインタフェースの場合、送信、受信にそれぞれ
 バッファを持ちますが、SPIは送受信で利用するバッファ
 は、1バイト分だけです。

 送受信バッファが1バイトだけなので、マスターは次のように
 クロック同期で、1バイトデータを出力後、入力した1バイト
 を取出して、バッファに保存します。

   /* enable nSS */
   PORTB &= ~(1 << nSS_BIT) ;
   /* transfer */
   SPI.transfer( *(msg+i) );
   /* disable nSS */ 
   PORTB |= (1 << nSS_BIT) ;
   /* get 1 byte from buffer */
   tmp = SPSR ;
   /* store receive buffer */
   put_ring( SPDR );

 バッファは、リングバッファを採用します。

 リングバッファは、2のべき乗分の配列を用意し
 リード、ライトのポインタと保存しているデータ
 数を扱う変数を定義して利用します。



 リングバッファを実現するため、マスター側に
 アクセス関数を用意します。

volatile byte ssbuf[16] ;
volatile byte swrptr;
volatile byte srdptr;
volatile byte scap ;

void  put_ring(byte x)
{
  /* store */
  *(ssbuf+swrptr) = x ;
  /* update pointer */
  swrptr++ ;
  if ( swrptr == 16 ) { swrptr = 0 ; } 
  /* update capacity */
  scap++ ;
}

byte get_ring()
{
  byte result ;
  /* get */
  result = *(ssbuf+srdptr) ;
  /* update pointer */
  srdptr++ ;
  if ( srdptr == 16 ) { srdptr = 0 ; } 
  /* decrement */
  scap-- ;

  return result ;
}

byte num_ring()
{
  return scap ;
}

 リングバッファからデータを取出すとき、関数get_ringを
 使います。データをリングバッファへ格納する場合には
 関数put_ringを使います。

 リングバッファに入っているデータ数を知りたいときには
 関数num_ringを利用します。

 データ交換するために、SPIスレーブの中にあるレジスタに
 値を設定したり、レジスタ値を参照できるコマンドを用意
 します。



 端末で、スレーブのレジスタをアクセスするために
 用意したコマンドは、以下です。

 端末ソフトTeraTermで操作すると、次のように
 なります。



 上から順番に、操作内容を見ていきます。

 'T'コマンドで、レジスタ0に'A'を設定
 'N'コマンドで、レジスタ0を指定
 'G'コマンドで、レジスタ0の内容を表示
 'T'コマンドで、レジスタ1に'B'を設定
 'N'コマンドで、レジスタ1を指定
 'G'コマンドで、レジスタ1の内容を表示
 'N'コマンドで、レジスタ2を指定
 'G'コマンドで、レジスタ2の内容を表示
 'N'コマンドで、レジスタ3を指定
 'G'コマンドで、レジスタ3の内容を表示


 端末ソフトでPCからシリアルインタフェースで
 コマンドを与え、SPIでスレーブに対して指示
 しています。

 マスターのスケッチは、以下としました。

#include <SPI.h>

#define OFF 0
#define ON  OFF+1

#define nSS_BIT 2

#define MASK0F 0x0f

volatile byte sindex ;
volatile char sbuf[8] ;
volatile byte uflag;
volatile byte sflag;
volatile byte ssbuf[16] ;
volatile byte swrptr;
volatile byte srdptr;
volatile byte scap ;

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 show_help()
{
  rs_puts("? help");                 crlf();
  rs_puts("T send SPI data");        crlf();
  rs_puts("N send register Number"); crlf();
  rs_puts("G get register value");   crlf();
  rs_puts("S show SPI buffer");      crlf();
}

void  put_ring(byte x)
{
  /* store */
  *(ssbuf+swrptr) = x ;
  /* update pointer */
  swrptr++ ;
  if ( swrptr == 16 ) { swrptr = 0 ; } 
  /* update capacity */
  scap++ ;
}

byte get_ring()
{
  byte result ;
  /* get */
  result = *(ssbuf+srdptr) ;
  /* update pointer */
  srdptr++ ;
  if ( srdptr == 16 ) { srdptr = 0 ; } 
  /* decrement */
  scap-- ;

  return result ;
}

byte num_ring()
{
  return scap ;
}

char get_asc(byte x)
{
  char result ;
  /* default */
  result = '0' ;
  /* convert */
  if ( x > 9 ) { result = x - 10 + 'A' ; }
  else         { result = x + '0' ; }

  return result ;
}

/* 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 ;
    }
  }
}

void setup(void)
{
  /* initialilze serial port */
  Serial.begin(9600);
  sindex = 0 ;
  /* set I/O values */
  PORTB = 0x00 ;
  PORTC = 0x00 ;
  PORTD = 0x01 ;
  /* set port directions */
  DDRB = 0x2f ;
  DDRC = 0xff ; 
  DDRD = 0xfe ;
  /* initialize SPI mode */
  {
    SPI.begin();
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    SPI.setClockDivider(SPI_CLOCK_DIV8);
  }
  /* clear flags */
  uflag = OFF ;
  sflag = OFF ;
  /* initialize ring buffer */
  swrptr = 0 ;
  srdptr = 0 ;
  scap = 0 ;
}

void loop(void)
{
  char cmd ;
  char tmp ;
  byte i ;
  char msg[8] ;
  /* UART handling */
  if ( uflag == ON ) {
    /* clear flag */
    uflag = OFF ;
    /* new line */
    crlf() ;
    /* command */
    cmd = *(sbuf+0) ;
    /* help */
    if ( cmd == '?' ) { show_help() ; }
    /* show SPI buffer */
    if ( cmd == 'S' ) {
      for ( i = 0 ; i < 16 ; i++ ) {
        /* get data */
        tmp = *(ssbuf+i) ;
        /* show */
        rs_putchar( get_asc( (tmp >> 4) & MASK0F ) );
        rs_putchar( get_asc( tmp & MASK0F ) );
        rs_putchar( ' ' ) ;
        /* new line */
        if ( (i % 8) == 7 ) { crlf() ; }
      }
    }
    /* send data to SPI */
    if ( cmd == 'T' || cmd == 'N' || cmd == 'G' ) {
      /* copy */
      for ( i = 0 ; i < 8 ; i++ ) { *(msg+i) = *(sbuf+i) ; }
      /* get data */
      for ( i = 0 ; i < 8 ; i++ ) {
        /* enable nSS */
        PORTB &= ~(1 << nSS_BIT) ;
        /* transfer */
        SPI.transfer( *(msg+i) );
        /* disable nSS */ 
        PORTB |= (1 << nSS_BIT) ;
        /* get 1 byte from buffer */
        tmp = SPSR ;
        /* store receive buffer */
        put_ring( SPDR );
        /* delay 1ms */
        delay(1);
        /* exit */
        if ( *(sbuf+i) == '\r' ) break ;
      }
      /* Get command */
      if ( cmd == 'G' ) { sflag = ON ; }
    }
  }
  /* SPI handling */
  if ( sflag == ON ) {
    /* clear flag */
    sflag = OFF ;
    /* judge */
    if ( *(sbuf+0) == 'G' ) {
      /* get counter */
      tmp = num_ring() ;
      /* transfer */
      for ( i = 0 ; i < tmp ; i++ ) { *(msg+i) = get_ring() ; }
      rs_putchar( *(msg+3) );
      /* new line */
      crlf();
    }
  }
}

 シリアルインタフェースのコマンドは、次の5種類にしました。

 'S'コマンドで、SPIで送受信したコマンド、パラメータを
 確認できます。



 4Eは、ASCIIで'N'
 30は、ASCIIで'0'
 40は、ASCIIで'@'
 47は、ASCIIで'G'
 35は、ASCIIで'5'

 のように見ていけば、SPIでのコマンドとパラメータの
 やりとりがわかります。

 SPIでは、最初に与えたコマンドに対しての返値はゴミ
 になることに注意しないと、?となります。

 スレーブ側のスケッチは、以下。

#include <SPI.h>

#define OFF 0
#define ON  OFF+1

volatile byte xreg[8];
volatile byte yreg;
volatile byte sflag;
volatile char ssbuf[8];
volatile byte ssindex;
volatile byte uflag;
volatile char sbuf[8];
volatile byte sindex;

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');
}

byte  get_hex(char x)
{
  byte result ;
  /* default */
  result = 0 ;
  /* judge */
  if ( '0' <= x && x <= '9' ) { result = x - '0' ; }
  if ( 'A' <= x && x <= 'F' ) { result = x - 'A' + 10 ; }
  if ( 'a' <= x && x <= 'f' ) { result = x - 'a' + 10 ; }
  
  return result ;
}

void  show_help()
{
  rs_puts("? help");                    crlf();
  rs_puts("X set xregister value");     crlf();
  rs_puts("x show xregister contents"); crlf();
}

/* SPI interrupt routine */
ISR(SPI_STC_vect)
{
  byte xspi;
  /* get 1 byte from SPI Data Register */
  xspi = SPDR ;
  /* store */
  *(ssbuf+ssindex) = xspi ;
  /* pointer increment */
  ssindex++ ;
  /* set trigger flag */
  if ( xspi == '\r' ) {
    ssindex = 0 ;
    sflag = ON ;
  }
}

/* Serial Receive interrupt routine */
void serialEvent() 
{
  char ch;

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

void  setup (void)
{
  /* initialilze serial port */
  Serial.begin(9600);
  sindex = 0 ;
  rs_puts("Hello!");
  crlf();
  /* set I/O values */
  PORTD = 0x01 ;
  PORTB = 0xef ;
  PORTC = 0x00 ;
  /* set port directions */
  DDRD = 0xfe ;
  DDRB = 0x10 ;
  DDRC = 0xff ; 
  /* initialize SPI mode */
  {
    /* enable SPI */
    SPCR |= (1 << SPE);
    /* select slave mode (clear this bit) */
    SPCR &= ~(1 << MSTR);
    /* mode */
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    /* enable SPI interrupt */
    SPI.attachInterrupt();
    /* clear pointer */
    ssindex = 0 ;
  }
  /* clear flags */
  sflag = OFF ;
  uflag = OFF ;
  /* others */
  for ( byte i = 0 ; i < 8 ; i++ ) { *(xreg+i) = '@' ; }
}

void loop (void)
{
  byte i ;
  byte tmp ;
  char msg[9] ;
  char cmd ;
  /* UART flag handling */
  if ( uflag == ON ) {
    /* clear flag */
    uflag = OFF ;
    /* new line */
    crlf();
    /* get command */
    cmd = *(sbuf+0) ;
    /* help */
    if ( cmd == '?' ) { show_help() ; }
    /* set value */
    if ( cmd == 'X' ) {
      /* get register number */
      i = get_hex( *(sbuf+1) ) ;
      /* copy */
      *(xreg+i) = *(sbuf+2) ;
    }
    /* show */
    if ( cmd == 'x' ) {
      /* transfer */
      *(msg+8) = '\0' ;
      for ( i = 0 ; i < 8 ; i++ ) { *(msg+i) = *(xreg+i) ; }
      /* send */
      rs_puts( msg );
      /* new line */
      crlf(); 
    }
  }
  /* SPI flag handling */
  if ( sflag == ON ) {
    /* clear flag */
    sflag = OFF ;
    /* copy */
    for ( i = 0 ; i < 8 ; i++ ) {
      /* transfer */
      *(msg+i) = *(ssbuf+i) ;
      /* set delimiter */
      if ( *(msg+i) == '\r' ) { *(msg+i) = '\0' ; }
      /* exit */
      if ( *(ssbuf+i) == '\r' ) break ;
    } 
    /* set value */
    if ( *(ssbuf+0) == 'T' ) {
      tmp = get_hex( *(ssbuf+1) ) ;
      *(xreg+tmp) = *(ssbuf+2) ;
    }
    /* set number */
    if ( *(ssbuf+0) == 'N' ) {
      /* get pointer */
      tmp = get_hex( *(ssbuf+1) ) ;
      /* get data */
      yreg = *(xreg+tmp) ;
      /* store */
      SPDR = yreg ;
    }
    /* get value */
    if ( *(ssbuf+0) == 'G' ) { 
      SPDR = yreg ; 
    }
  }
}

 シリアルインタフェースとSPIの2つのコマンド
 インタプリタを用意しています。

 SPIで転送されてくるデータを受信バッファに保存し
 レコードの終わりを示す'\r'を受信した時点で
 専用インタプリタを動かしています。

 SPIでは、送受信にシフトレジスタを使っています。
 シフトレジスタにコマンドで指定されたデータを
 入れておけば、マスターがそのデータを受取れる
 ようになります。

 コマンド'N'を指定されたなら、シフトレジスタに
 指定レジスタの値を転送しておきます。これで
 マスターがクロック同期でデータを送信してくる
 のと、同時に指定レジスタの値が出ていきます。

 端末で利用するコマンドは、以下としました。

 スレーブ側で、自分が持っているレジスタの内容を
 参照あるいは設定できるようにしています。

 実際の操作は、以下のようになります。



 マスター、スレーブ間でレジスタの値設定、参照をすると
 次のようになります。



 マスター側でレジスタの値を設定するとともに
 スレーブ側でもレジスタ値を更新し、マスター
 で確認できています。


 マスター側にリングバッファを用意し、SPI上のプロトコルを
 決めると、スレーブ側のSPI用インタプリタを定義できます。
 スレーブ側ではコマンドに呼応して、SPI用シフトレジスタに
 データを格納するのがポイント。


目次

inserted by FC2 system