PIC32Cシリーズ⑤ PIC32CZ SDHC モジュールの使い方

PIC32Cシリーズ

概要

今回は、Microchip社の32ビットマイコン「PIC32CZ CA80」シリーズを搭載した開発ボード 「PIC32CZ CA80 Curiosity Ultra Development Board」 を使用し、SDHCモジュールを利用したuSDカードへの書込みの実装について紹介します。

記事変更履歴

公開/
変更日
特記
25/10/19初版公開

関連リンク

内部リンク

記事リンク
第1回PIC32Cシリーズ① 概要PIC32Cシリーズに関して – ぴくおの電子工作的な何かWP
第2回PIC32Cシリーズ② PIC32CZ CAシリーズPIC32Cシリーズ② PIC32CZ CAシリーズについて – ぴくおの電子工作的な何かWP
第3回PIC32Cシリーズ③ PIC32CZ CA80 Curiosity Ultra Development BoardについてPIC32Cシリーズ③ PIC32CZ CA80 Curiosity Ultra Development Boardについて – ぴくおの電子工作的な何かWP
第4回PIC32Cシリーズ④ PIC32CZ USB High-Speed モジュールの使い方PIC32Cシリーズ④ PIC32CZ USB High-Speed モジュールの使い方 – ぴくおの電子工作的な何かWP
第5回PIC32Cシリーズ⑤ PIC32CZ SDHCモジュールの使い方PIC32Cシリーズ⑤ PIC32CZ SDHC モジュールの使い方 – ぴくおの電子工作的な何かWP
(fig.1-1)各関連記事リンク
項目リンク
WFI32EによるmicroSDカード書き込みスループット計測WFI32EによるmicroSDカード書き込みスループット計測 – ぴくおの電子工作的な何かWP

外部リンク

項目リンク
PIC32CZ CA80 開発ボードサイトPIC32CZ CA80 Curiosity Ultra Development Board | Microchip Technology
データーシートPIC32CZ CA80/CA9x High Security (SG) Embedded Connectivity Family Data Sheet
Microchip MPLAB HarmonyMicrochip MPLAB Harmony · GitHub
FAT filesystem using SDMMC Mediacore_apps_pic32cz_ca/apps/fs/sdmmc_fat at master · Microchip-MPLAB-Harmony/core_apps_pic32cz_ca · GitHub

開発環境

項目リンク
ベースボードPIC32CZ CA80 Curiosity Ultra Development BoardPIC32CZ CA80 Curiosity Ultra Development Board | Microchip Technology
統合開発環境MPLAB X IDE v6.25MPLAB® X IDE | Microchip Technology
コンパイラMPLAB XC32 v4.60MPLAB® XC Compilers | Microchip Technology
デバイスファミリーパック1.6.163

SDHCモジュールについて

概要

SD/MMCホストコントローラ(SDHC)は、組み込み型マルチメディアカード(e.MMC)仕様、SDメモリーカード仕様、およびSDIO仕様をサポートしています。SDホストコントローラ標準仕様に準拠しています。
SDHCは「SD Host Controller Simplified Specification V3.00」で定義されたレジスタセットと、e.MMCデバイスおよび拡張機能を管理するための追加レジスタを含みます。
SDHCは最大3つのクロック(バスクロック、SDHCコアクロック、および特定の機能用のスロークロック)によって動作します。SDHCを使用する前に、MCLKとGCLKの両方を設定する必要があります。

SDHCモジュールの特徴
  • 互換性:
    – SDホストコントローラ標準仕様
    – マルチメディアカード仕様
    – SDメモリーカード仕様
    – SDIO仕様バージョン
  • 1ビット/4ビットSD/SDIOデバイスのサポート
  • 1ビット/4ビットe.MMC デバイスのサポート
  • SD/SDIOデフォルト速度のサポート(最大SDCLK周波数= 25 MHz)
  • SD/SDIO ハイスピード対応(最大SDCLK 周波数= 50 MHz)
  • e.MMCデフォルト速度のサポート(最大SDCLK周波数= 52 MHz)
  • e.MMCブート操作モードサポート
  • ブロックサイズのサポート範囲:1~512バイト
  • ストリーム、ブロック、マルチブロックデータの読み書きのサポート- 高度なDMAおよび
    SDMA機能
  • 内部2 x 512 (1024) バイトデュアルポートRAM
  • 同期および非同期のアボートのサポート
  • SDIOカード割り込みのサポート
    Complete

