「MSP430F149 - FatFsの応用」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

ページの作成:「== 概要 == MSP430F149でFatFsモジュールを実用的に使用するには、2[KB] RAMという制約を常に意識した実装設計が求められる。<br> <br> FatFsのTINYモード (FF_FS_TINY = 1) を有効にした場合、FATFS構造体 (約564バイト) とFIL構造体 (約40バイト) の合計は約604バイトとなる。<br> これにスタック領域 (最低256バイト) を加えると、アプリケーションが自由に使用できるRAMは…」
 
802行目: 802行目:
*: [[マイコン - FatFs]] - FatFsモジュールの概要とAPI
*: [[マイコン - FatFs]] - FatFsモジュールの概要とAPI
*: [[マイコン - FatFsの設定]] - ffconf.hの設定項目詳細
*: [[マイコン - FatFsの設定]] - ffconf.hの設定項目詳細
*: [[MSP430F149 - FatfFs]] - ハードウェア接続とドライバ実装
*: [[MSP430F149 - FatFs]] - ハードウェア接続とドライバ実装
<br><br>
<br><br>



2026年4月22日 (水) 07:31時点における版

概要

MSP430F149でFatFsモジュールを実用的に使用するには、2[KB] RAMという制約を常に意識した実装設計が求められる。

FatFsのTINYモード (FF_FS_TINY = 1) を有効にした場合、FATFS構造体 (約564バイト) とFIL構造体 (約40バイト) の合計は約604バイトとなる。
これにスタック領域 (最低256バイト) を加えると、アプリケーションが自由に使用できるRAMは約1,188バイトとなる。

この限られたRAMの中で、ファイル操作用のバッファ、ADCの読み取りデータ、CSV文字列の生成バッファ等を確保する必要がある。
そのため、バッファサイズは必要最小限に抑え、動的メモリ割り当て (malloc / free) の使用は避ける。

FATFS構造体とFIL構造体はグローバル変数として宣言し、スタック消費を回避することが必須である。

SDカードへの書き込みでは、電源断によるデータ消失を防ぐために f_sync() 関数を定期的に呼び出す設計が重要となる。
ただし、f_sync() の呼び出しにはSPIアクセスが伴うため、頻度が高すぎるとパフォーマンスが低下する。
アプリケーションの要件に応じて、書き込み回数ごと、または一定時間ごとに f_sync() を呼び出すバランスが必要である。

MSP430F149はLPM3 (Low Power Mode 3) において約0.7[uA]の超低消費電力を実現する。
SDカードへの書き込みが不要な待機時間はLPM3に移行し、Timer_A割り込みでウェイクアップする設計とすることにより、バッテリ駆動の長期間データロギングが可能となる。


基本的なファイル操作

MSP430F149でFatFsを使用した基本的なファイル操作の実装例を以下に示す。

FATFS構造体とFIL構造体はスタック上に確保するとスタックオーバーフローの原因となるため、グローバル変数として宣言することを推奨する。

TINYモード (FF_FS_TINY = 1) を使用することで、FIL構造体のサイズを552バイトから約40バイトに削減できる。

ファイルの書き込み

FA_CREATE_ALWAYS | FA_WRITE フラグを使用してファイルを新規作成し、データを書き込む例を以下に示す。

 #include "ff.h"
 #include "diskio.h"
 #include <string.h>
 
 // グローバル変数として宣言 (スタック消費を避けるため)
 static FATFS g_fatfs;   // FATFSオブジェクト: TINYモードで約564バイト
 static FIL   g_fil;     // FILオブジェクト: TINYモードで約40バイト
 
 FRESULT write_file_example(void)
 {
    FRESULT res;
    UINT    bw;
    const char *data = "Hello, MSP430F149!\n";
 
    // ファイルシステムをマウント (即時マウント: opt=1)
    res = f_mount(&g_fatfs, "", 1);
    if (res != FR_OK) return res;
 
    // ファイルを新規作成 (既存ファイルは上書き)
    res = f_open(&g_fil, "DATA.TXT", FA_CREATE_ALWAYS | FA_WRITE);
    if (res != FR_OK) return res;
 
    // データを書き込む
    res = f_write(&g_fil, data, strlen(data), &bw);
    if (res != FR_OK || bw != strlen(data)) {
       f_close(&g_fil);
       return FR_DISK_ERR;
    }
 
    // バッファをフラッシュ (電源断対策)
    res = f_sync(&g_fil);
    if (res != FR_OK) {
       f_close(&g_fil);
       return res;
    }
 
    // ファイルをクローズ
    f_close(&g_fil);
    return FR_OK;
 }


