RX マイコン SDHI を使った SD モードドライバー


はじめに

RX マイコンには、SD モードでアクセスする為のインターフェース (SDHI) を内臓しているデバイスがあります。

SD モード:
SD カードアクセスの規格には、簡易的なモード (SPI) があり、より簡単なハードウェアーで、SD カードにアクセスする事ができます。
SD モードでは、SD カードの本来の機能を使って、より高速なアクセスを実現出来るようになっています。
SD モードでは、4ビットのバス (SDHC) でアクセス出来る為、より高速なアクセスが可能となっています。

SD カードアクセスは、組み込みでも一般的ですが、多くは SPI モードで、SD モードを扱った解説は少ないように思います。
今回、SDHI を使った、SD モードドライバーと、FatFs(ff13c)を使い、SD カードアクセスの実験を行ったので、要点を解説したいと思います。

FatFs は以下の外部 API を SD モード用に実装する事で、同じように使う事が出来ます。

  • DSTATUS disk_initialize(BYTE drv)
    ※SD カードの初期化で呼ばれる
  • DSTATUS disk_status(BYTE drv)
    ※SD カードの状態を返す
  • DRESULT disk_read(BYTE drv, BYTE* buff, DWORD sector, UINT count)
    ※セクターの読み出し
  • DRESULT disk_write(BYTE drv, const BYTE* buff, DWORD sector, UINT count)
    ※セクターの書き込み
  • DRESULT disk_ioctl(BYTE drv, BYTE ctrl, void* buff)
    ※SD カードの特殊な操作
  • DWORD get_fattime(void)
    ※ファイル書き込み時のタイムスタンプ取得

RX マイコンにおける SDHI 内臓デバイス

主要な RX マイコンで SDHI を内臓するデバイスは以下のようになっています。

デバイス コア SDHI デフォルト ハイスピード
R5F564MxDxxx RX64M 10MBits/s 15MBits/s
R5F564MxHxxx RX64M 10MBits/s 15MBits/s
R5F564MxCxxx RX64M × - -
R5F564MxGxxx RX64M × - -
R5F565xxAxxx RX65x × - -
R5F565xxBxxx RX65x 12.5MBits/s 25MBits/s
R5F565xxDxxx RX65x 12.5MBits/s 25MBits/s
R5F565xxExxx RX65x × - -
R5F565xxFxxx RX65x 12.5MBits/s 25MBits/s
R5F565xxHxxx RX65x 12.5MBits/s 25MBits/s
R5F571MxDxxx RX71M 10MBits/s 15MBis/s
R5F571MxHxxx RX71M 10MBits/s 15MBis/s
R5F571MxCxxx RX71M × - -
R5F571MxGxxx RX71M × - -

今回、RX65N Envision Kit を使って実験しました。

RX65N Envision Kit には、R5F565NEDDFB が実装されており、SDHI を利用する事が出来ます。

SD カードソケットと電源制御の追加

RX65N Envision Kit は購入時、SD カードソケットと、電源制御 IC が未実装である為、実装する必要があります。

SD カードソケットは、入手が困難で、購入出来てもコストが高いので、今回は、秋月電子で購入が可能なマイクロ SD 基板を流用します。
※オリジナルSDカードソケット「SD/MMCカードソケット:101-00565-64(AMPHENOL COMMERCIAL PRODUCTS製)」
また、電源制御 IC も入手とコストの関係で、汎用の MOS-FET を使いました。
※オリジナルSDカード電源用ゲートIC「ISL61861BIBZ」は、スイッチON時のラッシュ電流を制御する仕組みがある為、もし使えるなら、本家を使った方が良いです。
※ただ、電流制限は、大きいので、あまり意味はないかもしれません、ピンアサインは異なりますが、MIC2026 が安価で、電流も丁度良いです。
※電源制御は、無くても動作が可能ですが、一応制御していますが、電源電圧の確認は省略しています。

秋月電子、マイクロ SD スロット基板:

秋月電子、PchチップMOSFET DMG3415U(20V4A):

「R35」のゲート用プルアップ抵抗 10K も取り付けます。
※ CN1 と書かれたシルクの上です。

※ライトプロテクトは利用していません。
SD カードの制御信号や、バスはプルアップされており、電源制御からプルアップ電圧の供給を受けています。
※これは、結構「キモ」な部分で、常に内臓電源でプルアップすると、SD カードの電源を切断しても、内蔵電源から、プルアップ抵抗を経由して、電流が流れてしまい、微妙な電圧が SD カードに供給される状態となり、再初期化を行う場合に失敗する場合があるかもしれません。
※クロック信号、コマンド信号には、インピーダンスのマッチング用に抵抗が直列に入っています。
※クロック信号だけ、プルアップ抵抗が省略されていますが、パターンはあり、場合により抵抗を実装可能なようですが、必要無いと思います。

SD モードと SPI モードの違い

