ラズパイPicoでFatFsを使う

##はじめに
電子工作界隈で注目されている**Raspberry Pi Pico**（以後、**ラズパイPico**）でSDカード上のテストファイルやバイナリファイルへアクセスできるようオープンソースの**FatFs**を用いて実装することにしました。

以前に「Cypress FM4でFatfsを使う」で FatFsとCypress製マイコンのFM4へ SPI経由での接続、実装は経験済みで、基本的にSPIへアクセスする以下の６つの関数やマクロをラズパイPico用に改造すればよいことが既に分かっています。

・SPI初期化：init_spi()
・SPI１バイト送受信：xchg_spi()
・SPI複数バイト受信：rcvr_spi_multi()
・SPI複数バイト送信：xmit_spi_multi()
・チップセレクト・High：CS_HIGH()
・チップセレクト・Low：CS_LOW()

※Cypress FM4でFatfsを使う
　https://qiita.com/Yukiya_Ishioka/items/77cd75ca03fb5c7c5c0b

ラズパイPicoでは開発用に**Pico-SDK**という各種デバイスへのアクセス用サブルーチン群が提供されていて、この中にSPI用のアクセスルーチンがあることから、**Pico-SDK**のルーチンを使って実装しました。


##ラズパイPicoのビルド環境
私はインターフェース誌 2021年８月号でFreeRTOSを紹介した記事内に記してある Ubuntu上に必要なパッケージをインストールした環境を使っていますが、**Pico-SDK**を使ってビルドできる環境であれば問題ないと思います。

※インターフェース誌 2021年８月号
　https://interface.cqpub.co.jp/magazine/202108/

##ファイルの取得
ビルド環境の構築ができたら、今回使用するソース類をネットワークから取得します。

###FatFs
FatFsは以下のページからソースをダウンロードできます。
使用するファイルやダウンロード場所の詳細は、FatFsは先に紹介したQiitaの記事「Cypress FM4でFatfsを使う」を参照してください。
ここでも stm32f用のコードをラズパイPico用に改造して使用します。

※FatFsのページ
　http://elm-chan.org/fsw/ff/00index_e.html

FatFs本体と併せ、以下の２つのファイルをダウンロードしてください。
・**ff14b.zip**
・**ffsample.zip**

###Pico-SDK
**Pico-SDK**は以下のページからソースをダウンロードできます。
ダウロードや展開方法は、やはりインターフェース誌 2021年８月号のFreeRTOSの記事内に記してあるので参考にしてください。

※Raspberry Pi Pico SDKのページ
　https://github.com/raspberrypi/pico-sdk

##FatFsの展開

FatFs本体を展開します。
ff14b.zipファイル内から sourceディレクトリを取り出し、PC内の任意のディレクトリへコピーします。
今後の操作を考えると、以下のように**Pico-SDK**を展開した同じディレクトリにsrcディレクトリを作成し、その中に展開するとよいと思います。

```
|--pico-sdk
|
|--src
```

次に、ffsample.zipファイル内から**\stm32\mmc_stm32f1_spi.c**ファイルを取り出し、先のFatFs本体のファイルと同じディレクトリへ置きます。
その後、**mmc_pico_spi.c**というファイル名に置き替えます。
以下のファイルが置かれていると思います。

```
00history.txt
00readme.txt
diskio.c
diskio.h
ff.c
ff.h
ffconf.h
ffsystem.c
ffunicode.c
mmc_pico_spi.c
```

展開後のディレクトリ構成は以下のようになっていると思います。

```
|--pico-sdk
|
|--src
|  |--00history.txt
|  |--00readme.txt
|  |--diskio.c
|  |--diskio.h
|  |--ff.c
|  |--ff.h
|  |--ffconf.h
|  |--ffsystem.c
|  |--ffunicode.c
|  |--mmc_pico_spi.c

```

##FatFs試行用のハードウェア

・SDカードモジュールとの結線

| PicoのPin | GPIO No. | SD Card-Module |
|:------:|:------:|:------:|
| 21 | GPIO 16 | MISO (SDO) |
| 25 | GPIO 19 | MOSI (SDI) |
| 24 | GPIO 18 | SCK |
| 22 | GPIO 17 | /CS |

