Help us understand the problem. What is going on with this article?

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

はじめに

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

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

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

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

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

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

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

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

FatFs 0.13c アーカイブ

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

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

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

ffconf.h の修正

まず、「ffconf.h」の「FF_FS_REENTRANT」の設定を「0」から「1」にします。

/* #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」(1秒)となっているものと思います。
※複数ファイルを同時に扱うような場合、SDカードシステムの能力により、この値では不足する事も考えられます。

ffsystem.c の修正

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

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モジュール アプリケーション・ノート


試してみる!

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

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

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

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

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

    {
        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_」はSDカードのメインシステム起動とコマンドラインシェルです。
※16ms間隔でサービスしています、「sdc_dev_.service();」で、SDカードのスイッチなどを監視して、マウント処理などを行っています。

    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_」が、受け取ったファイル名に従い、25ミリ秒毎に、1024バイトづづ読み込み、読み込んだ総バイト数を表示します。

    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 構造体を使い、2つの整数を同期オブジェクトにしています。

    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_」の確認は、簡易的な同期なので、100ミリ秒毎に行っています。
※100ミリ秒を小さくすれば、起動が速くなりますが、「比較」作業の回数が増えます。
※1ミリ秒にしても、秒1000回の比較なので、実際には大した事はありませんが、本来「キュー」などを使ってファイル名を渡すなどの実装が望まれます。

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


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

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

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

  • MISO
  • MOSI
  • SPCK(出力)
  • SDカード選択(出力)
  • SDカード電源(出力)
  • SDカード検出(入力)
#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 カード電源制御(制御なし、常にON)
    typedef device::PORT<device::PORTB, device::bitpos::B7> SDC_DETECT; ///< SD カード検出

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

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

    // 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 の最大クロック周期となっていますが、ソフト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 は、一部、SDカード用 RSPI ポートと、JTAGポートが共有になっており、プルダウン抵抗が影響する為改修が必要。
詳しくはソースコードに記載があります。
※RX65N Envision kit の SDカードインターフェースは、SDHI接続ですが、ドライバーの実装が完成していないのでソフトSPIを使っています。
※RX65N Envision kit の RTC は、クリスタルが接続されていない為、利用出来ません。

ソースコードは GitHub/Master にプッシュ済み。


hira_kuni_45
ハード、ソフト、金属加工、内燃機関、色々やってます。
http://www.rvf-rc45.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした