greiman / SdFat

Arduino FAT16/FAT32 exFAT Library
MIT License
1.05k stars 496 forks source link

Sdfat on stm32 if sd card removed stm32 crash #456

Open roque-canales opened 7 months ago

roque-canales commented 7 months ago

Hello,

Using standard sdfat lib with stm32 on arduino,

when sdcard is bad quality if finish to cause arduino crash, or if using genuine sandisk and removing it from the slot cause arduino crash.

What I see per crash:

First program is blocked.

Secondly, it continue to run, but the timers are completly crazy.... so the program run very quickly but the i2c instances or uarts ports no work anymore.

How to solve this issue ?

Can we activate non blocking feature on sdfat library?

greiman commented 7 months ago

Which STM32 board package are you using?

There is a new version 2.7.1 of the STMicroelectronics package. Are you using it?

greiman commented 7 months ago

I tried the new 2.7.1 version with several examples and it works with a Nucleeo L746RG.

Here is the bench example output:

FreeStack: 94952 Type is exFAT Card size: 64.09 GB (GB = 1E9 bytes)

Manufacturer ID: 0X1B OEM ID: SM Product: EC1S5 Revision: 3.0 Serial number: 0X158D576A Manufacturing date: 11/2020

FILE_SIZE_MB = 5 BUF_SIZE = 512 bytes Starting write test, please wait.

write speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 1266.38,2263,401,402 1266.06,2431,401,402

Starting read test, please wait.

read speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 1165.43,449,437,437 1165.43,449,437,437

Done

greiman commented 7 months ago

I tried with no card:

FreeStack: 94952 begin() failed Do not reformat the SD. No card, wrong chip select pin, or wiring error? SdError: 0X1,0XFF

I pulled a card during write and the error was reported.

write speed and latency speed,max,min,avg KB/Sec,usec,usec,usec error: write failed SdError: 0X21,0XFF

I pulled card during read:

Read speed and latency speed,max,min,avg KB/Sec,usec,usec,usec error: data check error

So no crash for the examples I tried. Just an error return.

roque-canales commented 7 months ago

Ok thank lot for you for your report maybe I do something wrong.

code I use is this one:

https://github.com/greiman/SdFat/blob/master/examples/examplesV1/dataLogger/dataLogger.ino

Im using 2.7.1 but also I test all version from 2.4.0, with the same behavior..

Does your test is done using spi ? Or sdio?

On my application I use spi on blackpill stm32f411ceux… and sandisk 32gb fat32.

greiman commented 7 months ago

Ok thank lot for you for your report maybe I do something bad.

tomorrow I give you the code I use for you check whats happening..

I don't debug user code. Don't give me your code. In the past I looked at user code but I discovered about 95% of the time it was a user bug. I have waste endless hours finding user bugs.

Post a simple example with only the SdFat code that shows the failure.

Does your test is done using spi ? Or sdio?

SPI, I don't support SDIO for the STMicroelectronics Arduino package. The SDIO controller/driver on the STM32F411 is not worth the effort.

SPI is also junk on the STM32 Arduino package. It does not use DMA so I can get 4-5 times the performance on other boards that have a good SPI library with DMA.

roque-canales commented 7 months ago

I understand,

tomorrow i give you the simple sdfat example that lead to the crash.

Rgds.

greiman commented 7 months ago

I found an old Nucleo F411RE. This board has almost the same chip as the Black Pill F411. Nucleo has the 64 pin version and the Black Pill has the 48 pin version. Both have 512K byte flash and 128K bytes RAM.

I did the above tests. I did two configurations for the bench example, the default and one optimized for 2.7.1.

The optimized configuration changes these values in SdFatConfig.h:

#define SPI_DRIVER_SELECT 1  // was zero
#define USE_SPI_ARRAY_TRANSFER 2  // was zero

Here is the output

FreeStack: 127864 Type is exFAT Card size: 64.09 GB (GB = 1E9 bytes)

Manufacturer ID: 0X1B OEM ID: SM Product: EC1S5 Revision: 3.0 Serial number: 0X158D576A Manufacturing date: 11/2020

FILE_SIZE_MB = 5 BUF_SIZE = 512 bytes Starting write test, please wait.

write speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 1566.32,8219,323,325 1571.73,2496,323,324

Starting read test, please wait.

read speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 1537.89,338,331,332 1537.89,337,331,331

No crashes or hangs when removing the SD.

greiman commented 7 months ago

I have been using Pi Pico RP2040 overclocked at 250 MHz in some projects. I developed RP2040 PIO SPI and PIO SDIO libraries optimized for SdFat.

Can't beat this performance for a $4,00 board. It is easy to use both cores with the Arduino package.

It is a shame ST does not improve their STM32 package. There is a lot to like about STM32.

With PIO SPI bench runs 4.6 times faster on the RP2040 than the STM32F411.

Here is bench on the RP2040 using PIO SPI at 63 MHz.

write speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 7173.60,91,70,71 7173.60,91,70,71

Starting read test, please wait.

read speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 7142.86,108,70,71 7173.60,94,70,71

With PIO SPI the factor is 16.4 times faster than STM32F411 SPI.

Here is bench on the RP2040 using PIO SDIO:

write speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 25641.03,4755,38,39 25706.94,4709,38,39

Starting read test, please wait.

read speed and latency speed,max,min,avg KB/Sec,usec,usec,usec 25239.78,903,39,40 25239.78,1235,39,40

roque-canales commented 7 months ago

Hello Bill, y resolved my instability issue selecting shared SPI.

Can i call file.sync only when I close the system?

i see that when i call sync at each loop, this take lot more time than without.

can this cause instability ?

I write on the sd 50kB/s

Also thank you for the library parameter update for 2.7.1, I will try tomorow.

and about the Rp2040 its no enough for my project (7 adc+ spi + 3 i2c + 2 uarts + 6 dio)

I set spi clock to 18mhz it seems to be the fastest possible speed on the f411, can you confirm that?

Rgds

greiman commented 7 months ago

You must use shared SPI if you have more than one device on a SPI bus.

I set spi clock to 18mhz it seems to be the fastest possible speed on the f411, can you confirm that?

The ST SPI driver is junk, it does not use DMA. Here is a typical logic analyzer of SPI on STM32L4 at 40 MHz.:

Stm32L4 SPI

The SPI bus is idle most of the time. The driver uses a simple byte-at-a-time loop with huge gaps between bytes.

In the bench tests I requested 50 MHz SPI. The library should use the highest rate <= 50 MHz supported by the chip's SPI config.

Can i call file.sync only when I close the system?

You never need to call sync(). The main use of sync() is to do the equivalent of a close() so if a crash happens before the next write no data will be lost.

If you use shared SPI or call sync() you will get very poor performance. It should not cause instability. Faster SPI clock will not help much.

Write speed mainly depends on dedicated SPI access to an SD card. Read speed also suffers but not as much. Each read call still needs to read an entire flash page so you could read an internal 128 KiB flash page for each 512 byte transfer.

SD cards emulate 512 byte sectors in flash pages that are a multiple of 16 KiB. Buying a high end card with huge flash pages may slow performance.

To optimize write speed you must maintain access to the SD. If you release access, the entire page is programmed. If you write another 512 byte virtual sector, data must be moved to the a new flash page. The result can be 400 KB/sec or less write performance for a 90 MB/sec SD. There may be 200 MB/sec internal data movement in the SD and extreme flash wear.

sync() causes a huge amount of extra I/O. The data buffers are written to the SD, the directory entry is read, updated and written.

Each data sector on the SD will be rewritten a number of times. SD cards have huge internal buffers and flash pages so each sync() call may cause a number of flash pages to be rewritten to a new flash page and the old pages are part of 512 KiB Record Units which must be erased. You are increasing the internal I/O in the card by a huge factor.

Best solution is put the SD on its own SPI bus so you can use dedicated SPI.

roque-canales commented 7 months ago

Hello, in my case I use file.print no file.write, as I make simple csv for log data.

if I dont call sync data is like not wrote on the sd, I check many times and there is like no data on the file if I don’t call sync…

about the instability behavior, i don’t have anything else on the spi bus…. I tested many time and the only way to get off this instability issue is to set shared spi…

maybe there is interference with timers used for the 3 i2c and the 2 uarts….

Maybe I will consider to use file.write instead of file.print?

can i for example use many file.print and finish by file.write at each loop ?

greiman commented 7 months ago

Hello, in my case I use file.print no file.write, as I make simple csv for log data.

