目次
前
次
SPI通信スケッチ
2つのArduinoをSPIで接続して、マスターとスレーブで通信
しながら、データ交換する実験をしてみました。
SPIは、マスターとスレーブが個々にもっているシフトレジスタ
を接続し、クロックに同期して「トコロテン」方式でデータ交換
します。
マスターとスレーブの役割は、次のように単純です。
- マスターは、シフトレジスタを動かすクロックを出力
- スレーブは、クロックを利用
- マスターはnSSを使い、データ交換するスレーブを指定
- マスターはnSSを複数利用して、異なるスレーブを指定
- スレーブは、nSSが'L'のときだけ、データ交換に応じる
クロック同期のデータ交換なので、マスターとスレーブの間で
シリアルインターフェースと同様なプロトコルを決めてから
制御します。
シリアルインタフェースの場合、送信、受信にそれぞれ
バッファを持ちますが、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スレーブの中にあるレジスタに
値を設定したり、レジスタ値を参照できるコマンドを用意
します。
端末で、スレーブのレジスタをアクセスするために
用意したコマンドは、以下です。
- T send SPI data
- N send register Number
- G get register value
端末ソフト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種類にしました。
- ? help
- T send SPI data
- N send register Number
- G get register value
- S show SPI buffer
'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'を指定されたなら、シフトレジスタに
指定レジスタの値を転送しておきます。これで
マスターがクロック同期でデータを送信してくる
のと、同時に指定レジスタの値が出ていきます。
端末で利用するコマンドは、以下としました。
- ? help
- X set xregister value
- x show xregister contents
スレーブ側で、自分が持っているレジスタの内容を
参照あるいは設定できるようにしています。
実際の操作は、以下のようになります。
マスター、スレーブ間でレジスタの値設定、参照をすると
次のようになります。
マスター側でレジスタの値を設定するとともに
スレーブ側でもレジスタ値を更新し、マスター
で確認できています。
マスター側にリングバッファを用意し、SPI上のプロトコルを
決めると、スレーブ側のSPI用インタプリタを定義できます。
スレーブ側ではコマンドに呼応して、SPI用シフトレジスタに
データを格納するのがポイント。
目次
前
次