「MSP430F149 - FatFs」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

 
894行目: 894行目:
*: [[マイコン - FatFs]] - FatFsモジュールの概要、API一覧、ディスクI/Oインターフェースの説明
*: [[マイコン - FatFs]] - FatFsモジュールの概要、API一覧、ディスクI/Oインターフェースの説明
*: [[マイコン - FatFsの設定]] - ffconf.hの設定項目詳細とMSP430F149向け推奨設定
*: [[マイコン - FatFsの設定]] - ffconf.hの設定項目詳細とMSP430F149向け推奨設定
*: [[MSP430F149- FATFsの応用]] - アプリケーション実装例とトラブルシューティング
*: [[MSP430F149 - FatFsの応用]] - アプリケーション実装例とトラブルシューティング
<br><br>
<br><br>



2026年5月1日 (金) 03:52時点における最新版

概要

MSP430F149は、Texas Instrumentsが提供する16ビットRISC超低消費電力マイコンである。
最大8[MHz]動作、60[KB]フラッシュメモリ、2[KB] RAMを搭載し、1.8[V] ~ 3.6[V]の広い電源電圧範囲で動作する。

MSP430F149はUSARTモジュールを2チャネル搭載しており、それぞれUARTモードとSPIモードを切り替えて使用できる。
このうち1チャネルをSPIマスターモードで動作させることにより、SDカードとのSPI通信が可能となる。

FatFsモジュールのディスクI/O層をMSP430F149のSPIドライバ上に実装することで、SDカード上のFATファイルシステムに対する読み書きを実現できる。

ただし、MSP430F149のRAMは2[KB]しかないため、FatFsをそのまま使用するとメモリが不足する。
FatFsのTINYモード (FF_FS_TINY=1) を有効にすることが必須であり、これにより、FIL構造体のサイズが約552バイトから約40バイトに削減される。

TINYモードでは全てのオープンファイルがFATFS構造体内の単一セクタバッファを共有するため、
1ボリューム・1ファイル構成で約604バイトのRAMでFatFsを動作させることが可能となる。

MSP430F149は3.3[V]で動作させるのが一般的であり、SDカードの動作電圧 (2.7[V] ~ 3.6[V]) と一致する。
そのため、レベルシフタを使用せずにMSP430F149とSDカードを直結できるメリットがある。


MSP430F149の仕様

MSP430F149は、Texas Instrumentsが提供する16ビットRISC超低消費電力マイコンである。
FatFsとSDカードを組み合わせたデータロギングシステム等、組み込みアプリケーションに広く使用されている。

下表に、主要な仕様を示す。

MSP430F149 主要仕様
項目 仕様
CPU 16ビットRISC (MSP430)
最大動作周波数 8[MHz]
電源電圧 1.8[V] ~ 3.6[V] (通常3.3[V])
フラッシュメモリ 60[KB] + 256バイト (情報メモリ)
RAM 2[KB] (0x0200 ~ 0x09FF)
パッケージ QFP-64
I/Oピン 48本


RAM 2[KB]という制約は、FatFsのTINYモード有効化が必須となる主な理由である。
TINYモードを有効にすることで、FIL構造体のサイズを約552バイトから約40バイトに削減できる。

詳細な設定方法は マイコン - FatFsの設定 を参照のこと。

下表に、MSP430F149のメモリマップを示す。

MSP430F149 メモリマップ
アドレス範囲 サイズ 用途
0x0000 ~ 0x00FF 256バイト ペリフェラルレジスタ (8ビットバス)
0x0100 ~ 0x01FF 256バイト ペリフェラルレジスタ (16ビットバス)
0x0200 ~ 0x09FF 2[KB] RAM
0x1000 ~ 0x10FF 256バイト 情報メモリ (フラッシュ)
0x1100 ~ 0xFFFF 60[KB]強 メインフラッシュメモリ


下表に、MSP430F149に搭載されているペリフェラルの概要を示す。

