First Commit

This commit is contained in:
MindCreeper03
2025-02-27 19:31:50 +01:00
parent bcbb6aff9a
commit e490df1715
2470 changed files with 1479965 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2011..2020 Bill Greiman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

110
libraries/SdFat/README.md Normal file
View File

@@ -0,0 +1,110 @@
File copy constructors and file assignment operators have been made private by
default in 2.2.3 to prevent call by value and multiple copies of file instances.
SdFatConfig.h has options to make file constructors and assignment operators
public.
UTF-8 encoded filenames are supported in v2.1.0 or later.
Try the UnicodeFilenames example. Here is output from ls:
<pre>
Type any character to begin
ls:
0 😀/
20 россиянин
17 très élégant
9 狗.txt
</pre>
SdFat Version 2 supports FAT16/FAT32 and exFAT SD cards. It is mostly
backward compatible with SdFat Version 1 for FAT16/FAT32 cards.
exFAT supports files larger than 4GB so files sizes and positions are
type uint64_t for classes that support exFAT.
exFAT has many features not available in FAT16/FAT32. exFAT has excellent
support for contiguous files on flash devices and supports preallocation.
If the SD card is the only SPI device, use dedicated SPI mode. This can
greatly improve performance. See the bench example.
Here is write performance for an old, 2011, card on a Due board.
```
Shared SPI:
write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
294.45,24944,1398,1737
Dedicated SPI:
write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
3965.11,16733,110,127
```
The default version of SdFatConfig.h enables support for dedicated SPI and
optimized access to contiguous files. This makes SdFat Version 2 slightly
larger than Version 1. If these features are disabled, Version 2 is smaller
than Version 1.
The types for the classes SdFat and File are defined in SdFatConfig.h.
The default version of SdFatConfig.h defines SdFat to only support FAT16/FAT32.
SdFat and File are defined in terms of more basic classes by typedefs. You
can use these basic classes in applications.
Support for exFAT requires a substantial amount of flash. Here are sizes on
an UNO for a simple program that opens a file, prints one line, and closes
the file.
```
FAT16/FAT32 only: 9780 bytes flash, 875 bytes SRAM.
exFAT only: 13830 bytes flash, 938 bytes SRAM.
FAT16/FAT32/exFAT: 19326 bytes flash, 928 bytes SRAM.
```
The section below of SdFatConfig.h has been edited to uses FAT16/FAT32 for
small AVR boards and FAT16/FAT32/exFAT for all other boards.
```
/**
* File types for SdFat, File, SdFile, SdBaseFile, fstream,
* ifstream, and ofstream.
*
* Set SDFAT_FILE_TYPE to:
*
* 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
*/
#if defined(__AVR__) && FLASHEND < 0X8000
// FAT16/FAT32 for 32K AVR boards.
#define SDFAT_FILE_TYPE 1
#else // defined(__AVR__) && FLASHEND < 0X8000
// FAT16/FAT32 and exFAT for all other boards.
#define SDFAT_FILE_TYPE 3
#endif // defined(__AVR__) && FLASHEND < 0X8000
```
The SdBaseFile class has no Arduino Stream or Print support.
The File class is derived from Stream and SdBaseFile.
The SdFile class is derived from SdBaseFile and Print.
Please try the examples. Start with SdInfo, bench, and ExFatLogger.
To use SdFat Version 2, unzip the download file, rename the library folder
SdFat and place the SdFat folder into the libraries sub-folder in your main
sketch folder.
For more information see the Manual installation section of this guide:
http://arduino.cc/en/Guide/Libraries
A number of configuration options can be set by editing SdFatConfig.h
define macros. See the html documentation File tab for details.
Please read the html documentation for this library in SdFat/doc/SdFat.html.
Start with the Main Page. Next go to the Classes tab and read the
documentation for the classes SdFat32, SdExFat, SdFs, File32, ExFile, FsFile.
The SdFat and File classes are defined in terms of the above classes by
typedefs. Edit SdFatConfig.h to select class options.
Please continue by reading the html documentation in the SdFat/doc folder.

2783
libraries/SdFat/doc/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
2022-07-01
Run the SdErrorCode example to produce an updated list.
Code,Symbol - failed operation
0X00,SD_CARD_ERROR_NONE - No error
0X01,SD_CARD_ERROR_CMD0 - Card reset failed
0X02,SD_CARD_ERROR_CMD2 - SDIO read CID
0X03,SD_CARD_ERROR_CMD3 - SDIO publish RCA
0X04,SD_CARD_ERROR_CMD6 - Switch card function
0X05,SD_CARD_ERROR_CMD7 - SDIO card select
0X06,SD_CARD_ERROR_CMD8 - Send and check interface settings
0X07,SD_CARD_ERROR_CMD9 - Read CSD data
0X08,SD_CARD_ERROR_CMD10 - Read CID data
0X09,SD_CARD_ERROR_CMD12 - Stop multiple block read
0X0A,SD_CARD_ERROR_CMD13 - Read card status
0X0B,SD_CARD_ERROR_CMD17 - Read single block
0X0C,SD_CARD_ERROR_CMD18 - Read multiple blocks
0X0D,SD_CARD_ERROR_CMD24 - Write single block
0X0E,SD_CARD_ERROR_CMD25 - Write multiple blocks
0X0F,SD_CARD_ERROR_CMD32 - Set first erase block
0X10,SD_CARD_ERROR_CMD33 - Set last erase block
0X11,SD_CARD_ERROR_CMD38 - Erase selected blocks
0X12,SD_CARD_ERROR_CMD58 - Read OCR register
0X13,SD_CARD_ERROR_CMD59 - Set CRC mode
0X14,SD_CARD_ERROR_ACMD6 - Set SDIO bus width
0X15,SD_CARD_ERROR_ACMD13 - Read extended status
0X16,SD_CARD_ERROR_ACMD23 - Set pre-erased count
0X17,SD_CARD_ERROR_ACMD41 - Activate card initialization
0X18,SD_CARD_ERROR_ACMD51 - Read SCR data
0X19,SD_CARD_ERROR_READ_TOKEN - Bad read data token
0X1A,SD_CARD_ERROR_READ_CRC - Read CRC error
0X1B,SD_CARD_ERROR_READ_FIFO - SDIO fifo read timeout
0X1C,SD_CARD_ERROR_READ_REG - Read CID or CSD failed.
0X1D,SD_CARD_ERROR_READ_START - Bad readStart argument
0X1E,SD_CARD_ERROR_READ_TIMEOUT - Read data timeout
0X1F,SD_CARD_ERROR_STOP_TRAN - Multiple block stop failed
0X20,SD_CARD_ERROR_TRANSFER_COMPLETE - SDIO transfer complete
0X21,SD_CARD_ERROR_WRITE_DATA - Write data not accepted
0X22,SD_CARD_ERROR_WRITE_FIFO - SDIO fifo write timeout
0X23,SD_CARD_ERROR_WRITE_START - Bad writeStart argument
0X24,SD_CARD_ERROR_WRITE_PROGRAMMING - Flash programming
0X25,SD_CARD_ERROR_WRITE_TIMEOUT - Write timeout
0X26,SD_CARD_ERROR_DMA - DMA transfer failed
0X27,SD_CARD_ERROR_ERASE - Card did not accept erase commands
0X28,SD_CARD_ERROR_ERASE_SINGLE_SECTOR - Card does not support erase
0X29,SD_CARD_ERROR_ERASE_TIMEOUT - Erase command timeout
0X2A,SD_CARD_ERROR_INIT_NOT_CALLED - Card has not been initialized
0X2B,SD_CARD_ERROR_INVALID_CARD_CONFIG - Invalid card config
0X2C,SD_CARD_ERROR_FUNCTION_NOT_SUPPORTED - Unsupported SDIO command
0X2D,SD_CARD_ERROR_UNKNOWN - Unknown error

View File

@@ -0,0 +1,10 @@
<html>
<head>
<title>A web page that points a browser to a different page</title>
<meta http-equiv="refresh" content="0; URL=html/index.html">
<meta name="keywords" content="automatic redirection">
</head>
<body>
Your browser didn't automatically redirect. Open html/index.html manually.
</body>
</html>

View File

@@ -0,0 +1,4 @@
<h3>Replace the content of the html folder by unzipping html.zip.</h3>
<h3>I have zipped the documentation since Doxygen changes every file each time it runs.</h3>
<h3>This makes viewing changes on GitHub difficult.</h3>
<p>&nbsp;</p>

View File

@@ -0,0 +1,3 @@
del html\*.md5
del html\*.map
pause

View File

@@ -0,0 +1,3 @@
rm html/*.*
rm html/search/*.*
pause

Binary file not shown.

View File

@@ -0,0 +1,4 @@
<h3>Replace the content of the html folder by unzipping html.zip.</h3>
<h3>I have zipped the documentation since Doxygen changes every file each time it runs.</h3>
<h3>This makes viewing changes on GitHub difficult.</h3>
<p>&nbsp;</p>

View File

@@ -0,0 +1,289 @@
/**
* Copyright (c) 2011-2024 Bill Greiman
* This file is part of the SdFat library for SD memory cards.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/**
\mainpage Arduino %SdFat Library
\section Warn Warnings for SdFat V2
This is a major new version of SdFat. It is mostly
backward compatible with SdFat Version 1 for FAT16/FAT32 cards.
You should edit SdFatConfig.h to select features. The default version of
SdFatConfig.h is suitable for UNO and other small AVR boards.
\section Intro Introduction
The Arduino %SdFat library supports FAT16, FAT32, and exFAT file systems
on Standard SD, SDHC, and SDXC cards.
In %SdFat version 1, SdFat and File are the main classes.
In %SdFat version 2, SdFat and File are defined by typedefs in terms of the
following classes.
The file system classes in the %SdFat library are SdFat32, SdExFat, and SdFs.
SdFat32 supports FAT16 and FAT32. SdExFat supports exFAT, SdFs supports
FAT16, FAT32, and exFAT.
The corresponding file classes are File32, ExFile, and FsFile.
The types for SdFat and File are defined in SdFatConfig.h. This version
uses FAT16/FAT32 for small AVR boards and FAT16/FAT32/exFAT for all other
boards.
\code{.cpp}
// File types for SdFat, File, SdFile, SdBaseFile, fstream,
// ifstream, and ofstream.
//
// Set SDFAT_FILE_TYPE to:
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
//
#if defined(__AVR__) && FLASHEND < 0X8000
// FAT16/FAT32 for 32K AVR boards.
#define SDFAT_FILE_TYPE 1
#else // defined(__AVR__) && FLASHEND < 0X8000
// FAT16/FAT32 and exFAT for all other boards.
#define SDFAT_FILE_TYPE 3
#endif // defined(__AVR__) && FLASHEND < 0X8000
\endcode
It is possible to use option three, support or FAT16/FAT32 and exFat
on an Uno or other AVR board with 32KB flash and 2KB SRAM but memory
will be very limited.
Uno memory use for a simple data logger is:
> option 1, FAT16/FAT32, 11902 bytes of flash and 855 bytes of SRAM.
>
> option 2, exFAT, 14942 bytes of flash and 895 bytes of SRAM.
>
> option 3, FAT16/FAT32 and exFAT, 21834 bytes of flash and 908 bytes of SRAM.
Please read documentation under the above classes tab for more information.
A number of example are provided in the %SdFat/examples folder. These were
developed to test %SdFat and illustrate its use.
\section exFAT exFAT Features
exFAT has many features not available in FAT16/FAT32.
Files larger than 4GiB, 64-bit file size and file position.
Free space allocation performance improved by using a free space bitmap.
Removal of the physical "." and ".." directory entries that appear in
FAT16/FAT32 subdirectories.
Better support for large flash pages with boundary alignment offsets
for the FAT table and data region.
exFAT files have two separate 64-bit length fields. The DataLength
field indicate how much space is allocate to the file. The ValidDataLength
field indicates how much actual data has been written to the file.
An exFAT file can be contiguous with pre-allocate clusters and bypass the
use of the FAT table. In this case the contiguous flag is set in the
directory entry. This allows an entire file to be written as one large
multi-block write.
\section SDPath Paths and Working Directories
Relative paths in %SdFat are resolved in a manner similar to Windows.
Each instance of SdFat32, SdExFat, and SdFs has a current directory.
This directory is called the volume working directory, vwd.
Initially this directory is the root directory for the volume.
The volume working directory is changed by calling the chdir(path).
The call sd.chdir("/2014") will change the volume working directory
for sd to "/2014", assuming "/2014" exists.
Relative paths for member functions are resolved by starting at
the volume working directory.
For example, the call sd.mkdir("April") will create the directory
"/2014/April" assuming the volume working directory is "/2014".
There is current working directory, cwd, that is used to resolve paths
for file.open() calls.
For a single SD card, the current working directory is always the volume
working directory for that card.
For multiple SD cards the current working directory is set to the volume
working directory of a card by calling the chvol() member function.
The chvol() call is like the Windows \<drive letter>: command.
The call sd2.chvol() will set the current working directory to the volume
working directory for sd2.
If the volume working directory for sd2 is "/music" the call
file.open("BigBand.wav", O_READ);
will open "/music/BigBand.wav" on sd2.
\section Install Installation
You must manually install %SdFat by renaming the download folder %SdFat
and copy the %SdFat folder to the Arduino libraries folder in your
sketchbook folder.
It will be necessary to unzip and rename the folder if you download a zip
file from GitHub.
See the Manual installation section of this guide.
http://arduino.cc/en/Guide/Libraries
\section SDconfig SdFat Configuration
Several configuration options may be changed by editing the SdFatConfig.h
file in the %SdFat/src folder.
Here are a few of the key options.
If the symbol ENABLE_DEDICATED_SPI is nonzero, multi-block SD I/O may
be used for better performance. The SPI bus may not be shared with
other devices in this mode.
The symbol SPI_DRIVER_SELECT is used to select the SPI driver.
> If the symbol SPI_DRIVER_SELECT is:
>
> 0 - An optimized custom SPI driver is used if it exists
> else the standard library driver is used.
>
> 1 - The standard library driver is always used.
>
> 2 - The software SPI driver is always used.
>
> 3 - An external SPI driver derived from SdSpiBaseClass is always used.
To enable SD card CRC checking in SPI mode set USE_SD_CRC nonzero.
See SdFatConfig.h for other options.
\section Hardware Hardware Configuration
The hardware interface to the SD card should not use a resistor based level
shifter. Resistor based level shifters results in signal rise times that are
too slow for many newer SD cards.
\section HowTo How to format SD Cards
The best way to restore an SD card's format on a PC or Mac is to use
SDFormatter which can be downloaded from:
http://www.sdcard.org/downloads
A formatter program, SdFormatter.ino, is included in the
%SdFat/examples/SdFormatter directory. This program attempts to
emulate SD Association's SDFormatter.
SDFormatter aligns flash erase boundaries with file
system structures which reduces write latency and file system overhead.
The PC/Mac SDFormatter does not have an option for FAT type so it may format
very small cards as FAT12. Use the %SdFormatter example to force FAT16
formatting of small cards.
Do not format the SD card with an OS utility, OS utilities do not format SD
cards in conformance with the SD standard.
You should use a freshly formatted SD card for best performance. FAT
file systems become slower if many files have been created and deleted.
This is because the directory entry for a deleted file is marked as deleted,
but is not deleted. When a new file is created, these entries must be scanned
before creating the file. Also files can become
fragmented which causes reads and writes to be slower.
\section ExampleFiles Examples
A number of examples are provided in the SdFat/examples folder.
To access these examples from the Arduino development environment
go to: %File -> Examples -> %SdFat -> \<program Name\>
Compile, upload to your Arduino and click on Serial Monitor to run
the example.
Here is a list:
AvrAdcLogger - Fast AVR ADC logger using Timer/ADC interrupts.
BackwardCompatibility - Demonstrate SD.h compatibility with %SdFat.h.
bench - A read/write benchmark.
%BufferedPrint - Demo a buffered print class for AVR loggers.
debug folder - Some of my debug programs - will be remove in the future.
DirectoryFunctions - Use of chdir(), ls(), mkdir(), and rmdir().
examplesV1 folder - Examples from SdFat V1 for compatibility tests.
ExFatLogger - A data-logger optimized for exFAT features.
MinimumSizeSdReader - Example of small file reader for FAT16/FAT32.
OpenNext - Open all files in the root dir and print their filename.
QuickStart - Quick hardware test for SPI card access.
ReadCsvFile - Function to read a CSV text file one field at a time.
rename - demonstrates use of rename().
RtcTimestampTest - Demonstration of timestamps with RTClib.
SdErrorCodes - Produce a list of error codes.
SdFormatter - This program will format an SD, SDHC, or SDXC card.
SdInfo - Initialize an SD card and analyze its structure for trouble shooting.
SoftwareSpi - Demo of limited Software SPI support in SdFat V2.
STM32Test - Example use of two SPI ports on an STM32 board.
TeensyDmaAdcLogger - Fast logger using DMA ADC.
TeensyRtcTimestamp - %File timestamps for Teensy3.
TeensySdioDemo - Demo of SDIO and SPI modes for the Teensy 3.5/3.6 built-in SD.
TeensySdioLogger - Fast logger using a ring buffer.
UnicodeFilenames - Test program for Unicode file names.
UserChipSelectFunction - Useful for port expanders or replacement of the standard GPIO functions.
UserSPIDriver - An example of an external SPI driver.
*/

View File

@@ -0,0 +1,33 @@
#ifndef AnalogBinLogger_h
#define AnalogBinLogger_h
const size_t BLOCK_SIZE = 64;
//------------------------------------------------------------------------------
// First block of file.
const size_t PIN_NUM_DIM =
BLOCK_SIZE - 3 * sizeof(uint32_t) - 2 * sizeof(uint8_t);
struct metadata_t {
uint32_t adcFrequency; // ADC clock frequency
uint32_t cpuFrequency; // CPU clock frequency
uint32_t sampleInterval; // Sample interval in CPU cycles.
uint8_t recordEightBits; // Size of ADC values, nonzero for 8-bits.
uint8_t pinCount; // Number of analog pins in a sample.
uint8_t pinNumber[PIN_NUM_DIM]; // List of pin numbers in a sample.
};
//------------------------------------------------------------------------------
// Data block for 8-bit ADC mode.
const size_t DATA_DIM8 = (BLOCK_SIZE - 2 * sizeof(uint16_t)) / sizeof(uint8_t);
struct block8_t {
uint16_t count; // count of data values
uint16_t overrun; // count of overruns since last block
uint8_t data[DATA_DIM8];
};
//------------------------------------------------------------------------------
// Data block for 10-bit ADC mode.
const size_t DATA_DIM16 =
(BLOCK_SIZE - 2 * sizeof(uint16_t)) / sizeof(uint16_t);
struct block16_t {
unsigned short count; // count of data values
unsigned short overrun; // count of overruns since last block
unsigned short data[DATA_DIM16];
};
#endif // AnalogBinLogger_h

View File

@@ -0,0 +1,906 @@
/**
* This program logs data from the Arduino ADC to a binary file.
*
* Samples are logged at regular intervals. Each Sample consists of the ADC
* values for the analog pins defined in the PIN_LIST array. The pins numbers
* may be in any order.
*
* Edit the configuration constants below to set the sample pins, sample rate,
* and other configuration values.
*
* If your SD card has a long write latency, it may be necessary to use
* slower sample rates. Using a Mega Arduino helps overcome latency
* problems since more 64 byte buffer blocks will be used.
*
* Each 64 byte data block in the file has a four byte header followed by up
* to 60 bytes of data. (60 values in 8-bit mode or 30 values in 10-bit mode)
* Each block contains an integral number of samples with unused space at the
* end of the block.
*
*/
#ifdef __AVR__
#include <SPI.h>
#include "AvrAdcLogger.h"
#include "BufferedPrint.h"
#include "FreeStack.h"
#include "SdFat.h"
// Save SRAM if 328.
#ifdef __AVR_ATmega328P__
#include "MinimumSerial.h"
MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
//------------------------------------------------------------------------------
// This example was designed for exFAT but will support FAT16/FAT32.
//
// Note: Uno will not support SD_FAT_TYPE = 3.
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 2
//------------------------------------------------------------------------------
// Set USE_RTC nonzero for file timestamps.
// RAM use will be marginal on Uno with RTClib.
// Set USE_RTC nonzero for file timestamps.
// RAM use will be marginal on Uno with RTClib.
// 0 - RTC not used
// 1 - DS1307
// 2 - DS3231
// 3 - PCF8523
#define USE_RTC 0
#if USE_RTC
#include "RTClib.h"
#endif // USE_RTC
//------------------------------------------------------------------------------
// Pin definitions.
//
// Digital pin to indicate an error, set to -1 if not used.
// The led blinks for fatal errors. The led goes on solid for SD write
// overrun errors and logging continues.
const int8_t ERROR_LED_PIN = -1;
// SD chip select pin.
const uint8_t SD_CS_PIN = SS;
//------------------------------------------------------------------------------
// Analog pin number list for a sample. Pins may be in any order and pin
// numbers may be repeated.
const uint8_t PIN_LIST[] = {0, 1, 2, 3, 4};
//------------------------------------------------------------------------------
// Sample rate in samples per second.
const float SAMPLE_RATE = 5000; // Must be 0.25 or greater.
// The interval between samples in seconds, SAMPLE_INTERVAL, may be set to a
// constant instead of being calculated from SAMPLE_RATE. SAMPLE_RATE is not
// used in the code below. For example, setting SAMPLE_INTERVAL = 2.0e-4
// will result in a 200 microsecond sample interval.
const float SAMPLE_INTERVAL = 1.0 / SAMPLE_RATE;
// Setting ROUND_SAMPLE_INTERVAL non-zero will cause the sample interval to
// be rounded to a a multiple of the ADC clock period and will reduce sample
// time jitter.
#define ROUND_SAMPLE_INTERVAL 1
//------------------------------------------------------------------------------
// Reference voltage. See the processor data-sheet for reference details.
// uint8_t const ADC_REF = 0; // External Reference AREF pin.
uint8_t const ADC_REF = (1 << REFS0); // Vcc Reference.
// uint8_t const ADC_REF = (1 << REFS1); // Internal 1.1 (only 644 1284P Mega)
// uint8_t const ADC_REF = (1 << REFS1) | (1 << REFS0); // Internal 1.1 or 2.56
//------------------------------------------------------------------------------
// File definitions.
//
// Maximum file size in bytes.
// The program creates a contiguous file with MAX_FILE_SIZE_MiB bytes.
// The file will be truncated if logging is stopped early.
const uint32_t MAX_FILE_SIZE_MiB = 100; // 100 MiB file.
// log file name. Integer field before dot will be incremented.
#define LOG_FILE_NAME "AvrAdc00.bin"
// Maximum length name including zero byte.
const size_t NAME_DIM = 40;
// Set RECORD_EIGHT_BITS non-zero to record only the high 8-bits of the ADC.
#define RECORD_EIGHT_BITS 0
//------------------------------------------------------------------------------
// FIFO size definition. Use a multiple of 512 bytes for best performance.
//
#if RAMEND < 0X8FF
#error SRAM too small
#elif RAMEND < 0X10FF
const size_t FIFO_SIZE_BYTES = 512;
#elif RAMEND < 0X20FF
const size_t FIFO_SIZE_BYTES = 4 * 512;
#elif RAMEND < 0X40FF
const size_t FIFO_SIZE_BYTES = 12 * 512;
#else // RAMEND
const size_t FIFO_SIZE_BYTES = 16 * 512;
#endif // RAMEND
//------------------------------------------------------------------------------
// ADC clock rate.
// The ADC clock rate is normally calculated from the pin count and sample
// interval. The calculation attempts to use the lowest possible ADC clock
// rate.
//
// You can select an ADC clock rate by defining the symbol ADC_PRESCALER to
// one of these values. You must choose an appropriate ADC clock rate for
// your sample interval.
// #define ADC_PRESCALER 7 // F_CPU/128 125 kHz on an Uno
// #define ADC_PRESCALER 6 // F_CPU/64 250 kHz on an Uno
// #define ADC_PRESCALER 5 // F_CPU/32 500 kHz on an Uno
// #define ADC_PRESCALER 4 // F_CPU/16 1000 kHz on an Uno
// #define ADC_PRESCALER 3 // F_CPU/8 2000 kHz on an Uno (8-bit mode only)
//==============================================================================
// End of configuration constants.
//==============================================================================
// Temporary log file. Will be deleted if a reset or power failure occurs.
#define TMP_FILE_NAME "tmp_adc.bin"
// Number of analog pins to log.
const uint8_t PIN_COUNT = sizeof(PIN_LIST) / sizeof(PIN_LIST[0]);
// Minimum ADC clock cycles per sample interval
const uint16_t MIN_ADC_CYCLES = 15;
// Extra cpu cycles to setup ADC with more than one pin per sample.
const uint16_t ISR_SETUP_ADC = PIN_COUNT > 1 ? 100 : 0;
// Maximum cycles for timer0 system interrupt.
const uint16_t ISR_TIMER0 = 160;
//==============================================================================
const uint32_t MAX_FILE_SIZE = MAX_FILE_SIZE_MiB << 20;
// Max SPI rate for AVR is 10 MHz for F_CPU 20 MHz, 8 MHz for F_CPU 16 MHz.
#define SPI_CLOCK SD_SCK_MHZ(10)
// Select fastest interface.
#if ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // ENABLE_DEDICATED_SPI
#if SD_FAT_TYPE == 0
SdFat sd;
typedef File file_t;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
typedef File32 file_t;
#elif SD_FAT_TYPE == 2
SdExFat sd;
typedef ExFile file_t;
#elif SD_FAT_TYPE == 3
SdFs sd;
typedef FsFile file_t;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
file_t binFile;
file_t csvFile;
char binName[] = LOG_FILE_NAME;
#if RECORD_EIGHT_BITS
const size_t BLOCK_MAX_COUNT = PIN_COUNT * (DATA_DIM8 / PIN_COUNT);
typedef block8_t block_t;
#else // RECORD_EIGHT_BITS
const size_t BLOCK_MAX_COUNT = PIN_COUNT * (DATA_DIM16 / PIN_COUNT);
typedef block16_t block_t;
#endif // RECORD_EIGHT_BITS
// Size of FIFO in blocks.
size_t const FIFO_DIM = FIFO_SIZE_BYTES / sizeof(block_t);
block_t* fifoData;
volatile size_t fifoCount = 0; // volatile - shared, ISR and background.
size_t fifoHead = 0; // Only accessed by ISR during logging.
size_t fifoTail = 0; // Only accessed by writer during logging.
//==============================================================================
// Interrupt Service Routines
// Disable ADC interrupt if true.
volatile bool isrStop = false;
// Pointer to current buffer.
block_t* isrBuf = nullptr;
// overrun count
uint16_t isrOver = 0;
// ADC configuration for each pin.
uint8_t adcmux[PIN_COUNT];
uint8_t adcsra[PIN_COUNT];
uint8_t adcsrb[PIN_COUNT];
uint8_t adcindex = 1;
// Insure no timer events are missed.
volatile bool timerError = false;
volatile bool timerFlag = false;
//------------------------------------------------------------------------------
// ADC done interrupt.
ISR(ADC_vect) {
// Read ADC data.
#if RECORD_EIGHT_BITS
uint8_t d = ADCH;
#else // RECORD_EIGHT_BITS
// This will access ADCL first.
uint16_t d = ADC;
#endif // RECORD_EIGHT_BITS
if (!isrBuf) {
if (fifoCount < FIFO_DIM) {
isrBuf = fifoData + fifoHead;
} else {
// no buffers - count overrun
if (isrOver < 0XFFFF) {
isrOver++;
}
// Avoid missed timer error.
timerFlag = false;
return;
}
}
// Start ADC for next pin
if (PIN_COUNT > 1) {
ADMUX = adcmux[adcindex];
ADCSRB = adcsrb[adcindex];
ADCSRA = adcsra[adcindex];
if (adcindex == 0) {
timerFlag = false;
}
adcindex = adcindex < (PIN_COUNT - 1) ? adcindex + 1 : 0;
} else {
timerFlag = false;
}
// Store ADC data.
isrBuf->data[isrBuf->count++] = d;
// Check for buffer full.
if (isrBuf->count >= BLOCK_MAX_COUNT) {
fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
fifoCount++;
// Check for end logging.
if (isrStop) {
adcStop();
return;
}
// Set buffer needed and clear overruns.
isrBuf = nullptr;
isrOver = 0;
}
}
//------------------------------------------------------------------------------
// timer1 interrupt to clear OCF1B
ISR(TIMER1_COMPB_vect) {
// Make sure ADC ISR responded to timer event.
if (timerFlag) {
timerError = true;
}
timerFlag = true;
}
//==============================================================================
// Error messages stored in flash.
#define error(msg) (Serial.println(F(msg)), errorHalt())
#define assert(e) ((e) ? (void)0 : error("assert: " #e))
//------------------------------------------------------------------------------
//
void fatalBlink() {
while (true) {
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
delay(200);
digitalWrite(ERROR_LED_PIN, LOW);
delay(200);
}
}
}
//------------------------------------------------------------------------------
void errorHalt() {
// Print minimal error data.
// sd.errorPrint(&Serial);
// Print extended error info - uses extra bytes of flash.
sd.printSdError(&Serial);
// Try to save data.
binFile.close();
fatalBlink();
}
//------------------------------------------------------------------------------
void printUnusedStack() {
Serial.print(F("\nUnused stack: "));
Serial.println(UnusedStack());
}
//------------------------------------------------------------------------------
#if USE_RTC
#if USE_RTC == 1
RTC_DS1307 rtc;
#elif USE_RTC == 2
RTC_DS3231 rtc;
#elif USE_RTC == 3
RTC_PCF8523 rtc;
#else // USE_RTC == type
#error USE_RTC type not implemented.
#endif // USE_RTC == type
// Call back for file timestamps. Only called for file create and sync().
void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
DateTime now = rtc.now();
// Return date using FS_DATE macro to format fields.
*date = FS_DATE(now.year(), now.month(), now.day());
// Return time using FS_TIME macro to format fields.
*time = FS_TIME(now.hour(), now.minute(), now.second());
// Return low time bits in units of 10 ms.
*ms10 = now.second() & 1 ? 100 : 0;
}
#endif // USE_RTC
//==============================================================================
#if ADPS0 != 0 || ADPS1 != 1 || ADPS2 != 2
#error unexpected ADC prescaler bits
#endif
//------------------------------------------------------------------------------
inline bool adcActive() { return (1 << ADIE) & ADCSRA; }
//------------------------------------------------------------------------------
// initialize ADC and timer1
void adcInit(metadata_t* meta) {
uint8_t adps; // prescaler bits for ADCSRA
uint32_t ticks =
F_CPU * SAMPLE_INTERVAL + 0.5; // Sample interval cpu cycles.
if (ADC_REF & ~((1 << REFS0) | (1 << REFS1))) {
error("Invalid ADC reference");
}
#ifdef ADC_PRESCALER
if (ADC_PRESCALER > 7 || ADC_PRESCALER < 2) {
error("Invalid ADC prescaler");
}
adps = ADC_PRESCALER;
#else // ADC_PRESCALER
// Allow extra cpu cycles to change ADC settings if more than one pin.
int32_t adcCycles = (ticks - ISR_TIMER0) / PIN_COUNT - ISR_SETUP_ADC;
for (adps = 7; adps > 0; adps--) {
if (adcCycles >= (MIN_ADC_CYCLES << adps)) {
break;
}
}
#endif // ADC_PRESCALER
meta->adcFrequency = F_CPU >> adps;
if (meta->adcFrequency > (RECORD_EIGHT_BITS ? 2000000 : 1000000)) {
error("Sample Rate Too High");
}
#if ROUND_SAMPLE_INTERVAL
// Round so interval is multiple of ADC clock.
ticks += 1 << (adps - 1);
ticks >>= adps;
ticks <<= adps;
#endif // ROUND_SAMPLE_INTERVAL
if (PIN_COUNT > BLOCK_MAX_COUNT || PIN_COUNT > PIN_NUM_DIM) {
error("Too many pins");
}
meta->pinCount = PIN_COUNT;
meta->recordEightBits = RECORD_EIGHT_BITS;
for (int i = 0; i < PIN_COUNT; i++) {
uint8_t pin = PIN_LIST[i];
if (pin >= NUM_ANALOG_INPUTS) {
error("Invalid Analog pin number");
}
meta->pinNumber[i] = pin;
// Set ADC reference and low three bits of analog pin number.
adcmux[i] = (pin & 7) | ADC_REF;
if (RECORD_EIGHT_BITS) {
adcmux[i] |= 1 << ADLAR;
}
// If this is the first pin, trigger on timer/counter 1 compare match B.
adcsrb[i] = i == 0 ? (1 << ADTS2) | (1 << ADTS0) : 0;
#ifdef MUX5
if (pin > 7) {
adcsrb[i] |= (1 << MUX5);
}
#endif // MUX5
adcsra[i] = (1 << ADEN) | (1 << ADIE) | adps;
// First pin triggers on timer 1 compare match B rest are free running.
adcsra[i] |= i == 0 ? 1 << ADATE : 1 << ADSC;
}
// Setup timer1
TCCR1A = 0;
uint8_t tshift;
if (ticks < 0X10000) {
// no prescale, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
tshift = 0;
} else if (ticks < 0X10000 * 8) {
// prescale 8, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11);
tshift = 3;
} else if (ticks < 0X10000 * 64) {
// prescale 64, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11) | (1 << CS10);
tshift = 6;
} else if (ticks < 0X10000 * 256) {
// prescale 256, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12);
tshift = 8;
} else if (ticks < 0X10000 * 1024) {
// prescale 1024, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12) | (1 << CS10);
tshift = 10;
} else {
error("Sample Rate Too Slow");
}
// divide by prescaler
ticks >>= tshift;
// set TOP for timer reset
ICR1 = ticks - 1;
// compare for ADC start
OCR1B = 0;
// multiply by prescaler
ticks <<= tshift;
// Sample interval in CPU clock ticks.
meta->sampleInterval = ticks;
meta->cpuFrequency = F_CPU;
float sampleRate = (float)meta->cpuFrequency / meta->sampleInterval;
Serial.print(F("Sample pins:"));
for (uint8_t i = 0; i < meta->pinCount; i++) {
Serial.print(' ');
Serial.print(meta->pinNumber[i], DEC);
}
Serial.println();
Serial.print(F("ADC bits: "));
Serial.println(meta->recordEightBits ? 8 : 10);
Serial.print(F("ADC clock kHz: "));
Serial.println(meta->adcFrequency / 1000);
Serial.print(F("Sample Rate: "));
Serial.println(sampleRate);
Serial.print(F("Sample interval usec: "));
Serial.println(1000000.0 / sampleRate);
}
//------------------------------------------------------------------------------
// enable ADC and timer1 interrupts
void adcStart() {
// initialize ISR
adcindex = 1;
isrBuf = nullptr;
isrOver = 0;
isrStop = false;
// Clear any pending interrupt.
ADCSRA |= 1 << ADIF;
// Setup for first pin.
ADMUX = adcmux[0];
ADCSRB = adcsrb[0];
ADCSRA = adcsra[0];
// Enable timer1 interrupts.
timerError = false;
timerFlag = false;
TCNT1 = 0;
TIFR1 = 1 << OCF1B;
TIMSK1 = 1 << OCIE1B;
}
//------------------------------------------------------------------------------
inline void adcStop() {
TIMSK1 = 0;
ADCSRA = 0;
}
//------------------------------------------------------------------------------
// Convert binary file to csv file.
void binaryToCsv() {
uint8_t lastPct = 0;
block_t* pd;
metadata_t* pm;
uint32_t t0 = millis();
// Use fast buffered print class.
BufferedPrint<file_t, 64> bp(&csvFile);
block_t binBuffer[FIFO_DIM];
assert(sizeof(block_t) == sizeof(metadata_t));
binFile.rewind();
uint32_t tPct = millis();
bool doMeta = true;
while (!Serial.available()) {
pd = binBuffer;
int nb = binFile.read(binBuffer, sizeof(binBuffer));
if (nb < 0) {
error("read binFile failed");
}
size_t nd = nb / sizeof(block_t);
if (nd < 1) {
break;
}
if (doMeta) {
doMeta = false;
pm = (metadata_t*)pd++;
if (PIN_COUNT != pm->pinCount) {
error("Invalid pinCount");
}
bp.print(F("Interval,"));
float intervalMicros =
1.0e6 * pm->sampleInterval / (float)pm->cpuFrequency;
bp.print(intervalMicros, 4);
bp.println(F(",usec"));
for (uint8_t i = 0; i < PIN_COUNT; i++) {
if (i) {
bp.print(',');
}
bp.print(F("pin"));
bp.print(pm->pinNumber[i]);
}
bp.println();
if (nd-- == 1) {
break;
}
}
for (size_t i = 0; i < nd; i++, pd++) {
if (pd->overrun) {
bp.print(F("OVERRUN,"));
bp.println(pd->overrun);
}
for (size_t j = 0; j < pd->count; j += PIN_COUNT) {
for (size_t i = 0; i < PIN_COUNT; i++) {
if (!bp.printField(pd->data[i + j],
i == (PIN_COUNT - 1) ? '\n' : ',')) {
error("printField failed");
}
}
}
}
if ((millis() - tPct) > 1000) {
uint8_t pct = binFile.curPosition() / (binFile.fileSize() / 100);
if (pct != lastPct) {
tPct = millis();
lastPct = pct;
Serial.print(pct, DEC);
Serial.println('%');
}
}
}
if (!bp.sync() || !csvFile.close()) {
error("close csvFile failed");
}
Serial.print(F("Done: "));
Serial.print(0.001 * (millis() - t0));
Serial.println(F(" Seconds"));
}
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void createBinFile() {
binFile.close();
while (sd.exists(binName)) {
char* p = strchr(binName, '.');
if (!p) {
error("no dot in filename");
}
while (true) {
p--;
if (p < binName || *p < '0' || *p > '9') {
error("Can't create file name");
}
if (p[0] != '9') {
p[0]++;
break;
}
p[0] = '0';
}
}
Serial.print(F("Opening: "));
Serial.println(binName);
if (!binFile.open(binName, O_RDWR | O_CREAT)) {
error("open binName failed");
}
Serial.print(F("Allocating: "));
Serial.print(MAX_FILE_SIZE_MiB);
Serial.println(F(" MiB"));
if (!binFile.preAllocate(MAX_FILE_SIZE)) {
error("preAllocate failed");
}
}
//------------------------------------------------------------------------------
bool createCsvFile() {
char csvName[NAME_DIM];
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return false;
}
binFile.getName(csvName, sizeof(csvName));
char* dot = strchr(csvName, '.');
if (!dot) {
error("no dot in binName");
}
strcpy(dot + 1, "csv");
if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
error("open csvFile failed");
}
Serial.print(F("Writing: "));
Serial.print(csvName);
Serial.println(F(" - type any character to stop"));
return true;
}
//------------------------------------------------------------------------------
// log data
void logData() {
uint32_t t0;
uint32_t t1;
uint32_t overruns = 0;
uint32_t count = 0;
uint32_t maxLatencyUsec = 0;
size_t maxFifoUse = 0;
block_t fifoBuffer[FIFO_DIM];
adcInit((metadata_t*)fifoBuffer);
// Write metadata.
if (sizeof(metadata_t) != binFile.write(fifoBuffer, sizeof(metadata_t))) {
error("Write metadata failed");
}
fifoCount = 0;
fifoHead = 0;
fifoTail = 0;
fifoData = fifoBuffer;
// Initialize all blocks to save ISR overhead.
memset(fifoBuffer, 0, sizeof(fifoBuffer));
Serial.println(F("Logging - type any character to stop"));
// Wait for Serial Idle.
Serial.flush();
delay(10);
t0 = millis();
t1 = t0;
// Start logging interrupts.
adcStart();
while (1) {
uint32_t m;
noInterrupts();
size_t tmpFifoCount = fifoCount;
interrupts();
if (tmpFifoCount) {
block_t* pBlock = fifoData + fifoTail;
// Write block to SD.
m = micros();
if (sizeof(block_t) != binFile.write(pBlock, sizeof(block_t))) {
error("write data failed");
}
m = micros() - m;
t1 = millis();
if (m > maxLatencyUsec) {
maxLatencyUsec = m;
}
if (tmpFifoCount > maxFifoUse) {
maxFifoUse = tmpFifoCount;
}
count += pBlock->count;
// Add overruns and possibly light LED.
if (pBlock->overrun) {
overruns += pBlock->overrun;
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
}
}
// Initialize empty block to save ISR overhead.
pBlock->count = 0;
pBlock->overrun = 0;
fifoTail = fifoTail < (FIFO_DIM - 1) ? fifoTail + 1 : 0;
noInterrupts();
fifoCount--;
interrupts();
if (binFile.curPosition() >= MAX_FILE_SIZE) {
// File full so stop ISR calls.
adcStop();
break;
}
}
if (timerError) {
error("Missed timer event - rate too high");
}
if (Serial.available()) {
// Stop ISR interrupts.
isrStop = true;
}
if (fifoCount == 0 && !adcActive()) {
break;
}
}
Serial.println();
// Truncate file if recording stopped early.
if (binFile.curPosition() < MAX_FILE_SIZE) {
Serial.println(F("Truncating file"));
Serial.flush();
if (!binFile.truncate()) {
error("Can't truncate file");
}
}
Serial.print(F("Max write latency usec: "));
Serial.println(maxLatencyUsec);
Serial.print(F("Record time sec: "));
Serial.println(0.001 * (t1 - t0), 3);
Serial.print(F("Sample count: "));
Serial.println(count / PIN_COUNT);
Serial.print(F("Overruns: "));
Serial.println(overruns);
Serial.print(F("FIFO_DIM: "));
Serial.println(FIFO_DIM);
Serial.print(F("maxFifoUse: "));
Serial.println(maxFifoUse + 1); // include ISR use.
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
void openBinFile() {
char name[NAME_DIM];
clearSerialInput();
Serial.println(F("Enter file name"));
if (!serialReadLine(name, sizeof(name))) {
return;
}
if (!sd.exists(name)) {
Serial.println(name);
Serial.println(F("File does not exist"));
return;
}
binFile.close();
if (!binFile.open(name, O_RDWR)) {
Serial.println(name);
Serial.println(F("open failed"));
return;
}
Serial.println(F("File opened"));
}
//------------------------------------------------------------------------------
// Print data file to Serial
void printData() {
block_t buf;
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
if (binFile.read(&buf, sizeof(buf)) != sizeof(buf)) {
error("Read metadata failed");
}
Serial.println(F("Type any character to stop"));
delay(1000);
while (!Serial.available() &&
binFile.read(&buf, sizeof(buf)) == sizeof(buf)) {
if (buf.count == 0) {
break;
}
if (buf.overrun) {
Serial.print(F("OVERRUN,"));
Serial.println(buf.overrun);
}
for (size_t i = 0; i < buf.count; i++) {
Serial.print(buf.data[i], DEC);
if ((i + 1) % PIN_COUNT) {
Serial.print(',');
} else {
Serial.println();
}
}
}
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
bool serialReadLine(char* str, size_t size) {
size_t n = 0;
while (!Serial.available()) {
}
while (true) {
int c = Serial.read();
if (c < ' ') break;
str[n++] = c;
if (n >= size) {
Serial.println(F("input too long"));
return false;
}
uint32_t m = millis();
while (!Serial.available() && (millis() - m) < 100) {
}
if (!Serial.available()) break;
}
str[n] = 0;
return true;
}
//------------------------------------------------------------------------------
void setup(void) {
if (ERROR_LED_PIN >= 0) {
pinMode(ERROR_LED_PIN, OUTPUT);
}
Serial.begin(9600);
while (!Serial) {
}
Serial.println(F("Type any character to begin."));
while (!Serial.available()) {
}
FillStack();
// Read the first sample pin to init the ADC.
analogRead(PIN_LIST[0]);
#if !ENABLE_DEDICATED_SPI
Serial.println(
F("\nFor best performance edit SdFatConfig.h\n"
"and set ENABLE_DEDICATED_SPI nonzero"));
#endif // !ENABLE_DEDICATED_SPI
// Initialize SD.
if (!sd.begin(SD_CONFIG)) {
error("sd.begin failed");
}
#if USE_RTC
if (!rtc.begin()) {
error("rtc.begin failed");
}
if (!rtc.isrunning()) {
// Set RTC to sketch compile date & time.
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
error("RTC is NOT running!");
} else {
Serial.println(F("RTC is running"));
}
// Set callback
FsDateTime::setCallback(dateTime);
#endif // USE_RTC
}
//------------------------------------------------------------------------------
void loop(void) {
printUnusedStack();
// Read any Serial data.
clearSerialInput();
Serial.println();
Serial.println(F("type:"));
Serial.println(F("b - open existing bin file"));
Serial.println(F("c - convert file to csv"));
Serial.println(F("l - list files"));
Serial.println(F("p - print data to Serial"));
Serial.println(F("r - record ADC data"));
while (!Serial.available()) {
yield();
}
char c = tolower(Serial.read());
Serial.println();
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, LOW);
}
// Read any Serial data.
clearSerialInput();
if (c == 'b') {
openBinFile();
} else if (c == 'c') {
if (createCsvFile()) {
binaryToCsv();
}
} else if (c == 'l') {
Serial.println(F("ls:"));
sd.ls(&Serial, LS_DATE | LS_SIZE);
} else if (c == 'p') {
printData();
} else if (c == 'r') {
createBinFile();
logData();
} else {
Serial.println(F("Invalid entry"));
}
}
#else // __AVR__
#error This program is only for AVR.
#endif // __AVR__