・USB-シリアル モジュールとの結線

| PicoのPin | GPIO No. | USB-シリアル |
|:------:|:------:|:------:|
| 25 | GPIO 20 | TxD |
| 26 | GPIO 21 | RxD |

![Pico_FatFs_Pic1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/3d85f0a2-486f-9d02-f397-1aca1586e2b9.jpeg)

##Pico用コードの作成
コピーした状態では mmc_pico_spi.c はSTM32用の制御コードとなっているため、１４行目から２０８行目までを以下のコードへ置き替えます。

``` mmc_pico_spi.c
#include "pico/stdlib.h"
#include "hardware/spi.h"

/*
   GPIO 16 (pin 21) MISO/spi0_rx-> SDO
   GPIO 17 (pin 22) Chip select -> !CS
   GPIO 18 (pin 24) SCK/spi0_sclk -> SCK
   GPIO 19 (pin 25) MOSI/spi0_tx -> SDI
*/
#define DEF_SPI_TX_PIN	19
#define DEF_SPI_RX_PIN	16
#define DEF_SPI_SCK_PIN	18
#define DEF_SPI_CSN_PIN	17

#define SPIDEV		spi0

#define FCLK_FAST()	{ }
#define FCLK_SLOW()	{ }

#define CS_HIGH()	{ gpio_put( DEF_SPI_CSN_PIN, 1 ); /* HIGH */ }
#define CS_LOW()	{ gpio_put(DEF_SPI_CSN_PIN , 0 ); /* LOW */ }

#define	MMC_CD		1 /* Card detect (yes:true, no:false, default:true) */
#define	MMC_WP		0 /* Write protected (yes:true, no:false, default:false) */


volatile  int  conter1;

void  Delay( int ms )
{
  for( conter1=0; conter1<125*ms ; conter1++ ) ;
}


/*--------------------------------------------------------------------------

   Module Private Functions

---------------------------------------------------------------------------*/
#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */


/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC3		0x01		/* MMC ver 3 */
#define CT_MMC4		0x02		/* MMC ver 4+ */
#define CT_MMC		0x03		/* MMC */
#define CT_SDC1		0x02		/* SDC ver 1 */
#define CT_SDC2		0x04		/* SDC ver 2+ */
#define CT_SDC		0x0C		/* SDC */
#define CT_BLOCK	0x10		/* Block addressing */


/* MMC/SD command */
#define CMD0	(0)			/* GO_IDLE_STATE */
#define CMD1	(1)			/* SEND_OP_COND (MMC) */
#define	ACMD41	(0x80+41)	/* SEND_OP_COND (SDC) */
#define CMD8	(8)			/* SEND_IF_COND */
#define CMD9	(9)			/* SEND_CSD */
#define CMD10	(10)		/* SEND_CID */
#define CMD12	(12)		/* STOP_TRANSMISSION */
#define ACMD13	(0x80+13)	/* SD_STATUS (SDC) */
#define CMD16	(16)		/* SET_BLOCKLEN */
#define CMD17	(17)		/* READ_SINGLE_BLOCK */
#define CMD18	(18)		/* READ_MULTIPLE_BLOCK */
#define CMD23	(23)		/* SET_BLOCK_COUNT (MMC) */
#define	ACMD23	(0x80+23)	/* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24	(24)		/* WRITE_BLOCK */
#define CMD25	(25)		/* WRITE_MULTIPLE_BLOCK */
#define CMD32	(32)		/* ERASE_ER_BLK_START */
#define CMD33	(33)		/* ERASE_ER_BLK_END */
#define CMD38	(38)		/* ERASE */
#define CMD55	(55)		/* APP_CMD */
#define CMD58	(58)		/* READ_OCR */


static volatile DSTATUS Stat = STA_NOINIT;	/* Physical drive status */
static volatile UINT Timer1, Timer2;		/* 1kHz decrement timer stopped at zero (disk_timerproc()) */

static BYTE CardType;	/* Card type flags */



/*-----------------------------------------------------------------------*/
/* SPI controls (Platform dependent)                                     */
/*-----------------------------------------------------------------------*/

/* Initialize MMC interface */
static void init_spi (void)
{
	spi_init( SPIDEV, 5 * 1000 * 1000 ); /* 5Mbps */
	gpio_set_function( DEF_SPI_TX_PIN, GPIO_FUNC_SPI );
	gpio_set_function( DEF_SPI_RX_PIN, GPIO_FUNC_SPI );
	gpio_set_function( DEF_SPI_SCK_PIN, GPIO_FUNC_SPI );

	/* CS# */
	gpio_init( DEF_SPI_CSN_PIN );
	gpio_set_dir( DEF_SPI_CSN_PIN, GPIO_OUT);

	CS_HIGH();            /* Set CS# high */

#if 0
	for (Timer1 = 10; Timer1; ) ;	/* 10ms */
#else
	Delay( 10 );
#endif
}


/* Exchange a byte */
static BYTE xchg_spi (
	BYTE dat	/* Data to send */
)
{
	uint8_t  src, dst;

	src = dat;
	spi_write_read_blocking( SPIDEV, &src, &dst, 1 );
	return  (BYTE)dst;
}


/* Receive multiple byte */
static void rcvr_spi_multi (
	BYTE *buff,		/* Pointer to data buffer */
	UINT btr		/* Number of bytes to receive (even number) */
)
{

	spi_read_blocking( SPIDEV, 0xff, buff, btr );
}


/* Send multiple byte */
static void xmit_spi_multi (
	const BYTE *buff,	/* Pointer to the data */
	UINT btx			/* Number of bytes to send (even number) */
)
{
	spi_write_blocking( SPIDEV, buff, btx );
}
```

