FATシステムを使ってSDカードを使えるようにする (H8/3048fone)
(1)日立評価版コンパイラVer2使用
(2)HEW開発環境                

Nov2011
KOSAKA TNCT

1.はじめに

H8/3048fone(mode7:内部ROMRAM利用1Mbyteメモリモード)に SDカードソケットをつけて,FATシステムを導入し,ファイルの読み書きができるようにしている。

FATシステムを0から作るのは大変なので,「FatFs(小規模な組み込みシステム向けの汎用FATファイルシステム・モジュール)」を用いる。このモジュールはChaN氏作成のフリー・ソフトウェアであり,使いやすく作成されているものである。SDカードの使い方の説明もChaN氏のサイトにあるので,そちらで学んでほしい。

このページで公開しているFATシステムでできることは次の通りである。
(1)ファイルの読み書き(fwrite, fprintf, fread)
(2)ディレクトリの作成,カレントディレクトリの移動,ディレクトリの消去,カレントディレクトリの取得
(3)ディレクトリ内のファイル名の巡回取得
(4)1バイト文字限定のロングファイル名の使用
対応しているのはMMC,SDC,SDHCのカードである。

また,このシステムでは,H8/3048fone25MHzにて転送速度 6.25Mbps(純粋に転送する速度,システムクロック4周期で1bit転送)を実現している。(H8のSCI0のクロック同期通 信とDMAを利用,このアイディアは専攻科生田畑さん)

さらに,ChaN氏の示す下位レイヤ(サンプルプログラムによる)を2つに分け,SDカードIOレイヤとハードウェアIOレイヤに分けている。ハードウェアIOレイヤを差し替えると,別な構成のマイクロコンピュータへの移植が簡単にできるようになっている。

2.3つのレイヤよりなるFATシステム

このFATシステムは次に示す3つのレイヤよりできている。

レイヤ 説明
FAT Moduleレイヤ ChaN氏の作成した論理的なFATシステムレイヤで,SDカード限定ではなく,複数の用途に使えるモジュールである。オリジナルからコンフィグファイル(ffconf.h)を一部変更しただけで,そのまま利用している。
SD Card IOレイヤ ChaN氏の作成したサンプルを分割・チューニングして作成した論理的なSDカードIOモジュールで,CPUの構造から独立したレイヤである。(CPUのIOを直接操作しないレイヤ)
HardwareIOレイヤ CPUのIOレジスタに対する操作を行うレイヤである。CPUが変更されたり,あるいは使用するIOセットが変更されたりした場合は,このレイヤだけ変更すればよいようになっている。
3つの通信方法から用途に応じて選べるようになっている。

 

3.SDカードソケット

SDカードソケットはDM1B-DSF-PEJ(秋月電子で入手)を用いており,配線は次の通りとした。SCIを使った通信の場合と,ソフトウェア駆動でポートを使った通信の場合で配線が異なる。本来はCPUからSDCに 向けた信号レベルを5V→3.3V変換しなければならないが,信号線を直結してテストしている。(本番ではレベル変換して使うべき) (この回路の元は専攻科生田畑さんの製作)

SCIを使った通信の場合の配線(polling方式,DMA方式)

ソフトウェア駆動でポート4を使った通信の場合の配線


参考 AKI- H8-3048foneを使う場合はCPUカードに手なおしが必要

 

4.サンプルプログラム

サンプルプログラム(日立評価版コンパイラVer2用)のダウンロード

サンプルプログラム(HEW用)のダウンロード

使用しているリソース

