Raspberry Pi Pico C/C++ PetitFatFsでSDカード読み込みしてみる

#PetitFatFsとは?
[PetitFatFs](http://elm-chan.org/fsw/ff/00index_p.html)とはChaN氏によるFatFsを最低限の機能に絞り
手軽にFatFileSystemのデータ操作ができるありがたいライブラリです。
このライブラリはハード依存の部分とそうでない部分にわかれており
ユーザーは自分の使うハードに合わせてハード依存の部分(diskio.c)さえ作ってやれば良いだけになっています。

#diskio.cをつくる
てことでハード依存の部分(diskio.c)を実装してみました。
(Readで力尽きたのでWriteは実装してません)

とりあえず自分の手持ちのSDカード(2GB)では動作確認できましたが、万能ではないです。
まず、CMD1で初期化できるカードしか使えません。
そして、フォーマットするソフトによっても使えたり使えなかったりします。
win10標準のFAT16フォーマットではダメで[Disk Formatter](https://www.buffalo.jp/support/download/detail/?dl_contents_id=60879)を使うといけました。
（多様なSDカードに対応している世の中の製品ってすごいなあと実感しました。。。）


ピンアサインは下記の通りです。

| GPIO | 役割 | SDカード |
|:-:|:-:|:-:|
|  2 |  SCK |  SCLK/CLK |
|  3 |   TX |  DI/CMD |
|  4 |   RX |  D0/DAT0 |
|  5 |   CS |  CS/CD/DAT3 |

```c:diskio.c

#include "user_define.h"
#include "diskio.h"

/* Definitions for MMC/SDC command */
#define CMD0    (0x40+0)    /* GO_IDLE_STATE */
#define CMD1    (0x40+1)    /* SEND_OP_COND (MMC) */
#define CMD16    (0x40+16)    /* SET_BLOCKLEN */
#define CMD17    (0x40+17)    /* READ_SINGLE_BLOCK */

#define CS_HIGH() {gpio_put(P_SPI_CS, 1);}
#define CS_LOW() {gpio_put(P_SPI_CS, 0);}

static BYTE rcv_spi( void );        /* Send a 0xFF to the SDC/MMC and get the received byte */
static void xmit_spi( BYTE d );    /* Send a byte to the SDC/MMC */
static BYTE send_cmd( BYTE cmd, DWORD arg );

/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize( void )
{
    DSTATUS stat = STA_NOINIT;
    WORD i;

    /* マイコンリソースの初期設定 */
    {
        spi_init( SPI_CH, 100 * 1000 );
        spi_set_format( SPI_CH, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST );
        gpio_set_function( P_SPI_RX, GPIO_FUNC_SPI );
        gpio_set_function( P_SPI_SCK, GPIO_FUNC_SPI );
        gpio_set_function( P_SPI_TX, GPIO_FUNC_SPI );

        gpio_init( P_SPI_CS );
        gpio_set_dir( P_SPI_CS, GPIO_OUT );
    }

    CS_HIGH();

    /* ダミークロックを80発 */
    {
        BYTE i;
        for ( i= 0; i < 10; i++ )
        {
            rcv_spi();
        }
    }

    {
        BYTE cmd_res;

        /* CMD0を0x01が返ってくるまでトライ */
        for ( i = 0; i < 10000; i++ )
        {
            cmd_res = send_cmd( CMD0, 0 );
            if ( cmd_res == 0x01 )
            {
                break;
            }
        }

        if ( cmd_res == 0x01 )/* Enter Idle state */
        {
            /* CMD1を0x00が返ってくるまでトライ */
            for ( i = 0; i < 10000; i++ )
            {
                cmd_res = send_cmd( CMD1, 0 );
                if ( cmd_res == 0x00 )
                {
                    /* 初期化完了 */
                    stat = 0;
                    break;
                }
                sleep_us( 100 );
            }

            /* CMD16でブロックサイズを512バイトに設定 */
            send_cmd( CMD16, 512 );
        }
    }

    CS_HIGH();
    rcv_spi();

    return ( stat );
}



/*-----------------------------------------------------------------------*/
/* Read Partial Sector                                                   */
/*-----------------------------------------------------------------------*/

DRESULT disk_readp (
    BYTE* buff,        /* Pointer to the destination object */
    DWORD sector,    /* Sector number (LBA) */
    UINT offset,    /* Offset in the sector */
    UINT count        /* Byte count (bit15:destination) */
)
{
    DRESULT res;
    BYTE cmd_res;

    res = RES_ERROR;

    cmd_res = send_cmd( CMD17, ( sector * 512 ) );
    if ( cmd_res == 0x00 )
    {
        WORD i;
        /* なんらかの応答が来るまでループ */
        for ( i = 0; i < 40000; i++ )
        {
            cmd_res = rcv_spi();
            if ( cmd_res != 0xFF )
            {
                break;
            }
        }

        /* 応答がデータパケットの先頭0xFE時の処理 */
        if ( cmd_res == 0xFE )
        {
            /* offsetまで読み飛ばす */
            for ( i = 0; i < offset; i++ )
            {
                rcv_spi();
            }

            /* データを読み込む */
            if ( buff != 0 )
            {
                for ( i = 0; i < count; i++ )
                {
                    buff[ i ] = rcv_spi();
                }
            }
            else
            {
                for ( i = 0; i < count; i++ )
                {
                    rcv_spi();
                }
            }

            /* 残りは読み飛ばす */
            for ( i = 0; i < ( 514 - ( offset + count ) ); i++ )
            {
                rcv_spi();
            }

            res = RES_OK;
        }
    }

    CS_HIGH();
    rcv_spi();

    return ( res );
}



/*-----------------------------------------------------------------------*/
/* Write Partial Sector                                                  */
/*-----------------------------------------------------------------------*/

DRESULT disk_writep (
    const BYTE* buff,        /* Pointer to the data to be written, NULL:Initiate/Finalize write operation */
    DWORD sc        /* Sector number (LBA) or Number of bytes to send */
)
{
    DRESULT res;


    if (!buff) {
        if (sc) {

            // Initiate write process

        } else {

            // Finalize write process

        }
    } else {

        // Send data to the disk

    }

    return res;
}


static void xmit_spi( BYTE d )    /* Send a byte to the SDC/MMC */
{
    BYTE src[ 1 ];

    src[ 0 ] = d;
    spi_write_blocking( SPI_CH, src, 1 );
}

static BYTE rcv_spi( void )        /* Send a 0xFF to the SDC/MMC and get the received byte */
{
    BYTE dst[ 1 ];

    spi_read_blocking( SPI_CH, 0xFF, dst, 1 );

    return ( dst[ 0 ] );
}


static BYTE send_cmd(
    BYTE cmd,        /* 1st byte (Start + Index) */
    DWORD arg        /* Argument (32 bits) */
)
{
    BYTE n, res;

    /* Select the card */
    CS_HIGH();
    rcv_spi();
    CS_LOW();
    rcv_spi();

    /* Send a command packet */
    xmit_spi( cmd );                        /* Start + Command index */
    xmit_spi( (BYTE)( arg >> 24 ) );        /* Argument[31..24] */
    xmit_spi( (BYTE)( arg >> 16 ) );        /* Argument[23..16] */
    xmit_spi( (BYTE)( arg >>  8 ) );            /* Argument[15..8] */
    xmit_spi( (BYTE)arg );                /* Argument[7..0] */
    n = 0x01;                            /* Dummy CRC + Stop */
    if ( cmd == CMD0 )
    {
        n = 0x95;            /* Valid CRC for CMD0(0) */
    }
    xmit_spi( n );

    /* Receive a command response */
    n = 10;                                /* Wait for a valid response in timeout of 10 attempts */
    for ( n = 0; n < 10; n++ )
    {
        res = rcv_spi();
        if ( ( res & 0x80 ) == 0x00 )
        {
            break;
        }
    }

    return ( res );            /* Return with the response value */
}

```

#動かしてみる
SDカード内にHelloWorld!SDと書かれたファイルtest.txtをいれておいて
picoでSDカードから読み出しUSBでPCに出力してみます

```txt:test.txt
HelloWorld!SD

```

main()関数はこんな感じ

```c:main.c
void main( void )
{
    FATFS fs;
    BYTE buff[16];
    UINT br;
    FRESULT res;

    stdio_init_all();

    res = pf_mount( &fs );
    if ( res != 0 )
    {
        printf( "Error:pf_mount,%d\n", res );
        sleep_ms( 1000 );
    }
    res = pf_open( "TEST.TXT" );
    if ( res != 0 )
    {
        printf( "Error:pf_open,%d\n", res );
        sleep_ms( 1000 );
    }
    res = pf_read( buff, 16, &br );
    if ( res != 0 )
    {
        printf( "Error:pf_read,%d\n", res );
        sleep_ms( 1000 );
    }

    while ( true )
    {
        printf( buff );
        printf( "\n" );
        sleep_ms( 1000 );
    }
}
```

これで動かしてみると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398022/a04fcafd-5d7d-bf05-75ba-fc99f5414de6.png)

読み込めました！