USBモジュールブロック図

以下にPIC32CZ CAシリーズのSDHCモジュールブロック図を示します

SDHCモジュールブロック図。DS60001749F – 1601 より抜粋

検証内容

SDHCモジュールの基本的な挙動と実用的な性能を把握するため、今回は次の2点に焦点を当てて検証します。

1. Harmony3のサンプルプロジェクトを用いた基本的な読み込み/書き込み動作の確認
2. 各種設定条件(ブロックサイズ、バッファ長など)を変更しながらの書き込みスループット計測

開発手順

1. サンプルプロジェクトの実行

Harmony3プロジェクト内にある「sdmmc_fat」フォルダを任意の作業用フォルダへコピーします。
 この時点でプロジェクト名を変えておくと、後の識別がしやすくなります。

PC側で、μSDカードに「FILE_TOO_LONG_NAME_EXAMPLE_123.JPG」というファイルを保存します。
 保存ができたら、そのμSDカードをCuriosityボードのスロットに挿入します。

プロジェクトをビルド&実行します。
 正しく動作していれば、SDカードのルートに「Dir1」というフォルダが自動生成され、その中に「FILE_TOO_LONG_NAME_EXAMPLE_123.JPG」がコピーされます。ここまで確認できれば成功です。。

2. プロジェクトの生成

①MPLAB X IDEを立ち上げ File >New Project.. をクリック

②Microchip Embeddedを選択し「NEXT」ボタンをクリック

③PIC32CZ8110CA80208を選択し「NEXT」ボタンをクリック

④コンパイラを選択し「NEXT」ボタンをクリック

⑤プロジェクト名、プロジェクト保存先、エンコーディングを選択し「Finish」をクリック。

3. MCC上でのモジュール追加

①プロジェクト生成が完了すると、自動でMCCが立ち上がります。

②Libraries > Harmony > System Hardware Definitions(SHD) > Main Boards > PIC32CZ-CA80-CURIOSITY-ULTRAを追加します

③追加途中でCoreを追加するか聞かれるので「Yes」をクリック

④FreeRTOS上で動作させるか聞かれるので「No」をクリック

⑥Libraries > Harmony > USB > Peripherals >SDMMC > SDMMC1を追加

⑦「SDMMC1」ブロックのダイヤマークを右クリック。出てきたメニューの 「Available Consumers」を選択し、更に出てきたメニューから「SDCMMC」を選択すると「USB High Speed Driver」ブロックが追加される。

⑧追加途中でTimeを追加するか聞かれるので「Yes」をクリック

⑨TIME のTMRダイヤマークを右クリック。出てきたメニューの 「Available Satisfiers」を選択し、更に出てきたメニューから「TCC0」を選択(今回の場合)

(fig.**) Timer選択画面

⑩「SDMMC」の「DRV_MEDIA」四角マークを右クリック。出てきたメニューの 「Available Consumers」を選択し、更に出てきたメニューから「File SYSTEM」を選択すると「File SYSTEM」ブロックが追加される。

⑪TCC0モジュールを呼び出します

4. 各モジュールの設定

①「PIC32CZ-CA80-CURIOSITY-ULTRA」ブロックを選択し、右のペインから「PIC32CZ-CA80-CURIOSITY-ULTRA」> 「Digital interface」でUser LED0,1とUser Switch 0,1のチェックを入れます

②「SDMMC1」を選択し、右のペインから値を設定する。

項目設定値意味
Number of ADMA2 Descriptor Lines1ADMA2(Advanced DMA 2)ディスクリプタラインの数。
ADMA2とは
SDMMCコントローラが使用する高度なDMA転送方式
ディスクリプタを使って複数の非連続メモリ領域を効率的に転送可能
設定値の意味
1: 最小限の設定。シンプルな転送に対応
値を増やす: より複雑な転送(分散/収集DMA)が可能になるが、メモリ使用量が増加
一般的な使用例
通常のファイルシステム操作では1~2で十分
大量の小さなファイルを高速処理する場合は増やすことを検討

③「SDMMC」の「Instance 0」を選択し、右のペインから値を設定する。

