Cypress FM4でFatfsを使う

##はじめに

**Cortex-M4**を使ったマイコンシステムのソフトウェアを開発するにあたり、対象ボードが使えるようになるまでの間、別の市販マイコンボードを使って作業を行うこととしました。
また、開発するソフトウェアの評価に大きなデータを扱う必要が出てきたため、データをメモリカードから読み書きでるようにしようと考えました。
このため市販マイコンボードで**Fatfs**を使えるようにしました。

**Cortex-M4**でRAMやFlashメモリをそれなりに搭載しているマイコンボードとして、Cypress（現在は infineon+Cypressか）の**FM4**を搭載した **FM4-176L-S6E2CC-ETH** が手元にあったことから、このボードで**SDカード**（今回はマクロSDカードを利用）と**Fatfs**が使えるようにしました。
![pic1_fm4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/503f782f-86db-d153-aab0-685056f93ca6.jpeg)
![pic2_fm4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/54634b04-4f80-c542-fd89-5dc281e0f4dc.jpeg)

・FM4-176L-S6E2CC-ETH
https://japan.cypress.com/documentation/development-kitsboards/sk-fm4-176l-s6e2cc-fm4-family-quick-start-guide

・FM4 32-bit Arm Cortex-M4 Microcontroller (MCU) Families
https://japan.cypress.com/products/fm4-32-bit-arm-cortex-m4-microcontroller-mcu-families

・FM4 S6E2C-Series High Performance Arm Cortex-M4 Microcontroller (MCU) Family
https://japan.cypress.com/products/fm4-s6e2c-series-high-performance-arm-cortex-m4-microcontroller-mcu-family

##ハードウェア
今回は制御や配線が容易で、過去に別MCUやマイコンボードで利用実績のある、**SPI**で**SDカード**を接続するとにしました。
SDカードソケットは amazon で購入した以下のモジュールを使用し、マイコンボード **FM4-176L-S6E2CC-ETH** の**Arduino Shield I/F**のピンを使て接続します。

・Micro SD TFカードメモリシールドモジュール
https://www.amazon.co.jp/gp/product/B010GXAFFU/ref=ppx_od_dt_b_asin_image_s00?ie=UTF8&psc=1
![pic3_sd_con.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/2b8bcb2f-0044-918b-f17f-ef3dc2e60da6.jpeg)

・回路図
![pic4_schem.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/16001093-c65a-bf8e-6aae-ae8adf1eba8b.jpeg)

・実際の接続状態
![pic5_fm4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/8a10b674-3632-57b7-6686-38f6811d1eb0.jpeg)

##流用元ソフトウェア

流用元となるFatfsのソースコードを入手します。
使用するのは Fatfs本体のコードと、デバイスアクセス用のサンプルコードの２つです。
それぞれの場所からダウンロードします。

###Fatfs本体のコード

Fatfs本体のコードは以下のURLのページからダウンロードします。

http://elm-chan.org/fsw/ff/00index_e.html

この記事執筆時点では **0.14b** というバージョンが最新のようです。
以下の**赤矢印** 「Download: FatFs R0.14b」をクリックすることでダウンロードすることができます。
ダウンロードファイルは **ff14b.zip** になります。
![pic6_download.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/a52f17d7-d69f-aa12-f067-989d53807261.jpeg)

###サンプルコード

Fatfsを使ってマイコンボードからSDカードへアクセスするには、通信デバイスの制御プログラムが必要になります。
今回は通信デバイスとして SPIを使いSDカードへのアクセスコマンドやデータの受信を行います。
通信デバイスの制御コードを独自に作り込むこともさほど難しくはないと思いますが、別MCU用ですがSPIを使った制御プログラムがサンプルプログラムとして提供されているので、今回はサンプルプログラムを流用することにしました。

