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

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

新規登録して質問してみよう
ただいま回答率
85.36%
Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

Q&A

解決済

4回答

11234閲覧

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

Alyn

総合スコア51

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

0グッド

1クリップ

投稿2019/05/04 04:39

編集2019/05/05 03:57

前提・実現したいこと

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

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

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

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

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

ソースコード

Arduino

1#include <SD.h> 2#include <SPI.h> 3#include <mcp_can.h> 4#include <mcp_can_dfs.h> 5 6const byte SD_SS = 4; 7String fname = "SDwrite.txt"; 8File file; 9 10void setup() 11{ 12 unsigned long count = 1; 13 unsigned long s_time = 0; 14 unsigned long e_time = 0; 15 unsigned long c_buf[50] = { 0 }; 16 unsigned long t_buf[50] = { 0 }; 17 18 Serial.begin(57600); 19 if (!SD.begin(SD_SS)) 20 { 21 Serial.println("SD_FAIL"); 22 return; 23 } 24 25 file = SD.open(fname, FILE_WRITE); //ファイル新規作成、書き込みモード 26 for (int i = 0; i < 50; i++) 27 { 28 bool flg = 0; 29 while (!flg) 30 { 31 s_time = micros(); //起動経過時間をマイクロ秒(us)で返す 32 file.print('1'); //1byte書き込み 33 e_time = micros(); //起動経過時間をマイクロ秒(us)で返す 34 if ((e_time - s_time) >= 60) 35 { 36 c_buf[i] = count; 37 t_buf[i] = (e_time - s_time); 38 flg = 1; 39 } 40 count++; 41 } 42 } 43 file.close(); 44 45 for (int j = 0; j < 50; j++) 46 { 47 Serial.print(c_buf[j]); 48 Serial.print('\t'); 49 Serial.println(t_buf[j]); 50 } 51 52} 53 54void loop() 55{ 56 //何もしない 57}

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

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);する方法で行います。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

前提: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/04 06:12

編集2019/05/06 00:57
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Alyn

2019/05/04 06:39

回答ありがとうございます。 今後のことを見据えると書き込みは100us間隔で3時間ほどできるようにしたいと考えています。 データの読み込みの時も同様の処理時間なのですが、512byteまとめて読み込んで、512byte読み込みが終わったら次の512byte読み込むから処理時間が長いのでしょうか?
thkana

2019/05/04 14:25

読み込みでも同じ時間とすると、単にSDカードとのやりとりに時間がかかっているんじゃないか、という推測も出てきますが、2.5秒はちょっとかかりすぎのように思います。 調べるとなるとライブラリの中を覗いてみることになるかしら。(SDカードの通信線をオシロなどで見てみるのも一つの手ですが)
ikadzuchi

2019/05/05 11:05

2500usは2.5秒でなく2.5msですね。
thkana

2019/05/05 12:11

あう>桁違い
Alyn

2019/05/05 13:20

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

2019/05/10 12:02

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

0

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

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

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

投稿2019/05/05 15:32

編集2019/05/05 20:47
mappy

総合スコア65

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Alyn

2019/05/05 15:36

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

2019/05/05 20:22 編集

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

0

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

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

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

投稿2019/05/04 11:38

pepperleaf

総合スコア6385

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Alyn

2019/05/05 03:56

回答ありがとうございます。 今後のことを見据えるとmicroSDにCAN信号のログを取りたいと考えています。 CAN信号は21byte、それに時刻を加えて1信号辺り30byte程度の書き込みを400us以内に行いたいです(CAN信号が400us周期で来るため)。 書き込み方法は文字型配列にデータをまとめて一気にprintする方法を考えています。 512byte単位で行うと400usを超えると思われます……
pepperleaf

2019/05/05 09:11

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

2019/05/05 13:15

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

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も怪しい)。あるいは何かつまらないミスがあるかもしれません。
とりあえず思っていた処理はできそうだと分かったので私は満足です。

Arduino

1#include <SD.h> 2#include <SPI.h> 3 4const byte SD_SS = 4; 5String fname = "SDwrite.txt"; 6File file; 7 8unsigned long time_last = 0; 9unsigned long time_now = 0; 10 11byte buf[2][256] = {0}; 12int count[2] = {0}; 13byte phase = 0; 14int i = 0; 15 16ISR(TIMER2_COMPA_vect) 17{ 18 time_now = micros(); 19 i++; 20 unsigned long time_diff = time_now-time_last; 21 buf[phase][count[phase] ] = time_diff&0xFF; 22 buf[phase][count[phase]+1] = (time_diff>> 8)&0xFF; 23 buf[phase][count[phase]+2] = (time_diff>>16)&0xFF; 24 buf[phase][count[phase]+3] = i&0xFF; 25 count[phase]+=4; 26 time_last = time_now; 27} 28 29void setup() 30{ 31 TCCR2A = 0b10000010; 32 TCCR2B = 0b00000011; // clk/32 33 OCR2A = 199; // 400usで割り込み 34 TIMSK2 = 0b00000010; 35 Serial.begin(57600); 36 37 unsigned long temp = 0L; 38 39 if (!SD.begin(SD_SS)) 40 { 41 Serial.println("SD_FAIL"); 42 return; 43 } 44 45 Serial.println("start"); 46 file = SD.open(fname, FILE_WRITE); //ファイル新規作成、書き込みモード 47 sei(); 48 49 byte buf_x[100] = {0}; 50 int num = 100; 51 52 while (true) 53 { 54 if(micros() > 5000000L) 55 { 56 break; 57 } 58 phase ^= 1; 59 temp += count[phase^1]; 60 if(count[phase^1] != 0) 61 { 62 file.write(buf[phase^1], count[phase^1]); 63 count[phase^1] = 0; 64 } 65 } 66 file.close(); 67 68 Serial.println("close"); 69 Serial.println(i); 70 Serial.println(temp); 71} 72 73void loop() 74{ 75 //何もしない 76}

追記:
割り込みと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 10:14

編集2019/05/07 23:55
ikadzuchi

総合スコア3047

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Alyn

2019/05/04 11:11

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

2019/05/05 11:08

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

2019/05/05 13:24

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

2019/05/05 13:36

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問