MSP430F149 ペリフェラル概要
ペリフェラル 説明
Timer_A 3チャネルCC、16ビットタイマ
Timer_B 7チャネルCC、16ビットタイマ
ADC12 12ビットSAR ADC、8チャネル
コンパレータ_A アナログ比較
USART0 UART/SPI兼用 (P3.1=SIMO, P3.2=SOMI, P3.3=UCLK)
USART1 UART/SPI兼用 (P3.6=TXD, P3.7=RXD)


FatFsのSDカード接続にはUSART0をSPIモードで使用する。


SPIインターフェース

USART-SPIモードの概要

MSP430F149のUSARTは、UCTLレジスタのSYNCビットを1に設定することでSPIモードに切り替えることができる。

SPI機能を有効化するためのレジスタ設定を以下に示す。

SPIの有効化設定
項目 説明
USART0のSPI有効化 ME1レジスタのUSPIE0ビット (ビット6) を1に設定する。
USART1のSPI有効化 ME2レジスタのUSPIE1ビット (ビット4) を1に設定する。


3ピンモードでは、STE (CS) を使用せずにSIMO、SOMI、UCLKの3線で通信する。
FatFsのSDカード接続では、CSをGPIOで手動制御するため、3ピンモードを使用する。

MSP430F149のSPI信号名は一般的なSPI信号名と異なる名称を使用している。

下表に、対応関係を示す。

SPI信号名対応表
MSP430F149の信号名 一般的な信号名 説明
SIMO MOSI マスタ出力 / スレーブ入力
SOMI MISO マスタ入力 / スレーブ出力
UCLK CLK クロック信号
STE CS スレーブ選択 (チップセレクト)


SPI関連レジスタ

下表に、USART0のSPI動作に関わる主要レジスタを示す。

USART0 SPI関連レジスタ一覧
レジスタ名 アドレス 説明
UCTL0 0x0070 USARTモード制御レジスタ (SYNC、MM、CHARビット等)
UBR00 0x0072 ボーレートレジスタ (下位バイト)
UBR10 0x0073 ボーレートレジスタ (上位バイト)
UTCTL0 0x0074 送信制御レジスタ (CLKSビット等でクロック源選択)
URCTL0 0x0075 受信制御レジスタ
UTXBUF0 0x0076 送信バッファ
URXBUF0 0x0077 受信バッファ
IE1 0x0000 割り込み有効レジスタ1 (URXIE0、UTXIE0ビット)
IFG1 0x0002 割り込みフラグレジスタ1 (URXIFG0、UTXIFG0ビット)
ME1 0x0004 モジュール有効レジスタ1 (USPIE0ビット)


SPIクロック設定

SPIのクロック周波数は、MCLK (8[MHz]) をUBRレジスタの値で分周して設定する。
SDカードの初期化シーケンスでは、クロック周波数を100[kHz] ~ 400[kHz]以下に設定する必要がある。
初期化完了後に高速クロックに切り替える。

SPIクロック設定例 (MCLK = 8[MHz])
UBR値 クロック周波数 用途
0 (= 1) 8[MHz] 通常動作時の最大速度
2 4[MHz] 安定動作が必要な場合
8 1[MHz] 中速動作
80 100[kHz] SDカード初期化時


SPI送受信の仕組み

MSP430F149のSPI送受信の手順を以下に示す。

データ転送の動作
項目 説明
送信 UTXBUFレジスタにデータを書き込むと、シフトレジスタに転送されてSIMOピンから出力される。
受信 同時にSOMIピンからデータが入力され、URXBUFレジスタから読み出せる。
データ順序 MSBファーストでデータが転送される。



SDカードのハードウェア接続

電圧レベルと接続の基本方針

MSP430F149は3.3[V]動作であり、SDカードの動作電圧 (2.7[V] ~ 3.6[V]) と一致するため、レベルシフタは不要である。

下表に、MSP430F149とSDカードのピン接続を示す。

