search
LoginSignup
3
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

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 にプッシュ済み。


Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3
Help us understand the problem. What are the problem?