はじめに
前回、RX マイコンに、FreeRTOS をポーティングする概要を紹介しました。
今度はもっと実用的に使ってみたいので、みんなお世話になっている ChaN氏 の FatFs をスレッドセーフで運用してみました。
FatFs のバージョンは、「ff13c」を使いました。
※比較的新しいバージョンで、色々細かく拡張されており、以前の物より格段に使いやすくなりました。
FatFs を FreeRTOS に対応させるには、「ffconf.h」に設定する他、ソースコードをほんの少しだけ修正する必要があります。
この改修は、RX マイコン以外でも使えるようになるものと思います。
※FatFs のファイル名は、UTF-8 を使い、コードページ932(Shift-JIS)でおこなっています。
FatFs のソースコードを FreeRTOS に対応させる
まず、ff13c のソースコードをダウンロードして、展開します。
改修する部分は以下の二点になります。
- 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 の関数は、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 にプッシュ済み。