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

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

新規登録して質問してみよう
ただいま回答率
85.35%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

Q&A

解決済

2回答

8667閲覧

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

_kimura_

総合スコア14

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

0グッド

0クリップ

投稿2020/04/23 04:47

##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台で通信

MasterUnit

1/*Pro miniがマスター 2 * 3 */ 4 5#include <SPI.h> 6int i,j,MasterReceive; 7byte arr[2] = { i, j}; 8 9void setup(){ 10 Serial.begin(9600); 11 SPI.setBitOrder( MSBFIRST ); 12 SPI.setDataMode( SPI_MODE3 ); 13 SPI.begin(); 14} 15 16void loop() { 17 digitalWrite( SS, LOW ); //対象の電子部品を接続したSS 18 //MasterReceive = SPI.transfer(arr,2); //コンパイルエラー発生 "void value not ignored as it ought to be" 19 SPI.transfer(arr,2); 20 digitalWrite( SS, HIGH ); //通信終了 21 Serial.print("MasterReceive = "); 22 Serial.println(MasterReceive); 23 i++; 24 if ( i > 255) i = 0; 25 j--; 26 if ( j < 0 ) j = 0; 27}

SlaveUnit

1/* 2 * SPIスレーブ 3 * SS - Pin10 4 * MOSI - Pin11 5 * MISO - Pin12 6 * SCK - Pin13 7 */ 8 9#include <SPI.h> 10 11static const int spiClk = 1000000; // クロック 1 MHz 12uint16_t i,j; 13volatile byte Slavereceived,Slavesend; 14 15 16void setup() { 17 Serial.begin(9600); 18 pinMode(MISO,OUTPUT); //MISOを出力 19 SPI.setBitOrder(MSBFIRST); //最上位ビット(MSB)から送信 20 SPI.setClockDivider(SPI_CLOCK_DIV16); //通信速度をデフォルト 21 SPI.setDataMode(SPI_MODE3); //アイドル5Vで0V→5Vの変化で送信する 22 SPCR |= _BV(SPE); 23 SPI.attachInterrupt(); 24} 25 26// SPI割り込み処理 27ISR(SPI_STC_vect) 28{ 29 Slavereceived = SPDR; 30 SPDR = Slavesend; 31} 32 33void loop() { 34 Serial.print("Slavereceived = "); 35 Serial.print(Slavereceived); 36 Serial.print(","); 37 Serial.print("Slavesend = "); 38 Serial.println(Slavesend); 39 Slavesend = i++; 40}

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

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

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

Arduino

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

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


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

実は、そのプログラムでも双方向通信(のようなこと)は出来ています。ただし、タイミング的に無理があって、自在にデータをやり取り出来る状態ではない、のです。
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間)で双方向らしい動きは確認できました。

Arduino

1/*マスター*/ 2 3#include <SPI.h> 4int i, j; 5byte arr[2] = {}; 6 7void setup() { 8 Serial.begin(9600); 9 SPI.setBitOrder( MSBFIRST ); 10 SPI.setDataMode( SPI_MODE3 ); 11 SPI.begin(); 12} 13 14void loop() { 15 digitalWrite( SS, LOW ); //対象の電子部品を接続したSS 16 arr[0] = i; 17 arr[1] = j; 18 Serial.print("MasterSend = "); 19 Serial.print(arr[0]); 20 Serial.print(','); 21 Serial.println(arr[1]); 22 for ( int idx = 0; idx < 2; idx++) { 23 arr[idx]=SPI.transfer(arr[idx]); 24 delayMicroseconds(50); 25 } 26 digitalWrite( SS, HIGH ); //通信終了 27 Serial.print("MasterReceive = "); 28 Serial.print(arr[0]); 29 Serial.print(','); 30 Serial.println(arr[1]); 31 i++; 32 if ( i > 255) i = 0; 33 j--; 34 if ( j < 0 ) j = 255; 35}

Arduino

1/* 2 * SPIスレーブ 3 * SS - Pin10 4 * MOSI - Pin11 5 * MISO - Pin12 6 * SCK - Pin13 7 */ 8 9#include <SPI.h> 10 11uint16_t i,j; 12volatile byte Slavereceived[2],Slavesend; 13 14 15void setup() { 16 Serial.begin(9600); 17 pinMode(MISO,OUTPUT); //MISOを出力 18 SPI.setBitOrder(MSBFIRST); //最上位ビット(MSB)から送信 19 SPI.setDataMode(SPI_MODE3); //アイドル5Vで0V→5Vの変化で送信する 20 SPCR |= _BV(SPE); 21 SPI.attachInterrupt(); 22} 23 24// SPI割り込み処理 25ISR(SPI_STC_vect) 26{ 27 Slavereceived[j] = SPDR; 28 j=(j+1)%2; 29 SPDR = Slavesend; 30 Slavesend = i++; 31} 32 33void loop() { 34//表示は遅いので飛び飛びになることも 35 Serial.print("Slavereceived = "); 36 Serial.print(Slavereceived[0]); 37 Serial.print('/'); 38 Serial.print(Slavereceived[1]); 39 Serial.print(", "); 40 Serial.print("Slavesend = "); 41 Serial.println(Slavesend); 42}

投稿2020/04/24 00:33

thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

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

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

投稿2020/04/23 04:54

y_waiwai

総合スコア88042

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

_kimura_

2020/04/23 05:53

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

2020/04/24 00:35

シリアルのほうが面倒がない、というのは同意ですが、 > SPIの双方向通信は 以降は誤りと考えます。
y_waiwai

2020/04/24 00:42

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

2020/04/24 00:44

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問