View File

@@ -0,0 +1,84 @@
// A simple read/write example for SD.h.
// Mostly from the SD.h ReadWrite example.
//
// Your SD must be formatted FAT16/FAT32.
//
// SD.h does not support some default SdFat features.
// To compare flash size, set USE_FAT_FILE_FLAG_CONTIGUOUS,
// ENABLE_DEDICATED_SPI, and USE_LONG_FILE_NAMES to zero also
// set SDFAT_FILE_TYPE to one in SdFat/src/SdFatCongfig.h
//
// Set USE_SD_H nonzero to use SD.h.
// Set USE_SD_H zero to use SdFat.h.
//
#define USE_SD_H 0
//
#if USE_SD_H
#include <SD.h>
#else // USE_SD_H
#include "SdFat.h"
SdFat SD;
#endif // USE_SD_H
// Modify SD_CS_PIN for your board.
// For Teensy 3.6 and SdFat.h use BUILTIN_SDCARD.
#define SD_CS_PIN SS
File myFile;
void setup() {
Serial.begin(9600);
while (!Serial) {
}
#if USE_SD_H
Serial.println(F("Using SD.h. Set USE_SD_H zero to use SdFat.h."));
#else // USE_SD_H
Serial.println(F("Using SdFat.h. Set USE_SD_H nonzero to use SD.h."));
#endif // USE_SD_H
Serial.println(F("\nType any character to begin."));
while (!Serial.available()) {
yield();
}
Serial.print("Initializing SD card...");
if (!SD.begin(SD_CS_PIN)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
// open the file.
myFile = SD.open("test.txt", FILE_WRITE);
// if the file opened okay, write to it:
if (myFile) {
Serial.print("Writing to test.txt...");
myFile.println("testing 1, 2, 3.");
// close the file:
myFile.close();
Serial.println("done.");
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
// re-open the file for reading:
myFile = SD.open("test.txt");
if (myFile) {
Serial.println("test.txt:");
// read from the file until there's nothing else in it:
while (myFile.available()) {
Serial.write(myFile.read());
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
void loop() {
// nothing happens after setup
}

View File

@@ -0,0 +1,236 @@
// Test and benchmark of the fast bufferedPrint class.
//
// Mainly for AVR but may improve print performance with other CPUs.
#include "BufferedPrint.h"
#include "SdFat.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
#if SD_FAT_TYPE == 0
SdFat sd;
typedef File file_t;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
typedef File32 file_t;
#elif SD_FAT_TYPE == 2
SdExFat sd;
typedef ExFile file_t;
#elif SD_FAT_TYPE == 3
SdFs sd;
typedef FsFile file_t;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
// number of lines to print
const uint16_t N_PRINT = 20000;
//------------------------------------------------------------------------------
void benchmark() {
file_t file;
BufferedPrint<file_t, 64> bp;
// do write test
Serial.println();
for (int test = 0; test < 6; test++) {
char fileName[13] = "bench0.txt";
fileName[5] = '0' + test;
// open or create file - truncate existing file.
if (!file.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) {
sd.errorHalt(&Serial, F("open failed"));
}
if (test & 1) {
bp.begin(&file);
}
uint32_t t = millis();
switch (test) {
case 0:
Serial.println(F("Test of println(uint16_t)"));
for (uint16_t i = 0; i < N_PRINT; i++) {
file.println(i);
}
break;
case 1:
Serial.println(F("Test of printField(uint16_t, char)"));
for (uint16_t i = 0; i < N_PRINT; i++) {
bp.printField(i, '\n');
}
break;
case 2:
Serial.println(F("Test of println(uint32_t)"));
for (uint16_t i = 0; i < N_PRINT; i++) {
file.println(12345678UL + i);
}
break;
case 3:
Serial.println(F("Test of printField(uint32_t, char)"));
for (uint16_t i = 0; i < N_PRINT; i++) {
bp.printField(12345678UL + i, '\n');
}
break;
case 4:
Serial.println(F("Test of println(double)"));
for (uint16_t i = 0; i < N_PRINT; i++) {
file.println((double)0.01 * i);
}
break;
case 5:
Serial.println(F("Test of printField(double, char)"));
for (uint16_t i = 0; i < N_PRINT; i++) {
bp.printField((double)0.01 * i, '\n');
}
break;
}
if (test & 1) {
bp.sync();
}
if (file.getWriteError()) {
sd.errorHalt(&Serial, F("write failed"));
}
double s = file.fileSize();
file.close();
t = millis() - t;
Serial.print(F("Time "));
Serial.print(0.001 * t, 3);
Serial.println(F(" sec"));
Serial.print(F("File size "));
Serial.print(0.001 * s);
Serial.println(F(" KB"));
Serial.print(F("Write "));
Serial.print(s / t);
Serial.println(F(" KB/sec"));
Serial.println();
}
}
//------------------------------------------------------------------------------
void testMemberFunctions() {
BufferedPrint<Print, 32> bp(&Serial);
char c = 'c'; // char
//#define BASIC_TYPES
#ifdef BASIC_TYPES
signed char sc = -1; // signed 8-bit
unsigned char uc = 1; // unsiged 8-bit
signed short ss = -2; // signed 16-bit
unsigned short us = 2; // unsigned 16-bit
signed long sl = -4; // signed 32-bit
unsigned long ul = 4; // unsigned 32-bit
#else // BASIC_TYPES
int8_t sc = -1; // signed 8-bit
uint8_t uc = 1; // unsiged 8-bit
int16_t ss = -2; // signed 16-bit
uint16_t us = 2; // unsigned 16-bit
int32_t sl = -4; // signed 32-bit
uint32_t ul = 4; // unsigned 32-bit
#endif // BASIC_TYPES
float f = -1.234;
double d = -5.678;
bp.println();
bp.println("Test print()");
bp.print(c);
bp.println();
bp.print("string");
bp.println();
bp.print(F("flash"));
bp.println();
bp.print(sc);
bp.println();
bp.print(uc);
bp.println();
bp.print(ss);
bp.println();
bp.print(us);
bp.println();
bp.print(sl);
bp.println();
bp.print(ul);
bp.println();
bp.print(f);
bp.println();
bp.print(d);
bp.println();
bp.println();
bp.println("Test println()");
bp.println(c);
bp.println("string");
bp.println(F("flash"));
bp.println(sc);
bp.println(uc);
bp.println(ss);
bp.println(us);
bp.println(sl);
bp.println(ul);
bp.println(f);
bp.println(d);
bp.println();
bp.println("Test printField()");
bp.printField(c, ',');
bp.printField("string", ',');
bp.printField(F("flash"), ',');
bp.printField(sc, ',');
bp.printField(uc, ',');
bp.printField(ss, ',');
bp.printField(us, ',');
bp.printField(sl, ',');
bp.printField(ul, ',');
bp.printField(f, ',');
bp.printField(d, '\n');
bp.sync();
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {
}
Serial.println("Type any character to begin.");
while (!Serial.available()) {
}
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
Serial.println();
Serial.println(F("Test member funcions:"));
testMemberFunctions();
Serial.println();
Serial.println(
F("Benchmark performance for uint16_t, uint32_t, and double:"));
benchmark();
Serial.println("Done");
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,157 @@
/*
* Example use of chdir(), ls(), mkdir(), and rmdir().
*/
#include "SdFat.h"
#include "sdios.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
//------------------------------------------------------------------------------
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
File root;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
File32 root;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
ExFile root;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
FsFile root;
#endif // SD_FAT_TYPE
// Create a Serial output stream.
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// Store error strings in flash to save RAM.
#define error(s) sd.errorHalt(&Serial, F(s))
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(1000);
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize the SD card.
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
if (sd.exists("Folder1") || sd.exists("Folder1/file1.txt") ||
sd.exists("Folder1/File2.txt")) {
error("Please remove existing Folder1, file1.txt, and File2.txt");
}
int rootFileCount = 0;
if (!root.open("/")) {
error("open root");
}
while (file.openNext(&root, O_RDONLY)) {
if (!file.isHidden()) {
rootFileCount++;
}
file.close();
if (rootFileCount > 10) {
error("Too many files in root. Please use an empty SD.");
}
}
if (rootFileCount) {
cout << F("\nPlease use an empty SD for best results.\n\n");
delay(1000);
}
// Create a new folder.
if (!sd.mkdir("Folder1")) {
error("Create Folder1 failed");
}
cout << F("Created Folder1\n");
// Create a file in Folder1 using a path.
if (!file.open("Folder1/file1.txt", O_WRONLY | O_CREAT)) {
error("create Folder1/file1.txt failed");
}
file.close();
cout << F("Created Folder1/file1.txt\n");
// Change volume working directory to Folder1.
if (!sd.chdir("Folder1")) {
error("chdir failed for Folder1.\n");
}
cout << F("chdir to Folder1\n");
// Create File2.txt in current directory.
if (!file.open("File2.txt", O_WRONLY | O_CREAT)) {
error("create File2.txt failed");
}
file.close();
cout << F("Created File2.txt in current directory\n");
cout << F("\nList of files on the SD.\n");
sd.ls("/", LS_R);
// Remove files from current directory.
if (!sd.remove("file1.txt") || !sd.remove("File2.txt")) {
error("remove failed");
}
cout << F("\nfile1.txt and File2.txt removed.\n");
// Change current directory to root.
if (!sd.chdir()) {
error("chdir to root failed.\n");
}
cout << F("\nList of files on the SD.\n");
sd.ls(LS_R);
// Remove Folder1.
if (!sd.rmdir("Folder1")) {
error("rmdir for Folder1 failed\n");
}
cout << F("\nFolder1 removed.\n");
cout << F("\nList of files on the SD.\n");
sd.ls(LS_R);
cout << F("Done!\n");
}
//------------------------------------------------------------------------------
// Nothing happens in loop.
void loop() {}

View File

@@ -0,0 +1,9 @@
// Avoid IDE problems by defining struct in septate .h file.
// Pad record so size is a power of two for best write performance.
#ifndef ExFatLogger_h
#define ExFatLogger_h
const size_t ADC_COUNT = 4;
struct data_t {
uint16_t adc[ADC_COUNT];
};
#endif // ExFatLogger_h

View File

@@ -0,0 +1,597 @@
// Example to demonstrate write latency for preallocated exFAT files.
// I suggest you write a PC program to convert very large bin files.
//
// The maximum data rate will depend on the quality of your SD,
// the size of the FIFO, and using dedicated SPI.
#include "ExFatLogger.h"
#include "FreeStack.h"
#include "SdFat.h"
//------------------------------------------------------------------------------
// This example was designed for exFAT but will support FAT16/FAT32.
// Note: Uno will not support SD_FAT_TYPE = 3.
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 2
//------------------------------------------------------------------------------
// Interval between data records in microseconds.
// Try 250 with Teensy 3.6, Due, or STM32.
// Try 2000 with AVR boards.
// Try 4000 with SAMD Zero boards.
const uint32_t LOG_INTERVAL_USEC = 2000;
// Set USE_RTC nonzero for file timestamps.
// RAM use will be marginal on Uno with RTClib.
// 0 - RTC not used
// 1 - DS1307
// 2 - DS3231
// 3 - PCF8523
#define USE_RTC 0
#if USE_RTC
#include "RTClib.h"
#endif // USE_RTC
// LED to light if overruns occur.
#define ERROR_LED_PIN -1
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// FIFO SIZE - 512 byte sectors. Modify for your board.
#ifdef __AVR_ATmega328P__
// Use 512 bytes for 328 boards.
#define FIFO_SIZE_SECTORS 1
#elif defined(__AVR__)
// Use 2 KiB for other AVR boards.
#define FIFO_SIZE_SECTORS 4
#else // __AVR_ATmega328P__
// Use 8 KiB for non-AVR boards.
#define FIFO_SIZE_SECTORS 16
#endif // __AVR_ATmega328P__
// Preallocate 1GiB file.
const uint32_t PREALLOCATE_SIZE_MiB = 1024UL;
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
// Save SRAM if 328.
#ifdef __AVR_ATmega328P__
#include "MinimumSerial.h"
MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
//==============================================================================
// Replace logRecord(), printRecord(), and ExFatLogger.h for your sensors.
void logRecord(data_t* data, uint16_t overrun) {
if (overrun) {
// Add one since this record has no adc data. Could add overrun field.
overrun++;
data->adc[0] = 0X8000 | overrun;
} else {
for (size_t i = 0; i < ADC_COUNT; i++) {
data->adc[i] = analogRead(i);
}
}
}
//------------------------------------------------------------------------------
void printRecord(Print* pr, data_t* data) {
static uint32_t nr = 0;
if (!data) {
pr->print(F("LOG_INTERVAL_USEC,"));
pr->println(LOG_INTERVAL_USEC);
pr->print(F("rec#"));
for (size_t i = 0; i < ADC_COUNT; i++) {
pr->print(F(",adc"));
pr->print(i);
}
pr->println();
nr = 0;
return;
}
if (data->adc[0] & 0X8000) {
uint16_t n = data->adc[0] & 0X7FFF;
nr += n;
pr->print(F("-1,"));
pr->print(n);
pr->println(F(",overuns"));
} else {
pr->print(nr++);
for (size_t i = 0; i < ADC_COUNT; i++) {
pr->write(',');
pr->print(data->adc[i]);
}
pr->println();
}
}
//==============================================================================
const uint64_t PREALLOCATE_SIZE = (uint64_t)PREALLOCATE_SIZE_MiB << 20;
// Max length of file name including zero byte.
#define FILE_NAME_DIM 40
// Max number of records to buffer while SD is busy.
const size_t FIFO_DIM = 512 * FIFO_SIZE_SECTORS / sizeof(data_t);
#if SD_FAT_TYPE == 0
typedef SdFat sd_t;
typedef File file_t;
#elif SD_FAT_TYPE == 1
typedef SdFat32 sd_t;
typedef File32 file_t;
#elif SD_FAT_TYPE == 2
typedef SdExFat sd_t;
typedef ExFile file_t;
#elif SD_FAT_TYPE == 3
typedef SdFs sd_t;
typedef FsFile file_t;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
sd_t sd;
file_t binFile;
file_t csvFile;
// You may modify the filename. Digits before the dot are file versions.
char binName[] = "ExFatLogger00.bin";
//------------------------------------------------------------------------------
#if USE_RTC
#if USE_RTC == 1
RTC_DS1307 rtc;
#elif USE_RTC == 2
RTC_DS3231 rtc;
#elif USE_RTC == 3
RTC_PCF8523 rtc;
#else // USE_RTC == type
#error USE_RTC type not implemented.
#endif // USE_RTC == type
// Call back for file timestamps. Only called for file create and sync().
void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
DateTime now = rtc.now();
// Return date using FS_DATE macro to format fields.
*date = FS_DATE(now.year(), now.month(), now.day());
// Return time using FS_TIME macro to format fields.
*time = FS_TIME(now.hour(), now.minute(), now.second());
// Return low time bits in units of 10 ms.
*ms10 = now.second() & 1 ? 100 : 0;
}
#endif // USE_RTC
//------------------------------------------------------------------------------
#define error(s) sd.errorHalt(&Serial, F(s))
#define dbgAssert(e) ((e) ? (void)0 : error("assert " #e))
//-----------------------------------------------------------------------------
// Convert binary file to csv file.
void binaryToCsv() {
uint8_t lastPct = 0;
uint32_t t0 = millis();
data_t binData[FIFO_DIM];
if (!binFile.seekSet(512)) {
error("binFile.seek failed");
}
uint32_t tPct = millis();
printRecord(&csvFile, nullptr);
while (!Serial.available() && binFile.available()) {
int nb = binFile.read(binData, sizeof(binData));
if (nb <= 0) {
error("read binFile failed");
}
size_t nr = nb / sizeof(data_t);
for (size_t i = 0; i < nr; i++) {
printRecord(&csvFile, &binData[i]);
}
if ((millis() - tPct) > 1000) {
uint8_t pct = binFile.curPosition() / (binFile.fileSize() / 100);
if (pct != lastPct) {
tPct = millis();
lastPct = pct;
Serial.print(pct, DEC);
Serial.println('%');
csvFile.sync();
}
}
if (Serial.available()) {
break;
}
}
csvFile.close();
Serial.print(F("Done: "));
Serial.print(0.001 * (millis() - t0));
Serial.println(F(" Seconds"));
}
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//-------------------------------------------------------------------------------
void createBinFile() {
binFile.close();
while (sd.exists(binName)) {
char* p = strchr(binName, '.');
if (!p) {
error("no dot in filename");
}
while (true) {
p--;
if (p < binName || *p < '0' || *p > '9') {
error("Can't create file name");
}
if (p[0] != '9') {
p[0]++;
break;
}
p[0] = '0';
}
}
if (!binFile.open(binName, O_RDWR | O_CREAT)) {
error("open binName failed");
}
Serial.println(binName);
if (!binFile.preAllocate(PREALLOCATE_SIZE)) {
error("preAllocate failed");
}
Serial.print(F("preAllocated: "));
Serial.print(PREALLOCATE_SIZE_MiB);
Serial.println(F(" MiB"));
}
//-------------------------------------------------------------------------------
bool createCsvFile() {
char csvName[FILE_NAME_DIM];
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return false;
}
// Create a new csvFile.
binFile.getName(csvName, sizeof(csvName));
char* dot = strchr(csvName, '.');
if (!dot) {
error("no dot in filename");
}
strcpy(dot + 1, "csv");
if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
error("open csvFile failed");
}
clearSerialInput();
Serial.print(F("Writing: "));
Serial.print(csvName);
Serial.println(F(" - type any character to stop"));
return true;
}
//-------------------------------------------------------------------------------
void logData() {
int32_t delta; // Jitter in log time.
int32_t maxDelta = 0;
uint32_t maxLogMicros = 0;
uint32_t maxWriteMicros = 0;
size_t maxFifoUse = 0;
size_t fifoCount = 0;
size_t fifoHead = 0;
size_t fifoTail = 0;
uint16_t overrun = 0;
uint16_t maxOverrun = 0;
uint32_t totalOverrun = 0;
uint32_t fifoBuf[128 * FIFO_SIZE_SECTORS];
data_t* fifoData = (data_t*)fifoBuf;
// Write dummy sector to start multi-block write.
dbgAssert(sizeof(fifoBuf) >= 512);
memset(fifoBuf, 0, sizeof(fifoBuf));
if (binFile.write(fifoBuf, 512) != 512) {
error("write first sector failed");
}
clearSerialInput();
Serial.println(F("Type any character to stop"));
// Wait until SD is not busy.
while (sd.card()->isBusy()) {
}
// Start time for log file.
uint32_t m = millis();
// Time to log next record.
uint32_t logTime = micros();
while (true) {
// Time for next data record.
logTime += LOG_INTERVAL_USEC;
// Wait until time to log data.
delta = micros() - logTime;
if (delta > 0) {
Serial.print(F("delta: "));
Serial.println(delta);
error("Rate too fast");
}
while (delta < 0) {
delta = micros() - logTime;
}
if (fifoCount < FIFO_DIM) {
uint32_t m = micros();
logRecord(fifoData + fifoHead, overrun);
m = micros() - m;
if (m > maxLogMicros) {
maxLogMicros = m;
}
fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
fifoCount++;
if (overrun) {
if (overrun > maxOverrun) {
maxOverrun = overrun;
}
overrun = 0;
}
} else {
totalOverrun++;
overrun++;
if (overrun > 0XFFF) {
error("too many overruns");
}
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
}
}
// Save max jitter.
if (delta > maxDelta) {
maxDelta = delta;
}
// Write data if SD is not busy.
if (!sd.card()->isBusy()) {
size_t nw = fifoHead > fifoTail ? fifoCount : FIFO_DIM - fifoTail;
// Limit write time by not writing more than 512 bytes.
const size_t MAX_WRITE = 512 / sizeof(data_t);
if (nw > MAX_WRITE) nw = MAX_WRITE;
size_t nb = nw * sizeof(data_t);
uint32_t usec = micros();
if (nb != binFile.write(fifoData + fifoTail, nb)) {
error("write binFile failed");
}
usec = micros() - usec;
if (usec > maxWriteMicros) {
maxWriteMicros = usec;
}
fifoTail = (fifoTail + nw) < FIFO_DIM ? fifoTail + nw : 0;
if (fifoCount > maxFifoUse) {
maxFifoUse = fifoCount;
}
fifoCount -= nw;
if (Serial.available()) {
break;
}
}
}
Serial.print(F("\nLog time: "));
Serial.print(0.001 * (millis() - m));
Serial.println(F(" Seconds"));
binFile.truncate();
binFile.sync();
Serial.print(("File size: "));
// Warning cast used for print since fileSize is uint64_t.
Serial.print((uint32_t)binFile.fileSize());
Serial.println(F(" bytes"));
Serial.print(F("totalOverrun: "));
Serial.println(totalOverrun);
Serial.print(F("FIFO_DIM: "));
Serial.println(FIFO_DIM);
Serial.print(F("maxFifoUse: "));
Serial.println(maxFifoUse);
Serial.print(F("maxLogMicros: "));
Serial.println(maxLogMicros);
Serial.print(F("maxWriteMicros: "));
Serial.println(maxWriteMicros);
Serial.print(F("Log interval: "));
Serial.print(LOG_INTERVAL_USEC);
Serial.print(F(" micros\nmaxDelta: "));
Serial.print(maxDelta);
Serial.println(F(" micros"));
}
//------------------------------------------------------------------------------
void openBinFile() {
char name[FILE_NAME_DIM];
clearSerialInput();
Serial.println(F("Enter file name"));
if (!serialReadLine(name, sizeof(name))) {
return;
}
if (!sd.exists(name)) {
Serial.println(name);
Serial.println(F("File does not exist"));
return;
}
binFile.close();
if (!binFile.open(name, O_RDONLY)) {
Serial.println(name);
Serial.println(F("open failed"));
return;
}
Serial.println(F("File opened"));
}
//-----------------------------------------------------------------------------
void printData() {
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return;
}
// Skip first dummy sector.
if (!binFile.seekSet(512)) {
error("seek failed");
}
clearSerialInput();
Serial.println(F("type any character to stop\n"));
delay(1000);
printRecord(&Serial, nullptr);
while (binFile.available() && !Serial.available()) {
data_t record;
if (binFile.read(&record, sizeof(data_t)) != sizeof(data_t)) {
error("read binFile failed");
}
printRecord(&Serial, &record);
}
}
//------------------------------------------------------------------------------
void printUnusedStack() {
#if HAS_UNUSED_STACK
Serial.print(F("\nUnused stack: "));
Serial.println(UnusedStack());
#endif // HAS_UNUSED_STACK
}
//------------------------------------------------------------------------------
bool serialReadLine(char* str, size_t size) {
size_t n = 0;
while (!Serial.available()) {
yield();
}
while (true) {
int c = Serial.read();
if (c < ' ') break;
str[n++] = c;
if (n >= size) {
Serial.println(F("input too long"));
return false;
}
uint32_t m = millis();
while (!Serial.available() && (millis() - m) < 100) {
}
if (!Serial.available()) break;
}
str[n] = 0;
return true;
}
//------------------------------------------------------------------------------
void testSensor() {
const uint32_t interval = 200000;
int32_t diff;
data_t data;
clearSerialInput();
Serial.println(F("\nTesting - type any character to stop\n"));
delay(1000);
printRecord(&Serial, nullptr);
uint32_t m = micros();
while (!Serial.available()) {
m += interval;
do {
diff = m - micros();
} while (diff > 0);
logRecord(&data, 0);
printRecord(&Serial, &data);
}
}
//------------------------------------------------------------------------------
void setup() {
if (ERROR_LED_PIN >= 0) {
pinMode(ERROR_LED_PIN, OUTPUT);
digitalWrite(ERROR_LED_PIN, HIGH);
}
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(1000);
Serial.println(F("Type any character to begin"));
while (!Serial.available()) {
yield();
}
FillStack();
#if !ENABLE_DEDICATED_SPI
Serial.println(
F("\nFor best performance edit SdFatConfig.h\n"
"and set ENABLE_DEDICATED_SPI nonzero"));
#endif // !ENABLE_DEDICATED_SPI
Serial.print(FIFO_DIM);
Serial.println(F(" FIFO entries will be used."));
// Initialize SD.
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
#if USE_RTC
if (!rtc.begin()) {
error("rtc.begin failed");
}
if (!rtc.isrunning()) {
// Set RTC to sketch compile date & time.
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
error("RTC is NOT running!");
}
// Set callback
FsDateTime::setCallback(dateTime);
#endif // USE_RTC
}
//------------------------------------------------------------------------------
void loop() {
printUnusedStack();
// Read any Serial data.
clearSerialInput();
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, LOW);
}
Serial.println();
Serial.println(F("type: "));
Serial.println(F("b - open existing bin file"));
Serial.println(F("c - convert file to csv"));
Serial.println(F("l - list files"));
Serial.println(F("p - print data to Serial"));
Serial.println(F("r - record data"));
Serial.println(F("t - test without logging"));
while (!Serial.available()) {
yield();
}
char c = tolower(Serial.read());
Serial.println();
if (c == 'b') {
openBinFile();
} else if (c == 'c') {
if (createCsvFile()) {
binaryToCsv();
}
} else if (c == 'l') {
Serial.println(F("ls:"));
sd.ls(&Serial, LS_DATE | LS_SIZE);
} else if (c == 'p') {
printData();
} else if (c == 'r') {
createBinFile();
logData();
} else if (c == 't') {
testSensor();
} else {
Serial.println(F("Invalid entry"));
}
}

View File

@@ -0,0 +1,60 @@
// Create a text file on the SD with this path using short 8.3 names.
#define SFN_PATH "/DIR/TEST.TXT"
// Modify CS_PIN for your chip select pin.
#define CS_PIN SS
// Set USE_SD_H to one for SD.h or zero for SdFat.
#define USE_SD_H 0
#if USE_SD_H
#include "SD.h"
File file;
#else
#include "SdFat.h"
// Setting ENABLE_DEDICATED_SPI to zero saves over 200 more bytes.
#if ENABLE_DEDICATED_SPI
#warning \
"Set ENABLE_DEDICATED_SPI zero in SdFat/src/SdFatConfig.h for minimum size"
#endif // ENABLE_DEDICATED_SPI
// Insure FAT16/FAT32 only.
SdFat32 SD;
// FatFile does not support Stream functions, just simple read/write.
FatFile file;
#endif
void error(const char* msg) {
Serial.println(msg);
while (true) {
}
}
void setup() {
int n;
char buf[4];
Serial.begin(9600);
while (!Serial) {
}
Serial.println("Type any character to begin");
while (!Serial.available()) {
}
if (!SD.begin(CS_PIN)) error("SD.begin");
#if USE_SD_H
file = SD.open(SFN_PATH);
if (!file) error("open");
#else
// Open existing file with a path of 8.3 names.
// Directories will be opened O_RDONLY files O_RDWR.
if (!file.openExistingSFN(SFN_PATH)) error("open");
#endif
while ((n = file.read(buf, sizeof(buf)))) {
Serial.write(buf, n);
}
// close() is only needed if you write to the file. For example, read
// config data, modify the data, rewind the file and write the data.
// file.close();
}
void loop() {}

View File

