4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-10-23

はじめに

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):

RTK5_MicroSD_PSW.jpg

「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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?