Print just calls write so using write won't fix the problem.

if I dont call sync data is like not wrote on the sd, I check many times and there is like no data on the file if I don’t call sync…

You have some basic problems - hundreds of thousands of users use SdFat and don't have these problem. SdFat is the base for the SD.h library for most official Arduino boards. SD.h is just a wrapper to make an old version of SdFat easier to use. Teensy and the the popular RP2040 package use SdFat with a wrapper.

I suspect you will have more problems.

I need more information to see if it's worth trying to fix your problem or if you need a new approach.

What data items are you logging and what data rate in samples per second do you need?

Does the interval between samples need to be precise? Can there be missed samples if the SD is busy?

The SD specification allows SD cards to have occasional write delays of up to 500 ms. So you may need buffering and busy checks for the SD.

This example logs data at 25,000 samples per second for one ADC on a Teensy board. I can do 10,000 samples per second for five ADCs. I suspect you won't understand it.

I edited it and tested it on my F411 Nucleo. I can do one ADC easily at 1,000 samples per second. It could probably log six ADCs at this rate.

I used analogRead() which is very slow in the STM32 board package and since SPI is slow, the rate on the F411 is less than 1/20 of Teensy.

Here is the edited example. Notice it uses dedicated SPI and has no sync() calls. It uses a ring buffer to avoid long write delays. It preallocates a file to avoid delays finding free space on the SD.

// Test STM32F411 with write busy and RingBuf in a data logger demo.

// STM32 analogRead is so slow, about 50-60 usec per sample, 
// so micros is good enough to determine sample time. 

#include "RingBuf.h"
#include "SdFat.h"

const uint8_t SD_CS_PIN = SS;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)

// Interval between points for 1,000 sps.
#define LOG_INTERVAL_USEC 1000

// Size to log 10 byte lines at 1,000 sps for more ten minutes.
#define LOG_FILE_SIZE 10 *1000 * 600 

// SD standard allows cards to have 1/2 second delays.  Allow one second of buffering.
// Space to hold more than one second of data for 10 byte lines at 1,000 sps.
#define RING_BUF_CAPACITY 20 * 512

#define LOG_FILENAME "STM32F411Logger.csv"

SdFs sd;
FsFile file;

// RingBuf for File type FsFile.
RingBuf<FsFile, RING_BUF_CAPACITY> rb;

void logData() {
  // Initialize the SD.
  if (!sd.begin(SD_CONFIG)) {
    sd.initErrorHalt(&Serial);
  }
  // Open or create file - truncate existing file.
  if (!file.open(LOG_FILENAME, O_RDWR | O_CREAT | O_TRUNC)) {
    Serial.println("open failed\n");
    return;
  }
  // File must be pre-allocated to avoid huge
  // delays searching for free clusters.
  if (!file.preAllocate(LOG_FILE_SIZE)) {
    Serial.println("preAllocate failed\n");
    file.close();
    return;
  }
  // initialize the RingBuf.
  rb.begin(&file);
  Serial.println("Type any character to stop");

  // Max RingBuf used bytes. Useful to understand RingBuf overrun.
  size_t maxUsed = 0;

  uint32_t maxJitterMicros = 0;

  // Min spare micros in loop.
  int32_t minSpareMicros = INT32_MAX;

  // Actual log time micros
  uint32_t m;

  // Start time.
  uint32_t logTime = micros();

  // Log data until Serial input or file full.
  while (!Serial.available()) {
    // Amount of data in ringBuf.
    size_t n = rb.bytesUsed();
    if ((n + file.curPosition()) > (LOG_FILE_SIZE - 20)) {
      Serial.println("File full - quitting.");
      break;
    }
    if (n > maxUsed) {
      maxUsed = n;
    }
    // Not busy only allows one sector before possible busy wait.
    // Write one sector from RingBuf to file.
    if (n >= 512 && !file.isBusy()) {
      // This is the limiting factor for sample rate.
      // The STM32F411 SPI library is slow so this takes around 350 usec.
      if (512 != rb.writeOut(512)) {
        Serial.println("writeOut failed");
        break;
      }
    }
    // Time for next point.
    logTime += LOG_INTERVAL_USEC;
    int32_t spareMicros = logTime - micros();
    if (spareMicros < minSpareMicros) {
      minSpareMicros = spareMicros;
    }
    if (spareMicros <= 0) {
      Serial.print("Rate too fast ");
      Serial.println(spareMicros);
      break;
    }
    // Wait until time to log data.
    // This loop is so fast that jitter is not over one usec.
    do {
      m = micros();
    } while (m < logTime);

    // Read ADC0 - about 50-60 usec on STM32F411.
    uint16_t adc = analogRead(0);
    // Print spareMicros into the RingBuf as test data.
    rb.print(spareMicros);
    rb.write(',');
    // Print adc into RingBuf.
    rb.println(adc);
    if (rb.getWriteError()) {
      // Error caused by too few free bytes in RingBuf.
      Serial.println("WriteError");
      break;
    }
    m -= logTime;
    if (m > maxJitterMicros) {
      maxJitterMicros = m;
    }
  }
  // Write any RingBuf data to file.
  rb.sync();
  file.truncate();
  file.rewind();
  // Print first twenty lines of file.
  Serial.println("spareMicros,ADC0");
  for (uint8_t n = 0; n < 20 && file.available();) {
    int c = file.read();
    if (c < 0) {
      break;
    }
    Serial.write(c);
    if (c == '\n') n++;
  }
  Serial.print("fileSize: ");
  Serial.println((uint32_t)file.fileSize());
  Serial.print("maxBytesUsed: ");
  Serial.println(maxUsed);
  Serial.print("minSpareMicros: ");
  Serial.println(minSpareMicros);
  Serial.print("maxJitterMicros: ");
  Serial.println(maxJitterMicros);
  file.close();
  sd.end();
}
void clearSerialInput() {
  for (uint32_t m = micros(); micros() - m < 10000;) {
    if (Serial.read() >= 0) {
      m = micros();
    }
  }
}
void setup() {
  Serial.begin(9600);
  while (!Serial) {
  }
}

