質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.80%

microSDの読み書き時間について

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,932

Alyn

score 40

前提・実現したいこと

ArduinoでmicroSDに文字の読み書きをしています。
その際の処理時間に差があることに気づきました。
それは初回読み書き時と512byte読み書きするごとに処理時間の遅延が発生していました。

1byte書き込みは通常48us程度で終わるが、2500us以上の処理時間を要していました。
これは読み込みと書き込みに共通して起こる現象でした。
そこで以下の質問です。

質問①:初回読み書き時に遅延するのは何故か。

質問②:512byte読み書きするごとに遅延するのは何故か。

質問③:上記2つの遅延を100us以内に処理時間を減らす方法はないのか。(100us以内でなくても処理時間を減らす方法があれば)

ソースコード

#include <SD.h>
#include <SPI.h>
#include <mcp_can.h>
#include <mcp_can_dfs.h>

const byte SD_SS = 4;
String fname = "SDwrite.txt";
File file;

void setup()
{
    unsigned long count = 1;
    unsigned long s_time = 0;
    unsigned long e_time = 0;
    unsigned long c_buf[50] = { 0 };
    unsigned long t_buf[50] = { 0 };

    Serial.begin(57600);
    if (!SD.begin(SD_SS))
    {
        Serial.println("SD_FAIL");
        return;
    }

    file = SD.open(fname, FILE_WRITE);    //ファイル新規作成、書き込みモード
    for (int i = 0; i < 50; i++)
    {
        bool flg = 0;
        while (!flg)
        {
            s_time = micros();        //起動経過時間をマイクロ秒(us)で返す
            file.print('1');        //1byte書き込み
            e_time = micros();        //起動経過時間をマイクロ秒(us)で返す
            if ((e_time - s_time) >= 60)
            {
                c_buf[i] = count;
                t_buf[i] = (e_time - s_time);
                flg = 1;
            }
            count++;
        }
    }
    file.close();

    for (int j = 0; j < 50; j++)
    {
        Serial.print(c_buf[j]);
        Serial.print('\t');
        Serial.println(t_buf[j]);
    }

}

void loop()
{
    //何もしない
}

実行結果(シリアルモニター)

1    19468
513    2816
1025    2516
1537    2520
2049    2516
-----省略-----
23553    2536
24065    2532
24577    2548
25089    2544

補足情報

・Arduino Uno R3
・Seeed Studio CAN-BUSシールドV2
・SAMSUNG microSD HC 32GB

今後やりたいこと

400us間隔で受信するCAN信号のログ保存を考えています。ログデータは1信号辺り21byteです。質問にある書き込み遅延の影響でログの取得漏れが起こっています。ログ保存の方法は文字型配列(buf)にデータを入れてからまとめてfile.print(buf);する方法で行います。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+1

前提:SDカードに使われるFLASH EEPROMデバイスは、消去や書き込みに10mS程度の時間がかかります。また、これらの操作はある大きさのブロック単位(デバイスによって異なるが、512byte~16KBとか)で行われます。

SDカードライブラリの中身を読めばより確かなことがわかると思いますが、

質問①:初回読み書き時に遅延するのは何故か。 

ファイルの管理情報を生成、書き込みするからかと思われます。

質問②:512byte読み書きするごとに遅延するのは何故か。 

書き込みのコストが高いので、1byteづつには書き込みを行わずRAM上のバッファに保持しているのでしょう。だから、1byteの書き込み命令はすぐにリターンします。
512byte分データが溜まったところで、一気に書き込みを行います。ファイルの管理情報の更新と、データ本体の書き込みを行います。2.5秒はちょっと時間がかかりすぎな気もしますが、ライブラリの作りの問題かと思います。

質問③:上記2つの遅延を100us以内に処理時間を減らす方法はないのか。

ライブラリのソースコードを覗いて、改造してみては。
メモリーに余裕があるのなら、512byteのバッファをもう一面確保して、タイマー割り込み等をうまく使いながらバックグラウンドで書き込みを行うようにすると見た目の動作はずいぶん早く出来るかも知れません。(512byteの書き込みが終わる前に次の512byteのバッファが一杯になってしまうようなデータ量だと破綻しますが)


コメント欄でのやり取りが煮詰まってきたので、解決案っぽいことをこちらに追記します。

作業を開始してから完了するまで2.5msかかる、という時間自体は動かないと思います。
しかし、2.5ms本当にCPUを占拠し続けなければ出来ない仕事なのかどうか、という点で検討の余地があるでしょう。