@@ -0,0 +1,105 @@
/*
* Print size, modify date/time, and name for all files in root.
*/
#include "SdFat.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
#if SD_FAT_TYPE == 0
SdFat sd;
File dir;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 dir;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile dir;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile dir;
FsFile file;
#else // SD_FAT_TYPE
#error invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
//------------------------------------------------------------------------------
// Store error strings in flash to save RAM.
#define error(s) sd.errorHalt(&Serial, F(s))
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
yield();
}
// Initialize the SD.
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
// Open root directory
if (!dir.open("/")) {
error("dir.open failed");
}
// Open next file in root.
// Warning, openNext starts at the current position of dir so a
// rewind may be necessary in your application.
while (file.openNext(&dir, O_RDONLY)) {
file.printFileSize(&Serial);
Serial.write(' ');
file.printModifyDateTime(&Serial);
Serial.write(' ');
file.printName(&Serial);
if (file.isDir()) {
// Indicate a directory.
Serial.write('/');
}
Serial.println();
file.close();
}
if (dir.getError()) {
Serial.println("openNext failed");
} else {
Serial.println("Done!");
}
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,192 @@
// Quick hardware test for SPI card access.
//
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
//
// Set DISABLE_CHIP_SELECT to disable a second SPI device.
// For example, with the Ethernet shield, set DISABLE_CHIP_SELECT
// to 10 to disable the Ethernet controller.
const int8_t DISABLE_CHIP_SELECT = -1;
//
// Test with reduced SPI speed for breadboards. SD_SCK_MHZ(4) will select
// the highest speed supported by the board that is not over 4 MHz.
// Change SPI_SPEED to SD_SCK_MHZ(50) for best performance.
#define SPI_SPEED SD_SCK_MHZ(4)
//------------------------------------------------------------------------------
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
// Serial streams
ArduinoOutStream cout(Serial);
// input buffer for line
char cinBuf[40];
ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));
// SD card chip select
int chipSelect;
void cardOrSpeed() {
cout << F("Try another SD card or reduce the SPI bus speed.\n");
cout << F("Edit SPI_SPEED in this program to change it.\n");
}
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
void reformatMsg() {
cout << F("Try reformatting the card. For best results use\n");
cout << F("the SdFormatter program in SdFat/examples or download\n");
cout << F("and use SDFormatter from www.sdcard.org/downloads.\n");
}
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("\nSPI pins:\n");
cout << F("MISO: ") << int(MISO) << endl;
cout << F("MOSI: ") << int(MOSI) << endl;
cout << F("SCK: ") << int(SCK) << endl;
cout << F("SS: ") << int(SS) << endl;
#ifdef SDCARD_SS_PIN
cout << F("SDCARD_SS_PIN: ") << int(SDCARD_SS_PIN) << endl;
#endif // SDCARD_SS_PIN
if (DISABLE_CHIP_SELECT < 0) {
cout << F(
"\nBe sure to edit DISABLE_CHIP_SELECT if you have\n"
"a second SPI device. For example, with the Ethernet\n"
"shield, DISABLE_CHIP_SELECT should be set to 10\n"
"to disable the Ethernet controller.\n");
}
cout << F(
"\nSD chip select is the key hardware option.\n"
"Common values are:\n"
"Arduino Ethernet shield, pin 4\n"
"Sparkfun SD shield, pin 8\n"
"Adafruit SD shields and modules, pin 10\n");
}
bool firstTry = true;
void loop() {
// Read any existing Serial data.
clearSerialInput();
if (!firstTry) {
cout << F("\nRestarting\n");
}
firstTry = false;
cout << F("\nEnter the chip select pin number: ");
while (!Serial.available()) {
yield();
}
cin.readline();
if (cin >> chipSelect) {
cout << chipSelect << endl;
} else {
cout << F("\nInvalid pin number\n");
return;
}
if (DISABLE_CHIP_SELECT < 0) {
cout << F(
"\nAssuming the SD is the only SPI device.\n"
"Edit DISABLE_CHIP_SELECT to disable another device.\n");
} else {
cout << F("\nDisabling SPI device on pin ");
cout << int(DISABLE_CHIP_SELECT) << endl;
pinMode(DISABLE_CHIP_SELECT, OUTPUT);
digitalWrite(DISABLE_CHIP_SELECT, HIGH);
}
if (!sd.begin(chipSelect, SPI_SPEED)) {
if (sd.card()->errorCode()) {
cout << F(
"\nSD initialization failed.\n"
"Do not reformat the card!\n"
"Is the card correctly inserted?\n"
"Is chipSelect set to the correct value?\n"
"Does another SPI device need to be disabled?\n"
"Is there a wiring/soldering problem?\n");
cout << F("\nerrorCode: ") << hex << showbase;
cout << int(sd.card()->errorCode());
cout << F(", errorData: ") << int(sd.card()->errorData());
cout << dec << noshowbase << endl;
return;
}
cout << F("\nCard successfully initialized.\n");
if (sd.vol()->fatType() == 0) {
cout << F("Can't find a valid FAT16/FAT32/exFAT partition.\n");
reformatMsg();
return;
}
cout << F("Can't determine error type\n");
return;
}
cout << F("\nCard successfully initialized.\n");
cout << endl;
uint32_t size = sd.card()->sectorCount();
if (size == 0) {
cout << F("Can't determine the card size.\n");
cardOrSpeed();
return;
}
uint32_t sizeMB = 0.000512 * size + 0.5;
cout << F("Card size: ") << sizeMB;
cout << F(" MB (MB = 1,000,000 bytes)\n");
cout << endl;
if (sd.fatType() <= 32) {
cout << F("\nVolume is FAT") << int(sd.fatType());
} else {
cout << F("\nVolume is exFAT");
}
cout << F(", Cluster size (bytes): ") << sd.vol()->bytesPerCluster();
cout << endl << endl;
cout << F("Files found (date time size name):\n");
sd.ls(LS_R | LS_DATE | LS_SIZE);
if ((sizeMB > 1100 && sd.vol()->sectorsPerCluster() < 64) ||
(sizeMB < 2200 && sd.vol()->fatType() == 32)) {
cout << F("\nThis card should be reformatted for best performance.\n");
cout << F("Use a cluster size of 32 KB for cards larger than 1 GB.\n");
cout << F("Only cards larger than 2 GB should be formatted FAT32.\n");
reformatMsg();
return;
}
// Read any extra Serial data.
clearSerialInput();
cout << F("\nSuccess! Type any character to restart.\n");
while (!Serial.available()) {
yield();
}
}

View File

@@ -0,0 +1,155 @@
#include "SdFat.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
char line[40];
//------------------------------------------------------------------------------
// Store error strings in flash to save RAM.
#define error(s) sd.errorHalt(&Serial, F(s))
//------------------------------------------------------------------------------
// Check for extra characters in field or find minus sign.
char* skipSpace(char* str) {
while (isspace(*str)) str++;
return str;
}
//------------------------------------------------------------------------------
bool parseLine(char* str) {
char* ptr;
// Set strtok start of line.
str = strtok(str, ",");
if (!str) return false;
// Print text field.
Serial.println(str);
// Subsequent calls to strtok expects a null pointer.
str = strtok(nullptr, ",");
if (!str) return false;
// Convert string to long integer.
int32_t i32 = strtol(str, &ptr, 0);
if (str == ptr || *skipSpace(ptr)) return false;
Serial.println(i32);
str = strtok(nullptr, ",");
if (!str) return false;
// strtoul accepts a leading minus with unexpected results.
if (*skipSpace(str) == '-') return false;
// Convert string to unsigned long integer.
uint32_t u32 = strtoul(str, &ptr, 0);
if (str == ptr || *skipSpace(ptr)) return false;
Serial.println(u32);
str = strtok(nullptr, ",");
if (!str) return false;
// Convert string to double.
double d = strtod(str, &ptr);
if (str == ptr || *skipSpace(ptr)) return false;
Serial.println(d);
// Check for extra fields.
return strtok(nullptr, ",") == nullptr;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
yield();
}
// Initialize the SD.
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
return;
}
// Remove any existing file.
if (sd.exists("ReadCsvDemo.csv")) {
sd.remove("ReadCsvDemo.csv");
}
// Create the file.
if (!file.open("ReadCsvDemo.csv", FILE_WRITE)) {
error("open failed");
}
// Write test data.
file.print(
F("abc,123,456,7.89\r\n"
"def,-321,654,-9.87\r\n"
"ghi,333,0xff,5.55"));
// Rewind file for read.
file.rewind();
while (file.available()) {
int n = file.fgets(line, sizeof(line));
if (n <= 0) {
error("fgets failed");
}
if (line[n - 1] != '\n' && n == (sizeof(line) - 1)) {
error("line too long");
}
if (!parseLine(line)) {
error("parseLine failed");
}
Serial.println();
}
file.close();
Serial.println(F("Done"));
}
void loop() {}

View File

@@ -0,0 +1,235 @@
// Test of time-stamp callback.
// Set the callback with this statement.
// FsDateTime::setCallback(dateTime);
#include "SdFat.h"
// https://github.com/adafruit/RTClib
#include "RTClib.h"
// Set RTC_TYPE for file timestamps.
// 0 - millis()
// 1 - DS1307
// 2 - DS3231
// 3 - PCF8523
#define RTC_TYPE 3
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
#if RTC_TYPE == 0
RTC_Millis rtc;
#elif RTC_TYPE == 1
RTC_DS1307 rtc;
#elif RTC_TYPE == 2
RTC_DS3231 rtc;
#elif RTC_TYPE == 3
RTC_PCF8523 rtc;
#else // RTC_TYPE == type
#error RTC_TYPE type not implemented.
#endif // RTC_TYPE == type
//------------------------------------------------------------------------------
// Call back for file timestamps. Only called for file create and sync().
void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
DateTime now = rtc.now();
// Return date using FS_DATE macro to format fields.
*date = FS_DATE(now.year(), now.month(), now.day());
// Return time using FS_TIME macro to format fields.
*time = FS_TIME(now.hour(), now.minute(), now.second());
// Return low time bits in units of 10 ms, 0 <= ms10 <= 199.
*ms10 = now.second() & 1 ? 100 : 0;
}
//------------------------------------------------------------------------------
#define error(msg) (Serial.println(F("error " msg)), false)
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void getLine(char* line, size_t size) {
size_t i = 0;
uint32_t t;
line[0] = '\0';
while (!Serial.available()) {
yield();
}
while (true) {
t = millis() + 10;
while (!Serial.available()) {
if (millis() > t) {
return;
}
}
int c = Serial.read();
if (i >= (size - 1) || c == '\r' || c == '\n') {
return;
}
line[i++] = c;
line[i] = '\0';
}
}
//------------------------------------------------------------------------------
void printField(Print* pr, char sep, uint8_t v) {
if (sep) {
pr->write(sep);
}
if (v < 10) {
pr->write('0');
}
pr->print(v);
}
//------------------------------------------------------------------------------
void printNow(Print* pr) {
DateTime now = rtc.now();
pr->print(now.year());
printField(pr, '-', now.month());
printField(pr, '-', now.day());
printField(pr, ' ', now.hour());
printField(pr, ':', now.minute());
printField(pr, ':', now.second());
}
//------------------------------------------------------------------------------
bool setRtc() {
uint16_t y;
uint8_t m, d, hh, mm, ss;
char line[30];
char* ptr;
clearSerialInput();
Serial.println(F("Enter: YYYY-MM-DD hh:mm:ss"));
getLine(line, sizeof(line));
Serial.print(F("Input: "));
Serial.println(line);
y = strtol(line, &ptr, 10);
if (*ptr++ != '-' || y < 2000 || y > 2099) return error("year");
m = strtol(ptr, &ptr, 10);
if (*ptr++ != '-' || m < 1 || m > 12) return error("month");
d = strtol(ptr, &ptr, 10);
if (d < 1 || d > 31) return error("day");
hh = strtol(ptr, &ptr, 10);
if (*ptr++ != ':' || hh > 23) return error("hour");
mm = strtol(ptr, &ptr, 10);
if (*ptr++ != ':' || mm > 59) return error("minute");
ss = strtol(ptr, &ptr, 10);
if (ss > 59) return error("second");
rtc.adjust(DateTime(y, m, d, hh, mm, ss));
Serial.print(F("RTC set to "));
printNow(&Serial);
Serial.println();
return true;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {
yield();
}
#if RTC_TYPE == 0
rtc.begin(DateTime(F(__DATE__), F(__TIME__)));
#else // RTC_TYPE
if (!rtc.begin()) {
Serial.println(F("rtc.begin failed"));
return;
}
if (!rtc.isrunning()) {
Serial.println(F("RTC is NOT running!"));
return;
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
#endif // RTC_TYPE
while (true) {
Serial.print(F("DateTime::now "));
printNow(&Serial);
Serial.println();
clearSerialInput();
Serial.println(F("Type Y to set RTC, any other character to continue"));
while (!Serial.available()) {
}
if (Serial.read() != 'Y') break;
if (setRtc()) break;
}
Serial.println();
// Set callback
FsDateTime::setCallback(dateTime);
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
// Remove old version to set create time.
if (sd.exists("RtcTest.txt")) {
sd.remove("RtcTest.txt");
}
if (!file.open("RtcTest.txt", FILE_WRITE)) {
Serial.println(F("file.open failed"));
return;
}
// Print current date time to file.
file.print(F("Test file at: "));
printNow(&file);
file.println();
file.close();
// List files in SD root.
sd.ls(LS_DATE | LS_SIZE);
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,20 @@
// Print a list of error codes, symbols, and comments.
#include "SdFat.h"
void setup() {
Serial.begin(9600);
while (!Serial) {
}
delay(1000);
Serial.println();
Serial.println(F("Code,Symbol - failed operation"));
for (uint8_t code = 0; code <= SD_CARD_ERROR_UNKNOWN; code++) {
Serial.print(code < 16 ? "0X0" : "0X");
Serial.print(code, HEX);
Serial.print(",");
printSdErrorSymbol(&Serial, code);
Serial.print(" - ");
printSdErrorText(&Serial, code);
Serial.println();
}
}
void loop() {}

View File

@@ -0,0 +1,253 @@
/*
* This program will format SD/SDHC/SDXC cards.
* Warning all data will be deleted!
*
* This program attempts to match the format
* generated by SDFormatter available here:
*
* http://www.sdcard.org/consumers/formatter/
*
* For very small cards this program uses FAT16
* and the above SDFormatter uses FAT12.
*/
#include "SdFat.h"
#include "sdios.h"
/*
Set DISABLE_CS_PIN to disable a second SPI device.
For example, with the Ethernet shield, set DISABLE_CS_PIN
to 10 to disable the Ethernet controller.
*/
const int8_t DISABLE_CS_PIN = -1;
/*
Change the value of SD_CS_PIN if you are using SPI
and your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
//==============================================================================
// Serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
uint32_t cardSectorCount = 0;
uint8_t sectorBuffer[512];
//------------------------------------------------------------------------------
// SdCardFactory constructs and initializes the appropriate card.
SdCardFactory cardFactory;
// Pointer to generic SD card.
SdCard* m_card = nullptr;
//------------------------------------------------------------------------------
#define sdError(msg) \
{ \
cout << F("error: ") << F(msg) << endl; \
sdErrorHalt(); \
}
//------------------------------------------------------------------------------
void sdErrorHalt() {
if (!m_card) {
cout << F("Invalid SD_CONFIG") << endl;
} else if (m_card->errorCode()) {
if (m_card->errorCode() == SD_CARD_ERROR_CMD0) {
cout << F("No card, wrong chip select pin, or wiring error?") << endl;
}
cout << F("SD errorCode: ") << hex << showbase;
printSdErrorSymbol(&Serial, m_card->errorCode());
cout << F(" = ") << int(m_card->errorCode()) << endl;
cout << F("SD errorData = ") << int(m_card->errorData()) << endl;
}
while (true) {
}
}
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
// flash erase all data
uint32_t const ERASE_SIZE = 262144L;
void eraseCard() {
cout << endl << F("Erasing\n");
uint32_t firstBlock = 0;
uint32_t lastBlock;
uint16_t n = 0;
do {
lastBlock = firstBlock + ERASE_SIZE - 1;
if (lastBlock >= cardSectorCount) {
lastBlock = cardSectorCount - 1;
}
if (!m_card->erase(firstBlock, lastBlock)) {
sdError("erase failed");
}
cout << '.';
if ((n++) % 64 == 63) {
cout << endl;
}
firstBlock += ERASE_SIZE;
} while (firstBlock < cardSectorCount);
cout << endl;
if (!m_card->readSector(0, sectorBuffer)) {
sdError("readBlock");
}
cout << hex << showbase << setfill('0') << internal;
cout << F("All data set to ") << setw(4) << int(sectorBuffer[0]) << endl;
cout << dec << noshowbase << setfill(' ') << right;
cout << F("Erase done\n");
}
//------------------------------------------------------------------------------
void formatCard() {
ExFatFormatter exFatFormatter;
FatFormatter fatFormatter;
// Format exFAT if larger than 32GB.
bool rtn = cardSectorCount > 67108864
? exFatFormatter.format(m_card, sectorBuffer, &Serial)
: fatFormatter.format(m_card, sectorBuffer, &Serial);
if (!rtn) {
sdErrorHalt();
}
cout << F("Run the SdInfo example for format details.") << endl;
}
//------------------------------------------------------------------------------
void printConfig(SdSpiConfig config) {
if (DISABLE_CS_PIN < 0) {
cout << F(
"\nAssuming the SD is the only SPI device.\n"
"Edit DISABLE_CS_PIN to disable an SPI device.\n");
} else {
cout << F("\nDisabling SPI device on pin ");
cout << int(DISABLE_CS_PIN) << endl;
pinMode(DISABLE_CS_PIN, OUTPUT);
digitalWrite(DISABLE_CS_PIN, HIGH);
}
cout << F("\nAssuming the SD chip select pin is: ") << int(config.csPin);
cout << F("\nEdit SD_CS_PIN to change the SD chip select pin.\n");
}
//------------------------------------------------------------------------------
void printConfig(SdioConfig config) {
(void)config;
cout << F("Assuming an SDIO interface.\n");
}
//------------------------------------------------------------------------------
void setup() {
char c;
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
printConfig(SD_CONFIG);
cout << F("\nType any character to start\n");
while (!Serial.available()) {
yield();
}
// Discard any extra characters.
clearSerialInput();
cout << F(
"\n"
"This program can erase and/or format SD/SDHC/SDXC cards.\n"
"\n"
"Erase uses the card's fast flash erase command.\n"
"Flash erase sets all data to 0X00 for most cards\n"
"and 0XFF for a few vendor's cards.\n"
"\n"
"Cards up to 2 GiB (GiB = 2^30 bytes) will be formated FAT16.\n"
"Cards larger than 2 GiB and up to 32 GiB will be formatted\n"
"FAT32. Cards larger than 32 GiB will be formatted exFAT.\n"
"\n"
"Warning, all data on the card will be erased.\n"
"Enter 'Y' to continue: ");
while (!Serial.available()) {
yield();
}
c = Serial.read();
cout << c << endl;
if (c != 'Y') {
cout << F("Quiting, you did not enter 'Y'.\n");
return;
}
// Read any existing Serial data.
clearSerialInput();
// Select and initialize proper card driver.
m_card = cardFactory.newCard(SD_CONFIG);
if (!m_card || m_card->errorCode()) {
sdError("card init failed.");
return;
}
cardSectorCount = m_card->sectorCount();
if (!cardSectorCount) {
sdError("Get sector count failed.");
return;
}
cout << F("\nCard size: ") << cardSectorCount * 5.12e-7;
cout << F(" GB (GB = 1E9 bytes)\n");
cout << F("Card size: ") << cardSectorCount / 2097152.0;
cout << F(" GiB (GiB = 2^30 bytes)\n");
cout << F("Card will be formated ");
if (cardSectorCount > 67108864) {
cout << F("exFAT\n");
} else if (cardSectorCount > 4194304) {
cout << F("FAT32\n");
} else {
cout << F("FAT16\n");
}
cout << F(
"\n"
"Options are:\n"
"E - erase the card and skip formatting.\n"
"F - erase and then format the card. (recommended)\n"
"Q - quick format the card without erase.\n"
"\n"
"Enter option: ");
while (!Serial.available()) {
yield();
}
c = Serial.read();
cout << c << endl;
if (!strchr("EFQ", c)) {
cout << F("Quiting, invalid option entered.") << endl;
return;
}
if (c == 'E' || c == 'F') {
eraseCard();
}
if (c == 'F' || c == 'Q') {
formatCard();
}
}
void loop() {}

View File

@@ -0,0 +1,281 @@
/*
* This program attempts to initialize an SD card and analyze its structure.
* The CID and CSD registers are also printed in HEX for use in online
* decoders like these.
*
* https://gurumeditation.org/1342/sd-memory-card-register-decoder/
* https://archive.goughlui.com/static/multicid.htm
*/
#include "SdFat.h"
#include "sdios.h"
/*
Set DISABLE_CS_PIN to disable a second SPI device.
For example, with the Ethernet shield, set DISABLE_CS_PIN
to 10 to disable the Ethernet controller.
*/
const int8_t DISABLE_CS_PIN = -1;
/*
Change the value of SD_CS_PIN if you are using SPI
and your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16))
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(16))
#endif // HAS_SDIO_CLASS
//------------------------------------------------------------------------------
SdFs sd;
cid_t cid;
csd_t csd;
scr_t scr;
uint8_t cmd6Data[64];
uint32_t eraseSize;
uint32_t ocr;
static ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void cidDmp() {
cout << F("\nManufacturer ID: ");
cout << uppercase << showbase << hex << int(cid.mid) << dec << endl;
cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
cout << F("Product: ");
for (uint8_t i = 0; i < 5; i++) {
cout << cid.pnm[i];
}
cout << F("\nRevision: ") << cid.prvN() << '.' << cid.prvM() << endl;
cout << F("Serial number: ") << hex << cid.psn() << dec << endl;
cout << F("Manufacturing date: ");
cout << cid.mdtMonth() << '/' << cid.mdtYear() << endl;
cout << F("CID HEX: ");
hexDmp(&cid, sizeof(cid));
}
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void csdDmp() {
eraseSize = csd.eraseSize();
cout << F("\ncardSize: ") << 0.000512 * csd.capacity();
cout << F(" MB (MB = 1,000,000 bytes)\n");
cout << F("flashEraseSize: ") << int(eraseSize) << F(" blocks\n");
cout << F("eraseSingleBlock: ");
if (csd.eraseSingleBlock()) {
cout << F("true\n");
} else {
cout << F("false\n");
}
cout << F("dataAfterErase: ");
if (scr.dataAfterErase()) {
cout << F("ones\n");
} else {
cout << F("zeros\n");
}
cout << F("CSD HEX: ");
hexDmp(&csd, sizeof(csd));
}
//------------------------------------------------------------------------------
void errorPrint() {
if (sd.sdErrorCode()) {
cout << F("SD errorCode: ") << hex << showbase;
printSdErrorSymbol(&Serial, sd.sdErrorCode());
cout << F(" = ") << int(sd.sdErrorCode()) << endl;
cout << F("SD errorData = ") << int(sd.sdErrorData()) << dec << endl;
}
}
//------------------------------------------------------------------------------
void hexDmp(void* reg, uint8_t size) {
uint8_t* u8 = reinterpret_cast<uint8_t*>(reg);
cout << hex << noshowbase;
for (size_t i = 0; i < size; i++) {
cout << setw(2) << setfill('0') << int(u8[i]);
}
cout << dec << endl;
}
//------------------------------------------------------------------------------
bool mbrDmp() {
MbrSector_t mbr;
bool valid = true;
if (!sd.card()->readSector(0, (uint8_t*)&mbr)) {
cout << F("\nread MBR failed.\n");
errorPrint();
return false;
}
cout << F("\nSD Partition Table\n");
cout << F("part,boot,bgnCHS[3],type,endCHS[3],start,length\n");
for (uint8_t ip = 1; ip < 5; ip++) {
MbrPart_t* pt = &mbr.part[ip - 1];
if ((pt->boot != 0 && pt->boot != 0X80) ||
getLe32(pt->relativeSectors) > csd.capacity()) {
valid = false;
}
cout << int(ip) << ',' << uppercase << showbase << hex;
cout << int(pt->boot) << ',';
for (int i = 0; i < 3; i++) {
cout << int(pt->beginCHS[i]) << ',';
}
cout << int(pt->type) << ',';
for (int i = 0; i < 3; i++) {
cout << int(pt->endCHS[i]) << ',';
}
cout << dec << getLe32(pt->relativeSectors) << ',';
cout << getLe32(pt->totalSectors) << endl;
}
if (!valid) {
cout << F("\nMBR not valid, assuming Super Floppy format.\n");
}
return true;
}
//------------------------------------------------------------------------------
void dmpVol() {
cout << F("\nScanning FAT, please wait.\n");
int32_t freeClusterCount = sd.freeClusterCount();
if (sd.fatType() <= 32) {
cout << F("\nVolume is FAT") << int(sd.fatType()) << endl;
} else {
cout << F("\nVolume is exFAT\n");
}
cout << F("sectorsPerCluster: ") << sd.sectorsPerCluster() << endl;
cout << F("fatStartSector: ") << sd.fatStartSector() << endl;
cout << F("dataStartSector: ") << sd.dataStartSector() << endl;
cout << F("clusterCount: ") << sd.clusterCount() << endl;
cout << F("freeClusterCount: ");
if (freeClusterCount >= 0) {
cout << freeClusterCount << endl;
} else {
cout << F("failed\n");
errorPrint();
}
}
//------------------------------------------------------------------------------
void printCardType() {
cout << F("\nCard type: ");
switch (sd.card()->type()) {
case SD_CARD_TYPE_SD1:
cout << F("SD1\n");
break;
case SD_CARD_TYPE_SD2:
cout << F("SD2\n");
break;
case SD_CARD_TYPE_SDHC:
if (csd.capacity() < 70000000) {
cout << F("SDHC\n");
} else {
cout << F("SDXC\n");
}
break;
default:
cout << F("Unknown\n");
}
}
//------------------------------------------------------------------------------
void printConfig(SdSpiConfig config) {
if (DISABLE_CS_PIN < 0) {
cout << F(
"\nAssuming the SD is the only SPI device.\n"
"Edit DISABLE_CS_PIN to disable an SPI device.\n");
} else {
cout << F("\nDisabling SPI device on pin ");
cout << int(DISABLE_CS_PIN) << endl;
pinMode(DISABLE_CS_PIN, OUTPUT);
digitalWrite(DISABLE_CS_PIN, HIGH);
}
cout << F("\nAssuming the SD chip select pin is: ") << int(config.csPin);
cout << F("\nEdit SD_CS_PIN to change the SD chip select pin.\n");
}
//------------------------------------------------------------------------------
void printConfig(SdioConfig config) {
(void)config;
cout << F("Assuming an SDIO interface.\n");
}
//-----------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("SdFat version: ") << SD_FAT_VERSION_STR << endl;
printConfig(SD_CONFIG);
}
//------------------------------------------------------------------------------
void loop() {
// Read any existing Serial data.
clearSerialInput();
// F stores strings in flash to save RAM
cout << F("\ntype any character to start\n");
while (!Serial.available()) {
yield();
}
uint32_t t = millis();
if (!sd.cardBegin(SD_CONFIG)) {
cout << F(
"\nSD initialization failed.\n"
"Do not reformat the card!\n"
"Is the card correctly inserted?\n"
"Is there a wiring/soldering problem?\n");
if (isSpi(SD_CONFIG)) {
cout << F(
"Is SD_CS_PIN set to the correct value?\n"
"Does another SPI device need to be disabled?\n");
}
errorPrint();
return;
}
t = millis() - t;
cout << F("init time: ") << dec << t << " ms" << endl;
if (!sd.card()->readCID(&cid) || !sd.card()->readCSD(&csd) ||
!sd.card()->readOCR(&ocr) || !sd.card()->readSCR(&scr)) {
cout << F("readInfo failed\n");
errorPrint();
return;
}
printCardType();
cout << F("sdSpecVer: ") << 0.01 * scr.sdSpecVer() << endl;
cout << F("HighSpeedMode: ");
if (scr.sdSpecVer() > 101 && sd.card()->cardCMD6(0X00FFFFFF, cmd6Data) &&
(2 & cmd6Data[13])) {
cout << F("true\n");
} else {
cout << F("false\n");
}
cidDmp();
csdDmp();
cout << F("\nOCR: ") << uppercase << showbase;
cout << hex << ocr << dec << endl;
if (!mbrDmp()) {
return;
}
if (!sd.volumeBegin()) {
cout << F("\nvolumeBegin failed. Is the card formatted?\n");
errorPrint();
return;
}
dmpVol();
}

View File

@@ -0,0 +1,80 @@
// An example of the SoftSpiDriver template class.
// This example is for an old Adafruit Data Logging Shield on a Mega.
// Software SPI is required on Mega since this shield connects to pins 10-13.
// This example will also run on an Uno and other boards using software SPI.
//
#include "SdFat.h"
#if SPI_DRIVER_SELECT == 2 // Must be set in SdFat/SdFatConfig.h
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
//
// Chip select may be constant or RAM variable.
const uint8_t SD_CS_PIN = 10;
//
// Pin numbers in templates must be constants.
const uint8_t SOFT_MISO_PIN = 12;
const uint8_t SOFT_MOSI_PIN = 11;
const uint8_t SOFT_SCK_PIN = 13;
// SdFat software SPI template
SoftSpiDriver<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN> softSpi;
// Speed argument is ignored for software SPI.
#if ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(0), &softSpi)
#else // ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(0), &softSpi)
#endif // ENABLE_DEDICATED_SPI
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
yield();
}
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt();
}
if (!file.open("SoftSPI.txt", O_RDWR | O_CREAT)) {
sd.errorHalt(F("open failed"));
}
file.println(F("This line was printed using software SPI."));
file.rewind();
while (file.available()) {
Serial.write(file.read());
}
file.close();
Serial.println(F("Done."));
}
//------------------------------------------------------------------------------
void loop() {}
#else // SPI_DRIVER_SELECT
#error SPI_DRIVER_SELECT must be two in SdFat/SdFatConfig.h
#endif // SPI_DRIVER_SELECT

View File

@@ -0,0 +1,66 @@
// This is a simple SPI loop-back test.
//
// Connect SD_MISO to SD_MOSI
//
// Modify these defines for your configuration.
#define SD_SPI SPI
#define SD_MISO MISO
#define SD_MOSI MOSI
#include "SPI.h"
void setup() {
uint8_t rx, tx;
Serial.begin(9600);
while (!Serial) {
yield();
}
Serial.println(F("\nType any character to start"));
while (!Serial.available()) {
yield();
}
Serial.print("Begin, SD_MISO: ");
Serial.print(SD_MISO), Serial.print(", SD_MOSI: ");
Serial.println(SD_MOSI);
pinMode(SD_MISO, INPUT_PULLUP);
pinMode(SD_MOSI, OUTPUT);
digitalWrite(SD_MOSI, HIGH);
if (!digitalRead(SD_MISO)) {
Serial.println("Error: SD_MISO not HIGH");
goto fail;
}
digitalWrite(SD_MOSI, LOW);
if (digitalRead(SD_MISO)) {
Serial.println("Error: SD_MISO not LOW");
goto fail;
}
pinMode(SD_MISO, INPUT);
pinMode(SD_MOSI, INPUT);
// Modify if SD_SPI.begin has arguments and use this style SdFat begin call:
// sd.begin(SdSpiConfig(CS_PIN, USER_SPI_BEGIN | <other options>, &SD_SPI));
SD_SPI.begin();
// Start with a 400 kHz clock. Try full speed if success for 400 kHz.
SD_SPI.beginTransaction(SPISettings(400000, MSBFIRST, SPI_MODE0));
tx = 0;
do {
rx = SD_SPI.transfer(tx);
if (tx != rx) {
Serial.print("Error rx: 0x");
Serial.print(rx, HEX);
Serial.print(" != tx: 0x");
Serial.println(tx, HEX);
SD_SPI.endTransaction();
goto fail;
}
} while (tx++ < 255);
SD_SPI.endTransaction();
Serial.println("Success!");
return;
fail:
SD_SPI.endTransaction();
Serial.println("Is SD_MISO connected to SD_MOSI?");
Serial.println("Are SD_MISO and SD_MOSI correct?");
}
void loop() {}

View File

@@ -0,0 +1,214 @@
// Test of Teensy exFAT DMA ADC logger.
// This is mainly to test use of RingBuf in an ISR.
// This example only supports pins on the first ADC.
// it has only been tested on Teensy 3.6 and 4.1.
// You should modify it for serious use as a data logger.
//
#include "ADC.h"
#include "DMAChannel.h"
#include "FreeStack.h"
#include "RingBuf.h"
#include "SdFat.h"
// Pin must be on first ADC.
#define ADC_PIN A0
// 400 sector RingBuf - could be larger on Teensy 4.1.
const size_t RING_BUF_SIZE = 400 * 512;
// Preallocate 8GiB file.
const uint64_t PRE_ALLOCATE_SIZE = 8ULL << 30;
// Use FIFO SDIO.
#define SD_CONFIG SdioConfig(FIFO_SDIO)
ADC adc;
DMAChannel dma(true);
SdFs sd;
FsFile file;
// Ping-pong DMA buffer.
DMAMEM static uint16_t __attribute__((aligned(32))) dmaBuf[2][256];
// Count of DMA interrupts.
volatile size_t dmaCount;
// RingBuf for 512 byte sectors.
RingBuf<FsFile, RING_BUF_SIZE> rb;
// Shared between ISR and background.
volatile size_t maxBytesUsed;
// Overrun error for write to RingBuf.
volatile bool overrun;
//------------------------------------------------------------------------------
// ISR for DMA.
static void isr() {
if (!overrun) {
// Clear cache for buffer filled by DMA to insure read from DMA memory.
arm_dcache_delete((void*)dmaBuf[dmaCount & 1], 512);
// Enable RingBuf functions to be called in ISR.
rb.beginISR();
if (rb.write(dmaBuf[dmaCount & 1], 512) == 512) {
dmaCount++;
if (rb.bytesUsed() > maxBytesUsed) {
maxBytesUsed = rb.bytesUsed();
}
} else {
overrun = true;
}
// End use of RingBuf functions in ISR.
rb.endISR();
}
dma.clearComplete();
dma.clearInterrupt();
#if defined(__IMXRT1062__)
// Handle clear interrupt glitch in Teensy 4.x!
asm("DSB");
#endif // defined(__IMXRT1062__)
}
//------------------------------------------------------------------------------
#if defined(__IMXRT1062__) // Teensy 4.x
#define SOURCE_SADDR ADC1_R0
#define SOURCE_EVENT DMAMUX_SOURCE_ADC1
#else
#define SOURCE_SADDR ADC0_RA
#define SOURCE_EVENT DMAMUX_SOURCE_ADC0
#endif
//------------------------------------------------------------------------------
static void init(uint8_t pin) {
dma.begin();
dma.attachInterrupt(isr);
dma.source((volatile const signed short&)SOURCE_SADDR);
dma.destinationBuffer((volatile uint16_t*)dmaBuf, sizeof(dmaBuf));
dma.interruptAtHalf();
dma.interruptAtCompletion();
dma.triggerAtHardwareEvent(SOURCE_EVENT);
dma.enable();
adc.adc0->enableDMA();
adc.adc0->startContinuous(pin);
}
//------------------------------------------------------------------------------
void stopDma() {
adc.adc0->disableDMA();
dma.disable();
}
//------------------------------------------------------------------------------
void printTest(Print* pr) {
if (file.fileSize() < 1024 * 2) {
return;
}
file.rewind();
rb.begin(&file);
// Could readIn RING_BUF_SIZE bytes and write to a csv file in a loop.
if (rb.readIn(2048) != 2048) {
sd.errorHalt("rb.readIn failed");
}
uint16_t data;
for (size_t i = 0; i < 1024; i++) {
pr->print(i);
pr->print(',');
// Test read with: template <typename Type>bool read(Type* data).
rb.read(&data);
pr->println(data);
}
}
//------------------------------------------------------------------------------
void runTest(uint8_t pin) {
dmaCount = 0;
maxBytesUsed = 0;
overrun = false;
do {
delay(10);
} while (Serial.read() >= 0);
if (!file.open("IsrLoggerTest.bin", O_CREAT | O_TRUNC | O_RDWR)) {
sd.errorHalt("file.open failed");
}
if (!file.preAllocate(PRE_ALLOCATE_SIZE)) {
sd.errorHalt("file.preAllocate failed");
}
rb.begin(&file);
Serial.println("Type any character to stop\n");
init(pin);
uint32_t samplingTime = micros();
while (!overrun && !Serial.available()) {
size_t n = rb.bytesUsed();
if ((n + file.curPosition()) >= (PRE_ALLOCATE_SIZE - 512)) {
Serial.println("File full - stopping");
break;
}
if (n >= 512) {
if (rb.writeOut(512) != 512) {
Serial.println("writeOut() failed");
file.close();
return;
}
}
}
stopDma();
samplingTime = micros() - samplingTime;
if (!rb.sync()) {
Serial.println("sync() failed");
file.close();
return;
}
if (!file.truncate()) {
sd.errorHalt("truncate failed");
}
if (overrun) {
Serial.println("Overrun ERROR!!");
}
Serial.print("dmsCount ");
Serial.println(dmaCount);
Serial.print("RingBufSize ");
Serial.println(RING_BUF_SIZE);
Serial.print("maxBytesUsed ");
Serial.println(maxBytesUsed);
Serial.print("fileSize ");
file.printFileSize(&Serial);
Serial.println();
Serial.print(0.000001 * samplingTime);
Serial.println(" seconds");
Serial.print(1.0 * file.fileSize() / samplingTime, 3);
Serial.println(" MB/sec\n");
printTest(&Serial);
file.close();
}
//------------------------------------------------------------------------------
void waitSerial(const char* msg) {
do {
delay(10);
} while (Serial.read() >= 0);
Serial.println(msg);
while (!Serial.available()) {
}
Serial.println();
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {
yield();
}
waitSerial("Type any character to begin");
Serial.print("FreeStack: ");
Serial.println(FreeStack());
}
//------------------------------------------------------------------------------
void loop() {
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
// Try for max speed.
adc.adc0->setAveraging(1);
adc.adc0->setResolution(10);
adc.adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc.adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
runTest(ADC_PIN);
waitSerial("Type any character to run test again");
}

View File

@@ -0,0 +1,135 @@
// Test of time-stamp callback with Teensy 3/4.
// The upload time will be used to set the RTC.
// You must arrange for syncing the RTC.
#include <TimeLib.h>
#include "SdFat.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
//------------------------------------------------------------------------------
// Call back for file timestamps. Only called for file create and sync().
void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
// Return date using FS_DATE macro to format fields.
*date = FS_DATE(year(), month(), day());
// Return time using FS_TIME macro to format fields.
*time = FS_TIME(hour(), minute(), second());
// Return low time bits in units of 10 ms.
*ms10 = second() & 1 ? 100 : 0;
}
//------------------------------------------------------------------------------
time_t getTeensy3Time() { return Teensy3Clock.get(); }
//------------------------------------------------------------------------------
void printField(Print* pr, char sep, uint8_t v) {
if (sep) {
pr->write(sep);
}
if (v < 10) {
pr->write('0');
}
pr->print(v);
}
//------------------------------------------------------------------------------
void printNow(Print* pr) {
pr->print(year());
printField(pr, '-', month());
printField(pr, '-', day());
printField(pr, ' ', hour());
printField(pr, ':', minute());
printField(pr, ':', second());
}
//------------------------------------------------------------------------------
void setup() {
// set the Time library to use Teensy 3.0's RTC to keep time
setSyncProvider(getTeensy3Time);
Serial.begin(9600);
while (!Serial) {
yield();
}
Serial.println(F("Type any character to begin"));
while (!Serial.available()) {
yield();
}
if (timeStatus() != timeSet) {
Serial.println("Unable to sync with the RTC");
return;
}
Serial.print(F("DateTime::now "));
printNow(&Serial);
Serial.println();
// Set callback
FsDateTime::setCallback(dateTime);
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
// Remove old version to set create time.
if (sd.exists("RtcTest.txt")) {
sd.remove("RtcTest.txt");
}
if (!file.open("RtcTest.txt", FILE_WRITE)) {
Serial.println(F("file.open failed"));
return;
}
// Print current date time to file.
file.print(F("Test file at: "));
printNow(&file);
file.println();
file.close();
// List files in SD root.
sd.ls(LS_DATE | LS_SIZE);
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,220 @@
// Simple performance test for Teensy 3.5/3.6 4.0 SDHC.
// Demonstrates yield() efficiency for SDIO modes.
#include "SdFat.h"
// Use built-in SD for SPI modes on Teensy 3.5/3.6.
// Teensy 4.0 use first SPI port.
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
// 32 KiB buffer.
const size_t BUF_DIM = 32768;
// 8 MiB file.
const uint32_t FILE_SIZE = 256UL * BUF_DIM;
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
uint8_t buf[BUF_DIM];
// buffer as uint32_t
uint32_t* buf32 = (uint32_t*)buf;
// Total usec in read/write calls.
uint32_t totalMicros = 0;
// Time in yield() function.
uint32_t yieldMicros = 0;
// Number of yield calls.
uint32_t yieldCalls = 0;
// Max busy time for single yield call.
uint32_t yieldMaxUsec = 0;
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void errorHalt(const char* msg) {
Serial.print("Error: ");
Serial.println(msg);
if (sd.sdErrorCode()) {
if (sd.sdErrorCode() == SD_CARD_ERROR_ACMD41) {
Serial.println("Try power cycling the SD card.");
}
printSdErrorSymbol(&Serial, sd.sdErrorCode());
Serial.print(", ErrorData: 0X");
Serial.println(sd.sdErrorData(), HEX);
}
while (true) {
}
}
bool ready = false;
//------------------------------------------------------------------------------
bool sdBusy() { return ready ? sd.card()->isBusy() : false; }
//------------------------------------------------------------------------------
// Replace "weak" system yield() function.
void yield() {
// Only count cardBusy time.
if (!sdBusy()) {
return;
}
uint32_t m = micros();
yieldCalls++;
while (sdBusy()) {
// Do something here.
}
m = micros() - m;
if (m > yieldMaxUsec) {
yieldMaxUsec = m;
}
yieldMicros += m;
}
//------------------------------------------------------------------------------
void runTest() {
// Zero Stats
totalMicros = 0;
yieldMicros = 0;
yieldCalls = 0;
yieldMaxUsec = 0;
if (!file.open("TeensyDemo.bin", O_RDWR | O_CREAT)) {
errorHalt("open failed");
}
Serial.println("\nsize,write,read");
Serial.println("bytes,KB/sec,KB/sec");
for (size_t nb = 512; nb <= BUF_DIM; nb *= 2) {
uint32_t nRdWr = FILE_SIZE / nb;
if (!file.truncate(0)) {
errorHalt("truncate failed");
}
Serial.print(nb);
Serial.print(',');
uint32_t t = micros();
for (uint32_t n = 0; n < nRdWr; n++) {
// Set start and end of buffer.
buf32[0] = n;
buf32[nb / 4 - 1] = n;
if (nb != file.write(buf, nb)) {
errorHalt("write failed");
}
}
t = micros() - t;
totalMicros += t;
Serial.print(1000.0 * FILE_SIZE / t);
Serial.print(',');
file.rewind();
t = micros();
for (uint32_t n = 0; n < nRdWr; n++) {
if ((int)nb != file.read(buf, nb)) {
errorHalt("read failed");
}
// crude check of data.
if (buf32[0] != n || buf32[nb / 4 - 1] != n) {
errorHalt("data check");
}
}
t = micros() - t;
totalMicros += t;
Serial.println(1000.0 * FILE_SIZE / t);
}
file.close();
Serial.print("\ntotalMicros ");
Serial.println(totalMicros);
Serial.print("yieldMicros ");
Serial.println(yieldMicros);
Serial.print("yieldCalls ");
Serial.println(yieldCalls);
Serial.print("yieldMaxUsec ");
Serial.println(yieldMaxUsec);
// Serial.print("kHzSdClk ");
// Serial.println(kHzSdClk());
Serial.println("Done");
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {
}
}
//------------------------------------------------------------------------------
void loop() {
static bool warn = true;
if (warn) {
warn = false;
Serial.println(
"SD cards must be power cycled to leave\n"
"SPI mode so do SDIO tests first.\n"
"\nCycle power on the card if an error occurs.");
}
clearSerialInput();
Serial.println(
"\nType '1' for FIFO SDIO"
"\n '2' for DMA SDIO"
"\n '3' for Dedicated SPI"
"\n '4' for Shared SPI");
while (!Serial.available()) {
}
char c = Serial.read();
if (c == '1') {
if (!sd.begin(SdioConfig(FIFO_SDIO))) {
errorHalt("begin failed");
}
Serial.println("\nFIFO SDIO mode.");
} else if (c == '2') {
if (!sd.begin(SdioConfig(DMA_SDIO))) {
errorHalt("begin failed");
}
Serial.println("\nDMA SDIO mode - slow for small transfers.");
} else if (c == '3') {
#if ENABLE_DEDICATED_SPI
if (!sd.begin(SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(50)))) {
errorHalt("begin failed");
}
Serial.println("\nDedicated SPI mode.");
#else // ENABLE_DEDICATED_SPI
Serial.println("ENABLE_DEDICATED_SPI must be non-zero.");
return;
#endif // ENABLE_DEDICATED_SPI
} else if (c == '4') {
if (!sd.begin(SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50)))) {
errorHalt("begin failed");
}
Serial.println("\nShared SPI mode - slow for small transfers.");
} else {
Serial.println("Invalid input");
return;
}
ready = true;
runTest();
ready = false;
}

View File

@@ -0,0 +1,151 @@
// Test Teensy SDIO with write busy in a data logger demo.
//
// The driver writes to the uSDHC controller's FIFO then returns
// while the controller writes the data to the SD. The first sector
// puts the controller in write mode and takes about 11 usec on a
// Teensy 4.1. About 5 usec is required to write a sector when the
// controller is in write mode.
#include "RingBuf.h"
#include "SdFat.h"
// Use Teensy SDIO
#define SD_CONFIG SdioConfig(FIFO_SDIO)
// Interval between points for 25 ksps.
#define LOG_INTERVAL_USEC 40
// Size to log 10 byte lines at 25 kHz for more than ten minutes.
#define LOG_FILE_SIZE 10 * 25000 * 600 // 150,000,000 bytes.
// Space to hold more than 800 ms of data for 10 byte lines at 25 ksps.
#define RING_BUF_CAPACITY 400 * 512
#define LOG_FILENAME "SdioLogger.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;
// Min spare micros in loop.
int32_t minSpareMicros = INT32_MAX;
// 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;
}
if (n >= 512 && !file.isBusy()) {
// Not busy only allows one sector before possible busy wait.
// Write one sector from RingBuf to file.
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.
while (micros() < logTime) {
}
// Read ADC0 - about 17 usec on Teensy 4, Teensy 3.6 is faster.
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;
}
}
// 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);
file.close();
}
void clearSerialInput() {
for (uint32_t m = micros(); micros() - m < 10000;) {
if (Serial.read() >= 0) {
m = micros();
}
}
}
void setup() {
Serial.begin(9600);
while (!Serial) {
}
// Go faster or log more channels. ADC quality will suffer.
// analogReadAveraging(1);
}
void loop() {
clearSerialInput();
Serial.println("Type any character to start");
while (!Serial.available()) {
}
clearSerialInput();
logData();
}

View File

@@ -0,0 +1,97 @@
// Simple test of Unicode filename.
// Unicode is supported as UTF-8 encoded strings.
#include "SdFat.h"
// USE_UTF8_LONG_NAMES must be non-zero in SdFat/src/SdFatCongfig.h
#if USE_UTF8_LONG_NAMES
#define UTF8_FOLDER u8"😀"
const char* names[] = {u8"россиянин", u8"très élégant", u8"狗.txt", nullptr};
// Remove files if non-zero.
#define REMOVE_UTF8_FILES 1
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 0
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16))
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(16))
#endif // HAS_SDIO_CLASS
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
void setup() {
Serial.begin(9600);
while (!Serial) {
yield();
}
Serial.println("Type any character to begin");
while (!Serial.available()) {
yield();
}
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
if (!sd.exists(UTF8_FOLDER)) {
if (!sd.mkdir(UTF8_FOLDER)) {
Serial.println("sd.mkdir failed");
return;
}
}
if (!sd.chdir(UTF8_FOLDER)) {
Serial.println("sd.chdir failed");
return;
}
for (uint8_t i = 0; names[i]; i++) {
if (!file.open(names[i], O_WRONLY | O_CREAT)) {
Serial.println("file.open failed");
return;
}
file.println(names[i]);
file.close();
}
Serial.println("ls:");
sd.ls("/", LS_SIZE | LS_R);
#if REMOVE_UTF8_FILES // For debug test of remove and rmdir.
for (uint8_t i = 0; names[i]; i++) {
sd.remove(names[i]);
}
sd.chdir();
sd.rmdir(UTF8_FOLDER);
Serial.println("After remove and rmdir");
sd.ls(LS_SIZE | LS_R);
#endif // REMOVE_UTF8_FILES
Serial.println("Done!");
}
void loop() {}
#else // USE_UTF8_LONG_NAMES
#error USE_UTF8_LONG_NAMES must be non-zero in SdFat/src/SdFatCongfig.h
#endif // USE_UTF8_LONG_NAMES

View File

@@ -0,0 +1,47 @@
// An example of an external chip select functions.
// Useful for port expanders or replacement of the standard GPIO functions.
//
#include "SdFat.h"
// SD_CHIP_SELECT_MODE must be set to one or two in SdFat/SdFatConfig.h.
// A value of one allows optional replacement and two requires replacement.
#if SD_CHIP_SELECT_MODE == 1 || SD_CHIP_SELECT_MODE == 2
// SD chip select pin.
#define SD_CS_PIN SS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50))
SdFat sd;
// Stats to verify function calls.
uint32_t initCalls = 0;
uint32_t writeCalls = 0;
//------------------------------------------------------------------------------
// Modify these functions for your port expander or custom GPIO library.
void sdCsInit(SdCsPin_t pin) {
initCalls++;
pinMode(pin, OUTPUT);
}
void sdCsWrite(SdCsPin_t pin, bool level) {
writeCalls++;
digitalWrite(pin, level);
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
sd.ls(&Serial, LS_SIZE);
Serial.print(F("sdCsInit calls: "));
Serial.println(initCalls);
Serial.print(F("sdCsWrite calls: "));
Serial.println(writeCalls);
}
//------------------------------------------------------------------------------
void loop() {}
#else // SD_CHIP_SELECT_MODE == 1 || SD_CHIP_SELECT_MODE == 2
#error SD_CHIP_SELECT_MODE must be one or two in SdFat/SdFatConfig.h
#endif // SD_CHIP_SELECT_MODE == 1 || SD_CHIP_SELECT_MODE == 2

View File

@@ -0,0 +1,75 @@
// An example of an external SPI driver.
//
#include "SPI.h" // Only required if you use features in the SPI library.
#include "SdFat.h"
#if SPI_DRIVER_SELECT == 3 // Must be set in SdFat/SdFatConfig.h
// SD chip select pin.
#define SD_CS_PIN SS
// This is a simple driver based on the the standard SPI.h library.
// You can write a driver entirely independent of SPI.h.
// It can be optimized for your board or a different SPI port can be used.
// The driver must be derived from SdSpiBaseClass.
// See: SdFat/src/SpiDriver/SdSpiBaseClass.h
class MySpiClass : public SdSpiBaseClass {
public:
// Activate SPI hardware with correct speed and mode.
void activate() { SPI.beginTransaction(m_spiSettings); }
// Initialize the SPI bus.
void begin(SdSpiConfig config) {
(void)config;
SPI.begin();
}
// Deactivate SPI hardware.
void deactivate() { SPI.endTransaction(); }
// Receive a byte.
uint8_t receive() { return SPI.transfer(0XFF); }
// Receive multiple bytes.
// Replace this function if your board has multiple byte receive.
uint8_t receive(uint8_t* buf, size_t count) {
for (size_t i = 0; i < count; i++) {
buf[i] = SPI.transfer(0XFF);
}
return 0;
}
// Send a byte.
void send(uint8_t data) { SPI.transfer(data); }
// Send multiple bytes.
// Replace this function if your board has multiple byte send.
void send(const uint8_t* buf, size_t count) {
for (size_t i = 0; i < count; i++) {
SPI.transfer(buf[i]);
}
}
// Save SPISettings for new max SCK frequency
void setSckSpeed(uint32_t maxSck) {
m_spiSettings = SPISettings(maxSck, MSBFIRST, SPI_MODE0);
}
private:
SPISettings m_spiSettings;
} mySpi;
#if ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(50), &mySpi)
#else // ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50), &mySpi)
#endif // ENABLE_DEDICATED_SPI
SdFat sd;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
sd.ls(&Serial, LS_SIZE);
Serial.println("Done");
}
//------------------------------------------------------------------------------
void loop() {}
#else // SPI_DRIVER_SELECT
#error SPI_DRIVER_SELECT must be three in SdFat/SdFatConfig.h
#endif // SPI_DRIVER_SELECT

View File

@@ -0,0 +1,273 @@
/*
* This program is a simple binary write/read benchmark.
*/
#include "SdFat.h"
#include "FreeStack.h"
#include "sdios.h"
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 3
/*
Change the value of SD_CS_PIN if you are using SPI and
your hardware does not use the default value, SS.
Common values are:
Arduino Ethernet shield: pin 4
Sparkfun SD shield: pin 8
Adafruit SD shields and modules: pin 10
*/
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
// Set PRE_ALLOCATE true to pre-allocate file clusters.
const bool PRE_ALLOCATE = true;
// Set SKIP_FIRST_LATENCY true if the first read/write to the SD can
// be avoid by writing a file header or reading the first record.
const bool SKIP_FIRST_LATENCY = true;
// Size of read/write.
const size_t BUF_SIZE = 512;
// File size in MB where MB = 1,000,000 bytes.
const uint32_t FILE_SIZE_MB = 5;
// Write pass count.
const uint8_t WRITE_COUNT = 2;
// Read pass count.
const uint8_t READ_COUNT = 2;
//==============================================================================
// End of configuration constants.
//------------------------------------------------------------------------------
// File size in bytes.
const uint32_t FILE_SIZE = 1000000UL * FILE_SIZE_MB;
// Insure 4-byte alignment.
uint32_t buf32[(BUF_SIZE + 3) / 4];
uint8_t* buf = (uint8_t*)buf32;
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
// Serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// Store error strings in flash to save RAM.
#define error(s) sd.errorHalt(&Serial, F(s))
//------------------------------------------------------------------------------
void cidDmp() {
cid_t cid;
if (!sd.card()->readCID(&cid)) {
error("readCID failed");
}
cout << F("\nManufacturer ID: ");
cout << uppercase << showbase << hex << int(cid.mid) << dec << endl;
cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
cout << F("Product: ");
for (uint8_t i = 0; i < 5; i++) {
cout << cid.pnm[i];
}
cout << F("\nRevision: ") << cid.prvN() << '.' << cid.prvM() << endl;
cout << F("Serial number: ") << hex << cid.psn() << dec << endl;
cout << F("Manufacturing date: ");
cout << cid.mdtMonth() << '/' << cid.mdtYear() << endl;
cout << endl;
}
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(1000);
cout << F("\nUse a freshly formatted SD for best performance.\n");
if (!ENABLE_DEDICATED_SPI) {
cout << F(
"\nSet ENABLE_DEDICATED_SPI nonzero in\n"
"SdFatConfig.h for best SPI performance.\n");
}
// use uppercase in hex and use 0X base prefix
cout << uppercase << showbase << endl;
}
//------------------------------------------------------------------------------
void loop() {
float s;
uint32_t t;
uint32_t maxLatency;
uint32_t minLatency;
uint32_t totalLatency;
bool skipLatency;
// Discard any input.
clearSerialInput();
// F() stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
#if HAS_UNUSED_STACK
cout << F("FreeStack: ") << FreeStack() << endl;
#endif // HAS_UNUSED_STACK
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
if (sd.fatType() == FAT_TYPE_EXFAT) {
cout << F("Type is exFAT") << endl;
} else {
cout << F("Type is FAT") << int(sd.fatType()) << endl;
}
cout << F("Card size: ") << sd.card()->sectorCount() * 512E-9;
cout << F(" GB (GB = 1E9 bytes)") << endl;
cidDmp();
// open or create file - truncate existing file.
if (!file.open("bench.dat", O_RDWR | O_CREAT | O_TRUNC)) {
error("open failed");
}
// fill buf with known data
if (BUF_SIZE > 1) {
for (size_t i = 0; i < (BUF_SIZE - 2); i++) {
buf[i] = 'A' + (i % 26);
}
buf[BUF_SIZE - 2] = '\r';
}
buf[BUF_SIZE - 1] = '\n';
cout << F("FILE_SIZE_MB = ") << FILE_SIZE_MB << endl;
cout << F("BUF_SIZE = ") << BUF_SIZE << F(" bytes\n");
cout << F("Starting write test, please wait.") << endl << endl;
// do write test
uint32_t n = FILE_SIZE / BUF_SIZE;
cout << F("write speed and latency") << endl;
cout << F("speed,max,min,avg") << endl;
cout << F("KB/Sec,usec,usec,usec") << endl;
for (uint8_t nTest = 0; nTest < WRITE_COUNT; nTest++) {
file.truncate(0);
if (PRE_ALLOCATE) {
if (!file.preAllocate(FILE_SIZE)) {
error("preAllocate failed");
}
}
maxLatency = 0;
minLatency = 9999999;
totalLatency = 0;
skipLatency = SKIP_FIRST_LATENCY;
t = millis();
for (uint32_t i = 0; i < n; i++) {
uint32_t m = micros();
if (file.write(buf, BUF_SIZE) != BUF_SIZE) {
error("write failed");
}
m = micros() - m;
totalLatency += m;
if (skipLatency) {
// Wait until first write to SD, not just a copy to the cache.
skipLatency = file.curPosition() < 512;
} else {
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
}
}
file.sync();
t = millis() - t;
s = file.fileSize();
cout << s / t << ',' << maxLatency << ',' << minLatency;
cout << ',' << totalLatency / n << endl;
}
cout << endl << F("Starting read test, please wait.") << endl;
cout << endl << F("read speed and latency") << endl;
cout << F("speed,max,min,avg") << endl;
cout << F("KB/Sec,usec,usec,usec") << endl;
// do read test
for (uint8_t nTest = 0; nTest < READ_COUNT; nTest++) {
file.rewind();
maxLatency = 0;
minLatency = 9999999;
totalLatency = 0;
skipLatency = SKIP_FIRST_LATENCY;
t = millis();
for (uint32_t i = 0; i < n; i++) {
buf[BUF_SIZE - 1] = 0;
uint32_t m = micros();
int32_t nr = file.read(buf, BUF_SIZE);
if (nr != BUF_SIZE) {
error("read failed");
}
m = micros() - m;
totalLatency += m;
if (buf[BUF_SIZE - 1] != '\n') {
error("data check error");
}
if (skipLatency) {
skipLatency = false;
} else {
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
}
}
s = file.fileSize();
t = millis() - t;
cout << s / t << ',' << maxLatency << ',' << minLatency;
cout << ',' << totalLatency / n << endl;
}
cout << endl << F("Done") << endl;
file.close();
sd.end();
}

View File

@@ -0,0 +1,119 @@
#include "SdFat.h"
#ifdef __AVR__
const uint32_t FILE_SIZE_MiB = 10UL;
#else // __AVR__
const uint32_t FILE_SIZE_MiB = 100UL;
#endif
bool waitBusy = true;
#define SD_CONFIG SdSpiConfig(SS, DEDICATED_SPI)
//#define SD_CONFIG SdSpiConfig(SS, SHARED_SPI)
// Config for Teensy 3.5/3.6 buit-in SD.
//#define SD_CONFIG SdSpiConfig(SDCARD_SS_PIN, DEDICATED_SPI)
//#define SD_CONFIG SdioConfig(FIFO_SDIO)
//------------------------------------------------------------------------------
const uint64_t FILE_SIZE = (uint64_t)FILE_SIZE_MiB << 20;
SdFs sd;
FsFile file;
uint8_t buf[512];
#define error(s) sd.errorHalt(&Serial, F(s))
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(1000);
//------------------------------------------------------------------------------
}
void loop() {
clearSerialInput();
Serial.println(F("\nType any character to start\n"));
while (!Serial.available()) {
yield();
}
// Initialize the SD card.
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt();
}
if (!file.open("SdBusyTest.bin", O_RDWR | O_CREAT |O_TRUNC)) {
error("file open failed");
}
if (!file.preAllocate(FILE_SIZE)) {
error("preallocate failed");
}
Serial.print(F("Starting write of "));
Serial.print(FILE_SIZE_MiB);
Serial.println(F(" MiB."));
uint32_t maxWrite = 0;
uint32_t minWrite = 99999999;
uint32_t ms = millis();
uint32_t maxBusy = 0;
uint32_t minBusy = UINT32_MAX;
// Write a dummy sector to start a multi-sector write.
if(file.write(buf, sizeof(buf)) != sizeof(buf)) {
error("write failed for first sector");
}
while (file.position() < FILE_SIZE) {
uint32_t m = micros();
if (waitBusy) {
m = micros();
while (sd.card()->isBusy()) {}
m = micros() - m;
if (m < minBusy) {
minBusy = m;
}
if (m > maxBusy) {
maxBusy = m;
}
}
m = micros();
if (file.write(buf, sizeof(buf)) != sizeof(buf)) {
error("write failed");
}
m = micros() - m;
if (m < minWrite) {
minWrite = m;
}
if (m > maxWrite) {
maxWrite = m;
}
}
file.close();
ms = millis() - ms;
Serial.println(F("\nTimes in micros"));
if (waitBusy) {
Serial.print(F("minBusy: "));
Serial.println(minBusy);
Serial.print(F("maxBusy: "));
Serial.println(maxBusy);
}
Serial.print(F("minWrite: "));
Serial.println(minWrite);
Serial.print(F("maxWrite: "));
Serial.println(maxWrite);
Serial.print(1e-3*ms);
Serial.println(F(" Seconds"));
Serial.print(1.0*FILE_SIZE/ms);
Serial.println(F(" KB/sec"));
}