項目設定値意味
PLIB UsedSDMMC1使用するペリフェラルライブラリ。SDMMC1ペリフェラルを使用。
Number of Clients1このドライバを使用するクライアント(上位層)の数。通常は1つのファイルシステムが使用。
Transfer Queue Size2転送キューのサイズ。同時に保留できる転送リクエストの数。値を大きくすると応答性が向上しますが、メモリ使用量が増加。
Data Transfer Bus Width4bitデータ転送バス幅の設定:
1-bit: 最も低速だがピン数が少ない
4-bit: 標準的な速度(現在の設定)
8-bit: 最高速(eMMCで使用可能)
Bus SpeedHIGH_SPEEDDEFAULT_SPEED: 標準速度(~25MHz)
HIGH_SPEED: 高速モード(~50MHz、現在の設定)
ProtocolSD使用するプロトコル:
SD: SDカード用プロトコル(現在の設定)
eMMC: 組み込みマルチメディアカード用
Card Detection MethodUse SDCD PinSDカードの挿入検出方法:
Use SDCD Pin: 専用のカード検出ピン(SDCD)を使用(現在の設定)
Polling: ポーリングで定期的にチェック(ピン節約)
Enable Write Protection Check?未チェック書き込み保護スイッチのチェック機能:
有効にすると、SDカードの物理的な書き込み保護スイッチの状態を確認
書き込み保護ピン(WP)の設定が必要
File system for SDMMC Driver EnabledチェックSDMMCドライバをファイルシステム(先ほどのFileSystem設定)と連携させる設定。これが有効でないとファイルシステムからSDカードにアクセスできません。

④「FILE SYSTEM」を選択し、右のペインから値を設定する。

項目設定値意味
Maximum Simultaneous File Access1同時に開けるファイルの最大数。値が大きいほどメモリ使用量が増加します。
Size Of Block512ファイルシステムが扱うブロック(セクタ)の基本単位(バイト)。通常は512バイト(標準的なセクタサイズ)。
Size Of Media Manager Buffer2048メディアマネージャが使用するバッファサイズ(バイト)。読み書き性能に影響します。
Use File System Auto Mount Feature?チェックファイルシステムの自動マウント機能。チェックすると起動時に自動的にマウントします。
Total Number Of Media1接続可能なメディア(SDカード、USBメモリなど)の総数
Total Number Of Volumes1マウント可能なボリューム(パーティション)の総数。
Media0チェックメディア0を使用するかの設定
File System Types1サポートするファイルシステムタイプの数
FAT File SystemチェックFATファイルシステムのサポートを有効化。
FAT File System Versionv0.15使用するFATファイルシステムライブラリのバージョン。
Make FAT File System Read-only未チェック読み取り専用モードにする場合にチェック。書き込み機能が不要ならメモリ節約可能。
OEM code page to be usedU.S.文字コードページの設定。日本語対応なら”Japanese”を選択することもあります。
Enable exFAT File System Support未チェックexFAT(拡張FAT)のサポート。大容量ファイル(4GB以上)が必要な場合に有効化。
Enable Long File Name Supportチェック長いファイル名(LFN)のサポート。無効にすると8.3形式のみ(メモリ節約)。
File name length in bytes255ファイル名の最大長(バイト)。255は標準的な最大値。
Current working directory scratch buffer length in bytes1024カレントディレクトリ用の作業バッファサイズ(バイト)。
Enable Cache Lineチェックキャッシュライン機能を有効化。
Aligned Buffer for Cache Managementチェックキャッシュ管理用のアライメントされたバッファを使用。パフォーマンス向上。
Aligned Buffer Length in Multiple of 512 Bytes512キャッシュバッファのサイズ。512バイトの倍数で指定。

⑤TCC0モジュールをクリックし、以下のパラメータ設定を行う

5. クロック設定

SDMMC1クロックを100MHz(25MHzの倍数)とSDMMC1_SLOWクロックを12MHzに設定します

6. コードの生成

「Generate」ボタンを押し、コードを生成します

7. アプリケーションコードの生成

生成されたApp.hとApp.cをそれぞれ以下のコードと差し替えます。

■App.h

#ifndef _APP_H
#define _APP_H