・ITU1
・SCI0(SCIを用いた転送方式(polling方式,DMA方式)の時

・Port4

FATシステムオプション設定

ロングファイルネーム対応(ただし,日本語など2バイト文字は使えない)

 

5.各レイヤとグローバル関数

各レイヤモジュールが提供する関数は次のとおりである。

レイヤ ファイル レイヤモジュールが提供する
グローバル関数
レイヤモジュールが使用する
下位モジュールレイヤの関数
FATモジュールレイヤ

ff.c
ff.h
ffconf.h
 (FATモジュール
  設定ファイル)
ccsbcs.c
 (ロングファイル
  名対応関数)

f_mount - ワークエリアの登録・削除

f_open - ファイルのオープン・作成
f_close - ファイルのクローズ
f_read - ファイルの読み出し
f_write - ファイルの書き込み
f_lseek - リード/ライト・ポインタの
     移動,ファイルの拡張
f_truncate - ファイル・サイズの
       切り詰め
f_sync - キャッシュされたデータの
     フラッシュ
f_opendir - ディレクトリのオープン
f_readdir - ディレクトリの読み出し
f_getfree - ボリューム空き領域の取得
f_stat - ファイル・ステータスの取得
f_mkdir - ディレクトリの作成
f_unlink - ファイル/ディレクトリの削除
f_chmod - ファイル/ディレクトリの
     属性の変更
f_utime - ファイル/ディレクトリの
     タイムスタンプの変更
f_rename - ファイル/ディレクトリの
     名前変更・移動
f_chdir - カレント・ディレクトリの変更
f_chdrive - カレント・ドライブの変更
f_getcwd - カレント・ディレクトリの取得
f_forward - ファイル・データを
      ストリーム関数に転送
f_mkfs - 論理ドライブのフォーマット
f_fdisk - 物理ドライブの分割
 (関数f_fdiskはサポートされていない。
  必要ならばffconf.hを設定変更する)
f_gets - 文字列の読み出し
f_putc - 文字の書き込み
f_puts - 文字列の書き込み
f_printf - 書式化文字列の書き込み
f_tell - 現在のリード/ライト・
    ポインタの取得
f_eof - ファイル終端の有無の取得
f_size - ファイル・サイズの取得
f_error - ファイルのエラーの有無の取得

詳細はこちら

disk_initialize - ドライブの
                  初期化
disk_status - ドライブの
              状態取得
disk_read - データの読み出し
disk_write - データの書き込み
disk_ioctl - その他の
             ドライブ制御



get_fattime - 日付・時刻の
              取得
(ファイルのタイムスタンプ用
としてファイル書き込みに使わ
れる。これは別に用意しておく
必要がある。この関数は存在し
なければならない。
GPSや電波時計などで時刻がわか
る場合はそれを使えばよい。
現在時刻がわからない場合は
0を返すとタイムスタンプは
一定の無意味な値となる。)

SDカードIOモジュールレイヤ

diskio.c
diskio.h

disk_initialize - ドライブの初期化

disk_status - ドライブの状態取得
disk_read - データの読み出し
disk_write - データの書き込み
disk_ioctl - その他のドライブ制御

詳細はこちら

SPI_putData - SDCに複数
              バイト出力
SPI_getData - SDCから複数
              バイト入力
SPI_assertCS - CS信号をLに
SPI_negateCS - CS信号をHに
SPI_setDownCounter - タイム
  アウト用ダウンカウンタ
  のセット
SPI_getDownCounter - ダウン
  カウンタの読み出し

ハードウェアIOモジュールレイヤ

intprg.c
(割り込み関数)
spi.c
spi.h

initSPI - SCI0を初期化
initSPI_CS - CS出力ポートを初期化

SPI_putData - SDCに複数バイト出力
SPI_getData - SDCから複数バイト入力

SPI_assertCS - CS信号をLに
SPI_negateCS - CS信号をHに

SPI_setDownCounter - タイム
  アウト用ダウンカウンタ
  のセット
SPI_getDownCounter - ダウン
  カウンタの読み出し

SPI_TimerWork - 10msecごとに
        呼び出される作業関数
 (タイマ割り込みで呼び出されるよう
  にしておく必要がある)

 

 

6.ハードウェアIOモジュールレイヤについて

SDCに複数バイト入出力する方式は,
(1)ソフトウェアポート駆動方式 (転送速度 100kbps[低速モード], 714kbps[高速モード])
(2)ポーリング方式SCI      (転送速度 125kbps[低速モード],6250kbps[高速モード])
(3)DMA方式SCI         (転送速度 125kbps[低速モード],6250kbps[高速モード])
の3種類を準備している。SDメモリの操作に専念できるアプリケーションの場 合はDMA方式SCIで高速にデータ転送を行うと良い。そうでない場合(タイマ割り込み作業が優先で,そちらが忙しい場合など)では ポーリング方式SCIでデータ転送を行うと良い。どちらもデータ転送速度は同じだが,ポーリング方式ではCPU内のデータハンドリン グに時間がかかる。また,SCIがすでに他用途で使われていて使えない場合は,ソフトウェアポート駆動方式を用いるとよい。
低速モードはSDカードとの通信を初期化するときに用いる.初期化(SDカードのフォーマットではない)が終ったら,高速モードが使 える

関数の詳細

void initSPI(int mode);
  SCI0を初期化 mode 0:低速モード 1:高速モード 各baudrate値はspi.cで設定
  転送速度を変更する目的で,mainから複数回呼び出されても構わない。

void initSPI_CS(void);
  CS信号ポートの初期化
int SPI_putData(unsigned char *data, int num);
  ポーリング方式SCIまたはDMA方式SCI,ソフトウェア駆動方式で
  *dataからnumバイト出力
  どの方式を用いるかは,spi.h中の設定で定める。
int SPI_getData(unsigned char *data, int num);
  ポーリング方式SCIまたはDMA方式SCI,ソフトウェア駆動方式で
  *dataにnumバイト入力
  バッファサイズは呼び出し側の責任で確保しておく。
  どの方式を用いるかは,spi.h中の設定で定める。
void SPI_assertCS();
  CS信号をLにする
void SPI_negateCS();
  CS信号をHにする
void SPI_setDownCounter(unsigned char chan, unsigned char csec);
  タイムアウト用ダウンカウンタ値csecのセット(10msec単位)
  このダウンカウンタは2つあり,chan=0,1で選択する
unsigned char SPI_getDownCounter(unsigned char chan);
  タイムアウト用ダウンカウンタ値の読み出し(10msec単位)
  このダウンカウンタは2つあり,chan=0,1で選 択する

7.サンプルプログラムの動作について


SDCに複数バイト入出力する方式が3種類(ソフトウェアポート駆動方式,ポーリング方式SCIDMA方式SCI
あるが,どれを使うかは「spi.h」内で設定できる。

サンプルプログラムのmain()を含むfile_SDC.cには7
つの関数main()が記述されている。
それぞれmain0からmain6までの名前で「file_SDC.c」の先頭で条件コンパイルにより,1つだけ実行される。

(1)main0

信号をオシロスコープで見ながら,主にハードウェアIOモジュールの関数をループで実行する
実行の様子のスコープ画面の参照(SDカード未挿入で観察,CPUのRXD(カードの
DO)端子は常にhigh)

(2)main1

SDカードIOモジュール内の「disk_initialize - ドライブの初期化」をループで実行する。
PCのモニタアプリ(teratermなど)に表示する。うまく動作すると「res=0」が表示される。

(3)main2

SDカードIOモジュール内の「disk_ioctl - その他のドライブ制御」でSDカード用法を読み出して,PCのモニタアプリ(teratermなど)に表示する。

実行例 (KINGMAX SDHC 4GB の例)

level 2
res=0 disk_initialize
res=0 CTRL_SYNC
res=0 size_sect=512
res=0 num_sect=7909376
res=0 type_flag=12
res=0 CSD
40 0e b0 32 5b 59 00 00 1e 2b 7f 80 0a 40 00 f3
res=0 CID
13 4b 47 53 44 30 34 47 00 00 c8 02 66 00 9c f1 .KGSD04G....f...
completed

(4)main3

SDカードIOモジュール内の「disk_read - データの読み出し」でSDカードのセクタ読み出しを行い,PCのモニタアプリ(teratermなど)に表示する。

実行例 (KINGMAX SDHC 4GB の例)

level 3 read sector
res=0 disk_initialize
res=0 disk_read
sector number=0 [0]
0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
01a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
01b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
01c0: 03 01 0b 69 e9 d4 00 20 00 00 00 90 78 00 00 00 ...i........x...
01d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
01e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
01f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa ..............U.
completed

(5)main4

FATモジュールを使って,ファイル"filetest.txt"をSDカードのルートディレクトリに書き込み,
次に読み出して,PCのモニタアプリ(teratermなど)に表示する。

実行例

level 4 read file
res=0 disk_initialize
res=0 f_open
res=0 f_close
res=0 f_open
Hello world.
This is the line 1.
This is the line 2.
This is the line 3.
This is the last line.
res=0 f_close
res=0 f_opendir
----A 1980/00/00 00:00        96  filetest.txt
----A 2011/11/15 15:53        14  hello0.txt
   2 File(s),       110 bytes
   0 Dir(s),    3946400K bytes free
completed

(6)main5

FATモジュールを使って,以下のディレクトリ操作を行う。
ルートディレクトリにディレクトり「testdir」がなければ,
ディレクトり「testdir」を作り,その中にファイル「filetest.txt」を作る。
もし,ディレクトり「testdir」があれば,その中のファイル「filetest.txt」を
表示し,ファイルを消去し,ディレクトり「testdir」も消去する。
この状況をPCのモニタアプリ(teratermなど)に表示する。

実行例

level 5 make dir
res=0 disk_initialize
res=0 f_getcwd
current directory name : [0:/]
res=0 f_opendir
directory name : []
----A 1980/00/00 00:00        96  filetest.txt
----A 2011/11/15 15:53        14  hello0.txt
   2 File(s),       110 bytes
   0 Dir(s),    3946400K bytes free
res=5 f_opendir testdir
res=0 f_mkdir testdir
res=0 f_chdir testdir
res=0 f_open filetest.txt
res=0 f_close
res=0 f_getcwd
current directory name : [0:/TESTDIR]
res=0 f_opendir
directory name : []
D---- 1980/00/00 00:00         0  .
D---- 1980/00/00 00:00         0  ..
----A 1980/00/00 00:00        96  filetest.txt
   1 File(s),        96 bytes
   0 Dir(s),    3946336K bytes free
res=0 f_chdir ..
res=0 f_getcwd
current directory name : [0:/]
res=0 f_opendir
directory name : []
----A 1980/00/00 00:00        96  filetest.txt
D---- 1980/00/00 00:00         0  testdir
----A 2011/11/15 15:53        14  hello0.txt
   2 File(s),       110 bytes
   1 Dir(s),    3946336K bytes free
completed

level 5 make dir
res=0 disk_initialize
res=0 f_getcwd
current directory name : [0:/]
res=0 f_opendir
directory name : []
----A 1980/00/00 00:00        96  filetest.txt
D---- 1980/00/00 00:00         0  testdir
----A 2011/11/15 15:53        14  hello0.txt
   2 File(s),       110 bytes
   1 Dir(s),    3946336K bytes free
res=0 f_opendir testdir
res=0 f_chdir testdir
res=0 f_open filetest.txt
Hello world.
This is the line 1.
This is the line 2.
This is the line 3.
This is the last line.
res=0 f_close
res=0 f_unlink filetest.txt
res=0 f_chdir ..
res=0 f_getcwd
current directory name : [0:/]
res=0 f_opendir testdir
directory name : [testdir]
D---- 1980/00/00 00:00         0  .
D---- 1980/00/00 00:00         0  ..
   0 File(s),         0 bytes
   0 Dir(s),    3946368K bytes free
res=0 f_unlink testdir
res=0 f_getcwd
current directory name : [0:/]
res=0 f_opendir
directory name : []
----A 1980/00/00 00:00        96  filetest.txt
----A 2011/11/15 15:53        14  hello0.txt
   2 File(s),       110 bytes
   0 Dir(s),    3946400K bytes free
completed

(7)main6

FATモジュールを使って,ロングファイル名のファイル"filetest_longname.txt"を
SDカードのルートディレクトリに書き込み,次に読み出して,
PCのモニタアプリ(teratermなど)に表示する。

実行例

level 6 long file name
res=0 disk_initialize
file name = (filetest_longname.txt)
res=0 f_open
res=0 f_close
res=0 f_open
Hello world.
This is the line 1.
This is the line 2.
This is the line 3.
This is the last line.
res=0 f_close
res=0 f_getcwd
current directory name : [0:/]
res=0 f_opendir
directory name : []
----A 2011/11/18 09:59        98  filetest.txt
----A 2011/11/21 11:54        14  hello0.txt
D---- 1980/00/00 00:00         0  testdir
----A 1980/00/00 00:00        96  filetest_longname.txt
   3 File(s),       208 bytes
   1 Dir(s),    3946304K bytes free
completed

8.サンプルプログラム中mainの初期化記述について

関数mainの初期化部分を抜き出すと次のようになる。
最初は低速モード(通信速度)でドライブを初期化し(disk_initialize(DRIVE_NO);),
初期化が終わったら高速モード(通信速度)に切り替える。

initLed(); /*h8_3048fone.cで 定義*/
initTimer1Int(10000); /*10msecインターバルタイマ割り込み h8_3048fone.cで定義*/
startTimer1(); /*h8_3048fone.cで 定義*/
initSCI1(); /*h8_3048fone.cで 定義*/
initSPI_CS(); /*spi.cで定 義*/
msecwait(10); /*SD初期化のためには1msec以上のwaitが必要*/
initSPI(0); /*モード0(低速)で初期化  spi.cで定義*/
initDMAC();  /*spi.cで 定義*/
set_imask_ccr(0);

SCI1_printf("\nlevel 6 long file name\n");
do {
    res=disk_initialize(DRIVE_NO); /*diskio.cで定義,ff.cからも呼び出される*/
    SCI1_printf("res=%d disk_initialize\n",res);
    msecwait(100);
    if (tick=1-tick) turnOnLed(0);
    else turnOffLed(0);
} while (res);
initSPI(1); /*モード1(高速)で初期化  spi.cで定義*/

f_mount(DRIVE_NO, &fatfs); /*FATシステムのマウント  ff.cで定義*/


9.spi.c開発メモ

(1)ポーリング方式でクロック同期送信(マイコンからの)は,SCR.BIT.TE=1にしてTDRに送信したいデータ(1バイト)
を置くだけで,SCKより8個のクロックが出て,送信される。

(2)ポーリング方式でクロック同期受信(マイコンへの)では,SCR.BIT.RE=1にすると,SCR.BIT.RE=0にするま で
SCKよりクロックが出続け,8の整数倍個のクロックにはなるとは限らず,うまく同期が取れない。
SCR.BIT.TEとSCR.BIT.REを同時に1にすると,(別々に1にしたのではだめ)同時クロック同期送受信モードとなり,
そのままではクロックは出ない。しかし,1バイトのダミーデータ(0xff)を出力すると,SCKより8個のクロックが出て,
ダミーデータが送信されつつ,1バイトのデータ受信することができる。

(3)DMAモード方式でクロック同期送信(マイコンからの)は,SCI割り込みによって起動するDMAを採用する。
DMAのIOモードで送信する。DMA送信終了割り込みを使わなくても,DMAC0A.DTCR.BIT.DTE=1にしてから,
SCR.BYTE = 0xa0にして,送信を開始することができ,
while (DMAC0A.DTCR.BIT.DTE);
SCR.BIT.TIE=0;   /*送信割り込み禁止*/
のようにすれば,SCIの送信割り込みが止まるはずだが,送信速度が速いため,タイミングによっては,送信割り
込み禁止が間に合わないことが生ずる。
(間に合わない時は,SCI送信割り込みがCPUにかかってしまう。通常はSCI送信割り込み要求はDMACが受け取るが,
DMACが動作を止めるとCPUにまで割り込み要求が達してしまう。)
そこで,DMA終了割り込みを使い,割り込みルーチン内で,送信割り込み禁止しなくてはならない。
SSR.BIT.TENDが1になったら,送信終了なので,送受信禁止にする。

(4)DMAモード方式でクロック同期受信(マイコンへの)は,SCI割り込みによって起動するDMAを採用する。
SCR.BIT.TEとSCR.BIT.REを同時に1にして,(別々に1にしたのではだめ)同時クロック同期送受信モードとする。
理由はポーリング方式でクロック同期受信と同じである。
ダミーデータ(0xff)をDMAアイドルモードで出力すると,SCKより8個のクロックが出て,ダミーデータが送信されつつ,
データを DMAのIOモードで受信することができる。
DMA送信終了割り込みを使って,割り込みルーチン内で,送信割り込み禁止およびDMAC0A.DTCR.BIT.DTEを0にする。
主ルーチン側で,while (DMAC0A.DTCR.BIT.DTE);で待ち,さらに
SSR.BIT.TENDが1になったら,送信終了なので,送受信禁止, SCI送受信割り込み禁止にする。