View File

@@ -0,0 +1,51 @@
#include "SdFat.h"
#define DUMP_RAW 0
#define DUMP_UPCASE 0
const uint8_t CS_PIN = SS;
//#define SD_CONFIG SdioConfig(FIFO_SDIO)
#define SD_CONFIG SdSpiConfig(CS_PIN)
SdExFat sd;
#define error(s) sd.errorHalt(&Serial, F(s))
void setup() {
Serial.begin(9600);
while (!Serial) {
yield();
}
Serial.println(F("Type any character to begin"));
while (!Serial.available()) {
yield();
}
if (!sd.begin(SD_CONFIG)){
error("begin failed");
}
#if DUMP_RAW
sd.dmpSector(&Serial, 0);
for (uint8_t i = 0; i < 24; i++) {
sd.dmpSector(&Serial, 0X8000 + i);
Serial.println();
}
return;
#endif // DUMP_RAW
ExFatFile root;
if (!root.openRoot(&sd)) {
error("openRoot failed");
}
sd.printDir(&Serial, &root);
// startSector = 0, sectorCount = 1.
sd.dmpFat(&Serial, 0, 1);
sd.dmpBitmap(&Serial);
sd.printVolInfo(&Serial);
sd.checkUpcase(&Serial);
#if DUMP_UPCASE
sd.printUpcase(&Serial);
#endif // DUMP_UPCASE
// sd.dmpCluster(&Serial, 8, 0, 4);
Serial.println("Done");
}
void loop() {
// put your main code here, to run repeatedly:
}

View File

@@ -0,0 +1,43 @@
#include "SdFat.h"
SdFs sd;
FsFile file;
const char* name[] = {
"SFN.TXT",
"LongFilename.txt",
#if USE_UTF8_LONG_NAMES
u8"très élégant.txt",
#endif // USE_UTF8_LONG_NAMES
nullptr};
char buf[32];
void setup() {
Serial.begin(9600);
while (!Serial) {}
Serial.println("Type any character to begin");
while (!Serial.available()) {}
if (!sd.begin(SS)) {
sd.initErrorHalt();
}
for (uint8_t i = 0; name[i]; i++) {
if (!file.open(name[i], O_CREAT |O_RDWR)) {
sd.errorHalt("open");
}
size_t len = strlen(name[i]);
size_t rtn = file.getName(buf, len);
if (rtn != 0) {
Serial.println("fail len");
}
rtn = file.getName(buf, len + 1);
if (rtn != len) {
Serial.println("fail len + 1");
}
Serial.print(rtn);
Serial.print(' ');
Serial.println(buf);
if (!file.remove()) {
sd.errorHalt("remove");
}
}
Serial.println("Done");
}
void loop() {}

View File

@@ -0,0 +1,140 @@
/*
* This sketch is a test of subdirectory and file creation.
* It also tests allocation of clusters to directories.
*
* It will create two subdirectories and create enough files
* to force the allocation of a cluster to each directory.
*
* More than 3000 files may be created on a FAT32 volume.
*
* Note: Some cards may 'stutter' others just get slow due
* to the number of flash erases this program causes.
*/
#include <SdFat.h>
const uint8_t SD_CHIP_SELECT = SS;
SdFat sd;
typedef File file_t;
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(&Serial, F(s))
/*
* create enough files to force a cluster to be allocated to dir.
*/
void dirAllocTest(file_t* dir) {
char buf[32], name[32];
file_t file;
uint16_t n;
uint32_t size = dir->dirSize();
// create files and write name to file
for (n = 0; ; n++){
// make file name
sprintf(name, "%u.TXT", n);
// open start time
uint32_t t0 = millis();
if (!file.open(dir, name, O_WRONLY | O_CREAT | O_EXCL)) {
error("open for write failed");
}
// open end time and write start time
uint32_t t1 = millis();
// write file name to file
file.print(name);
if (!file.close()) error("close write");
// write end time
uint32_t t2 = millis();
Serial.print(F("WR "));
Serial.print(n);
Serial.write(' ');
// print time to create file
Serial.print(t1 - t0);
Serial.write(' ');
// print time to write file
Serial.println(t2 - t1);
// directory size will change when a cluster is added
if (dir->curPosition() > size) break;
}
// read files and check content
for (uint16_t i = 0; i <= n; i++) {
sprintf(name, "%u.TXT", i);
// open start time
uint32_t t0 = millis();
if (!file.open(dir, name, O_RDONLY)) {
error("open for read failed");
}
// open end time and read start time
uint32_t t1 = millis();
int16_t nr = file.read(buf, sizeof(buf));
if (nr < 5) error("file.read failed");
// read end time
uint32_t t2 = millis();
// check file content
if (strlen(name) != (size_t)nr || strncmp(name, buf, nr)) {
error("content compare failed");
}
if (!file.close()) error("close read failed");
Serial.print(F("RD "));
Serial.print(i);
Serial.write(' ');
// print open time
Serial.print(t1 - t0);
Serial.write(' ');
// print read time
Serial.println(t2 - t1);
}
}
void setup() {
file_t root;
Serial.begin(9600);
while (!Serial) {} // wait for Leonardo
Serial.println(F("Type any character to start"));
while (Serial.read() <= 0) {}
delay(200); // Catch Due reset problem
// initialize the SD card at SPI_FULL_SPEED for best performance.
// try lower speed if bus errors occur.
if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) {
sd.initErrorHalt(&Serial);
}
root.openRoot(&sd);
uint32_t m = millis();
// write files to root if not FAT16
if (sd.fatType() != 16) {
Serial.println(F("Writing files to root"));
dirAllocTest(&root);
}
// create sub1 and write files
file_t sub1;
if (!sub1.mkdir(&root, "SUB1")) error("makdeDir SUB1 failed");
Serial.println(F("Writing files to SUB1"));
dirAllocTest(&sub1);
// create sub2 and write files
file_t sub2;
if (!sub2.mkdir(&sub1, "SUB2")) error("mkdir SUB2 failed");
Serial.println(F("Writing files to SUB2"));
dirAllocTest(&sub2);
m = millis() - m;
Serial.print(F("Done millis: "));
Serial.println(m);
}
void loop() { }

View File

@@ -0,0 +1,99 @@
/*
* This sketch will remove the files and directories
* created by the SdFatMakeDir.pde sketch.
*
* Performance is erratic due to the large number
* of flash erase operations caused by many random
* writes to file structures.
*/
#include <SdFat.h>
const uint8_t SD_CHIP_SELECT = SS;
SdFat sd;
typedef File file_t;
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(&Serial, F(s))
/*
* remove all files in dir.
*/
void deleteFiles(FatFile* dir) {
char name[32];
file_t file;
// open and delete files
for (uint16_t n = 0; ; n++){
sprintf(name, "%u.TXT", n);
// open start time
uint32_t t0 = millis();
// assume done if open fails
if (!file.open(dir, name, O_WRONLY)) return;
// open end time and remove start time
uint32_t t1 = millis();
if (!file.remove()) error("file.remove failed");
// remove end time
uint32_t t2 = millis();
Serial.print(F("RM "));
Serial.print(n);
Serial.write(' ');
// open time
Serial.print(t1 - t0);
Serial.write(' ');
// remove time
Serial.println(t2 - t1);
}
}
void setup() {
file_t root;
Serial.begin(9600);
while (!Serial) {} // wait for Leonardo
Serial.println(F("Type any character to start"));
while (Serial.read() <= 0) {}
delay(200); // Catch Due reset problem
// initialize the SD card at SPI_FULL_SPEED for best performance.
// try lower speed if bus errors occur.
if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) {
sd.initErrorHalt(&Serial);
}
root.openRoot(&sd);
// delete files in root if not FAT16.
if (sd.fatType() != 16) {
Serial.println(F("Remove files in root"));
deleteFiles(&root);
}
// open SUB1 and delete files
file_t sub1;
if (!sub1.open("SUB1", O_RDONLY)) error("open SUB1 failed");
Serial.println(F("Remove files in SUB1"));
deleteFiles(&sub1);
// open SUB2 and delete files
file_t sub2;
if (!sub2.open(&sub1, "SUB2", O_RDONLY)) error("open SUB2 failed");
Serial.println(F("Remove files in SUB2"));
deleteFiles(&sub2);
// remove SUB2
if (!sub2.rmdir()) error("sub2.rmdir failed");
Serial.println(F("SUB2 removed"));
// remove SUB1
if (!sub1.rmdir()) error("sub1.rmdir failed");
Serial.println(F("SUB1 removed"));
Serial.println(F("Done"));
}
void loop() { }

View File

@@ -0,0 +1,162 @@
/*
* This program tests the dateTimeCallback() function
* and the timestamp() function.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
SdFs sd;
FsFile file;
// Default SD chip select is SS pin
const uint8_t chipSelect = SS;
// create Serial stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
/*
* date/time values for debug
* normally supplied by a real-time clock or GPS
*/
// date 1-Oct-21
uint16_t year = 2021;
uint8_t month = 10;
uint8_t day = 1;
// time 20:30:40
uint8_t hour = 20;
uint8_t minute = 30;
uint8_t second = 40;
//------------------------------------------------------------------------------
/*
* User provided date time callback function.
* See SdFile::dateTimeCallback() for usage.
*/
void dateTime(uint16_t* date, uint16_t* time) {
// User gets date and time from GPS or real-time
// clock in real callback function
// return date using FAT_DATE macro to format fields
*date = FAT_DATE(year, month, day);
// return time using FAT_TIME macro to format fields
*time = FAT_TIME(hour, minute, second);
}
//------------------------------------------------------------------------------
/*
* Function to print all timestamps.
*/
void printTimestamps(FsFile& f) {
cout << F("Creation: ");
f.printCreateDateTime(&Serial);
cout << endl << F("Modify: ");
f.printModifyDateTime(&Serial);
cout << endl << F("Access: ");
f.printAccessDateTime(&Serial);
cout << endl;
}
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// remove files if they exist
sd.remove("callback.txt");
sd.remove("default.txt");
sd.remove("stamp.txt");
// create a new file with default timestamps
if (!file.open("default.txt", O_WRONLY | O_CREAT)) {
error("open default.txt failed");
}
cout << F("\nOpen with default times\n");
printTimestamps(file);
// close file
file.close();
/*
* Test the date time callback function.
*
* dateTimeCallback() sets the function
* that is called when a file is created
* or when a file's directory entry is
* modified by sync().
*
* The callback can be disabled by the call
* SdFile::dateTimeCallbackCancel()
*/
// set date time callback function
SdFile::dateTimeCallback(dateTime);
// create a new file with callback timestamps
if (!file.open("callback.txt", O_WRONLY | O_CREAT)) {
error("open callback.txt failed");
}
cout << ("\nOpen with callback times\n");
printTimestamps(file);
// change call back date
day += 1;
// must add two to see change since FAT second field is 5-bits
second += 2;
// modify file by writing a byte
file.write('t');
// force dir update
file.sync();
cout << F("\nTimes after write\n");
printTimestamps(file);
// close file
file.close();
/*
* Test timestamp() function
*
* Cancel callback so sync will not
* change access/modify timestamp
*/
SdFile::dateTimeCallbackCancel();
// create a new file with default timestamps
if (!file.open("stamp.txt", O_WRONLY | O_CREAT)) {
error("open stamp.txt failed");
}
// set creation date time
if (!file.timestamp(T_CREATE, 2021, 11, 10, 1, 2, 3)) {
error("set create time failed");
}
// set write/modification date time
if (!file.timestamp(T_WRITE, 2021, 11, 11, 4, 5, 6)) {
error("set write time failed");
}
// set access date
if (!file.timestamp(T_ACCESS, 2021, 11, 12, 7, 8, 9)) {
error("set access time failed");
}
cout << F("\nTimes after timestamp() calls\n");
printTimestamps(file);
file.close();
cout << F("\nDone\n");
}
void loop() {}

View File

@@ -0,0 +1,197 @@
// A simple data logger for the Arduino analog pins with optional DS1307
// uses RTClib from https://github.com/adafruit/RTClib
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#include "FreeStack.h"
#define SD_CHIP_SELECT SS // SD chip select pin
#define USE_DS1307 0 // set nonzero to use DS1307 RTC
#define LOG_INTERVAL 1000 // mills between entries
#define SENSOR_COUNT 3 // number of analog pins to log
#define ECHO_TO_SERIAL 1 // echo data to serial port if nonzero
#define WAIT_TO_START 1 // Wait for serial input in setup()
#define ADC_DELAY 10 // ADC delay for high impedence sensors
// file system object
SdFat sd;
// text file for logging
ofstream logfile;
// Serial print stream
ArduinoOutStream cout(Serial);
// buffer to format data - makes it eaiser to echo to Serial
char buf[80];
//------------------------------------------------------------------------------
#if SENSOR_COUNT > 6
#error SENSOR_COUNT too large
#endif // SENSOR_COUNT
//------------------------------------------------------------------------------
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
#if USE_DS1307
// use RTClib from Adafruit
// https://github.com/adafruit/RTClib
// The Arduino IDE has a bug that causes Wire and RTClib to be loaded even
// if USE_DS1307 is false.
#error remove this line and uncomment the next two lines.
//#include <Wire.h>
//#include <RTClib.h>
RTC_DS1307 RTC; // define the Real Time Clock object
//------------------------------------------------------------------------------
// call back for file timestamps
void dateTime(uint16_t* date, uint16_t* time) {
DateTime now = RTC.now();
// return date using FAT_DATE macro to format fields
*date = FAT_DATE(now.year(), now.month(), now.day());
// return time using FAT_TIME macro to format fields
*time = FAT_TIME(now.hour(), now.minute(), now.second());
}
//------------------------------------------------------------------------------
// format date/time
ostream& operator << (ostream& os, DateTime& dt) {
os << dt.year() << '/' << int(dt.month()) << '/' << int(dt.day()) << ',';
os << int(dt.hour()) << ':' << setfill('0') << setw(2) << int(dt.minute());
os << ':' << setw(2) << int(dt.second()) << setfill(' ');
return os;
}
#endif // USE_DS1307
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial.
while (!Serial) {
yield();
}
// F() stores strings in flash to save RAM
cout << endl << F("FreeStack: ") << FreeStack() << endl;
#if WAIT_TO_START
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Discard input.
do {
delay(10);
} while(Serial.available() && Serial.read() >= 0);
#endif // WAIT_TO_START
#if USE_DS1307
// connect to RTC
Wire.begin();
if (!RTC.begin()) {
error("RTC failed");
}
// set date time callback function
SdFile::dateTimeCallback(dateTime);
DateTime now = RTC.now();
cout << now << endl;
#endif // USE_DS1307
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CHIP_SELECT, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// create a new file in root, the current working directory
char name[] = "logger00.csv";
for (uint8_t i = 0; i < 100; i++) {
name[6] = i/10 + '0';
name[7] = i%10 + '0';
if (sd.exists(name)) {
continue;
}
logfile.open(name);
break;
}
if (!logfile.is_open()) {
error("file.open");
}
cout << F("Logging to: ") << name << endl;
cout << F("Type any character to stop\n\n");
// format header in buffer
obufstream bout(buf, sizeof(buf));
bout << F("millis");
#if USE_DS1307
bout << F(",date,time");
#endif // USE_DS1307
for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
bout << F(",sens") << int(i);
}
logfile << buf << endl;
#if ECHO_TO_SERIAL
cout << buf << endl;
#endif // ECHO_TO_SERIAL
}
//------------------------------------------------------------------------------
void loop() {
uint32_t m;
// wait for time to be a multiple of interval
do {
m = millis();
} while (m % LOG_INTERVAL);
// use buffer stream to format line
obufstream bout(buf, sizeof(buf));
// start with time in millis
bout << m;
#if USE_DS1307
DateTime now = RTC.now();
bout << ',' << now;
#endif // USE_DS1307
// read analog pins and format data
for (uint8_t ia = 0; ia < SENSOR_COUNT; ia++) {
#if ADC_DELAY
analogRead(ia);
delay(ADC_DELAY);
#endif // ADC_DELAY
bout << ',' << analogRead(ia);
}
bout << endl;
// log data and flush to SD
logfile << buf << flush;
// check for error
if (!logfile) {
error("write data failed");
}
#if ECHO_TO_SERIAL
cout << buf;
#endif // ECHO_TO_SERIAL
// don't log two points in the same millis
if (m == millis()) {
delay(1);
}
if (!Serial.available()) {
return;
}
logfile.close();
cout << F("Done!");
while (true) {}
}