MSP430F149 - SDカード ピン接続表
MSP430F149ピン 機能 SDカードピン 説明
P3.1 USART0 SIMO DI (ピン2) マスタ --> SDカード
P3.2 USART0 SOMI DO (ピン7) SDカード --> マスタ
P3.3 USART0 UCLK CLK (ピン5) クロック信号
P1.0 (任意) GPIO出力 CS (ピン1) チップセレクト (アクティブLow)
VCC (3.3[V]) 電源 VDD (ピン4) SDカード電源
GND グラウンド VSS (ピン3, 6) GND


プルアップ抵抗

下表に、プルアップ抵抗の要否を示す。

各ピンのプルアップ要否
ピン プルアップ 説明
DO (MISO) ピン 10[kΩ]プルアップ必要 SDカードのDO出力はオープンドレイン構成のため、外部プルアップが必須である。
SIMO (MOSI) ピン 不要 MSP430F149のプッシュプル出力で駆動するため、プルアップは不要である。
UCLK (CLK) ピン 不要 プッシュプル出力のため、プルアップは不要である。
CS ピン 不要 GPIOのプッシュプル出力で制御する。


回路接続の要点

回路設計の要点を以下に示す。

  • MSP430F149の3.3[V]電源からSDカードのVDDに接続する。
  • DO (MISO) ラインには3.3[V]ラインから10[kΩ]のプルアップ抵抗を接続する。
  • SDカードのCSピンはP1.0に接続し、初期状態でHighに設定する。
  • デカップリングコンデンサとして、SDカードのVDD-VSS間に0.1[uF]セラミックコンデンサを配置する。



SPIドライバの実装

ヘッダファイルの定義

SPIドライバのヘッダファイル spi.h を以下に示す。
CSピンの定義と、SPIドライバが提供する関数のプロトタイプを定義する。

 #ifndef SPI_H
 #define SPI_H
 
 #include <msp430.h>
 #include <stdint.h>
 
 // CSピン定義 (P1.0を使用する場合の例)
 #define SD_CS_PORT   P1OUT
 #define SD_CS_DDR    P1DIR
 #define SD_CS_PIN    BIT0
 
 // CSピン制御マクロ
 #define SD_CS_LOW()  (SD_CS_PORT &= ~SD_CS_PIN)
 #define SD_CS_HIGH() (SD_CS_PORT |= SD_CS_PIN)
 
 // 関数プロトタイプ
 void     SPI_Init(void);
 uint8_t  SPI_SendByte(uint8_t data);
 uint8_t  SPI_ReceiveByte(void);
 void     SPI_SetSpeedHigh(void);
 void     SPI_SetSpeedLow(void);
 
 #endif // SPI_H


SPI初期化関数

USART0をSPIマスターモードで初期化する SPI_Init() 関数の実装例を以下に示す。

SWRST (ソフトウェアリセット) ビットを設定した状態でレジスタを設定して、最後にSWRSTを解除してUSARTを有効化する手順となる。

 #include "spi.h"
 
 void SPI_Init(void)
 {
    // SWRSTビット設定 (リセット状態でレジスタを設定する)
    U0CTL |= SWRST;
 
    // SPIモード設定: SYNC=1 (同期), MM=1 (マスタ), CHAR=1 (8ビット)
    U0CTL |= SYNC | MM | CHAR;
    U0CTL &= ~(LISTEN | I2C);   // リスンモード無効、I2Cモード無効
 
    // クロック設定 (初期化時は低速: 100[kHz])
    // MCLK (8[MHz]) / 80 = 100[kHz]
    U0TCTL |= CKPH | SSEL1;      // CLKSビット: SMCLK選択、クロック位相設定
    UBR00 = 80;                  // ボーレート下位バイト
    UBR10 = 0;                   // ボーレート上位バイト
    UMCTL0 = 0;                  // モジュレーション不要
 
    // USART0のSPI機能を有効化
    ME1 |= USPIE0;
 
    // ポート設定 (P3.1 = SIMO, P3.2 = SOMI, P3.3 = UCLK)
    P3SEL |= BIT1 | BIT2 | BIT3;     // P3.1, P3.2, P3.3をUSART機能に設定
    P3DIR |= BIT1 | BIT3;            // SIMO, UCLKを出力に設定
    P3DIR &= ~BIT2;                  // SOMIを入力に設定
 
    // CS用GPIOの設定 (P1.0)
    SD_CS_DDR |= SD_CS_PIN;          // CSピンを出力に設定
    SD_CS_HIGH();                    // 初期状態はCSをHighに設定
 
    // SWRSTを解除してUSARTを有効化
    U0CTL &= ~SWRST;
 
    // 送受信割り込みは使用しない (ポーリング方式)
    IE1 &= ~(URXIE0 | UTXIE0);
 }