// *****************************************************************************
// *****************************************************************************
// Section: Included Files
// *****************************************************************************
// *****************************************************************************

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include "configuration.h"
#include "system/fs/sys_fs.h"

// DOM-IGNORE-BEGIN
#ifdef __cplusplus  // Provide C++ Compatibility

extern "C" {

#endif
// DOM-IGNORE-END

// *****************************************************************************
// *****************************************************************************
// Section: Type Definitions
// *****************************************************************************
// *****************************************************************************
#define APP_DATA_LEN         512

// *****************************************************************************
/* Application states

  Summary:
    Application states enumeration

  Description:
    This enumeration defines the valid application states.  These states
    determine the behavior of the application at various times.
*/

typedef enum
{
    /* Application's state machine's initial state. */
    /* The app waits for sdcard to be mounted */
    APP_MOUNT_WAIT = 0,

    /* Set the current drive */
   // APP_SET_CURRENT_DRIVE,

    /* The app opens the file to read */
    APP_OPEN_FILE,

        /* Create directory */
   // APP_CREATE_DIRECTORY,

        /* The app opens the file to write */
   // APP_OPEN_SECOND_FILE,
    APP_WRITE_FILE ,
    /* The app reads from a file and writes to another file */
    APP_READ_FILE,

    /* The app closes the file*/
  //  APP_CLOSE_FILE,

    /* The app closes the file and idles */
    APP_IDLE,

    /* An app error has occurred */
    APP_ERROR

} APP_STATES;


// *****************************************************************************
/* Application Data

  Summary:
    Holds application data

  Description:
    This structure holds the application's data.

  Remarks:
    Application strings and buffers are be defined outside this structure.
 */

typedef struct
{
    /* SYS_FS File handle for 1st file */
    SYS_FS_HANDLE      fileHandle;

    /* SYS_FS File handle for 2nd file */
    SYS_FS_HANDLE      fileHandle1;

    /* Application's current state */
    APP_STATES         state;

    int32_t            nBytesRead;

    /* Flag to indicate SDCARD mount status */
    volatile bool      sdCardMountFlag;
} APP_DATA;



void APP_Initialize ( void );
void APP_Tasks( void );



//DOM-IGNORE-BEGIN
#ifdef __cplusplus
}
#endif
//DOM-IGNORE-END

/*******************************************************************************
 End of File
 */



#endif /* _APP_H */int debounceCount;

    /* アプリケーションのCDC読み取りバッファ */
    uint8_t * cdcReadBuffer;

    /* アプリケーションのCDC書き込みバッファ */
    uint8_t * cdcWriteBuffer;

    /* ホストから読み取ったバイト数 */
    uint32_t numBytesRead; 
} APP_DATA;


void APP_Initialize ( void );
void APP_Tasks ( void );


#endif /* _APP_H */

■App.C

#include <string.h>
#include "app.h"
#include "user.h"
#include "config/default/bsp/bsp.h"
#include "config/default/peripheral/tcc/plib_tcc0.h"

#define SDCARD_MOUNT_NAME    SYS_FS_MEDIA_IDX0_MOUNT_NAME_VOLUME_IDX0
#define SDCARD_DEV_NAME      SYS_FS_MEDIA_IDX0_DEVICE_NAME_VOLUME_IDX0
#define SDCARD_FILE_NAME     "FILE_TOO_LONG_NAME_EXAMPLE_123.JPG"
#define SDCARD_DIR_NAME      "Dir1"
#define LED_ON()			BSP_LED0_On() 
#define LED_OFF()			BSP_LED0_Off()
#define LED_TOGGLE()		BSP_LED0_Toggle()
#define BUFFER_ATTRIBUTES       CACHE_ALIGN



static APP_DATA appData;

/* Application data buffer */
//static uint8_t BUFFER_ATTRIBUTES dataBuffer[APP_DATA_LEN];

// *****************************************************************************
// *****************************************************************************
// Section: Application Callback Functions
// *****************************************************************************
// *****************************************************************************