####１）定義
修正プログラムの先頭部分は ラズパイPico用のヘッダファイルの読み込みやデバイスアクセスに必要な定数やマクロを定義しています。

####２）init_spi()
今回、SDカードのアクセスに SPIを用いるので、SPIデバイスの初期化と使用するピンの設定を行っています。
SPIの信号は以下のピンを用いています。

```
   GPIO 16 (pin 21) MISO/spi0_rx-> SDO
   GPIO 17 (pin 22) Chip select -> !CS
   GPIO 18 (pin 24) SCK/spi0_sclk -> SCK
   GPIO 19 (pin 25) MOSI/spi0_tx -> SDI
```

####３）xchg_spi()
SPIへのデータの送信と受信を行う関数です。
SDK内の spi_write_read_blocking()を使って書き込み、読み出しを行っています。

####４）rcvr_spi_multi()
複数データの受信を行う関数です。
SDK内の spi_read_blocking()を使って読み出しを行っています。

####５）xmit_spi_multi()
複数データの送信を行う関数です。
SDK内の spi_write_blocking()を使って書き込みを行っています。

###Fatfsのコンフィグレーション
Fatfsをどのように使うかのコンフィグレーションをFatfs本体内のファイル **ffconf.h**で設定することができます。
今回はファイルのタイムスタンプに必要な時間を取得する機能がなたいめ、ファイル更新日時をマクロ固定で取得することにしました。
コンフィグレーションを以下のように変更します。

``` ffconf.h
#define FF_FS_NORTC             0
```
　↓

``` ffconf.h
#define FF_FS_NORTC             1
```

##cmake用の定義ファイル
cmakeでビルドするための定義ファイルをsrcディレクトリ内に追加します。
**add_executable()**へビルド対象のCソースファイルを列記します。
diskio.c は今回使用しないため、記述に含みません。
main()関数を含むサンプルプログラムを記述するためのsd-test.cというファイル名を追記します。
ビルド後、 sd-testという名称のファイル群が出力されます。

``` CMakeLists.txt
add_executable(sd-test
	sd-test.c
	ff.c
	ffsystem.c
	ffunicode.c
	mmc_pico_spi.c
        )

# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(sd-test pico_stdlib  hardware_spi)

# create map/bin/hex file etc.
pico_add_extra_outputs(sd-test)

# add url via pico_set_program_url
example_auto_set_url(sd-test)
```

##サンプルプログラム
以下のサンプルプログラムを示します。
SDカード上の /TEST.TXT ファイルの内容を読み出し、/W_TEST1.TXT と W_TEST2.TXT を生成して読み出した内容を書き込みます。
プログラムの動作状況は UARTへ出力します。
最後に TEST.TXT の先頭１０バイトを UARTへ出力します。