いわゆるマルチタスクな構成にすればよさそうです。しかし。ありモノのライブラリで(言い方は悪いですが)でっち上げるArduino的な手法では無理と言っていいのでは。Arduinoは、マイコンの知識があまりなくてもそこそこ使える、という開発環境と私は理解していますが、その範疇から外れます。マイコン本来の使い方に立ち返って、タスク設計とかをある程度やらないといけないでしょう。

そこで、方針を転じて、物量で解決する、という手はいかがでしょう。
Arduinoを2台使います。1台はCANの通信を行い、ロギングすべきデータをバッファリングし、シリアル/I2C/SPIなど適当な通信手段で吐き出します。もう1台は、CAN側が出したデータを受信し、SDカードに書き込む仕事をします。(シリアルはダメかな。115Kbaudでも1byte100us弱だから、21byte/400usには足りない)
CAN側のバッファは、SDカードアクセス中の2.5msに湧いてくるデータを貯め込むのに十分な量を確保しておきます。そして、SDカード側がデータを受け取れることを確認しながらデータの流し込みを行います。
これなら、Arduinoの範囲内でなんとかなりそうな気がします。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/05 21:11

    あう>桁違い

    キャンセル

  • 2019/05/05 22:20

    読み書きすることに2.5msかかってしまうことは避けられないのでしょうか?
    512byte読み書きを1ms未満にすることも難しそうですね…

    キャンセル

  • 2019/05/10 21:02

    シリアルバッファを拡張すればなんとかできそうな気がします!

    キャンセル

0

thkanaさんの回答でだいたいあっていると思いますが少々補足で。

SD(HC/XC含む)カードは、512バイトが読み書きの最小単位です。(初期には1バイト単位のものもあったようだが)
これはFlashメモリの構造とは関係なく、Flashのブロックが数KBであっても内部でバッファして512バイト単位での読み書きを実現します。
ArduinoのSDライブラリが512バイト単位で実際の読み書きをするのはそのためです。

SDの読み書き自体は初期化時以外はいつでも中断してよいので、
2つのバッファを交互に使い、書くべき値を作る処理はタイマー割り込みで行い、メインでは512バイト溜まったらSDに書き出すという形でたぶんできると思います。
ArduinoのSDライブラリでは割り込みは使っていませんので、ライブラリ自体を書き換えなくてもいけるはずです。


気になったので試してみました。
割り込みを利用してのバックグラウンドでの書き込み自体はどうやら成功しました。
・400us間隔のタイマー割り込みでバッファに値を溜める
・メインのコードでは「バッファを切り替え、書き込んでいない側のバッファをSDに書き出す」を繰り返す
という処理で、SDには値が400us間隔で生成されているような書き込みがなされました。
(なお考えてみれば512バイト待つ必要も無かったのと容量を食うのでバッファは256バイトにした)

ただ、プログラムの挙動は異常で、setup()がループしてしまっています。どこかで割り込みが使われていて干渉しているのかもしれません(SDライブラリ内か、あるいはSerialか、microsも怪しい)。あるいは何かつまらないミスがあるかもしれません。
とりあえず思っていた処理はできそうだと分かったので私は満足です。

#include <SD.h>
#include <SPI.h>

const byte SD_SS = 4;
String fname = "SDwrite.txt";
File file;

unsigned long time_last = 0;
unsigned long time_now = 0;

byte buf[2][256] = {0};
int count[2] = {0};
byte phase = 0;
int i = 0;

ISR(TIMER2_COMPA_vect)
{
  time_now = micros();
  i++;
  unsigned long time_diff = time_now-time_last;
  buf[phase][count[phase]  ] = time_diff&0xFF;
  buf[phase][count[phase]+1] = (time_diff>> 8)&0xFF;
  buf[phase][count[phase]+2] = (time_diff>>16)&0xFF;
  buf[phase][count[phase]+3] = i&0xFF;
  count[phase]+=4;
  time_last = time_now;
}

