2016年11月22日火曜日

Nucleo F401RE(mbed)でSDカードを使う

Nucleo F401REでSDカードを動作させてみた。以前、Arduinoで動かせたので(「ArduinoでSDカードを使ってみる。74HC4050でレベルシフト」)軽く考えていたが結構ハマってしまった。

まず、mbedオフィシャルの「SDFileSystem https://developer.mbed.org/users/mbed_official/code/SDFileSystem/」はうまく動かせなかった。SPI信号は出力されるようだが、カードを認識してくれなかった。

かなりあ~だこ~だやった末、Neil Thiessenさんがre-writtenされたバージョンのSDFileSystem (https://developer.mbed.org/users/neilt6/code/SDFileSystem/)で動作させることができた。

配線図



Nucleoは3.3V駆動なので信号線は直結した。

mbed repository:
https://developer.mbed.org/users/ryood/code/SDFileSystem_Test/

main.cpp

/*
 * SDFileSystem Test
 * 
 * Library
 * SDFileSystem: https://developer.mbed.org/users/neilt6/code/SDFileSystem/ Revision:26
 * mbed: Revision: 124
 *
 * 2016.11.22 created
 *
 */

#include "mbed.h"
#include "SDFileSystem.h"

//Create an SDFileSystem object
SDFileSystem sd(D11, D12, D13, D10, "sd");

int main()
{
    //Mount the filesystem
    sd.mount();

    //Perform a write test
    printf("\nWriting to SD card...");
    FILE *fp = fopen("/sd/sdtest.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "We're writing to an SD card!");
        fclose(fp);
        printf("success!\n");
    } else {
        printf("failed!\n");
    }

    //Perform a read test
    printf("Reading from SD card...");
    fp = fopen("/sd/sdtest.txt", "r");
    if (fp != NULL) {
        char c = fgetc(fp);
        if (c == 'W')
            printf("success!\n");
        else
            printf("incorrect char (%c)!\n", c);
        fclose(fp);
    } else {
        printf("failed!\n");
    }

    //Unmount the filesystem
    sd.unmount();
}

APIドキュメントに載っているExampleから

SDFileSystem sd(D11, D12, D13, D10, "sd");

とNucleoのArduino HeaderのPinNameに変更した。

I/OエキスパンダーのMCP23S17とSPIバスを共用するテスト


製作中のベースマシンで使う予定だが、3つあるSPIをすでに全部使ってしまっている。

SPI1: シンセの制御
SPI2: Graphic LCD
SPI3: I/Oエキスパンダー

SPI1はシンセ本体用なのであまり他と干渉させたくないし、SPI2のLCDも処理が重いので専用にしたい。I/Oエキスパンダーが一番余裕がありそうなのでSPI3を共用することにした。

が、SDFileSystemは

SDFileSystem (PinName mosi, PinName miso, PinName sclk, PinName cs, const char *name, PinName cd=NC, SwitchType cdtype=SWITCH_NONE, int hz=1000000)

とPinNameを渡してSPIオブジェクトをコンストラクタ内で生成し、privateなメンバ変数として持つ仕様になっているのでそのままでは共用(アクセス)できない。

しかたがないので、

    SPI* SpiPointer() { return &m_Spi; }

というアクセサを追加して、外部からSPIオブジェクトを参照出来るようにした。ホントは人様の作ったライブラリに手を入れるのは好ましくないが、慎重に使うことにする。

配線図


mbed repository:
https://developer.mbed.org/users/ryood/code/SDFileSystem_Bin_Test/

main.cpp

/*
 * SDFileSystem Binary R/W Test
 *
 * Library
 * SDFileSystem: https://developer.mbed.org/users/neilt6/code/SDFileSystem/ Revision:26
 * mbed: Revision: 124
 * mbed-rtos: Revision: 117
 *
 * 2016.11.22 created
 *
 */
 
#include "mbed.h"
#include "rtos.h"
#include "SDFileSystem.h"
 
#include "ExioMcp23s17.h" 
#include "ExioInBuffer.h"
#include "ExioBufferedIn.h"
#include "ExioBufferedDebounceIn.h"
 
typedef struct {
    uint8_t x;
    uint8_t y;
    uint8_t z;
} DataT;
 
//SPI Spi(PC_12, PC_11, PC_10); // SPI3: mosi, miso, sclk
SDFileSystem sd(PC_12, PC_11, PC_10, PA_14, "sd"); // SPI3: mosi, miso, sclk, cs
 
void writeSD(DataT* data)
{
    //Mount the filesystem
    sd.mount();
 
    //Perform a write test
    printf("\r\nWriting binary data to SD card...");
    FileHandle* file = sd.open("Test File.bin", O_WRONLY | O_CREAT | O_TRUNC);
    if (file != NULL) {
        if (file->write(data, sizeof(*data)) != sizeof(*data)) {
            error("write error!\r\n");
        }
        if (file->close()) {
            printf("failed to close file!\r\n");
        } else {
            printf("done!\r\n");
        }
    } else {
        printf("failed to create file!\r\n");
    }
 
    //Unmount the filesystem
    sd.unmount();
}
 
void readSD(DataT* data)
{
    //Mount the filesystem
    sd.mount();
 
    //Perform a read test
    printf("\r\nReading binary data from SD card...");
    FileHandle* file = sd.open("Test File.bin", O_RDONLY);
    if (file != NULL) {
        if (file->read(data, sizeof(*data)) != sizeof(*data)) {
            error("read error!\r\n");
        }
        if (file->close()) {
            printf("failed to close file!\r\n");
        } else {
            printf("done!\r\n");
        }
    } else {
        printf("failed to open file!\r\n");
    }
 
    //Unmount the filesystem
    sd.unmount();
}
 
int main()
{
    DataT data, rdata;
 
    data.x = 0xff;
    data.y = 0x55;
    data.z = 0xaa;
 
    printf("*** Test SDFileSystem & ExioBufferedDebounceIn ***\r\n");
    
    // ExioMcp23s17(int hardwareaddress, SPI& spi, PinName nCs, PinName nReset);
    ExioMcp23s17 Exio(0x00, *sd.SpiPointer(), PD_2, PA_13);
    
    // Reset MCP23S17 (初期化時にreset()が必要)
    Exio.reset();
 
    ExioInBuffer inBufferB(&Exio, ExioPortB);
    ExioBufferedDebounceIn inB[] = {
        ExioBufferedDebounceIn(&inBufferB, 0),
        ExioBufferedDebounceIn(&inBufferB, 1),
        ExioBufferedDebounceIn(&inBufferB, 2),
        ExioBufferedDebounceIn(&inBufferB, 3),
        ExioBufferedDebounceIn(&inBufferB, 4),
        ExioBufferedDebounceIn(&inBufferB, 5),
        ExioBufferedDebounceIn(&inBufferB, 6),
        ExioBufferedDebounceIn(&inBufferB, 7)
    };
 
    // Start Timers
    inBufferB.run(10);
    for (int i = 0; i < 8; i++) {
        inB[i].set_debounce_us(10000);
    }
 
    // SDカードの読み書きの前にExioのSPIアクセスを停止
    inBufferB.stop();
      
    writeSD(&data);
    readSD(&rdata);
    
    // ExioのSPIアクセスを再開
    inBufferB.run(10);
 
    printf("data: x:%02x y:%02x z:%02x\r\n", rdata.x, rdata.y, rdata.z);
    
    while(1) {
        uint8_t x = 0;
        for (int i = 0; i < 8; i++) {
            int vb = inB[i].read();
            x |= (vb << i);
        }
        if (x != 0 && data.x != x) {
            data.x = x;
            printf("Write to SD: x:%02x y:%02x z:%02x\r\n", data.x, data.y, data.z);
            // SDカードの読み書きの前にExioのSPIアクセスを停止
            inBufferB.stop();
            writeSD(&data);
            readSD(&rdata);
            // ExioのSPIアクセスを再開
            inBufferB.run(10);
            printf("Read from SD: x:%02x y:%02x z:%02x\r\n", rdata.x, rdata.y, rdata.z);
        }
    }
}

MCP23S17は通信コストを下げるためにバッファリングするようにした自作のExioBufferedControllerライブラリを使った。ExioBufferedDebounceInクラスはチャタリング対策したもの。

オブジェクトの生成がかなりめんどくさいことになっているが、SPIオブジェクト(の参照)を渡して初期化できるようになっている。

SPIバスを共用するのでSDカードを使う場合は、

inBufferB.stop();

としてSPI経由のMCP23S17への定期的な問い合わせを停止する。

また、SDカードの読み書きは低レベルなファイルハンドルを使ってバイナリの読み書きをしている。

SDカードとMCP23S17のCS

ch1: MCP23S17 ch2:SD

波形が込み入っているが、SDカードに読み書きしている間はMCP23S17のCSはhiになっていて問い合わせは停止している。

メモ:


SPIバスを共用しているのでSDカードの読み書き中はI/Oエキスパンダー経由の入力は受け付けなくなる。

SDカードの読み書きはファイルを変えていくつかのパターンを保存できるようにしたい。(ファイル名は単純に番号を振ればいいか?)