ファイルの読み込み

FA_READ フラグを使用してファイルを開き、データを読み込む例を以下に示す。
バッファサイズを小さく保つことで、RAM消費量を抑制できる。

 FRESULT read_file_example(void)
 {
    FRESULT res;
    UINT    br;
    char    buffer[64];   // 読み込みバッファ (RAM節約のため小さくする)
 
    // ファイルを読み取りモードでオープン
    res = f_open(&g_fil, "DATA.TXT", FA_READ);
    if (res != FR_OK) return res;
 
    // データを読み込む
    res = f_read(&g_fil, buffer, sizeof(buffer) - 1, &br);
    if (res != FR_OK) {
       f_close(&g_fil);
       return res;
    }
    buffer[br] = '\0';   // NULL終端
 
    // ファイルをクローズ
    f_close(&g_fil);
    return FR_OK;
 }


ファイルへの追記

FA_OPEN_APPEND | FA_WRITE フラグを使用して、既存ファイルの末尾にデータを追記する例を以下に示す。
ファイルが存在しない場合は新規作成される。

 FRESULT append_file_example(const char *data)
 {
    FRESULT res;
    UINT    bw;
 
    // 追記モードでファイルをオープン (ファイルが存在しない場合は新規作成)
    res = f_open(&g_fil, "LOG.TXT", FA_OPEN_APPEND | FA_WRITE);
    if (res != FR_OK) return res;
 
    // データを追記
    res = f_write(&g_fil, data, strlen(data), &bw);
    if (res != FR_OK || bw != strlen(data)) {
       f_close(&g_fil);
       return FR_DISK_ERR;
    }
 
    // ファイルをクローズ
    f_close(&g_fil);
    return FR_OK;
 }



データロギングアプリケーション

MSP430F149の12ビットADC (ADC12) でセンサデータを取得して、SDカードへ定期的に記録する実用的なデータロガーの実装例を示す。

Timer_AとACLKを組み合わせた定期タイマ割り込みにより、低消費電力モード (LPM3) からウェイクアップしてデータを記録するアーキテクチャを採用する。

ADC初期化とタイマ割り込みの設定

MSP430F149のADC12モジュールを初期化し、Timer_AによるACLKベースの約1秒間隔タイマ割り込みを設定する実装例を以下に示す。

 #include <msp430f149.h>
 #include "ff.h"
 
 // グローバルフラグ: タイマ割り込み発生フラグ
 volatile unsigned char g_log_flag = 0;
 
 // ADC12の初期化
 void adc12_init(void)
 {
    // ADC12CTL0: サンプリング時間64サイクル、ADC12オン
    ADC12CTL0 = SHT0_3 | ADC12ON;
 
    // ADC12CTL1: シングルチャネル変換、ADC12CLK使用
    ADC12CTL1 = SHP;
 
    // ADC12MCTL0: チャネルA0、VCC/GSSリファレンス
    ADC12MCTL0 = INCH_0;
 
    // 変換許可
    ADC12CTL0 |= ENC;
 }
 
 // ADC変換を実行してアナログ値を取得 (12ビット: 0〜4095)
 unsigned int adc12_read(void)
 {
    // 変換開始
    ADC12CTL0 |= ADC12SC;
 
    // 変換完了を待機
    while (ADC12CTL1 & ADC12BUSY) {}
 
    return ADC12MEM0;
 }
 
 // Timer_Aの初期化 (ACLK=32768[Hz]、約1秒ごとに割り込み発生)
 void timera_init(void)
 {
    // TACTL: ACLK使用、分周 / 8、アップモード、タイマ割り込み有効
    TACTL = TASSEL_1 | ID_3 | MC_1 | TAIE;
 
    // TACCR0: 4096カウントで割り込み (32768Hz / 8 / 4096 ≒ 1秒)
    TACCR0 = 4096;
 }
 
 // Timer_A割り込みハンドラ
 #pragma vector=TIMERA1_VECTOR
 __interrupt void timera1_isr(void)
 {
    switch (TAIV) {
       case 10:   // オーバーフロー割り込み
          g_log_flag = 1;
          // LPM3からウェイクアップ
          LPM3_EXIT;
          break;
       default:
          break;
    }
 }


