Fix rmdir does not remove not empty directory

Remove SDFat V1 support as not necessary now have SDFat 2.x
Remove SPIFFS esp8266 support as now removed from core
Bump version
Remove outdated test scripts
This commit is contained in:
Luc 2022-08-23 15:57:56 +08:00
parent 159b9e6f47
commit ee18988a83
1516 changed files with 144 additions and 296870 deletions

View File

@ -10,7 +10,8 @@
"unordered_set": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp"
"initializer_list": "cpp",
"typeinfo": "cpp"
},
"cmake.configureOnOpen": false
}

View File

@ -245,7 +245,6 @@
/* SD card library
* ESP_SD_NATIVE //esp32 / esp8266
* ESP_SDIO //esp32 only
* ESP_SDFAT //esp8266 / esp32
* ESP_SDFAT2 //esp8266 / esp32
*/
//#define SD_DEVICE ESP_SDFAT2

View File

@ -200,8 +200,7 @@
//SD READER FS type supported
#define ESP_SD_NATIVE 1
#define ESP_SDIO 2
#define ESP_SDFAT 3
#define ESP_SDFAT2 4
#define ESP_SDFAT2 3
//SDIO Mode
#define SD_ONE_BIT_MODE 1

View File

@ -22,7 +22,7 @@
#define _VERSION_ESP3D_H
//version and sources location
#define FW_VERSION "3.0.0.a208"
#define FW_VERSION "3.0.0.a209"
#define REPOSITORY "https://github.com/luc-github/ESP3D/tree/3.0"
#endif //_VERSION_ESP3D_H

View File

