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

Oct2013
KOSAKA TNCT

1.はじめに

H8/3048fone(mode7:内部ROMRAM利用1Mbyteメモリモード)に SDカードソケットをつけて,FATシステムを導入し,ファイルの読み書きができるようにしている。
松林基板はポート4のビットアサインが先の試作版と異なる。しかし,ハードウエア直結部分はレイヤが最下層であるため,このレイヤだけ変更することで,対応が可能になるというよい例となった。
異なる点は
(1)
SCIを使ったSDカードとの通信のみをサポート
(2)カードデテクト(SDカードが挿入されているかどうかのチェック)機能サポート
(3)ライトプロテクト(SDカードが書き込み禁止になっているかどうかのチェック)
機能サポート
(4)(1)(2)のためH8/3048f-oneのポート4のビットアサインが変更
である。

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を使った通信のみをサポートしている。

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

H8/3014f-oneのポートアサイン

SDカード,ソケット
H8/3048のポートとビット H8/3048から見た信号の方向
備考
SD_CSelect
P4-3
OUT

SD_DIn
TXD0(P9-0)
IN

SD_SCLK
SCK0(P9-4)
OUT

SD_DOut
RXD0(P9-2)
OUT

SDS_Card_Detect
P4-0
IN
プルアップが必要(P4-0はプルアップコントロールON)
SDS_Write_Protect
P4-1
IN
プルアップが必要(P4-1はプルアップコントロールON)


参考 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ごとに
        呼び出される作業関数
 (タイマ割り込みで呼び出されるよう
  にしておく必要がある)
SPI_CardDetected - カードが挿入されて
  いるかどうかチェック
SPI_WriteProtected - カードが書き込み
  禁止になっているかどうかチェック

 

 

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

SDCに複数バイト入出力する方式は,
(1)ポーリング方式SCI      (転送速度 125kbps[低速モード],6250kbps[高速モード])
(2)DMA方式SCI         (転送速度 125kbps[低速モード],6250kbps[高速モード])
の3種類を準備している。SDメモリの操作に専念できるアプリケーションの場 合はDMA方式SCIで高速にデータ転送を行うと良い。そうでない場合(タイマ割り込み作業が優先で,そちらが忙しい場合など)では ポーリング方式SCIでデータ転送を行うと良い。どちらもデータ転送速度は同じだが,ポーリング方式ではCPU内のデータハンドリン グに時間がかかる。
低速モードは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で選 択する
int SPI_CardDetected(void) - カードが挿入されているかどうかチェック
  戻り値
   挿入されている 1
   されていない 0
int SPI_WriteProtected(void) - カードが書き込み禁止になっているかどうかチェック
  戻り値
   書き込み禁止 1
   書き込み可能  0

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


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

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

(1)main00

    CardDetect信号,WriteProtect信号のみチェック

(2)main0

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

(3)main1

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

(4)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

(5)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

(6)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

(7)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

(8)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");
while (!SPI_CardDetected()) {
    SCI1_printf("not detect SD card\n");
    msecwait(250);
    if (tick=1-tick) turnOnLed(0);
    else turnOffLed(0);
}
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送受信割り込み禁止にする。