SPI送受信関数

1バイトの送受信を行う SPI_SendByte() および SPI_ReceiveByte() 関数の実装例を以下に示す。

UTXBUFレジスタへの書き込みで送信を開始して、URXBUFレジスタから受信データを読み出す。

 // 1バイト送受信 (全二重)
 // 引数: data=送信データ
 // 戻り値: 受信データ
 uint8_t SPI_SendByte(uint8_t data)
 {
    // 送信バッファが空になるまで待機
    while (!(IFG1 & UTXIFG0));
 
    // データを送信バッファに書き込む
    TXBUF0 = data;
 
    // 受信バッファにデータが入るまで待機
    while (!(IFG1 & URXIFG0));
 
    // 受信データを返す
    return RXBUF0;
 }
 
 // ダミーバイト送信でデータを受信する
 // 戻り値: 受信データ
 uint8_t SPI_ReceiveByte(void)
 {
    return SPI_SendByte(0xFF);
 }


SPI速度変更関数とCS制御マクロ

SDカード初期化後に高速クロックに切り替える SPI_SetSpeedHigh() および SPI_SetSpeedLow() 関数の実装例を以下に示す。

速度変更時はSWRSTを設定してからUBRレジスタを変更して、SWRSTを解除する手順が必要である。

 // 高速クロックに設定 (8[MHz])
 // SDカード初期化完了後に呼び出す
 void SPI_SetSpeedHigh(void)
 {
    U0CTL |= SWRST;
    UBR00 = 1;        // 8[MHz] / 1 = 8[MHz]
    UBR10 = 0;
    UMCTL0 = 0;
    U0CTL &= ~SWRST;
 }
 
 // 低速クロックに設定 (初期化用: 100[kHz])
 void SPI_SetSpeedLow(void)
 {
    U0CTL |= SWRST;
    UBR00 = 80;       // 8[MHz] / 80 = 100[kHz]
    UBR10 = 0;
    UMCTL0 = 0;
    U0CTL &= ~SWRST;
 }



SDカード初期化シーケンス

初期化フロー

SDカードをSPIモードで初期化する手順を以下に示す。

SPIモードによるSDカード初期化手順
ステップ 内容 説明
ステップ1 電源投入後の安定待ち 電源投入後、最低1[ms]以上待機してからSPIクロックを送出する。
ステップ2 74クロック以上の送出 (CS = HIGH) CS=HIGH状態でダミークロックを74クロック以上送出して、SDカードをSPIモードに移行させる。
実装上は10バイト (80クロック) 送出する。
ステップ3 CMD0 (リセット) CS=LOWにしてCMD0 (ソフトウェアリセット) を送信する。
R1レスポンスが0x01 (Idle State) であれば正常にSPIモードに移行している。
ステップ4 CMD8 (インターフェース条件確認) CMD8を送信してSDカードのバージョンを確認する。
正常な応答があればSDv2、エラーであればSDv1またはMMCとして扱う。
ステップ5 ACMD41ループ (初期化) CMD55 + ACMD41を繰り返し送信して初期化完了を待つ。
R1レスポンスが0x00になれば初期化完了である。
ステップ6 CMD58 (OCR読み出し) SDv2の場合にCMD58でOCRを読み出し、CCSビットでブロックアドレス対応か判定する。
ステップ7 高速クロック切替 初期化完了後に高速クロック (8[MHz]) に切り替える。


下表に、初期化シーケンスの詳細を示す。