View File

@@ -0,0 +1,46 @@
/*
* Program to test Short File Name character case flags.
*/
#include <SPI.h>
#include "SdFat.h"
const uint8_t chipSelect = SS;
SdFat sd;
SdFile file;
const char* name[] = {
"low.low", "low.Mix", "low.UP",
"Mix.low", "Mix.Mix", "Mix.UP",
"UP.low", "UP.Mix", "UP.UP"
};
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("type any character to start");
while (!Serial.available()) {
yield();
}
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
Serial.println("begin failed");
return;
}
for (uint8_t i = 0; i < 9; i++) {
sd.remove(name[i]);
if (!file.open(name[i], O_RDWR | O_CREAT | O_EXCL)) {
sd.errorHalt(name[i]);
}
file.println(name[i]);
file.close();
}
sd.ls(LS_DATE|LS_SIZE);
Serial.println("Done");
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,20 @@
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// create a serial output stream
ArduinoOutStream cout(Serial);
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(2000);
cout << "Hello, World!\n";
}
void loop() {}

View File

@@ -0,0 +1,29 @@
// This example illustrates use of SdFat's
// minimal unbuffered AVR Serial support.
//
// This is useful for debug and saves RAM
// Will not work on Due, Leonardo, or Teensy
#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
#ifdef UDR0 // Must be AVR with serial port zero.
#include "MinimumSerial.h"
MinimumSerial MiniSerial;
void setup() {
MiniSerial.begin(9600);
MiniSerial.println(FreeStack());
}
void loop() {
int c;
MiniSerial.println(F("Type any Character"));
while ((c = MiniSerial.read()) < 0) {}
MiniSerial.print(F("Read: "));
MiniSerial.println((char)c);
while (MiniSerial.read() >= 0) {}
}
#else // UDR0
#error no AVR serial port 0
#endif // UDR0

View File

@@ -0,0 +1,125 @@
/*
* This program is a simple Print benchmark.
*/
#include <SPI.h>
#include <SD.h>
// SD chip select pin
const uint8_t chipSelect = SS;
// number of lines to print
const uint16_t N_PRINT = 20000;
// test file
File file;
//------------------------------------------------------------------------------
void error(const char* s) {
Serial.println(s);
while (1) {
yield();
}
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
}
//------------------------------------------------------------------------------
void loop() {
uint32_t maxLatency;
uint32_t minLatency;
uint32_t totalLatency;
// Read any existing Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
// F() stores strings in flash to save RAM
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
yield();
}
// initialize the SD card
if (!SD.begin(chipSelect)) {
error("begin");
}
Serial.println(F("Starting print test. Please wait.\n"));
// do write test
for (int test = 0; test < 2; test++) {
file = SD.open("bench.txt", FILE_WRITE);
if (!file) {
error("open failed");
}
switch(test) {
case 0:
Serial.println(F("Test of println(uint16_t)"));
break;
case 1:
Serial.println(F("Test of println(double)"));
break;
}
maxLatency = 0;
minLatency = 999999;
totalLatency = 0;
uint32_t t = millis();
for (uint16_t i = 0; i < N_PRINT; i++) {
uint32_t m = micros();
switch(test) {
case 0:
file.println(i);
break;
case 1:
file.println((double)0.01*i);
break;
}
if (file.getWriteError()) {
error("write failed");
}
m = micros() - m;
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
totalLatency += m;
}
file.flush();
t = millis() - t;
double s = file.size();
Serial.print(F("Time "));
Serial.print(0.001*t);
Serial.println(F(" sec"));
Serial.print(F("File size "));
Serial.print(0.001*s);
Serial.print(F(" KB\n"));
Serial.print(F("Write "));
Serial.print(s/t);
Serial.print(F(" KB/sec\n"));
Serial.print(F("Maximum latency: "));
Serial.print(maxLatency);
Serial.print(F(" usec, Minimum Latency: "));
Serial.print(minLatency);
Serial.print(F(" usec, Avg Latency: "));
Serial.print(totalLatency/N_PRINT);
Serial.println(F(" usec\n"));
SD.remove("bench.txt");
}
file.close();
Serial.println(F("Done!\n"));
}

View File

@@ -0,0 +1,30 @@
/*
* Program to compare size of Arduino SD library with SdFat.
* See SdFatSize.ino for SdFat program.
*/
#include <SPI.h>
#include <SD.h>
File file;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
if (!SD.begin()) {
Serial.println("begin failed");
return;
}
file = SD.open("TEST_SD.TXT", FILE_WRITE);
file.println("Hello");
file.close();
Serial.println("Done");
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,33 @@
/*
* Program to compare size of SdFat with Arduino SD library.
* See SD_Size.ino for Arduino SD program.
*
*/
#include <SPI.h>
#include "SdFat.h"
SdFat sd;
SdFile file;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
if (!sd.begin()) {
Serial.println("begin failed");
return;
}
file.open("SizeTest.txt", O_RDWR | O_CREAT | O_AT_END);
file.println("Hello");
file.close();
Serial.println("Done");
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,44 @@
// Simple demo of the Stream parsInt() member function.
#include <SPI.h>
// The next two lines replace #include <SD.h>.
#include "SdFat.h"
SdFat SD;
// SD card chip select pin - Modify the value of csPin for your SD module.
const uint8_t csPin = SS;
File file;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial.
while(!Serial) {
yield();
}
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
yield();
}
// Initialize the SD.
if (!SD.begin(csPin)) {
Serial.println(F("begin error"));
return;
}
// Create and open the file. Use flag to truncate an existing file.
file = SD.open("stream.txt", O_RDWR|O_CREAT|O_TRUNC);
if (!file) {
Serial.println(F("open error"));
return;
}
// Write a test number to the file.
file.println("12345");
// Rewind the file and read the number with parseInt().
file.seek(0);
int i = file.parseInt();
Serial.print(F("parseInt: "));
Serial.println(i);
file.close();
}
void loop() {}

View File

@@ -0,0 +1,77 @@
/*
* Append Example
*
* This program shows how to use open for append.
* The program will append 100 line each time it opens the file.
* The program will open and close the file 100 times.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// file system object
SdFat sd;
// create Serial stream
ArduinoOutStream cout(Serial);
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
void setup() {
// filename for this example
char name[] = "append.txt";
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
// F() stores strings in flash to save RAM
cout << endl << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
cout << F("Appending to: ") << name;
for (uint8_t i = 0; i < 100; i++) {
// open stream for append
ofstream sdout(name, ios::out | ios::app);
if (!sdout) {
error("open failed");
}
// append 100 lines to the file
for (uint8_t j = 0; j < 100; j++) {
// use int() so byte will print as decimal number
sdout << "line " << int(j) << " of pass " << int(i);
sdout << " millis = " << millis() << endl;
}
// close the stream
sdout.close();
if (!sdout) {
error("append data failed");
}
// output progress indicator
if (i % 25 == 0) {
cout << endl;
}
cout << '.';
}
cout << endl << "Done" << endl;
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,82 @@
/*
* Calculate the sum and average of a list of floating point numbers
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// object for the SD file system
SdFat sd;
// define a serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void writeTestFile() {
// open the output file
ofstream sdout("AvgTest.txt");
// write a series of float numbers
for (int16_t i = -1001; i < 2000; i += 13) {
sdout << 0.1 * i << endl;
}
if (!sdout) {
sd.errorHalt("sdout failed");
}
sdout.close();
}
//------------------------------------------------------------------------------
void calcAverage() {
uint16_t n = 0; // count of input numbers
double num; // current input number
double sum = 0; // sum of input numbers
// open the input file
ifstream sdin("AvgTest.txt");
// check for an open failure
if (!sdin) {
sd.errorHalt("sdin failed");
}
// read and sum the numbers
while (sdin >> num) {
n++;
sum += num;
}
// print the results
cout << "sum of " << n << " numbers = " << sum << endl;
cout << "average = " << sum/n << endl;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
// F() stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// write the test file
writeTestFile();
// read the test file and calculate the average
calcAverage();
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,149 @@
/*
* This program is a simple binary write/read benchmark
* for the standard Arduino SD.h library.
*/
#include <SPI.h>
#include <SD.h>
// SD chip select pin
const uint8_t chipSelect = SS;
#define FILE_SIZE_MB 5
#define FILE_SIZE (1000000UL*FILE_SIZE_MB)
#define BUF_SIZE 100
uint8_t buf[BUF_SIZE];
// test file
File file;
//------------------------------------------------------------------------------
void error(const char* s) {
Serial.println(s);
while (1) {
yield();
}
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
}
//------------------------------------------------------------------------------
void loop() {
uint32_t maxLatency;
uint32_t minLatency;
uint32_t totalLatency;
// Discard any input.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
// F() stores strings in flash to save RAM
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
yield();
}
if (!SD.begin(chipSelect)) {
error("begin");
}
// open or create file - truncate existing file.
file = SD.open("Bench.dat", O_RDWR | O_TRUNC | O_CREAT);
if (!file) {
error("open failed");
}
// fill buf with known data
for (size_t_t i = 0; i < (BUF_SIZE-2); i++) {
buf[i] = 'A' + (i % 26);
}
buf[BUF_SIZE-2] = '\r';
buf[BUF_SIZE-1] = '\n';
Serial.print(F("File size "));
Serial.print(FILE_SIZE_MB);
Serial.println(F("MB"));
Serial.print(F("Buffer size "));
Serial.print(BUF_SIZE);
Serial.println(F(" bytes"));
Serial.println(F("Starting write test. Please wait up to a minute"));
// do write test
uint32_t n = FILE_SIZE/sizeof(buf);
maxLatency = 0;
minLatency = 999999;
totalLatency = 0;
uint32_t t = millis();
for (uint32_t i = 0; i < n; i++) {
uint32_t m = micros();
if (file.write(buf, sizeof(buf)) != sizeof(buf)) {
error("write failed");
}
m = micros() - m;
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
totalLatency += m;
}
file.flush();
t = millis() - t;
double s = file.size();
Serial.print(F("Write "));
Serial.print(s/t);
Serial.print(F(" KB/sec\n"));
Serial.print(F("Maximum latency: "));
Serial.print(maxLatency);
Serial.print(F(" usec, Minimum Latency: "));
Serial.print(minLatency);
Serial.print(F(" usec, Avg Latency: "));
Serial.print(totalLatency/n);
Serial.print(F(" usec\n\n"));
Serial.println(F("Starting read test. Please wait up to a minute"));
// do read test
file.seek(0);
maxLatency = 0;
minLatency = 99999;
totalLatency = 0;
t = millis();
for (uint32_t i = 0; i < n; i++) {
buf[BUF_SIZE-1] = 0;
uint32_t m = micros();
if (file.read(buf, sizeof(buf)) != sizeof(buf)) {
error("read failed");
}
m = micros() - m;
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
totalLatency += m;
if (buf[BUF_SIZE-1] != '\n') {
error("data check");
}
}
t = millis() - t;
Serial.print(F("Read "));
Serial.print(s/t);
Serial.print(F(" KB/sec\n"));
Serial.print(F("Maximum latency: "));
Serial.print(maxLatency);
Serial.print(F(" usec, Minimum Latency: "));
Serial.print(minLatency);
Serial.print(F(" usec, Avg Latency: "));
Serial.print(totalLatency/n);
Serial.print(F(" usec\n\n"));
Serial.print(F("Done\n\n"));
file.close();
}

View File

@@ -0,0 +1,39 @@
/*
* Use of ibufsteam to parse a line and obufstream to format a line
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// create a serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void setup() {
char buf[20]; // buffer for formatted line
int i, j, k; // values from parsed line
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(2000);
// initialize input string
ibufstream bin("123 456 789");
// parse the string "123 456 789"
bin >> i >> j >> k;
// initialize output buffer
obufstream bout(buf, sizeof(buf));
// format the output string
bout << k << ',' << j << ',' << i << endl;
// write the string to serial
cout << buf;
}
void loop() {}

View File

@@ -0,0 +1,39 @@
/*
* Demo of ArduinoInStream and ArduinoOutStream
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// create serial output stream
ArduinoOutStream cout(Serial);
// input line buffer
char cinBuf[40];
// create serial input stream
ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
}
//------------------------------------------------------------------------------
void loop() {
int32_t n = 0;
cout << "\nenter an integer\n";
cin.readline();
if (cin >> n) {
cout << "The number is: " << n;
} else {
// will fail if no digits or not in range [-2147483648, 2147483647]
cout << "Invalid input: " << cinBuf;
}
cout << endl;
}

View File

@@ -0,0 +1,62 @@
/*
* Append a line to a file - demo of pathnames and streams
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// file system object
SdFat sd;
// define a serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
/*
* Append a line to logfile.txt
*/
void logEvent(const char *msg) {
// create dir if needed
sd.mkdir("logs/2014/Jan");
// create or open a file for append
ofstream sdlog("logs/2014/Jan/logfile.txt", ios::out | ios::app);
// append a line to the file
sdlog << msg << endl;
// check for errors
if (!sdlog) {
sd.errorHalt("append failed");
}
sdlog.close();
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
// F() stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
delay(400); // catch Due reset problem
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// append a line to the logfile
logEvent("Another line for the logfile");
cout << F("Done - check /logs/2014/Jan/logfile.txt on the SD") << endl;
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,111 @@
// Demo of rewriting a line read by fgets
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD card chip select pin
const uint8_t chipSelect = SS;
// file system
SdFat sd;
// print stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// store error strings in flash memory
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
void demoFgets() {
char line[25];
int c;
uint32_t pos;
// open test file
SdFile rdfile("fgets.txt", O_RDWR);
// check for open error
if (!rdfile.isOpen()) {
error("demoFgets");
}
// list file
cout << F("-----Before Rewrite\r\n");
while ((c = rdfile.read()) >= 0) {
Serial.write(c);
}
rdfile.rewind();
// read lines from the file to get position
while (1) {
pos = rdfile.curPosition();
if (rdfile.fgets(line, sizeof(line)) < 0) {
error("Line not found");
}
// find line that contains "Line C"
if (strstr(line, "Line C")) {
break;
}
}
// rewrite line with 'C'
if (!rdfile.seekSet(pos)) {
error("seekSet");
}
rdfile.println("Line R");
rdfile.rewind();
// list file
cout << F("\r\n-----After Rewrite\r\n");
while ((c = rdfile.read()) >= 0) {
Serial.write(c);
}
// close so rewrite is not lost
rdfile.close();
}
//------------------------------------------------------------------------------
void makeTestFile() {
// create or open test file
SdFile wrfile("fgets.txt", O_WRONLY | O_CREAT | O_TRUNC);
// check for open error
if (!wrfile.isOpen()) {
error("MakeTestFile");
}
// write test file
wrfile.print(F(
"Line A\r\n"
"Line B\r\n"
"Line C\r\n"
"Line D\r\n"
"Line E\r\n"
));
wrfile.close();
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
makeTestFile();
demoFgets();
cout << F("\nDone\n");
}
void loop() {}

View File

@@ -0,0 +1,51 @@
/*
* Read the logfile created by the eventlog.ino example.
* Demo of pathnames and working directories
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// file system object
SdFat sd;
// define a serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void setup() {
int c;
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// set current working directory
if (!sd.chdir("logs/2014/Jan/")) {
sd.errorHalt("chdir failed. Did you run eventlog.ino?");
}
// open file in current working directory
ifstream file("logfile.txt");
if (!file.is_open()) {
sd.errorHalt("open failed");
}
// copy the file to Serial
while ((c = file.get()) >= 0) {
cout << (char)c;
}
cout << "Done" << endl;
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,34 @@
Old and debug examples.
AnalogLogger - A simple data logger for one or more analog pins.
append - This sketch creates a large file by successive
open/write/close operations.
average - A demonstration of parsing floating point numbers.
BaseExtCaseTest - Long file name test.
benchSD - A read/write benchmark for the standard Arduino SD.h library.
bufstream - ibufsteam to parse a line and obufstream to format a line.
cin_cout - Demo of ArduinoInStream and ArduinoOutStream.
eventlog - Append a line to a file - demo of pathnames and streams.
fgetsRewrite - Demo of rewriting a line read by fgets.
HelloWorld - Create a serial output stream.
MiniSerial - SdFat minimal serial support for debug.
PrintBenchmarkSD - Bench mark SD.h print.
readlog - Read file. Demo of pathnames and current working directory.
SD_Size - Determine flash used by SD.h example.
SdFatSize - Determine flash used by SdFat.
StreamParseInt - Simple demo of the Stream parsInt() member function.

View File

@@ -0,0 +1,39 @@
#ifndef AnalogBinLogger_h
#define AnalogBinLogger_h
//------------------------------------------------------------------------------
// First block of file.
struct metadata_t {
unsigned long adcFrequency; // ADC clock frequency
unsigned long cpuFrequency; // CPU clock frequency
unsigned long sampleInterval; // Sample interval in CPU cycles.
unsigned long recordEightBits; // Size of ADC values, nonzero for 8-bits.
unsigned long pinCount; // Number of analog pins in a sample.
unsigned long pinNumber[123]; // List of pin numbers in a sample.
};
//------------------------------------------------------------------------------
// Data block for 8-bit ADC mode.
const size_t DATA_DIM8 = 508;
struct block8_t {
unsigned short count; // count of data values
unsigned short overrun; // count of overruns since last block
unsigned char data[DATA_DIM8];
};
//------------------------------------------------------------------------------
// Data block for 10-bit ADC mode.
const size_t DATA_DIM16 = 254;
struct block16_t {
unsigned short count; // count of data values
unsigned short overrun; // count of overruns since last block
unsigned short data[DATA_DIM16];
};
//------------------------------------------------------------------------------
// Data block for PC use
struct adcdata_t {
unsigned short count; // count of data values
unsigned short overrun; // count of overruns since last block
union {
unsigned char u8[DATA_DIM8];
unsigned short u16[DATA_DIM16];
} data;
};
#endif // AnalogBinLogger_h

View File

@@ -0,0 +1,827 @@
/**
* This program logs data from the Arduino ADC to a binary file.
*
* Samples are logged at regular intervals. Each Sample consists of the ADC
* values for the analog pins defined in the PIN_LIST array. The pins numbers
* may be in any order.
*
* Edit the configuration constants below to set the sample pins, sample rate,
* and other configuration values.
*
* If your SD card has a long write latency, it may be necessary to use
* slower sample rates. Using a Mega Arduino helps overcome latency
* problems since 13 512 byte buffers will be used.
*
* Each 512 byte data block in the file has a four byte header followed by up
* to 508 bytes of data. (508 values in 8-bit mode or 254 values in 10-bit mode)
* Each block contains an integral number of samples with unused space at the
* end of the block.
*
* Data is written to the file using a SD multiple block write command.
*/
#ifdef __AVR__
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#include "FreeStack.h"
#include "AnalogBinLogger.h"
//------------------------------------------------------------------------------
// Analog pin number list for a sample. Pins may be in any order and pin
// numbers may be repeated.
const uint8_t PIN_LIST[] = {0, 1, 2, 3, 4};
//------------------------------------------------------------------------------
// Sample rate in samples per second.
const float SAMPLE_RATE = 5000; // Must be 0.25 or greater.
// The interval between samples in seconds, SAMPLE_INTERVAL, may be set to a
// constant instead of being calculated from SAMPLE_RATE. SAMPLE_RATE is not
// used in the code below. For example, setting SAMPLE_INTERVAL = 2.0e-4
// will result in a 200 microsecond sample interval.
const float SAMPLE_INTERVAL = 1.0/SAMPLE_RATE;
// Setting ROUND_SAMPLE_INTERVAL non-zero will cause the sample interval to
// be rounded to a a multiple of the ADC clock period and will reduce sample
// time jitter.
#define ROUND_SAMPLE_INTERVAL 1
//------------------------------------------------------------------------------
// ADC clock rate.
// The ADC clock rate is normally calculated from the pin count and sample
// interval. The calculation attempts to use the lowest possible ADC clock
// rate.
//
// You can select an ADC clock rate by defining the symbol ADC_PRESCALER to
// one of these values. You must choose an appropriate ADC clock rate for
// your sample interval.
// #define ADC_PRESCALER 7 // F_CPU/128 125 kHz on an Uno
// #define ADC_PRESCALER 6 // F_CPU/64 250 kHz on an Uno
// #define ADC_PRESCALER 5 // F_CPU/32 500 kHz on an Uno
// #define ADC_PRESCALER 4 // F_CPU/16 1000 kHz on an Uno
// #define ADC_PRESCALER 3 // F_CPU/8 2000 kHz on an Uno (8-bit mode only)
//------------------------------------------------------------------------------
// Reference voltage. See the processor data-sheet for reference details.
// uint8_t const ADC_REF = 0; // External Reference AREF pin.
uint8_t const ADC_REF = (1 << REFS0); // Vcc Reference.
// uint8_t const ADC_REF = (1 << REFS1); // Internal 1.1 (only 644 1284P Mega)
// uint8_t const ADC_REF = (1 << REFS1) | (1 << REFS0); // Internal 1.1 or 2.56
//------------------------------------------------------------------------------
// File definitions.
//
// Maximum file size in blocks.
// The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
// This file is flash erased using special SD commands. The file will be
// truncated if logging is stopped early.
const uint32_t FILE_BLOCK_COUNT = 256000;
// log file base name. Must be six characters or less.
#define FILE_BASE_NAME "analog"
// Set RECORD_EIGHT_BITS non-zero to record only the high 8-bits of the ADC.
#define RECORD_EIGHT_BITS 0
//------------------------------------------------------------------------------
// Pin definitions.
//
// Digital pin to indicate an error, set to -1 if not used.
// The led blinks for fatal errors. The led goes on solid for SD write
// overrun errors and logging continues.
const int8_t ERROR_LED_PIN = 3;
// SD chip select pin.
const uint8_t SD_CS_PIN = SS;
//------------------------------------------------------------------------------
// Buffer definitions.
//
// The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional
// buffers. QUEUE_DIM must be a power of two larger than
//(BUFFER_BLOCK_COUNT + 1).
//
#if RAMEND < 0X8FF
#error Too little SRAM
//
#elif RAMEND < 0X10FF
// Use total of two 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 1;
// Dimension for queues of 512 byte SD blocks.
const uint8_t QUEUE_DIM = 4; // Must be a power of two!
//
#elif RAMEND < 0X20FF
// Use total of five 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 4;
// Dimension for queues of 512 byte SD blocks.
const uint8_t QUEUE_DIM = 8; // Must be a power of two!
//
#elif RAMEND < 0X40FF
// Use total of 13 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 12;
// Dimension for queues of 512 byte SD blocks.
const uint8_t QUEUE_DIM = 16; // Must be a power of two!
//
#else // RAMEND
// Use total of 29 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 28;
// Dimension for queues of 512 byte SD blocks.
const uint8_t QUEUE_DIM = 32; // Must be a power of two!
#endif // RAMEND
//==============================================================================
// End of configuration constants.
//==============================================================================
// Temporary log file. Will be deleted if a reset or power failure occurs.
#define TMP_FILE_NAME "tmp_log.bin"
// Size of file base name. Must not be larger than six.
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
// Number of analog pins to log.
const uint8_t PIN_COUNT = sizeof(PIN_LIST)/sizeof(PIN_LIST[0]);
// Minimum ADC clock cycles per sample interval
const uint16_t MIN_ADC_CYCLES = 15;
// Extra cpu cycles to setup ADC with more than one pin per sample.
const uint16_t ISR_SETUP_ADC = PIN_COUNT > 1 ? 100 : 0;
// Maximum cycles for timer0 system interrupt, millis, micros.
const uint16_t ISR_TIMER0 = 160;
//==============================================================================
SdFat sd;
SdBaseFile binFile;
char binName[13] = FILE_BASE_NAME "00.bin";
#if RECORD_EIGHT_BITS
const size_t SAMPLES_PER_BLOCK = DATA_DIM8/PIN_COUNT;
typedef block8_t block_t;
#else // RECORD_EIGHT_BITS
const size_t SAMPLES_PER_BLOCK = DATA_DIM16/PIN_COUNT;
typedef block16_t block_t;
#endif // RECORD_EIGHT_BITS
block_t* emptyQueue[QUEUE_DIM];
uint8_t emptyHead;
uint8_t emptyTail;
block_t* fullQueue[QUEUE_DIM];
volatile uint8_t fullHead; // volatile insures non-interrupt code sees changes.
uint8_t fullTail;
// queueNext assumes QUEUE_DIM is a power of two
inline uint8_t queueNext(uint8_t ht) {
return (ht + 1) & (QUEUE_DIM -1);
}
//==============================================================================
// Interrupt Service Routines
// Pointer to current buffer.
block_t* isrBuf;
// Need new buffer if true.
bool isrBufNeeded = true;
// overrun count
uint16_t isrOver = 0;
// ADC configuration for each pin.
uint8_t adcmux[PIN_COUNT];
uint8_t adcsra[PIN_COUNT];
uint8_t adcsrb[PIN_COUNT];
uint8_t adcindex = 1;
// Insure no timer events are missed.
volatile bool timerError = false;
volatile bool timerFlag = false;
//------------------------------------------------------------------------------
// ADC done interrupt.
ISR(ADC_vect) {
// Read ADC data.
#if RECORD_EIGHT_BITS
uint8_t d = ADCH;
#else // RECORD_EIGHT_BITS
// This will access ADCL first.
uint16_t d = ADC;
#endif // RECORD_EIGHT_BITS
if (isrBufNeeded && emptyHead == emptyTail) {
// no buffers - count overrun
if (isrOver < 0XFFFF) {
isrOver++;
}
// Avoid missed timer error.
timerFlag = false;
return;
}
// Start ADC
if (PIN_COUNT > 1) {
ADMUX = adcmux[adcindex];
ADCSRB = adcsrb[adcindex];
ADCSRA = adcsra[adcindex];
if (adcindex == 0) {
timerFlag = false;
}
adcindex = adcindex < (PIN_COUNT - 1) ? adcindex + 1 : 0;
} else {
timerFlag = false;
}
// Check for buffer needed.
if (isrBufNeeded) {
// Remove buffer from empty queue.
isrBuf = emptyQueue[emptyTail];
emptyTail = queueNext(emptyTail);
isrBuf->count = 0;
isrBuf->overrun = isrOver;
isrBufNeeded = false;
}
// Store ADC data.
isrBuf->data[isrBuf->count++] = d;
// Check for buffer full.
if (isrBuf->count >= PIN_COUNT*SAMPLES_PER_BLOCK) {
// Put buffer isrIn full queue.
uint8_t tmp = fullHead; // Avoid extra fetch of volatile fullHead.
fullQueue[tmp] = (block_t*)isrBuf;
fullHead = queueNext(tmp);
// Set buffer needed and clear overruns.
isrBufNeeded = true;
isrOver = 0;
}
}
//------------------------------------------------------------------------------
// timer1 interrupt to clear OCF1B
ISR(TIMER1_COMPB_vect) {
// Make sure ADC ISR responded to timer event.
if (timerFlag) {
timerError = true;
}
timerFlag = true;
}
//==============================================================================
// Error messages stored in flash.
#define error(msg) {sd.errorPrint(F(msg));fatalBlink();}
//------------------------------------------------------------------------------
//
void fatalBlink() {
while (true) {
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
delay(200);
digitalWrite(ERROR_LED_PIN, LOW);
delay(200);
}
}
}
//==============================================================================
#if ADPS0 != 0 || ADPS1 != 1 || ADPS2 != 2
#error unexpected ADC prescaler bits
#endif
//------------------------------------------------------------------------------
// initialize ADC and timer1
void adcInit(metadata_t* meta) {
uint8_t adps; // prescaler bits for ADCSRA
uint32_t ticks = F_CPU*SAMPLE_INTERVAL + 0.5; // Sample interval cpu cycles.
if (ADC_REF & ~((1 << REFS0) | (1 << REFS1))) {
error("Invalid ADC reference");
}
#ifdef ADC_PRESCALER
if (ADC_PRESCALER > 7 || ADC_PRESCALER < 2) {
error("Invalid ADC prescaler");
}
adps = ADC_PRESCALER;
#else // ADC_PRESCALER
// Allow extra cpu cycles to change ADC settings if more than one pin.
int32_t adcCycles = (ticks - ISR_TIMER0)/PIN_COUNT - ISR_SETUP_ADC;
for (adps = 7; adps > 0; adps--) {
if (adcCycles >= (MIN_ADC_CYCLES << adps)) {
break;
}
}
#endif // ADC_PRESCALER
meta->adcFrequency = F_CPU >> adps;
if (meta->adcFrequency > (RECORD_EIGHT_BITS ? 2000000 : 1000000)) {
error("Sample Rate Too High");
}
#if ROUND_SAMPLE_INTERVAL
// Round so interval is multiple of ADC clock.
ticks += 1 << (adps - 1);
ticks >>= adps;
ticks <<= adps;
#endif // ROUND_SAMPLE_INTERVAL
if (PIN_COUNT > sizeof(meta->pinNumber)/sizeof(meta->pinNumber[0])) {
error("Too many pins");
}
meta->pinCount = PIN_COUNT;
meta->recordEightBits = RECORD_EIGHT_BITS;
for (int i = 0; i < PIN_COUNT; i++) {
uint8_t pin = PIN_LIST[i];
if (pin >= NUM_ANALOG_INPUTS) {
error("Invalid Analog pin number");
}
meta->pinNumber[i] = pin;
// Set ADC reference and low three bits of analog pin number.
adcmux[i] = (pin & 7) | ADC_REF;
if (RECORD_EIGHT_BITS) {
adcmux[i] |= 1 << ADLAR;
}
// If this is the first pin, trigger on timer/counter 1 compare match B.
adcsrb[i] = i == 0 ? (1 << ADTS2) | (1 << ADTS0) : 0;
#ifdef MUX5
if (pin > 7) {
adcsrb[i] |= (1 << MUX5);
}
#endif // MUX5
adcsra[i] = (1 << ADEN) | (1 << ADIE) | adps;
adcsra[i] |= i == 0 ? 1 << ADATE : 1 << ADSC;
}
// Setup timer1
TCCR1A = 0;
uint8_t tshift;
if (ticks < 0X10000) {
// no prescale, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
tshift = 0;
} else if (ticks < 0X10000*8) {
// prescale 8, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11);
tshift = 3;
} else if (ticks < 0X10000*64) {
// prescale 64, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11) | (1 << CS10);
tshift = 6;
} else if (ticks < 0X10000*256) {
// prescale 256, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12);
tshift = 8;
} else if (ticks < 0X10000*1024) {
// prescale 1024, CTC mode
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12) | (1 << CS10);
tshift = 10;
} else {
error("Sample Rate Too Slow");
}
// divide by prescaler
ticks >>= tshift;
// set TOP for timer reset
ICR1 = ticks - 1;
// compare for ADC start
OCR1B = 0;
// multiply by prescaler
ticks <<= tshift;
// Sample interval in CPU clock ticks.
meta->sampleInterval = ticks;
meta->cpuFrequency = F_CPU;
float sampleRate = (float)meta->cpuFrequency/meta->sampleInterval;
Serial.print(F("Sample pins:"));
for (uint8_t i = 0; i < meta->pinCount; i++) {
Serial.print(' ');
Serial.print(meta->pinNumber[i], DEC);
}
Serial.println();
Serial.print(F("ADC bits: "));
Serial.println(meta->recordEightBits ? 8 : 10);
Serial.print(F("ADC clock kHz: "));
Serial.println(meta->adcFrequency/1000);
Serial.print(F("Sample Rate: "));
Serial.println(sampleRate);
Serial.print(F("Sample interval usec: "));
Serial.println(1000000.0/sampleRate, 4);
}
//------------------------------------------------------------------------------
// enable ADC and timer1 interrupts
void adcStart() {
// initialize ISR
isrBufNeeded = true;
isrOver = 0;
adcindex = 1;
// Clear any pending interrupt.
ADCSRA |= 1 << ADIF;
// Setup for first pin.
ADMUX = adcmux[0];
ADCSRB = adcsrb[0];
ADCSRA = adcsra[0];
// Enable timer1 interrupts.
timerError = false;
timerFlag = false;
TCNT1 = 0;
TIFR1 = 1 << OCF1B;
TIMSK1 = 1 << OCIE1B;
}
//------------------------------------------------------------------------------
void adcStop() {
TIMSK1 = 0;
ADCSRA = 0;
}
//------------------------------------------------------------------------------
// Convert binary file to csv file.
void binaryToCsv() {
uint8_t lastPct = 0;
block_t buf;
metadata_t* pm;
uint32_t t0 = millis();
char csvName[13];
StdioStream csvStream;
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
if (binFile.read(&buf , 512) != 512) {
error("Read metadata failed");
}
// Create a new csv file.
strcpy(csvName, binName);
strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
if (!csvStream.fopen(csvName, "w")) {
error("open csvStream failed");
}
Serial.println();
Serial.print(F("Writing: "));
Serial.print(csvName);
Serial.println(F(" - type any character to stop"));
pm = (metadata_t*)&buf;
csvStream.print(F("Interval,"));
float intervalMicros = 1.0e6*pm->sampleInterval/(float)pm->cpuFrequency;
csvStream.print(intervalMicros, 4);
csvStream.println(F(",usec"));
for (uint8_t i = 0; i < pm->pinCount; i++) {
if (i) {
csvStream.putc(',');
}
csvStream.print(F("pin"));
csvStream.print(pm->pinNumber[i]);
}
csvStream.println();
uint32_t tPct = millis();
while (!Serial.available() && binFile.read(&buf, 512) == 512) {
if (buf.count == 0) {
break;
}
if (buf.overrun) {
csvStream.print(F("OVERRUN,"));
csvStream.println(buf.overrun);
}
for (uint16_t j = 0; j < buf.count; j += PIN_COUNT) {
for (uint16_t i = 0; i < PIN_COUNT; i++) {
if (i) {
csvStream.putc(',');
}
csvStream.print(buf.data[i + j]);
}
csvStream.println();
}
if ((millis() - tPct) > 1000) {
uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
if (pct != lastPct) {
tPct = millis();
lastPct = pct;
Serial.print(pct, DEC);
Serial.println('%');
}
}
if (Serial.available()) {
break;
}
}
csvStream.fclose();
Serial.print(F("Done: "));
Serial.print(0.001*(millis() - t0));
Serial.println(F(" Seconds"));
}
//------------------------------------------------------------------------------
// read data file and check for overruns
void checkOverrun() {
bool headerPrinted = false;
block_t buf;
uint32_t bgnBlock, endBlock;
uint32_t bn = 0;
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return;
}
if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
error("contiguousRange failed");
}
binFile.rewind();
Serial.println();
Serial.println(F("Checking overrun errors - type any character to stop"));
if (binFile.read(&buf , 512) != 512) {
error("Read metadata failed");
}
bn++;
while (binFile.read(&buf, 512) == 512) {
if (buf.count == 0) {
break;
}
if (buf.overrun) {
if (!headerPrinted) {
Serial.println();
Serial.println(F("Overruns:"));
Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
headerPrinted = true;
}
Serial.print(bn);
Serial.print(',');
Serial.print(bgnBlock + bn);
Serial.print(',');
Serial.println(buf.overrun);
}
bn++;
}
if (!headerPrinted) {
Serial.println(F("No errors found"));
} else {
Serial.println(F("Done"));
}
}
//------------------------------------------------------------------------------
// dump data file to Serial
void dumpData() {
block_t buf;
if (!binFile.isOpen()) {
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
if (binFile.read(&buf , 512) != 512) {
error("Read metadata failed");
}
Serial.println();
Serial.println(F("Type any character to stop"));
delay(1000);
while (!Serial.available() && binFile.read(&buf , 512) == 512) {
if (buf.count == 0) {
break;
}
if (buf.overrun) {
Serial.print(F("OVERRUN,"));
Serial.println(buf.overrun);
}
for (uint16_t i = 0; i < buf.count; i++) {
Serial.print(buf.data[i], DEC);
if ((i+1)%PIN_COUNT) {
Serial.print(',');
} else {
Serial.println();
}
}
}
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
// log data
// max number of blocks to erase per erase call
uint32_t const ERASE_SIZE = 262144L;
void logData() {
uint32_t bgnBlock, endBlock;
// Allocate extra buffer space.
block_t block[BUFFER_BLOCK_COUNT];
Serial.println();
// Initialize ADC and timer1.
adcInit((metadata_t*) &block[0]);
// Find unused file name.
if (BASE_NAME_SIZE > 6) {
error("FILE_BASE_NAME too long");
}
while (sd.exists(binName)) {
if (binName[BASE_NAME_SIZE + 1] != '9') {
binName[BASE_NAME_SIZE + 1]++;
} else {
binName[BASE_NAME_SIZE + 1] = '0';
if (binName[BASE_NAME_SIZE] == '9') {
error("Can't create file name");
}
binName[BASE_NAME_SIZE]++;
}
}
// Delete old tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("Deleting tmp file"));
if (!sd.remove(TMP_FILE_NAME)) {
error("Can't remove tmp file");
}
}
// Create new file.
Serial.println(F("Creating new file"));
binFile.close();
if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
error("createContiguous failed");
}
// Get the address of the file on the SD.
if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
error("contiguousRange failed");
}
// Use SdFat's internal buffer.
uint8_t* cache = (uint8_t*)sd.vol()->cacheClear();
if (cache == 0) {
error("cacheClear failed");
}
// Flash erase all data in the file.
Serial.println(F("Erasing all data"));
uint32_t bgnErase = bgnBlock;
uint32_t endErase;
while (bgnErase < endBlock) {
endErase = bgnErase + ERASE_SIZE;
if (endErase > endBlock) {
endErase = endBlock;
}
if (!sd.card()->erase(bgnErase, endErase)) {
error("erase failed");
}
bgnErase = endErase + 1;
}
// Start a multiple block write.
if (!sd.card()->writeStart(bgnBlock)) {
error("writeBegin failed");
}
// Write metadata.
if (!sd.card()->writeData((uint8_t*)&block[0])) {
error("Write metadata failed");
}
// Initialize queues.
emptyHead = emptyTail = 0;
fullHead = fullTail = 0;
// Use SdFat buffer for one block.
emptyQueue[emptyHead] = (block_t*)cache;
emptyHead = queueNext(emptyHead);
// Put rest of buffers in the empty queue.
for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) {
emptyQueue[emptyHead] = &block[i];
emptyHead = queueNext(emptyHead);
}
// Give SD time to prepare for big write.
delay(1000);
Serial.println(F("Logging - type any character to stop"));
// Wait for Serial Idle.
Serial.flush();
delay(10);
uint32_t bn = 1;
uint32_t t0 = millis();
uint32_t t1 = t0;
uint32_t overruns = 0;
uint32_t count = 0;
uint32_t maxLatency = 0;
// Start logging interrupts.
adcStart();
while (1) {
if (fullHead != fullTail) {
// Get address of block to write.
block_t* pBlock = fullQueue[fullTail];
// Write block to SD.
uint32_t usec = micros();
if (!sd.card()->writeData((uint8_t*)pBlock)) {
error("write data failed");
}
usec = micros() - usec;
t1 = millis();
if (usec > maxLatency) {
maxLatency = usec;
}
count += pBlock->count;
// Add overruns and possibly light LED.
if (pBlock->overrun) {
overruns += pBlock->overrun;
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
}
}
// Move block to empty queue.
emptyQueue[emptyHead] = pBlock;
emptyHead = queueNext(emptyHead);
fullTail = queueNext(fullTail);
bn++;
if (bn == FILE_BLOCK_COUNT) {
// File full so stop ISR calls.
adcStop();
break;
}
}
if (timerError) {
error("Missed timer event - rate too high");
}
if (Serial.available()) {
// Stop ISR calls.
adcStop();
if (isrBuf != 0 && isrBuf->count >= PIN_COUNT) {
// Truncate to last complete sample.
isrBuf->count = PIN_COUNT*(isrBuf->count/PIN_COUNT);
// Put buffer in full queue.
fullQueue[fullHead] = isrBuf;
fullHead = queueNext(fullHead);
isrBuf = 0;
}
if (fullHead == fullTail) {
break;
}
}
}
if (!sd.card()->writeStop()) {
error("writeStop failed");
}
// Truncate file if recording stopped early.
if (bn != FILE_BLOCK_COUNT) {
Serial.println(F("Truncating file"));
if (!binFile.truncate(512L * bn)) {
error("Can't truncate file");
}
}
if (!binFile.rename(binName)) {
error("Can't rename file");
}
Serial.print(F("File renamed: "));
Serial.println(binName);
Serial.print(F("Max block write usec: "));
Serial.println(maxLatency);
Serial.print(F("Record time sec: "));
Serial.println(0.001*(t1 - t0), 3);
Serial.print(F("Sample count: "));
Serial.println(count/PIN_COUNT);
Serial.print(F("Samples/sec: "));
Serial.println((1000.0/PIN_COUNT)*count/(t1-t0));
Serial.print(F("Overruns: "));
Serial.println(overruns);
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
void setup(void) {
if (ERROR_LED_PIN >= 0) {
pinMode(ERROR_LED_PIN, OUTPUT);
}
Serial.begin(9600);
// Read the first sample pin to init the ADC.
analogRead(PIN_LIST[0]);
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
sd.initErrorPrint();
fatalBlink();
}
}
//------------------------------------------------------------------------------
void loop(void) {
// Read any Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
Serial.println();
Serial.println(F("type:"));
Serial.println(F("c - convert file to csv"));
Serial.println(F("d - dump data to Serial"));
Serial.println(F("e - overrun error details"));
Serial.println(F("r - record ADC data"));
while(!Serial.available()) {
yield();
}
char c = tolower(Serial.read());
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, LOW);
}
// Read any Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
if (c == 'c') {
binaryToCsv();
} else if (c == 'd') {
dumpData();
} else if (c == 'e') {
checkOverrun();
} else if (c == 'r') {
logData();
} else {
Serial.println(F("Invalid entry"));
}
}
#else // __AVR__
#error This program is only for AVR.
#endif // __AVR__

