2016年5月9日月曜日

Arduino UnoをSPIのMasterにして、複数のSlaveを使う。

PSoC 4 Pioneer KitをSPIのスレーブとして動作させて、SPI LCDのNokia 5110も同じSPIバスを使って動作させてみた。


※実際の配線では「Arduino Uno」と「Nokia 5110」間のMISOはつながっていません。

SPIはスレーブの切り替えにSS(Slave Select。CS/Chip Select)を使う。信号線はMasterSlaveのMOSIとMasterSlaveのMISOの2本だがスレーブの数だけSSが必要になる。

<追記:2016.06.03>

書き忘れてましたが、SPI通信には信号線以外にクロック線のSCKが必要です(^q^;

</追記>

ブレッドボード図

動作としては、

1) MasterのArduinoが、繋がれているPOTの値を読み取り、SPIでSlaveのPSoC4に送信する。

2) SlaveのPSoC4はArduinoから送られてきたデータを受け取り、繋がれているロータリー・エンコーダーの値を読み取り、MasterのArduinoに送信する。

3) SlaveのPSoC4はArduinoから送られてきたデータの値(RX)と、Arduinoに送ったデータ(TX)をキャラクタLCD(パラレル接続)に表示する。

4) MasterのArduinoは1)で自分が送ったデータ(TX)と、SlaveのPSoC4から送られてきたデータ(RX)をSPI接続のLCDのNokia 5110に表示する。

したがって、POTを回しても、ロータリー・エンコーダーを回してもArduinoのLCDにもPSoC4のLCDにも同じデータが表示される。

Master側のArduinoのスケッチ


// PSoC 4 SPI Slave用のMasterプログラム
// 
// POTでTXデータを設定、NOKIA 5110でTX,RXデータを表示
//
// PSoC 4 Slave
//      SCK    13
//      MISO   12
//      MOSI   11
//      CS      9
//
// LCD5110 
//      SCK  - Pin 13
//      MOSI - Pin 11
//      DC   - Pin 2
//      RST  - Pin 3
//      CS   - Pin 10
//
// POT  A0
//
// 2016.05.09
#include <stdio.h>
#include <SPI.h>
#include <LCD5110_Graph.h>

#define PIN_PSOC_SS  9
#define PIN_POT      A0

#define TX_PACKET_SIZE  3
#define RX_PACKET_SIZE  3

LCD5110 myGLCD(13,11,2,3,10);  // SCK, MOSI, DC, RST, CS

extern uint8_t SmallFont[];

byte txBuffer[TX_PACKET_SIZE];
byte rxBuffer[RX_PACKET_SIZE];

char strBuffer[17];

void setup()
{
  myGLCD.InitLCD();
  myGLCD.setFont(SmallFont);
  
  myGLCD.clrScr();
  myGLCD.print("SPI Master", 0, 0);
  myGLCD.update();
  
  pinMode(PIN_PSOC_SS, OUTPUT);
}

void loop()
{
  int txData;
  int rxData;
  
  txData = analogRead(PIN_POT);
  rxData = sendRecieveData(txData);
  displayData(txData, rxData);
  delay(100);
}

int sendRecieveData(int txData) {
  static byte cnt;

  txBuffer[0] = cnt++;
  txBuffer[1] = txData >> 8;
  txBuffer[2] = txData & 0xff;

  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(PIN_PSOC_SS, LOW);
  rxBuffer[0] = SPI.transfer(txBuffer[0]);
  rxBuffer[1] = SPI.transfer(txBuffer[1]);
  rxBuffer[2] = SPI.transfer(txBuffer[2]);
  digitalWrite(PIN_PSOC_SS, HIGH);
  SPI.endTransaction();
  SPI.end();

  return ((int)rxBuffer[1] << 8) | (rxBuffer[2]);
}

void displayData(int txData, int rxData) {
  myGLCD.clrScr();
  myGLCD.print("SPI Master", 0, 0);
  
  sprintf(strBuffer, "TXDATA: %d", txData);
  myGLCD.print(strBuffer, 0, 10);
  
  sprintf(strBuffer, "TX:%03d %03d %03d", txBuffer[0], txBuffer[1], txBuffer[2]);
  myGLCD.print(strBuffer, 0, 20);

  sprintf(strBuffer, "RX:%03d %03d %03d", rxBuffer[0], rxBuffer[1], rxBuffer[2]);
  myGLCD.print(strBuffer, 0, 30);

  sprintf(strBuffer, "RXDATA: %d", rxData);
  myGLCD.print(strBuffer, 0, 40);

  myGLCD.update();
}

Nokia 5110の表示には「Nokia5110_Graph」ライブラリを使っている。(参考「ロジックICでSPIの5V->3.3Vレベルシフト Nokia LCD5110を動かす」)。

PSoC4とのSPI通信にはArduinoの「SPI」ライブラリを使った。「NOKIA5110_Graph」はArduinoのSPIライブラリを使用していない(ATMega328pのハードウェアSPIも使っていない)ので、PSoC4との通信の前後でSPIオブジェクトをbegin()、end()させている。こうしないとNokia 5110との通信がうまくいかない。

送受信データは、どちらも3byteにしていて、Masterが生成したカウンターの値(1byte)+POT/RotaryEncoderの16bitの読み取り値(2byte)。

Slave側のPSoC4のプロジェクト


TopDesign

SPISはSCBのSPIコンポーネントでスレーブに設定している。UARTはデバッグ用に使った。PIN_REというPINコンポーネントはロータリー・エンコーダーの読み取り用。

Github:
https://github.com/ryood/PSoC4_SPI_Slave_Test

Master側 (Arduino)
https://github.com/ryood/PSoC4_SPI_Slave_Test/tree/master/Arduino(Master)/SPI_Master_PSOC4_Slave_w_UI

Slave側 (PSoC4)
https://github.com/ryood/PSoC4_SPI_Slave_Test/tree/master/PSoC(Slave)/PSoC4_SPI_Slave_Test

メモ:


SPIは全二重なので送信だけ、受信だけという使い方にするには少し工夫が必要。普通のシリアル通信のようにプログラムを書くと電話でお互いが勝手に喋っべいるみたいな状態(自分の声でかき消されて相手が何を言っているか聞き取れない?)になってしまってハマってしまった。

SlaveからMasterに送信するには、Master側からSlaveに「どうぞ」と声をかけてからSlaveが送信するとか、Master側から制御するR/Wの信号線を追加するとかしないとダメかもしれない。