void setup()
{
    TCCR2A = 0b10000010;
    TCCR2B = 0b00000011; // clk/32
    OCR2A = 199; // 400usで割り込み
    TIMSK2 = 0b00000010;
    Serial.begin(57600);

    unsigned long temp = 0L;

    if (!SD.begin(SD_SS))
    {
        Serial.println("SD_FAIL");
        return;
    }

    Serial.println("start");
    file = SD.open(fname, FILE_WRITE);    //ファイル新規作成、書き込みモード
    sei();

    byte buf_x[100] = {0};
    int num = 100;

    while (true)
    {
        if(micros() > 5000000L)
        {
          break;
        }
        phase ^= 1;
        temp += count[phase^1];
        if(count[phase^1] != 0)
        {
          file.write(buf[phase^1], count[phase^1]);
          count[phase^1] = 0;
        }
    }
    file.close();

    Serial.println("close");
    Serial.println(i);
    Serial.println(temp);
}

void loop()
{
    //何もしない
}


追記:
割り込みとSD出力の参考にしたページです。あとデータシートも。
https://qiita.com/suzukinori/items/939cc9f49e535c4eadd7
http://usicolog.nomaki.jp/engineering/avr/avrInterrupt.html
https://garretlab.web.fc2.com/arduino_reference/libraries/standard_libraries/SD/File/write.html

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/04 20:11

    回答ありがとうございます。
    Arduino初心者でタイマー割り込みに関する処理がよくわからないので、2つのバッファがどのような処理をするのか詳しく教えていただけませんか?

    キャンセル

  • 2019/05/05 20:08

    あまりしっかりと考えているわけではありませんが、単純に、バッファが1つだとSDへの書き込み中にデータを取得することができないので、もう1つバッファを用意してやればそちらに書き込む間にもう一方からSDへ書き込めるという考えです。既にライブラリ内に1つバッファがあるはずなのでもう1個用意するだけでよいかもしれません。

    キャンセル

  • 2019/05/05 22:24

    Arduinoはファイル書き込み(512byte)と信号入出力を同時並行に行うことができるのでしょうか?

    キャンセル

  • 2019/05/05 22:36

    SDの読み書きは自由に中断でき、ArduinoのSDライブラリでは割り込みを使っている箇所が見当たらないので、できるはずだと思っています。もっとも、試したことはありません。

    キャンセル

0

既に回答がついていて、この辺のハードウェアがらみの処理ってほとんど、手を出すところが少ない、ですね、、と。(この辺の処理は困った時しか見ない)

一般的方法としては、512byte単位での処理との事なので、読み書きを極力、512byte単位にまとめる、てのが定番かと。
また、書込みを printで行っていますが、writeで行い、512byteに合わせる。
SDライブラリの実装は分かりませんが、Unixとかのライブラリだと、違いがあります。通常のLinux/Unix環境では誤差。ただ、μ秒を気にするならば、検討の価値はあると思います。

実際の環境がよく分からないので、参考までに。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/05 12:56

    回答ありがとうございます。

    今後のことを見据えるとmicroSDにCAN信号のログを取りたいと考えています。
    CAN信号は21byte、それに時刻を加えて1信号辺り30byte程度の書き込みを400us以内に行いたいです(CAN信号が400us周期で来るため)。

    書き込み方法は文字型配列にデータをまとめて一気にprintする方法を考えています。

    512byte単位で行うと400usを超えると思われます……

    キャンセル

  • 2019/05/05 18:11

    512byte単位での書込みは難しいってことですね。
    正確なところは、ライブラリの中、見るしかないのですが、Unix系でのライブラリだと、(ライブラリ内での余分な処理が無い分)write()の方が若干、速かったです。似たような関数でも、速度比較をお勧めします。(特に、今回みたいな、少しでも速くの場合)

    キャンセル

  • 2019/05/05 22:15

    512byte問題がなければ速度上全く問題なくログ取得できるのですけどねぇ。
    マイコン本体にメモリがたくさんあれば受信したCAN信号をテスト終了後にまとめて書き込めるのですが、信号数が膨大すぎて難しいです…

    キャンセル

0

SDIOライブラリを利用することをお勧めします。
STM32F407VET6で32byteのデータを1000回の書き込み試験を行いましたが全体で124835us
1回の書き込みの最大値が1925us、最小値が3usでした。

F103CではRET6辺りから利用可能(詳しく確認はしてません)だそうです。

512byteのデータを1000回の書き込み試験を追加で行いましたが、
AVE:1219us、MAX:44252usで2000us超過は44252us1回のみでした。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/06 00:36

    回答ありがとうございます。

    ここで言うところの512byte問題は発生しないのでしょうか?
    またSDIOライブラリをダウンロードできるページを教えていただけませんか?

    キャンセル

  • 2019/05/06 04:55 編集

    再度確認しましたが、msとusを間違えていました。申し訳ありません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る