CSV形式データ書き込みとメインループ

センサデータをCSV形式でSDカードに書き込む実装と、LPM3を活用したメインループの例を以下に示す。

MSP430F149はRAMが2[KB]しかないため、バッファは小さく保ち、f_sync() を10回に1回の間引き呼び出しにすることでパフォーマンスを維持しつつデータ保全を図る。

 // グローバル変数 (スタック消費を避けるため)
 static FATFS g_fatfs;
 static FIL   g_fil;
 static unsigned int g_timestamp = 0;
 
 // CSVヘッダ書き込み (初回のみ呼び出す)
 FRESULT write_csv_header(void)
 {
    FRESULT res;
    UINT    bw;
    const char *header = "timestamp,sensor_value\n";
 
    res = f_open(&g_fil, "SENSOR.CSV", FA_CREATE_ALWAYS | FA_WRITE);
    if (res != FR_OK) return res;
 
    res = f_write(&g_fil, header, strlen(header), &bw);
    f_close(&g_fil);
    return res;
 }
 
 // センサデータをCSV形式で追記 (小さいバッファで実装)
 FRESULT log_sensor_data(unsigned int sensor_val)
 {
    FRESULT res;
    UINT    bw;
    char    line[24];   // "65535,4095\n" 程度に収まるサイズ
    int     len;
 
    // CSV行を生成 (sprintfでフォーマット)
    len = sprintf(line, "%u,%u\n", g_timestamp, sensor_val);
 
    // 追記モードでオープン
    res = f_open(&g_fil, "SENSOR.CSV", FA_OPEN_APPEND | FA_WRITE);
    if (res != FR_OK) return res;
 
    // データを書き込む
    res = f_write(&g_fil, line, (UINT)len, &bw);
    if (res != FR_OK) {
       f_close(&g_fil);
       return res;
    }
 
    // 10回に1回 f_sync() を呼び出してフラッシュ (間引き呼び出し)
    // 毎回呼び出すとパフォーマンスが低下するため適度に間引く
    if (g_timestamp % 10 == 0) {
       f_sync(&g_fil);
    }
 
    f_close(&g_fil);
    g_timestamp++;
    return FR_OK;
 }
 
 int main(void)
 {
    FRESULT      res;
    unsigned int sensor_val;
 
    // ウォッチドッグタイマを停止
    WDTCTL = WDTPW | WDTHOLD;
 
    // 各モジュールの初期化
    adc12_init();
    timera_init();
 
    // FatFsの初期化とマウント
    res = f_mount(&g_fatfs, "", 1);
    if (res != FR_OK) {
       while (1) {}   // マウント失敗時のエラー停止
    }
 
    // CSVヘッダを書き込む (初回のみ)
    write_csv_header();
 
    // グローバル割り込み有効化
    __enable_interrupt();
 
    // メインループ: LPM3で待機 --> タイマ割り込みでウェイクアップ --> データ記録
    while (1) {
       // LPM3で低消費電力モードに移行
       // Timer_A割り込み (g_log_flag=1、LPM3_EXIT) でウェイクアップ
       LPM3;
 
       // ウェイクアップ後の処理
       if (g_log_flag) {
          g_log_flag = 0;
 
          // ADCでセンサ値を読み取る
          sensor_val = adc12_read();
 
          // CSVファイルにデータを追記
          log_sensor_data(sensor_val);
       }
    }
 
    return 0;
 }



