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