SDカード初期化シーケンス
ステップ 操作 期待するレスポンス 補足
1 電源投入 --> 1[ms]以上待機 - 電源安定のために必要
2 CS=HIGH、74クロック以上出力 - SDカードをSPIモードに移行させる
3 CMD0送信 0x01 (Idle State) CRCが必要 (0x95)
4 CMD8送信 (引数: 0x1AA) 0x01 + 4バイト応答 SDv2判定、CRC必要 (0x87)
5 ACMD41ループ (SDv2: 引数HCS=1) 0x00 (Ready) 最大1秒程度待機
6 CMD58送信 (OCR読み出し) 0x00 + 4バイトOCR CCSビットでアドレス方式確認
7 高速クロックに切り替え - 初期化完了後に実施


SDカードコマンド送信関数

SDカードへのコマンド送信とR1レスポンス受信を行う SD_SendCmd() 関数の実装例を以下に示す。

CMD0とCMD8のみ有効なCRCが必要であり、その他のコマンドはダミーCRCを使用する。

 // カードタイプ定義
 #define CT_MMC      0x01    // MMCカード
 #define CT_SD1      0x02    // SDカード Ver.1
 #define CT_SD2      0x04    // SDカード Ver.2
 #define CT_SDC      (CT_SD1 | CT_SD2)
 #define CT_BLOCK    0x08    // ブロックアドレス対応カード
 
 // SDカードコマンド定義
 #define CMD0    (0)         // リセット
 #define CMD8    (8)         // インターフェース条件送信
 #define CMD9    (9)         // CSD読み出し
 #define CMD12   (12)        // 転送停止
 #define CMD17   (17)        // シングルブロック読み込み
 #define CMD18   (18)        // マルチブロック読み込み
 #define CMD24   (24)        // シングルブロック書き込み
 #define CMD25   (25)        // マルチブロック書き込み
 #define CMD41   (41)        // SDカード初期化 (ACMD41)
 #define CMD55   (55)        // アプリケーションコマンド
 #define CMD58   (58)        // OCR読み出し
 
 // SDカードコマンド送信
 // 引数: cmd = コマンド番号 (上位ビットが1の場合はACMD), arg=引数
 // 戻り値: R1レスポンスバイト
 static uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg)
 {
    uint8_t n, res;
 
    // ACMD (アプリケーションコマンド) の場合はCMD55を先行送信
    if (cmd & 0x80) {
       cmd &= 0x7F;
       res = SD_SendCmd(CMD55, 0);
       if (res > 1) return res;
    }
 
    // カードがReadyになるまで待機
    SD_CS_HIGH();
    SPI_SendByte(0xFF);
    SD_CS_LOW();
    SPI_SendByte(0xFF);
 
    // コマンドパケット送信 (6バイト)
    SPI_SendByte(0x40 | cmd);              // スタートビット + コマンドインデックス
    SPI_SendByte((uint8_t)(arg >> 24));    // 引数バイト3 (MSB)
    SPI_SendByte((uint8_t)(arg >> 16));    // 引数バイト2
    SPI_SendByte((uint8_t)(arg >> 8));     // 引数バイト1
    SPI_SendByte((uint8_t)arg);            // 引数バイト0 (LSB)
 
    // CRC送信 (CMD0とCMD8のみ有効なCRCが必要)
    n = 0x01;   // ダミーCRC (ストップビットのみ)
    if (cmd == CMD0) n = 0x95;  // CMD0のCRC
    if (cmd == CMD8) n = 0x87;  // CMD8のCRC
    SPI_SendByte(n);
 
    // CMD12の場合はダミーバイトを送信
    if (cmd == CMD12) SPI_SendByte(0xFF);
 
    // R1レスポンス受信 (最大10バイト待機)
    n = 10;
    do {
       res = SPI_SendByte(0xFF);
    } while ((res & 0x80) && --n);
 
    return res;
 }


SDカード初期化関数