設定ファイルの読み込み

SDカードに保存された設定ファイル (簡易INI形式) を読み込み、RAMに展開する実装例を示す。

マイコン起動時に設定値をSDカードから読み込むことで、フラッシュ書き換えなしにパラメータ変更が可能になる。

INIファイルは "key=value" 形式の1行1設定とし、バッファを小さく保つことでMSP430F149の2[KB] RAM制約に対応する。
設定ファイルの例として、"interval=10"、"threshold=2048"、"filename=LOG.CSV" のような設定を想定する。

簡易INIパーサの実装

設定構造体の定義とINIファイル読み込み関数の実装例を以下に示す。

 #include "ff.h"
 #include <string.h>
 #include <stdlib.h>
 
 // 設定値を保持する構造体
 typedef struct {
    unsigned int interval;      // データ記録間隔 (秒)
    unsigned int threshold;     // ADCしきい値 (0〜4095)
    char         filename[13];  // ログファイル名 (8.3形式 + NULL)
 } AppConfig;
 
 // デフォルト設定値
 static AppConfig g_config = {
    10,           // interval: 10秒
    2048,         // threshold: 中間値
    "LOG.CSV"     // filename: デフォルトファイル名
 };
 
 // 1行のINIエントリをパースして設定構造体に格納する
 static void parse_ini_line(AppConfig *cfg, const char *line)
 {
    char key[16];
    char val[16];
    char *eq;
    int  i;
 
    // "=" の位置を探す
    eq = strchr(line, '=');
    if (eq == NULL) return;   // "=" がない行はスキップ
 
    // keyを抽出
    i = (int)(eq - line);
    if (i <= 0 || i >= (int)sizeof(key)) return;
    strncpy(key, line, (unsigned int)i);
    key[i] = '\0';
 
    // valueを抽出 (改行文字を除去)
    strncpy(val, eq + 1, sizeof(val) - 1);
    val[sizeof(val) - 1] = '\0';
    i = strlen(val);
    while (i > 0 && (val[i - 1] == '\n' || val[i - 1] == '\r')) {
       val[--i] = '\0';
    }
 
    // keyに応じて設定値を格納
    if (strcmp(key, "interval") == 0) {
       cfg->interval = (unsigned int)atoi(val);
    }
    else if (strcmp(key, "threshold") == 0) {
       cfg->threshold = (unsigned int)atoi(val);
    }
    else if (strcmp(key, "filename") == 0) {
       strncpy(cfg->filename, val, sizeof(cfg->filename) - 1);
       cfg->filename[sizeof(cfg->filename) - 1] = '\0';
    }
 }
 
 // SDカードからINI設定ファイルを読み込む
 // 設定ファイル例 (CONFIG.INI):
 //   interval=10
 //   threshold=2048
 //   filename=LOG.CSV
 FRESULT load_config_from_sd(AppConfig *cfg)
 {
    FRESULT res;
    UINT    br;
    char    line_buf[32];   // 1行分の小さなバッファ
    int     pos;
    char    c;
 
    // CONFIG.INIをオープン
    res = f_open(&g_fil, "CONFIG.INI", FA_READ);
    if (res != FR_OK) {
       // ファイルが存在しない場合はデフォルト設定を維持
       return res;
    }
 
    // 1文字ずつ読み込み、行単位でパース
    pos = 0;
    while (1) {
       res = f_read(&g_fil, &c, 1, &br);
       if (res != FR_OK || br == 0) break;   // EOF or エラー
 
       if (c == '\n' || c == '\r') {
          // 行末: パースして次の行へ
          if (pos > 0) {
             line_buf[pos] = '\0';
             parse_ini_line(cfg, line_buf);
             pos = 0;
          }
       }
       else if (c == '#' || c == ';') {
          // コメント行: 行末まで読み飛ばす
          while (1) {
             res = f_read(&g_fil, &c, 1, &br);
             if (res != FR_OK || br == 0 || c == '\n') break;
          }
          pos = 0;
       }
       else {
          // 通常文字: バッファに格納
          if (pos < (int)sizeof(line_buf) - 1) {
             line_buf[pos++] = c;
          }
       }
    }
 
    // 最終行 (改行なしの場合) をパース
    if (pos > 0) {
       line_buf[pos] = '\0';
       parse_ini_line(cfg, line_buf);
    }
 
    f_close(&g_fil);
    return FR_OK;
 }