View File

@@ -0,0 +1,102 @@
// Example use of lfnOpenNext and open by index.
// You can use test files located in
// SdFat/examples/LongFileName/testFiles.
#include<SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
// SD card chip select pin.
const uint8_t SD_CS_PIN = SS;
SdFat sd;
SdFile file;
SdFile dirFile;
// Number of files found.
uint16_t n = 0;
// Max of ten files since files are selected with a single digit.
const uint16_t nMax = 10;
// Position of file's directory entry.
uint16_t dirIndex[nMax];
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {}
delay(1000);
// Print the location of some test files.
Serial.println(F("\r\n"
"You can use test files located in\r\n"
"SdFat/examples/LongFileName/testFiles"));
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println();
// List files in root directory.
if (!dirFile.open("/", O_RDONLY)) {
sd.errorHalt("open root failed");
}
while (n < nMax && file.openNext(&dirFile, O_RDONLY)) {
// Skip directories and hidden files.
if (!file.isSubDir() && !file.isHidden()) {
// Save dirIndex of file in directory.
dirIndex[n] = file.dirIndex();
// Print the file number and name.
Serial.print(n++);
Serial.write(' ');
file.printName(&Serial);
Serial.println();
}
file.close();
}
}
//------------------------------------------------------------------------------
void loop() {
int c;
// Read any existing Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
Serial.print(F("\r\nEnter File Number: "));
while (!Serial.available()) {
yield();
}
c = Serial.read();
uint8_t i = c - '0';
if (!isdigit(c) || i >= n) {
Serial.println(F("Invald number"));
return;
}
Serial.println(i);
if (!file.open(&dirFile, dirIndex[i], O_RDONLY)) {
sd.errorHalt(F("open"));
}
Serial.println();
char last = 0;
// Copy up to 500 characters to Serial.
for (int k = 0; k < 500 && (c = file.read()) > 0; k++) {
Serial.write(last = (char)c);
}
// Add new line if missing from last line.
if (last != '\n') {
Serial.println();
}
file.close();
Serial.flush();
delay(100);
}

View File

@@ -0,0 +1,4 @@
This is "A long name can be 255 characters.txt"
This file has a typical Long File Name.
The maximum length of a Long File Name is 255 characters.

View File

@@ -0,0 +1 @@
LFN,NAME.TXT is not 8.3 since it has a comma.

View File

@@ -0,0 +1,5 @@
MIXCASE.txt does not have a Long File Name.
Starting with NT, file names of this form,
have the basename and extension character case
encoded in two bits of the 8.3 directory entry.

View File

@@ -0,0 +1,2 @@
Not_8_3.txt has a Long File Name
since the basename is mixed case.

View File

@@ -0,0 +1 @@
OK%83.TXT is a valid 8.3 name.

View File

@@ -0,0 +1 @@
STD_8_3.TXT - a vanilla 8.3 name.

View File

@@ -0,0 +1,2 @@
With Blank.txt
Just another example of a Long File Name.

View File

@@ -0,0 +1,2 @@
"With Two.dots.txt"
Lots of reasons this is a Long File Name.

View File

@@ -0,0 +1,5 @@
lower.txt does not have a Long File Name.
Starting with NT, file names of this form,
have the basename and extension character case
encoded in two bits of the 8.3 directory entry.

View File

@@ -0,0 +1,5 @@
mixed.TXT does not have a Long File Name.
Starting with NT, file names of this form,
have the basename and extension character case
encoded in two bits of the 8.3 directory entry.

View File

@@ -0,0 +1,655 @@
/**
* This program logs data to a binary file. Functions are included
* to convert the binary file to a csv text file.
*
* Samples are logged at regular intervals. The maximum logging rate
* depends on the quality of your SD card and the time required to
* read sensor data. This example has been tested at 500 Hz with
* good SD card on an Uno. 4000 HZ is possible on a Due.
*
* If your SD card has a long write latency, it may be necessary to use
* slower sample rates. Using a Mega Arduino helps overcome latency
* problems since 12 512 byte buffers will be used.
*
* Data is written to the file using a SD multiple block write command.
*/
#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
#include "UserTypes.h"
#ifdef __AVR_ATmega328P__
#include "MinimumSerial.h"
MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
//==============================================================================
// Start of configuration constants.
//==============================================================================
// Abort run on an overrun. Data before the overrun will be saved.
#define ABORT_ON_OVERRUN 1
//------------------------------------------------------------------------------
//Interval between data records in microseconds.
const uint32_t LOG_INTERVAL_USEC = 2000;
//------------------------------------------------------------------------------
// Set USE_SHARED_SPI non-zero for use of an SPI sensor.
// May not work for some cards.
#ifndef USE_SHARED_SPI
#define USE_SHARED_SPI 0
#endif // USE_SHARED_SPI
//------------------------------------------------------------------------------
// Pin definitions.
//
// SD chip select pin.
const uint8_t SD_CS_PIN = SS;
//
// Digital pin to indicate an error, set to -1 if not used.
// The led blinks for fatal errors. The led goes on solid for
// overrun errors and logging continues unless ABORT_ON_OVERRUN
// is non-zero.
#ifdef ERROR_LED_PIN
#undef ERROR_LED_PIN
#endif // ERROR_LED_PIN
const int8_t ERROR_LED_PIN = -1;
//------------------------------------------------------------------------------
// File definitions.
//
// Maximum file size in blocks.
// The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
// This file is flash erased using special SD commands. The file will be
// truncated if logging is stopped early.
const uint32_t FILE_BLOCK_COUNT = 256000;
//
// log file base name if not defined in UserTypes.h
#ifndef FILE_BASE_NAME
#define FILE_BASE_NAME "data"
#endif // FILE_BASE_NAME
//------------------------------------------------------------------------------
// Buffer definitions.
//
// The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT-1 additional
// buffers.
//
#ifndef RAMEND
// Assume ARM. Use total of ten 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 10;
//
#elif RAMEND < 0X8FF
#error Too little SRAM
//
#elif RAMEND < 0X10FF
// Use total of two 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 2;
//
#elif RAMEND < 0X20FF
// Use total of four 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 4;
//
#else // RAMEND
// Use total of 12 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 12;
#endif // RAMEND
//==============================================================================
// End of configuration constants.
//==============================================================================
// Temporary log file. Will be deleted if a reset or power failure occurs.
#define TMP_FILE_NAME FILE_BASE_NAME "##.bin"
// Size of file base name.
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
const uint8_t FILE_NAME_DIM = BASE_NAME_SIZE + 7;
char binName[FILE_NAME_DIM] = FILE_BASE_NAME "00.bin";
SdFat sd;
SdBaseFile binFile;
// Number of data records in a block.
const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t);
//Compute fill so block size is 512 bytes. FILL_DIM may be zero.
const uint16_t FILL_DIM = 512 - 4 - DATA_DIM*sizeof(data_t);
struct block_t {
uint16_t count;
uint16_t overrun;
data_t data[DATA_DIM];
uint8_t fill[FILL_DIM];
};
//==============================================================================
// Error messages stored in flash.
#define error(msg) {sd.errorPrint(&Serial, F(msg));fatalBlink();}
//------------------------------------------------------------------------------
//
void fatalBlink() {
while (true) {
yield();
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
delay(200);
digitalWrite(ERROR_LED_PIN, LOW);
delay(200);
}
}
}
//------------------------------------------------------------------------------
// read data file and check for overruns
void checkOverrun() {
bool headerPrinted = false;
block_t block;
uint32_t bn = 0;
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
Serial.println();
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println(F("Checking overrun errors - type any character to stop"));
while (binFile.read(&block, 512) == 512) {
if (block.count == 0) {
break;
}
if (block.overrun) {
if (!headerPrinted) {
Serial.println();
Serial.println(F("Overruns:"));
Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
headerPrinted = true;
}
Serial.print(bn);
Serial.print(',');
Serial.print(binFile.firstBlock() + bn);
Serial.print(',');
Serial.println(block.overrun);
}
bn++;
}
if (!headerPrinted) {
Serial.println(F("No errors found"));
} else {
Serial.println(F("Done"));
}
}
//-----------------------------------------------------------------------------
// Convert binary file to csv file.
void binaryToCsv() {
uint8_t lastPct = 0;
block_t block;
uint32_t t0 = millis();
uint32_t syncCluster = 0;
SdFile csvFile;
char csvName[FILE_NAME_DIM];
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
Serial.println();
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
// Create a new csvFile.
strcpy(csvName, binName);
strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
error("open csvFile failed");
}
binFile.rewind();
Serial.print(F("Writing: "));
Serial.print(csvName);
Serial.println(F(" - type any character to stop"));
printHeader(&csvFile);
uint32_t tPct = millis();
while (!Serial.available() && binFile.read(&block, 512) == 512) {
uint16_t i;
if (block.count == 0 || block.count > DATA_DIM) {
break;
}
if (block.overrun) {
csvFile.print(F("OVERRUN,"));
csvFile.println(block.overrun);
}
for (i = 0; i < block.count; i++) {
printData(&csvFile, &block.data[i]);
}
if (csvFile.curCluster() != syncCluster) {
csvFile.sync();
syncCluster = csvFile.curCluster();
}
if ((millis() - tPct) > 1000) {
uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
if (pct != lastPct) {
tPct = millis();
lastPct = pct;
Serial.print(pct, DEC);
Serial.println('%');
}
}
if (Serial.available()) {
break;
}
}
csvFile.close();
Serial.print(F("Done: "));
Serial.print(0.001*(millis() - t0));
Serial.println(F(" Seconds"));
}
//-----------------------------------------------------------------------------
void createBinFile() {
// max number of blocks to erase per erase call
const uint32_t ERASE_SIZE = 262144L;
uint32_t bgnBlock, endBlock;
// Delete old tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("Deleting tmp file " TMP_FILE_NAME));
if (!sd.remove(TMP_FILE_NAME)) {
error("Can't remove tmp file");
}
}
// Create new file.
Serial.println(F("\nCreating new file"));
binFile.close();
if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
error("createContiguous failed");
}
// Get the address of the file on the SD.
if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
error("contiguousRange failed");
}
// Flash erase all data in the file.
Serial.println(F("Erasing all data"));
uint32_t bgnErase = bgnBlock;
uint32_t endErase;
while (bgnErase < endBlock) {
endErase = bgnErase + ERASE_SIZE;
if (endErase > endBlock) {
endErase = endBlock;
}
if (!sd.card()->erase(bgnErase, endErase)) {
error("erase failed");
}
bgnErase = endErase + 1;
}
}
//------------------------------------------------------------------------------
// dump data file to Serial
void dumpData() {
block_t block;
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
Serial.println();
Serial.println(F("Type any character to stop"));
delay(1000);
printHeader(&Serial);
while (!Serial.available() && binFile.read(&block , 512) == 512) {
if (block.count == 0) {
break;
}
if (block.overrun) {
Serial.print(F("OVERRUN,"));
Serial.println(block.overrun);
}
for (uint16_t i = 0; i < block.count; i++) {
printData(&Serial, &block.data[i]);
}
}
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
// log data
void logData() {
createBinFile();
recordBinFile();
renameBinFile();
}
//------------------------------------------------------------------------------
void openBinFile() {
char name[FILE_NAME_DIM];
strcpy(name, binName);
Serial.println(F("\nEnter two digit version"));
Serial.write(name, BASE_NAME_SIZE);
for (int i = 0; i < 2; i++) {
while (!Serial.available()) {
yield();
}
char c = Serial.read();
Serial.write(c);
if (c < '0' || c > '9') {
Serial.println(F("\nInvalid digit"));
return;
}
name[BASE_NAME_SIZE + i] = c;
}
Serial.println(&name[BASE_NAME_SIZE+2]);
if (!sd.exists(name)) {
Serial.println(F("File does not exist"));
return;
}
binFile.close();
strcpy(binName, name);
if (!binFile.open(binName, O_RDONLY)) {
Serial.println(F("open failed"));
return;
}
Serial.println(F("File opened"));
}
//------------------------------------------------------------------------------
void recordBinFile() {
const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 1;
// Index of last queue location.
const uint8_t QUEUE_LAST = QUEUE_DIM - 1;
// Allocate extra buffer space.
block_t block[BUFFER_BLOCK_COUNT - 1];
block_t* curBlock = 0;
block_t* emptyStack[BUFFER_BLOCK_COUNT];
uint8_t emptyTop;
uint8_t minTop;
block_t* fullQueue[QUEUE_DIM];
uint8_t fullHead = 0;
uint8_t fullTail = 0;
// Use SdFat's internal buffer.
emptyStack[0] = (block_t*)sd.vol()->cacheClear();
if (emptyStack[0] == 0) {
error("cacheClear failed");
}
// Put rest of buffers on the empty stack.
for (int i = 1; i < BUFFER_BLOCK_COUNT; i++) {
emptyStack[i] = &block[i - 1];
}
emptyTop = BUFFER_BLOCK_COUNT;
minTop = BUFFER_BLOCK_COUNT;
// Start a multiple block write.
if (!sd.card()->writeStart(binFile.firstBlock())) {
error("writeStart failed");
}
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println(F("Logging - type any character to stop"));
bool closeFile = false;
uint32_t bn = 0;
uint32_t maxLatency = 0;
uint32_t overrun = 0;
uint32_t overrunTotal = 0;
uint32_t logTime = micros();
while(1) {
// Time for next data record.
logTime += LOG_INTERVAL_USEC;
if (Serial.available()) {
closeFile = true;
}
if (closeFile) {
if (curBlock != 0) {
// Put buffer in full queue.
fullQueue[fullHead] = curBlock;
fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
curBlock = 0;
}
} else {
if (curBlock == 0 && emptyTop != 0) {
curBlock = emptyStack[--emptyTop];
if (emptyTop < minTop) {
minTop = emptyTop;
}
curBlock->count = 0;
curBlock->overrun = overrun;
overrun = 0;
}
if ((int32_t)(logTime - micros()) < 0) {
error("Rate too fast");
}
int32_t delta;
do {
delta = micros() - logTime;
} while (delta < 0);
if (curBlock == 0) {
overrun++;
overrunTotal++;
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
}
#if ABORT_ON_OVERRUN
Serial.println(F("Overrun abort"));
break;
#endif // ABORT_ON_OVERRUN
} else {
#if USE_SHARED_SPI
sd.card()->spiStop();
#endif // USE_SHARED_SPI
acquireData(&curBlock->data[curBlock->count++]);
#if USE_SHARED_SPI
sd.card()->spiStart();
#endif // USE_SHARED_SPI
if (curBlock->count == DATA_DIM) {
fullQueue[fullHead] = curBlock;
fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
curBlock = 0;
}
}
}
if (fullHead == fullTail) {
// Exit loop if done.
if (closeFile) {
break;
}
} else if (!sd.card()->isBusy()) {
// Get address of block to write.
block_t* pBlock = fullQueue[fullTail];
fullTail = fullTail < QUEUE_LAST ? fullTail + 1 : 0;
// Write block to SD.
uint32_t usec = micros();
if (!sd.card()->writeData((uint8_t*)pBlock)) {
error("write data failed");
}
usec = micros() - usec;
if (usec > maxLatency) {
maxLatency = usec;
}
// Move block to empty queue.
emptyStack[emptyTop++] = pBlock;
bn++;
if (bn == FILE_BLOCK_COUNT) {
// File full so stop
break;
}
}
}
if (!sd.card()->writeStop()) {
error("writeStop failed");
}
Serial.print(F("Min Free buffers: "));
Serial.println(minTop);
Serial.print(F("Max block write usec: "));
Serial.println(maxLatency);
Serial.print(F("Overruns: "));
Serial.println(overrunTotal);
// Truncate file if recording stopped early.
if (bn != FILE_BLOCK_COUNT) {
Serial.println(F("Truncating file"));
if (!binFile.truncate(512L * bn)) {
error("Can't truncate file");
}
}
}
//------------------------------------------------------------------------------
void recoverTmpFile() {
uint16_t count;
if (!binFile.open(TMP_FILE_NAME, O_RDWR)) {
return;
}
if (binFile.read(&count, 2) != 2 || count != DATA_DIM) {
error("Please delete existing " TMP_FILE_NAME);
}
Serial.println(F("\nRecovering data in tmp file " TMP_FILE_NAME));
uint32_t bgnBlock = 0;
uint32_t endBlock = binFile.fileSize()/512 - 1;
// find last used block.
while (bgnBlock < endBlock) {
uint32_t midBlock = (bgnBlock + endBlock + 1)/2;
binFile.seekSet(512*midBlock);
if (binFile.read(&count, 2) != 2) error("read");
if (count == 0 || count > DATA_DIM) {
endBlock = midBlock - 1;
} else {
bgnBlock = midBlock;
}
}
// truncate after last used block.
if (!binFile.truncate(512*(bgnBlock + 1))) {
error("Truncate " TMP_FILE_NAME " failed");
}
renameBinFile();
}
//-----------------------------------------------------------------------------
void renameBinFile() {
while (sd.exists(binName)) {
if (binName[BASE_NAME_SIZE + 1] != '9') {
binName[BASE_NAME_SIZE + 1]++;
} else {
binName[BASE_NAME_SIZE + 1] = '0';
if (binName[BASE_NAME_SIZE] == '9') {
error("Can't create file name");
}
binName[BASE_NAME_SIZE]++;
}
}
if (!binFile.rename(binName)) {
error("Can't rename file");
}
Serial.print(F("File renamed: "));
Serial.println(binName);
Serial.print(F("File size: "));
Serial.print(binFile.fileSize()/512);
Serial.println(F(" blocks"));
}
//------------------------------------------------------------------------------
void testSensor() {
const uint32_t interval = 200000;
int32_t diff;
data_t data;
Serial.println(F("\nTesting - type any character to stop\n"));
// Wait for Serial Idle.
delay(1000);
printHeader(&Serial);
uint32_t m = micros();
while (!Serial.available()) {
m += interval;
do {
diff = m - micros();
} while (diff > 0);
acquireData(&data);
printData(&Serial, &data);
}
}
//------------------------------------------------------------------------------
void setup(void) {
if (ERROR_LED_PIN >= 0) {
pinMode(ERROR_LED_PIN, OUTPUT);
}
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.print(F("\nFreeStack: "));
Serial.println(FreeStack());
Serial.print(F("Records/block: "));
Serial.println(DATA_DIM);
if (sizeof(block_t) != 512) {
error("Invalid block size");
}
// Allow userSetup access to SPI bus.
pinMode(SD_CS_PIN, OUTPUT);
digitalWrite(SD_CS_PIN, HIGH);
// Setup sensors.
userSetup();
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
sd.initErrorPrint(&Serial);
fatalBlink();
}
// recover existing tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("\nType 'Y' to recover existing tmp file " TMP_FILE_NAME));
while (!Serial.available()) {
yield();
}
if (Serial.read() == 'Y') {
recoverTmpFile();
} else {
error("'Y' not typed, please manually delete " TMP_FILE_NAME);
}
}
}
//------------------------------------------------------------------------------
void loop(void) {
// Read any Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
Serial.println();
Serial.println(F("type:"));
Serial.println(F("b - open existing bin file"));
Serial.println(F("c - convert file to csv"));
Serial.println(F("d - dump data to Serial"));
Serial.println(F("e - overrun error details"));
Serial.println(F("l - list files"));
Serial.println(F("r - record data"));
Serial.println(F("t - test without logging"));
while(!Serial.available()) {
yield();
}
#if WDT_YIELD_TIME_MICROS
Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
while (true) {}
#endif
char c = tolower(Serial.read());
// Discard extra Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, LOW);
}
if (c == 'b') {
openBinFile();
} else if (c == 'c') {
binaryToCsv();
} else if (c == 'd') {
dumpData();
} else if (c == 'e') {
checkOverrun();
} else if (c == 'l') {
Serial.println(F("\nls:"));
sd.ls(&Serial, LS_SIZE);
} else if (c == 'r') {
logData();
} else if (c == 't') {
testSensor();
} else {
Serial.println(F("Invalid entry"));
}
}

View File

@@ -0,0 +1,41 @@
#include "UserTypes.h"
// User data functions. Modify these functions for your data items.
// Start time for data
static uint32_t startMicros;
// Acquire a data record.
void acquireData(data_t* data) {
data->time = micros();
for (int i = 0; i < ADC_DIM; i++) {
data->adc[i] = analogRead(i);
}
}
// Print a data record.
void printData(Print* pr, data_t* data) {
if (startMicros == 0) {
startMicros = data->time;
}
pr->print(data->time - startMicros);
for (int i = 0; i < ADC_DIM; i++) {
pr->write(',');
pr->print(data->adc[i]);
}
pr->println();
}
// Print data header.
void printHeader(Print* pr) {
startMicros = 0;
pr->print(F("micros"));
for (int i = 0; i < ADC_DIM; i++) {
pr->print(F(",adc"));
pr->print(i);
}
pr->println();
}
// Sensor setup
void userSetup() {
}

View File

@@ -0,0 +1,15 @@
#ifndef UserTypes_h
#define UserTypes_h
#include "Arduino.h"
// User data types. Modify for your data items.
#define FILE_BASE_NAME "adc4pin"
const uint8_t ADC_DIM = 4;
struct data_t {
uint32_t time;
uint16_t adc[ADC_DIM];
};
void acquireData(data_t* data);
void printData(Print* pr, data_t* data);
void printHeader(Print* pr);
void userSetup();
#endif // UserTypes_h

View File

@@ -0,0 +1,655 @@
/**
* This program logs data to a binary file. Functions are included
* to convert the binary file to a csv text file.
*
* Samples are logged at regular intervals. The maximum logging rate
* depends on the quality of your SD card and the time required to
* read sensor data. This example has been tested at 500 Hz with
* good SD card on an Uno. 4000 HZ is possible on a Due.
*
* If your SD card has a long write latency, it may be necessary to use
* slower sample rates. Using a Mega Arduino helps overcome latency
* problems since 12 512 byte buffers will be used.
*
* Data is written to the file using a SD multiple block write command.
*/
#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
#include "UserTypes.h"
#ifdef __AVR_ATmega328P__
#include "MinimumSerial.h"
MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
//==============================================================================
// Start of configuration constants.
//==============================================================================
// Abort run on an overrun. Data before the overrun will be saved.
#define ABORT_ON_OVERRUN 1
//------------------------------------------------------------------------------
//Interval between data records in microseconds.
const uint32_t LOG_INTERVAL_USEC = 2000;
//------------------------------------------------------------------------------
// Set USE_SHARED_SPI non-zero for use of an SPI sensor.
// May not work for some cards.
#ifndef USE_SHARED_SPI
#define USE_SHARED_SPI 0
#endif // USE_SHARED_SPI
//------------------------------------------------------------------------------
// Pin definitions.
//
// SD chip select pin.
const uint8_t SD_CS_PIN = SS;
//
// Digital pin to indicate an error, set to -1 if not used.
// The led blinks for fatal errors. The led goes on solid for
// overrun errors and logging continues unless ABORT_ON_OVERRUN
// is non-zero.
#ifdef ERROR_LED_PIN
#undef ERROR_LED_PIN
#endif // ERROR_LED_PIN
const int8_t ERROR_LED_PIN = -1;
//------------------------------------------------------------------------------
// File definitions.
//
// Maximum file size in blocks.
// The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
// This file is flash erased using special SD commands. The file will be
// truncated if logging is stopped early.
const uint32_t FILE_BLOCK_COUNT = 256000;
//
// log file base name if not defined in UserTypes.h
#ifndef FILE_BASE_NAME
#define FILE_BASE_NAME "data"
#endif // FILE_BASE_NAME
//------------------------------------------------------------------------------
// Buffer definitions.
//
// The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT-1 additional
// buffers.
//
#ifndef RAMEND
// Assume ARM. Use total of ten 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 10;
//
#elif RAMEND < 0X8FF
#error Too little SRAM
//
#elif RAMEND < 0X10FF
// Use total of two 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 2;
//
#elif RAMEND < 0X20FF
// Use total of four 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 4;
//
#else // RAMEND
// Use total of 12 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 12;
#endif // RAMEND
//==============================================================================
// End of configuration constants.
//==============================================================================
// Temporary log file. Will be deleted if a reset or power failure occurs.
#define TMP_FILE_NAME FILE_BASE_NAME "##.bin"
// Size of file base name.
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
const uint8_t FILE_NAME_DIM = BASE_NAME_SIZE + 7;
char binName[FILE_NAME_DIM] = FILE_BASE_NAME "00.bin";
SdFat sd;
SdBaseFile binFile;
// Number of data records in a block.
const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t);
//Compute fill so block size is 512 bytes. FILL_DIM may be zero.
const uint16_t FILL_DIM = 512 - 4 - DATA_DIM*sizeof(data_t);
struct block_t {
uint16_t count;
uint16_t overrun;
data_t data[DATA_DIM];
uint8_t fill[FILL_DIM];
};
//==============================================================================
// Error messages stored in flash.
#define error(msg) {sd.errorPrint(&Serial, F(msg));fatalBlink();}
//------------------------------------------------------------------------------
//
void fatalBlink() {
while (true) {
yield();
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
delay(200);
digitalWrite(ERROR_LED_PIN, LOW);
delay(200);
}
}
}
//------------------------------------------------------------------------------
// read data file and check for overruns
void checkOverrun() {
bool headerPrinted = false;
block_t block;
uint32_t bn = 0;
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
Serial.println();
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println(F("Checking overrun errors - type any character to stop"));
while (binFile.read(&block, 512) == 512) {
if (block.count == 0) {
break;
}
if (block.overrun) {
if (!headerPrinted) {
Serial.println();
Serial.println(F("Overruns:"));
Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
headerPrinted = true;
}
Serial.print(bn);
Serial.print(',');
Serial.print(binFile.firstBlock() + bn);
Serial.print(',');
Serial.println(block.overrun);
}
bn++;
}
if (!headerPrinted) {
Serial.println(F("No errors found"));
} else {
Serial.println(F("Done"));
}
}
//-----------------------------------------------------------------------------
// Convert binary file to csv file.
void binaryToCsv() {
uint8_t lastPct = 0;
block_t block;
uint32_t t0 = millis();
uint32_t syncCluster = 0;
SdFile csvFile;
char csvName[FILE_NAME_DIM];
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
Serial.println();
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
// Create a new csvFile.
strcpy(csvName, binName);
strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
error("open csvFile failed");
}
binFile.rewind();
Serial.print(F("Writing: "));
Serial.print(csvName);
Serial.println(F(" - type any character to stop"));
printHeader(&csvFile);
uint32_t tPct = millis();
while (!Serial.available() && binFile.read(&block, 512) == 512) {
uint16_t i;
if (block.count == 0 || block.count > DATA_DIM) {
break;
}
if (block.overrun) {
csvFile.print(F("OVERRUN,"));
csvFile.println(block.overrun);
}
for (i = 0; i < block.count; i++) {
printData(&csvFile, &block.data[i]);
}
if (csvFile.curCluster() != syncCluster) {
csvFile.sync();
syncCluster = csvFile.curCluster();
}
if ((millis() - tPct) > 1000) {
uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
if (pct != lastPct) {
tPct = millis();
lastPct = pct;
Serial.print(pct, DEC);
Serial.println('%');
}
}
if (Serial.available()) {
break;
}
}
csvFile.close();
Serial.print(F("Done: "));
Serial.print(0.001*(millis() - t0));
Serial.println(F(" Seconds"));
}
//-----------------------------------------------------------------------------
void createBinFile() {
// max number of blocks to erase per erase call
const uint32_t ERASE_SIZE = 262144L;
uint32_t bgnBlock, endBlock;
// Delete old tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("Deleting tmp file " TMP_FILE_NAME));
if (!sd.remove(TMP_FILE_NAME)) {
error("Can't remove tmp file");
}
}
// Create new file.
Serial.println(F("\nCreating new file"));
binFile.close();
if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
error("createContiguous failed");
}
// Get the address of the file on the SD.
if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
error("contiguousRange failed");
}
// Flash erase all data in the file.
Serial.println(F("Erasing all data"));
uint32_t bgnErase = bgnBlock;
uint32_t endErase;
while (bgnErase < endBlock) {
endErase = bgnErase + ERASE_SIZE;
if (endErase > endBlock) {
endErase = endBlock;
}
if (!sd.card()->erase(bgnErase, endErase)) {
error("erase failed");
}
bgnErase = endErase + 1;
}
}
//------------------------------------------------------------------------------
// dump data file to Serial
void dumpData() {
block_t block;
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
Serial.println();
Serial.println(F("Type any character to stop"));
delay(1000);
printHeader(&Serial);
while (!Serial.available() && binFile.read(&block , 512) == 512) {
if (block.count == 0) {
break;
}
if (block.overrun) {
Serial.print(F("OVERRUN,"));
Serial.println(block.overrun);
}
for (uint16_t i = 0; i < block.count; i++) {
printData(&Serial, &block.data[i]);
}
}
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
// log data
void logData() {
createBinFile();
recordBinFile();
renameBinFile();
}
//------------------------------------------------------------------------------
void openBinFile() {
char name[FILE_NAME_DIM];
strcpy(name, binName);
Serial.println(F("\nEnter two digit version"));
Serial.write(name, BASE_NAME_SIZE);
for (int i = 0; i < 2; i++) {
while (!Serial.available()) {
yield();
}
char c = Serial.read();
Serial.write(c);
if (c < '0' || c > '9') {
Serial.println(F("\nInvalid digit"));
return;
}
name[BASE_NAME_SIZE + i] = c;
}
Serial.println(&name[BASE_NAME_SIZE+2]);
if (!sd.exists(name)) {
Serial.println(F("File does not exist"));
return;
}
binFile.close();
strcpy(binName, name);
if (!binFile.open(binName, O_RDONLY)) {
Serial.println(F("open failed"));
return;
}
Serial.println(F("File opened"));
}
//------------------------------------------------------------------------------
void recordBinFile() {
const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 1;
// Index of last queue location.
const uint8_t QUEUE_LAST = QUEUE_DIM - 1;
// Allocate extra buffer space.
block_t block[BUFFER_BLOCK_COUNT - 1];
block_t* curBlock = 0;
block_t* emptyStack[BUFFER_BLOCK_COUNT];
uint8_t emptyTop;
uint8_t minTop;
block_t* fullQueue[QUEUE_DIM];
uint8_t fullHead = 0;
uint8_t fullTail = 0;
// Use SdFat's internal buffer.
emptyStack[0] = (block_t*)sd.vol()->cacheClear();
if (emptyStack[0] == 0) {
error("cacheClear failed");
}
// Put rest of buffers on the empty stack.
for (int i = 1; i < BUFFER_BLOCK_COUNT; i++) {
emptyStack[i] = &block[i - 1];
}
emptyTop = BUFFER_BLOCK_COUNT;
minTop = BUFFER_BLOCK_COUNT;
// Start a multiple block write.
if (!sd.card()->writeStart(binFile.firstBlock())) {
error("writeStart failed");
}
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println(F("Logging - type any character to stop"));
bool closeFile = false;
uint32_t bn = 0;
uint32_t maxLatency = 0;
uint32_t overrun = 0;
uint32_t overrunTotal = 0;
uint32_t logTime = micros();
while(1) {
// Time for next data record.
logTime += LOG_INTERVAL_USEC;
if (Serial.available()) {
closeFile = true;
}
if (closeFile) {
if (curBlock != 0) {
// Put buffer in full queue.
fullQueue[fullHead] = curBlock;
fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
curBlock = 0;
}
} else {
if (curBlock == 0 && emptyTop != 0) {
curBlock = emptyStack[--emptyTop];
if (emptyTop < minTop) {
minTop = emptyTop;
}
curBlock->count = 0;
curBlock->overrun = overrun;
overrun = 0;
}
if ((int32_t)(logTime - micros()) < 0) {
error("Rate too fast");
}
int32_t delta;
do {
delta = micros() - logTime;
} while (delta < 0);
if (curBlock == 0) {
overrun++;
overrunTotal++;
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
}
#if ABORT_ON_OVERRUN
Serial.println(F("Overrun abort"));
break;
#endif // ABORT_ON_OVERRUN
} else {
#if USE_SHARED_SPI
sd.card()->spiStop();
#endif // USE_SHARED_SPI
acquireData(&curBlock->data[curBlock->count++]);
#if USE_SHARED_SPI
sd.card()->spiStart();
#endif // USE_SHARED_SPI
if (curBlock->count == DATA_DIM) {
fullQueue[fullHead] = curBlock;
fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
curBlock = 0;
}
}
}
if (fullHead == fullTail) {
// Exit loop if done.
if (closeFile) {
break;
}
} else if (!sd.card()->isBusy()) {
// Get address of block to write.
block_t* pBlock = fullQueue[fullTail];
fullTail = fullTail < QUEUE_LAST ? fullTail + 1 : 0;
// Write block to SD.
uint32_t usec = micros();
if (!sd.card()->writeData((uint8_t*)pBlock)) {
error("write data failed");
}
usec = micros() - usec;
if (usec > maxLatency) {
maxLatency = usec;
}
// Move block to empty queue.
emptyStack[emptyTop++] = pBlock;
bn++;
if (bn == FILE_BLOCK_COUNT) {
// File full so stop
break;
}
}
}
if (!sd.card()->writeStop()) {
error("writeStop failed");
}
Serial.print(F("Min Free buffers: "));
Serial.println(minTop);
Serial.print(F("Max block write usec: "));
Serial.println(maxLatency);
Serial.print(F("Overruns: "));
Serial.println(overrunTotal);
// Truncate file if recording stopped early.
if (bn != FILE_BLOCK_COUNT) {
Serial.println(F("Truncating file"));
if (!binFile.truncate(512L * bn)) {
error("Can't truncate file");
}
}
}
//------------------------------------------------------------------------------
void recoverTmpFile() {
uint16_t count;
if (!binFile.open(TMP_FILE_NAME, O_RDWR)) {
return;
}
if (binFile.read(&count, 2) != 2 || count != DATA_DIM) {
error("Please delete existing " TMP_FILE_NAME);
}
Serial.println(F("\nRecovering data in tmp file " TMP_FILE_NAME));
uint32_t bgnBlock = 0;
uint32_t endBlock = binFile.fileSize()/512 - 1;
// find last used block.
while (bgnBlock < endBlock) {
uint32_t midBlock = (bgnBlock + endBlock + 1)/2;
binFile.seekSet(512*midBlock);
if (binFile.read(&count, 2) != 2) error("read");
if (count == 0 || count > DATA_DIM) {
endBlock = midBlock - 1;
} else {
bgnBlock = midBlock;
}
}
// truncate after last used block.
if (!binFile.truncate(512*(bgnBlock + 1))) {
error("Truncate " TMP_FILE_NAME " failed");
}
renameBinFile();
}
//-----------------------------------------------------------------------------
void renameBinFile() {
while (sd.exists(binName)) {
if (binName[BASE_NAME_SIZE + 1] != '9') {
binName[BASE_NAME_SIZE + 1]++;
} else {
binName[BASE_NAME_SIZE + 1] = '0';
if (binName[BASE_NAME_SIZE] == '9') {
error("Can't create file name");
}
binName[BASE_NAME_SIZE]++;
}
}
if (!binFile.rename(binName)) {
error("Can't rename file");
}
Serial.print(F("File renamed: "));
Serial.println(binName);
Serial.print(F("File size: "));
Serial.print(binFile.fileSize()/512);
Serial.println(F(" blocks"));
}
//------------------------------------------------------------------------------
void testSensor() {
const uint32_t interval = 200000;
int32_t diff;
data_t data;
Serial.println(F("\nTesting - type any character to stop\n"));
// Wait for Serial Idle.
delay(1000);
printHeader(&Serial);
uint32_t m = micros();
while (!Serial.available()) {
m += interval;
do {
diff = m - micros();
} while (diff > 0);
acquireData(&data);
printData(&Serial, &data);
}
}
//------------------------------------------------------------------------------
void setup(void) {
if (ERROR_LED_PIN >= 0) {
pinMode(ERROR_LED_PIN, OUTPUT);
}
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.print(F("\nFreeStack: "));
Serial.println(FreeStack());
Serial.print(F("Records/block: "));
Serial.println(DATA_DIM);
if (sizeof(block_t) != 512) {
error("Invalid block size");
}
// Allow userSetup access to SPI bus.
pinMode(SD_CS_PIN, OUTPUT);
digitalWrite(SD_CS_PIN, HIGH);
// Setup sensors.
userSetup();
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
sd.initErrorPrint(&Serial);
fatalBlink();
}
// recover existing tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("\nType 'Y' to recover existing tmp file " TMP_FILE_NAME));
while (!Serial.available()) {
yield();
}
if (Serial.read() == 'Y') {
recoverTmpFile();
} else {
error("'Y' not typed, please manually delete " TMP_FILE_NAME);
}
}
}
//------------------------------------------------------------------------------
void loop(void) {
// Read any Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
Serial.println();
Serial.println(F("type:"));
Serial.println(F("b - open existing bin file"));
Serial.println(F("c - convert file to csv"));
Serial.println(F("d - dump data to Serial"));
Serial.println(F("e - overrun error details"));
Serial.println(F("l - list files"));
Serial.println(F("r - record data"));
Serial.println(F("t - test without logging"));
while(!Serial.available()) {
yield();
}
#if WDT_YIELD_TIME_MICROS
Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
while (true) {}
#endif
char c = tolower(Serial.read());
// Discard extra Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, LOW);
}
if (c == 'b') {
openBinFile();
} else if (c == 'c') {
binaryToCsv();
} else if (c == 'd') {
dumpData();
} else if (c == 'e') {
checkOverrun();
} else if (c == 'l') {
Serial.println(F("\nls:"));
sd.ls(&Serial, LS_SIZE);
} else if (c == 'r') {
logData();
} else if (c == 't') {
testSensor();
} else {
Serial.println(F("Invalid entry"));
}
}