static void APP_SysFSEventHandler(SYS_FS_EVENT event,void* eventData,uintptr_t context)
{
    switch(event)
    {
        /* If the event is mount then check if SDCARD media has been mounted */
        case SYS_FS_EVENT_MOUNT:
            if(strcmp((const char *)eventData, SDCARD_MOUNT_NAME) == 0)
            {
                appData.sdCardMountFlag = true;
            }
            break;

        /* If the event is unmount then check if SDCARD media has been unmount */
        case SYS_FS_EVENT_UNMOUNT:
            if(strcmp((const char *)eventData, SDCARD_MOUNT_NAME) == 0)
            {
                appData.sdCardMountFlag = false;

                appData.state = APP_MOUNT_WAIT;

                LED_OFF();
            }

            break;

        case SYS_FS_EVENT_ERROR:
        default:
            break;
    }
}


uint32_t time[16];

void APP_Initialize ( void )
{
    /* Place the App state machine in its initial state. */
    appData.state = APP_MOUNT_WAIT;

    /* Register the File System Event handler */
    SYS_FS_EventHandlerSet((void const*)APP_SysFSEventHandler,(uintptr_t)NULL);
	
	time[0] = TCC0_Timer32bitCounterGet();

}


/******************************************************************************
  Function:
    void APP_Tasks ( void )

  Remarks:
    See prototype in app.h.
 */

uint32_t cnt;

void APP_Tasks(void)
{
    switch (appData.state )
    {
        case APP_MOUNT_WAIT:        
			
			if(appData.sdCardMountFlag == true)
            {
				time[1] = TCC0_Timer32bitCounterGet();
                appData.state = APP_OPEN_FILE;
            }
            break;

        case APP_OPEN_FILE:
			time[2] = TCC0_Timer32bitCounterGet();
            appData.fileHandle = SYS_FS_FileOpen("/mnt/myDrive1/test.txt", (SYS_FS_FILE_OPEN_WRITE_PLUS));
			time[3] = TCC0_Timer32bitCounterGet();
            if (appData.fileHandle != SYS_FS_HANDLE_INVALID)
            {
                appData.state  = APP_WRITE_FILE;
            }
            else
            {
                appData.state  = APP_ERROR;
            }
            break;

        case APP_WRITE_FILE:
        {
            char writeData[2048] ;
			char cchar = 20;
			for (int i=0;i<2048;i++)
			{
				writeData[i] = cchar++;
				if (cchar>126){cchar = 20;}
				
			}
			time[4] = TCC0_Timer32bitCounterGet();
			
			for (cnt = 0 ;cnt<9999;cnt ++)
			{
				SYS_FS_FileWrite(appData.fileHandle, (void*)writeData, strlen(writeData));
			}
			
            if (SYS_FS_FileWrite(appData.fileHandle, (void*)writeData, strlen(writeData)) == SYS_FS_RES_FAILURE)
            {
				time[5] = TCC0_Timer32bitCounterGet();
                appData.state  = APP_ERROR;
            }
            else
            {
				time[5] = TCC0_Timer32bitCounterGet();
                SYS_FS_FileSync(appData.fileHandle); // データフラッシュ
				time[6] = TCC0_Timer32bitCounterGet();
                SYS_FS_FileClose(appData.fileHandle);
				time[7] = TCC0_Timer32bitCounterGet();
                appData.state  = APP_READ_FILE;
            }
            break;
        }

        case APP_READ_FILE:
        {
            char readBuffer[2048] = {0};
			time[8] = TCC0_Timer32bitCounterGet();
            appData.fileHandle = SYS_FS_FileOpen("/mnt/myDrive1/test.txt", (SYS_FS_FILE_OPEN_READ));
            if (appData.fileHandle != SYS_FS_HANDLE_INVALID)
            {
				time[9] = TCC0_Timer32bitCounterGet();
                SYS_FS_FileRead(appData.fileHandle, (void*)readBuffer, sizeof(readBuffer)-1);
				time[10] = TCC0_Timer32bitCounterGet();
                SYS_FS_FileClose(appData.fileHandle);
				time[11] = TCC0_Timer32bitCounterGet();

                // UART出力(デバッグ確認用)
                printf("Read from SD: %s\n", readBuffer);

                appData.state  = APP_IDLE;
            }
            else
            {
                appData.state  = APP_ERROR;
            }
            break;
        }

        case APP_IDLE:
            // 正常終了
			printf("SDMMC rw done.\n");
            break;

        case APP_ERROR:
            printf("SDMMC Error occurred.\n");
            break;
    }
}