void loop() {
  clearSerialInput();
  Serial.println("Type any character to start");
  while (!Serial.available()) {
  }
  clearSerialInput();
  logData();
}

Here is output. Notice the time between samples is accurate to one usec. maxJitterMicros is zero in the output.

The example prints the first 20 samples.

spareMicros is long here but when a 512 byte block is written to the SD it can be shorter, 599 usec. This limits the sample rate.

Type any character to start Type any character to stop spareMicros,ADC0 999,267 936,320 936,334 936,345 936,358 936,358 936,354 937,350 936,357 936,352 936,341 936,351 936,364 936,361 936,364 936,371 937,375 936,377 936,374 936,372 fileSize: 255744 maxBytesUsed: 550 minSpareMicros: 599 maxJitterMicros: 0

greiman commented 7 months ago

You can look at the html documentation for SdFat in doc folder of the library.

unzip html.zip to the html folder. Look at the Classes tab to see all member functions for a class.

Here is a screen shot of the mainpage that you will see if you click on SdFat.html

SdFat Library

roque-canales commented 7 months ago

Hello Bill, here you have the tests with your last bench prog:

936,0 936,0 935,0 935,0 935,0 935,0 fileSize: 518 maxBytesUsed: 518 minSpareMicros: -562 maxJitterMicros: 0 Type any character to start

I make ten times the test and I always get this result....

Board I'm using original 32GB SANDISK

I moded your test to see whats happening at 20Hz with my log: FLT,100110000100100,43,5,43.20,43.20,43.20,43.20,43.20 FLZ,599827,4,43.20,43.20,43.20,43.20,43.20,43.20,43.20 FLT,100110000100100,43,5,43.20,43.20,43.20,43.20,43.20 FLZ,599847,4,43.20,43.20,43.20,43.20,43.20,43.20,43.20 FLT,100110000100100,43,5,43.20,43.20,43.20,43.20,43.20 FLZ,599867,4,43.20,43.20,43.20,43.20,43.20,43.20,43.20 fileSize: 1091298 maxBytesUsed: 623 minSpareMicros: 12639 maxJitterMicros: 0 Type any character to start

my log is like this: rb.print(F("FLT,")); rb.print(spareMicros); rb.print(F(",")); rb.print(43); rb.print(F(",")); rb.print(5); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.println(43.2,2); rb.print(F("FLZ,")); rb.print(millis()); rb.print(F(",")); rb.print(4); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.print(43.2,2); rb.print(F(",")); rb.println(43.2,2);