View File

@@ -0,0 +1 @@
// Empty file with name LowLatencyLoggerADXL345.ino to make IDE happy.

View File

@@ -0,0 +1,70 @@
#include "UserTypes.h"
// User data functions. Modify these functions for your data items.
// Start time for data
static uint32_t startMicros;
const uint8_t ADXL345_CS = 9;
const uint8_t POWER_CTL = 0x2D; //Power Control Register
const uint8_t DATA_FORMAT = 0x31;
const uint8_t DATAX0 = 0x32; //X-Axis Data 0
const uint8_t DATAX1 = 0x33; //X-Axis Data 1
const uint8_t DATAY0 = 0x34; //Y-Axis Data 0
const uint8_t DATAY1 = 0x35; //Y-Axis Data 1
const uint8_t DATAZ0 = 0x36; //Z-Axis Data 0
const uint8_t DATAZ1 = 0x37; //Z-Axis Data 1
void writeADXL345Register(const uint8_t registerAddress, const uint8_t value) {
// Max SPI clock frequency is 5 MHz with CPOL = 1 and CPHA = 1.
SPI.beginTransaction(SPISettings(5000000, MSBFIRST, SPI_MODE3));
digitalWrite(ADXL345_CS, LOW);
SPI.transfer(registerAddress);
SPI.transfer(value);
digitalWrite(ADXL345_CS, HIGH);
SPI.endTransaction();
}
void userSetup() {
SPI.begin();
pinMode(ADXL345_CS, OUTPUT);
digitalWrite(ADXL345_CS, HIGH);
//Put the ADXL345 into +/- 4G range by writing the value 0x01 to the DATA_FORMAT register.
writeADXL345Register(DATA_FORMAT, 0x01);
//Put the ADXL345 into Measurement Mode by writing 0x08 to the POWER_CTL register.
writeADXL345Register(POWER_CTL, 0x08); //Measurement mode
}
// Acquire a data record.
void acquireData(data_t* data) {
// Max SPI clock frequency is 5 MHz with CPOL = 1 and CPHA = 1.
SPI.beginTransaction(SPISettings(5000000, MSBFIRST, SPI_MODE3));
data->time = micros();
digitalWrite(ADXL345_CS, LOW);
// Read multiple bytes so or 0XC0 with address.
SPI.transfer(DATAX0 | 0XC0);
data->accel[0] = SPI.transfer(0) | (SPI.transfer(0) << 8);
data->accel[1] = SPI.transfer(0) | (SPI.transfer(0) << 8);
data->accel[2] = SPI.transfer(0) | (SPI.transfer(0) << 8);
digitalWrite(ADXL345_CS, HIGH);
SPI.endTransaction();
}
// Print a data record.
void printData(Print* pr, data_t* data) {
if (startMicros == 0) {
startMicros = data->time;
}
pr->print(data->time - startMicros);
for (int i = 0; i < ACCEL_DIM; i++) {
pr->write(',');
pr->print(data->accel[i]);
}
pr->println();
}
// Print data header.
void printHeader(Print* pr) {
startMicros = 0;
pr->println(F("micros,ax,ay,az"));
}

View File

@@ -0,0 +1,17 @@
#ifndef UserTypes_h
#define UserTypes_h
#include "Arduino.h"
#include "SPI.h"
#define USE_SHARED_SPI 1
#define FILE_BASE_NAME "ADXL4G"
// User data types. Modify for your data items.
const uint8_t ACCEL_DIM = 3;
struct data_t {
uint32_t time;
int16_t accel[ACCEL_DIM];
};
void acquireData(data_t* data);
void printData(Print* pr, data_t* data);
void printHeader(Print* pr);
void userSetup();
#endif // UserTypes_h

View File

@@ -0,0 +1 @@
Test of shared SPI for LowLatencyLogger.

View File

@@ -0,0 +1,655 @@
/**
* This program logs data to a binary file. Functions are included
* to convert the binary file to a csv text file.
*
* Samples are logged at regular intervals. The maximum logging rate
* depends on the quality of your SD card and the time required to
* read sensor data. This example has been tested at 500 Hz with
* good SD card on an Uno. 4000 HZ is possible on a Due.
*
* If your SD card has a long write latency, it may be necessary to use
* slower sample rates. Using a Mega Arduino helps overcome latency
* problems since 12 512 byte buffers will be used.
*
* Data is written to the file using a SD multiple block write command.
*/
#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
#include "UserTypes.h"
#ifdef __AVR_ATmega328P__
#include "MinimumSerial.h"
MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
//==============================================================================
// Start of configuration constants.
//==============================================================================
// Abort run on an overrun. Data before the overrun will be saved.
#define ABORT_ON_OVERRUN 1
//------------------------------------------------------------------------------
//Interval between data records in microseconds.
const uint32_t LOG_INTERVAL_USEC = 2000;
//------------------------------------------------------------------------------
// Set USE_SHARED_SPI non-zero for use of an SPI sensor.
// May not work for some cards.
#ifndef USE_SHARED_SPI
#define USE_SHARED_SPI 0
#endif // USE_SHARED_SPI
//------------------------------------------------------------------------------
// Pin definitions.
//
// SD chip select pin.
const uint8_t SD_CS_PIN = SS;
//
// Digital pin to indicate an error, set to -1 if not used.
// The led blinks for fatal errors. The led goes on solid for
// overrun errors and logging continues unless ABORT_ON_OVERRUN
// is non-zero.
#ifdef ERROR_LED_PIN
#undef ERROR_LED_PIN
#endif // ERROR_LED_PIN
const int8_t ERROR_LED_PIN = -1;
//------------------------------------------------------------------------------
// File definitions.
//
// Maximum file size in blocks.
// The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
// This file is flash erased using special SD commands. The file will be
// truncated if logging is stopped early.
const uint32_t FILE_BLOCK_COUNT = 256000;
//
// log file base name if not defined in UserTypes.h
#ifndef FILE_BASE_NAME
#define FILE_BASE_NAME "data"
#endif // FILE_BASE_NAME
//------------------------------------------------------------------------------
// Buffer definitions.
//
// The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT-1 additional
// buffers.
//
#ifndef RAMEND
// Assume ARM. Use total of ten 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 10;
//
#elif RAMEND < 0X8FF
#error Too little SRAM
//
#elif RAMEND < 0X10FF
// Use total of two 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 2;
//
#elif RAMEND < 0X20FF
// Use total of four 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 4;
//
#else // RAMEND
// Use total of 12 512 byte buffers.
const uint8_t BUFFER_BLOCK_COUNT = 12;
#endif // RAMEND
//==============================================================================
// End of configuration constants.
//==============================================================================
// Temporary log file. Will be deleted if a reset or power failure occurs.
#define TMP_FILE_NAME FILE_BASE_NAME "##.bin"
// Size of file base name.
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
const uint8_t FILE_NAME_DIM = BASE_NAME_SIZE + 7;
char binName[FILE_NAME_DIM] = FILE_BASE_NAME "00.bin";
SdFat sd;
SdBaseFile binFile;
// Number of data records in a block.
const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t);
//Compute fill so block size is 512 bytes. FILL_DIM may be zero.
const uint16_t FILL_DIM = 512 - 4 - DATA_DIM*sizeof(data_t);
struct block_t {
uint16_t count;
uint16_t overrun;
data_t data[DATA_DIM];
uint8_t fill[FILL_DIM];
};
//==============================================================================
// Error messages stored in flash.
#define error(msg) {sd.errorPrint(&Serial, F(msg));fatalBlink();}
//------------------------------------------------------------------------------
//
void fatalBlink() {
while (true) {
yield();
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
delay(200);
digitalWrite(ERROR_LED_PIN, LOW);
delay(200);
}
}
}
//------------------------------------------------------------------------------
// read data file and check for overruns
void checkOverrun() {
bool headerPrinted = false;
block_t block;
uint32_t bn = 0;
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
Serial.println();
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println(F("Checking overrun errors - type any character to stop"));
while (binFile.read(&block, 512) == 512) {
if (block.count == 0) {
break;
}
if (block.overrun) {
if (!headerPrinted) {
Serial.println();
Serial.println(F("Overruns:"));
Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
headerPrinted = true;
}
Serial.print(bn);
Serial.print(',');
Serial.print(binFile.firstBlock() + bn);
Serial.print(',');
Serial.println(block.overrun);
}
bn++;
}
if (!headerPrinted) {
Serial.println(F("No errors found"));
} else {
Serial.println(F("Done"));
}
}
//-----------------------------------------------------------------------------
// Convert binary file to csv file.
void binaryToCsv() {
uint8_t lastPct = 0;
block_t block;
uint32_t t0 = millis();
uint32_t syncCluster = 0;
SdFile csvFile;
char csvName[FILE_NAME_DIM];
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
Serial.println();
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
// Create a new csvFile.
strcpy(csvName, binName);
strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
error("open csvFile failed");
}
binFile.rewind();
Serial.print(F("Writing: "));
Serial.print(csvName);
Serial.println(F(" - type any character to stop"));
printHeader(&csvFile);
uint32_t tPct = millis();
while (!Serial.available() && binFile.read(&block, 512) == 512) {
uint16_t i;
if (block.count == 0 || block.count > DATA_DIM) {
break;
}
if (block.overrun) {
csvFile.print(F("OVERRUN,"));
csvFile.println(block.overrun);
}
for (i = 0; i < block.count; i++) {
printData(&csvFile, &block.data[i]);
}
if (csvFile.curCluster() != syncCluster) {
csvFile.sync();
syncCluster = csvFile.curCluster();
}
if ((millis() - tPct) > 1000) {
uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
if (pct != lastPct) {
tPct = millis();
lastPct = pct;
Serial.print(pct, DEC);
Serial.println('%');
}
}
if (Serial.available()) {
break;
}
}
csvFile.close();
Serial.print(F("Done: "));
Serial.print(0.001*(millis() - t0));
Serial.println(F(" Seconds"));
}
//-----------------------------------------------------------------------------
void createBinFile() {
// max number of blocks to erase per erase call
const uint32_t ERASE_SIZE = 262144L;
uint32_t bgnBlock, endBlock;
// Delete old tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("Deleting tmp file " TMP_FILE_NAME));
if (!sd.remove(TMP_FILE_NAME)) {
error("Can't remove tmp file");
}
}
// Create new file.
Serial.println(F("\nCreating new file"));
binFile.close();
if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
error("createContiguous failed");
}
// Get the address of the file on the SD.
if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
error("contiguousRange failed");
}
// Flash erase all data in the file.
Serial.println(F("Erasing all data"));
uint32_t bgnErase = bgnBlock;
uint32_t endErase;
while (bgnErase < endBlock) {
endErase = bgnErase + ERASE_SIZE;
if (endErase > endBlock) {
endErase = endBlock;
}
if (!sd.card()->erase(bgnErase, endErase)) {
error("erase failed");
}
bgnErase = endErase + 1;
}
}
//------------------------------------------------------------------------------
// dump data file to Serial
void dumpData() {
block_t block;
if (!binFile.isOpen()) {
Serial.println();
Serial.println(F("No current binary file"));
return;
}
binFile.rewind();
Serial.println();
Serial.println(F("Type any character to stop"));
delay(1000);
printHeader(&Serial);
while (!Serial.available() && binFile.read(&block , 512) == 512) {
if (block.count == 0) {
break;
}
if (block.overrun) {
Serial.print(F("OVERRUN,"));
Serial.println(block.overrun);
}
for (uint16_t i = 0; i < block.count; i++) {
printData(&Serial, &block.data[i]);
}
}
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
// log data
void logData() {
createBinFile();
recordBinFile();
renameBinFile();
}
//------------------------------------------------------------------------------
void openBinFile() {
char name[FILE_NAME_DIM];
strcpy(name, binName);
Serial.println(F("\nEnter two digit version"));
Serial.write(name, BASE_NAME_SIZE);
for (int i = 0; i < 2; i++) {
while (!Serial.available()) {
yield();
}
char c = Serial.read();
Serial.write(c);
if (c < '0' || c > '9') {
Serial.println(F("\nInvalid digit"));
return;
}
name[BASE_NAME_SIZE + i] = c;
}
Serial.println(&name[BASE_NAME_SIZE+2]);
if (!sd.exists(name)) {
Serial.println(F("File does not exist"));
return;
}
binFile.close();
strcpy(binName, name);
if (!binFile.open(binName, O_RDONLY)) {
Serial.println(F("open failed"));
return;
}
Serial.println(F("File opened"));
}
//------------------------------------------------------------------------------
void recordBinFile() {
const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 1;
// Index of last queue location.
const uint8_t QUEUE_LAST = QUEUE_DIM - 1;
// Allocate extra buffer space.
block_t block[BUFFER_BLOCK_COUNT - 1];
block_t* curBlock = 0;
block_t* emptyStack[BUFFER_BLOCK_COUNT];
uint8_t emptyTop;
uint8_t minTop;
block_t* fullQueue[QUEUE_DIM];
uint8_t fullHead = 0;
uint8_t fullTail = 0;
// Use SdFat's internal buffer.
emptyStack[0] = (block_t*)sd.vol()->cacheClear();
if (emptyStack[0] == 0) {
error("cacheClear failed");
}
// Put rest of buffers on the empty stack.
for (int i = 1; i < BUFFER_BLOCK_COUNT; i++) {
emptyStack[i] = &block[i - 1];
}
emptyTop = BUFFER_BLOCK_COUNT;
minTop = BUFFER_BLOCK_COUNT;
// Start a multiple block write.
if (!sd.card()->writeStart(binFile.firstBlock())) {
error("writeStart failed");
}
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
Serial.println(F("Logging - type any character to stop"));
bool closeFile = false;
uint32_t bn = 0;
uint32_t maxLatency = 0;
uint32_t overrun = 0;
uint32_t overrunTotal = 0;
uint32_t logTime = micros();
while(1) {
// Time for next data record.
logTime += LOG_INTERVAL_USEC;
if (Serial.available()) {
closeFile = true;
}
if (closeFile) {
if (curBlock != 0) {
// Put buffer in full queue.
fullQueue[fullHead] = curBlock;
fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
curBlock = 0;
}
} else {
if (curBlock == 0 && emptyTop != 0) {
curBlock = emptyStack[--emptyTop];
if (emptyTop < minTop) {
minTop = emptyTop;
}
curBlock->count = 0;
curBlock->overrun = overrun;
overrun = 0;
}
if ((int32_t)(logTime - micros()) < 0) {
error("Rate too fast");
}
int32_t delta;
do {
delta = micros() - logTime;
} while (delta < 0);
if (curBlock == 0) {
overrun++;
overrunTotal++;
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, HIGH);
}
#if ABORT_ON_OVERRUN
Serial.println(F("Overrun abort"));
break;
#endif // ABORT_ON_OVERRUN
} else {
#if USE_SHARED_SPI
sd.card()->spiStop();
#endif // USE_SHARED_SPI
acquireData(&curBlock->data[curBlock->count++]);
#if USE_SHARED_SPI
sd.card()->spiStart();
#endif // USE_SHARED_SPI
if (curBlock->count == DATA_DIM) {
fullQueue[fullHead] = curBlock;
fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
curBlock = 0;
}
}
}
if (fullHead == fullTail) {
// Exit loop if done.
if (closeFile) {
break;
}
} else if (!sd.card()->isBusy()) {
// Get address of block to write.
block_t* pBlock = fullQueue[fullTail];
fullTail = fullTail < QUEUE_LAST ? fullTail + 1 : 0;
// Write block to SD.
uint32_t usec = micros();
if (!sd.card()->writeData((uint8_t*)pBlock)) {
error("write data failed");
}
usec = micros() - usec;
if (usec > maxLatency) {
maxLatency = usec;
}
// Move block to empty queue.
emptyStack[emptyTop++] = pBlock;
bn++;
if (bn == FILE_BLOCK_COUNT) {
// File full so stop
break;
}
}
}
if (!sd.card()->writeStop()) {
error("writeStop failed");
}
Serial.print(F("Min Free buffers: "));
Serial.println(minTop);
Serial.print(F("Max block write usec: "));
Serial.println(maxLatency);
Serial.print(F("Overruns: "));
Serial.println(overrunTotal);
// Truncate file if recording stopped early.
if (bn != FILE_BLOCK_COUNT) {
Serial.println(F("Truncating file"));
if (!binFile.truncate(512L * bn)) {
error("Can't truncate file");
}
}
}
//------------------------------------------------------------------------------
void recoverTmpFile() {
uint16_t count;
if (!binFile.open(TMP_FILE_NAME, O_RDWR)) {
return;
}
if (binFile.read(&count, 2) != 2 || count != DATA_DIM) {
error("Please delete existing " TMP_FILE_NAME);
}
Serial.println(F("\nRecovering data in tmp file " TMP_FILE_NAME));
uint32_t bgnBlock = 0;
uint32_t endBlock = binFile.fileSize()/512 - 1;
// find last used block.
while (bgnBlock < endBlock) {
uint32_t midBlock = (bgnBlock + endBlock + 1)/2;
binFile.seekSet(512*midBlock);
if (binFile.read(&count, 2) != 2) error("read");
if (count == 0 || count > DATA_DIM) {
endBlock = midBlock - 1;
} else {
bgnBlock = midBlock;
}
}
// truncate after last used block.
if (!binFile.truncate(512*(bgnBlock + 1))) {
error("Truncate " TMP_FILE_NAME " failed");
}
renameBinFile();
}
//-----------------------------------------------------------------------------
void renameBinFile() {
while (sd.exists(binName)) {
if (binName[BASE_NAME_SIZE + 1] != '9') {
binName[BASE_NAME_SIZE + 1]++;
} else {
binName[BASE_NAME_SIZE + 1] = '0';
if (binName[BASE_NAME_SIZE] == '9') {
error("Can't create file name");
}
binName[BASE_NAME_SIZE]++;
}
}
if (!binFile.rename(binName)) {
error("Can't rename file");
}
Serial.print(F("File renamed: "));
Serial.println(binName);
Serial.print(F("File size: "));
Serial.print(binFile.fileSize()/512);
Serial.println(F(" blocks"));
}
//------------------------------------------------------------------------------
void testSensor() {
const uint32_t interval = 200000;
int32_t diff;
data_t data;
Serial.println(F("\nTesting - type any character to stop\n"));
// Wait for Serial Idle.
delay(1000);
printHeader(&Serial);
uint32_t m = micros();
while (!Serial.available()) {
m += interval;
do {
diff = m - micros();
} while (diff > 0);
acquireData(&data);
printData(&Serial, &data);
}
}
//------------------------------------------------------------------------------
void setup(void) {
if (ERROR_LED_PIN >= 0) {
pinMode(ERROR_LED_PIN, OUTPUT);
}
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.print(F("\nFreeStack: "));
Serial.println(FreeStack());
Serial.print(F("Records/block: "));
Serial.println(DATA_DIM);
if (sizeof(block_t) != 512) {
error("Invalid block size");
}
// Allow userSetup access to SPI bus.
pinMode(SD_CS_PIN, OUTPUT);
digitalWrite(SD_CS_PIN, HIGH);
// Setup sensors.
userSetup();
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
sd.initErrorPrint(&Serial);
fatalBlink();
}
// recover existing tmp file.
if (sd.exists(TMP_FILE_NAME)) {
Serial.println(F("\nType 'Y' to recover existing tmp file " TMP_FILE_NAME));
while (!Serial.available()) {
yield();
}
if (Serial.read() == 'Y') {
recoverTmpFile();
} else {
error("'Y' not typed, please manually delete " TMP_FILE_NAME);
}
}
}
//------------------------------------------------------------------------------
void loop(void) {
// Read any Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
Serial.println();
Serial.println(F("type:"));
Serial.println(F("b - open existing bin file"));
Serial.println(F("c - convert file to csv"));
Serial.println(F("d - dump data to Serial"));
Serial.println(F("e - overrun error details"));
Serial.println(F("l - list files"));
Serial.println(F("r - record data"));
Serial.println(F("t - test without logging"));
while(!Serial.available()) {
yield();
}
#if WDT_YIELD_TIME_MICROS
Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
while (true) {}
#endif
char c = tolower(Serial.read());
// Discard extra Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
if (ERROR_LED_PIN >= 0) {
digitalWrite(ERROR_LED_PIN, LOW);
}
if (c == 'b') {
openBinFile();
} else if (c == 'c') {
binaryToCsv();
} else if (c == 'd') {
dumpData();
} else if (c == 'e') {
checkOverrun();
} else if (c == 'l') {
Serial.println(F("\nls:"));
sd.ls(&Serial, LS_SIZE);
} else if (c == 'r') {
logData();
} else if (c == 't') {
testSensor();
} else {
Serial.println(F("Invalid entry"));
}
}

View File

@@ -0,0 +1,2 @@
// Empty file with name LowLatencyLoggerMPU6050.ino to make IDE happy.

View File

@@ -0,0 +1,51 @@
// User data functions. Modify these functions for your data items.
#include "UserTypes.h"
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
//------------------------------------------------------------------------------
MPU6050 mpu;
static uint32_t startMicros;
// Acquire a data record.
void acquireData(data_t* data) {
data->time = micros();
mpu.getMotion6(&data->ax, &data->ay, &data->az,
&data->gx, &data->gy, &data->gz);
}
// setup AVR I2C
void userSetup() {
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
Wire.setClock(400000);
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true);
#endif
mpu.initialize();
}
// Print a data record.
void printData(Print* pr, data_t* data) {
if (startMicros == 0) {
startMicros = data->time;
}
pr->print(data->time- startMicros);
pr->write(',');
pr->print(data->ax);
pr->write(',');
pr->print(data->ay);
pr->write(',');
pr->print(data->az);
pr->write(',');
pr->print(data->gx);
pr->write(',');
pr->print(data->gy);
pr->write(',');
pr->println(data->gz);
}
// Print data header.
void printHeader(Print* pr) {
startMicros = 0;
pr->println(F("micros,ax,ay,az,gx,gy,gz"));
}

View File

@@ -0,0 +1,18 @@
#ifndef UserTypes_h
#define UserTypes_h
#include "Arduino.h"
#define FILE_BASE_NAME "mpuraw"
struct data_t {
unsigned long time;
int16_t ax;
int16_t ay;
int16_t az;
int16_t gx;
int16_t gy;
int16_t gz;
};
void acquireData(data_t* data);
void printData(Print* pr, data_t* data);
void printHeader(Print* pr);
void userSetup();
#endif // UserTypes_h

View File

@@ -0,0 +1,152 @@
/*
* This program is a simple Print benchmark.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#include "FreeStack.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// number of lines to print
const uint16_t N_PRINT = 20000;
// file system
SdFat sd;
// test file
SdFile file;
// Serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
}
//------------------------------------------------------------------------------
void loop() {
uint32_t maxLatency;
uint32_t minLatency;
uint32_t totalLatency;
// Read any existing Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
// F stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
delay(400); // catch Due reset problem
cout << F("FreeStack: ") << FreeStack() << endl;
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
cout << F("Type is FAT") << int(sd.vol()->fatType()) << endl;
cout << F("Starting print test. Please wait.\n\n");
// do write test
for (int test = 0; test < 6; test++) {
char fileName[13] = "bench0.txt";
fileName[5] = '0' + test;
// open or create file - truncate existing file.
if (!file.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) {
error("open failed");
}
maxLatency = 0;
minLatency = 999999;
totalLatency = 0;
switch(test) {
case 0:
cout << F("Test of println(uint16_t)\n");
break;
case 1:
cout << F("Test of printField(uint16_t, char)\n");
break;
case 2:
cout << F("Test of println(uint32_t)\n");
break;
case 3:
cout << F("Test of printField(uint32_t, char)\n");
break;
case 4:
cout << F("Test of println(float)\n");
break;
case 5:
cout << F("Test of printField(float, char)\n");
break;
}
uint32_t t = millis();
for (uint16_t i = 0; i < N_PRINT; i++) {
uint32_t m = micros();
switch(test) {
case 0:
file.println(i);
break;
case 1:
file.printField(i, '\n');
break;
case 2:
file.println(12345678UL + i);
break;
case 3:
file.printField(12345678UL + i, '\n');
break;
case 4:
file.println((float)0.01*i);
break;
case 5:
file.printField((float)0.01*i, '\n');
break;
}
if (file.getWriteError()) {
error("write failed");
}
m = micros() - m;
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
totalLatency += m;
}
file.close();
t = millis() - t;
double s = file.fileSize();
cout << F("Time ") << 0.001*t << F(" sec\n");
cout << F("File size ") << 0.001*s << F(" KB\n");
cout << F("Write ") << s/t << F(" KB/sec\n");
cout << F("Maximum latency: ") << maxLatency;
cout << F(" usec, Minimum Latency: ") << minLatency;
cout << F(" usec, Avg Latency: ");
cout << totalLatency/N_PRINT << F(" usec\n\n");
}
cout << F("Done!\n\n");
}

View File

@@ -0,0 +1,180 @@
/*
* This program illustrates raw write functions in SdFat that
* can be used for high speed data logging.
*
* This program simulates logging from a source that produces
* data at a constant rate of RATE_KB_PER_SEC.
*
* Note: Apps should create a very large file then truncates it
* to the length that is used for a logging. It only takes
* a few seconds to erase a 500 MB file since the card only
* marks the blocks as erased; no data transfer is required.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#include "FreeStack.h"
// SD chip select pin
const uint8_t chipSelect = SS;
const uint32_t RATE_KB_PER_SEC = 100;
const uint32_t TEST_TIME_SEC = 100;
// Time between printing progress dots
const uint32_t DOT_TIME_MS = 5000UL;
// number of blocks in the contiguous file
const uint32_t BLOCK_COUNT = (1000*RATE_KB_PER_SEC*TEST_TIME_SEC + 511)/512;
// file system
SdFat sd;
// test file
SdFile file;
// file extent
uint32_t bgnBlock, endBlock;
// Serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
}
//------------------------------------------------------------------------------
void loop(void) {
// Read any extra Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
// F stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
cout << F("FreeStack: ") << FreeStack() << endl;
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// delete possible existing file
sd.remove("RawWrite.txt");
// create a contiguous file
if (!file.createContiguous("RawWrite.txt", 512UL*BLOCK_COUNT)) {
error("createContiguous failed");
}
// get the location of the file's blocks
if (!file.contiguousRange(&bgnBlock, &endBlock)) {
error("contiguousRange failed");
}
//*********************NOTE**************************************
// NO SdFile calls are allowed while cache is used for raw writes
//***************************************************************
// clear the cache and use it as a 512 byte buffer
uint8_t* pCache = (uint8_t*)sd.vol()->cacheClear();
// fill cache with eight lines of 64 bytes each
memset(pCache, ' ', 512);
for (uint16_t i = 0; i < 512; i += 64) {
// put line number at end of line then CR/LF
pCache[i + 61] = '0' + (i/64);
pCache[i + 62] = '\r';
pCache[i + 63] = '\n';
}
cout << F("Start raw write of ") << file.fileSize()/1000UL << F(" KB\n");
cout << F("Target rate: ") << RATE_KB_PER_SEC << F(" KB/sec\n");
cout << F("Target time: ") << TEST_TIME_SEC << F(" seconds\n");
// tell card to setup for multiple block write with pre-erase
if (!sd.card()->writeStart(bgnBlock, BLOCK_COUNT)) {
error("writeStart failed");
}
// init stats
delay(1000);
uint32_t dotCount = 0;
uint32_t maxQueuePrint = 0;
uint32_t maxWriteTime = 0;
uint32_t minWriteTime = 9999999;
uint32_t totalWriteTime = 0;
uint32_t maxQueueSize = 0;
uint32_t nWrite = 0;
uint32_t b = 0;
// write data
uint32_t startTime = millis();
while (nWrite < BLOCK_COUNT) {
uint32_t nProduced = RATE_KB_PER_SEC*(millis() - startTime)/512UL;
uint32_t queueSize = nProduced - nWrite;
if (queueSize == 0) continue;
if (queueSize > maxQueueSize) {
maxQueueSize = queueSize;
}
if ((millis() - startTime - dotCount*DOT_TIME_MS) > DOT_TIME_MS) {
if (maxQueueSize != maxQueuePrint) {
cout << F("\nQ: ") << maxQueueSize << endl;
maxQueuePrint = maxQueueSize;
} else {
cout << ".";
if (++dotCount%10 == 0) {
cout << endl;
}
}
}
// put block number at start of first line in block
uint32_t n = b++;
for (int8_t d = 5; d >= 0; d--) {
pCache[d] = n || d == 5 ? n % 10 + '0' : ' ';
n /= 10;
}
// write a 512 byte block
uint32_t tw = micros();
if (!sd.card()->writeData(pCache)) {
error("writeData failed");
}
tw = micros() - tw;
totalWriteTime += tw;
// check for max write time
if (tw > maxWriteTime) {
maxWriteTime = tw;
}
if (tw < minWriteTime) {
minWriteTime = tw;
}
nWrite++;
}
uint32_t endTime = millis();
uint32_t avgWriteTime = totalWriteTime/BLOCK_COUNT;
// end multiple block write mode
if (!sd.card()->writeStop()) {
error("writeStop failed");
}
cout << F("\nDone\n");
cout << F("maxQueueSize: ") << maxQueueSize << endl;
cout << F("Elapsed time: ") << setprecision(3)<< 1.e-3*(endTime - startTime);
cout << F(" seconds\n");
cout << F("Min block write time: ") << minWriteTime << F(" micros\n");
cout << F("Max block write time: ") << maxWriteTime << F(" micros\n");
cout << F("Avg block write time: ") << avgWriteTime << F(" micros\n");
// close file for next pass of loop
file.close();
Serial.println();
}

View File

@@ -0,0 +1,212 @@
// Functions to read a CSV text file one field at a time.
//
#include <limits.h>
#include <SPI.h>
// next line for SD.h
//#include <SD.h>
// next two lines for SdFat
#include <SdFat.h>
SdFat SD;
#define CS_PIN SS
// example can use comma or semicolon
#define CSV_DELIM ','
File file;
/*
* Read a file one field at a time.
*
* file - File to read.
*
* str - Character array for the field.
*
* size - Size of str array.
*
* delim - csv delimiter.
*
* return - negative value for failure.
* delimiter, '\n' or zero(EOF) for success.
*/
int csvReadText(File* file, char* str, size_t size, char delim) {
char ch;
int rtn;
size_t n = 0;
while (true) {
// check for EOF
if (!file->available()) {
rtn = 0;
break;
}
if (file->read(&ch, 1) != 1) {
// read error
rtn = -1;
break;
}
// Delete CR.
if (ch == '\r') {
continue;
}
if (ch == delim || ch == '\n') {
rtn = ch;
break;
}
if ((n + 1) >= size) {
// string too long
rtn = -2;
n--;
break;
}
str[n++] = ch;
}
str[n] = '\0';
return rtn;
}
//------------------------------------------------------------------------------
int csvReadInt32(File* file, int32_t* num, char delim) {
char buf[20];
char* ptr;
int rtn = csvReadText(file, buf, sizeof(buf), delim);
if (rtn < 0) return rtn;
*num = strtol(buf, &ptr, 10);
if (buf == ptr) return -3;
while(isspace(*ptr)) ptr++;
return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadInt16(File* file, int16_t* num, char delim) {
int32_t tmp;
int rtn = csvReadInt32(file, &tmp, delim);
if (rtn < 0) return rtn;
if (tmp < INT_MIN || tmp > INT_MAX) return -5;
*num = tmp;
return rtn;
}
//------------------------------------------------------------------------------
int csvReadUint32(File* file, uint32_t* num, char delim) {
char buf[20];
char* ptr;
int rtn = csvReadText(file, buf, sizeof(buf), delim);
if (rtn < 0) return rtn;
*num = strtoul(buf, &ptr, 10);
if (buf == ptr) return -3;
while(isspace(*ptr)) ptr++;
return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadUint16(File* file, uint16_t* num, char delim) {
uint32_t tmp;
int rtn = csvReadUint32(file, &tmp, delim);
if (rtn < 0) return rtn;
if (tmp > UINT_MAX) return -5;
*num = tmp;
return rtn;
}
//------------------------------------------------------------------------------
int csvReadDouble(File* file, double* num, char delim) {
char buf[20];
char* ptr;
int rtn = csvReadText(file, buf, sizeof(buf), delim);
if (rtn < 0) return rtn;
*num = strtod(buf, &ptr);
if (buf == ptr) return -3;
while(isspace(*ptr)) ptr++;
return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadFloat(File* file, float* num, char delim) {
double tmp;
int rtn = csvReadDouble(file, &tmp, delim);
if (rtn < 0)return rtn;
// could test for too large.
*num = tmp;
return rtn;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
yield();
}
// Initialize the SD.
if (!SD.begin(CS_PIN)) {
Serial.println("begin failed");
return;
}
// Remove existing file.
SD.remove("READTEST.TXT");
// Create the file.
file = SD.open("READTEST.TXT", FILE_WRITE);
if (!file) {
Serial.println("open failed");
return;
}
// Write test data.
file.print(F(
#if CSV_DELIM == ','
"36,23.20,20.70,57.60,79.50,01:08:14,23.06.16\r\n"
"37,23.21,20.71,57.61,79.51,02:08:14,23.07.16\r\n"
#elif CSV_DELIM == ';'
"36;23.20;20.70;57.60;79.50;01:08:14;23.06.16\r\n"
"37;23.21;20.71;57.61;79.51;02:08:14;23.07.16\r\n"
#else
#error "Bad CSV_DELIM"
#endif
));
// Rewind the file for read.
file.seek(0);
// Read the file and print fields.
int16_t tcalc;
float t1, t2, h1, h2;
// Must be dim 9 to allow for zero byte.
char timeS[9], dateS[9];
while (file.available()) {
if (csvReadInt16(&file, &tcalc, CSV_DELIM) != CSV_DELIM
|| csvReadFloat(&file, &t1, CSV_DELIM) != CSV_DELIM
|| csvReadFloat(&file, &t2, CSV_DELIM) != CSV_DELIM
|| csvReadFloat(&file, &h1, CSV_DELIM) != CSV_DELIM
|| csvReadFloat(&file, &h2, CSV_DELIM) != CSV_DELIM
|| csvReadText(&file, timeS, sizeof(timeS), CSV_DELIM) != CSV_DELIM
|| csvReadText(&file, dateS, sizeof(dateS), CSV_DELIM) != '\n') {
Serial.println("read error");
int ch;
int nr = 0;
// print part of file after error.
while ((ch = file.read()) > 0 && nr++ < 100) {
Serial.write(ch);
}
break;
}
Serial.print(tcalc);
Serial.print(CSV_DELIM);
Serial.print(t1);
Serial.print(CSV_DELIM);
Serial.print(t2);
Serial.print(CSV_DELIM);
Serial.print(h1);
Serial.print(CSV_DELIM);
Serial.print(h2);
Serial.print(CSV_DELIM);
Serial.print(timeS);
Serial.print(CSV_DELIM);
Serial.println(dateS);
}
file.close();
}
//------------------------------------------------------------------------------
void loop() {
}

View File

@@ -0,0 +1,139 @@
// Read a two dimensional array from a CSV file.
//
#include <SPI.h>
#include <SdFat.h>
#define CS_PIN SS
// 5 X 4 array
#define ROW_DIM 5
#define COL_DIM 4
SdFat SD;
File file;
/*
* Read a file one field at a time.
*
* file - File to read.
*
* str - Character array for the field.
*
* size - Size of str array.
*
* delim - String containing field delimiters.
*
* return - length of field including terminating delimiter.
*
* Note, the last character of str will not be a delimiter if
* a read error occurs, the field is too long, or the file
* does not end with a delimiter. Consider this an error
* if not at end-of-file.
*
*/
size_t readField(File* file, char* str, size_t size, const char* delim) {
char ch;
size_t n = 0;
while ((n + 1) < size && file->read(&ch, 1) == 1) {
// Delete CR.
if (ch == '\r') {
continue;
}
str[n++] = ch;
if (strchr(delim, ch)) {
break;
}
}
str[n] = '\0';
return n;
}
//------------------------------------------------------------------------------
#define errorHalt(msg) {Serial.println(F(msg)); while (true) {}}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
yield();
}
// Initialize the SD.
if (!SD.begin(CS_PIN)) {
errorHalt("begin failed");
}
// Create or open the file.
file = SD.open("READNUM.TXT", FILE_WRITE);
if (!file) {
errorHalt("open failed");
}
// Rewind file so test data is not appended.
file.rewind();
// Write test data.
file.print(F(
"11,12,13,14\r\n"
"21,22,23,24\r\n"
"31,32,33,34\r\n"
"41,42,43,44\r\n"
"51,52,53,54" // Allow missing endl at eof.
));
// Rewind the file for read.
file.rewind();
// Array for data.
int array[ROW_DIM][COL_DIM];
int i = 0; // First array index.
int j = 0; // Second array index
size_t n; // Length of returned field with delimiter.
char str[20]; // Must hold longest field with delimiter and zero byte.
char *ptr; // Test for valid field.
// Read the file and store the data.
for (i = 0; i < ROW_DIM; i++) {
for (j = 0; j < COL_DIM; j++) {
n = readField(&file, str, sizeof(str), ",\n");
if (n == 0) {
errorHalt("Too few lines");
}
array[i][j] = strtol(str, &ptr, 10);
if (ptr == str) {
errorHalt("bad number");
}
while (*ptr == ' ') {
ptr++;
}
if (*ptr != ',' && *ptr != '\n' && *ptr != '\0') {
errorHalt("extra characters in field");
}
if (j < (COL_DIM-1) && str[n-1] != ',') {
errorHalt("line with too few fields");
}
}
// Allow missing endl at eof.
if (str[n-1] != '\n' && file.available()) {
errorHalt("missing endl");
}
}
// Print the array.
for (i = 0; i < ROW_DIM; i++) {
for (j = 0; j < COL_DIM; j++) {
if (j) {
Serial.print(' ');
}
Serial.print(array[i][j]);
}
Serial.println();
}
Serial.println("Done");
file.close();
}
//------------------------------------------------------------------------------
void loop() {
}

