質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.32%

Arduino 2台によるSPI通信による複数バイト通信の方法を教えてください。

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,196

_kimura_

score 13

SPI接続による複数バイト相互通信

Arduinoを2台利用してSPI接続による複数バイトの相互通信を試みています。
マスター側のスケッチでは

SPI.transfer(buffer, size)

を利用することで複数バイト送信ができるという事が分かったのですが、スレイブ側のデータを受信するため、

uint16_t MasterReceived = SPI.transfer(buffer, size);

というスケッチを書くと

"void value not ignored as it ought to be"

というエラーメッセージが出てしまいます。
また、スレイブ側でもMOSIの受信は1バイト単位での読み取りを行うスケッチを作成することができたのですが、複数バイトでの送受信ができません。

私が作成した以下のスケッチはこちらのリンクを参考にしました。
Arduino UnoでSPI通信(その1)Arduino Uno2台で通信

/*Pro miniがマスター
 * 
 */

#include <SPI.h>
int i,j,MasterReceive;
byte arr[2] = { i, j};

void setup(){
  Serial.begin(9600);
  SPI.setBitOrder( MSBFIRST ); 
  SPI.setDataMode( SPI_MODE3 ); 
  SPI.begin();
}

void loop() {
  digitalWrite( SS, LOW ); //対象の電子部品を接続したSS
  //MasterReceive = SPI.transfer(arr,2); //コンパイルエラー発生 "void value not ignored as it ought to be"
  SPI.transfer(arr,2);
  digitalWrite( SS, HIGH ); //通信終了
  Serial.print("MasterReceive = ");
  Serial.println(MasterReceive);
  i++;
  if ( i > 255) i = 0;
  j--;
  if ( j < 0 ) j = 0;
}
/*
 * SPIスレーブ
 * SS   - Pin10
 * MOSI - Pin11
 * MISO - Pin12
 * SCK  - Pin13
 */

#include <SPI.h>

static const int spiClk = 1000000; // クロック 1 MHz
uint16_t i,j;
volatile byte Slavereceived,Slavesend;


void setup() {
  Serial.begin(9600);
  pinMode(MISO,OUTPUT); //MISOを出力
  SPI.setBitOrder(MSBFIRST);  //最上位ビット(MSB)から送信
  SPI.setClockDivider(SPI_CLOCK_DIV16);  //通信速度をデフォルト
  SPI.setDataMode(SPI_MODE3);   //アイドル5Vで0V→5Vの変化で送信する
  SPCR |= _BV(SPE);
  SPI.attachInterrupt(); 
}

// SPI割り込み処理
ISR(SPI_STC_vect)
{
  Slavereceived = SPDR;
  SPDR = Slavesend;
}

