2016年9月27日火曜日

Arduino UnoをSPI Master、Nucleo F401REをSPI Slaveにして通信してみる。


配線図

Nucleo(Slave)のCSピンはArduinoのようにD10にすると実行時にSerial経由でエラーが表示される。Nucleo F401REのピンアウトを見ると、A2がSPI1_NSSとなっているので、こちらに設定したら動作した。NSSのNはたぶんActive Lowの意味でSS(Slave Select)に前置しているんだと思う。

<追記:2016.10.01>

シリアル経由のエラーメッセージ


</追記>


SPIの信号線だけではSlaveからアクションを起こせないので、SPIとは別に外部割込みを配線(Nucleo:PC_7→Arduino:D2)し、Nucleo(Slave)からArduino(Master)に割込みをかけてMasterからSPI通信を開始するようにした。

また、秋月のNucleo F401REの商品説明によると「GPIO数:50(5Vトレラント、一部を除く)」となっているので5V→3.3Vのレベルシフトはしないで直結した。除かれる「一部」がどこのピンか調べてないがAruduinoヘッダは5Vトレラントだろうと憶測(^q^;

<追記:2016.09.29>

STM32 F401のDATASHEET(DM00102166.pdf 「STM32F401xD STM32F401xE」)の「4 Pinouts and pin description」の「Table 8. STM32F401xD/xE pin definitions」にどのピンが5Vトレラントなのか載っていた。「Pin type」がFTとなっているピンが5Vトレラントで、LQFP64パッケージのI/Oピンはすべて5Vトレラントのようです。

VREFとBOOT0(何をするピンかはよく調べてない)は5Vトレラントではないので要注意です。

</追記>

動作概要


1   タクトスイッチの読み取り(Master→Slave送信)


1.1 Arduino(SPI Master)でタクトスイッチ×6の押し下げ状態を取得。

1.2 タクトスイッチの押し下げ状態に変化があった場合に、SPIで読み取り値をNucleo(SPI Slave)に送信。Nucleoからの返値は無視。

1.3 NucleoはSPI受信した読み取り値をLED×6に表示

2.  一定間隔でカウンタをインクリメント(Slave→Master送信)


2.1 NucleoはmbedのRtosTimerで一定間隔でカウンタをインクリメントし、SPI通信の返値にセット。カウンタをインクリメントした時に一度だけArduinoに外部割込みをかける。

2.2 Arduinoは外部割込みがかかったときNucleoにSPI送信(Master→Slave)し、返値にセットされているカウンタ値をLEDx4に表示。

Arduino(Master)のスケッチ

SPI_Master_for_Nucleo_SPI_Slave.ino

#include <SPI.h>

const int SpiCsPin = 10;
const int SpiSpeed = 10000000;

const int InterruptPin = 2;

volatile bool isStepChanged = false;
uint8_t prevSendVal = 0x00;

void setup()
{
  // PD4 - PD7: Output
  DDRD  = 0xF0;
  
  // LED Check
  for (int i = 0; i < 5; i++) {
    PORTD = 0xF0;
    delay(100);
    PORTD = 0x00;
    delay(100);
  }
  
  // PORTC: Input Pullup
  DDRC  = 0x00;
  PORTC = 0xFF;
  
  // Setup Interrupt Pin
  pinMode(InterruptPin , INPUT);
  attachInterrupt(digitalPinToInterrupt(InterruptPin), setStepChange, RISING);  
  
  // Setup SPI
  pinMode(SpiCsPin, OUTPUT);
  digitalWrite(SpiCsPin, HIGH);
  SPI.begin();
  SPI.beginTransaction(SPISettings(SpiSpeed, MSBFIRST, SPI_MODE0));
}

void loop()
{
  uint8_t sendVal = ~PINC & 0x3f;
  
  if (prevSendVal != sendVal || isStepChanged) {
    digitalWrite(SpiCsPin, LOW);
    uint8_t recievedVal = SPI.transfer(sendVal);
    digitalWrite(SpiCsPin, HIGH);
    
    prevSendVal = sendVal;
    
    if (isStepChanged) {
      PORTD = recievedVal << 4;
      isStepChanged = false;
    }
  }
}

void setStepChange() {
  isStepChanged = true;
}

Nucleo(Slave)のプログラム(mbed)

https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPISlave_Test/

mbedのLibraryは個人的に動作実績のあるRevisionを使うことにした。

mbed: Revision 121
mbed-rtos Revision 117

main.cpp

#include "mbed.h"
#include "rtos.h"
#include "SPISlave.h"

#define SPI_SPEED   (10000000)

BusOut Leds(PA_10, PB_3, PB_5, PB_4, PB_10, PA_8);
DigitalOut StepChangePin(PC_7);

SPISlave SpiS(PA_7, PA_6, PA_5, PA_4);    // mosi, miso, sclk, ssel

unsigned int step = 0;

void stepUp(void const* arg)
{
    step++;
   
    // Masterにinterruptをかける。
    StepChangePin.write(1);
    StepChangePin.write(0);
}

int main()
{
    printf("\r\n\nNucleo rtos SPISlave Test..\r\n");
   
    // Setup LED
    for (int i = 0; i < 5; i++) {
        Leds.write(0x3f);
        Thread::wait(100);
        Leds.write(0x00);
        Thread::wait(100);
    }

    // Setup SPISlave
    SpiS.format(8, 0);
    SpiS.frequency(SPI_SPEED);

    // RtosTimer
    RtosTimer stepTimer(stepUp, osTimerPeriodic, (void *)0);
    stepTimer.start(250);   // BPM:60
   
    SpiS.reply(0);
    while(1) {
        if(SpiS.receive()) {
            int v = SpiS.read();   // Read byte from master
            Leds.write(v);
            SpiS.reply(step % 16);
        }
    }
}

SPI通信のようす。


MOSI(タクトスイッチの押し下げ状態)


ch1:MOSI ch2:SCK

MISO(カウンタ)


ch1:MISO ch2:SCK

CS


ch1:CS ch2:SCK

Interrupt


ch1:Interrupt ch2:SCK

Arduinoへの外部割込み信号とSPIのSCK信号の間隔は最大5マス程度で変動する。

MISO/MOSI


ch1:MOSI ch2:MISO

SPIのMOSIとMISOは同時にデータを送り合っている。