サンプルコードはFatfs本体と同じページからダウンロードできます。
以下の**赤矢印** 「Download: FatFs sample projects for various platforms」をクリックすることでダウンロードできます。
ダウンロードファイルは **ffsample.zip** になります。
![pic7_ffsample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/bb196c07-bc0f-744b-39f4-33109ba15cfe.jpeg)

##FM4へのFatfsの対応

FatfsをFM4搭載のFM4-176L-S6E2CC-ETHでアクセスするための対応方法を記します。

###FM4用定義ファイルの取得
以下の**赤矢印** 「Download S6E2CC series Template Project Version 1.0」をクリックすることでダウンロードできます。
ただし、ダウンロードには infineon か Cypressのレジストレーションが必要になるので、必要に応じてレジストレーションしてください。
（各チップベンダもですが、ちょっと面倒なところです）
ダウンロードファイルは **s6e2cc-series.zip** になります。

https://japan.cypress.com/documentation/development-kitsboards/sk-fm4-176l-s6e2cc-fm4-family-quick-start-guide
![pic8_fm4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/925a1417-5f69-d218-f00e-a195dc70588c.jpeg)


ダウンロードしたzipファイル内の以下のディレクトリのヘッダファイル群を使うとMCU搭載デバイスのレジスタへのアクセスを容易に行うことができます。
あとで紹介するプログラムのアクセスに必要となるので、ビルド時にインクルードパスへ登録しておいてください。

```
\s6e2cc-series\s6e2cc_template-v13\common
 s6e2cc.h
 system_s6e2cc.h
```

```
\s6e2cc-series\s6e2cc_template-v13\example\source
 interrupt_type.h
 mcu.h
```

###CMSISファイルの取得

前項でダウンロードしたFM4用のヘッダファイルを使い Cortex-Mのマイコンリソースへアクセスするための CMSISの定義ファイルが必要になります。
以下のURLからダウンロードします。

https://github.com/ARM-software/CMSIS_5

以下の**赤矢印** 「Download ZIP」をクリックすることでダウンロードできます。
ダウンロードファイルは **CMSIS_5-develop.zip** になります。
![pic9_cmsis.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/dae27cb1-f0e5-0169-cb08-c48b4f622d71.jpeg)


ダウンロードしたzipファイル内の以下のディレクトリのヘッダファイル群を s6e2cc.h からインクルードして使われるため、今回のFatfsのビルド時にインクルードパスへ登録しておいてください。

```
\CMSIS_5-develop\CMSIS\Core\Include
```

###Fatfsコードの改造、作成

Fatfs本体のソースコードを取り出します。
ff14b.zipファイル内から sourceディレクトリを取り出し、PC内の任意のディレクトリへコピーします。
以下のファイルが取得できると思います。

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

通信デバイスを制御するため、ffsample.zipファイル内から**\stm32\mmc_stm32f1_spi.c**ファイルを取り出し、**mmc_fm4_spi.c**というファイル名に置き替えます。
ファイルは前述のFatfs本体のソースディレクトリ**source** へ配置してください。
なお、**mmc_fm4_spi.c**を使う場合、**diskio.c**は不要となります。ビルド対象外にするか、削除してしまって構いません。

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