void loop() {
  Serial.print("Slavereceived = ");
  Serial.print(Slavereceived);
  Serial.print(",");
  Serial.print("Slavesend = ");
  Serial.println(Slavesend);
  Slavesend = i++;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

まず。Arduino言語はC++をベースとしています。さすがに、「なにもわかりません」でプログラムが成立するほど甘くはありません。C++、せめてCの入門書を手元において、出来れば一通り目を通してからプログラムに挑んで下さい。

根本的に大事なこと(これは知らなきゃ話にならない)

C++そしてArduino言語では(今後はArduino言語に代表させますが)、基本的に「上から順に」プログラムを実行していきます。なので、
byte arr[2] = { i, j};
としたときには、その時点での i,jの'値'がarrの要素に設定されて初期化が行われますが、その後iとかjが変化してもarrに既に設定されている値には何の影響も及ぼしません。

int i,j; //グローバル変数なのでi,jは0に初期化される
byte arr[2] = { i, j};//このときのi,jは0なので、arrは{0,0}に初期化される
void setup(){
  Serial.begin(9600);
}
void loop(){
  i=100;//iが100になろうと
  Serial.println(arr[0]);//arr[0]は0のまま
  j=10000; //jが10000になっても
  Serial.println(arr[1]);//arr[1]も0のまま
}


とでもしてシリアルモニタで値を確認してみて下さい。


というレベルがあやふやだと、あとの話はどうしようかなぁ...ずぶずぶと深みにハマっていくのですが。
まぁ、お付き合いいただきましょうか。質問があればどうぞ。

実は、そのプログラムでも双方向通信(のようなこと)は出来ています。ただし、タイミング的に無理があって、自在にデータをやり取り出来る状態ではない、のです。
SPI通信は、ざっくり

  • 送り側のSPDRに値をセットする
  • クロックに同期してデータを送信する
  • 送り側のSPDRと受け側のSPDRのデータが交換される(送り側のSPDRの内容が受け側のSPDRに入る/受け側のSPDRに入っていた内容が送り側のSPDRに入る)

ということが行われると思って下さい。void transfer(void *buf, size_t count)関数では、送信後のSPDRの内容を受け取ったbuf領域に書き込んでいます。なので、双方向通信は実は出来ている...のですがしかし。もう少し細かく考えると

  • 送り側のSPDRに値をセットする
  • クロックに同期してデータを送信する
  • 1バイト分のデータが揃ったら...
    受信側ではSPDRからデータを取得し、返信のデータをSPDRに設定する
    送信側は、次のデータをSPDRに設定する
  • 送信側でSPDRにデータが設定されると、次のデータ送信が始まる

送信側の方がちょっと手数が少ないですよね。そうすると、受信側で「返信のデータをSPDRに設定する」作業が間に合わない可能性が出てきます。というか、transfer()で送り側がどんどんデータを送りつけると全く間に合いません。返信のデータが間に合わないということは、受信した時点では受け取ったデータがSPDRに入っていますから、そのデータがそのまま次の返信に使用される(つまり送られたデータをそのまま送り返す)ことになります。実際にはこうなっちゃうわけですね。

  • 1バイト分のデータが揃ったら受信側ではSPDRからデータを取得する
  • 送信側は、次のデータをSPDRに設定する
  • 送信側でSPDRにデータが設定されると、次のデータ送信が始まる。受信側は、前回受け取ったSPDRを持って通信開始する
  • 受信側では返信のデータをSPDRに設定する
  • 1バイト分のデータが揃ったら受信側ではSPDRからデータを取得する (受信側で設定したSPDRデータは上書きされちゃって無くなってる)

つまり、双方向で任意の複数データをやりとりしたいと思ったら、送信側は1byte分転送する度にいくらかの時間(別にdelayでミリ秒を確保する必要はありませんが)を設けて、受信側が返信データをSPDRに設定する余裕を作らなければいけない、ということです。


"void value not ignored as it ought to be"について...

C++では、関数名が同じでも引数が違うと別の関数とみなされます。
さて、spi.hを覗いてみると、
  inline static uint8_t transfer(uint8_t data) {
とあるので、SPIClass::transfer()に1つの整数値を与えたときは返り値がありますが、
  inline static void transfer(void *buf, size_t count) {
は返り値がありませんからuint16_t MasterReceived = SPI.transfer(buffer, size);はエラーになります。

(void *buf, size_t count)の形式の引数であれば、bufには配列(ポインタ)を与え、countには配列のサイズを与えてポインタを介して配列の中身を読み書きする、というのが定番です。


とりあえず、以下で私の手元では(Arduino UNO - nano間)で双方向らしい動きは確認できました。

/*マスター*/

#include <SPI.h>
int i, j;
byte arr[2] = {};

void setup() {
  Serial.begin(9600);
  SPI.setBitOrder( MSBFIRST );
  SPI.setDataMode( SPI_MODE3 );
  SPI.begin();
}

void loop() {
  digitalWrite( SS, LOW ); //対象の電子部品を接続したSS
  arr[0] = i;
  arr[1] = j;
  Serial.print("MasterSend = ");
  Serial.print(arr[0]);
  Serial.print(',');
  Serial.println(arr[1]);
  for ( int idx = 0; idx < 2; idx++) {
    arr[idx]=SPI.transfer(arr[idx]);
    delayMicroseconds(50);
  }
  digitalWrite( SS, HIGH ); //通信終了
  Serial.print("MasterReceive = ");
  Serial.print(arr[0]);
  Serial.print(',');
  Serial.println(arr[1]);
  i++;
  if ( i > 255) i = 0;
  j--;
  if ( j < 0 ) j = 255;
}
/*
 * SPIスレーブ
 * SS   - Pin10
 * MOSI - Pin11
 * MISO - Pin12
 * SCK  - Pin13
 */

#include <SPI.h>

uint16_t i,j;
volatile byte Slavereceived[2],Slavesend;


void setup() {
  Serial.begin(9600);
  pinMode(MISO,OUTPUT); //MISOを出力
  SPI.setBitOrder(MSBFIRST);  //最上位ビット(MSB)から送信
  SPI.setDataMode(SPI_MODE3);   //アイドル5Vで0V→5Vの変化で送信する
  SPCR |= _BV(SPE);
  SPI.attachInterrupt(); 
}

// SPI割り込み処理
ISR(SPI_STC_vect)
{
  Slavereceived[j] = SPDR;
  j=(j+1)%2;
  SPDR = Slavesend;
  Slavesend = i++;
}

void loop() {
//表示は遅いので飛び飛びになることも
  Serial.print("Slavereceived = ");
  Serial.print(Slavereceived[0]);
  Serial.print('/');
  Serial.print(Slavereceived[1]);
  Serial.print(", ");
  Serial.print("Slavesend = ");
  Serial.println(Slavesend);
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

-3

できれば、SPIでの双方向通信はやめておいたほうがいいです
シリアル通信で行うようにしましょう

SPIの双方向通信は、Arduinoの標準関数だけでは実装できないので、SPIデバイスを直接イジる必要があります。
そのうえ、SPIの受信は、送信側と完全に同期しないとダメなので、オシロスコープなどで信号をモニタしながら実装していかないと難しいです

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/23 14:53

    今までI2CとSPIしか通信方法を知っておりませんでした。
    シリアル通信で試してみようと思います。
    アドバイスいただきありがとうございました。

    キャンセル

  • 2020/04/24 09:35

    シリアルのほうが面倒がない、というのは同意ですが、

    > SPIの双方向通信は
    以降は誤りと考えます。

    キャンセル

  • 2020/04/24 09:42

    まあ、個人の考え方次第ですんで、誤りというのは否定しません。
    が、あなたの回答では、SPIデバイスを直接いじってる、と、私には見えていますw

    キャンセル

  • 2020/04/24 09:44

    あぁ、そうですね。言われてみれば。
    じゃ、範囲を「そのうえ」以降に訂正。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.32%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る