2016年10月4日火曜日

Nucleo F401RE(mbed)同士でSPI通信してみる。(その2)

1バイトずつの送受信は、「Nucleo F401同士でSPI通信してみる。」でうまくいったが、データ列を送受信しようと思うとエラーが発生してしまっていた。

プログラムをかんたんにして、条件を変えつつテストしてみた。

配線図


ベースマシン用の配線をそのまま使ったので配線が込み入っているが、基本的には配線図の左側のブレッドボード上に配線しているSPIの信号線4本(SCK、MISO、MOSI、CS)+SlaveからMasterへの割り込み線1本+GND1本の計6本を使っている。

1Byte(8bit)ずつ連続で送ってみる。


mbedのSPIには

template<typename Type >
int transfer (const Type *tx_buffer, int tx_length, Type *rx_buffer, int rx_length, const event_callback_t &callback, int event=SPI_EVENT_COMPLETE)
  Start non-blocking SPI transfer using 8bit buffers. 

という関数があって、ブロック単位で送信出来るが、<SPI.h>では「#if DEVICE_SPI_ASYNCH」ディレクティブ内で宣言されている。

    #if DEVICE_SPI_ASYNCH
    printf("DEVICE_SPI_ASYNCH: defined\r\n");
    #else
    printf("DEVICE_SPI_ASYNCH: not defined\r\n");
    #endif

というプログラムを書いて動かしてみると、「DEVICE_SPI_ASYNCH」は#defineされていないようで、Nuceloのmbedでは実装されていない感じだ。なので

virtual int write(int value)

を使って、連続的にバイトデータを送信してみた。プログラムの動作は前回の「Nucleo F401同士でSPI通信してみる。」のものとほぼ同じだが、8bitカウンタをインクリメントして送信。受信側は1ずつ増加しているかどうかチェックして違っていたらエラーの総数をLEDに表示するようにした。

Master側プログラム

https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPI_Master_Test/ revison:5

main.cpp

#include "mbed.h"
#include "rtos.h"
#include "st7565LCD.h"
 
#define SPI_SPEED       (4000000)
#define SPI_DUMMY_DATA  (0x55)
 
BusIn Switches(PA_0, PA_1, PA_4, PB_0, PC_1, PC_0);
 
SPI SpiM(PA_7, PA_6, PA_5); // mosi, miso, sclk
DigitalOut SpiMCs(PB_6);
 
InterruptIn stepChangeInterrupt(PC_7);
 
//ST7565(PinName mosi, PinName sclk, PinName cs, PinName rst, PinName a0);
ST7565 gLCD(PB_15, PB_13, PB_12, PB_2, PB_1);
 
volatile bool isStepChanged = false;
uint8_t prevSendVal = 0x00;
 
void setChangeStep()
{
    isStepChanged = true;
}
 
int main()
{
    printf("\r\n\nNucleo rtos SPI Master Test..\r\n");
    
    #if DEVICE_SPI_ASYNCH
    printf("DEVICE_SPI_ASYNCH: defined\r\n");
    #else
    printf("DEVICE_SPI_ASYNCH: not defined\r\n");
    #endif
    
    // Setup LCD
    
    gLCD.begin(0x10);
    gLCD.drawstring(0, 0, "SPI Master Test");
    gLCD.display();
        
    // Setup Switches
    Switches.mode(PullUp);
    /*
    while(1) {
        printf("%x\r\n", ~Switches.read() &0x3f);
        Thread::wait(100);
    }
    */
    
    // Setup Interrupt
    stepChangeInterrupt.fall(&setChangeStep);
    
    // Setup SPI
    SpiMCs = 1;
    SpiM.format(8, 0);
    SpiM.frequency(SPI_SPEED);
    
    Thread::wait(1000);
    //gLCD.clear();
    
    uint8_t count = 0;
    for (;;) {
        SpiMCs = 0;
        uint8_t receivedVal = SpiM.write(count);
        SpiMCs = 1;
        count++;
        if (isStepChanged) {
            char lineBuffer[20];
            sprintf(lineBuffer, "Step: %02d", receivedVal);
            gLCD.drawstring(0, 0, lineBuffer);
            gLCD.display();
            isStepChanged = false;
 
        }
        //Thread::wait(1);
        wait_us(100);
    }
}


