LPC810メモ:SPI interface(SDカードを読み書き)

SDカードに書き込みを行うサンプルですが、ファイルシステムは無視して書き込むので、使ったカードは再フォーマットしないとPCからアクセスできなくなると思われます。もし試そうとしてくれる方がいましたら、必ず不要なSDカードを使ってください。

SPI通信の例として、SDカードを読み書きする例。
前提とする環境はこちら→LPC810メモ 共通の準備

  • 本例の動作。電源を入れると、以下を順に行う。
    • SDカードの最後のブロックに、1,2,3,1,2,3...というデータを書き込む。
    • 同じブロックからデータを読み込む
    • 読み込んだ値に応じた回数のLEDを点滅させる。つまり、1回、2回、3回、1回、2回、3回...と点滅する。

サンプルコード

#include "LPC8xx.h"

// 成功するまで粘るコマンドのための回数定義
#define TIMEOUT 5000

// 失敗したここに飛ばす。
// 引数の回数だけLED点滅させておく。
static void error(uint8_t e) {
    while(1) {
        for(uint8_t i = 0; i < e * 2; i++) {
            LPC_GPIO_PORT->NOT0 |= (1<<0);
            for(volatile int j = 0; j < 100000; j++) {}
        }
        for(volatile int i = 0; i < 300000; i++) {}
    }
}

static bool sdhc; // SDHC/XCの場合にtrue(途中で設定)
static uint32_t sdBlocks; // ブロックの数(途中で設定)

// read/write時のアドレス指定(sdhcだとblock単位)
static uint32_t sdAdress(uint32_t block) {  return block * (sdhc ? 1 : 512); }

// CSにつなぐ1番pin(GPIO5)をHIGHにする、LOWにする
static void csSet() { LPC_GPIO_PORT->SET0 |= (1<<5); };
static void csClear() { LPC_GPIO_PORT->CLR0 |= (1<<5); };

// 1byte読み書き
// UM10601 29.3.5 Transmit and receive a byte to/from slave 0
// の例を真似。
static uint8_t spiWrite(uint8_t arg = 0xff) {
    // UM10601 17.6.3 SPI Status register
    // TXRDY(Transmitter Ready flag)が立つまで待つ
    while(~LPC_SPI0->STAT & (1 << 1));
    // UM10601 17.6.7 SPI Transmitter Data and Control register
    // 1バイト書き込み
    LPC_SPI0->TXDATCTL =
        arg // TXDAT = 送信するデータ
        | (8 - 1) << 24 // LEN = 8bit送信
        | (1 << 20); //  EOT = 1 (data stallの発生を防ぐのに必要だとか)

    // UM10601 17.6.3 SPI Status register
    // RXRDY(Receiver Ready flag)が立つまで待つ
    while(~LPC_SPI0->STAT & (1 << 0));

    // UM10601 17.6.6 SPI Receiver Data register
    // 読み込み結果を返す
    return LPC_SPI0->RXDAT;
}

// SDカードにコマンド送信
// skipはR1byteの後に何バイト読むか(R7なら4byteなど)。ただし読むだけで無視する。
static uint8_t sdCmd(uint8_t cmd, uint32_t arg = 0, int skip = 0, uint8_t crc=0) {
    csClear();
    spiWrite(0x40 | cmd);
    spiWrite(arg >> 24);  spiWrite(arg >> 16); spiWrite(arg >> 8);  spiWrite(arg >> 0);
    spiWrite(crc);
    for (int i = 0; i < TIMEOUT; i++) {
        int response = spiWrite();
        if (!(response & 0x80)) {
            for (int j = 0; j < skip; j++) {  spiWrite(); }
            csSet();  spiWrite();
            return response;
        }
    }
    csSet();  spiWrite();
    return 0xff; // タイムアウト
}