``` mmc_fm4_spi.c
#include "mcu.h"

#define  DEF_FAST_HZ	 8000000
#define  DEF_SLOW_HZ	  400000
#define  DEF_BUSCLK_HZ	96000000

#define  MFS		FM4_MFS3

#define FCLK_FAST()	{ MFS->BGR = DEF_BUSCLK_HZ / DEF_FAST_HZ - 1; }
#define FCLK_SLOW()	{ MFS->BGR = DEF_BUSCLK_HZ / DEF_SLOW_HZ - 1; }

#define CS_HIGH()	{ FM4_GPIO->PDORF |=  (0x1<<0); /* HIGH */ }
#define CS_LOW()	{ FM4_GPIO->PDORF &= ~(0x1<<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) */

void  WaitMsec( int ms );



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

   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)
{
	/*
	  P40:SIN3_1
	  P41:SOT3_1
	  P42:SCK3_1
	  PF0:GPIO : /CS
	*/
	/* PFR  0:GPIO  1:PERI */
	FM4_GPIO->PFR4 |=  0x0007;
	FM4_GPIO->PFRF &= ~0x0001;

	/* EPFR07 0/1:_0 2:_1 3:_2 */
	FM4_GPIO->EPFR07 &= ~((0x3<<22)|(0x3<<24)|(0x3<<26));
	FM4_GPIO->EPFR07 |=  ((0x2<<22)|(0x2<<24)|(0x2<<26));

	/* PF0 :GPIO/pull-up/OUT */
	FM4_GPIO->PCRF  |=  (0x1<<0);
	FM4_GPIO->DDRF  |=  (0x1<<0);
	FM4_GPIO->PDORF |=  (0x1<<0); /* HIGH */
	// FM4_GPIO->PDORF &= ~(0x1<<0); /* LOW */

	MFS->SCR    = 0x80;   /* UPCL:reset CSIO */
	MFS->FBYTE2 = 0x01;   /* RX FIFO data num */
	MFS->FCR    = 0x000f; /* Enable Tx/Rx FIFO */
	MFS->ESCR   = 0x00;
	MFS->SMR    = 0x4f;   /* MD:010 */
	MFS->SCR    = 0x23;   /* SPI, RXE, TXE */

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

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


/* Exchange a byte */
static BYTE xchg_spi (
	BYTE dat	/* Data to send */
)
{
	MFS->TDR = dat;			/* Start an SPI transaction */
	while ( !(MFS->SSR & 0x04) ) ;	/* Wait for end of the transaction */
	return (BYTE)MFS->RDR;		/* Return received byte */
}


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

	for (n = 0; n < 14; n++)
		MFS->TDR = 0xFF;
	btr -= 14;
	while (btr) {
		while (!(MFS->SSR & 0x04)) ;
		*buff++ = MFS->RDR;
		MFS->TDR = 0xFF;
		btr--;
	}
	for (n = 0; n < 14; n++) {
		while (!(MFS->SSR & 0x04)) ;
		*buff++ = MFS->RDR;
	}
}


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

	for (n = 0; n < 14; n++)
		MFS->TDR = *buff++;
	btx -= 14;
	while (btx) {
		while (!(MFS->SSR & 0x04)) ;
		MFS->RDR;
		MFS->TDR = *buff++;
		btx--;
	}
	for (n = 0; n < 14; n++) {
		while (!(MFS->SSR & 0x04)) ;
		MFS->RDR;
	}
}
```

この中のプログラムの説明をします。

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

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

```
  P40:SIN3_1
  P41:SOT3_1
  P42:SCK3_1
  PF0:GPIO : /CS
```

なお、init_spi()内でデバイス初期化の待ち時間としてWaitMsec()という関数を呼び出しています。
使用するシステムに合ったミリ秒単位の待ち処理を用意してください。

```
WaitMsec( 10 );
```

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

####４）rcvr_spi_multi()
複数データの受信を行う関数です。
FM4のSPIデバイスでは、データを送信することで通信クロックが出力されるので、**0xFF**データを送信後、データレジスタから受信データの読み出しを行っています。

####５）xmit_spi_multi()
複数データの送信を行う関数です。
FM4のSPIデバイスでは、データを送信すると受信も同時に行ってしまうため、データ送信後に空の読み出しも行っています。

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

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

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


##サクセスサンプル

最後にFM4対応した Fatfsでのアクセス確認用のサンプルプログラムを紹介します。

###初期化

```c++
#include "ff.h"
#include "diskio.h"
#include <string.h>


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

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

    return  result;
}
```

###読み出しサンプル
SDカード上にデータの入った（適当なテキストデータが入った）**test.txt**というファイルがある想定です。

```c++
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;
}
```


###書き込みサンプル

```c++
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;
}
```

－以上 －