メモリ最適化

MSP430F149の2[KB] RAM制約下でFatFsを動作させるためのメモリ最適化について説明する。

TINYモード (FF_FS_TINY=1) の適用が最も効果的であり、FIL構造体のサイズを552バイトから約40バイトに削減できる。

RAM使用量の内訳

下表に、TINYモードを有効にした場合のMSP430F149でのRAM使用量の内訳を示す。

MSP430F149でのRAM使用量内訳 (TINYモード)
用途 サイズ 備考
FATFS構造体 564バイト TINYモード、セクタバッファ含む
FIL構造体 40バイト TINYモード (通常モードは約552バイト)
スタック領域 最低256バイト 割り込みネスト考慮
アプリケーション用 残り約1,188バイト グローバル変数、バッファ等
合計 約2,048バイト (2[KB]) MSP430F149最大RAM


FF_FS_TINY効果の比較

下表に、FF_FS_TINY設定の有無によるメモリ使用量と動作の違いを示す。

FF_FS_TINY 設定効果の比較
項目 FF_FS_TINY = 0 (通常) FF_FS_TINY = 1 (TINY)
FIL構造体サイズ 約552バイト 約40バイト
セクタバッファ FIL構造体ごとに512バイト FATFS構造体と共有
複数ファイル同時オープン 高速 (各ファイルが専用バッファ) バッファ再読み込みあり
1ファイル運用 通常速度 ほぼ同等の速度


スタック使用量の監視

MSP430F149のスタック消費量を監視するには、マジックナンバー方式を使用する。
起動時にスタック領域の先頭に既知のパターン (例: 0xDEAD) を書き込み、実行後に上書きされた位置を確認することで最大スタック使用量を把握できる。
この方法は、オシロスコープや外部デバッガなしで実施できるため、MSP430F149のような組み込み環境に適している。

グローバル変数配置

FATFS構造体 (TINYモードで約564バイト) とFIL構造体 (約40バイト) は、必ずグローバル変数または静的変数として宣言する。
ローカル変数として宣言するとスタック上に確保されるため、スタックオーバーフローを引き起こす危険がある。

MSP430F149のスタックは2[KB] RAMの高位アドレス側に確保されており、FATFS構造体をローカル変数にすると容易にオーバーフローに達する。

動的メモリ割り当ての回避

MSP430F149のような小メモリ環境では、malloc() / free() の使用を避け、静的バッファ割り当てを徹底する。
動的メモリ割り当てを使用しない理由として、ヒープ管理オーバーヘッドの発生、ヒープ領域がRAMを消費すること、メモリフラグメンテーションのリスクがある。

静的バッファ割り当てでは、バッファサイズがコンパイル時に確定するため、RAM使用量が予測可能になる。

セクタバッファ共有の動作原理

TINYモードでは、FIL構造体から512バイトのセクタバッファが除外される。
ファイルの読み書き時には、FATFS構造体内のセクタバッファを一時的に借用する仕組みになっている。

複数のファイルを同時にオープンした場合、ファイル切り替え時にバッファを再読み込みする必要が生じる。

MSP430F149での1ファイルのみの運用 (データロガー等) では、バッファ再読み込みはほぼ発生しないため、パフォーマンスへの影響は無視できる。