このプログラムではSPI::write()で1バイト送信するごとにCSをL/Hしている。SPI_SPEEDの値を変えてSPIの通信速度を変えてテストした。また、メインループで、「Thread::wait()」または「wait_us()」を入れて送信の間隔を調整。

「Thread::wait()」はウェイトしている間RTOSに制御が戻るので、RTOSを使う場合はこっちを使ったほうがいいらしいが、ミリ秒単位でしか指定できない。マイクロ秒単位のウェイトにはwait_us()を使った。

Slave側プログラム

https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPISlave_16bit_Test/ Revision:3

main.cpp

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

#define SPI_SPEED   (4000000)

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

//SPISlave SpiS(PA_7, PA_6, PA_5, PA_4);    // mosi, miso, sclk, ssel
// SPI2
SPISlave SpiS(PB_15, PB_14, PB_13, PB_12);    // mosi, miso, sclk, ssel

unsigned int step = 0;

void stepUp(void const* arg)
{
    step++;
 
    // Slaveに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(500);   // BPM:30
 
    SpiS.reply(0);
    uint8_t prevVal = 0;
    uint8_t errCount = 0;
    while(1) {
        if(SpiS.receive()) {
            uint8_t val = SpiS.read();   // Read byte from master
            prevVal++;
            if (val != prevVal) {
                errCount++;
                Leds.write(errCount);
            }
            prevVal = val;
            SpiS.reply(step % 16);
        }
    }
}

SPIの通信速度に1MHz、2MHz、4MHz、6MHz、10MHzを指定してみたが、4MHzまでは送受信がうまく行っているようだ。

→エラーカウント表示用のLEDが変化しない。ただしRESET後にLEDの表示は0x02になる。

また、メインループに入れているウェイトを除去するとLEDが点灯しっぱなしとなり、送受信がうまくいかない。Thread::wait(1)を入れるとOK。wait_us(100)(100マイクロ秒)だと時々エラーが発生した。

送受信のようす


SPI_SPEED 4MHz / Thread::wait(1) : OK

ch1:MISO ch2:SCK

プログラムでは4MHzを指定しているが、ch2:SCKを見ると約2.6MHz程度になっている。ch1の波形は何回か分の波形が重なって表示されている。

SPI_SPEED 6MHz / Thread::wait(1) : NG

ch2:sckを見ると約5MHzになっていて実クロック5MHz程度を超えるとだめな感じだ。

SPI_SPEED 4MHz / wait_us(100) : NG

ch1:CS ch2:sck

CSのL/Hの間に複数データを送る


Master側
https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPI_Master_Test/ revision:6

Slave側
https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPISlave_16bit_Test/ Revision:3

SPI_SPEEDが4MHzで、Thread::wait(1)でも、CSがLとCSがHの間に8バイト送信するようにすると時々エラーが発生した。128バイトだと頻繁にエラーが発生。

8byte送信時のようす

1バイト送信ごとにWaitを入れれば通信がうまくいくかもしれない?

16bitずつ送受信してみる


SPI::format()で1度に送受信するbit数を指定できるので16bitを指定してやってみた。

Master側
https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPI_Master_Test/ revision:7

Slave側
https://developer.mbed.org/users/ryood/code/Nucleo_rtos_SPISlave_16bit_Test/ Revision:4

送受信のようす

8bitのコマンド+8bitのデータというような使い方ができるかもしれない。

メモ:


連続送信をする場合は途中でエラーが発生するとえらいことになりそうなので、チェックサムを入れて違ってたら破棄したりとか、なかなかおおごとになってきそう(@@;

ベースマシンのUI Controller->Sequencerにデータを送るのは、データまるごとではなく、変更点があったものをその都度送るようにしたほうがいいかもしれない。データ本体以外に変更点の指定が必要になるのでデータ量としては冗長になるが。

なぜだかわからないが、ブレッドボード上でMOSIの信号線の途中でオシロのプローブを当てると受信エラーが発生する。

まあでも、Nucleo(mbed)同士のSPI通信は全然ダメというわけではなく、通信速度と通信間隔をうまく調節すればなんとかなるかもしれない(^q^/)