@ -143,35 +143,39 @@ bool ESP_FileSystem::mkdir(const char *path)
bool ESP_FileSystem::rmdir(const char *path)
{
String p = path;
if(p[0]!='/') {
p="/"+p;
if (!p.startsWith("/")) {
p = '/'+p;
}
if (p[p.length()-1] == '/') {
if (p!="/") {
if (p!= "/") {
if (p.endsWith("/")) {
p.remove(p.length()-1);
}
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String > pathlist;
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
File dir = FFat.open(pathlist.top().c_str());
File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDirectory()) {
candelete = false;
String newdir = f.name();
String newdir = pathlist.top()+ '/';
newdir+= f.name();
pathlist.push(newdir);
f.close();
f = File();
} else {
FFat.remove(f.name());
String filepath = pathlist.top()+ '/';
filepath+= f.name();
f.close();
if (!FFat.remove(filepath.c_str())) {
res = false;
}
f = dir.openNextFile();
}
}

View File

@ -150,35 +150,39 @@ bool ESP_FileSystem::mkdir(const char *path)
bool ESP_FileSystem::rmdir(const char *path)
{
String p = path;
if(p[0]!='/') {
p="/"+p;
if (!p.startsWith("/")) {
p = '/'+p;
}
if (p[p.length()-1] == '/') {
if (p!="/") {
if (p!= "/") {
if (p.endsWith("/")) {
p.remove(p.length()-1);
}
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String> pathlist;
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
File dir = LittleFS.open(pathlist.top().c_str());
File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDirectory()) {
candelete = false;
String newdir = f.name();
String newdir = pathlist.top()+ '/';
newdir+= f.name();
pathlist.push(newdir);
f.close();
f = File();
} else {
LittleFS.remove(f.name());
String filepath = pathlist.top()+ '/';
filepath+= f.name();
f.close();
if (!LittleFS.remove(filepath.c_str())) {
res = false;
}
f = dir.openNextFile();
}
}

View File

@ -153,46 +153,40 @@ bool ESP_FileSystem::mkdir(const char *path)
bool ESP_FileSystem::rmdir(const char *path)
{
if (!exists(path)) {
String p = path;
if (!p.endsWith("/")) {
p+= '/';
}
if (!p.startsWith("/")) {
p = '/'+p;
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String> pathlist;
String spath = path;
spath.trim();
if (spath[spath.length()-1] != '/') {
spath+="/";
}
if (spath[0] != '/') {
spath ="/" + spath;
}
pathlist.push(spath);
pathlist.push(p);
while (pathlist.size() > 0) {
spath=pathlist.top();
bool candelete = true;
if (LittleFS.exists(spath.c_str())) {
Dir dir = LittleFS.openDir(pathlist.top().c_str());
while (dir.next()) {
if (dir.isDirectory()) {
candelete = false;
String newdir = pathlist.top() + dir.fileName() + "/";
pathlist.push(newdir);
} else {
log_esp3d("remove %s", dir.fileName().c_str());
String s = spath + dir.fileName();
LittleFS.remove(s);
}
Dir dir = LittleFS.openDir(pathlist.top().c_str());
while (dir.next()) {
if (dir.isDirectory()) {
candelete = false;
String newdir = pathlist.top() + dir.fileName() + "/";
pathlist.push(newdir);
} else {
String filepath = pathlist.top()+ '/';
filepath+= dir.fileName();
log_esp3d("remove %s", filepath.c_str());
LittleFS.remove(filepath.c_str());
}
}
if (candelete) {
if (spath !="/") {
if (spath[spath.length()-1] == '/') {
spath.remove(spath.length()-1);
if (pathlist.top() !="/") {
if (LittleFS.exists(pathlist.top().c_str())) {
res = LittleFS.rmdir(pathlist.top().c_str());
}
if (LittleFS.exists(spath.c_str())) {
res = LittleFS.rmdir(spath.c_str());
}
log_esp3d("rmdir %s %d", spath.c_str(), res);
}
pathlist.pop();
}

View File

@ -168,11 +168,11 @@ bool ESP_FileSystem::rmdir(const char *path)
{
String spath = path;
spath.trim();
if(spath[0]!='/') {
spath="/"+spath;
if (!spath.startsWith("/")) {
spath = '/'+spath;
}
if (spath[spath.length()-1] == '/') {
if (spath!="/") {
if (spath!= "/") {
if (spath.endsWith("/")) {
spath.remove(spath.length()-1);
}
}

View File

@ -1,389 +0,0 @@
/*
spiffs_8266_filesystem.cpp - ESP3D filesystem configuration class
Copyright (c) 2014 Luc Lebosse. All rights reserved.
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with This code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../../../include/esp3d_config.h"
#if (FILESYSTEM_FEATURE == ESP_SPIFFS_FILESYSTEM) && defined (ARDUINO_ARCH_ESP8266)
#include "../esp_filesystem.h"
#include <FS.h>
Dir tDir_handle[ESP_MAX_OPENHANDLE];
extern File tFile_handle[ESP_MAX_OPENHANDLE];
bool ESP_FileSystem::begin()
{
_started = SPIFFS.begin();
return _started;
}
void ESP_FileSystem::end()
{
_started = false;
SPIFFS.end();
}
size_t ESP_FileSystem::freeBytes()
{
return totalBytes() - usedBytes();
}
size_t ESP_FileSystem::totalBytes()
{
fs::FSInfo info;
SPIFFS.info (info);
return info.totalBytes;
}
size_t ESP_FileSystem::usedBytes()
{
fs::FSInfo info;
SPIFFS.info (info);
return info.usedBytes;
}
uint ESP_FileSystem::maxPathLength()
{
return 32;
}
bool ESP_FileSystem::rename(const char *oldpath, const char *newpath)
{
return SPIFFS.rename(oldpath,newpath);
}
const char * ESP_FileSystem::FilesystemName()
{
return "SPIFFS";
}
bool ESP_FileSystem::format()
{
bool res = SPIFFS.format();
if (res) {
res = begin();
}
return res;
}
ESP_File ESP_FileSystem::open(const char* path, uint8_t mode)
{
//do some check
if(((strcmp(path,"/") == 0) && ((mode == ESP_FILE_WRITE) || (mode == ESP_FILE_APPEND))) || (strlen(path) == 0)) {
return ESP_File();
}
// path must start by '/'
if (path[0] != '/') {
return ESP_File();
}
File ftmp = SPIFFS.open(path, (mode == ESP_FILE_READ)?"r":(mode == ESP_FILE_WRITE)?"w":"a");
if(ftmp) {
log_esp3d("Success openening file: %s", path);
ESP_File esptmp(&ftmp, false,(mode == ESP_FILE_READ)?false:true, path);
return esptmp;
}
(void)mode;
Dir dtmp = SPIFFS.openDir(path);
ESP_File esptmp(&dtmp, true, false, path);
log_esp3d("Success openening dir: %s", path);
return esptmp;
}
bool ESP_FileSystem::exists(const char* path)
{
bool res = false;
//root should always be there if started
if (strcmp(path, "/") == 0) {
return _started;
}
String spath = path;
spath.trim();
if (spath[spath.length()-1] == '/') {
if (spath!="/") {
spath.remove(spath.length()-1);
}
}
res = SPIFFS.exists(spath.c_str());
if (!res) {
String newpath = spath;
if (newpath[newpath.length()-1] != '/') {
newpath+="/";
}
newpath+=".";
log_esp3d("Check %s", newpath.c_str());
res = SPIFFS.exists(newpath);
if (!res) {
ESP_File f = ESP_FileSystem::open(path, ESP_FILE_READ);
if (f) {
//Check directories
ESP_File sub = f.openNextFile();
if (sub) {
sub.close();
res = true;
}
f.close();
}
}
}
return res;
}
bool ESP_FileSystem::remove(const char *path)
{
String p = path;
if(p[0]!='/') {
p="/"+p;
}
return SPIFFS.remove(p);
}
bool ESP_FileSystem::mkdir(const char *path)
{
//Use file named . to simulate directory
String p = path;
if (p[p.length()-1] != '/') {
p+="/";
}
p+=".";
log_esp3d("Dir create : %s", p.c_str());
ESP_File f = open(p.c_str(), ESP_FILE_WRITE);
if (f) {
f.close();
return true;
} else {
return false;
}
}
bool ESP_FileSystem::rmdir(const char *path)
{
Dir dtmp = SPIFFS.openDir(path);
log_esp3d("Deleting : %s",path);
while (dtmp.next()) {
if (!SPIFFS.remove(dtmp.fileName().c_str())) {
return false;
}
}
return true;
}
void ESP_FileSystem::closeAll()
{
for (uint8_t i = 0; i < ESP_MAX_OPENHANDLE; i++) {
tDir_handle[i] = Dir();
tFile_handle[i].close();
tFile_handle[i] = File();
}
}
ESP_File::ESP_File(void* handle, bool isdir, bool iswritemode, const char * path)
{
_isdir = isdir;
_dirlist = "";
_isfakedir = false;
_index = -1;
_filename = "";
_name = "";
_lastwrite = 0;
_iswritemode = iswritemode;
_size = 0;
if (!handle) {
log_esp3d("No handle");
return ;
}
bool set =false;
if (_isdir) {
for (uint8_t i=0; (i < ESP_MAX_OPENHANDLE) && !set; i++) {
if (tDir_handle[i].fileName().length() == 0) {
tDir_handle[i] = *((Dir *)handle);
_index = i;
//Path = filename
if (path) {
_filename = path;
if (_filename == "/") {
_filename = "/.";
}
if (_filename[_filename.length()-1] != '.') {
if (_filename[_filename.length()-2] != '/') {
_filename+="/";
}
_filename+=".";
}
log_esp3d("Filename: %s", _filename.c_str());
//Name
if (_filename == "/.") {
_name = "/";
} else {
_name = _filename;
if (_name.length() >=2) {
if ((_name[_name.length() - 1] == '.') && (_name[_name.length() - 2] == '/')) {
_name.remove( _name.length() - 2,2);
}
}
_name.remove( 0, _name.lastIndexOf('/')+1);
}
}
log_esp3d("Dir: %s index: %d", _name.c_str(), _index);
log_esp3d("name: %s", _name.c_str());
log_esp3d("filename: %s", _filename.c_str());
set = true;
}
}
return;
}
for (uint8_t i=0; (i < ESP_MAX_OPENHANDLE) && !set; i++) {
if (!tFile_handle[i]) {
tFile_handle[i] = *((File*)handle);
//filename
_filename = tFile_handle[i].name();
//if root
if (_filename == "/") {
_filename = "/.";
}
if (_isdir) {
if (_filename[_filename.length()-1] != '.') {
if (_filename[_filename.length()-2] != '/') {
_filename+="/";
}
_filename+=".";
}
}
//name
if (_filename == "/.") {
_name = "/";
} else {
_name = _filename;
if (_name.endsWith("/.")) {
_name.remove( _name.length() - 2,2);
_isfakedir = true;
_isdir = true;
}
if (_name[0] == '/') {
_name.remove( 0, 1);
}
int pos = _name.lastIndexOf('/');
if (pos != -1) {
_name.remove( 0, pos+1);
}
}
//size
_size = tFile_handle[i].size();
//time
_lastwrite = tFile_handle[i].getLastWrite();
_index = i;
log_esp3d("Opening File at index %d",_index);
log_esp3d("name: %s", _name.c_str());
log_esp3d("filename: %s", _filename.c_str());
set = true;
}
}
}
bool ESP_File::seek(uint32_t pos, uint8_t mode)
{
return tFile_handle[_index].seek(pos, (SeekMode)mode);
}
void ESP_File::close()
{
if (_index != -1) {
if (_isdir && !_isfakedir) {
log_esp3d("Closing Dir at index %d", _index);
tDir_handle[_index] = Dir();
_index = -1;
return;
}
log_esp3d("Closing File at index %d", _index);
tFile_handle[_index].close();
//reopen if mode = write
//udate size + date
if (_iswritemode && !_isdir) {
File ftmp = SPIFFS.open(_filename.c_str(), "r");
if (ftmp) {
_size = ftmp.size();
_lastwrite = ftmp.getLastWrite();
ftmp.close();
}
}
_index = -1;
}
}
ESP_File ESP_File::openNextFile()
{
if ((_index == -1) || !_isdir) {
log_esp3d("openNextFile failed");
return ESP_File();
}
if(tDir_handle[_index].next()) {
log_esp3d("Getting next file from %s", _filename.c_str());
File tmp = tDir_handle[_index].openFile("r");
while (tmp) {
ESP_File esptmp(&tmp);
esptmp.close();
String sub = esptmp.filename();
sub.remove(0,_filename.length()-1);
int pos = sub.indexOf("/");
if (pos!=-1) {
//is subdir
sub = sub.substring(0,pos);
log_esp3d("file name:%s name: %s %s sub:%s root:%s", esptmp.filename(), esptmp.name(), (esptmp.isDirectory())?"isDir":"isFile", sub.c_str(), _filename.c_str());
String tag = "*" + sub + "*";
//test if already in directory list
if (_dirlist.indexOf(tag) == -1) {//not in list so add it and return the info
_dirlist+= tag;
String fname = _filename.substring(0,_filename.length()-1) + sub + "/.";
log_esp3d("Found dir # name: %s filename:%s", sub.c_str(), fname.c_str());
if (sub == ".") {
log_esp3d("Dir tag, ignore it");
if(!tDir_handle[_index].next()) {
return ESP_File();
} else {
tmp = tDir_handle[_index].openFile("r");
}
} else {
esptmp = ESP_File(sub.c_str(), fname.c_str());
return esptmp;
}
} else { //already in list so ignore it
log_esp3d("Dir name: %s already in list", sub.c_str());
if(!tDir_handle[_index].next()) {
return ESP_File();
} else {
tmp = tDir_handle[_index].openFile("r");
}
}
} else { //is file
log_esp3d("file name:%s name: %s %s sub:%s root:%s", esptmp.filename(), esptmp.name(), (esptmp.isDirectory())?"isDir":"isFile", sub.c_str(), _filename.c_str());
if (sub == ".") {
log_esp3d("Dir tag, ignore it");
if(!tDir_handle[_index].next()) {
return ESP_File();
} else {
tmp = tDir_handle[_index].openFile("r");
}
} else {
log_esp3d("Found file # name: %s filename:%s", esptmp.filename(), esptmp.name());
return esptmp;
}
}
}
}
return ESP_File();
}
#endif //ESP_SPIFFS_FILESYSTEM

View File

@ -209,30 +209,42 @@ bool ESP_SD::mkdir(const char *path)
bool ESP_SD::rmdir(const char *path)
{
if (!exists(path)) {
String p = path;
if (!p.startsWith("/")) {
p = '/'+p;
}
if (p!= "/") {
if (p.endsWith("/")) {
p.remove(p.length()-1);
}
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String > pathlist;
String p = path;
if (p.endsWith("/")) {
p.remove( p.length() - 1,1);
}
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
File dir = SD.open(pathlist.top().c_str());
File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDirectory()) {
candelete = false;
String newdir = f.name();
String newdir = pathlist.top()+ '/';
newdir+= f.name();
pathlist.push(newdir);
f.close();
f = File();
} else {
SD.remove(f.name());
String filepath = pathlist.top()+ '/';
filepath+= f.name();
f.close();
if(!SD.remove(filepath.c_str())) {
res = false;
}
f = dir.openNextFile();
}
}

View File

@ -252,29 +252,39 @@ bool ESP_SD::mkdir(const char *path)
bool ESP_SD::rmdir(const char *path)
{
if (!exists(path)) {
String p = path;
if (!p.endsWith("/")) {
p+= '/';
}
if (!p.startsWith("/")) {
p = '/'+p;
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String > pathlist;
String p = path;
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
File dir = SD.open(pathlist.top().c_str());
dir.rewindDirectory();
File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDirectory()) {
candelete = false;
String newdir = f.name();
String newdir = pathlist.top() + f.name();
newdir+="/";
pathlist.push(newdir);
f.close();
f = File();
} else {
_sizechanged = true;
SD.remove(f.fullName());
String filepath = pathlist.top() + f.name();
f.close();
if (!SD.remove(filepath.c_str())) {
res= false;
}
f = dir.openNextFile();
}
}

View File

@ -362,25 +362,33 @@ bool ESP_SD::mkdir(const char *path)
bool ESP_SD::rmdir(const char *path)
{
if (!exists(path)) {
String p = path;
if (!p.endsWith("/")) {
p+= '/';
}
if (!p.startsWith("/")) {
p = '/'+p;
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String > pathlist;
String p = path;
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
File dir = SD.open(pathlist.top().c_str());
dir.rewindDirectory();
File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDir()) {
candelete = false;
String newdir;
char tmp[255];
f.getName(tmp,254);
newdir = tmp;
newdir = pathlist.top() + tmp;
newdir+="/";
pathlist.push(newdir);
f.close();
f = File();
@ -388,8 +396,11 @@ bool ESP_SD::rmdir(const char *path)
char tmp[255];
f.getName(tmp,254);
_sizechanged = true;
SD.remove(tmp);
String filepath = pathlist.top() + tmp;
f.close();
if (!SD.remove(filepath.c_str())) {
res= false;
}
f = dir.openNextFile();
}
}
@ -402,7 +413,7 @@ bool ESP_SD::rmdir(const char *path)
dir.close();
}
p = String();
log_esp3d("count %d", pathlist.size());
log_esp3d("count %d has error %d\n",pathlist.size(), res);
return res;
}

View File

@ -347,25 +347,32 @@ bool ESP_SD::mkdir(const char *path)
bool ESP_SD::rmdir(const char *path)
{
if (!exists(path)) {
String p = path;
if (!p.endsWith("/")) {
p+= '/';
}
if (!p.startsWith("/")) {
p = '/'+p;
}
if (!exists(p.c_str())) {
return false;
}
bool res = true;
std::stack <String > pathlist;
String p = path;
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
sdfat::File dir = SD.open(pathlist.top().c_str());
dir.rewindDirectory();
sdfat::File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDir()) {
candelete = false;
String newdir;
char tmp[255];
f.getName(tmp,254);
newdir = tmp;
newdir = pathlist.top() + tmp;
newdir+="/";
pathlist.push(newdir);
f.close();
f = sdfat::File();
@ -373,8 +380,11 @@ bool ESP_SD::rmdir(const char *path)
char tmp[255];
f.getName(tmp,254);
_sizechanged = true;
SD.remove(tmp);
String filepath = pathlist.top() + tmp;
f.close();
if (!SD.remove(filepath.c_str())) {
res= false;
}
f = dir.openNextFile();
}
}

View File

@ -1,853 +0,0 @@
/*
sd_sdfat_esp32.cpp - ESP3D sd support class
Copyright (c) 2014 Luc Lebosse. All rights reserved.
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with This code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../../../include/esp3d_config.h"
#if defined (ARDUINO_ARCH_ESP32) && defined(SD_DEVICE)
#if (SD_DEVICE == ESP_SDFAT)
#include "../esp_sd.h"
#include <stack>
#include "../../../core/settings_esp3d.h"
#include <SdFat.h>
extern File tSDFile_handle[ESP_MAX_SD_OPENHANDLE];
//Max Freq Working
#define FREQMZ 40
SdFat SD;
void dateTime (uint16_t* date, uint16_t* dtime)
{
struct tm tmstruct;
time_t now;
time (&now);
localtime_r (&now, &tmstruct);
*date = FAT_DATE ( (tmstruct.tm_year) + 1900, ( tmstruct.tm_mon) + 1, tmstruct.tm_mday);
*dtime = FAT_TIME (tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
}
time_t getDateTimeFile(File & filehandle)
{
static time_t dt = 0;
#ifdef SD_TIMESTAMP_FEATURE
struct tm timefile;
dir_t d;
if(filehandle) {
if (filehandle.dirEntry(&d)) {
timefile.tm_year = FAT_YEAR(d.lastWriteDate) - 1900;
timefile.tm_mon = FAT_MONTH(d.lastWriteDate) - 1;
timefile.tm_mday = FAT_DAY(d.lastWriteDate);
timefile.tm_hour = FAT_HOUR(d.lastWriteTime);
timefile.tm_min = FAT_MINUTE(d.lastWriteTime);
timefile.tm_sec = FAT_SECOND(d.lastWriteTime);
timefile.tm_isdst = -1;
dt = mktime(&timefile);
if (dt == -1) {
log_esp3d("mktime failed");
}
} else {
log_esp3d("stat file failed");
}
} else {
log_esp3d("check stat file failed");
}
#endif //SD_TIMESTAMP_FEATURE
return dt;
}
uint8_t ESP_SD::getState(bool refresh)
{
#if defined(ESP_SD_DETECT_PIN) && ESP_SD_DETECT_PIN != -1
//no need to go further if SD detect is not correct
if (!((digitalRead (ESP_SD_DETECT_PIN) == ESP_SD_DETECT_VALUE) ? true : false)) {
_state = ESP_SDCARD_NOT_PRESENT;
return _state;
}
#endif //ESP_SD_DETECT_PIN
//if busy doing something return state
if (!((_state == ESP_SDCARD_NOT_PRESENT) || _state == ESP_SDCARD_IDLE)) {
return _state;
}
if (!refresh) {
return _state; //to avoid refresh=true + busy to reset SD and waste time
} else {
_sizechanged = true;
}
//SD is idle or not detected, let see if still the case
_state = ESP_SDCARD_NOT_PRESENT;
log_esp3d("Spi : CS: %d, Miso: %d, Mosi: %d, SCK: %d",ESP_SD_CS_PIN!=-1?ESP_SD_CS_PIN:SS, ESP_SD_MISO_PIN!=-1?ESP_SD_MISO_PIN:MISO, ESP_SD_MOSI_PIN!=-1?ESP_SD_MOSI_PIN:MOSI, ESP_SD_SCK_PIN!=-1?ESP_SD_SCK_PIN:SCK);
//refresh content if card was removed
if (SD.begin((ESP_SD_CS_PIN == -1)?SS:ESP_SD_CS_PIN, SD_SCK_MHZ(FREQMZ/_spi_speed_divider))) {
if (SD.card()->cardSize() > 0 ) {
_state = ESP_SDCARD_IDLE;
}
}
return _state;
}
bool ESP_SD::begin()
{
#if (ESP_SD_CS_PIN != -1) || (ESP_SD_MISO_PIN != -1) || (ESP_SD_MOSI_PIN != -1) || (ESP_SD_SCK_PIN != -1)
SPI.begin(ESP_SD_SCK_PIN, ESP_SD_MISO_PIN, ESP_SD_MOSI_PIN, ESP_SD_CS_PIN);
#endif
_started = true;
_state = ESP_SDCARD_NOT_PRESENT;
_spi_speed_divider = Settings_ESP3D::read_byte(ESP_SD_SPEED_DIV);
//sanity check
if (_spi_speed_divider <= 0) {
_spi_speed_divider = 1;
}
#ifdef SD_TIMESTAMP_FEATURE
//set callback to get time on files on SD
SdFile::dateTimeCallback (dateTime);
#endif //SD_TIMESTAMP_FEATURE
//Setup pins
#if defined(ESP_SD_DETECT_PIN) && ESP_SD_DETECT_PIN != -1
pinMode (ESP_SD_DETECT_PIN, INPUT);
#endif //ESP_SD_DETECT_PIN
#if SD_DEVICE_CONNECTION == ESP_SHARED_SD
#if defined(ESP_FLAG_SHARED_SD_PIN) && ESP_FLAG_SHARED_SD_PIN != -1
pinMode (ESP_FLAG_SHARED_SD_PIN, OUTPUT);
digitalWrite(ESP_FLAG_SHARED_SD_PIN, !ESP_FLAG_SHARED_SD_VALUE);
#endif //ESP_FLAG_SHARED_SD_PIN
#endif //SD_DEVICE_CONNECTION == ESP_SHARED_SD
return _started;
}
void ESP_SD::end()
{
_state = ESP_SDCARD_NOT_PRESENT;
_started = false;
}
void ESP_SD::refreshStats(bool force)
{
if (force || _sizechanged) {
usedBytes(true);
}
_sizechanged = false;
}
uint64_t ESP_SD::totalBytes(bool refresh)
{
static uint64_t _totalBytes = 0;
if (refresh || _totalBytes==0) {
_totalBytes = SD.vol()->clusterCount();
uint8_t blocks = SD.vol()->blocksPerCluster();
_totalBytes = _totalBytes * blocks * 512;
}
return _totalBytes;
}
uint64_t ESP_SD::usedBytes(bool refresh)
{
return totalBytes(refresh) - freeBytes(refresh);
}
uint64_t ESP_SD::freeBytes(bool refresh)
{
static uint64_t _freeBytes = 0;
if (refresh || _freeBytes==0) {
uint64_t volFree = SD.vol()->freeClusterCount();
uint8_t blocks = SD.vol()->blocksPerCluster();
_freeBytes = volFree * blocks * 512;
}
return _freeBytes;
}
uint ESP_SD::maxPathLength()
{
return 255;
}
bool ESP_SD::rename(const char *oldpath, const char *newpath)
{
return SD.rename(oldpath,newpath);
}
// strings needed in file system structures
#define noName "NO NAME "
#define fat16str "FAT16 "
#define fat32str "FAT32 "
// constants for file system structure
#define BU16 128
#define BU32 8192
#define ERASE_SIZE 262144L
//------------------------------------------------------------------------------
// write cached block to the card
uint8_t writeCache(uint32_t lbn, Sd2Card & card, cache_t & cache)
{
return card.writeBlock(lbn, cache.data);
}
//------------------------------------------------------------------------------
// initialize appropriate sizes for SD capacity
bool initSizes(uint32_t cardCapacityMB, uint8_t & sectorsPerCluster, uint8_t & numberOfHeads, uint8_t & sectorsPerTrack)
{
if (cardCapacityMB <= 6) {
return false;
} else if (cardCapacityMB <= 16) {
sectorsPerCluster = 2;
} else if (cardCapacityMB <= 32) {
sectorsPerCluster = 4;
} else if (cardCapacityMB <= 64) {
sectorsPerCluster = 8;
} else if (cardCapacityMB <= 128) {
sectorsPerCluster = 16;
} else if (cardCapacityMB <= 1024) {
sectorsPerCluster = 32;
} else if (cardCapacityMB <= 32768) {
sectorsPerCluster = 64;
} else {
// SDXC cards
sectorsPerCluster = 128;
}
// set fake disk geometry
sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63;
if (cardCapacityMB <= 16) {
numberOfHeads = 2;
} else if (cardCapacityMB <= 32) {
numberOfHeads = 4;
} else if (cardCapacityMB <= 128) {
numberOfHeads = 8;
} else if (cardCapacityMB <= 504) {
numberOfHeads = 16;
} else if (cardCapacityMB <= 1008) {
numberOfHeads = 32;
} else if (cardCapacityMB <= 2016) {
numberOfHeads = 64;
} else if (cardCapacityMB <= 4032) {
numberOfHeads = 128;
} else {
numberOfHeads = 255;
}
return true;
}
//------------------------------------------------------------------------------
// zero cache and optionally set the sector signature
void clearCache(uint8_t addSig, cache_t & cache)
{
memset(&cache, 0, sizeof(cache));
if (addSig) {
cache.mbr.mbrSig0 = BOOTSIG0;
cache.mbr.mbrSig1 = BOOTSIG1;
}
}
//------------------------------------------------------------------------------
// zero FAT and root dir area on SD
bool clearFatDir(uint32_t bgn, uint32_t count, Sd2Card & card, cache_t & cache, ESP3DOutput * output)
{
clearCache(false, cache);
if (!card.writeStart(bgn, count)) {
return false;
}
for (uint32_t i = 0; i < count; i++) {
if ((i & 0XFF) == 0) {
if (output) {
output->print(".");
}
}
if (!card.writeData(cache.data)) {
return false;
}
}
if (!card.writeStop()) {
return false;
}
return true;
}
//------------------------------------------------------------------------------
// return cylinder number for a logical block number
uint16_t lbnToCylinder(uint32_t lbn, uint8_t numberOfHeads, uint8_t sectorsPerTrack)
{
return lbn / (numberOfHeads * sectorsPerTrack);
}
//------------------------------------------------------------------------------
// return head number for a logical block number
uint8_t lbnToHead(uint32_t lbn, uint8_t numberOfHeads, uint8_t sectorsPerTrack)
{
return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack;
}
//------------------------------------------------------------------------------
// return sector number for a logical block number
uint8_t lbnToSector(uint32_t lbn, uint8_t sectorsPerTrack)
{
return (lbn % sectorsPerTrack) + 1;
}
//------------------------------------------------------------------------------
// format and write the Master Boot Record
bool writeMbr(Sd2Card & card, cache_t & cache, uint8_t partType, uint32_t relSector, uint32_t partSize, uint8_t numberOfHeads, uint8_t sectorsPerTrack)
{
clearCache(true, cache);
part_t* p = cache.mbr.part;
p->boot = 0;
uint16_t c = lbnToCylinder(relSector, numberOfHeads, sectorsPerTrack);
if (c > 1023) {
return false;
}
p->beginCylinderHigh = c >> 8;
p->beginCylinderLow = c & 0XFF;
p->beginHead = lbnToHead(relSector, numberOfHeads, sectorsPerTrack);
p->beginSector = lbnToSector(relSector, sectorsPerTrack);
p->type = partType;
uint32_t endLbn = relSector + partSize - 1;
c = lbnToCylinder(endLbn,numberOfHeads, sectorsPerTrack);
if (c <= 1023) {
p->endCylinderHigh = c >> 8;
p->endCylinderLow = c & 0XFF;
p->endHead = lbnToHead(endLbn, numberOfHeads, sectorsPerTrack);
p->endSector = lbnToSector(endLbn, sectorsPerTrack);
} else {
// Too big flag, c = 1023, h = 254, s = 63
p->endCylinderHigh = 3;
p->endCylinderLow = 255;
p->endHead = 254;
p->endSector = 63;
}
p->firstSector = relSector;
p->totalSectors = partSize;
if (!writeCache(0, card, cache)) {
return false;
}
return true;
}
//------------------------------------------------------------------------------
// generate serial number from card size and micros since boot
uint32_t volSerialNumber(uint32_t cardSizeBlocks)
{
return (cardSizeBlocks << 8) + micros();
}
// format the SD as FAT16
bool makeFat16(uint32_t & dataStart, Sd2Card & card, cache_t & cache, uint8_t numberOfHeads, uint8_t sectorsPerTrack, uint32_t cardSizeBlocks, uint8_t sectorsPerCluster, uint32_t &relSector, uint32_t partSize, uint8_t & partType, uint32_t &fatSize, uint32_t &fatStart, uint16_t reservedSectors, ESP3DOutput * output)
{
uint32_t nc;
for (dataStart = 2 * BU16;; dataStart += BU16) {
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
fatSize = (nc + 2 + 255)/256;
uint32_t r = BU16 + 1 + 2 * fatSize + 32;
if (dataStart < r) {
continue;
}
relSector = dataStart - r + BU16;
break;
}
// check valid cluster count for FAT16 volume
if (nc < 4085 || nc >= 65525) {
return false;
}
reservedSectors = 1;
fatStart = relSector + reservedSectors;
partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32;
if (partSize < 32680) {
partType = 0X01;
} else if (partSize < 65536) {
partType = 0X04;
} else {
partType = 0X06;
}
// write MBR
if (!writeMbr(card, cache, partType, relSector, partSize, numberOfHeads, sectorsPerTrack)) {
return false;
}
clearCache(true, cache);
fat_boot_t* pb = &cache.fbs;
pb->jump[0] = 0XEB;
pb->jump[1] = 0X00;
pb->jump[2] = 0X90;
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
pb->oemId[i] = ' ';
}
pb->bytesPerSector = 512;
pb->sectorsPerCluster = sectorsPerCluster;
pb->reservedSectorCount = reservedSectors;
pb->fatCount = 2;
pb->rootDirEntryCount = 512;
pb->mediaType = 0XF8;
pb->sectorsPerFat16 = fatSize;
pb->sectorsPerTrack = sectorsPerTrack;
pb->headCount = numberOfHeads;
pb->hidddenSectors = relSector;
pb->totalSectors32 = partSize;
pb->driveNumber = 0X80;
pb->bootSignature = EXTENDED_BOOT_SIG;
pb->volumeSerialNumber = volSerialNumber(cardSizeBlocks);
memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
memcpy(pb->fileSystemType, fat16str, sizeof(pb->fileSystemType));
// write partition boot sector
if (!writeCache(relSector, card, cache)) {
return false;
}
// clear FAT and root directory
clearFatDir(fatStart, dataStart - fatStart, card, cache, output);
clearCache(false, cache);
cache.fat16[0] = 0XFFF8;
cache.fat16[1] = 0XFFFF;
// write first block of FAT and backup for reserved clusters
if (!writeCache(fatStart, card, cache)
|| !writeCache(fatStart + fatSize, card, cache)) {
return false;
}
return true;
}
// format the SD as FAT32
bool makeFat32(uint32_t & dataStart, Sd2Card & card, cache_t & cache, uint8_t numberOfHeads, uint8_t sectorsPerTrack, uint32_t cardSizeBlocks, uint8_t sectorsPerCluster, uint32_t &relSector, uint32_t partSize, uint8_t & partType, uint32_t &fatSize, uint32_t &fatStart, uint16_t reservedSectors, ESP3DOutput * output)
{
uint32_t nc;
relSector = BU32;
for (dataStart = 2 * BU32;; dataStart += BU32) {
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
fatSize = (nc + 2 + 127)/128;
uint32_t r = relSector + 9 + 2 * fatSize;
if (dataStart >= r) {
break;
}
}
// error if too few clusters in FAT32 volume
if (nc < 65525) {
return false;
}
reservedSectors = dataStart - relSector - 2 * fatSize;
fatStart = relSector + reservedSectors;
partSize = nc * sectorsPerCluster + dataStart - relSector;
// type depends on address of end sector
// max CHS has lbn = 16450560 = 1024*255*63
if ((relSector + partSize) <= 16450560) {
// FAT32
partType = 0X0B;
} else {
// FAT32 with INT 13
partType = 0X0C;
}
if (!writeMbr(card, cache, partType, relSector, partSize, numberOfHeads, sectorsPerTrack)) {
return false;
}
clearCache(true, cache);
fat32_boot_t* pb = &cache.fbs32;
pb->jump[0] = 0XEB;
pb->jump[1] = 0X00;
pb->jump[2] = 0X90;
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
pb->oemId[i] = ' ';
}
pb->bytesPerSector = 512;
pb->sectorsPerCluster = sectorsPerCluster;
pb->reservedSectorCount = reservedSectors;
pb->fatCount = 2;
pb->mediaType = 0XF8;
pb->sectorsPerTrack = sectorsPerTrack;
pb->headCount = numberOfHeads;
pb->hidddenSectors = relSector;
pb->totalSectors32 = partSize;
pb->sectorsPerFat32 = fatSize;
pb->fat32RootCluster = 2;
pb->fat32FSInfo = 1;
pb->fat32BackBootBlock = 6;
pb->driveNumber = 0X80;
pb->bootSignature = EXTENDED_BOOT_SIG;
pb->volumeSerialNumber = volSerialNumber(cardSizeBlocks);
memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType));
// write partition boot sector and backup
if (!writeCache(relSector, card, cache)
|| !writeCache(relSector + 6, card, cache)) {
return false;
}
clearCache(true, cache);
// write extra boot area and backup
if (!writeCache(relSector + 2, card, cache)
|| !writeCache(relSector + 8, card, cache)) {
return false;
}
fat32_fsinfo_t* pf = &cache.fsinfo;
pf->leadSignature = FSINFO_LEAD_SIG;
pf->structSignature = FSINFO_STRUCT_SIG;
pf->freeCount = 0XFFFFFFFF;
pf->nextFree = 0XFFFFFFFF;
// write FSINFO sector and backup
if (!writeCache(relSector + 1, card, cache)
|| !writeCache(relSector + 7, card, cache)) {
return false;
}
clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster, card, cache, output);
clearCache(false, cache);
cache.fat32[0] = 0x0FFFFFF8;
cache.fat32[1] = 0x0FFFFFFF;
cache.fat32[2] = 0x0FFFFFFF;
// write first block of FAT and backup for reserved clusters
if (!writeCache(fatStart, card, cache)
|| !writeCache(fatStart + fatSize, card, cache)) {
return false;
}
return true;
}
bool eraseCard(Sd2Card & card, cache_t & cache, uint32_t cardSizeBlocks, ESP3DOutput * output)
{
uint32_t firstBlock = 0;
uint32_t lastBlock = 0;
//uint16_t n = 0;
if (output) {
output->printMSG("Erasing ", false);
}
do {
lastBlock = firstBlock + ERASE_SIZE - 1;
if (lastBlock >= cardSizeBlocks) {
lastBlock = cardSizeBlocks - 1;
}
if (!card.erase(firstBlock, lastBlock)) {
return false;
}
if (output) {
output->print(".");
}
firstBlock += ERASE_SIZE;
} while (firstBlock < cardSizeBlocks);
if (!card.readBlock(0, cache.data)) {
return false;
}
if (output) {
output->printLN("");
}
return true;
}
bool formatCard(uint32_t & dataStart, Sd2Card & card,
cache_t & cache, uint8_t numberOfHeads,
uint8_t sectorsPerTrack, uint32_t cardSizeBlocks,
uint8_t sectorsPerCluster, uint32_t &relSector,
uint32_t partSize, uint8_t & partType,
uint32_t &fatSize, uint32_t &fatStart,
uint32_t cardCapacityMB, uint16_t reservedSectors, ESP3DOutput * output)
{
initSizes(cardCapacityMB, sectorsPerCluster, numberOfHeads, sectorsPerTrack);
if (card.type() != SD_CARD_TYPE_SDHC) {
if (output) {
output->printMSG("Formating FAT16 ");
}
if(!makeFat16(dataStart, card, cache, numberOfHeads, sectorsPerTrack, cardSizeBlocks, sectorsPerCluster, relSector, partSize, partType, fatSize, fatStart, reservedSectors, output)) {
return false;
}
} else {
if (output) {
output->printMSG("Formating FAT32 ", false);
}
if(!makeFat32(dataStart, card, cache, numberOfHeads, sectorsPerTrack, cardSizeBlocks, sectorsPerCluster, relSector, partSize, partType, fatSize, fatStart, reservedSectors, output)) {
return false;
}
}
if (output) {
output->printLN("");
}
return true;
}
bool ESP_SD::format(ESP3DOutput * output)
{
if (ESP_SD::getState(true) == ESP_SDCARD_IDLE) {
Sd2Card card;
uint32_t cardSizeBlocks;
uint32_t cardCapacityMB;
// cache for SD block
cache_t cache;
// MBR information
uint8_t partType = 0;
uint32_t relSector = 0;
uint32_t partSize = 0;
// Fake disk geometry
uint8_t numberOfHeads = 0;
uint8_t sectorsPerTrack = 0;
// FAT parameters
uint16_t reservedSectors = 0;
uint8_t sectorsPerCluster = 0;
uint32_t fatStart = 0;
uint32_t fatSize = 0;
uint32_t dataStart = 0;
if (!card.begin((ESP_SD_CS_PIN == -1)?SS:ESP_SD_CS_PIN, SD_SCK_MHZ(FREQMZ/_spi_speed_divider))) {
return false;
}
cardSizeBlocks = card.cardSize();
if (cardSizeBlocks == 0) {
return false;
}
cardCapacityMB = (cardSizeBlocks + 2047)/2048;
if (output) {
String s = "Capacity detected :" + String((1.048576*cardCapacityMB)/1024) + "GB";
output->printMSG(s.c_str());
}
if (!eraseCard(card, cache, cardSizeBlocks, output)) {
return false;
}
if (!formatCard(dataStart, card, cache, numberOfHeads,
sectorsPerTrack, cardSizeBlocks,
sectorsPerCluster, relSector, partSize, partType,
fatSize, fatStart, cardCapacityMB, reservedSectors,output)) {
return false;
}
return true;
}
return false;
}
ESP_SDFile ESP_SD::open(const char* path, uint8_t mode)
{
//do some check
if(((strcmp(path,"/") == 0) && ((mode == ESP_FILE_WRITE) || (mode == ESP_FILE_APPEND))) || (strlen(path) == 0)) {
_sizechanged = true;
return ESP_SDFile();
}
// path must start by '/'
if (path[0] != '/') {
return ESP_SDFile();
}
if (mode != ESP_FILE_READ) {
//check container exists
String p = path;
p.remove(p.lastIndexOf('/') +1);
if (!exists(p.c_str())) {
log_esp3d("Error opening: %s", path);
return ESP_SDFile();
}
}
File tmp = SD.open(path, (mode == ESP_FILE_READ)?FILE_READ:(mode == ESP_FILE_WRITE)?FILE_WRITE:FILE_WRITE);
ESP_SDFile esptmp(&tmp, tmp.isDir(),(mode == ESP_FILE_READ)?false:true, path);
return esptmp;
}
bool ESP_SD::exists(const char* path)
{
bool res = false;
//root should always be there if started
if (strcmp(path, "/") == 0) {
return _started;
}
res = SD.exists(path);
if (!res) {
ESP_SDFile root = ESP_SD::open(path, ESP_FILE_READ);
if (root) {
res = root.isDirectory();
}
}
return res;
}
bool ESP_SD::remove(const char *path)
{
_sizechanged = true;
return SD.remove(path);
}
bool ESP_SD::mkdir(const char *path)
{
return SD.mkdir(path);
}
bool ESP_SD::rmdir(const char *path)
{
if (!exists(path)) {
return false;
}
bool res = true;
std::stack <String > pathlist;
String p = path;
pathlist.push(p);
while (pathlist.size() > 0) {
File dir = SD.open(pathlist.top().c_str());
dir.rewindDirectory();
File f = dir.openNextFile();
bool candelete = true;
while (f) {
if (f.isDir()) {
candelete = false;
String newdir;
char tmp[255];
f.getName(tmp,254);
newdir = tmp;
pathlist.push(newdir);
f.close();
f = File();
} else {
char tmp[255];
f.getName(tmp,254);
_sizechanged = true;
SD.remove(tmp);
f.close();
f = dir.openNextFile();
}
}
if (candelete) {
if (pathlist.top() !="/") {
res = SD.rmdir(pathlist.top().c_str());
}
pathlist.pop();
}
dir.close();
}
p = String();
log_esp3d("count %d", pathlist.size());
return res;
}
void ESP_SD::closeAll()
{
for (uint8_t i = 0; i < ESP_MAX_SD_OPENHANDLE; i++) {
tSDFile_handle[i].close();
tSDFile_handle[i] = File();
}
}
bool ESP_SDFile::seek(uint32_t pos, uint8_t mode)
{
if (mode == ESP_SEEK_END) {
return tSDFile_handle[_index].seek(-pos); //based on SDFS comment
}
return tSDFile_handle[_index].seek(pos);
}
ESP_SDFile::ESP_SDFile(void* handle, bool isdir, bool iswritemode, const char * path)
{
_isdir = isdir;
_dirlist = "";
_index = -1;
_filename = "";
_name = "";
_lastwrite = 0 ;
_iswritemode = iswritemode;
_size = 0;
if (!handle) {
return ;
}
bool set =false;
for (uint8_t i=0; (i < ESP_MAX_SD_OPENHANDLE) && !set; i++) {
if (!tSDFile_handle[i]) {
tSDFile_handle[i] = *((File*)handle);
//filename
char tmp[255];
tSDFile_handle[i].getName(tmp,254);
_filename = path;
//name
_name = tmp;
if (_name.endsWith("/")) {
_name.remove( _name.length() - 1,1);
_isdir = true;
}
if (_name[0] == '/') {
_name.remove( 0, 1);
}
int pos = _name.lastIndexOf('/');
if (pos != -1) {
_name.remove( 0, pos+1);
}
if (_name.length() == 0) {
_name = "/";
}
//size
_size = tSDFile_handle[i].size();
//time
if (!_isdir && !iswritemode) {
_lastwrite = getDateTimeFile(tSDFile_handle[i]);
} else {
//no need date time for directory
_lastwrite = 0;
}
_index = i;
//log_esp3d("Opening File at index %d",_index);
set = true;
}
}
}
//todo need also to add short filename
const char* ESP_SDFile::shortname() const
{
static char sname[13];
File ftmp = SD.open(_filename.c_str());
if (ftmp) {
ftmp.getSFN(sname);
ftmp.close();
return sname;
} else {
return _name.c_str();
}
}
void ESP_SDFile::close()
{
if (_index != -1) {
//log_esp3d("Closing File at index %d", _index);
tSDFile_handle[_index].close();
//reopen if mode = write
//udate size + date
if (_iswritemode && !_isdir) {
File ftmp = SD.open(_filename.c_str());
if (ftmp) {
_size = ftmp.size();
_lastwrite = getDateTimeFile(ftmp);
ftmp.close();
}
}
tSDFile_handle[_index] = File();
//log_esp3d("Closing File at index %d",_index);
_index = -1;
}
}
ESP_SDFile ESP_SDFile::openNextFile()
{
if ((_index == -1) || !_isdir) {
log_esp3d("openNextFile failed");
return ESP_SDFile();
}
File tmp = tSDFile_handle[_index].openNextFile();
if (tmp) {
char tmps[255];
tmp.getName(tmps,254);
log_esp3d("tmp name :%s %s", tmps, (tmp.isDir())?"isDir":"isFile");
String s = _filename ;
if (s!="/") {
s+="/";
}
s += tmps;
ESP_SDFile esptmp(&tmp, tmp.isDir(),false, s.c_str());
esptmp.close();
return esptmp;
}
return ESP_SDFile();
}
const char * ESP_SD::FilesystemName()
{
return "SDFat - " SD_FAT_VERSION_STR ;
}
#endif //SD_DEVICE == ESP_SDFAT
#endif //ARCH_ESP32 && SD_DEVICE

View File

@ -1,864 +0,0 @@
/*
sd_sdfat_esp8266.cpp - ESP3D sd support class
Copyright (c) 2014 Luc Lebosse. All rights reserved.
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with This code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../../../include/esp3d_config.h"
#if defined (ARDUINO_ARCH_ESP8266) && defined(SD_DEVICE)
#if (SD_DEVICE == ESP_SDFAT)
#define FS_NO_GLOBALS
#include "../esp_sd.h"
#include <stack>
#include "../../../core/settings_esp3d.h"
#define NO_GLOBAL_SD
#include <SdFat.h>
extern sdfat::File tSDFile_handle[ESP_MAX_SD_OPENHANDLE];
using namespace sdfat;
SdFat SD;
void dateTime (uint16_t* date, uint16_t* dtime)
{
struct tm tmstruct;
time_t now;
time (&now);
localtime_r (&now, &tmstruct);
*date = FAT_DATE ( (tmstruct.tm_year) + 1900, ( tmstruct.tm_mon) + 1, tmstruct.tm_mday);
*dtime = FAT_TIME (tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
}
time_t getDateTimeFile(sdfat::File & filehandle)
{
static time_t dt = 0;
#ifdef SD_TIMESTAMP_FEATURE
struct tm timefile;
dir_t d;
if(filehandle) {
if (filehandle.dirEntry(&d)) {
timefile.tm_year = FAT_YEAR(d.lastWriteDate) - 1900;
timefile.tm_mon = FAT_MONTH(d.lastWriteDate) - 1;
timefile.tm_mday = FAT_DAY(d.lastWriteDate);
timefile.tm_hour = FAT_HOUR(d.lastWriteTime);
timefile.tm_min = FAT_MINUTE(d.lastWriteTime);
timefile.tm_sec = FAT_SECOND(d.lastWriteTime);
timefile.tm_isdst = -1;
dt = mktime(&timefile);
if (dt == -1) {
log_esp3d("mktime failed");
}
} else {
log_esp3d("stat file failed");
}
} else {
log_esp3d("check file for stat failed");
}
#endif //SD_TIMESTAMP_FEATURE
return dt;
}
uint8_t ESP_SD::getState(bool refresh)
{
#if defined(ESP_SD_DETECT_PIN) && ESP_SD_DETECT_PIN != -1
//no need to go further if SD detect is not correct
if (!((digitalRead (ESP_SD_DETECT_PIN) == ESP_SD_DETECT_VALUE) ? true : false)) {
log_esp3d("No SD State %d vs %d", digitalRead (ESP_SD_DETECT_PIN), ESP_SD_DETECT_VALUE);
_state = ESP_SDCARD_NOT_PRESENT;
return _state;
} else {
log_esp3d("SD Detect Pin ok");
}
#endif //ESP_SD_DETECT_PIN
//if busy doing something return state
if (!((_state == ESP_SDCARD_NOT_PRESENT) || _state == ESP_SDCARD_IDLE)) {
log_esp3d("Busy SD State");
return _state;
}
if (!refresh) {
log_esp3d("SD State cache is %d", _state);
return _state; //to avoid refresh=true + busy to reset SD and waste time
} else {
_sizechanged = true;
}
//SD is idle or not detected, let see if still the case
_state = ESP_SDCARD_NOT_PRESENT;
//refresh content if card was removed
if (SD.begin((ESP_SD_CS_PIN == -1)?SS:ESP_SD_CS_PIN, SD_SCK_HZ(F_CPU/_spi_speed_divider))) {
log_esp3d("Init SD State ok");
if (SD.card()->cardSize() > 0 ) {
log_esp3d("SD available");
_state = ESP_SDCARD_IDLE;
} else {
log_esp3d("Cannot get card size");
}
} else {
log_esp3d("Init SD State failed");
}
log_esp3d("SD State is %d", _state);
return _state;
}
bool ESP_SD::begin()
{
_started = true;
_state = ESP_SDCARD_NOT_PRESENT;
_spi_speed_divider = Settings_ESP3D::read_byte(ESP_SD_SPEED_DIV);
//sanity check
if (_spi_speed_divider <= 0) {
_spi_speed_divider = 1;
}
#ifdef SD_TIMESTAMP_FEATURE
//set callback to get time on files on SD
SdFile::dateTimeCallback (dateTime);
#endif //SD_TIMESTAMP_FEATURE
//Setup pins
#if defined(ESP_SD_DETECT_PIN) && ESP_SD_DETECT_PIN != -1
pinMode (ESP_SD_DETECT_PIN, INPUT);
#endif //ESP_SD_DETECT_PIN
#if SD_DEVICE_CONNECTION == ESP_SHARED_SD
#if defined(ESP_FLAG_SHARED_SD_PIN) && ESP_FLAG_SHARED_SD_PIN != -1
pinMode (ESP_FLAG_SHARED_SD_PIN, OUTPUT);
digitalWrite(ESP_FLAG_SHARED_SD_PIN, !ESP_FLAG_SHARED_SD_VALUE);
#endif //ESP_FLAG_SHARED_SD_PIN
#endif //SD_DEVICE_CONNECTION == ESP_SHARED_SD
return _started;
}
void ESP_SD::end()
{
_state = ESP_SDCARD_NOT_PRESENT;
_started = false;
}
void ESP_SD::refreshStats(bool force)
{
if (force || _sizechanged) {
usedBytes(true);
}
_sizechanged = false;
}
uint64_t ESP_SD::totalBytes(bool refresh)
{
static uint64_t _totalBytes = 0;
if (refresh || _totalBytes==0) {
_totalBytes = SD.vol()->clusterCount();
uint8_t blocks = SD.vol()->blocksPerCluster();
_totalBytes = _totalBytes * blocks * 512;
}
return _totalBytes;
}
uint64_t ESP_SD::usedBytes(bool refresh)
{
return totalBytes(refresh) - freeBytes(refresh);
}
uint64_t ESP_SD::freeBytes(bool refresh)
{
static uint64_t _freeBytes = 0;
if (_freeBytes== 0 || refresh) {
_freeBytes = SD.vol()->freeClusterCount();
uint8_t blocks = SD.vol()->blocksPerCluster();
_freeBytes = _freeBytes * blocks * 512;
}
return _freeBytes;
}
uint ESP_SD::maxPathLength()
{
return 255;
}
bool ESP_SD::rename(const char *oldpath, const char *newpath)
{
return SD.rename(oldpath,newpath);
}
// strings needed in file system structures
#define noName "NO NAME "
#define fat16str "FAT16 "
#define fat32str "FAT32 "
// constants for file system structure
#define BU16 128
#define BU32 8192
#define ERASE_SIZE 262144L
//------------------------------------------------------------------------------
// write cached block to the card
uint8_t writeCache(uint32_t lbn, Sd2Card & card, cache_t & cache)
{
return card.writeBlock(lbn, cache.data);
}
//------------------------------------------------------------------------------
// initialize appropriate sizes for SD capacity
bool initSizes(uint32_t cardCapacityMB, uint8_t & sectorsPerCluster, uint8_t & numberOfHeads, uint8_t & sectorsPerTrack)
{
if (cardCapacityMB <= 6) {
return false;
} else if (cardCapacityMB <= 16) {
sectorsPerCluster = 2;
} else if (cardCapacityMB <= 32) {
sectorsPerCluster = 4;
} else if (cardCapacityMB <= 64) {
sectorsPerCluster = 8;
} else if (cardCapacityMB <= 128) {
sectorsPerCluster = 16;
} else if (cardCapacityMB <= 1024) {
sectorsPerCluster = 32;
} else if (cardCapacityMB <= 32768) {
sectorsPerCluster = 64;
} else {
// SDXC cards
sectorsPerCluster = 128;
}
// set fake disk geometry
sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63;
if (cardCapacityMB <= 16) {
numberOfHeads = 2;
} else if (cardCapacityMB <= 32) {
numberOfHeads = 4;
} else if (cardCapacityMB <= 128) {
numberOfHeads = 8;
} else if (cardCapacityMB <= 504) {
numberOfHeads = 16;
} else if (cardCapacityMB <= 1008) {
numberOfHeads = 32;
} else if (cardCapacityMB <= 2016) {
numberOfHeads = 64;
} else if (cardCapacityMB <= 4032) {
numberOfHeads = 128;
} else {
numberOfHeads = 255;
}
return true;
}
//------------------------------------------------------------------------------
// zero cache and optionally set the sector signature
void clearCache(uint8_t addSig, cache_t & cache)
{
memset(&cache, 0, sizeof(cache));
if (addSig) {
cache.mbr.mbrSig0 = BOOTSIG0;
cache.mbr.mbrSig1 = BOOTSIG1;
}
}
//------------------------------------------------------------------------------
// zero FAT and root dir area on SD
bool clearFatDir(uint32_t bgn, uint32_t count, Sd2Card & card, cache_t & cache, ESP3DOutput * output)
{
clearCache(false, cache);
if (!card.writeStart(bgn, count)) {
return false;
}
for (uint32_t i = 0; i < count; i++) {
if ((i & 0XFF) == 0) {
if (output) {
output->print(".");
}
}
if (!card.writeData(cache.data)) {
return false;
}
}
if (!card.writeStop()) {
return false;
}
return true;
}
//------------------------------------------------------------------------------
// return cylinder number for a logical block number
uint16_t lbnToCylinder(uint32_t lbn, uint8_t numberOfHeads, uint8_t sectorsPerTrack)
{
return lbn / (numberOfHeads * sectorsPerTrack);
}
//------------------------------------------------------------------------------
// return head number for a logical block number
uint8_t lbnToHead(uint32_t lbn, uint8_t numberOfHeads, uint8_t sectorsPerTrack)
{
return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack;
}
//------------------------------------------------------------------------------
// return sector number for a logical block number
uint8_t lbnToSector(uint32_t lbn, uint8_t sectorsPerTrack)
{
return (lbn % sectorsPerTrack) + 1;
}
//------------------------------------------------------------------------------
// format and write the Master Boot Record
bool writeMbr(Sd2Card & card, cache_t & cache, uint8_t partType, uint32_t relSector, uint32_t partSize, uint8_t numberOfHeads, uint8_t sectorsPerTrack)
{
clearCache(true, cache);
part_t* p = cache.mbr.part;
p->boot = 0;
uint16_t c = lbnToCylinder(relSector, numberOfHeads, sectorsPerTrack);
if (c > 1023) {
return false;
}
p->beginCylinderHigh = c >> 8;
p->beginCylinderLow = c & 0XFF;
p->beginHead = lbnToHead(relSector, numberOfHeads, sectorsPerTrack);
p->beginSector = lbnToSector(relSector, sectorsPerTrack);
p->type = partType;
uint32_t endLbn = relSector + partSize - 1;
c = lbnToCylinder(endLbn,numberOfHeads, sectorsPerTrack);
if (c <= 1023) {
p->endCylinderHigh = c >> 8;
p->endCylinderLow = c & 0XFF;
p->endHead = lbnToHead(endLbn, numberOfHeads, sectorsPerTrack);
p->endSector = lbnToSector(endLbn, sectorsPerTrack);
} else {
// Too big flag, c = 1023, h = 254, s = 63
p->endCylinderHigh = 3;
p->endCylinderLow = 255;
p->endHead = 254;
p->endSector = 63;
}
p->firstSector = relSector;
p->totalSectors = partSize;
if (!writeCache(0, card, cache)) {
return false;
}
return true;
}
//------------------------------------------------------------------------------
// generate serial number from card size and micros since boot
uint32_t volSerialNumber(uint32_t cardSizeBlocks)
{
return (cardSizeBlocks << 8) + micros();
}
// format the SD as FAT16
bool makeFat16(uint32_t & dataStart, Sd2Card & card, cache_t & cache, uint8_t numberOfHeads, uint8_t sectorsPerTrack, uint32_t cardSizeBlocks, uint8_t sectorsPerCluster, uint32_t &relSector, uint8_t & partType, uint32_t &fatSize, uint32_t &fatStart, ESP3DOutput * output)
{
uint32_t nc;
for (dataStart = 2 * BU16;; dataStart += BU16) {
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
fatSize = (nc + 2 + 255)/256;
uint32_t r = BU16 + 1 + 2 * fatSize + 32;
if (dataStart < r) {
continue;
}
relSector = dataStart - r + BU16;
break;
}
// check valid cluster count for FAT16 volume
if (nc < 4085 || nc >= 65525) {
return false;
}
uint16_t reservedSectors = 1;
fatStart = relSector + reservedSectors;
uint32_t partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32;
if (partSize < 32680) {
partType = 0X01;
} else if (partSize < 65536) {
partType = 0X04;
} else {
partType = 0X06;
}
// write MBR
if (!writeMbr(card, cache, partType, relSector, partSize, numberOfHeads, sectorsPerTrack)) {
return false;
}
clearCache(true, cache);
fat_boot_t* pb = &cache.fbs;
pb->jump[0] = 0XEB;
pb->jump[1] = 0X00;
pb->jump[2] = 0X90;
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
pb->oemId[i] = ' ';
}
pb->bytesPerSector = 512;
pb->sectorsPerCluster = sectorsPerCluster;
pb->reservedSectorCount = reservedSectors;
pb->fatCount = 2;
pb->rootDirEntryCount = 512;
pb->mediaType = 0XF8;
pb->sectorsPerFat16 = fatSize;
pb->sectorsPerTrack = sectorsPerTrack;
pb->headCount = numberOfHeads;
pb->hidddenSectors = relSector;
pb->totalSectors32 = partSize;
pb->driveNumber = 0X80;
pb->bootSignature = EXTENDED_BOOT_SIG;
pb->volumeSerialNumber = volSerialNumber(cardSizeBlocks);
memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
memcpy(pb->fileSystemType, fat16str, sizeof(pb->fileSystemType));
// write partition boot sector
if (!writeCache(relSector, card, cache)) {
return false;
}
// clear FAT and root directory
clearFatDir(fatStart, dataStart - fatStart, card, cache, output);
clearCache(false, cache);
cache.fat16[0] = 0XFFF8;
cache.fat16[1] = 0XFFFF;
// write first block of FAT and backup for reserved clusters
if (!writeCache(fatStart, card, cache)
|| !writeCache(fatStart + fatSize, card, cache)) {
return false;
}
return true;
}
// format the SD as FAT32
bool makeFat32(uint32_t & dataStart, Sd2Card & card, cache_t & cache, uint8_t numberOfHeads, uint8_t sectorsPerTrack, uint32_t cardSizeBlocks, uint8_t sectorsPerCluster, uint32_t &relSector, uint8_t & partType, uint32_t &fatSize, uint32_t &fatStart, ESP3DOutput * output)
{
uint32_t nc;
relSector = BU32;
for (dataStart = 2 * BU32;; dataStart += BU32) {
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
fatSize = (nc + 2 + 127)/128;
uint32_t r = relSector + 9 + 2 * fatSize;
if (dataStart >= r) {
break;
}
}
// error if too few clusters in FAT32 volume
if (nc < 65525) {
return false;
}
uint16_t reservedSectors = dataStart - relSector - 2 * fatSize;
fatStart = relSector + reservedSectors;
uint32_t partSize = nc * sectorsPerCluster + dataStart - relSector;
// type depends on address of end sector
// max CHS has lbn = 16450560 = 1024*255*63
if ((relSector + partSize) <= 16450560) {
// FAT32
partType = 0X0B;
} else {
// FAT32 with INT 13
partType = 0X0C;
}
if (!writeMbr(card, cache, partType, relSector, partSize, numberOfHeads, sectorsPerTrack)) {
return false;
}
clearCache(true, cache);
fat32_boot_t* pb = &cache.fbs32;
pb->jump[0] = 0XEB;
pb->jump[1] = 0X00;
pb->jump[2] = 0X90;
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
pb->oemId[i] = ' ';
}
pb->bytesPerSector = 512;
pb->sectorsPerCluster = sectorsPerCluster;
pb->reservedSectorCount = reservedSectors;
pb->fatCount = 2;
pb->mediaType = 0XF8;
pb->sectorsPerTrack = sectorsPerTrack;
pb->headCount = numberOfHeads;
pb->hidddenSectors = relSector;
pb->totalSectors32 = partSize;
pb->sectorsPerFat32 = fatSize;
pb->fat32RootCluster = 2;
pb->fat32FSInfo = 1;
pb->fat32BackBootBlock = 6;
pb->driveNumber = 0X80;
pb->bootSignature = EXTENDED_BOOT_SIG;
pb->volumeSerialNumber = volSerialNumber(cardSizeBlocks);
memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType));
// write partition boot sector and backup
if (!writeCache(relSector, card, cache)
|| !writeCache(relSector + 6, card, cache)) {
return false;
}
clearCache(true, cache);
// write extra boot area and backup
if (!writeCache(relSector + 2, card, cache)
|| !writeCache(relSector + 8, card, cache)) {
return false;
}
fat32_fsinfo_t* pf = &cache.fsinfo;
pf->leadSignature = FSINFO_LEAD_SIG;
pf->structSignature = FSINFO_STRUCT_SIG;
pf->freeCount = 0XFFFFFFFF;
pf->nextFree = 0XFFFFFFFF;
// write FSINFO sector and backup
if (!writeCache(relSector + 1, card, cache)
|| !writeCache(relSector + 7, card, cache)) {
return false;
}
clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster, card, cache, output);
clearCache(false, cache);
cache.fat32[0] = 0x0FFFFFF8;
cache.fat32[1] = 0x0FFFFFFF;
cache.fat32[2] = 0x0FFFFFFF;
// write first block of FAT and backup for reserved clusters
if (!writeCache(fatStart, card, cache)
|| !writeCache(fatStart + fatSize, card, cache)) {
return false;
}
return true;
}
bool eraseCard(Sd2Card & card, cache_t & cache, uint32_t cardSizeBlocks, ESP3DOutput * output)
{
uint32_t firstBlock = 0;
uint32_t lastBlock;
if (output) {
output->printMSG("Erasing ", false);
}
do {
lastBlock = firstBlock + ERASE_SIZE - 1;
if (lastBlock >= cardSizeBlocks) {
lastBlock = cardSizeBlocks - 1;
}
if (!card.erase(firstBlock, lastBlock)) {
return false;
}
if (output) {
output->print(".");
}
firstBlock += ERASE_SIZE;
} while (firstBlock < cardSizeBlocks);
if (!card.readBlock(0, cache.data)) {
return false;
}
if (output) {
output->printLN("");
}
return true;
}
bool formatCard(uint32_t & dataStart, Sd2Card & card,
cache_t & cache, uint32_t cardSizeBlocks,
uint32_t &relSector,
uint8_t & partType,
uint32_t &fatSize, uint32_t &fatStart,
uint32_t cardCapacityMB, ESP3DOutput * output)
{
// Fake disk geometry
uint8_t numberOfHeads;
uint8_t sectorsPerTrack;
// FAT parameters
uint8_t sectorsPerCluster;
initSizes(cardCapacityMB, sectorsPerCluster, numberOfHeads, sectorsPerTrack);
if (card.type() != SD_CARD_TYPE_SDHC) {
if (output) {
output->printMSG("Formating FAT16 ");
}
if(!makeFat16(dataStart, card, cache, numberOfHeads, sectorsPerTrack, cardSizeBlocks, sectorsPerCluster, relSector, partType, fatSize, fatStart, output)) {
return false;
}
} else {
if (output) {
output->printMSG("Formating FAT32 ", false);
}
if(!makeFat32(dataStart, card, cache, numberOfHeads, sectorsPerTrack, cardSizeBlocks, sectorsPerCluster, relSector, partType, fatSize, fatStart, output)) {
return false;
}
}
if (output) {
output->printLN("");
}
return true;
}
bool ESP_SD::format(ESP3DOutput * output)
{
if (ESP_SD::getState(true) == ESP_SDCARD_IDLE) {
Sd2Card card;
uint32_t cardSizeBlocks;
uint32_t cardCapacityMB;
// cache for SD block
cache_t cache;
// MBR information
uint8_t partType;
uint32_t relSector;
// FAT parameters
uint32_t fatStart;
uint32_t fatSize;
uint32_t dataStart;
if (!card.begin((ESP_SD_CS_PIN == -1)?SS:ESP_SD_CS_PIN, SD_SCK_HZ(F_CPU/_spi_speed_divider))) {
return false;
}
cardSizeBlocks = card.cardSize();
if (cardSizeBlocks == 0) {
return false;
}
cardCapacityMB = (cardSizeBlocks + 2047)/2048;
if (output) {
String s = "Capacity detected :" + String((1.048576*cardCapacityMB)/1024) + "GB";
output->printMSG(s.c_str());
}
if (!eraseCard(card, cache, cardSizeBlocks, output)) {
return false;
}
if (!formatCard(dataStart, card, cache, cardSizeBlocks,
relSector, partType,
fatSize, fatStart, cardCapacityMB,output)) {
return false;
}
return true;
}
return false;
}
ESP_SDFile ESP_SD::open(const char* path, uint8_t mode)
{
//do some check
if(((strcmp(path,"/") == 0) && ((mode == ESP_FILE_WRITE) || (mode == ESP_FILE_APPEND))) || (strlen(path) == 0)) {
_sizechanged = true;
return ESP_SDFile();
}
// path must start by '/'
if (path[0] != '/') {
return ESP_SDFile();
}
if (mode != ESP_FILE_READ) {
//check container exists
String p = path;
p.remove(p.lastIndexOf('/') +1);
if (!exists(p.c_str())) {
log_esp3d("Error opening: %s", path);
return ESP_SDFile();
}
}
sdfat::File tmp = SD.open(path, (mode == ESP_FILE_READ)?FILE_READ:(mode == ESP_FILE_WRITE)?FILE_WRITE:FILE_WRITE);
ESP_SDFile esptmp(&tmp, tmp.isDir(),(mode == ESP_FILE_READ)?false:true, path);
return esptmp;
}
bool ESP_SD::exists(const char* path)
{
bool res = false;
//root should always be there if started
if (strcmp(path, "/") == 0) {
return _started;
}
log_esp3d("%s exists ?", path);
res = SD.exists(path);
if (!res) {
log_esp3d("Seems not - trying open it");
ESP_SDFile root = ESP_SD::open(path, ESP_FILE_READ);
if (root) {
res = root.isDirectory();
}
}
log_esp3d("Seems %s", res?"yes":"no");
return res;
}
bool ESP_SD::remove(const char *path)
{
_sizechanged = true;
return SD.remove(path);
}
bool ESP_SD::mkdir(const char *path)
{
return SD.mkdir(path);
}
bool ESP_SD::rmdir(const char *path)
{
if (!exists(path)) {
return false;
}
bool res = true;
std::stack <String > pathlist;
String p = path;
pathlist.push(p);
while (pathlist.size() > 0) {
sdfat::File dir = SD.open(pathlist.top().c_str());
dir.rewindDirectory();
sdfat::File f = dir.openNextFile();
bool candelete = true;
while (f) {
if (f.isDir()) {
candelete = false;
String newdir;
char tmp[255];
f.getName(tmp,254);
newdir = tmp;
pathlist.push(newdir);
f.close();
f = sdfat::File();
} else {
char tmp[255];
f.getName(tmp,254);
_sizechanged = true;
SD.remove(tmp);
f.close();
f = dir.openNextFile();
}
}
if (candelete) {
if (pathlist.top() !="/") {
res = SD.rmdir(pathlist.top().c_str());
}
pathlist.pop();
}
dir.close();
}
p = String();
log_esp3d("count %d", pathlist.size());
return res;
}
bool ESP_SDFile::seek(uint32_t pos, uint8_t mode)
{
if (mode == SeekCur) {
return tSDFile_handle[_index].seekCur(pos);
}
if (mode == SeekEnd) {
return tSDFile_handle[_index].seekEnd(pos);
}
// if (mode == SeekSet)
return tSDFile_handle[_index].seekSet(pos);
}
void ESP_SD::closeAll()
{
for (uint8_t i = 0; i < ESP_MAX_SD_OPENHANDLE; i++) {
tSDFile_handle[i].close();
tSDFile_handle[i] = sdfat::File();
}
}
ESP_SDFile::ESP_SDFile(void* handle, bool isdir, bool iswritemode, const char * path)
{
_isdir = isdir;
_dirlist = "";
_index = -1;
_filename = "";
_name = "";
_lastwrite = 0;
_iswritemode = iswritemode;
_size = 0;
if (!handle) {
return ;
}
bool set =false;
for (uint8_t i=0; (i < ESP_MAX_SD_OPENHANDLE) && !set; i++) {
if (!tSDFile_handle[i]) {
tSDFile_handle[i] = *((sdfat::File*)handle);
//filename
char tmp[255];
tSDFile_handle[i].getName(tmp,254);
_filename = path;
//name
_name = tmp;
if (_name.endsWith("/")) {
_name.remove( _name.length() - 1,1);
_isdir = true;
}
if (_name[0] == '/') {
_name.remove( 0, 1);
}
int pos = _name.lastIndexOf('/');
if (pos != -1) {
_name.remove( 0, pos+1);
}
if (_name.length() == 0) {
_name = "/";
}
//size
_size = tSDFile_handle[i].size();
//time
if (!_isdir) {
_lastwrite = getDateTimeFile(tSDFile_handle[i]);
} else {
//no need date time for directory
_lastwrite = 0;
}
_index = i;
//log_esp3d("Opening File at index %d",_index);
set = true;
}
}
}
//todo need also to add short filename
const char* ESP_SDFile::shortname() const
{
static char sname[13];
sdfat::File ftmp = SD.open(_filename.c_str());
if (ftmp) {
ftmp.getSFN(sname);
ftmp.close();
return sname;
} else {
return _name.c_str();
}
}
void ESP_SDFile::close()
{
if (_index != -1) {
//log_esp3d("Closing File at index %d", _index);
tSDFile_handle[_index].close();
//reopen if mode = write
//udate size + date
if (_iswritemode && !_isdir) {
sdfat::File ftmp = SD.open(_filename.c_str());
if (ftmp) {
_size = ftmp.size();
_lastwrite = getDateTimeFile(ftmp);
ftmp.close();
}
}
tSDFile_handle[_index] = sdfat::File();
//log_esp3d("Closing File at index %d",_index);
_index = -1;
}
}
ESP_SDFile ESP_SDFile::openNextFile()
{
if ((_index == -1) || !_isdir) {
log_esp3d("openNextFile failed");
return ESP_SDFile();
}
sdfat::File tmp = tSDFile_handle[_index].openNextFile();
if (tmp) {
char tmps[255];
tmp.getName(tmps,254);
log_esp3d("tmp name :%s %s", tmps, (tmp.isDir())?"isDir":"isFile");
String s = _filename ;
if (s!="/") {
s+="/";
}
s += tmps;
ESP_SDFile esptmp(&tmp, tmp.isDir(),false, s.c_str());
esptmp.close();
return esptmp;
}
return ESP_SDFile();
}
const char * ESP_SD::FilesystemName()
{
return "SDFat - " SD_FAT_VERSION_STR ;
}
#endif //SD_DEVICE == ESP_SDFAT
#endif //ARCH_ESP8266 && SD_DEVICE

View File

@ -243,20 +243,25 @@ bool ESP_SD::rmdir(const char *path)
p.remove( p.length() - 1,1);
}
pathlist.push(p);
while (pathlist.size() > 0) {
while (pathlist.size() > 0 && res) {
File dir = SD_MMC.open(pathlist.top().c_str());
File f = dir.openNextFile();
bool candelete = true;
while (f) {
while (f && res) {
if (f.isDirectory()) {
candelete = false;
String newdir = f.name();
String newdir = pathlist.top()+ '/';
newdir+= f.name();
pathlist.push(newdir);
f.close();
f = File();
} else {
SD_MMC.remove(f.name());
String filepath = pathlist.top()+ '/';
filepath+= f.name();
f.close();
if (!SD_MMC.remove(filepath.c_str())) {
res = false;
}
f = dir.openNextFile();
}
}

View File

@ -118,7 +118,7 @@ void HTTP_Server::SDFileupload ()
break;
}
}
if (pos+1 >= path.length()-1) {
if ((size_t)(pos+1) >= path.length()-1) {
pos=-1;
break;
} else {

View File

@ -1,22 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2011..2017 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.

View File

@ -1,17 +0,0 @@
This is a downstream version of Bill Greiman's <fat16lib@sbcglobal.net>
"SdFat" library for Arduino.
No code changes were intended to be made. Only a custom namespace called
"sdfat" was wrapped around all objects and structures in the library.
This namespace is required because the ESP8266 already has global class
types whose names conflict with SdFat's built-in ones (File, others).
"using namespace sdfat;" at the top of a sketch should make it work as-is,
and all examples have been so updated.
By performing this wrapping I hope to be able to integrate the latest
updates from upstream and pull them into an ESP8266 compatible FS (like
SPIFFS or LittleFS) to make it much more useful and work much better with
the ESP8266 Arduino ecosystem.
-Earle F. Philhower, III <earlephilhower@yahoo.com>

View File

@ -1,42 +0,0 @@
The Arduino SdFat library provides read/write access to FAT16/FAT32
file systems on SD/SDHC flash cards.
SdFat requires Arduino 1.6x or greater.
Key changes:
Support for multiple SPI ports now uses a pointer to a SPIClass object.
See the STM32Test example.
```
explicit SdFat(SPIClass* spiPort);
\\ or
explicit SdFatEX(SPIClass* spiPort);
```
Open flags now follow POSIX conventions. O_RDONLY is the same as O_READ and O_WRONLY is the
same as O_WRITE. One and only one of of the mode flags, O_RDONLY, O_RDWR, or
O_WRONLY is required.
Open flags for Particle Gen3 and ARM boards are now defined by fcntl.h.
See SdFatConfig.h for options.
Support for particle Gen3 devices.
New cluster allocation algorithm.
Please read changes.txt and the html documentation in the extras folder for more information.
Please report problems as issues.
A number of configuration options can be set by editing SdFatConfig.h
define macros. See the html documentation for details
Please read the html documentation for this library. Start with
html/index.html and read the Main Page. Next go to the Classes tab and
read the documentation for the classes SdFat, SdFatEX, SdBaseFile,
SdFile, File, StdioStream, ifstream, ofstream, and others.
Please continue by reading the html documentation in SdFat/extras/html
Updated 28 Dec 2018

View File

@ -1,199 +0,0 @@
// 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"
using namespace sdfat;
#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) {
SysCall::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()) {
SysCall::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!");
SysCall::halt();
}

View File

@ -1,48 +0,0 @@
/*
* Program to test Short File Name character case flags.
*/
#include <SPI.h>
#include "SdFat.h"
using namespace sdfat;
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) {
SysCall::yield();
}
Serial.println("type any character to start");
while (!Serial.available()) {
SysCall::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

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

View File

@ -1,31 +0,0 @@
// 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"
using namespace sdfat;
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

@ -1,127 +0,0 @@
/*
* This program is a simple Print benchmark.
*/
#include <SPI.h>
#include <SD.h>
using namespace sdfat;
// 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

@ -1,32 +0,0 @@
/*
* Program to compare size of Arduino SD library with SdFat.
* See SdFatSize.ino for SdFat program.
*/
#include <SPI.h>
#include <SD.h>
using namespace sdfat;
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

@ -1,35 +0,0 @@
/*
* Program to compare size of SdFat with Arduino SD library.
* See SD_Size.ino for Arduino SD program.
*
*/
#include <SPI.h>
#include "SdFat.h"
using namespace sdfat;
SdFat sd;
SdFile file;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::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

@ -1,47 +0,0 @@
// Simple demo of the Stream parsInt() member function.
#include <SPI.h>
// The next two lines replace #include <SD.h>.
#include "SdFat.h"
using namespace sdfat;
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) {
SysCall::yield();
}
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
SysCall::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

@ -1,79 +0,0 @@
/*
* 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"
using namespace sdfat;
// 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) {
SysCall::yield();
}
// F() stores strings in flash to save RAM
cout << endl << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,84 +0,0 @@
/*
* Calculate the sum and average of a list of floating point numbers
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::yield();
}
// F() stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,151 +0,0 @@
/*
* This program is a simple binary write/read benchmark
* for the standard Arduino SD.h library.
*/
#include <SPI.h>
#include <SD.h>
using namespace sdfat;
// 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 (uint16_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

@ -1,41 +0,0 @@
/*
* Use of ibufsteam to parse a line and obufstream to format a line
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::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

@ -1,41 +0,0 @@
/*
* Demo of ArduinoInStream and ArduinoOutStream
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::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

@ -1,65 +0,0 @@
/*
* Append a line to a file - demo of pathnames and streams
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::yield();
}
// F() stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,113 +0,0 @@
// Demo of rewriting a line read by fgets
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,53 +0,0 @@
/*
* Read the logfile created by the eventlog.ino example.
* Demo of pathnames and working directories
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::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

@ -1,34 +0,0 @@
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

@ -1,39 +0,0 @@
#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

@ -1,829 +0,0 @@
/**
* 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 "FreeStack.h"
#include "AnalogBinLogger.h"
using namespace sdfat;
//------------------------------------------------------------------------------
// 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, FILE_BLOCK_COUNT)) {
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()) {
SysCall::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

@ -1,132 +0,0 @@
/*
* Example use of chdir(), ls(), mkdir(), and rmdir().
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// SD card chip select pin.
const uint8_t chipSelect = SS;
//------------------------------------------------------------------------------
// File system object.
SdFat sd;
// Directory file.
SdFile root;
// Use for file creation in folders.
SdFile file;
// Create a Serial output stream.
ArduinoOutStream cout(Serial);
// Buffer for Serial input.
char cinBuf[40];
// Create a serial input stream.
ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));
//==============================================================================
// Error messages stored in flash.
#define error(msg) sd.errorHalt(F(msg))
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
delay(1000);
cout << F("Type any character to start\n");
// Wait for input line and discard.
cin.readline();
cout << 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();
}
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 failed");
}
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

@ -1,104 +0,0 @@
// 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"
using namespace sdfat;
// 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()) {
SysCall::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

@ -1,4 +0,0 @@
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

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

View File

@ -1,5 +0,0 @@
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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
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

@ -1,5 +0,0 @@
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

@ -1,658 +0,0 @@
/**
* 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"
sdfat::MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
using namespace sdfat;
//==============================================================================
// 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) {
SysCall::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()) {
SysCall::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) {
SysCall::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()) {
SysCall::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()) {
SysCall::yield();
}
#if WDT_YIELD_TIME_MICROS
Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
SysCall::halt();
#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

@ -1,43 +0,0 @@
#include "UserTypes.h"
// User data functions. Modify these functions for your data items.
using namespace sdfat;
// 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

@ -1,15 +0,0 @@
#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

@ -1,658 +0,0 @@
/**
* 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"
sdfat::MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
using namespace sdfat;
//==============================================================================
// 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) {
SysCall::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()) {
SysCall::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) {
SysCall::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()) {
SysCall::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()) {
SysCall::yield();
}
#if WDT_YIELD_TIME_MICROS
Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
SysCall::halt();
#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

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

View File

@ -1,72 +0,0 @@
#include "UserTypes.h"
// User data functions. Modify these functions for your data items.
using namespace sdfat;
// 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

@ -1,17 +0,0 @@
#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

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

View File

@ -1,658 +0,0 @@
/**
* 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"
sdfat::MinimumSerial MinSerial;
#define Serial MinSerial
#endif // __AVR_ATmega328P__
using namespace sdfat;
//==============================================================================
// 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) {
SysCall::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()) {
SysCall::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) {
SysCall::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()) {
SysCall::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()) {
SysCall::yield();
}
#if WDT_YIELD_TIME_MICROS
Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
SysCall::halt();
#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

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

View File

@ -1,54 +0,0 @@
// User data functions. Modify these functions for your data items.
#include "UserTypes.h"
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
using namespace sdfat;
//------------------------------------------------------------------------------
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

@ -1,18 +0,0 @@
#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

@ -1,62 +0,0 @@
/*
* Print size, modify date/time, and name for all files in root.
*/
#include <SPI.h>
#include "SdFat.h"
using namespace sdfat;
// SD default chip select pin.
const uint8_t chipSelect = SS;
// file system object
SdFat sd;
SdFile root;
SdFile file;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
SysCall::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();
}
if (!root.open("/")) {
sd.errorHalt("open root failed");
}
// Open next file in root.
// Warning, openNext starts at the current directory position
// so a rewind of the directory may be required.
while (file.openNext(&root, 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 (root.getError()) {
Serial.println("openNext failed");
} else {
Serial.println("Done!");
}
}
//------------------------------------------------------------------------------
void loop() {}

View File

@ -1,154 +0,0 @@
/*
* This program is a simple Print benchmark.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#include "FreeStack.h"
using namespace sdfat;
// 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) {
SysCall::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()) {
SysCall::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

@ -1,164 +0,0 @@
// Quick hardware test for SPI card access.
//
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
//
// 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)
//------------------------------------------------------------------------------
// File system object.
SdFat sd;
// 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 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) {
SysCall::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;
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.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
if (!firstTry) {
cout << F("\nRestarting\n");
}
firstTry = false;
cout << F("\nEnter the chip select pin number: ");
while (!Serial.available()) {
SysCall::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;
}
if (sd.vol()->fatType() == 0) {
cout << F("Can't find a valid FAT16/FAT32 partition.\n");
reformatMsg();
return;
}
cout << F("begin failed, can't determine error type\n");
return;
}
cout << F("\nCard successfully initialized.\n");
cout << endl;
uint32_t size = sd.card()->cardSize();
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;
cout << F("Volume is FAT") << int(sd.vol()->fatType());
cout << F(", Cluster size (bytes): ") << 512L * sd.vol()->blocksPerCluster();
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()->blocksPerCluster() < 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.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
cout << F("\nSuccess! Type any character to restart.\n");
while (!Serial.available()) {
SysCall::yield();
}
}

View File

@ -1,182 +0,0 @@
/*
* 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"
using namespace sdfat;
// 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) {
SysCall::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()) {
SysCall::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

@ -1,215 +0,0 @@
// 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>
using namespace sdfat;
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

@ -1,141 +0,0 @@
// 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
using namespace sdfat;
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)); SysCall::halt();}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
SysCall::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

@ -1,123 +0,0 @@
/*
* 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"
using namespace sdfat;
// 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) {
SysCall::yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,84 +0,0 @@
/*
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"
using namespace sdfat;
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

@ -1,177 +0,0 @@
/*
* 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"
using namespace sdfat;
// 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

@ -1,554 +0,0 @@
/*
* This program will format an SD or SDHC card.
* Warning all data will be deleted!
*
* For SD/SDHC cards larger than 64 MB this
* program attempts to match the format
* generated by SDFormatter available here:
*
* http://www.sdcard.org/consumers/formatter/
*
* For smaller cards this program uses FAT16
* and SDFormatter uses FAT12.
*/
// Set USE_SDIO to zero for SPI card access.
#define USE_SDIO 0
//
// Change the value of chipSelect if 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
const uint8_t chipSelect = SS;
// Initialize at highest supported speed not over 50 MHz.
// Reduce max speed if errors occur.
#define SPI_SPEED SD_SCK_MHZ(50)
// Print extra info for debug if DEBUG_PRINT is nonzero
#define DEBUG_PRINT 0
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#if DEBUG_PRINT
#include "FreeStack.h"
#endif // DEBUG_PRINT
using namespace sdfat;
// Serial output stream
ArduinoOutStream cout(Serial);
#if USE_SDIO
// Use faster SdioCardEX
SdioCardEX card;
// SdioCard card;
#else // USE_SDIO
Sd2Card card;
#endif // USE_SDIO
uint32_t cardSizeBlocks;
uint32_t cardCapacityMB;
// cache for SD block
cache_t cache;
// MBR information
uint8_t partType;
uint32_t relSector;
uint32_t partSize;
// Fake disk geometry
uint8_t numberOfHeads;
uint8_t sectorsPerTrack;
// FAT parameters
uint16_t reservedSectors;
uint8_t sectorsPerCluster;
uint32_t fatStart;
uint32_t fatSize;
uint32_t dataStart;
// constants for file system structure
uint16_t const BU16 = 128;
uint16_t const BU32 = 8192;
// strings needed in file system structures
char noName[] = "NO NAME ";
char fat16str[] = "FAT16 ";
char fat32str[] = "FAT32 ";
//------------------------------------------------------------------------------
#define sdError(msg) {cout << F("error: ") << F(msg) << endl; sdErrorHalt();}
//------------------------------------------------------------------------------
void sdErrorHalt() {
if (card.errorCode()) {
cout << F("SD error: ") << hex << int(card.errorCode());
cout << ',' << int(card.errorData()) << dec << endl;
}
SysCall::halt();
}
//------------------------------------------------------------------------------
#if DEBUG_PRINT
void debugPrint() {
cout << F("FreeStack: ") << FreeStack() << endl;
cout << F("partStart: ") << relSector << endl;
cout << F("partSize: ") << partSize << endl;
cout << F("reserved: ") << reservedSectors << endl;
cout << F("fatStart: ") << fatStart << endl;
cout << F("fatSize: ") << fatSize << endl;
cout << F("dataStart: ") << dataStart << endl;
cout << F("clusterCount: ");
cout << ((relSector + partSize - dataStart)/sectorsPerCluster) << endl;
cout << endl;
cout << F("Heads: ") << int(numberOfHeads) << endl;
cout << F("Sectors: ") << int(sectorsPerTrack) << endl;
cout << F("Cylinders: ");
cout << cardSizeBlocks/(numberOfHeads*sectorsPerTrack) << endl;
}
#endif // DEBUG_PRINT
//------------------------------------------------------------------------------
// write cached block to the card
uint8_t writeCache(uint32_t lbn) {
return card.writeBlock(lbn, cache.data);
}
//------------------------------------------------------------------------------
// initialize appropriate sizes for SD capacity
void initSizes() {
if (cardCapacityMB <= 6) {
sdError("Card is too small.");
} else if (cardCapacityMB <= 16) {
sectorsPerCluster = 2;
} else if (cardCapacityMB <= 32) {
sectorsPerCluster = 4;
} else if (cardCapacityMB <= 64) {
sectorsPerCluster = 8;
} else if (cardCapacityMB <= 128) {
sectorsPerCluster = 16;
} else if (cardCapacityMB <= 1024) {
sectorsPerCluster = 32;
} else if (cardCapacityMB <= 32768) {
sectorsPerCluster = 64;
} else {
// SDXC cards
sectorsPerCluster = 128;
}
cout << F("Blocks/Cluster: ") << int(sectorsPerCluster) << endl;
// set fake disk geometry
sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63;
if (cardCapacityMB <= 16) {
numberOfHeads = 2;
} else if (cardCapacityMB <= 32) {
numberOfHeads = 4;
} else if (cardCapacityMB <= 128) {
numberOfHeads = 8;
} else if (cardCapacityMB <= 504) {
numberOfHeads = 16;
} else if (cardCapacityMB <= 1008) {
numberOfHeads = 32;
} else if (cardCapacityMB <= 2016) {
numberOfHeads = 64;
} else if (cardCapacityMB <= 4032) {
numberOfHeads = 128;
} else {
numberOfHeads = 255;
}
}
//------------------------------------------------------------------------------
// zero cache and optionally set the sector signature
void clearCache(uint8_t addSig) {
memset(&cache, 0, sizeof(cache));
if (addSig) {
cache.mbr.mbrSig0 = BOOTSIG0;
cache.mbr.mbrSig1 = BOOTSIG1;
}
}
//------------------------------------------------------------------------------
// zero FAT and root dir area on SD
void clearFatDir(uint32_t bgn, uint32_t count) {
clearCache(false);
#if USE_SDIO
for (uint32_t i = 0; i < count; i++) {
if (!card.writeBlock(bgn + i, cache.data)) {
sdError("Clear FAT/DIR writeBlock failed");
}
if ((i & 0XFF) == 0) {
cout << '.';
}
}
#else // USE_SDIO
if (!card.writeStart(bgn, count)) {
sdError("Clear FAT/DIR writeStart failed");
}
for (uint32_t i = 0; i < count; i++) {
if ((i & 0XFF) == 0) {
cout << '.';
}
if (!card.writeData(cache.data)) {
sdError("Clear FAT/DIR writeData failed");
}
}
if (!card.writeStop()) {
sdError("Clear FAT/DIR writeStop failed");
}
#endif // USE_SDIO
cout << endl;
}
//------------------------------------------------------------------------------
// return cylinder number for a logical block number
uint16_t lbnToCylinder(uint32_t lbn) {
return lbn / (numberOfHeads * sectorsPerTrack);
}
//------------------------------------------------------------------------------
// return head number for a logical block number
uint8_t lbnToHead(uint32_t lbn) {
return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack;
}
//------------------------------------------------------------------------------
// return sector number for a logical block number
uint8_t lbnToSector(uint32_t lbn) {
return (lbn % sectorsPerTrack) + 1;
}
//------------------------------------------------------------------------------
// format and write the Master Boot Record
void writeMbr() {
clearCache(true);
part_t* p = cache.mbr.part;
p->boot = 0;
uint16_t c = lbnToCylinder(relSector);
if (c > 1023) {
sdError("MBR CHS");
}
p->beginCylinderHigh = c >> 8;
p->beginCylinderLow = c & 0XFF;
p->beginHead = lbnToHead(relSector);
p->beginSector = lbnToSector(relSector);
p->type = partType;
uint32_t endLbn = relSector + partSize - 1;
c = lbnToCylinder(endLbn);
if (c <= 1023) {
p->endCylinderHigh = c >> 8;
p->endCylinderLow = c & 0XFF;
p->endHead = lbnToHead(endLbn);
p->endSector = lbnToSector(endLbn);
} else {
// Too big flag, c = 1023, h = 254, s = 63
p->endCylinderHigh = 3;
p->endCylinderLow = 255;
p->endHead = 254;
p->endSector = 63;
}
p->firstSector = relSector;
p->totalSectors = partSize;
if (!writeCache(0)) {
sdError("write MBR");
}
}
//------------------------------------------------------------------------------
// generate serial number from card size and micros since boot
uint32_t volSerialNumber() {
return (cardSizeBlocks << 8) + micros();
}
//------------------------------------------------------------------------------
// format the SD as FAT16
void makeFat16() {
uint32_t nc;
for (dataStart = 2 * BU16;; dataStart += BU16) {
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
fatSize = (nc + 2 + 255)/256;
uint32_t r = BU16 + 1 + 2 * fatSize + 32;
if (dataStart < r) {
continue;
}
relSector = dataStart - r + BU16;
break;
}
// check valid cluster count for FAT16 volume
if (nc < 4085 || nc >= 65525) {
sdError("Bad cluster count");
}
reservedSectors = 1;
fatStart = relSector + reservedSectors;
partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32;
if (partSize < 32680) {
partType = 0X01;
} else if (partSize < 65536) {
partType = 0X04;
} else {
partType = 0X06;
}
// write MBR
writeMbr();
clearCache(true);
fat_boot_t* pb = &cache.fbs;
pb->jump[0] = 0XEB;
pb->jump[1] = 0X00;
pb->jump[2] = 0X90;
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
pb->oemId[i] = ' ';
}
pb->bytesPerSector = 512;
pb->sectorsPerCluster = sectorsPerCluster;
pb->reservedSectorCount = reservedSectors;
pb->fatCount = 2;
pb->rootDirEntryCount = 512;
pb->mediaType = 0XF8;
pb->sectorsPerFat16 = fatSize;
pb->sectorsPerTrack = sectorsPerTrack;
pb->headCount = numberOfHeads;
pb->hidddenSectors = relSector;
pb->totalSectors32 = partSize;
pb->driveNumber = 0X80;
pb->bootSignature = EXTENDED_BOOT_SIG;
pb->volumeSerialNumber = volSerialNumber();
memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
memcpy(pb->fileSystemType, fat16str, sizeof(pb->fileSystemType));
// write partition boot sector
if (!writeCache(relSector)) {
sdError("FAT16 write PBS failed");
}
// clear FAT and root directory
clearFatDir(fatStart, dataStart - fatStart);
clearCache(false);
cache.fat16[0] = 0XFFF8;
cache.fat16[1] = 0XFFFF;
// write first block of FAT and backup for reserved clusters
if (!writeCache(fatStart)
|| !writeCache(fatStart + fatSize)) {
sdError("FAT16 reserve failed");
}
}
//------------------------------------------------------------------------------
// format the SD as FAT32
void makeFat32() {
uint32_t nc;
relSector = BU32;
for (dataStart = 2 * BU32;; dataStart += BU32) {
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
fatSize = (nc + 2 + 127)/128;
uint32_t r = relSector + 9 + 2 * fatSize;
if (dataStart >= r) {
break;
}
}
// error if too few clusters in FAT32 volume
if (nc < 65525) {
sdError("Bad cluster count");
}
reservedSectors = dataStart - relSector - 2 * fatSize;
fatStart = relSector + reservedSectors;
partSize = nc * sectorsPerCluster + dataStart - relSector;
// type depends on address of end sector
// max CHS has lbn = 16450560 = 1024*255*63
if ((relSector + partSize) <= 16450560) {
// FAT32
partType = 0X0B;
} else {
// FAT32 with INT 13
partType = 0X0C;
}
writeMbr();
clearCache(true);
fat32_boot_t* pb = &cache.fbs32;
pb->jump[0] = 0XEB;
pb->jump[1] = 0X00;
pb->jump[2] = 0X90;
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
pb->oemId[i] = ' ';
}
pb->bytesPerSector = 512;
pb->sectorsPerCluster = sectorsPerCluster;
pb->reservedSectorCount = reservedSectors;
pb->fatCount = 2;
pb->mediaType = 0XF8;
pb->sectorsPerTrack = sectorsPerTrack;
pb->headCount = numberOfHeads;
pb->hidddenSectors = relSector;
pb->totalSectors32 = partSize;
pb->sectorsPerFat32 = fatSize;
pb->fat32RootCluster = 2;
pb->fat32FSInfo = 1;
pb->fat32BackBootBlock = 6;
pb->driveNumber = 0X80;
pb->bootSignature = EXTENDED_BOOT_SIG;
pb->volumeSerialNumber = volSerialNumber();
memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel));
memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType));
// write partition boot sector and backup
if (!writeCache(relSector)
|| !writeCache(relSector + 6)) {
sdError("FAT32 write PBS failed");
}
clearCache(true);
// write extra boot area and backup
if (!writeCache(relSector + 2)
|| !writeCache(relSector + 8)) {
sdError("FAT32 PBS ext failed");
}
fat32_fsinfo_t* pf = &cache.fsinfo;
pf->leadSignature = FSINFO_LEAD_SIG;
pf->structSignature = FSINFO_STRUCT_SIG;
pf->freeCount = 0XFFFFFFFF;
pf->nextFree = 0XFFFFFFFF;
// write FSINFO sector and backup
if (!writeCache(relSector + 1)
|| !writeCache(relSector + 7)) {
sdError("FAT32 FSINFO failed");
}
clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster);
clearCache(false);
cache.fat32[0] = 0x0FFFFFF8;
cache.fat32[1] = 0x0FFFFFFF;
cache.fat32[2] = 0x0FFFFFFF;
// write first block of FAT and backup for reserved clusters
if (!writeCache(fatStart)
|| !writeCache(fatStart + fatSize)) {
sdError("FAT32 reserve failed");
}
}
//------------------------------------------------------------------------------
// 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 >= cardSizeBlocks) {
lastBlock = cardSizeBlocks - 1;
}
if (!card.erase(firstBlock, lastBlock)) {
sdError("erase failed");
}
cout << '.';
if ((n++)%32 == 31) {
cout << endl;
}
firstBlock += ERASE_SIZE;
} while (firstBlock < cardSizeBlocks);
cout << endl;
if (!card.readBlock(0, cache.data)) {
sdError("readBlock");
}
cout << hex << showbase << setfill('0') << internal;
cout << F("All data set to ") << setw(4) << int(cache.data[0]) << endl;
cout << dec << noshowbase << setfill(' ') << right;
cout << F("Erase done\n");
}
//------------------------------------------------------------------------------
void formatCard() {
cout << endl;
cout << F("Formatting\n");
initSizes();
if (card.type() != SD_CARD_TYPE_SDHC) {
cout << F("FAT16\n");
makeFat16();
} else {
cout << F("FAT32\n");
makeFat32();
}
#if DEBUG_PRINT
debugPrint();
#endif // DEBUG_PRINT
cout << F("Format done\n");
}
//------------------------------------------------------------------------------
void setup() {
char c;
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::yield();
}
// Discard any extra characters.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
cout << F(
"\n"
"This program can erase and/or format SD/SDHC 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 larger than 2 GB will be formatted FAT32 and\n"
"smaller cards will be formatted FAT16.\n"
"\n"
"Warning, all data on the card will be erased.\n"
"Enter 'Y' to continue: ");
while (!Serial.available()) {
SysCall::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.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
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()) {
SysCall::yield();
}
c = Serial.read();
cout << c << endl;
if (!strchr("EFQ", c)) {
cout << F("Quiting, invalid option entered.") << endl;
return;
}
#if USE_SDIO
if (!card.begin()) {
sdError("card.begin failed");
}
#else // USE_SDIO
if (!card.begin(chipSelect, SPI_SPEED)) {
cout << F(
"\nSD initialization failure!\n"
"Is the SD card inserted correctly?\n"
"Is chip select correct at the top of this program?\n");
sdError("card.begin failed");
}
#endif
cardSizeBlocks = card.cardSize();
if (cardSizeBlocks == 0) {
sdError("cardSize");
}
cardCapacityMB = (cardSizeBlocks + 2047)/2048;
cout << F("Card Size: ") << setprecision(0) << 1.048576*cardCapacityMB;
cout << F(" MB, (MB = 1,000,000 bytes)") << endl;
if (c == 'E' || c == 'F') {
eraseCard();
}
if (c == 'F' || c == 'Q') {
formatCard();
}
}
//------------------------------------------------------------------------------
void loop() {}

View File

@ -1,250 +0,0 @@
/*
* This program attempts to initialize an SD card and analyze its structure.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// Set USE_SDIO to zero for SPI card access.
#define USE_SDIO 0
/*
* SD chip select pin. Common values are:
*
* Arduino Ethernet shield, pin 4.
* SparkFun SD shield, pin 8.
* Adafruit SD shields and modules, pin 10.
* Default SD chip select is the SPI SS pin.
*/
const uint8_t SD_CHIP_SELECT = SS;
/*
* 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;
#if USE_SDIO
// Use faster SdioCardEX
SdFatSdioEX sd;
// SdFatSdio sd;
#else // USE_SDIO
SdFat sd;
#endif // USE_SDIO
// serial output steam
ArduinoOutStream cout(Serial);
// global for card size
uint32_t cardSize;
// global for card erase size
uint32_t eraseSize;
//------------------------------------------------------------------------------
// store error strings in flash
#define sdErrorMsg(msg) sd.errorPrint(F(msg));
//------------------------------------------------------------------------------
uint8_t cidDmp() {
cid_t cid;
if (!sd.card()->readCID(&cid)) {
sdErrorMsg("readCID failed");
return false;
}
cout << F("\nManufacturer ID: ");
cout << 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("\nVersion: ");
cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl;
cout << F("Serial number: ") << hex << cid.psn << dec << endl;
cout << F("Manufacturing date: ");
cout << int(cid.mdt_month) << '/';
cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl;
cout << endl;
return true;
}
//------------------------------------------------------------------------------
uint8_t csdDmp() {
csd_t csd;
uint8_t eraseSingleBlock;
if (!sd.card()->readCSD(&csd)) {
sdErrorMsg("readCSD failed");
return false;
}
if (csd.v1.csd_ver == 0) {
eraseSingleBlock = csd.v1.erase_blk_en;
eraseSize = (csd.v1.sector_size_high << 1) | csd.v1.sector_size_low;
} else if (csd.v2.csd_ver == 1) {
eraseSingleBlock = csd.v2.erase_blk_en;
eraseSize = (csd.v2.sector_size_high << 1) | csd.v2.sector_size_low;
} else {
cout << F("csd version error\n");
return false;
}
eraseSize++;
cout << F("cardSize: ") << 0.000512*cardSize;
cout << F(" MB (MB = 1,000,000 bytes)\n");
cout << F("flashEraseSize: ") << int(eraseSize) << F(" blocks\n");
cout << F("eraseSingleBlock: ");
if (eraseSingleBlock) {
cout << F("true\n");
} else {
cout << F("false\n");
}
return true;
}
//------------------------------------------------------------------------------
// print partition table
uint8_t partDmp() {
mbr_t mbr;
if (!sd.card()->readBlock(0, (uint8_t*)&mbr)) {
sdErrorMsg("read MBR failed");
return false;
}
for (uint8_t ip = 1; ip < 5; ip++) {
part_t *pt = &mbr.part[ip - 1];
if ((pt->boot & 0X7F) != 0 || pt->firstSector > cardSize) {
cout << F("\nNo MBR. Assuming Super Floppy format.\n");
return true;
}
}
cout << F("\nSD Partition Table\n");
cout << F("part,boot,type,start,length\n");
for (uint8_t ip = 1; ip < 5; ip++) {
part_t *pt = &mbr.part[ip - 1];
cout << int(ip) << ',' << hex << int(pt->boot) << ',' << int(pt->type);
cout << dec << ',' << pt->firstSector <<',' << pt->totalSectors << endl;
}
return true;
}
//------------------------------------------------------------------------------
void volDmp() {
cout << F("\nVolume is FAT") << int(sd.vol()->fatType()) << endl;
cout << F("blocksPerCluster: ") << int(sd.vol()->blocksPerCluster()) << endl;
cout << F("clusterCount: ") << sd.vol()->clusterCount() << endl;
cout << F("freeClusters: ");
uint32_t volFree = sd.vol()->freeClusterCount();
cout << volFree << endl;
float fs = 0.000512*volFree*sd.vol()->blocksPerCluster();
cout << F("freeSpace: ") << fs << F(" MB (MB = 1,000,000 bytes)\n");
cout << F("fatStartBlock: ") << sd.vol()->fatStartBlock() << endl;
cout << F("fatCount: ") << int(sd.vol()->fatCount()) << endl;
cout << F("blocksPerFat: ") << sd.vol()->blocksPerFat() << endl;
cout << F("rootDirStart: ") << sd.vol()->rootDirStart() << endl;
cout << F("dataStartBlock: ") << sd.vol()->dataStartBlock() << endl;
if (sd.vol()->dataStartBlock() % eraseSize) {
cout << F("Data area is not aligned on flash erase boundaries!\n");
cout << F("Download and use formatter from www.sdcard.org!\n");
}
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
// use uppercase in hex and use 0X base prefix
cout << uppercase << showbase << endl;
// F stores strings in flash to save RAM
cout << F("SdFat version: ") << SD_FAT_VERSION << endl;
#if !USE_SDIO
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);
}
cout << F("\nAssuming the SD chip select pin is: ") <<int(SD_CHIP_SELECT);
cout << F("\nEdit SD_CHIP_SELECT to change the SD chip select pin.\n");
#endif // !USE_SDIO
}
//------------------------------------------------------------------------------
void loop() {
// Read any existing Serial data.
do {
delay(10);
} while (Serial.available() && Serial.read() >= 0);
// F stores strings in flash to save RAM
cout << F("\ntype any character to start\n");
while (!Serial.available()) {
SysCall::yield();
}
uint32_t t = millis();
#if USE_SDIO
if (!sd.cardBegin()) {
sdErrorMsg("\ncardBegin failed");
return;
}
#else // USE_SDIO
// 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.cardBegin(SD_CHIP_SELECT, SD_SCK_MHZ(50))) {
sdErrorMsg("cardBegin failed");
return;
}
#endif // USE_SDIO
t = millis() - t;
cardSize = sd.card()->cardSize();
if (cardSize == 0) {
sdErrorMsg("cardSize failed");
return;
}
cout << F("\ninit time: ") << t << " ms" << endl;
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 (cardSize < 70000000) {
cout << F("SDHC\n");
} else {
cout << F("SDXC\n");
}
break;
default:
cout << F("Unknown\n");
}
if (!cidDmp()) {
return;
}
if (!csdDmp()) {
return;
}
uint32_t ocr;
if (!sd.card()->readOCR(&ocr)) {
sdErrorMsg("\nreadOCR failed");
return;
}
cout << F("OCR: ") << hex << ocr << dec << endl;
if (!partDmp()) {
return;
}
if (!sd.fsBegin()) {
sdErrorMsg("\nFile System initialization failed.\n");
return;
}
volDmp();
}

View File

@ -1,61 +0,0 @@
// An example of the SdFatSoftSpi template class.
// This example is for an 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 <SPI.h>
#include "SdFat.h"
using namespace sdfat;
#if ENABLE_SOFTWARE_SPI_CLASS // Must be set in SdFat/SdFatConfig.h
//
// 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;
//
// Chip select may be constant or RAM variable.
const uint8_t SD_CHIP_SELECT_PIN = 10;
// SdFat software SPI template
SdFatSoftSpi<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN> sd;
// Test file.
SdFile file;
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
Serial.println("Type any character to start");
while (!Serial.available()) {
SysCall::yield();
}
if (!sd.begin(SD_CHIP_SELECT_PIN)) {
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 // ENABLE_SOFTWARE_SPI_CLASS
#error ENABLE_SOFTWARE_SPI_CLASS must be set non-zero in SdFat/SdFatConfig.h
#endif //ENABLE_SOFTWARE_SPI_CLASS

View File

@ -1,216 +0,0 @@
// Benchmark comparing SdFile and StdioStream.
#include <SPI.h>
#include "SdFat.h"
using namespace sdfat;
// 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;
uint32_t stdioSize = 0;
uint32_t printTime;
uint32_t stdioTime = 0;
Serial.begin(9600);
while (!Serial) {
SysCall::yield();
}
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
SysCall::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

@ -1,171 +0,0 @@
// Simple performance test for Teensy 3.5/3.6 SDHC.
// Demonstrates yield() efficiency.
// Warning SdFatSdio and SdFatSdioEX normally should
// not both be used in a program.
// Each has its own cache and member variables.
#include "SdFat.h"
using namespace sdfat;
// 32 KiB buffer.
const size_t BUF_DIM = 32768;
// 8 MiB file.
const uint32_t FILE_SIZE = 256UL*BUF_DIM;
SdFatSdio sd;
SdFatSdioEX sdEx;
File file;
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;
// Control access to the two versions of SdFat.
bool useEx = false;
//-----------------------------------------------------------------------------
bool sdBusy() {
return useEx ? sdEx.card()->isBusy() : sd.card()->isBusy();
}
//-----------------------------------------------------------------------------
void errorHalt(const char* msg) {
if (useEx) {
sdEx.errorHalt(msg);
} else {
sd.errorHalt(msg);
}
}
//------------------------------------------------------------------------------
uint32_t kHzSdClk() {
return useEx ? sdEx.card()->kHzSdClk() : sd.card()->kHzSdClk();
}
//------------------------------------------------------------------------------
// 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) {
file.truncate(0);
uint32_t nRdWr = FILE_SIZE/nb;
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) {
}
Serial.println("SdFatSdioEX uses extended multi-block transfers without DMA.");
Serial.println("SdFatSdio uses a traditional DMA SDIO implementation.");
Serial.println("Note the difference is speed and busy yield time.\n");
}
//-----------------------------------------------------------------------------
void loop() {
do {
delay(10);
} while (Serial.available() && Serial.read());
Serial.println("Type '1' for SdFatSdioEX or '2' for SdFatSdio");
while (!Serial.available()) {
}
char c = Serial.read();
if (c != '1' && c != '2') {
Serial.println("Invalid input");
return;
}
if (c =='1') {
useEx = true;
if (!sdEx.begin()) {
sd.initErrorHalt("SdFatSdioEX begin() failed");
}
// make sdEx the current volume.
sdEx.chvol();
} else {
useEx = false;
if (!sd.begin()) {
sd.initErrorHalt("SdFatSdio begin() failed");
}
// make sd the current volume.
sd.chvol();
}
runTest();
}

View File

@ -1,178 +0,0 @@
/*
* This program tests the dateTimeCallback() function
* and the timestamp() function.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
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) {
dir_t d;
if (!f.dirEntry(&d)) {
error("f.dirEntry failed");
}
cout << F("Creation: ");
f.printFatDate(d.creationDate);
cout << ' ';
f.printFatTime(d.creationTime);
cout << endl;
cout << F("Modify: ");
f.printFatDate(d.lastWriteDate);
cout <<' ';
f.printFatTime(d.lastWriteTime);
cout << endl;
cout << F("Access: ");
f.printFatDate(d.lastAccessDate);
cout << endl;
}
//------------------------------------------------------------------------------
void setup(void) {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,172 +0,0 @@
/*
* 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"
using namespace sdfat;
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 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) {
SysCall::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()) {
SysCall::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 (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

@ -1,85 +0,0 @@
/*
* This program demonstrates the freeClusterCount() call.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
/*
* 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) {
SysCall::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()) {
SysCall::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

@ -1,224 +0,0 @@
/*
* This program is a simple binary write/read benchmark.
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
#include "FreeStack.h"
using namespace sdfat;
// Set USE_SDIO to zero for SPI card access.
#define USE_SDIO 0
// SD chip select pin
const uint8_t chipSelect = SS;
// 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;
uint8_t buf[BUF_SIZE];
// file system
#if USE_SDIO
// Traditional DMA version.
// SdFatSdio sd;
// Faster version.
SdFatSdioEX sd;
#else // USE_SDIO
SdFat sd;
#endif // USE_SDIO
// Set ENABLE_EXTENDED_TRANSFER_CLASS to use extended SD I/O.
// Requires dedicated use of the SPI bus.
// SdFatEX sd;
// Set ENABLE_SOFTWARE_SPI_CLASS to use software SPI.
// Args are misoPin, mosiPin, sckPin.
// SdFatSoftSpi<6, 7, 5> 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 cidDmp() {
cid_t cid;
if (!sd.card()->readCID(&cid)) {
error("readCID failed");
}
cout << F("\nManufacturer ID: ");
cout << 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("\nVersion: ");
cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl;
cout << F("Serial number: ") << hex << cid.psn << dec << endl;
cout << F("Manufacturing date: ");
cout << int(cid.mdt_month) << '/';
cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl;
cout << endl;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
delay(1000);
cout << F("\nUse a freshly formatted SD for best 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;
// Discard any input.
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()) {
SysCall::yield();
}
cout << F("chipSelect: ") << int(chipSelect) << endl;
cout << F("FreeStack: ") << FreeStack() << endl;
#if USE_SDIO
if (!sd.begin()) {
sd.initErrorHalt();
}
#else // USE_SDIO
// 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();
}
#endif // USE_SDIO
cout << F("Type is FAT") << int(sd.vol()->fatType()) << endl;
cout << F("Card size: ") << sd.card()->cardSize()*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
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 ") << FILE_SIZE_MB << F(" MB\n");
cout << F("Buffer size ") << BUF_SIZE << F(" bytes\n");
cout << F("Starting write test, please wait.") << endl << endl;
// do write test
uint32_t n = FILE_SIZE/sizeof(buf);
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);
maxLatency = 0;
minLatency = 9999999;
totalLatency = 0;
t = millis();
for (uint32_t i = 0; i < n; i++) {
uint32_t m = micros();
if (file.write(buf, sizeof(buf)) != sizeof(buf)) {
sd.errorPrint("write failed");
file.close();
return;
}
m = micros() - m;
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
totalLatency += 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;
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, sizeof(buf));
if (nr != sizeof(buf)) {
sd.errorPrint("read failed");
file.close();
return;
}
m = micros() - m;
if (maxLatency < m) {
maxLatency = m;
}
if (minLatency > m) {
minLatency = m;
}
totalLatency += m;
if (buf[BUF_SIZE-1] != '\n') {
error("data check");
}
}
s = file.fileSize();
t = millis() - t;
cout << s/t <<',' << maxLatency << ',' << minLatency;
cout << ',' << totalLatency/n << endl;
}
cout << endl << F("Done") << endl;
file.close();
}

View File

@ -1,152 +0,0 @@
/*
* Simple data logger.
*/
#include <SPI.h>
#include "SdFat.h"
using namespace sdfat;
// 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) {
SysCall::yield();
}
delay(1000);
Serial.println(F("Type any character to start"));
while (!Serial.available()) {
SysCall::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"));
SysCall::halt();
}
}

View File

@ -1,90 +0,0 @@
// Demo of fgets function to read lines from a file.
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::yield();
}
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,75 +0,0 @@
/*
* Print a table with various formatting options
* Format dates
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// 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) {
SysCall::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

@ -1,86 +0,0 @@
/*
* 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"
using namespace sdfat;
// 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) {
SysCall::yield();
}
// F stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
SysCall::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

@ -1,108 +0,0 @@
/*
* This program demonstrates use of SdFile::rename()
* and SdFat::rename().
*/
#include <SPI.h>
#include "SdFat.h"
#include "sdios.h"
using namespace sdfat;
// SD chip select pin
const uint8_t chipSelect = SS;
// file system
SdFat sd;
// Serial print 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) {
SysCall::yield();
}
cout << F("Insert an empty SD. Type any character to start.") << endl;
while (!Serial.available()) {
SysCall::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 file/dirs from previous run.
if (sd.exists("dir2/DIR3/NAME3.txt")) {
cout << F("Removing /dir2/DIR3/NAME3.txt") << endl;
if (!sd.remove("dir2/DIR3/NAME3.txt") ||
!sd.rmdir("dir2/DIR3/") ||
!sd.rmdir("dir2/")) {
error("remove/rmdir failed");
}
}
// create a file and write one line to the file
SdFile file("Name1.txt", O_WRONLY | O_CREAT);
if (!file.isOpen()) {
error("Name1.txt");
}
file.println("A test line for Name1.txt");
// rename the file name2.txt and add a line.
if (!file.rename("name2.txt")) {
error("name2.txt");
}
file.println("A test line for name2.txt");
// list files
cout << F("------") << endl;
sd.ls(LS_R);
// make a new directory - "Dir1"
if (!sd.mkdir("Dir1")) {
error("Dir1");
}
// move file into Dir1, rename it NAME3.txt and add a line
if (!file.rename("Dir1/NAME3.txt")) {
error("NAME3.txt");
}
file.println("A line for Dir1/NAME3.txt");
// list files
cout << F("------") << endl;
sd.ls(LS_R);
// make directory "dir2"
if (!sd.mkdir("dir2")) {
error("dir2");
}
// close file before rename(oldPath, newPath)
file.close();
// move Dir1 into dir2 and rename it DIR3
if (!sd.rename("Dir1", "dir2/DIR3")) {
error("dir2/DIR3");
}
// open file for append in new location and add a line
if (!file.open("dir2/DIR3/NAME3.txt", O_WRONLY | O_APPEND)) {
error("dir2/DIR3/NAME3.txt");
}
file.println("A line for dir2/DIR3/NAME3.txt");
file.close();
// list files
cout << F("------") << endl;
sd.ls(LS_R);
cout << F("Done") << endl;
}
void loop() {}

View File

@ -1,45 +0,0 @@
// Example to wipe all data from an already formatted SD.
#include <SPI.h>
#include "SdFat.h"
using namespace sdfat;
const int chipSelect = SS;
SdFat sd;
void setup() {
int c;
Serial.begin(9600);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
Serial.println("Type 'Y' to wipe all data.");
while (!Serial.available()) {
SysCall::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() {
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

View File

@ -1,98 +0,0 @@
Static Tests of the Arduino Internal ADC.
Several people have asked about the DC accuracy of the Arduino ADC when used in my data logging applications at slow sample rates.
Here are my results of some "hobby level" measurements of the Arduino ADC.
One question is how important is the ADC clock rate. I did measurents for an ADC clock rate of 125 kHz to 2MHz.
Another question is how much does Noise Reduction Mode help. I did a series of measurements using this mode.
Noise Reduction Mode only reduced the mean absolute error slightly.
I do calibration to remove Offset Error and Gain Error. Calibration is very important for good accuracy.
These tests depend on the Arduino voltage regulator providing a stable voltage during the tests. The Arduino ADC reference voltage is Vcc for these tests. This may not be realistic for practical applications
Integral Non-linearity (INL) is the main remaining source of error.
Here are my results for static (DC) tests of the internal ADC for three UNOs.
The Arduinos are powered by a high quality nine volt power supply.
These tests measure a DC level so do not include problems due to time jitter, S/H time, and other dynamic errors.
There are several studies of the dynamic behavior of the Arduino ADC that determine ENOB (Effective Number Of Bits).
I used a shield with a 12-bit MCP4921 DAC to generate voltage levels. This ADC has an output buffer so it provides a very low impedance source.
I measured the voltage of the DAC with a calibrated 18-bit MCP3422 ADC on the shield.
I used DAC levels from 20 to 4075 to avoid zero offset errors at low voltages and DAC buffer problems at high voltages.
Each series of measurements has 4056 data points.
This is a voltage range of about 0.023 to 4.972 volts.
I calibrated the Arduino ADC for each series of measurements with a linear fit of the form.
v = a + b*adcValue
Errors are the difference between the value measured with the 18-bit ADC and the calibrated value measured with the AVR ADC.
I also show the results for no calibration, the NoCal column, using the datasheet formula.
Vin = Vref*adcValue/1024
The rows in the tables tables are.
Min - minimum error in millivolts
Max - maximum error in millivolts
MAE - mean absolute error in millivolts
The columns in the tables are:
Ideal - results for a perfect 10-bit ADC for comparison.
NoCal - datasheet formula (5/1024)*adcValue with Noise Reduction Mode.
NR128 - Noise Reduction mode with Prescaler of 128 (ADC clock of 125 kHz).
PS128 - analogRead with Prescaler of 128 (ADC clock of 125 kHz).
PS64 - analogRead with Prescaler of 64 (ADC clock of 250 kHz).
PS32 - analogRead with Prescaler of 32 (ADC clock of 500 kHz).
PS16 - analogRead with Prescaler of 16 (ADC clock of 1 MHz).
PS8 - analogRead with Prescaler of 8 (ADC clock of 2 MHz).
Results for three UNO Arduinos
First Arduino - Error Millivolts
Ideal NoCal NR128 PS128 PS64 PS32 PS16 PS8
Min -2.44 -2.43 -3.72 -4.01 -3.88 -4.53 -6.57 -27.18
Max 2.44 11.69 3.74 4.24 4.15 5.17 8.69 23.21
MAE 1.22 5.02 1.33 1.38 1.37 1.44 1.96 4.11
Second Arduino - Error Millivolts
Ideal NoCal NR128 PS128 PS64 PS32 PS16 PS8
Min -2.44 -9.24 -4.87 -4.86 -5.05 -5.34 -6.52 -24.04
Max 2.44 11.62 3.95 4.64 4.69 5.71 8.41 21.29
MAE 1.22 5.33 1.41 1.43 1.44 1.53 2.02 4.05
Third Arduino - Error Millivolts
Ideal NoCal NR128 PS128 PS64 PS32 PS16 PS8
Min -2.44 -7.88 -4.12 -4.40 -4.32 -4.41 -6.97 -26.93
Max 2.44 12.53 3.80 4.04 4.18 5.27 8.84 24.59
MAE 1.22 4.85 1.29 1.33 1.34 1.42 1.91 4.10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,21 +0,0 @@
Maximum Sample Rate Table
ADC clock kHz
125 250 500 1000
pins
1 7692 14286 25000 40000
2 3810 6667 11111 16667
3 2572 4790 8421 13559
4 1942 3636 6452 10526
5 1559 2930 5229 8602
6 1303 2454 4396 7273
7 1119 2111 3791 6299
8 980 1852 3333 5556
9 872 1649 2974 4969
10 786 1487 2685 4494
11 715 1354 2446 4103
12 656 1242 2247 3774
13 606 1148 2078 3493
14 563 1067 1932 3252
15 525 996 1806 3042
16 493 935 1695 2857

View File

@ -1,39 +0,0 @@
#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 bytes
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 bytes
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 bytes
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

@ -1,82 +0,0 @@
#include <stdio.h>
#include "AnalogBinLogger.h"
FILE *source;
FILE *destination;
int count = 0;
int main(int argc, char** argv) {
metadata_t meta;
adcdata_t adc;
// Make sure no padding/size problems.
if (sizeof(meta) != 512 || sizeof(adc) != 512) {
printf("block size error\n");
return 0;
}
if (argc != 3) {
printf("missing arguments:\n");
printf("%s binFile csvFile\n", argv[0]);
return 0;
}
source = fopen(argv[1], "rb");
if (!source) {
printf("open failed for %s\n", argv[1]);
return 0;
}
if (fread(&meta, sizeof(meta), 1, source) != 1) {
printf("read meta data failed\n");
return 0;
}
if ( meta.pinCount == 0
|| meta.pinCount > (sizeof(meta.pinNumber)/sizeof(meta.pinNumber[0]))
|| meta.adcFrequency < 50000 || meta.adcFrequency > 4000000) {
printf("Invalid meta data\n");
return 0;
}
destination = fopen(argv[2], "w");
if (!destination) {
printf("open failed for %s\n", argv[2]);
return 0;
}
int pinCount = meta.pinCount;
printf("pinCount: %d\n", pinCount);
printf("Sample pins:");
for (unsigned i = 0; i < meta.pinCount; i++) {
printf(" %d", meta.pinNumber[i]);
}
printf("\n");
printf("ADC clock rate: %g kHz\n", 0.001*meta.adcFrequency);
float sampleInterval = (float)meta.sampleInterval/(float)meta.cpuFrequency;
printf("Sample rate: %g per sec\n", 1.0/sampleInterval);
printf("Sample interval: %.4f usec\n", 1.0e6*sampleInterval);
fprintf(destination, "Interval,%.4f,usec\n", 1.0e6*sampleInterval);
// Write header with pin numbers
for (int i = 0; i < ((int)meta.pinCount - 1); i++) {
fprintf(destination, "pin%d,", meta.pinNumber[i]);
}
fprintf(destination, "pin%d\n", meta.pinNumber[meta.pinCount - 1]);
unsigned maxCount = meta.recordEightBits ? DATA_DIM8 : DATA_DIM16;
while (!feof(source)) {
if (fread(&adc, sizeof(adc), 1, source) != 1) break;
if (adc.count > maxCount) {
printf("****Invalid data block****\n");
return 0;
}
if (adc.overrun) {
fprintf(destination, "Overruns,%d\n", adc.overrun);
}
for (int i = 0; i < adc.count; i++) {
unsigned value = meta.recordEightBits ? adc.data.u8[i] : adc.data.u16[i];
if ((i + 1)%pinCount) {
fprintf(destination, "%d,", value);
} else {
fprintf(destination, "%d\n", value);
}
}
count += adc.count;
}
printf("%d ADC values read\n", count);
fclose(source);
fclose(destination);
return 0;
}

View File

@ -1,95 +0,0 @@
AnalogBinLogger.ino logs analog data to a binary SD file at high rates.
Samples are logged at regular intervals by using timer1. Timer/Counter1
Compare Match B is used to trigger the ADC for the first pin in a sample.
The ADC is triggered for remaining sample pins in the ADC conversion complete
interrupt routine.
Data is captured in the ADC interrupt routine and saved in 512 byte buffers.
Buffered data is written to the SD in a function called from loop(). The
entire data set is written to a large contiguous file as a single multi-block
write. This reduces write latency problems.
Many inexpensive SD cards work well at lower rates. I used a $6.00
SanDisk 4 GB class 4 card for testing.
SanDisk class 4 cards work well at fairly high rates. I used the 4 GB SanDisk
card to log a single pin at 40,000 samples per second.
You may need to increase the time between samples if your card has higher
latency. Using a Mega Arduino can help since it has more buffering.
The bintocsv folder contains a PC program for converting binary files to
CSV files. Build it from the included source files. bintocvs is a command line program.
bintocsv binFile csvFile
AnalogBinLogger requires a recent version of the SdFat library. The SdFat
folder contains a beta version I used for development.
The latest stable version is here:
http://code.google.com/p/sdfatlib/downloads/list
You also need to install the included BufferedWriter library. It provides
fast text formatting.
Example data for a 2 kHz sine wave logged at 40,000 samples per second is
shown in DATA.PNG and FFT.PNG shows a FFT of the data. See ExcelFFT.pdf
in the ADCdocs folder for details on calculating a FFT.
The accuracy of the ADC samples depends on the ADC clock rate. See the
ADC_ENOB.PNG file for a plot of accuracy vs ADC clock frequency.
See files in the ADCdocs folder for more information on ADC accuracy.
To modify this program you will need a good knowledge of the Arduino
ADC, timer1 and C++ programming. This is not for the newbie.
I have an LED and resistor connected to pin 3 to signal fatal errors and
data overruns. Fatal errors are indicated by a blinking led. Overrun errors
are indicated by a solid lit led. The count of samples dropped is written
to the SD and data logging continues.
You can disable the error led feature by setting the error pin number negative:
To use AnalogBinLogger, install these items.
Place the BufferWriter and SdFat folders in your sketchbook libraries folder.
Place the AnalogIsrLogger folder in your sketchbook folder.
You must edit the configuration constants at the beginning of the program
to set the sample pins, sample rate, and other configuration values.
Initially the program is setup to log the first five analog pins at 5000
samples per second. Change these values to suit your needs.
See RateTable.txt for maximum allowed sample rates vs pin count and ADC clock
frequency.
The program has four commands:
c - convert file to CSV
d - dump data to Serial
e - overrun error details
r - record ADC data
All commands can be terminated by entering a character from the serial monitor.
The c command converts the current binary file to a text file. Entering a
character on the serial monitor terminates the command.
The d command converts the binary file to text and displays it on the serial
monitor. Entering a character on the serial monitor terminates the command.
The e command displays details about overruns in the current binary file.
Data overruns happen when data samples are lost due to long write latency
of the SD.
The r command will record ADC data to a binary file. It will terminate
when a character is entered on the serial monitor or the the maximum file
block count has been reached.
A number of program options can be set by changing constants at the beginning
of the program.

View File

@ -1,403 +0,0 @@
/**
* Copyright (c) 20011-2018 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
<CENTER>Copyright &copy 2012-2018 by William Greiman
</CENTER>
\section Intro Introduction
The Arduino %SdFat Library is a minimal implementation of FAT16 and FAT32
file systems on SD flash memory cards. Standard SD and high capacity SDHC
cards are supported.
Experimental support for FAT12 can be enabled by setting FAT12_SUPPORT
nonzero in SdFatConfig.h.
The %SdFat library supports Long %File Names or short 8.3 names.
Edit the SdFatConfig.h file to select short or long file names.
The main classes in %SdFat are SdFat, SdFatEX, SdFatSoftSpi, SdFatSoftSpiEX,
SdBaseFile, SdFile, File, StdioStream, \ref fstream, \ref ifstream,
and \ref ofstream.
The SdFat, SdFatEX, SdFatSoftSpi and SdFatSoftSpiEX classes maintain a
FAT volume, a current working directory, and simplify initialization
of other classes. The SdFat and SdFatEX classes uses a fast custom hardware SPI
implementation. The SdFatSoftSpi and SdFatSoftSpiEX classes uses software SPI.
the SdFatEX and SdFatSoftSpiEX use extended multi-block I/O for enhanced
performance. These classes must have exclusive use of the SPI bus.
The SdBaseFile class provides basic file access functions such as open(),
binary read(), binary write(), close(), remove(), and sync(). SdBaseFile
is the smallest file class.
The SdFile class has all the SdBaseFile class functions plus the Arduino
Print class functions.
The File class has all the SdBaseFile functions plus the functions in
the Arduino SD.h File class. This provides compatibility with the
Arduino SD.h library.
The StdioStream class implements functions similar to Linux/Unix standard
buffered input/output.
The \ref fstream class implements C++ iostreams for both reading and writing
text files.
The \ref ifstream class implements C++ iostreams for reading text files.
The \ref ofstream class implements C++ iostreams for writing text files.
The classes \ref ifstream, \ref ofstream, \ref istream, and \ref ostream
follow the C++ \ref iostream standard when possible.
There are many tutorials and much documentation about using C++ iostreams
on the web.
http://www.cplusplus.com/ is a good C++ site for learning iostreams.
The classes \ref ibufstream and \ref obufstream format and parse character
strings in memory buffers.
the classes ArduinoInStream and ArduinoOutStream provide iostream functions
for Serial, LiquidCrystal, and other devices.
A number of example are provided in the %SdFat/examples folder. These were
developed to test %SdFat and illustrate its use.
\section Install Installation
You must manually install SdFat by copying the SdFat folder from the download
package to the Arduino libraries folder in your sketch folder.
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 folder.
Set USE_LONG_FILE_NAMES nonzero to enable Long %File Names. By default,
Long %File Names are enabled. For the leanest fastest library disable
Long %File Names. Long %File names require extra flash but no extra RAM.
Opening Long %File Names can be slower than opening Short %File Names.
Data read and write performance is not changed by the type of %File Name.
If the symbol ENABLE_EXTENDED_TRANSFER_CLASS is nonzero, the class SdFatEX
will be defined. If the symbol ENABLE_SOFTWARE_SPI_CLASS is also nonzero,
the class SdFatSoftSpiEX will be defined.
These classes used extended multi-block SD I/O for better performance.
the SPI bus may not be shared with other devices in this mode.
Set USE_STANDARD_SPI_LIBRARY and ENABLE_SOFTWARE_SPI_CLASS to
enable various SPI options. set USE_STANDARD_SPI_LIBRARY to use the standard
Arduino SPI library. set ENABLE_SOFTWARE_SPI_CLASS to enable the SdFatSoftSpi
class which uses software SPI.
To enable SD card CRC checking set USE_SD_CRC nonzero.
Set FAT12_SUPPORT nonzero to enable use of FAT12 volumes.
FAT12 has not been well tested and requires additional flash.
\section SDPath Paths and Working Directories
Relative paths in SdFat are resolved in a manner similar to Windows.
Each instance of SdFat has a current directory. In SdFat 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 SdFat::chdir(path).
The call sd.chdir("/2014") will change the volume working directory
for sd to "/2014", assuming "/2014" exists.
Relative paths for SdFat 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".
SdFat has a 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 SdFat::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 then open "/music/BigBand.wav" on sd2.
The following functions are used to change or get current directories.
See the html documentation for more information.
@code
bool SdFat::chdir(bool set_cwd = false);
bool SdFat::chdir(const char* path, bool set_cwd = false);
void SdFat::chvol();
SdBaseFile* SdFat::vwd();
static SdBaseFile* SdBaseFile::cwd();
@endcode
\section SDcard SD\SDHC Cards
Arduinos access SD cards using the cards SPI protocol. PCs, Macs, and
most consumer devices use the 4-bit parallel SD protocol. A card that
functions well on A PC or Mac may not work well on the Arduino.
Most cards have good SPI read performance but cards vary widely in SPI
write performance. Write performance is limited by how efficiently the
card manages internal erase/remapping operations. The Arduino cannot
optimize writes to reduce erase operations because of its limit RAM.
SanDisk cards generally have good write performance. They seem to have
more internal RAM buffering than other cards and therefore can limit
the number of flash erase operations that the Arduino forces due to its
limited RAM.
\section Hardware Hardware Configuration
%SdFat was developed using an
<A HREF = "http://www.adafruit.com/"> Adafruit Industries</A>
Data Logging Shield.
The hardware interface to the SD card should not use a resistor based level
shifter. %SdFat sets the SPI bus frequency to 8 MHz which results in signal
rise times that are too slow for the edge detectors in many newer SD card
controllers when resistor voltage dividers are used.
The 5 to 3.3 V level shifter for 5 V Arduinos should be IC based like the
74HC4050N based circuit shown in the file SdLevel.png. The Adafruit Wave Shield
uses a 74AHC125N. Gravitech sells SD and MicroSD Card Adapters based on the
74LCX245.
If you are using a resistor based level shifter and are having problems try
setting the SPI bus frequency to 4 MHz. This can be done by using
card.init(SPI_HALF_SPEED) to initialize the SD card.
A feature to use software SPI is available. Software SPI is slower
than hardware SPI but allows any digital pins to be used. See
SdFatConfig.h for software SPI definitions.
\section comment Bugs and Comments
If you wish to report bugs or have comments, send email to
fat16lib@sbcglobal.net. If possible, include a simple program that illustrates
the bug or problem.
\section Trouble Troubleshooting
The two example programs QuickStart, and SdInfo are useful for troubleshooting.
A message like this from SdInfo with errorCode 0X1 indicates the SD card
is not seen by SdFat. This is often caused by a wiring error and reformatting
the card will not solve the problem.
<PRE>
cardBegin failed
SD errorCode: 0X1
SD errorData: 0XFF
</PRE>
Here is a similar message from QuickStart:
<PRE>
SD initialization failed.
Do not reformat the card!
Is the card correctly inserted?
Is chipSelect set to the correct value?
Does another SPI device need to be disabled?
Is there a wiring/soldering problem?
errorCode: 0x1, errorData: 0xff
</PRE>
Here is a message from QuickStart that indicates a formatting problem:
<PRE>
Card successfully initialized.
Can't find a valid FAT16/FAT32 partition.
Try reformatting the card. For best results use
the SdFormatter program in SdFat/examples or download
and use SDFormatter from www.sdcard.org/downloads.
</PRE>
The best source of recent information and help is the Arduino forum.
http://arduino.cc/forum/
Also search the Adafruit forum.
http://forums.adafruit.com/
If you are using a Teensy try.
http://forum.pjrc.com/forum.php
\section SdFatClass SdFat Usage
SdFat supports Long File Names. Long names in SdFat are limited to 7-bit
ASCII characters in the range 0X20 - 0XFE The following are reserved characters:
<ul>
<li>< (less than)
<li>> (greater than)
<li>: (colon)
<li>" (double quote)
<li>/ (forward slash)
<li>\ (backslash)
<li>| (vertical bar or pipe)
<li>? (question mark)
<li>* (asterisk)
</ul>
%SdFat uses a slightly restricted form of short names.
Short names are limited to 8 characters followed by an optional period (.)
and extension of up to 3 characters. The characters may be any combination
of letters and digits. The following special characters are also allowed:
$ % ' - _ @ ~ ` ! ( ) { } ^ # &
Short names are always converted to upper case and their original case
value is lost. Files that have a base-name where all characters have the
same case and an extension where all characters have the same case will
display properly. Examples this type name are UPPER.low, lower.TXT,
UPPER.TXT, and lower.txt.
An application which writes to a file using print(), println() or
write() must close the file or call sync() at the appropriate time to
force data and directory information to be written to the SD Card.
Applications must use care calling sync()
since 2048 bytes of I/O is required to update file and
directory information. This includes writing the current data block, reading
the block that contains the directory entry for update, writing the directory
block back and reading back the current data block.
It is possible to open a file with two or more instances of a file object.
A file may be corrupted if data is written to the file by more than one
instance of a file object.
\section HowTo How to format SD Cards as FAT Volumes
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 SdFat formatter 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 ExampleFilder Examples
A number of examples are provided in the SdFat/examples folder.
See the html documentation for a list.
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:
AnalogBinLogger - Fast AVR ADC logger - see the AnalogBinLoggerExtras folder.
bench - A read/write benchmark.
dataLogger - A simple modifiable data logger.
DirectoryFunctions - Demo of chdir(), ls(), mkdir(), and rmdir().
fgets - Demo of the fgets read line/string function.
formating - Print a table with various formatting options.
getline - Example of getline from section 27.7.1.3 of the C++ standard.
LongFileName - Example use of openNext, printName, and open by index.
LowLatencyLogger - A data logger for higher data rates. ADC version.
LowLatencyLoggerADXL345 - A data logger for higher data rates. ADXL345 SPI.
LowLatencyLoggerMPU6050 - A data logger for higher data rates. MPU6050 I2C.
OpenNext - Open all files in the root dir and print their filename.
PrintBenchmark - A simple benchmark for printing to a text file.
QuickStart - A program to quickly test your SD card and SD shield/module.
RawWrite - A test of raw write functions for contiguous files.
ReadCsv - Function to read a CSV text file one field at a time.
ReadCsvStream - Read a comma-separated value file using iostream extractors.
ReadCsvArray - Read a two dimensional array from a CSV file.
ReadWrite - Compatibility test of Arduino SD ReadWrite example.
rename - A demo of SdFat::rename(old, new) and SdFile::rename(dirFile, newPath).
SdFormatter - This program will format an SD or SDHC card.
SoftwareSpi - Simple demonstration of the SdFatSoftSpi template class.
SdInfo - Initialize an SD card and analyze its structure for trouble shooting.
StdioBench - Demo and test of stdio style stream.
Timestamp - Sets file create, modify, and access timestamps.
TwoCards - Example using two SD cards.
VolumeFreeSpace - Demonstrate the freeClusterCount() call.
wipe - Example to wipe all data from an already formatted SD.
*/

View File

@ -1,10 +0,0 @@
<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>

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