Petit FatFS

Petit FatFSは、elm-chanが開発した極限メモリ環境向けのFatFsの軽量版である。
通常のFatFsよりもさらにRAM / ROM消費量が少なく、MSP430F149よりも小型のマイコン向けに設計されているが、MSP430F149でも応用できる。

特徴として、RAM要件は544バイト + 32バイト/ファイルであり、TINYモードのFatFs (約604バイト) よりも更に少ない。
対応ファイルシステムはFAT12、FAT16であり (FAT32の対応は部分的)、標準では読み取り専用で、PF_USE_WRITE = 1 を有効にすることで書き込みも可能になる。

ただし、同時にオープンできるファイルは1つのみという制限がある。

FatFs (TINYモード) と Petit FatFS の比較

下表に、FatFsとPetit FatFSの特徴の比較を示す。

FatFs (TINYモード) と Petit FatFS の比較
項目 FatFs (TINY) Petit FatFS
RAM使用量 約604バイト 約544バイト + 32バイト/ファイル
ROM使用量 約6〜8[KB] 約2〜4[KB]
対応ファイルシステム FAT12、FAT16、FAT32、exFAT FAT12、FAT16 (FAT32は部分対応)
読み取り 対応 対応
書き込み 対応 オプション (PF_USE_WRITE = 1)
同時オープンファイル数 複数可 1ファイルのみ


使い分け指針

FatFsとPetit FatFSのどちらを選択するかは、アプリケーションの要件による。

Petit FatFSを選択する場合:

  • アプリケーション用RAMを更に確保したい場合
    FatFsのRAM削減が不十分な場合に有効
  • 読み取り専用の用途 (設定ファイルの読み込み等) の場合
    書き込みが不要であれば、Petit FatFSの機能制限は問題にならない
  • FAT12 / FAT16のみ使用する場合
    SDカードを8[GB]クラス以下でFAT16フォーマットして使用する場合


FatFs (TINYモード) を選択する場合:

  • データのSDカードへの読み書き両方が必要な場合
    データロギングのように書き込みを伴うアプリケーション
  • FAT32フォーマットのSDカードを使用する場合
    大容量SDカードはFAT32でフォーマットされていることが多い



エラーハンドリング

FatFsを使用するアプリケーションでは、適切なエラーハンドリングが重要である。

MSP430F149のような組み込みシステムでは、エラーの早期検出とリカバリ処理が長時間安定動作の鍵となる。

FRESULTエラーコードの確認

FatFsの全API関数は FRESULT 型のエラーコードを返す。
全ての関数呼び出し後に戻り値を確認する習慣をつけることを推奨する。

以下に、エラーコードを文字列に変換するデバッグ用関数と、戻り値チェックのパターン例を示す。

 // エラーコードを文字列に変換するデバッグ用関数
 const char *fresult_str(FRESULT res)
 {
    switch (res) {
       case FR_OK:              return "OK";
       case FR_DISK_ERR:        return "DISK_ERR";
       case FR_INT_ERR:         return "INT_ERR";
       case FR_NOT_READY:       return "NOT_READY";
       case FR_NO_FILE:         return "NO_FILE";
       case FR_NO_PATH:         return "NO_PATH";
       case FR_INVALID_NAME:    return "INVALID_NAME";
       case FR_DENIED:          return "DENIED";
       case FR_WRITE_PROTECTED: return "WRITE_PROTECTED";
       case FR_NO_FILESYSTEM:   return "NO_FILESYSTEM";
       default:                 return "UNKNOWN";
    }
 }
 
 // 戻り値チェックのパターン例
 FRESULT safe_open_and_write(const char *filename, const char *data)
 {
    FRESULT res;
    UINT    bw;
 
    res = f_open(&g_fil, filename, FA_OPEN_APPEND | FA_WRITE);
    if (res != FR_OK) {
       // エラーコードをUARTで出力して原因を特定 (デバッグ時)
       // uart1_puts(fresult_str(res));
       return res;
    }
 
    res = f_write(&g_fil, data, strlen(data), &bw);
    f_close(&g_fil);
 
    if (res != FR_OK) return res;
    if (bw != strlen(data)) return FR_DISK_ERR;
 
    return FR_OK;
 }