View File

@@ -0,0 +1,121 @@
/*
* This example reads a simple CSV, comma-separated values, file.
* Each line of the file has a label and three values, a long and two floats.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// file system object
SdFat sd;
// create Serial stream
ArduinoOutStream cout(Serial);
char fileName[] = "testfile.csv";
//------------------------------------------------------------------------------
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
// read and print CSV test file
void readFile() {
long lg = 0;
float f1, f2;
char text[10];
char c1, c2, c3; // space for commas.
// open input file
ifstream sdin(fileName);
// check for open error
if (!sdin.is_open()) {
error("open");
}
// read until input fails
while (1) {
// Get text field.
sdin.get(text, sizeof(text), ',');
// Assume EOF if fail.
if (sdin.fail()) {
break;
}
// Get commas and numbers.
sdin >> c1 >> lg >> c2 >> f1 >> c3 >> f2;
// Skip CR/LF.
sdin.skipWhite();
if (sdin.fail()) {
error("bad input");
}
// error in line if not commas
if (c1 != ',' || c2 != ',' || c3 != ',') {
error("comma");
}
// print in six character wide columns
cout << text << setw(6) << lg << setw(6) << f1 << setw(6) << f2 << endl;
}
// Error in an input line if file is not at EOF.
if (!sdin.eof()) {
error("readFile");
}
}
//------------------------------------------------------------------------------
// write test file
void writeFile() {
// create or open and truncate output file
ofstream sdout(fileName);
// write file from string stored in flash
sdout << F(
"Line 1,1,2.3,4.5\n"
"Line 2,6,7.8,9.0\n"
"Line 3,9,8.7,6.5\n"
"Line 4,-4,-3.2,-1\n") << flush;
// check for any errors
if (!sdout) {
error("writeFile");
}
sdout.close();
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// create test file
writeFile();
cout << endl;
// read and print test
readFile();
cout << "\nDone!" << endl;
}
void loop() {}

View File

@@ -0,0 +1,81 @@
/*
SD card read/write
This example shows how to read and write data to and from an SD card file
The circuit:
* SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
created Nov 2010
by David A. Mellis
modified 9 Apr 2012
by Tom Igoe
This example code is in the public domain.
*/
#include <SPI.h>
//#include <SD.h>
#include "SdFat.h"
SdFat SD;
#define SD_CS_PIN SS
File myFile;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.print("Initializing SD card...");
if (!SD.begin(SD_CS_PIN)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
myFile = SD.open("test.txt", FILE_WRITE);
// if the file opened okay, write to it:
if (myFile) {
Serial.print("Writing to test.txt...");
myFile.println("testing 1, 2, 3.");
// close the file:
myFile.close();
Serial.println("done.");
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
// re-open the file for reading:
myFile = SD.open("test.txt");
if (myFile) {
Serial.println("test.txt:");
// read from the file until there's nothing else in it:
while (myFile.available()) {
Serial.write(myFile.read());
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
void loop() {
// nothing happens after setup
}

View File

@@ -0,0 +1,175 @@
/*
* Example use of two SPI ports on an STM32 board.
* Note SPI speed is limited to 18 MHz.
*/
#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
#error See new Version 2 STM32 example
// set ENABLE_EXTENDED_TRANSFER_CLASS non-zero to use faster EX classes
// Use first SPI port
SdFat sd1;
// SdFatEX sd1;
const uint8_t SD1_CS = PA4; // chip select for sd1
// Use second SPI port
SPIClass SPI_2(2);
SdFat sd2(&SPI_2);
// SdFatEX sd2(&SPI_2);
const uint8_t SD2_CS = PB12; // chip select for sd2
const uint8_t BUF_DIM = 100;
uint8_t buf[BUF_DIM];
const uint32_t FILE_SIZE = 1000000;
const uint16_t NWRITE = FILE_SIZE/BUF_DIM;
//------------------------------------------------------------------------------
// print error msg, any SD error codes, and halt.
// store messages in flash
#define errorExit(msg) errorHalt(F(msg))
#define initError(msg) initErrorHalt(F(msg))
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
}
// fill buffer with known data
for (size_t i = 0; i < sizeof(buf); i++) {
buf[i] = i;
}
Serial.println(F("type any character to start"));
while (!Serial.available()) {
}
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
// initialize the first card
if (!sd1.begin(SD1_CS, SD_SCK_MHZ(18))) {
sd1.initError("sd1:");
}
// create Dir1 on sd1 if it does not exist
if (!sd1.exists("/Dir1")) {
if (!sd1.mkdir("/Dir1")) {
sd1.errorExit("sd1.mkdir");
}
}
// initialize the second card
if (!sd2.begin(SD2_CS, SD_SCK_MHZ(18))) {
sd2.initError("sd2:");
}
// create Dir2 on sd2 if it does not exist
if (!sd2.exists("/Dir2")) {
if (!sd2.mkdir("/Dir2")) {
sd2.errorExit("sd2.mkdir");
}
}
// list root directory on both cards
Serial.println(F("------sd1 root-------"));
sd1.ls();
Serial.println(F("------sd2 root-------"));
sd2.ls();
// make /Dir1 the default directory for sd1
if (!sd1.chdir("/Dir1")) {
sd1.errorExit("sd1.chdir");
}
// remove test.bin from /Dir1 directory of sd1
if (sd1.exists("test.bin")) {
if (!sd1.remove("test.bin")) {
sd2.errorExit("remove test.bin");
}
}
// make /Dir2 the default directory for sd2
if (!sd2.chdir("/Dir2")) {
sd2.errorExit("sd2.chdir");
}
// remove rename.bin from /Dir2 directory of sd2
if (sd2.exists("rename.bin")) {
if (!sd2.remove("rename.bin")) {
sd2.errorExit("remove rename.bin");
}
}
// list current directory on both cards
Serial.println(F("------sd1 Dir1-------"));
sd1.ls();
Serial.println(F("------sd2 Dir2-------"));
sd2.ls();
Serial.println(F("---------------------"));
// set the current working directory for open() to sd1
sd1.chvol();
// create or open /Dir1/test.bin and truncate it to zero length
SdFile file1;
if (!file1.open("test.bin", O_RDWR | O_CREAT | O_TRUNC)) {
sd1.errorExit("file1");
}
Serial.println(F("Writing test.bin to sd1"));
// write data to /Dir1/test.bin on sd1
for (uint16_t i = 0; i < NWRITE; i++) {
if (file1.write(buf, sizeof(buf)) != sizeof(buf)) {
sd1.errorExit("sd1.write");
}
}
// set the current working directory for open() to sd2
sd2.chvol();
// create or open /Dir2/copy.bin and truncate it to zero length
SdFile file2;
if (!file2.open("copy.bin", O_WRONLY | O_CREAT | O_TRUNC)) {
sd2.errorExit("file2");
}
Serial.println(F("Copying test.bin to copy.bin"));
// copy file1 to file2
file1.rewind();
uint32_t t = millis();
while (1) {
int n = file1.read(buf, sizeof(buf));
if (n < 0) {
sd1.errorExit("read1");
}
if (n == 0) {
break;
}
if ((int)file2.write(buf, n) != n) {
sd2.errorExit("write2");
}
}
t = millis() - t;
Serial.print(F("File size: "));
Serial.println(file2.fileSize());
Serial.print(F("Copy time: "));
Serial.print(t);
Serial.println(F(" millis"));
// close test.bin
file1.close();
file2.close();
// list current directory on both cards
Serial.println(F("------sd1 -------"));
sd1.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("------sd2 -------"));
sd2.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("---------------------"));
Serial.println(F("Renaming copy.bin"));
// rename the copy
if (!sd2.rename("copy.bin", "rename.bin")) {
sd2.errorExit("sd2.rename");
}
// list current directory on both cards
Serial.println(F("------sd1 -------"));
sd1.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("------sd2 -------"));
sd2.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("---------------------"));
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,215 @@
// Benchmark comparing SdFile and StdioStream.
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// Define PRINT_FIELD nonzero to use printField.
#define PRINT_FIELD 0
// Number of lines to list on Serial.
#define STDIO_LIST_COUNT 0
#define VERIFY_CONTENT 0
const uint8_t SD_CS_PIN = SS;
SdFat sd;
SdFile printFile;
StdioStream stdioFile;
float f[100];
char buf[20];
const char* label[] =
{ "uint8_t 0 to 255, 100 times ", "uint16_t 0 to 20000",
"uint32_t 0 to 20000", "uint32_t 1000000000 to 1000010000",
"float nnn.ffff, 10000 times"
};
//------------------------------------------------------------------------------
void setup() {
uint32_t printSize = 0;
uint32_t stdioSize = 0;
uint32_t printTime = 0;
uint32_t stdioTime = 0;
Serial.begin(9600);
while (!Serial) {
yield();
}
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
yield();
}
Serial.println(F("Starting test"));
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
sd.errorHalt();
}
for (uint8_t i = 0; i < 100; i++) {
f[i] = 123.0 + 0.1234*i;
}
for (uint8_t dataType = 0; dataType < 5; dataType++) {
for (uint8_t fileType = 0; fileType < 2; fileType++) {
if (!fileType) {
if (!printFile.open("print.txt", O_RDWR | O_CREAT | O_TRUNC)) {
Serial.println(F("open fail"));
return;
}
printTime = millis();
switch (dataType) {
case 0:
for (uint16_t i =0; i < 100; i++) {
for (uint8_t j = 0; j < 255; j++) {
printFile.println(j);
}
}
break;
case 1:
for (uint16_t i = 0; i < 20000; i++) {
printFile.println(i);
}
break;
case 2:
for (uint32_t i = 0; i < 20000; i++) {
printFile.println(i);
}
break;
case 3:
for (uint16_t i = 0; i < 10000; i++) {
printFile.println(i + 1000000000UL);
}
break;
case 4:
for (int j = 0; j < 100; j++) {
for (uint8_t i = 0; i < 100; i++) {
printFile.println(f[i], 4);
}
}
break;
default:
break;
}
printFile.sync();
printTime = millis() - printTime;
printFile.rewind();
printSize = printFile.fileSize();
} else {
if (!stdioFile.fopen("stream.txt", "w+")) {
Serial.println(F("fopen fail"));
return;
}
stdioTime = millis();
switch (dataType) {
case 0:
for (uint16_t i =0; i < 100; i++) {
for (uint8_t j = 0; j < 255; j++) {
#if PRINT_FIELD
stdioFile.printField(j, '\n');
#else // PRINT_FIELD
stdioFile.println(j);
#endif // PRINT_FIELD
}
}
break;
case 1:
for (uint16_t i = 0; i < 20000; i++) {
#if PRINT_FIELD
stdioFile.printField(i, '\n');
#else // PRINT_FIELD
stdioFile.println(i);
#endif // PRINT_FIELD
}
break;
case 2:
for (uint32_t i = 0; i < 20000; i++) {
#if PRINT_FIELD
stdioFile.printField(i, '\n');
#else // PRINT_FIELD
stdioFile.println(i);
#endif // PRINT_FIELD
}
break;
case 3:
for (uint16_t i = 0; i < 10000; i++) {
uint32_t n = i + 1000000000UL;
#if PRINT_FIELD
stdioFile.printField(n, '\n');
#else // PRINT_FIELD
stdioFile.println(n);
#endif // PRINT_FIELD
}
break;
case 4:
for (int j = 0; j < 100; j++) {
for (uint8_t i = 0; i < 100; i++) {
#if PRINT_FIELD
stdioFile.printField(f[i], '\n', 4);
#else // PRINT_FIELD
stdioFile.println(f[i], 4);
#endif // PRINT_FIELD
}
}
break;
default:
break;
}
stdioFile.fflush();
stdioTime = millis() - stdioTime;
stdioSize = stdioFile.ftell();
if (STDIO_LIST_COUNT) {
size_t len;
stdioFile.rewind();
for (int i = 0; i < STDIO_LIST_COUNT; i++) {
stdioFile.fgets(buf, sizeof(buf), &len);
Serial.print(len);
Serial.print(',');
Serial.print(buf);
}
}
}
}
Serial.println(label[dataType]);
if (VERIFY_CONTENT && printSize == stdioSize) {
printFile.rewind();
stdioFile.rewind();
for (uint32_t i = 0; i < stdioSize; i++) {
if (printFile.read() != stdioFile.getc()) {
Serial.print(F("Files differ at pos: "));
Serial.println(i);
return;
}
}
}
Serial.print(F("fileSize: "));
if (printSize != stdioSize) {
Serial.print(printSize);
Serial.print(F(" != "));
}
Serial.println(stdioSize);
Serial.print(F("print millis: "));
Serial.println(printTime);
Serial.print(F("stdio millis: "));
Serial.println(stdioTime);
Serial.print(F("ratio: "));
Serial.println((float)printTime/(float)stdioTime);
Serial.println();
printFile.close();
stdioFile.fclose();
}
Serial.println(F("Done"));
}
void loop() {}

View File

@@ -0,0 +1,162 @@
/*
* This program tests the dateTimeCallback() function
* and the timestamp() function.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
SdFat sd;
SdFile file;
// Default SD chip select is SS pin
const uint8_t chipSelect = SS;
// create Serial stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// store error strings in flash to save RAM
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
/*
* date/time values for debug
* normally supplied by a real-time clock or GPS
*/
// date 1-Oct-14
uint16_t year = 2014;
uint8_t month = 10;
uint8_t day = 1;
// time 20:30:40
uint8_t hour = 20;
uint8_t minute = 30;
uint8_t second = 40;
//------------------------------------------------------------------------------
/*
* User provided date time callback function.
* See SdFile::dateTimeCallback() for usage.
*/
void dateTime(uint16_t* date, uint16_t* time) {
// User gets date and time from GPS or real-time
// clock in real callback function
// return date using FAT_DATE macro to format fields
*date = FAT_DATE(year, month, day);
// return time using FAT_TIME macro to format fields
*time = FAT_TIME(hour, minute, second);
}
//------------------------------------------------------------------------------
/*
* Function to print all timestamps.
*/
void printTimestamps(SdFile& f) {
cout << F("Creation: ");
f.printCreateDateTime(&Serial);
cout << endl << F("Modify: ");
f.printModifyDateTime(&Serial);
cout << endl << F("Access: ");
f.printAccessDateTime(&Serial);
cout << endl;
}
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// remove files if they exist
sd.remove("callback.txt");
sd.remove("default.txt");
sd.remove("stamp.txt");
// create a new file with default timestamps
if (!file.open("default.txt", O_WRONLY | O_CREAT)) {
error("open default.txt failed");
}
cout << F("\nOpen with default times\n");
printTimestamps(file);
// close file
file.close();
/*
* Test the date time callback function.
*
* dateTimeCallback() sets the function
* that is called when a file is created
* or when a file's directory entry is
* modified by sync().
*
* The callback can be disabled by the call
* SdFile::dateTimeCallbackCancel()
*/
// set date time callback function
SdFile::dateTimeCallback(dateTime);
// create a new file with callback timestamps
if (!file.open("callback.txt", O_WRONLY | O_CREAT)) {
error("open callback.txt failed");
}
cout << ("\nOpen with callback times\n");
printTimestamps(file);
// change call back date
day += 1;
// must add two to see change since FAT second field is 5-bits
second += 2;
// modify file by writing a byte
file.write('t');
// force dir update
file.sync();
cout << F("\nTimes after write\n");
printTimestamps(file);
// close file
file.close();
/*
* Test timestamp() function
*
* Cancel callback so sync will not
* change access/modify timestamp
*/
SdFile::dateTimeCallbackCancel();
// create a new file with default timestamps
if (!file.open("stamp.txt", O_WRONLY | O_CREAT)) {
error("open stamp.txt failed");
}
// set creation date time
if (!file.timestamp(T_CREATE, 2014, 11, 10, 1, 2, 3)) {
error("set create time failed");
}
// set write/modification date time
if (!file.timestamp(T_WRITE, 2014, 11, 11, 4, 5, 6)) {
error("set write time failed");
}
// set access date
if (!file.timestamp(T_ACCESS, 2014, 11, 12, 7, 8, 9)) {
error("set access time failed");
}
cout << F("\nTimes after timestamp() calls\n");
printTimestamps(file);
file.close();
cout << F("\nDone\n");
}
void loop() {}

View File

@@ -0,0 +1,170 @@
/*
* Warning This example requires extra RAM and may crash on Uno.
* Example use of two SD cards.
*/
#include <SPI.h>
#include "SdFat.h"
#include "FreeStack.h"
SdFat sd1;
const uint8_t SD1_CS = 10; // chip select for sd1
SdFat sd2;
const uint8_t SD2_CS = 4; // chip select for sd2
const uint8_t BUF_DIM = 100;
uint8_t buf[BUF_DIM];
const uint32_t FILE_SIZE = 1000000;
const uint32_t NWRITE = FILE_SIZE/BUF_DIM;
//------------------------------------------------------------------------------
// print error msg, any SD error codes, and halt.
// store messages in flash
#define errorExit(msg) errorHalt(F(msg))
#define initError(msg) initErrorHalt(F(msg))
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.print(F("FreeStack: "));
Serial.println(FreeStack());
// fill buffer with known data
for (size_t i = 0; i < sizeof(buf); i++) {
buf[i] = i;
}
Serial.println(F("type any character to start"));
while (!Serial.available()) {
yield();
}
// disable sd2 while initializing sd1
pinMode(SD2_CS, OUTPUT);
digitalWrite(SD2_CS, HIGH);
// initialize the first card
if (!sd1.begin(SD1_CS)) {
sd1.initError("sd1:");
}
// create Dir1 on sd1 if it does not exist
if (!sd1.exists("/Dir1")) {
if (!sd1.mkdir("/Dir1")) {
sd1.errorExit("sd1.mkdir");
}
}
// initialize the second card
if (!sd2.begin(SD2_CS)) {
sd2.initError("sd2:");
}
// create Dir2 on sd2 if it does not exist
if (!sd2.exists("/Dir2")) {
if (!sd2.mkdir("/Dir2")) {
sd2.errorExit("sd2.mkdir");
}
}
// list root directory on both cards
Serial.println(F("------sd1 root-------"));
sd1.ls();
Serial.println(F("------sd2 root-------"));
sd2.ls();
// make /Dir1 the default directory for sd1
if (!sd1.chdir("/Dir1")) {
sd1.errorExit("sd1.chdir");
}
// make /Dir2 the default directory for sd2
if (!sd2.chdir("/Dir2")) {
sd2.errorExit("sd2.chdir");
}
// list current directory on both cards
Serial.println(F("------sd1 Dir1-------"));
sd1.ls();
Serial.println(F("------sd2 Dir2-------"));
sd2.ls();
Serial.println(F("---------------------"));
// remove rename.bin from /Dir2 directory of sd2
if (sd2.exists("rename.bin")) {
if (!sd2.remove("rename.bin")) {
sd2.errorExit("remove rename.bin");
}
}
// set the current working directory for open() to sd1
sd1.chvol();
// create or open /Dir1/test.bin and truncate it to zero length
SdFile file1;
if (!file1.open("test.bin", O_RDWR | O_CREAT | O_TRUNC)) {
sd1.errorExit("file1");
}
Serial.println(F("Writing test.bin to sd1"));
// write data to /Dir1/test.bin on sd1
for (uint32_t i = 0; i < NWRITE; i++) {
if (file1.write(buf, sizeof(buf)) != sizeof(buf)) {
sd1.errorExit("sd1.write");
}
}
// set the current working directory for open() to sd2
sd2.chvol();
// create or open /Dir2/copy.bin and truncate it to zero length
SdFile file2;
if (!file2.open("copy.bin", O_WRONLY | O_CREAT | O_TRUNC)) {
sd2.errorExit("file2");
}
Serial.println(F("Copying test.bin to copy.bin"));
// copy file1 to file2
file1.rewind();
uint32_t t = millis();
while (1) {
int n = file1.read(buf, sizeof(buf));
if (n < 0) {
sd1.errorExit("read1");
}
if (n == 0) {
break;
}
if ((int)file2.write(buf, n) != n) {
sd2.errorExit("write2");
}
}
t = millis() - t;
Serial.print(F("File size: "));
Serial.println(file2.fileSize());
Serial.print(F("Copy time: "));
Serial.print(t);
Serial.println(F(" millis"));
// close test.bin
file1.close();
file2.close();
// list current directory on both cards
Serial.println(F("------sd1 -------"));
sd1.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("------sd2 -------"));
sd2.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("---------------------"));
Serial.println(F("Renaming copy.bin"));
// rename the copy
if (!sd2.rename("copy.bin", "rename.bin")) {
sd2.errorExit("sd2.rename");
}
// list current directory on both cards
Serial.println(F("------sd1 -------"));
sd1.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("------sd2 -------"));
sd2.ls("/", LS_R | LS_DATE | LS_SIZE);
Serial.println(F("---------------------"));
Serial.println(F("Done"));
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,82 @@
/*
* This program demonstrates the freeClusterCount() call.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
/*
* SD chip select pin. Common values are:
*
* Arduino Ethernet shield, pin 4.
* SparkFun SD shield, pin 8.
* Adafruit Datalogging shield, pin 10.
* Default SD chip select is the SPI SS pin.
*/
const uint8_t chipSelect = SS;
#define TEST_FILE "Cluster.test"
// file system
SdFat sd;
// test file
SdFile file;
// Serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void printFreeSpace() {
cout << F("freeClusterCount() call time: ");
uint32_t m = micros();
uint32_t volFree = sd.vol()->freeClusterCount();
cout << micros() - m << F(" micros\n");
cout << F("freeClusters: ") << volFree << setprecision(3) << endl;
float fs = 0.000512*volFree*sd.vol()->blocksPerCluster();
cout << F("freeSpace: ") << fs << F(" MB (MB = 1,000,000 bytes)\n\n");
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
if (!MAINTAIN_FREE_CLUSTER_COUNT) {
cout << F("Please edit SdFatConfig.h and set\n");
cout << F("MAINTAIN_FREE_CLUSTER_COUNT nonzero for\n");
cout << F("maximum freeClusterCount() performance.\n\n");
}
// F stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// Insure no TEST_FILE.
sd.remove(TEST_FILE);
cout << F("\nFirst call to freeClusterCount scans the FAT.\n\n");
printFreeSpace();
cout << F("Create and write to ") << TEST_FILE << endl;
if (!file.open(TEST_FILE, O_WRONLY | O_CREAT)) {
sd.errorHalt(F("Create failed"));
}
file.print(F("Cause a cluster to be allocated"));
file.close();
cout << F("\nSecond freeClusterCount call is faster if\n");
cout << F("MAINTAIN_FREE_CLUSTER_COUNT is nonzero.\n\n");
printFreeSpace();
cout << F("Remove ") << TEST_FILE << endl << endl;
sd.remove(TEST_FILE);
printFreeSpace();
cout << F("Done") << endl;
}
//------------------------------------------------------------------------------
void loop() {}

View File

@@ -0,0 +1,150 @@
/*
* Simple data logger.
*/
#include <SPI.h>
#include "SdFat.h"
// SD chip select pin. Be sure to disable any other SPI devices such as Enet.
const uint8_t chipSelect = SS;
// Interval between data records in milliseconds.
// The interval must be greater than the maximum SD write latency plus the
// time to acquire and write data to the SD to avoid overrun errors.
// Run the bench example to check the quality of your SD card.
const uint32_t SAMPLE_INTERVAL_MS = 1000;
// Log file base name. Must be six characters or less.
#define FILE_BASE_NAME "Data"
//------------------------------------------------------------------------------
// File system object.
SdFat sd;
// Log file.
SdFile file;
// Time in micros for next data record.
uint32_t logTime;
//==============================================================================
// User functions. Edit writeHeader() and logData() for your requirements.
const uint8_t ANALOG_COUNT = 4;
//------------------------------------------------------------------------------
// Write data header.
void writeHeader() {
file.print(F("micros"));
for (uint8_t i = 0; i < ANALOG_COUNT; i++) {
file.print(F(",adc"));
file.print(i, DEC);
}
file.println();
}
//------------------------------------------------------------------------------
// Log a data record.
void logData() {
uint16_t data[ANALOG_COUNT];
// Read all channels to avoid SD write latency between readings.
for (uint8_t i = 0; i < ANALOG_COUNT; i++) {
data[i] = analogRead(i);
}
// Write data to file. Start with log time in micros.
file.print(logTime);
// Write ADC data to CSV record.
for (uint8_t i = 0; i < ANALOG_COUNT; i++) {
file.write(',');
file.print(data[i]);
}
file.println();
}
//==============================================================================
// Error messages stored in flash.
#define error(msg) sd.errorHalt(F(msg))
//------------------------------------------------------------------------------
void setup() {
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[13] = FILE_BASE_NAME "00.csv";
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(1000);
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// Find an unused file name.
if (BASE_NAME_SIZE > 6) {
error("FILE_BASE_NAME too long");
}
while (sd.exists(fileName)) {
if (fileName[BASE_NAME_SIZE + 1] != '9') {
fileName[BASE_NAME_SIZE + 1]++;
} else if (fileName[BASE_NAME_SIZE] != '9') {
fileName[BASE_NAME_SIZE + 1] = '0';
fileName[BASE_NAME_SIZE]++;
} else {
error("Can't create file name");
}
}
if (!file.open(fileName, O_WRONLY | O_CREAT | O_EXCL)) {
error("file.open");
}
// Read any Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
Serial.print(F("Logging to: "));
Serial.println(fileName);
Serial.println(F("Type any character to stop"));
// Write data header.
writeHeader();
// Start on a multiple of the sample interval.
logTime = micros()/(1000UL*SAMPLE_INTERVAL_MS) + 1;
logTime *= 1000UL*SAMPLE_INTERVAL_MS;
}
//------------------------------------------------------------------------------
void loop() {
// Time for next record.
logTime += 1000UL*SAMPLE_INTERVAL_MS;
// Wait for log time.
int32_t diff;
do {
diff = micros() - logTime;
} while (diff < 0);
// Check for data rate too high.
if (diff > 10) {
error("Missed data record");
}
logData();
// Force data to SD and update the directory entry to avoid data loss.
if (!file.sync() || file.getWriteError()) {
error("write error");
}
if (Serial.available()) {
// Close file and stop.
file.close();
Serial.println(F("Done"));
while (true) {}
}
}

View File

@@ -0,0 +1,88 @@
// Demo of fgets function to read lines from a file.
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
SdFat sd;
// print stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// store error strings in flash memory
#define error(s) sd.errorHalt(F(s))
//------------------------------------------------------------------------------
void demoFgets() {
char line[25];
int n;
// open test file
SdFile rdfile("fgets.txt", O_RDONLY);
// check for open error
if (!rdfile.isOpen()) {
error("demoFgets");
}
cout << endl << F(
"Lines with '>' end with a '\\n' character\n"
"Lines with '#' do not end with a '\\n' character\n"
"\n");
// read lines from the file
while ((n = rdfile.fgets(line, sizeof(line))) > 0) {
if (line[n - 1] == '\n') {
cout << '>' << line;
} else {
cout << '#' << line << endl;
}
}
}
//------------------------------------------------------------------------------
void makeTestFile() {
// create or open test file
SdFile wrfile("fgets.txt", O_WRONLY | O_CREAT | O_TRUNC);
// check for open error
if (!wrfile.isOpen()) {
error("MakeTestFile");
}
// write test file
wrfile.print(F(
"Line with CRLF\r\n"
"Line with only LF\n"
"Long line that will require an extra read\n"
"\n" // empty line
"Line at EOF without NL"
));
wrfile.close();
}
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
delay(400); // catch Due reset problem
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
makeTestFile();
demoFgets();
cout << F("\nDone\n");
}
void loop(void) {}

View File

@@ -0,0 +1,73 @@
/*
* Print a table with various formatting options
* Format dates
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// create Serial stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// print a table to demonstrate format manipulators
void example(void) {
const int max = 10;
const int width = 4;
for (int row = 1; row <= max; row++) {
for (int col = 1; col <= max; col++) {
cout << setw(width) << row * col << (col == max ? '\n' : ' ');
}
}
cout << endl;
}
//------------------------------------------------------------------------------
// print a date as mm/dd/yyyy with zero fill in mm and dd
// shows how to set and restore the fill character
void showDate(int m, int d, int y) {
// convert two digit year
if (y < 100) {
y += 2000;
}
// set new fill to '0' save old fill character
char old = cout.fill('0');
// print date
cout << setw(2) << m << '/' << setw(2) << d << '/' << y << endl;
// restore old fill character
cout.fill(old);
}
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
delay(2000);
cout << endl << "default formatting" << endl;
example();
cout << showpos << "showpos" << endl;
example();
cout << hex << left << showbase << "hex left showbase" << endl;
example();
cout << internal << setfill('0') << uppercase;
cout << "uppercase hex internal showbase fill('0')" <<endl;
example();
// restore default format flags and fill character
cout.flags(ios::dec | ios::right | ios::skipws);
cout.fill(' ');
cout << "showDate example" <<endl;
showDate(7, 4, 11);
showDate(12, 25, 11);
}
void loop(void) {}

View File

@@ -0,0 +1,84 @@
/*
* Example of getline from section 27.7.1.3 of the C++ standard
* Demonstrates the behavior of getline for various exceptions.
* See http://www.cplusplus.com/reference/iostream/istream/getline/
*
* Note: This example is meant to demonstrate subtleties the standard and
* may not the best way to read a file.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
// SD chip select pin
const uint8_t chipSelect = SS;
// file system object
SdFat sd;
// create a serial stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
void makeTestFile() {
ofstream sdout("getline.txt");
// use flash for text to save RAM
sdout << F(
"short line\n"
"\n"
"17 character line\n"
"too long for buffer\n"
"line with no nl");
sdout.close();
}
//------------------------------------------------------------------------------
void testGetline() {
const int line_buffer_size = 18;
char buffer[line_buffer_size];
ifstream sdin("getline.txt");
int line_number = 0;
while (sdin.getline(buffer, line_buffer_size, '\n') || sdin.gcount()) {
int count = sdin.gcount();
if (sdin.fail()) {
cout << "Partial long line";
sdin.clear(sdin.rdstate() & ~ios_base::failbit);
} else if (sdin.eof()) {
cout << "Partial final line"; // sdin.fail() is false
} else {
count--; // Dont include newline in count
cout << "Line " << ++line_number;
}
cout << " (" << count << " chars): " << buffer << endl;
}
}
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
// F stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// make the test file
makeTestFile();
// run the example
testGetline();
cout << "\nDone!\n";
}
//------------------------------------------------------------------------------
void loop(void) {}

View File

@@ -0,0 +1,43 @@
// Example to wipe all data from an already formatted SD.
#error wipe is not supported in SdFat V2. Use bool format(print_t* pr = nullptr).
#include <SPI.h>
#include "SdFat.h"
const int chipSelect = SS;
SdFat sd;
void setup() {
int c;
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
yield();
}
Serial.println("Type 'Y' to wipe all data.");
while (!Serial.available()) {
yield();
}
c = Serial.read();
if (c != 'Y') {
sd.errorHalt("Quitting, you did not type 'Y'.");
}
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.initErrorHalt();
}
// Use wipe() for no dot progress indicator.
if (!sd.wipe(&Serial)) {
sd.errorHalt("Wipe failed.");
}
// Must reinitialize after wipe.
// Initialize at the highest speed supported by the board that is
// not over 50 MHz. Try a lower speed if SPI errors occur.
if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
sd.errorHalt("Second init failed.");
}
Serial.println("Done");
}
void loop() {
}

Some files were not shown because too many files have changed in this diff Show More