// データパケット読み込み
static void sdReadPacket(uint8_t *buffer, int length) {
    csClear();

    // データトークンを待つ
    for (int i = 0;; i++) {
        if(i > TIMEOUT) { error(5); };
        if(spiWrite() == 0xFE) { break; };
    }

    for (int i = 0; i < length; i++) { buffer[i] = spiWrite(); } // データ読み込み
    spiWrite(); // CRC
    spiWrite(); csSet(); spiWrite();
}

// 指定されたブロックから512byte読み込み
static void sdRead(uint8_t *buffer, uint32_t block_number) {
    // CMD17
    if (sdCmd(17, sdAdress(block_number)) != 0) { error(6); }
    sdReadPacket(buffer, 512);
}

// 指定されたブロックに512byte書き込み
static void sdWrite(uint8_t *buffer, uint32_t block_number) {
    // CMD24
    if (sdCmd(24, sdAdress(block_number)) != 0) { return;  }
    csClear();
    spiWrite(0xFE); // データトークン
    for (int i = 0; i < 512; i++) { spiWrite(buffer[i]);  } // データ読み込み
    spiWrite(); // CRC
    spiWrite();

    // レスポンストークン
    if ((spiWrite() & 0x1F) != 0x05) {
        // 成功
        csSet();  spiWrite();  return;
    }

    // なんらか失敗(0以外が戻るまで待つ)
    for (int i = 0;; i++) {
        if(i > TIMEOUT) { error(7); };
        if(spiWrite() != 0) { break; };
    }
    csSet();  spiWrite();
}

// SDカード初期化
void sdInitialize() {
    // SDカードが立ち上がってないかもしれないので少しだけ待つ
    for(volatile int i = 0; i < 5000; i++) {}

    // UM10601 17.6.10 SPI Divider register
    LPC_SPI0->DIV = 120 - 1; // 12MHz / 120 = 100KHzに設定

    // SPIをmasterとして有効化
    // UM10601 17.6.1 SPI Configuration register
    LPC_SPI0->CFG =
        (1 << 0) // ENABLE = 1
        | (1 << 2); // MASTER = 1

    // SDカード初期化のためにCSをHIGHにして0xffを送信(クロック74回以上)
    csSet();
    for(int i = 0; i < 16; i++) { spiWrite();  }  //  16byte送って、都合128クロック

    if(sdCmd(0,0,0,0x95) != 1) { error(2); }; // CMD0(リセット)

    // CMD8の結果が1(idle state, illegal commandではない)ならSDHC/XCとみなす。
    // 0x01 << 8は3.3v、0xaaはcheck pattern(なぜ0xaaなのかは知らない)
    sdhc = (sdCmd(8, 0x01aa, 4, 0x87) == 1) ;

    // ACMD41(初期化)
    for (int i = 0;; i++) {
        if(i > TIMEOUT) { error(3); };
        sdCmd(55);
        if (sdCmd(41, sdhc ? (1 << 30) : 0) == 0) { break;  }; // 成功
    }

    sdCmd(16, 512); // CMD16(ブロック長を512byteに設定。なくても良い気がする。)
    LPC_SPI0->DIV = 12 - 1; // 12MHz / 12 = 1MHzに設定

    // CMD9(CSDを読んで、カードのブロック(セクタ)の数を求める)
    if (sdCmd(9) != 0) { error(4); }
    uint8_t csd[16];
    sdReadPacket(csd, 16);
    if(sdhc) {
        //  CSD Version 2.0
        uint32_t c_size = (csd[7] & 0x3f) << 16 | csd[8] << 8 | csd[9];  // [69:48] device size
        sdBlocks = (c_size + 1) * 1024;
    } else {
        //  CSD Version 1.0
        uint32_t c_size = (csd[6] & 0x3) << 10 | (csd[7] << 2) | (csd[8] & 0xc0) >> 6; // [73:62] device size
        uint32_t c_size_mult = (csd[9] & 0x3) << 1 | (csd[10] & 0x80) >> 7; // [49:47] device size multiplier
        uint32_t read_bl_len = (csd[5] & 0xf); // [83:80] max. read data block length
        uint32_t block_len = 1 << read_bl_len;
        uint32_t mult = 1 << (c_size_mult + 2);
        uint32_t blocknr = (c_size + 1) * mult;
        uint32_t capacity = blocknr * block_len;
        sdBlocks = capacity / 512;
    }
}