SDカードの初期化シーケンス全体を実行する SD_Init() 関数の実装例を以下に示す。

 static volatile DSTATUS Stat = STA_NOINIT;  // ディスクステータス
 static uint8_t CardType;                    // カードタイプ
 
 // カードのBusy解除待ち
 // 戻り値: 1=Ready、0=タイムアウト
 static int wait_ready(void)
 {
    uint8_t d;
    uint16_t timeout = 5000;
 
    do {
       d = SPI_SendByte(0xFF);
       timeout--;
    } while ((d != 0xFF) && timeout);
 
    return (d == 0xFF) ? 1 : 0;
 }
 
 // SDカード初期化
 // 戻り値: ディスクステータス (0=成功, STA_NOINIT=失敗)
 DSTATUS SD_Init(void)
 {
    uint8_t n, cmd, ty, ocr[4];
    uint16_t tmr;
 
    // 電源投入後の安定待ち (1ms以上)
    for (tmr = 1000; tmr; tmr--) {
       __delay_cycles(8000);  // 約1ms (MCLK=8MHz)
    }
 
    // CS=HIGH状態で74クロック以上送出 (SDカードをSPIモードに移行)
    SD_CS_HIGH();
    for (n = 10; n; n--) {
       SPI_SendByte(0xFF);    // 10バイト x 8クロック = 80クロック
    }
 
    ty = 0;
    // CMD0: カードをアイドル状態にリセット
    if (SD_SendCmd(CMD0, 0) == 1) {
       // CMD8: SDカードv2の確認 (引数: VHS=1 (2.7-3.6V), Check Pattern=0xAA)
       if (SD_SendCmd(CMD8, 0x1AA) == 1) {
          // SDv2: OCRの応答 (4バイト) を受信
          for (n = 0; n < 4; n++) ocr[n] = SPI_SendByte(0xFF);
 
          if (ocr[2] == 0x01 && ocr[3] == 0xAA) {
             // 電圧範囲確認OK: ACMD41で初期化ループ
             for (tmr = 1000; tmr && SD_SendCmd(0x80 | CMD41, 1UL << 30); tmr--) {
                __delay_cycles(8000);
             }
 
             // CMD58: OCRを読み出してブロックアドレス対応確認
             if (tmr && SD_SendCmd(CMD58, 0) == 0) {
                for (n = 0; n < 4; n++) ocr[n] = SPI_SendByte(0xFF);
                ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
             }
          }
       }
       else {
          // SDv1またはMMC: ACMD41で初期化ループ
          if (SD_SendCmd(0x80 | CMD41, 0) <= 1) {
             ty = CT_SD1;
             cmd = 0x80 | CMD41;   // SDC: ACMD41を使用
          }
          else {
             ty = CT_MMC;
             cmd = 1;              // MMC: CMD1を使用
          }
 
          for (tmr = 1000; tmr && SD_SendCmd(cmd, 0); tmr--) {
             __delay_cycles(8000);
          }
 
          // ブロック長を512バイトに設定
          if (!tmr || SD_SendCmd(16, 512) != 0) ty = 0;
       }
    }
 
    CardType = ty;
 
    // CSをHighに戻してIDLE状態にする
    SD_CS_HIGH();
    SPI_SendByte(0xFF);
 
    if (ty) {
       // 初期化成功: 高速クロックに切り替え
       SPI_SetSpeedHigh();
       Stat &= ~STA_NOINIT;
    }
    else {
       Stat = STA_NOINIT;
    }
 
    return Stat;
 }



ディスクI/O層の実装

ヘルパー関数とマクロ定義

ディスクI/O層で使用するヘルパー関数の実装例を以下に示す。