リトライロジックとリカバリ

SDカード通信は物理的な要因で一時的にエラーが発生することがある。

最大3回のリトライとf_mount解除 --> 再マウントによる復帰処理を実装することにより、一時的な通信エラーを回避できる。

 #define MAX_RETRY_COUNT  3
 
 // リトライロジック付きファイル書き込み
 // エラー発生時はf_mountアンマウント→再マウントで復帰を試みる
 FRESULT write_with_retry(const char *filename, const void *data, UINT size)
 {
    FRESULT res;
    UINT    bw;
    int     retry;
 
    for (retry = 0; retry < MAX_RETRY_COUNT; retry++) {
       res = f_open(&g_fil, filename, FA_OPEN_APPEND | FA_WRITE);
       if (res != FR_OK) {
          // マウントを解除して再マウント (リカバリ処理)
          f_mount(NULL, "", 0);
          __delay_cycles(160000);   // 約10ms (16MHz動作時) 待機
          f_mount(&g_fatfs, "", 1);
          continue;
       }
 
       res = f_write(&g_fil, data, size, &bw);
       f_close(&g_fil);
 
       if (res == FR_OK && bw == size) {
          return FR_OK;   // 書き込み成功
       }
    }
 
    return FR_DISK_ERR;   // リトライ上限に達した
 }



トラブルシューティング

SD初期化失敗

症状: disk_initialize()STA_NOINIT を返す。

原因として以下が考えられる:

  • SPIクロック設定の不正 (初期化時は100〜400kHz以下が必要)
    SDカード初期化時のSPIクロックを400kHz以下に設定し、初期化完了後に通常速度 (1〜2MHz) へ変更する
  • 74クロック以上のダミークロックが不足している
    SDカードのパワーアップ後、CS=HIGHの状態で80クロック以上のダミークロックを送信してからCMD0を発行する
  • CMD0/CMD8の応答が異常 (0xFF応答が続く、またはタイムアウト)
    DO (MISO) ラインに10kΩのプルアップ抵抗を追加する
    CS信号をLowにした後、少なくとも1バイトのダミークロックを送信してからコマンドを送信する


f_mount失敗

症状: f_mount()FR_NO_FILESYSTEM または FR_NOT_READY を返す。

原因として以下が考えられる:

  • ブートセクタの読み込み失敗
    disk_initialize() の戻り値を確認してSPI初期化が成功しているか確認する
  • FAT形式が不正
    SDカードをFAT16またはFAT32でフォーマットし直す
    exFATフォーマットは FF_FS_EXFAT=0 の設定では認識できないため注意する


f_open失敗

症状: f_open()FR_NO_FILE または FR_NO_PATH を返す。

原因として以下が考えられる:

  • ファイル名の形式が不正 (8.3形式違反)
    LFN無効 (FF_USE_LFN=0) の設定では、8.3形式 (最大8文字+"."+3文字拡張子) のファイル名のみ使用できる
    小文字は内部的に大文字に変換されるが、記号や日本語は使用不可
  • パスの記述が不正
    ルートディレクトリのファイルは "/" を付けずに "FILENAME.TXT" と記述する
    サブディレクトリを使用する場合は "DIR/FILE.TXT" のように "/" で区切る


データ破損

症状: 書き込んだデータが欠落または破損している。

対処方法:

  • 重要なデータを書き込んだ後、必ず f_sync() を定期的に呼び出してバッファをフラッシュする
    f_close() は内部で f_sync() を呼び出すが、長時間ファイルを開いたままにする場合は定期的な f_sync() が必要
  • 電源断対策として、重要な書き込み後に f_sync() を確実に呼び出す設計にする


LPM復帰後エラー