int main(void) {
    // GPIOの設定 1,8番pinを出力に設定しておく
    // 1番はSDカードのCSにつなぐ
    // 8番はLEDにつなぐn
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);
    LPC_GPIO_PORT->DIR0 |= (1<<0) | (1<<5);

    // LED消灯
    LPC_GPIO_PORT->CLR0 |= (1<<0);

    // 「UM10601 17.3 Basic configuration」の手順で初期化
    // SPI0に対してclockを有効化(bit11。bit12はspi1なので関係ない)
    // In the SYSAHBCLKCTRL register, set bit 11 and 12 (Table 30) to
    // enable the clock to the register interface.
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<11);

    // Clear the SPI0/1 peripheral resets using the PRESETCTRL register (Table 19).
    LPC_SYSCON->PRESETCTRL &= ~(0x1<<0);
    LPC_SYSCON->PRESETCTRL |= (0x1<<0);

    // SWMの設定
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

    /* Pin Assign 8 bit Configuration */
    LPC_SWM->PINASSIGN3 = 0x04ffffffUL; // SPI0_SCK ->2番pin
    LPC_SWM->PINASSIGN4 = 0xffff0302UL; // SPI0_MISO->3番、SPI0_MOSI->4番
    LPC_SWM->PINENABLE0 = 0xffffffffUL; // 固定pin機能はreset含めて無効化(1番使いたいから)

    // SDカード初期化
    sdInitialize();

    // 読み書き用のbufferを用意。データを適当に入れる(頭から順に1,2,3,1,2,3....)
    uint8_t buffer[512];
    for(int x = 0; x < 512; x++) { buffer[x] = x % 3 + 1;  }

    // SDカードの最後のセクタにbufferの内容を書き込む
    sdWrite(buffer, sdBlocks-1);

    // 読み込み用のデータをゼロ初期化
    for(int x = 0; x < 512; x++) { buffer[x] = 0;  }

    // SDカードの最後のセクタからbufferの内容を読み込む
    sdRead(buffer, sdBlocks-1);

    // buffer[x]の値に応じた回数だけLED点滅
    for(int x = 0; x < 512; x++) {
        for(int j = 0; j < buffer[x] * 2; j++) {
            LPC_GPIO_PORT->NOT0 |= (1<<0);
            for(volatile int i = 0; i < 200000; i++) {}
        }
        for(volatile int i = 0; i < 1000000; i++) {}
    }

    return 0 ;
}

接続


  • SDカードとの結線は下記
LPC810 SDカード
1番pin(GPIO5) CS
2番pin(SPI0_SCK) CLK
3番pin(SPI0_MISO) SDO
4番pin(SPI0_MOSI) SDI
  • 実行後のSDカードの中身をHxDで確認した結果
    • 8MBのSDカード
      • 最後のSector(=Block)13278に、01,02,03...と書き込まれている。

    • 8GBのSDHCカード
      • 最後のSector(=Block)15278079に、01,02,03...と書き込まれている。


補足/雑感

  • SPIの使い方というより、SDカードの使い方がわからずに苦戦。
  • ネット上のわかりやすそうなサイトを読んで勉強。
  • それでも地力不足故に細かいところまでは理解できず。
  • 結局、mbedのSDFileSystemを参考にしてなんとか動かせた。
  • SDカードとSDHC/XCのどちらにも使えるようにするために、無駄にサイズ大きい。実際に何か作る時には、どちらかに決め打ちすべき?
  • 読み書きの単位が最低512byteといのも、SRAMが1KbyteのLPC810にとっては厳しい気がした。
  • 今回も苦戦しただけに達成感あり。

みっかぼの無料Androidアプリはこちら。