端子 CLK CMD D0 D1 D2 D3 CRC
SPI モード CLK(SCLK) Din(MOSI) Dout(MISO) × × CS 不要
SD モード クロック 双方向 D0 D1 D2 D3 必要

SD モードで大きな違いは、SD カードにコマンドを送るバス (CMD) と、R/W データバス(D0 ~ D3) が分離しており、CMD バスは双方向になっています。
※コマンドのレスポンスを受け取る場合。
また、エラー検出を行う為、CRC 多項式による演算が必要です。
SPI モードでは、コマンドバス、データバスは共有しており、データは一方向に流れる為、より簡単な構成が可能で、CRC のエラー検査も省略されます。

SDHC 以降のカードでは、電源電圧を下げ (1.8V)、より高速な転送速度を実現する為のモードが用意されています。
※クロックとデータの遅延を整合するような機能もあります。
※これらは、RX マイコンの SDHI では扱えない為 (1.8V のポート電圧をサポートしておらず、クロック速度の上限にも制限がある)、サポートしていません。
通常、SD カードの電圧範囲は、2.7V ~ 3.6V です。

SD モードでは、4ビットバスと1ビットバスを選択可能ですが、カードによっては1ビットバスをサポートしていない場合があるようです。

SD モードの初期化プロセス

SD モードでは、以下の方法で初期化を行います。
1GB 以下の容量が小さい、カードは、別の初期化プロセスが必要ですが、最近では、そのようなカードは既に入手が難しい事もあり、省略しています。
※1GB のカードでも、古い製造のカードは別の初期化プロセスが必要な場合があり、初期化に失敗します。

  • SD カード電源投入
  • 電源が安定するまで待つ(実装では、1秒待っています)
  • SDHI の初期化を行い、初期化用クロック速度を設定(400KHz 以下)
  • ダミークロックを74個入れる
  • CMD0, 0x00000000 を送信
  • 1 ミリ秒待つ
  • CMD8, 0x000001AA を送信
  • 下位12ビットに 0x1AA が返る事を確認
  • ACMD41, 0x40FF8000 を送信
  • レスポンスで、B31 が「1」になるまで繰り返す(1秒間投げ続けても「0」ならエラー)
  • レスポンスで、B30が「1」なら、ブロックアクセス(4GB 以上のカード)
  • CMD2, 0x00000000 を送信(カード識別コマンド)
  • レスポンスで返るカード識別 ID を取得
  • CMD3, 0x00000000 を送信(RCA を読み出す)
  • レスポンスで返る RCA(B31~B16)を取得
  • CMD7, RCA を送信(カード選択)
  • CMD16, 512(ブロックサイズを512に設定)
  • ACMD6, 0x00000002 を送信(バス幅を4ビットに変更)
  • SDHI のバス幅を4ビットに変更
  • SDHI のクロック速度をブースト

ACMDxx は、CMD55、CMDxx を送る。(SDHI では、ACMD ビットを「1」にする)

詳細な手順は、sdhi_io.hpp / disk_initialize(BYTE drv)を参照

セクター、リード・ライト

FatFs の関数プロトタイプは以下のようなもので、セクター単位(512バイト)でデータをやり取りします。

    //-----------------------------------------------------------------//
    /*!
        @brief  リード・セクター
        @param[in]  drv     Physical drive nmuber (0)
        @param[out] buff    Pointer to the data buffer to store read data
        @param[in]  sector  Start sector number (LBA)
        @param[in]  count   Sector count (1..128)
     */
    //-----------------------------------------------------------------//
    DRESULT disk_read(BYTE drv, void* buff, DWORD sector, UINT count) noexcept

    //-----------------------------------------------------------------//
    /*!
        @brief  ライト・セクター
        @param[in]  drv     Physical drive nmuber (0)
        @param[in]  buff    Pointer to the data to be written
        @param[in]  sector  Start sector number (LBA)
        @param[in]  count   Sector count (1..128)
     */
    //-----------------------------------------------------------------//
    DRESULT disk_write(BYTE drv, const void* buff, DWORD sector, UINT count) noexcept

初期化の時、ACMD41 のレスポンスで、B30 が「0」の場合は、実アドレスとなる為、「sector」を 512 倍する必要があります。
※1GB、2GB の SD カード

    // Convert LBA to byte address if needed
    if(!(card_type_ & CT_BLOCK)) sector *= 512;

シングルセクターと複数セクターでコマンドが異なります。

    // read command
    command cmd = count > 1 ? command::CMD18 : command::CMD17;

    // write command
    command cmd = count > 1 ? command::CMD25 : command::CMD24;