症状: LPM3からの復帰後にSPI通信が失敗する。

LPM3ではDCO (デジタル制御発振器) が停止するため、復帰後にDCOの再安定化を待つ必要がある。
以下にLPM3復帰後のSPI再初期化コードを示す。

 // LPM3ウェイクアップ後のSPI再初期化
 // DCOが再安定化するまで待機してからUSART0を再設定する
 void spi_reinit_after_lpm(void)
 {
    // DCOの再安定化を待つ (6マイクロ秒以上)
    __delay_cycles(100);   // 約6us (16MHz動作時)

    // USART0のSPIモードを再設定
    U0CTL  |= SWRST;                  // リセット状態に設定
    U0CTL   = SWRST | CHAR | SYNC | MM;
    U0TCTL  = SSEL1 | STC | CKPL;     // SMCLK使用、3ピンSPI、クロック極性設定
    U0CTL  &= ~SWRST;                 // リセット解除
 }


SPI不安定

症状: 不定期にSPI通信エラーが発生する。

対処方法:

  • MISO (DO) ラインのプルアップ抵抗 (10kΩ) を確認する
    プルアップが不足するとMISO信号が不定になり、受信データが化ける
  • SDカードのVCCラインにデカップリングコンデンサ (0.1μF + 10μF) を追加する
    電源ノイズがSPI通信に影響する場合がある
  • SDカードとMSP430F149間の配線長を短縮する
    長い配線はSPI信号に反射を引き起こし、通信エラーの原因になる



デバッグ方法

外部デバッガが使用できない環境でも、UART、GPIO、オシロスコープを組み合わせることで効果的なデバッグが可能である。

UARTログ

MSP430F149のUSART1をUARTモード (9600[bps]) で使用して、デバッグメッセージを出力する方法が有効である。

UARTログで出力すべき内容
項目 説明
FRESULTエラーコードの出力 各FatFs API呼び出しの戻り値を fresult_str() で文字列に変換してUART送信する。
エラー発生箇所の特定に直接役立つ。
disk_initialize() の各ステップのログ出力 CMD0応答コード、CMD8応答コード、ACMD41の完了状況を出力することにより、
初期化シーケンスの問題箇所を特定できる。
RAM使用量の表示 スタックモニタリングのマジックナンバー方式と組み合わせ、残余スタック量をUARTで定期出力する。


GPIOデバッグ

未使用のGPIOピンをデバッグ出力として活用する方法は、UARTが使用できない場合やリアルタイム性が必要な場合に有効である。

GPIOデバッグの活用方法
項目 説明
未使用GPIOピンをトグルして処理のタイミングを可視化する disk_read()disk_write() の開始・終了時にGPIOをトグルし、
オシロスコープでSPI転送時間を測定できる。
CS信号のアサート / デアサートタイミングを確認する CS信号波形が正しくなければSDカードはコマンドを受け付けない。
LED点灯によるエラー通知 f_mount()f_open() が失敗した際にLEDを点灯させることで、
組み込み機器のフィールドデバッグに活用できる。


SPI波形確認

オシロスコープまたはロジックアナライザを使用したSPI波形の検証は、通信レベルの問題解析に不可欠である。

オシロスコープで確認する項目
確認項目
CLK (UCLK) 周波数が想定値 (初期化時400[kHz]以下、通常動作時1〜2[MHz]) か確認する。
MOSI (SIMO) のデータが正しいSDカードコマンドフォーマットになっているか確認する。
MISO (SOMI) のプルアップが有効で、不定状態 (フロート) になっていないか確認する。
CS信号がコマンド送信の前後で正しくLow / Highに遷移しているか確認する。


ロジックアナライザで確認する項目
確認項目
SDカードコマンド (CMD0、CMD8、ACMD41等) のバイト列が正しい形式か確認する。
SDカードからの応答バイト (R1、R3等) の内容を解析する。
disk_initialize() 関数のシーケンス全体を記録して、どのコマンドで失敗しているか特定する。



参考リンク