また「initialization.c」内のSYS_Initialize関数内で、TCC0_TimerStart();関数を呼び、実行時間計測用のタイマーを開始します。

void SYS_Initialize ( void* data )
{
    PORT_Initialize();
    CLOCK_Initialize();
    BSP_Initialize();
    EVSYS_Initialize();

    TCC0_TimerInitialize();
    SYSTICK_TimerInitialize();
    SDMMC1_Initialize();

   sysObj.drvSDMMC0 = DRV_SDMMC_Initialize(DRV_SDMMC_INDEX_0,(SYS_MODULE_INIT *)&drvSDMMC0InitData);
        
    sysObj.sysTime = SYS_TIME_Initialize(SYS_TIME_INDEX_0, (SYS_MODULE_INIT *)&sysTimeInitData);
    
    (void) SYS_FS_Initialize( (const void *) sysFSInit );

    TCC0_TimerStart();
    APP_Initialize();
    NVIC_Initialize();

}

結果

1. 書き込み速度の劇的な向上

小容量書き込み(条件1〜4)では、書き込み速度は1kB/s〜35kB/s程度でした。

しかし、大容量連続書き込み(条件5〜6)では、驚異的な速度向上が見られました。

  • 条件5: 352kB/s (約10倍の向上)
  • 条件6: 1416kB/s (約40倍の向上!)

2. バススピードの影響

  • Default (25MHz)とHighSpeed (50MHz)を比較すると、小容量書き込みではほとんど差がありません
  • これは、ファイル操作のオーバーヘッドが支配的であるためと考えられます

3. ボトルネックの正体

SYS_FS_FileSyncの処理時間が非常に長い(5〜7ms)ことから、書き込み後の同期処理が大きなボトルネックになっていることが分かります。

小容量書き込みの場合:

  • 実際の書き込み: 0.4〜2.4ms
  • 同期処理: 5〜7ms

→ 同期処理が全体の70%以上を占めています。

4. 最適な書き込み方法

実験結果から、以下のことが明らかになりました。

まとめて書き込む方が圧倒的に高速

  • 14Byte単体で書き込む: 1kB/s
  • 512Byte×10000回: 352kB/s
  • 2048Byteずつ書き込む: 1416kB/s

バッファサイズを大きくする効果が大きい

  • 512Byte → 2048Byteで約4倍の速度向上
条件
SDMMC1100MHz
SDMMC1_SLOW12MHz
カードToshiba/32GB/Class10
条件1条件2条件3条件4条件5条件6
書込みバイト14Byte14Byte512Byte512Byte512Byte×10000回2048Byte×10000回
Bus SpeedDefault
(25MHz)
HighSpeed
(50MHz)
Default
(25MHz)
HighSpeed
(50MHz)
HighSpeed
(50MHz)
HighSpeed
(50MHz)
関数条件1(ms)条件2(ms)条件3(ms)条件4(ms)条件5(ms)条件6(ms)
SYS_FS_FileOpen
(WRITE_PLUS)
5.3775.2185.215.075.1625.16
SYS_FS_FileWrite0.4100.3842.3772.1021453314452
SYS_FS_FileSync7.8087.6837.6947.4665.675.63
SYS_FS_FileClose0.0020.0020.0020.0020.0020.002
SYS_FS_FileOpen
(read)
0.4310.4050.40404060.4060.404
SYS_FS_FileRead0.4870.4600.4630.4610.8762.448
SYS_FS_FileClose0.0010.0010.0010.0010.0010.001
書込み速度1kB/S1kB/S33.5kB/s35kB/s352kB/s1416kB/s

結論

microSDカードへの書き込み性能を最大化するには:

  1. できるだけ大きなバッファサイズを使用する
  2. 連続して書き込む
  3. 同期処理の回数を減らす(まとめて書き込んでから同期)

クロック周波数を上げるよりも、書き込み方法の最適化の方がはるかに効果的であることが実証されました。

なお、今回は検証対象に含めていませんが、「microSDカード自体の性能」も転送速度に大きく影響します。
したがって、上記の結果はあくまで今回使用した環境と条件における一例であることにご注意ください。

記事についての注意点

本記事は慎重に内容を検討し正確さに努めておりますが、内容に誤りがあったとしても、この記事を参考にして生じた損害等については一切の責任を負いません。

コメント

タイトルとURLをコピーしました