``` sd-test.c
#include <stdio.h>
#include <string.h>
#include "ff.h"
#include "diskio.h"

#include "hardware/gpio.h"
#include "hardware/uart.h"

#define UART_ID uart1
#define BAUD_RATE 115200

#define UART_TX_PIN 20
#define UART_RX_PIN 21


char  sbuff[64];
#define DEF_FATBUFF  1024
char  buff_fattest[ DEF_FATBUFF ];


int  fat_test_init( void )
{
    DSTATUS  ret;
    int  result = 0;

    ret = disk_initialize( 0 );
    if( ret & STA_NOINIT ) {
        result = -1;
    }

    return  result;
}


int  fat_test_read( char *buff, int bsize )
{
    FRESULT  ret;
    FATFS  fs;
    FIL  fil;
    UINT  rdsz ;

    ret = f_mount( &fs, "", 0 );
    if( ret != FR_OK ) {
        return  -1;
    }
    ret = f_open( &fil, "test.txt", FA_READ );
    if( ret != FR_OK ) {
        return  -2;
    }

    ret = f_read( &fil, buff, (UINT)bsize, &rdsz );
    if( ret != FR_OK ) {
        return  -3;
    }

    f_close( &fil );

    return  (int)rdsz;
}


int  fat_test_write( char *filename, char *buff, int size )
{
    FRESULT  ret;
    FATFS  fs;
    FIL  fil;
    UINT  wsize ;

    ret = f_mount( &fs, "", 1 );
    if( ret != FR_OK ) {
        return  -1;
    }
    ret = f_open( &fil, filename, FA_WRITE|FA_CREATE_ALWAYS );
    if( ret != FR_OK ) {
        return  -2;
    }

    ret = f_write( &fil, buff, (UINT)size, &wsize );
    if( ret != FR_OK ) {
        return  -3;
    }

    f_close( &fil );

    return  (int)wsize;
}


int  main( void )
{
    int  ret;
    int  wsize;

    uart_init( UART_ID, BAUD_RATE );
    gpio_set_function( UART_TX_PIN, GPIO_FUNC_UART );
    gpio_set_function( UART_RX_PIN, GPIO_FUNC_UART );

    uart_puts( UART_ID, "\n----- START fatfs on pico -----\n" );

    ret = fat_test_init();
    if( ret != 0 ) {
        uart_puts( UART_ID, "fat_test_init()  ERROR!\n" );
    }
    ret = fat_test_read( buff_fattest, DEF_FATBUFF );
    sprintf( sbuff, "fat_test_read()  ret = %d\n", ret );
    uart_puts( UART_ID, sbuff );

    if( ret > 0 ) {
        wsize = ret;
        ret = fat_test_write( "w_test1.txt", buff_fattest, wsize );
        sprintf(sbuff, "fat_test_write()  ret = %d\n", ret );
        uart_puts( UART_ID, sbuff );

        ret = fat_test_write( "w_test2.txt", buff_fattest, wsize );
        sprintf(sbuff, "fat_test_write()  ret = %d\n", ret );
        uart_puts( UART_ID, sbuff );
    }
    buff_fattest[ 10 ] = 0xd;
    buff_fattest[ 11 ] = 0xa;
    buff_fattest[ 12 ] = 0x0;
    uart_puts( UART_ID, buff_fattest );

    return  0;
}
```

以下の内容の **test.txt** を使った場合の出力結果です。

```　test.txt
ABCDEFGHTJKLMNOPQRSTUVWXYZ
abcdefghtjklmnopqrstuvwxyz
0123456789
```

![Pico_FatFs_Pic2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/5b64d5f3-8295-5dd5-ec0d-d71ad517cc2e.jpeg)

##さいごに
ラズパイPicoを使った工作はいろいろネット上に公開され、SDカードを使った例もあるようです。
ただ、複数の機能を組み合わせた工作品として組み込まれているため、SDカードのみを使う場合には他の機能を削除するなどの作業が必要そうだったので、FatFs単一でSDカードを使うプログラムを公開してみました。

配線もSDカードモジュールだけなら6本、UARTを含めても9本を接続することで使えるので、一度、試してみてはいかがでしょうか。

－ 以上 －