diskio.c のヘルパー関数部分であり、データブロックの受信・送信を担当する。

 #include "diskio.h"
 #include "spi.h"
 #include <stdint.h>
 
 // データブロック受信
 // 引数: buff = 受信バッファ、btr = 受信バイト数
 // 戻り値: 1 = 成功、0 = 失敗
 static int rcvr_datablock(BYTE *buff, UINT btr)
 {
    uint8_t token;
    uint16_t timeout = 2000;
 
    // データトークン待機 (0xFE)
    do {
       token = SPI_SendByte(0xFF);
       timeout--;
    } while ((token == 0xFF) && timeout);
 
    if (token != 0xFE) return 0;  // データトークンエラー
 
    // データ受信
    do {
       *buff++ = SPI_SendByte(0xFF);
       *buff++ = SPI_SendByte(0xFF);
    } while (btr -= 2);
 
    // CRC破棄 (2バイト)
    SPI_SendByte(0xFF);
    SPI_SendByte(0xFF);
 
    return 1;
 }
 
 // データブロック送信
 // 引数: buff = 送信バッファ、token = データトークン (0xFE: 通常, 0xFD: 終端)
 // 戻り値: 1 = 成功、0 = 失敗
 static int xmit_datablock(const BYTE *buff, BYTE token)
 {
    uint8_t resp;
    uint16_t cnt = 512;
 
    // カードReadyまで待機
    if (!wait_ready()) return 0;
 
    // データトークン送信
    SPI_SendByte(token);
 
    if (token != 0xFD) {
       // データ送信 (512バイト)
       do {
          SPI_SendByte(*buff++);
          SPI_SendByte(*buff++);
       } while (cnt -= 2);
 
       // ダミーCRC (2バイト)
       SPI_SendByte(0xFF);
       SPI_SendByte(0xFF);
 
       // データレスポンス受信
       resp = SPI_SendByte(0xFF);
       if ((resp & 0x1F) != 0x05) return 0;  // 書き込み受理確認
    }
 
    return 1;
 }


disk_status関数とdisk_initialize関数

FatFsが要求する disk_status() および disk_initialize() 関数の実装例を以下に示す。

disk_initialize() 関数は、内部でSD_Init()を呼び出してSDカードを初期化する。

 // ドライブステータス取得
 // 引数: pdrv = 物理ドライブ番号 (0のみサポート)
 // 戻り値: ディスクステータス
 DSTATUS disk_status(BYTE pdrv)
 {
    if (pdrv != 0) return STA_NOINIT;  // 物理ドライブ0のみサポート
    return Stat;
 }
 
 // ドライブ初期化
 // 引数: pdrv=物理ドライブ番号
 // 戻り値: ディスクステータス
 DSTATUS disk_initialize(BYTE pdrv)
 {
    if (pdrv != 0) return STA_NOINIT;
 
    SPI_Init();     // SPIドライバを初期化
    SD_Init();      // SDカードを初期化
 
    return Stat;
 }


disk_read関数

FatFsが要求する disk_read() 関数の実装例を以下に示す。

シングルブロック読み込みはCMD17、マルチブロック読み込みはCMD18を使用する。

 // セクタ読み込み
 // 引数: pdrv = 物理ドライブ番号、buff = 読み込みバッファ、sector = 開始セクタ番号、count = 読み込みセクタ数
 // 戻り値: RES_OK = 成功、RES_ERROR = 失敗
 DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count)
 {
    uint32_t sect = (uint32_t)sector;
 
    if (pdrv != 0 || !count) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
 
    // バイトアドレス変換 (ブロックアドレス対応でないカードはセクタ x 512)
    if (!(CardType & CT_BLOCK)) sect *= 512;
 
    if (count == 1) {
       // シングルブロック読み込み: CMD17
       if ((SD_SendCmd(CMD17, sect) == 0) && rcvr_datablock(buff, 512)) {
          count = 0;
       }
    }
    else {
       // マルチブロック読み込み: CMD18
       if (SD_SendCmd(CMD18, sect) == 0) {
          do {
             if (!rcvr_datablock(buff, 512)) break;
             buff += 512;
          } while (--count);
          SD_SendCmd(CMD12, 0);  // 転送停止
       }
    }
 
    SD_CS_HIGH();
    SPI_SendByte(0xFF);
 
    return (count ? RES_ERROR : RES_OK);
 }


disk_write関数

FatFsが要求する disk_write() 関数の実装例を以下に示す。