SDHI では、内臓バッファは512バイトで、32ビット単位に格納される為、格納先が、32ビット単位では無い場合に工夫する必要があります。

    if((reinterpret_cast<uint32_t>(buff) & 0x3) == 0) {
        uint32_t* p = static_cast<uint32_t*>(buff);
        for(uint32_t n = 0; n < (512 / 4); ++n) {
            *p++ = SDHI::SDBUFR();
        }
        buff = static_cast<void*>(p);
    } else {
        uint8_t* p = static_cast<uint8_t*>(buff);
        for(uint32_t n = 0; n < (512 / 4); ++n) {
            uint32_t tmp = SDHI::SDBUFR();
            std::memcpy(p, &tmp, 4);
            p += 4;
        }
        buff = static_cast<void*>(p);
    }

又、内臓バッファの並びは、リトルエンディアンを想定したものになっている為、RX マイコンをビッグエンディアンで動かす場合(めずらしい)は、SWAPする必要がありますが、ハードウェアーの機能として用意されています。

#ifdef BIG_ENDIAN
    debug_format("Turn SWAP mode for Big Endian\n");
    SDHI::SDSWAP = SDHI::SDSWAP.BWSWP.b(1) | SDHI::SDSWAP.BRSWP.b(1);
#endif

SD モードでの速度

QIDIAN MLC 32GB (SDHC) Class10
WriteOpen:  0 [ms]
Write: 430 KBytes/Sec
WriteClose: 5 [ms]

ReadOpen:  0 [ms]
Read: 1024 KBytes/Sec
ReadClose: 0 [ms]

-----------------------------
Lexar 633x 8GB (SDHC) Class10
WriteOpen:  170 [ms]
Write: 210 KBytes/Sec
WriteClose: 12 [ms]

ReadOpen:  2 [ms]
Read: 1272 KBytes/Sec
ReadClose: 0 [ms]

-------------------------------------
SanDisk Industrial 8GB (SDHC) Class10
WriteOpen:  3 [ms]
Write: 330 KBytes/Sec
WriteClose: 98 [ms]

ReadOpen:  1 [ms]
Read: 1706 KBytes/Sec
ReadClose: 0 [ms]

--------------------------------------
SanDisk Industrial 16GB (SDHC) Class10
WriteOpen:  6 [ms]
Write: 388 KBytes/Sec
WriteClose: 5 [ms]

ReadOpen:  2 [ms]
Read: 1199 KBytes/Sec
ReadClose: 0 [ms]

-----------------------------------------
TOSHIBA 40MB/s Taiwan 32GB (SDHC) Class10
WriteOpen:  1 [ms]
Write: 200 KBytes/Sec
WriteClose: 46 [ms]

Open:  1 [ms]
Read: 1065 KBytes/Sec
ReadClose: 0 [ms]

---------------------------------------------------
SanDisk Industrial 16GB (SDHC) Class10 for Soft-SPI
WriteOpen:  0 [ms]
Write: 177 KBytes/Sec
WriteClose: 17 [ms]

ReadOpen:  2 [ms]
Read: 227 KBytes/Sec
ReadClose: 0 [ms]

テストは512バイト単位である為、連続で行う場合、もっと高速だと思います。
現状の実装は、ポーリングによるもので、ソフトウェア転送を行っています、割り込みや DMA は利用しておらず、チューニングもほとんどされていません。

まとめ

ドライバー、ハードウェア定義は、C++ テンプレートを活用しています。
以下のように、「SDHI を使った SD モード」と「ソフト SPI を使った SPI モード」を簡単に切り替え出来ます。

    // 定義部

    // カード電源制御は使わない場合、「device::NULL_PORT」を指定する。
//  typedef device::NULL_PORT SDC_POWER;
    typedef device::PORT<device::PORT6, device::bitpos::B4> SDC_POWER;

#ifdef SDHI_IF
    // RX65N Envision Kit の SDHI ポートは、候補3になっている
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, device::port_map::option::THIRD> SDC;
    SDC         sdc_;
#else
    // Soft SDC 用 SPI 定義(SPI)
    typedef device::PORT<device::PORT2, device::bitpos::B2> MISO;  // DAT0
    typedef device::PORT<device::PORT2, device::bitpos::B0> MOSI;  // CMD
    typedef device::PORT<device::PORT2, device::bitpos::B1> SPCK;  // CLK

    typedef device::spi_io2<MISO, MOSI, SPCK> SPI;  ///< Soft SPI 定義

    SPI         spi_;

    typedef device::PORT<device::PORT1, device::bitpos::B7> SDC_SELECT;  // DAT3 カード選択信号
    typedef device::PORT<device::PORT2, device::bitpos::B5> SDC_DETECT;  // CD   カード検出

    typedef fatfs::mmc_io<SPI, SDC_SELECT, SDC_POWER, SDC_DETECT> SDC;   // ハードウェアー定義

    SDC         sdc_(spi_, 35000000);
#endif

    // メイン部

    // 1/60 毎に呼ばれるサービス
    while(1) {
        render_.sync_frame();

...

        sdc_.service();
    }

参照:
RX65N Envision Kit:
RTK5_LCD_sample / main.cpp

RX24T, RX64M, RX65N/RX651:
SDCARD_sample / main.cpp