I set:

define LOG_INTERVAL_USEC 20000

// Size to log 10 byte lines at 1,000 sps for more ten minutes.

define LOG_FILE_SIZE 60 50 600

// SD standard allows cards to have 1/2 second delays. Allow one second of buffering. // Space to hold more than one second of data for 10 byte lines at 1,000 sps.

define RING_BUF_CAPACITY 100 * 512

question is if I want to log 2 hours it's possible? there is no limitation for log file size ?

greiman commented 7 months ago

Something is really wrong with your setup. I can run this example on everything from an AVR Mega to a Teensy 4.1 and get good results.

Almost any SD works since the busy test insures the write goes at SPI speed to the internal SD buffer.

minSpareMicros: -562

Tells me the write was incredibly slow.

Also you must have analog pin A0 grounded since the value is zero. The time to do analogRead() looks correct with spareMicros 935.

I can't help since I can't put a scope or logic analyzer on your hardware.

You will need to figure it out. Sorry and good luck.

roque-canales commented 7 months ago

Also I use spi2 not spi1

roque-canales commented 7 months ago

also another curiuous thing, is that the program read back correctly the last 20 lines and the file it readable on computer with notepad.

But if I put out the sd without finishing typing a caracter to stop, the file is empty..... that's normal ?

so these commands below make somthing like file.sync? or there is the file close mandatory ?

rb.sync(); file.truncate(); file.rewind();

roque-canales commented 7 months ago

Hello Bill,

Can you tell me how to manage preallocation ? If I prealocate 10 minutes, when I will log for 2 hours, how to handle this ? can it be dynamicaly prealocated? how to extend the prealocation at the end of the alocation?

greiman commented 7 months ago

But if I put out the sd without finishing typing a caracter to stop, the file is empty..... that's normal ? so these commands below make somthing like file.sync? or there is the file close mandatory ?

If you don't close the file, the directory entry is not updated so the file looks empty and the SD is probably corrupt since any allocated clusters are lost.

Here is what these calls do:

  rb.sync();  // Write any data in the RingBuf to the SD.
  file.truncate();  // Remove any unused pre-allocated clusters on the SD.
  file.rewind();  // reposition the file to output the first 20 lines.

Finally:

file.close(); // Write any data in the sector cache to the SD and update the directory entry for the file.

Also I use spi2 not spi1

It's likely SPI2 is half the speed of SPI1. Many old STM32 parts like the F411 had lots of restrictions on bus speed. It is common to run the APB1 clocks at half the speed of the APB2 clocks. I think SPI2 is on the APB1 peripheral bus.

So slow the sample rate to for SPI2.

For pre-allocate size just calculate (max bytes per sample) x (sample rate) x ( sample time).

Add a generous safety factor. the excess will be removed by truncate(). truncate() removes all data after the current file position so call it after the last sample and before repositioning the file.

If a sample could be 50 bytes per sample and the rate is 500 samples per second for two hours:

Pre-allocate size = 50 x 500 x 2 x 3600 or 180,000,000 bytes. 300 MB would be safe.

roque-canales commented 7 months ago

Hello Bill thank you so much for the informations.

What you advice to regularly write data on the sd, how to add to you ringbuff example to call file.sync regularly to get data writed regularly. can I call it each minute or so?

where to place the file.sync to not interfer with isr?

I get good result, I'm writing the 650bytes in approx 600 microseconds...

Just I need to tune the ring buff to regularly sync data with the sd.

also I desactivated the preallocation, and it works preaty well.

greiman commented 7 months ago

Just copy the method in this example.

It uses RingBuf with a isr.

I can't waste more time on this.

greiman commented 5 months ago

Here is an example that shows it is possible to log 64 byte csv lines at 1000 line per second with an Uno R4. I just modified the STM32 example.

Here is the program:


// Test Uno R4 with write busy and RingBuf in a data logger demo.

#include "RingBuf.h"
#include "SdFat.h"

const uint8_t SD_CS_PIN = SS;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)

// Interval between points for 1,000 sps.
#define LOG_INTERVAL_USEC 1000

// Size to log 64 byte lines at 1,000 sps for more ten minutes.
#define LOG_FILE_SIZE 64 *1000 * 600 

