今まで素のAVRも含めてSPIのスレーブとして使ったことが無かったので、直接レジスタを叩いてやってみた。
Arduino UnoをSlaveにする
配線図
写真のオフィシャルのArduino Uno Rev3(緑色)をMaster、aitendoのびんぼうでいーの(黄土色)をSlaveとして使っている。
SPIの信号線の間にブレッドボードを入れているのはオシロで測定しやすくするためで、必要がなければArduinoのPin同士を直結した方がいい。以前mbedでSPIの接続を悪条件にしてテストした時エラーが多発した。(参考「Nucleo(mbed OS5)のSPI通信を検証してみる。」)
動作としては、Masterから1ビットずつシフトした8bit値をSlaveに送り(MOSI)、Slaveは受信したデータを8個のLEDに表示する。LEDは流れるように点灯する。
Slaveは1づつインクリメントした8bit値をMasterに送り(MISO)、Master側は受信したデータをSerialで出力する。←Arduino IDEのシリアルモニタでMasterが受信したデータを表示できる。
※Arduinoを2個使うとArduino IDEで操るのがそこそこややこしいです(^q^;
Masterのスケッチ
SPI_Master_LEDx8.ino
#include <SPI.h> #define SPI_CS_PIN (10) uint8_t cnt = 1; void setup() { Serial.begin(9600); Serial.println("SPI_Master_LEDx8 Test"); pinMode(SPI_CS_PIN, OUTPUT); digitalWrite(SPI_CS_PIN, HIGH); SPI.begin(); } void loop() { digitalWrite(SPI_CS_PIN, LOW); uint8_t rdata = SPI.transfer(1 << cnt); digitalWrite(SPI_CS_PIN, HIGH); cnt++; if (cnt > 8) { cnt = 0; } Serial.println(rdata); delay(100); }
Master側のSPIの設定はデフォルトで、Clock:4MHz、MSB First、Mode:0になる。
Slaveのスケッチ
SPI_Slave_LEDx8
volatile uint8_t sdata = 0; void setup() { // LEDs output DDRD = 0xFF; PORTD = 0xFF; _delay_ms(100); // LEDs check for (int i = 0; i < 8; i++) { PORTD = 1 << i; _delay_ms(100); } PORTD = 0x00; // SPI slave DDRB = 0x10; // MISO(PB4)をoutputに設定 // SPCRのビット設定 // SPI有効:1 | SPI割り込み有効:1 | MSBから送信:0 | スレーブ:0 // クロック極性:0 | クロック位相:0 | SPR1:0 | SPR0:0 SPCR = (1 << SPIE) | (1 << SPE); } ISR (SPI_STC_vect) { // 受信データをLEDに出力 PORTD = SPDR; // 次に送信するデータをセット SPDR = sdata; sdata++; } void loop() { }
Slave側のスケッチは、まぎれが無いように基本的にArduinoの関数は使っていない。
「//LED check」の部分は、起動時に接続しているLEDを順番に全点灯して配線ミスがないか確認できるようにしている。
ATMega328PのハードウェアSPIは
SCK: PB5 (D13)
MISO: PB4 (D12)
MOSI: PB3 (D11)
CS(SS): PB2 (D10)
を使うようになっていて、Slaveの場合MISOだけはPinの機能をOUTPUTに指定する必要があるので、
// SPI slave
DDRB = 0x10; // MISO(PB4)をoutputに設定
としている。
「ISR (SPI_STC_vect){}」は割り込みハンドラで、SPCRレジスタのSPIEビットを1に設定すると、SPI通信が完了したときに呼び出されるようになる。
SlaveからMasterに送信するデータはSPDRレジスタに設定すればよく、「ISR (SPI_STC_vect){}」内で、受信データをSPDRレジスタから読み出した後、SPDRに書き込んでいる。このデータは、次回SPI通信が行われたときにMISOから出力されてMasterに送信される。
SPI通信は割り込みで処理しているので、メインループの「loop()」では何もしていない。
SPI通信の状態
MOSI
ch1:MOSI ch2:SCK
SPIクロックは4MHz
MISO
ch1:MISO ch2:SCK
ATMega88VをSlaveにする
同じことをATMega328Pとピン互換で、安価なATMega88Vでもやってみた。
配線図
配線図ではatemega168となっているが、実際はATMega88Vを使って内蔵RC/8MHzで動作させている。
Fuse Bit
hFuse: DFh
lFuse: A2h (動作確認のためPB0からクロック出力)
eHuze: 01h
Slave(ATMega88V)のソースコード
Atmel Studio7 (Version: 7.0.1417)でビルド
/* * SPI_Slave_LEDx8.c * * Created: 2017/08/26 9:16:53 * Author : gizmo */ #define F_CPU (8000000UL) #include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> volatile uint8_t sdata = 0; ISR (SPI_STC_vect) { // 受信データをLEDに出力 PORTD = SPDR; // 次に送信するデータをセット SPDR = sdata; sdata++; } int main(void) { // LEDs output DDRD = 0xFF; PORTD = 0xFF; _delay_ms(100); // LEDs check for (int i = 0; i < 8; i++) { PORTD = 1 << i; _delay_ms(100); } PORTD = 0x00; // SPI slave DDRB = 0x10; // MISO(PB4)をoutputに設定 // SPCRのビット設定 // SPI有効:1 | SPI割り込み有効:1 | MSBから送信:0 | スレーブ:0 // クロック極性:0 | クロック位相:0 | SPR1:0 | SPR0:0 SPCR = (1 << SPIE) | (1 << SPE); sei(); // 割り込み許可 while (1) { } }
基本的にはArduinoのスケッチと同じだが、ハマってしまった点。
#include <interrupt.h>
これを指定しなくても、Atmel Studio 7ではWarningは出るがBuild出来てしまう。「int ISR (SPI_STC_vect){}」という普通の関数と解釈してコンパイルしてしまうからだろう。プログラムの中で「ISR()」は呼び出されていないのでリンクも出来てしまう。
sei();
Arduinoでは(全体の)割り込みはデフォルトで許可されているが、素のAVRのプログラムでは明示的にsei()を呼び出して、割り込みを有効化しないとダメ。
またSPIクロックも問題があって、Arduino同士で使ったMasterのスケッチではLEDの点灯動作がおかしくなった。
SlaveはATMega88Vを8MHz駆動させていて、SPIクロックの最大周波数はマスタークロックの1/2なので、Masterが送ってくるSPIクロックが4MHzだと処理が追いつかないんだと思う。
SPIクロックを1MHzにしたMasterのスケッチ
#include <SPI.h> #define SPI_CS_PIN (10) uint8_t cnt = 1; void setup() { Serial.begin(9600); Serial.println("SPI_Master_LEDx8 Test"); pinMode(SPI_CS_PIN, OUTPUT); digitalWrite(SPI_CS_PIN, HIGH); SPI.begin(); } void loop() { SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); digitalWrite(SPI_CS_PIN, LOW); uint8_t rdata = SPI.transfer(1 << cnt); digitalWrite(SPI_CS_PIN, HIGH); SPI.endTransaction(); cnt++; if (cnt > 8) { cnt = 0; } Serial.println(rdata); delay(100); }
どこが違うかというと、
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
として、SPIクロックを1MHzにしている点。SPI.beginTransaction()しているので、SPI通信の終了時にSPI.endtransmission()している。
SPIの通信状態
MOSI
ch1:MOSI ch2:SCK
SPIクロックは1MHz
MISO
ch1:MISO ch2:SCK
Github:
https://github.com/ryood/Arduino_SPI_Slave
0 件のコメント:
コメントを投稿