配線図
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は同時にデータを送り合っている。