// Space to hold more than 200 ms of data for 64 byte lines at 1,000 sps.
#define RING_BUF_CAPACITY 64 * 200

#define LOG_FILENAME "UnoR4Logger.csv"

SdFs sd;
FsFile file;

// RingBuf for File type FsFile.
RingBuf<FsFile, RING_BUF_CAPACITY> rb;

void logData() {
  // Initialize the SD.
  if (!sd.begin(SD_CONFIG)) {
    sd.initErrorHalt(&Serial);
  }
  // Open or create file - truncate existing file.
  if (!file.open(LOG_FILENAME, O_RDWR | O_CREAT | O_TRUNC)) {
    Serial.println("open failed\n");
    return;
  }
  // File must be pre-allocated to avoid huge
  // delays searching for free clusters.
  if (!file.preAllocate(LOG_FILE_SIZE)) {
    Serial.println("preAllocate failed\n");
    file.close();
    return;
  }
  // initialize the RingBuf.
  rb.begin(&file);
  Serial.println("Type any character to stop");

  // Max RingBuf used bytes. Useful to understand RingBuf overrun.
  size_t maxUsed = 0;

  uint32_t maxJitterMicros = 0;

  // Min spare micros in loop.
  int32_t minSpareMicros = INT32_MAX;

  // Actual log time micros
  uint32_t m;

  // Start time.
  uint32_t logTime = micros();

  // Log data until Serial input or file full.
  while (!Serial.available()) {
    // Amount of data in ringBuf.
    size_t n = rb.bytesUsed();
    if ((n + file.curPosition()) > (LOG_FILE_SIZE - 20)) {
      Serial.println("File full - quitting.");
      break;
    }
    if (n > maxUsed) {
      maxUsed = n;
    }
    // Not busy only allows one sector before possible busy wait.
    // Write one sector from RingBuf to file.
    if (n >= 512 && !file.isBusy()) {
      // This is the limiting factor for sample rate.
      // The STM32F411 SPI library is slow so this takes around 350 usec.
      if (512 != rb.writeOut(512)) {
        Serial.println("writeOut failed");
        break;
      }
    }
    // Time for next point.
    logTime += LOG_INTERVAL_USEC;
    int32_t spareMicros = logTime - micros();
    if (spareMicros < minSpareMicros) {
      minSpareMicros = spareMicros;
    }
    if (spareMicros <= 0) {
      Serial.print("Rate too fast ");
      Serial.println(spareMicros);
      break;
    }
    // Wait until time to log data.
    // This loop is so fast that jitter is not over one usec.
    do {
      m = micros();
    } while (m < logTime);
    const char* csv = "1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
    rb.print(csv);
    if (rb.getWriteError()) {
      // Error caused by too few free bytes in RingBuf.
      Serial.println("WriteError");
      break;
    }
    m -= logTime;
    if (m > maxJitterMicros) {
      maxJitterMicros = m;
    }
  }
  // Write any RingBuf data to file.
  rb.sync();
  file.truncate();
  file.rewind();
  // Print first twenty lines of file.
  for (uint8_t n = 0; n < 20 && file.available();) {
    int c = file.read();
    if (c < 0) {
      break;
    }
    Serial.write(c);
    if (c == '\n') n++;
  }
  Serial.print("fileSize: ");
  Serial.println((uint32_t)file.fileSize());
  Serial.print("maxBytesUsed: ");
  Serial.println(maxUsed);
  Serial.print("minSpareMicros: ");
  Serial.println(minSpareMicros);
  Serial.print("maxJitterMicros: ");
  Serial.println(maxJitterMicros);
  file.close();
  sd.end();
}
void clearSerialInput() {
  for (uint32_t m = micros(); micros() - m < 10000;) {
    if (Serial.read() >= 0) {
      m = micros();
    }
  }
}
void setup() {
  Serial.begin(9600);
  while (!Serial) {
  }
}

void loop() {
  clearSerialInput();
  Serial.println("Type any character to start");
  while (!Serial.available()) {
  }
  clearSerialInput();
  logData();
}

Here is output:

Type any character to start Type any character to stop 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890,abcdefghijklmnopqrstuvwxyz,ABCDEFGHIJKLMNOPQRSTUVWXYZ fileSize: 1980095 maxBytesUsed: 910 minSpareMicros: 627 maxJitterMicros: 1