シングルブロック書き込みはCMD24、マルチブロック書き込みはCMD25を使用する。

 // セクタ書き込み
 // 引数: pdrv = 物理ドライブ番号、buff = 書き込みデータ、sector = 開始セクタ番号、count = 書き込みセクタ数
 // 戻り値: RES_OK = 成功、RES_ERROR = 失敗
 DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count)
 {
    uint32_t sect = (uint32_t)sector;
 
    if (pdrv != 0 || !count) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    if (Stat & STA_PROTECT) return RES_WRPRT;
 
    if (!(CardType & CT_BLOCK)) sect *= 512;
 
    if (count == 1) {
       // シングルブロック書き込み: CMD24
       if ((SD_SendCmd(CMD24, sect) == 0) && xmit_datablock(buff, 0xFE)) {
          count = 0;
       }
    }
    else {
       // マルチブロック書き込み: CMD25
       if (CardType & CT_SDC) SD_SendCmd(0x80 | 23, count);  // ACMD23: 事前消去
       if (SD_SendCmd(CMD25, sect) == 0) {
          do {
             if (!xmit_datablock(buff, 0xFC)) break;
             buff += 512;
          } while (--count);
          if (!xmit_datablock(0, 0xFD)) count = 1;  // 終端トークン送信
       }
    }
 
    SD_CS_HIGH();
    SPI_SendByte(0xFF);
 
    return (count ? RES_ERROR : RES_OK);
 }


disk_ioctl関数 と get_fattime関数

FatFsが要求する disk_ioctl() および get_fattime() 関数の実装例を以下に示す。

RTCを搭載していない環境では、get_fattime() は固定のタイムスタンプを返す。

 // デバイス制御コマンド実行
 // 引数: pdrv = 物理ドライブ番号、cmd = 制御コマンド、buff = データバッファ
 // 戻り値: RES_OK = 成功、RES_ERROR = 失敗
 DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
 {
    DRESULT res;
    uint8_t n,
            csd[16];
    uint32_t csize;
 
    if (pdrv != 0) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
 
    res = RES_ERROR;
 
    switch (cmd) {
       case CTRL_SYNC:
          // 書き込み完了待ち
          SD_CS_LOW();
          if (wait_ready()) res = RES_OK;
          break;
       case GET_SECTOR_COUNT:
          // 総セクタ数の取得
          if ((SD_SendCmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {
             if ((csd[0] >> 6) == 1) {
                // SDv2 CSD
                csize = csd[9] + ((uint16_t)csd[8] << 8) + ((uint32_t)(csd[7] & 0x3F) << 16) + 1;
                *(DWORD*)buff = csize << 10;
             }
             else {
                // SDv1 CSD
                n = (csd[5] & 0x0F) + ((csd[10] & 0x80) >> 7) + ((csd[9] & 0x03) << 1) + 2;
                csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 0x03) << 10) + 1;
                *(DWORD*)buff = csize << (n - 9);
             }
             res = RES_OK;
          }
          break;
       case GET_SECTOR_SIZE:
          // セクタサイズ (常に512バイト)
          *(WORD*)buff = 512;
          res = RES_OK;
          break;
       case GET_BLOCK_SIZE:
          // 消去ブロックサイズ
          *(DWORD*)buff = 128;
          res = RES_OK;
          break;
       default:
          res = RES_PARERR;
    }
 
    SD_CS_HIGH();
    SPI_SendByte(0xFF);
 
    return res;
 }
 
 // タイムスタンプ用時刻取得 (RTCなし環境では固定値を返す)
 // ビットフォーマット:
 //   [31:25] = 年 - 1980
 //   [24:21] = 月 (1-12)
 //   [20:16] = 日 (1-31)
 //   [15:11] = 時 (0-23)
 //   [10:5]  = 分 (0-59)
 //   [4:0]   = 秒 / 2 (0-29)
 DWORD get_fattime(void)
 {
    return ((DWORD)(2024 - 1980) << 25)  // 年: 2024
         | ((DWORD)1 << 21)              // 月: 1月
         | ((DWORD)1 << 16)              // 日: 1日
         | ((DWORD)0 << 11)              // 時: 0時
         | ((DWORD)0 << 5)               // 分: 0分
         | ((DWORD)0 >> 1);              // 秒: 0秒
 }



参考リンク