RXマイコンを使って、FatFs を FreeRTOS で運用する

## はじめに
前回、RX マイコンに、FreeRTOS をポーティングする概要を紹介しました。

今度はもっと実用的に使ってみたいので、みんなお世話になっている ChaN氏 の FatFs をスレッドセーフで運用してみました。

FatFs のバージョンは、「ff13c」を使いました。
※比較的新しいバージョンで、色々細かく拡張されており、以前の物より格段に使いやすくなりました。

FatFs を FreeRTOS に対応させるには、「ffconf.h」に設定する他、ソースコードをほんの少しだけ修正する必要があります。

この改修は、RX マイコン以外でも使えるようになるものと思います。

※FatFs のファイル名は、UTF-8 を使い、コードページ９３２（Shift-JIS）でおこなっています。

## FatFs のソースコードを FreeRTOS に対応させる

まず、ff13c のソースコードをダウンロードして、展開します。

[FatFs 0.13c アーカイブ](http://elm-chan.org/fsw/ff/arc/ff13c.zip)

改修する部分は以下の二点になります。

 - ffconf.h の設定と修正
 - ffsystem.c の修正

初期段階では、WIN32 のスレッド対応になっているので、その部分を修正します。

### ffconf.h の修正
まず、「ffconf.h」の「FF_FS_REENTRANT」の設定を「０」から「１」にします。

```
/* #include <somertos.h>    // O/S definitions */
#define FF_FS_REENTRANT 1
#define FF_FS_TIMEOUT   1000
#define FF_SYNC_t       HANDLE
```

次に、FreeRTOS ヘッダーと、セマフォ関連ヘッダーのインクルードを行います。
また、同期オブジェクトハンドル名「FF_SYNC_t」の別名として、FreeRTOS セマフォハンドル名を設定します。

```
/* #include <somertos.h>    // O/S definitions */
#include "FreeRTOS.h"
#include "semphr.h"
#define FF_FS_REENTRANT 1
#define FF_FS_TIMEOUT   1000
// #define FF_SYNC_t        HANDLE
#define FF_SYNC_t       SemaphoreHandle_t
```

※「FF_FS_TIMEOUT」は、環境により調整する必要があります。
※上記の値は、FreeRTOS の標準設定では「1000ms」（１秒）となっているものと思います。
※複数ファイルを同時に扱うような場合、ＳＤカードシステムの能力により、この値では不足する事も考えられます。

### ffsystem.c の修正

初期段階で、WIN32 の実装になっているので、それをコメントアウトします。
FreeRTOS で使う場合、セマフォを使った同期部分は実装されており、コメントアウトしてあるだけなので、それを有効にします。
※合計４ヶ所あります。

```
int ff_cre_syncobj (    /* 1:Function succeeded, 0:Could not create the sync object */
    BYTE vol,           /* Corresponding volume (logical drive number) */
    FF_SYNC_t* sobj     /* Pointer to return the created sync object */
)
{
    /* Win32 */
//  *sobj = CreateMutex(NULL, FALSE, NULL);
//  return (int)(*sobj != INVALID_HANDLE_VALUE);

    /* FreeRTOS */
    *sobj = xSemaphoreCreateMutex();
    return (int)(*sobj != NULL);
```

```
int ff_del_syncobj (    /* 1:Function succeeded, 0:Could not delete due to an error */
    FF_SYNC_t sobj      /* Sync object tied to the logical drive to be deleted */
)
{
    /* Win32 */
//  return (int)CloseHandle(sobj);

    /* FreeRTOS */
    vSemaphoreDelete(sobj);
    return 1;
```

```
int ff_req_grant (  /* 1:Got a grant to access the volume, 0:Could not get a grant */
    FF_SYNC_t sobj  /* Sync object to wait */
)
{
    /* Win32 */
//  return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0);

    /* FreeRTOS */
    return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
```

```
void ff_rel_grant (
    FF_SYNC_t sobj  /* Sync object to be signaled */
)
{
    /* Win32 */
//  ReleaseMutex(sobj);

    /* FreeRTOS */
    xSemaphoreGive(sobj);
```

たったこれだけで終了です。

参考：
[FatFsモジュール アプリケーション・ノート](http://irtos.sourceforge.net/FAT32_ChaN/doc/ja/appnote.html)

---

## 試してみる！

では、実際に試してみましょう。

今回、簡単な実験として、ディレクトリーなどの表示を行う、コマンドライン入力の簡単なシェル、
それと、ファイルを間欠的に読み込み、読み込んだバイト数を表示するタスクを使いました。

※FatFs の関数は、C++ クラスでラップしてあり、それを使っています。
※全ソースコードはGitHubにありますので参照して下さい。

また、タスク間で、メッセージ（ファイル名）をやりとりしますが、FreeRTOS の機能は使わず、簡易な方法を使っています。

タスク設定は３つ起こしています。
スタックは、各タスクにより設定を変えています。

```
    {
        uint32_t stack_size = 2048;
        void* param = nullptr;
        uint32_t prio = 1;
        xTaskCreate(sdc_task_, "SD_MAN", stack_size, param, prio, nullptr);
    }
    {
        uint32_t stack_size = 256;
        void* param = nullptr;
        uint32_t prio = 1;
        xTaskCreate(led_task_, "LED", stack_size, param, prio, nullptr);
    }
    {
        uint32_t stack_size = 2048;
        void* param = nullptr;
        uint32_t prio = 1;
        xTaskCreate(scan_task_, "SCAN", stack_size, param, prio, nullptr);
    }

    vTaskStartScheduler();
```

「sdc_task_」はＳＤカードのメインシステム起動とコマンドラインシェルです。
※１６ｍｓ間隔でサービスしています、「sdc_dev_.service();」で、ＳＤカードのスイッチなどを監視して、マウント処理などを行っています。

```
    void sdc_task_(void *pvParameters)
    {
        sdc_dev_.start();

        cmd_.set_prompt("# ");

        while(1) {
            sdc_dev_.service();

            shell_();

            vTaskDelay(16 / portTICK_PERIOD_MS);
        }
    }
```

「led_task_」は単純で、LED を点滅させています。

```
    void led_task_(void *pvParameters)
    {
        while(1) {
            vTaskEnterCritical();
            LED::P = !LED::P();
            vTaskExitCritical();
            vTaskDelay(100 / portTICK_PERIOD_MS);
        }
    }
```

「scan_task_」が、受け取ったファイル名に従い、２５ミリ秒毎に、１０２４バイトづづ読み込み、読み込んだ総バイト数を表示します。

```
    void scan_task_(void *pvParameters)
    {
        while(1) {
            while(scan_t_.get_ == scan_t_.put_) {
                vTaskDelay(100 / portTICK_PERIOD_MS);
            }
            utils::file_io fio;
            if(!fio.open(scan_t_.filename_, "rb")) {
                utils::format("Can't open: '%s'\n") % scan_t_.filename_;
                scan_t_.get_++;
            } else {
                uint32_t pos = 0;
                uint8_t tmp[1024];
                while(pos < fio.get_file_size()) {
                    pos += fio.read(tmp, 1024);
                    vTaskDelay(25 / portTICK_PERIOD_MS);
                }
                utils::format("Scan Task: %u bytes\n") % pos;
                scan_t_.get_++;
                fio.close();
            }
        }
    }
```

実行の様子：

```
Start FreeRTOS FatFs sample for 'GR-KAEDE' 120[MHz]
# scan kfont16.bin
# dir
           Apr  4 2013 01:49 /Michael Jackson
           Mar 25 2017 21:14 /Re_package
     16384 May  4 2018 06:08  SoftShut.wad
     16384 May  4 2018 06:08  TFALL.WAD
           Oct 26 2017 23:57 /bbb
     16384 May  4 2018 06:08  TIMER.WAD
           Apr 13 2017 09:46 /seeda
     16384 May  4 2018 06:08  TOFF.WAD
     16384 May  4 2018 06:08  TON.WAD
     16384 May  4 2018 06:08  VSOFF.WAD
     16384 May  4 2018 06:08  VSON.WAD
           Nov  1 2017 07:25 /System Volume Information
           Jun  2 2018 01:05 /WAVs
           Mar 10 2019 07:38 /doriko
           Mar 10 2019 07:37 /inv_roms
           Mar 10 2019 07:37 /inv_wavs
    131088 Feb 16 2017 03:00  Dragon_Quest2_fix.nes
     65552 Feb 16 2017 02:56  DragonQuest_J_fix.nes
     24592 Jun 21 2000 07:56  Galaga_J.nes
     40976 Feb 28 1997 17:26  GALAXIAN.NES
     65680 Apr 26 1997 01:53  GRADIUS.NES
    249824 Jun 25 2018 18:14  kfont16.bin
     49785 Nov 14 2018 06:58  NoImage.jpg
     24592 Dec 24 1996 20:32  Pac-Man.nes
    131088 Sep 20 1999 10:59  Solstice_J.nes
     40976 Jul  8 2018 23:59  Super_mario_brothers.nes
     40976 Jul 17 1997 23:31  XEVIOUS.NES
     86072 Sep  5 2018 19:57  HRC_logo_s.bmp
    262160 Jul  1 2018 12:29  Super Mario USA (Japan).nes
           Jul 11 2016 07:47 /Rachmaninov_ Piano Concertos #2 & 3
           Apr  4 2013 01:52 /相川七瀬
           Apr  4 2013 01:51 /宮里久美
Total 32 files
# help
    pwd           list current path
    cd [path]     change current directory
    dir [path]    list current directory
    scan [file]   scan file (read 1024 bytes after wait 100ms)
# Scan Task: 249824 bytes
```

 - 「scan」コマンドで、「kfont16.bin」を要求。
 - 「dir」コマンドで、ルートのディレクトリーを表示
 - 「help」コマンドで、ヘルプ表示
 - scan_task_ が、ファイルサイズを表示

---

「sdc_task_」から「scan_task_」へファイル名を渡していますが、scan_t 構造体を使い、２つの整数を同期オブジェクトにしています。

```
    struct scan_t {
        char filename_[64];
        volatile uint32_t put_;
        volatile uint32_t get_;
        scan_t() : filename_{ 0 }, put_(0), get_(0) { }
    };
```

「put_」、「get_」が「volatile」になっている必要があります、これは、コンパイラが最適化によって、同期機構が「不必要」とされて「消さない」ようにする工夫です。

「sdc_task_」側は、以下のように、「put_、get_」が同じ値である事を確認してから、ファイル名をコピーします。
次に、「put_」をインクリメントして、ファイル名が有効になった事を「scan_task_」に伝えます。

```
    if(scan_t_.get_ != scan_t_.put_) {
        utils::format("Scan task is busy !\n");
    } else {
        cmd_.get_word(1, scan_t_.filename_, sizeof(scan_t_.filename_));
        scan_t_.put_++;
    }
```

「scan_task_」側は、「put_、get_」が異なった事を確認して、ファイル名を取り出しています。
※「put_、get_」の確認は、簡易的な同期なので、１００ミリ秒毎に行っています。
※１００ミリ秒を小さくすれば、起動が速くなりますが、「比較」作業の回数が増えます。
※１ミリ秒にしても、秒１０００回の比較なので、実際には大した事はありませんが、本来「キュー」などを使ってファイル名を渡すなどの実装が望まれます。

本来は、キューや、同期オブジェクトで、タスク間の通信を行うのが、「筋」ですが、上記のような簡易的なものでも、十分な場合もあります。
この簡易的な仕組みは、構造が簡単な事と汎用性が大きい点です。

---

## ＳＤカード、ハードウェアーを使う為の設定など

自分のフレームワークでは、ＳＤカードを扱うハードウェアーは主にＳＰＩで通信する方法を使います。
ＳＰＩ通信では、ＲＸマイコンのＳＰＩハードウェアー（ＲＳＰＩ）を使う方法と、ポートをＳＰＩのように操作するソフトＳＰＩを使えます。
※ソフトＳＰＩは簡易的なもので、本来のパフォーマンスが発揮できません。

ＳＤカードを扱うには、以下の信号が必要です。

 - ＭＩＳＯ
 - ＭＯＳＩ
 - ＳＰＣＫ（出力）
 - ＳＤカード選択（出力）
 - ＳＤカード電源（出力）
 - ＳＤカード検出（入力）

```
#if 0
    // Soft SDC 用　SPI 定義（SPI）
    typedef device::PORT<device::PORTC, device::bitpos::B7> MISO;
    typedef device::PORT<device::PORTC, device::bitpos::B6> MOSI;
    typedef device::PORT<device::PORTC, device::bitpos::B5> SPCK;
    typedef device::spi_io2<MISO, MOSI, SPCK> SDC_SPI;
#else
    ///< Hard SPI 定義
    typedef device::rspi_io<device::RSPI> SDC_SPI;
#endif

    ///< SDC インターフェースの定義
    typedef device::PORT<device::PORTC, device::bitpos::B4> SDC_SELECT; ///< SD カード選択信号
    typedef device::NULL_PORT  SDC_POWER;   ///< SD カード電源制御（制御なし、常にＯＮ）
    typedef device::PORT<device::PORTB, device::bitpos::B7> SDC_DETECT; ///< SD カード検出
```

※ＳＤカード電源制御は、「NULL_PORT」と定義すれば、制御を行いません。

ＳＤカードハードウェアーの実態は、以下のように定義します。

```
    // SDC ハードウェア
    SDC_SPI     sdc_spi_;

    ///< FatFs MMC ドライバ（ハードウェアインスタンス、ハードウェア最大速度）
    typedef fatfs::mmc_io<SDC_SPI, SDC_SELECT, SDC_POWER, SDC_DETECT> SDC_DEV;
    SDC_DEV     sdc_dev_(sdc_spi_, 35000000);
```

※上記の「35000000」は SPI の最大クロック周期となっていますが、ソフトＳＰＩの場合は無効な値です。

FatFs がハードウェアーを操作する部分は、「mmc_io.hpp」として分離してあり、メイン部分に、コンテキストを置いて、C 言語からリンク出来るようにしてあります。

```
extern "C" {

    DSTATUS disk_initialize(BYTE drv) {
        return sdc_dev_.disk_initialize(drv);
    }

    DSTATUS disk_status(BYTE drv) {
        return sdc_dev_.disk_status(drv);
    }

    DRESULT disk_read(BYTE drv, BYTE* buff, DWORD sector, UINT count) {
        return sdc_dev_.disk_read(drv, buff, sector, count);
    }

    DRESULT disk_write(BYTE drv, const BYTE* buff, DWORD sector, UINT count) {
        return sdc_dev_.disk_write(drv, buff, sector, count);
    }

    DRESULT disk_ioctl(BYTE drv, BYTE ctrl, void* buff) {
        return sdc_dev_.disk_ioctl(drv, ctrl, buff);
    }

    DWORD get_fattime(void) {
        time_t t = 0;
        rtc_.get_time(t);
        return utils::str::get_fattime(t);
    }
};
```

## まとめ

FatFs は、組み込みでは、標準的なファイルシステムの実装で、世界中の人が使っています。
少しづつ改良を続けており、より良く進化しています。
この場を借りて、この素晴らしいプロジェクトを続けている ChaN氏、ならびに、関わっている方々に御礼申し上げます。

GR-KAEDE、DIY のRX64M、RX65N Envition Kit (RTK5) などで動作を確認しました。

ハードウェア仕様：

|ハード|SPI 仕様|CPUクロック|RTC|メモリーモデル|
|-----|-----|-----|-----|-----|
|GR-KAEDE|RSPI|120MHz|○|heap_3.c|
|DIY RX64M|Soft SPI|120MHz|○|heap_3.c|
|RX65N Envision kit| Soft SPI|120MHz|△|heap_3.c|

※GR-KAEDE は、一部、ＳＤカード用 RSPI ポートと、ＪＴＡＧポートが共有になっており、プルダウン抵抗が影響する為改修が必要。
詳しくはソースコードに記載があります。
※RX65N Envision kit の ＳＤカードインターフェースは、ＳＤＨＩ接続ですが、ドライバーの実装が完成していないのでソフトＳＰＩを使っています。
※RX65N Envision kit の RTC は、クリスタルが接続されていない為、利用出来ません。

ソースコードは [GitHub/Master](https://github.com/hirakuni45/RX/tree/master/FreeRTOS_FatFs) にプッシュ済み。


---