mirror of https://github.com/ArduPilot/ardupilot
786 lines
17 KiB
C++
786 lines
17 KiB
C++
/*
|
|
night_ghost@ykoctpa.ru 2017
|
|
|
|
a port of SparkFun's SD class to FatFs (c) Chan
|
|
because it much better and faster than sdfatlib
|
|
|
|
also it was rewritten to:
|
|
* distinguish between flie at directory by stat(), not by try to open
|
|
* provide last error code and its text description
|
|
* added tracking of opened files for global sync()
|
|
* some new functions added
|
|
* reduced memory usage by half
|
|
|
|
|
|
original SparkFun readme below
|
|
----------------------------------------------
|
|
|
|
SD - a slightly more friendly wrapper for sdfatlib
|
|
|
|
This library aims to expose a subset of SD card functionality
|
|
in the form of a higher level "wrapper" object.
|
|
|
|
License: GNU General Public License V3
|
|
(Because sdfatlib is licensed with this.)
|
|
|
|
(C) Copyright 2010 SparkFun Electronics
|
|
|
|
|
|
This library provides four key benefits:
|
|
|
|
* Including `SD.h` automatically creates a global
|
|
`SD` object which can be interacted with in a similar
|
|
manner to other standard global objects like `Serial` and `Ethernet`.
|
|
|
|
* Boilerplate initialisation code is contained in one method named
|
|
`begin` and no further objects need to be created in order to access
|
|
the SD card.
|
|
|
|
* Calls to `open` can supply a full path name including parent
|
|
directories which simplifies interacting with files in subdirectories.
|
|
|
|
* Utility methods are provided to determine whether a file exists
|
|
and to create a directory heirarchy.
|
|
|
|
|
|
Note however that not all functionality provided by the underlying
|
|
sdfatlib library is exposed.
|
|
|
|
*/
|
|
|
|
|
|
extern "C" {
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
}
|
|
|
|
#include "assert.h"
|
|
#include <utility>
|
|
#include "SD.h"
|
|
|
|
#if defined(BOARD_SDCARD_CS_PIN) || defined(BOARD_DATAFLASH_FATFS)
|
|
|
|
SDClass SD;
|
|
|
|
Sd2Card SDClass::_card;
|
|
SdFatFs SDClass::_fatFs;
|
|
|
|
FRESULT SDClass::lastError = FR_OK;
|
|
|
|
/**
|
|
* @brief Link SD, register the file system object to the FatFs mode and configure
|
|
* relatives SD IOs
|
|
* @param None
|
|
* @retval TRUE or FALSE
|
|
*/
|
|
uint8_t SDClass::begin(AP_HAL::OwnPtr<F4Light::SPIDevice> spi)
|
|
{
|
|
if (_card.init(std::move(spi))) {
|
|
lastError=_fatFs.init(&_card);
|
|
if(lastError == FR_OK) return true;
|
|
printf("\nSD card error: %s\n", strError(lastError));
|
|
return false;
|
|
}
|
|
printf("\nSD card init error!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Check if a file or folder exist on the SD disk
|
|
* @param filename: File name
|
|
* @retval TRUE or FALSE
|
|
*/
|
|
uint8_t SDClass::exists(const char *filepath)
|
|
{
|
|
FILINFO fno;
|
|
|
|
lastError=f_stat(filepath, &fno);
|
|
|
|
// FatFs gives such error for root directory
|
|
return lastError == FR_OK || lastError == FR_INVALID_NAME;
|
|
}
|
|
|
|
/**
|
|
* @brief Create directory on the SD disk
|
|
* @param filename: File name
|
|
* @retval TRUE or FALSE
|
|
*/
|
|
uint8_t SDClass::mkdir(const char *filepath)
|
|
{
|
|
lastError = f_mkdir(filepath);
|
|
return lastError == FR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove directory on the SD disk
|
|
* @param filename: File name
|
|
* @retval TRUE or FALSE
|
|
*/
|
|
uint8_t SDClass::rmdir(const char *filepath)
|
|
{
|
|
lastError = f_unlink(filepath);
|
|
return lastError == FR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Open a file on the SD disk, if not existing it's created
|
|
* @param filename: File name
|
|
* @retval File object referring to the opened file
|
|
*/
|
|
File SDClass::open(const char *filepath)
|
|
{
|
|
File file = File(filepath);
|
|
|
|
FILINFO fno;
|
|
|
|
lastError=f_stat(filepath, &fno);
|
|
|
|
if(lastError!=FR_OK){ // no file
|
|
return file;
|
|
}
|
|
|
|
if(fno.fattrib & AM_DIR) {
|
|
lastError = f_opendir(&file._d.dir, filepath);
|
|
file.is_dir=true;
|
|
if( lastError != FR_OK) {
|
|
file.close();
|
|
}
|
|
} else {
|
|
lastError = f_open(&file._d.fil, filepath, FA_READ);
|
|
file.is_dir=false;
|
|
|
|
if( lastError == FR_OK) {
|
|
File::addOpenFile(&file._d.fil);
|
|
} else {
|
|
file.close();
|
|
}
|
|
}
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* @brief Open a file on the SD disk, if not existing it's created
|
|
* @param filename: File name
|
|
* @param mode: the mode in which to open the file
|
|
* @retval File object referring to the opened file
|
|
*/
|
|
File SDClass::open(const char *filepath, uint8_t mode)
|
|
{
|
|
File file = File(filepath);
|
|
|
|
FILINFO fno;
|
|
|
|
lastError=f_stat(filepath, &fno);
|
|
|
|
if(lastError == FR_OK && fno.fattrib & AM_DIR) { // exists and is dir
|
|
if(!(mode & FILE_WRITE)){
|
|
lastError = f_opendir(&file._d.dir, filepath);
|
|
file.is_dir=true;
|
|
} else {
|
|
lastError = FR_IS_DIR;
|
|
}
|
|
if( lastError != FR_OK) file.close();
|
|
} else { // dir not exists - regular file
|
|
|
|
if((mode & FILE_WRITE) && lastError==FR_OK) { // the modes of opening the file are different. if a file exists
|
|
mode &= ~FA_CREATE_NEW; // then remove the creation flag - or we will got error "file exists"
|
|
}
|
|
|
|
lastError = f_open(&file._d.fil, filepath, mode);
|
|
file.is_dir=false;
|
|
|
|
if( lastError == FR_OK){
|
|
if(mode & O_APPEND){
|
|
f_lseek(&file._d.fil, f_size(&file._d.fil));
|
|
}
|
|
File::addOpenFile(&file._d.fil);
|
|
} else {
|
|
file.close();
|
|
}
|
|
}
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove a file on the SD disk
|
|
* @param filename: File name
|
|
* @retval TRUE or FALSE
|
|
*/
|
|
uint8_t SDClass::remove(const char *filepath)
|
|
{
|
|
lastError = f_unlink(filepath);
|
|
return lastError == FR_OK;
|
|
}
|
|
|
|
File SDClass::openRoot(void)
|
|
{
|
|
File file = File(_fatFs.getRoot());
|
|
|
|
lastError = f_opendir(&file._d.dir, _fatFs.getRoot());
|
|
file.is_dir = true;
|
|
if(lastError != FR_OK) {
|
|
file._d.dir.obj.fs = 0;
|
|
}
|
|
return file;
|
|
}
|
|
|
|
|
|
|
|
uint32_t SDClass::getfree(const char *filepath, uint32_t * fssize){
|
|
FATFS *fs;
|
|
DWORD fre_clust, fre_sect;
|
|
|
|
|
|
/* Get volume information and free clusters of drive */
|
|
lastError = f_getfree(filepath, &fre_clust, &fs);
|
|
if (lastError != FR_OK) return -1;
|
|
|
|
/* Get total sectors and free sectors */
|
|
if(fssize) *fssize = (fs->n_fatent - 2) * fs->csize; // tot_sect
|
|
fre_sect = fre_clust * fs->csize;
|
|
|
|
return fre_sect;
|
|
}
|
|
|
|
|
|
//f_stat ( const TCHAR* path, /* Pointer to the file path */ FILINFO* fno )
|
|
uint8_t SDClass::stat(const char *filepath, FILINFO* fno){
|
|
lastError = f_stat(filepath, fno);
|
|
if(lastError != FR_OK) return -1;
|
|
return 0;
|
|
}
|
|
|
|
uint8_t SDClass::format(const char *filepath){
|
|
|
|
lastError = _fatFs.format(filepath, &_card);
|
|
|
|
return lastError == FR_OK;
|
|
}
|
|
|
|
|
|
|
|
//* *************************************
|
|
File::File()
|
|
{
|
|
_name = NULL;
|
|
_d.fil.obj.fs = 0;
|
|
_d.dir.obj.fs = 0;
|
|
}
|
|
|
|
File::File(const char* fname)
|
|
{
|
|
_name = (char*)malloc(strlen(fname) +1);
|
|
|
|
//assert(_name != NULL );
|
|
if(_name == NULL) return; // no HardFault, just not opened
|
|
|
|
strcpy(_name, fname);
|
|
_d.fil.obj.fs = 0;
|
|
_d.dir.obj.fs = 0;
|
|
}
|
|
|
|
/** List directory contents to given callback
|
|
*
|
|
* \param[in] flags The inclusive OR of
|
|
*
|
|
* LS_DATE - %Print file modification date
|
|
*
|
|
* LS_SIZE - %Print file size.
|
|
*
|
|
* LS_R - Recursive list of subdirectories.
|
|
*
|
|
* \param[in] indent Amount of space before file name. Used for recursive
|
|
* list to indicate subdirectory level.
|
|
*/
|
|
void File::ls(cb_putc cb, uint8_t flags, uint8_t indent) {
|
|
FRESULT res = FR_OK;
|
|
FILINFO fno;
|
|
char *fn;
|
|
|
|
if(!is_dir) return;
|
|
|
|
#if _USE_LFN
|
|
static char lfn[_MAX_LFN];
|
|
fno.lfname = lfn;
|
|
fno.lfsize = sizeof(lfn);
|
|
#endif
|
|
|
|
while(1) {
|
|
res = f_readdir(&_d.dir, &fno);
|
|
if(res != FR_OK || fno.fname[0] == 0) {
|
|
break;
|
|
}
|
|
if(fno.fname[0] == '.') {
|
|
continue;
|
|
}
|
|
#if _USE_LFN
|
|
fn = *fno.lfname ? fno.lfname : fno.fname;
|
|
#else
|
|
fn = fno.fname;
|
|
#endif
|
|
//print any indent spaces
|
|
for (int8_t i = 0; i < indent; i++) cb(' ');
|
|
printStr(fn, cb);
|
|
|
|
if((fno.fattrib & AM_DIR) == 0) {
|
|
// print modify date/time if requested
|
|
if (flags & LS_DATE) {
|
|
cb(' ');
|
|
printFatDate(fno.fdate,cb);
|
|
cb(' ');
|
|
printFatTime(fno.ftime,cb);
|
|
}
|
|
// print size if requested
|
|
if (flags & LS_SIZE) {
|
|
cb(' ');
|
|
printNumber(fno.fsize, cb);
|
|
}
|
|
cb('\r'); cb('\n');
|
|
}
|
|
else
|
|
{
|
|
// list subdirectory content if requested
|
|
if (flags & LS_R)
|
|
{
|
|
char *fullPath;
|
|
fullPath = (char*)malloc(strlen(_name) + 1 + strlen(fn) +1);
|
|
if (fullPath != NULL) {
|
|
sprintf(fullPath, "%s/%s", _name, fn);
|
|
File filtmp = SD.open(fullPath);
|
|
|
|
if (filtmp._name != NULL) {
|
|
cb('\r'); cb('\n');
|
|
filtmp.ls(cb,flags, indent+2);
|
|
filtmp.close();
|
|
} else {
|
|
printStr(fn, cb);
|
|
cb('\r'); cb('\n');
|
|
|
|
static const char err_s[] = "Error to open dir: ";
|
|
printStr(err_s, cb);
|
|
printStr(fn, cb);
|
|
}
|
|
free(fullPath);
|
|
} else {
|
|
cb('\r'); cb('\n');
|
|
static const char err_s[] = "Error to allocate memory!";
|
|
printStr(err_s, cb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %Print a directory date field to Serial.
|
|
*
|
|
* Format is yyyy-mm-dd.
|
|
*
|
|
* \param[in] fatDate The date field from a directory entry.
|
|
*/
|
|
void File::printFatDate(uint16_t fatDate, cb_putc cb) {
|
|
printNumber(FAT_YEAR(fatDate), cb);
|
|
cb('-');
|
|
printTwoDigits(FAT_MONTH(fatDate),cb);
|
|
cb('-');
|
|
printTwoDigits(FAT_DAY(fatDate),cb);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %Print a directory time field to Serial.
|
|
*
|
|
* Format is hh:mm:ss.
|
|
*
|
|
* \param[in] fatTime The time field from a directory entry.
|
|
*/
|
|
void File::printFatTime(uint16_t fatTime, cb_putc cb) {
|
|
printTwoDigits(FAT_HOUR(fatTime), cb);
|
|
cb(':');
|
|
printTwoDigits(FAT_MINUTE(fatTime), cb);
|
|
cb(':');
|
|
printTwoDigits(FAT_SECOND(fatTime), cb);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %Print a value as two digits to Serial.
|
|
*
|
|
* \param[in] v Value to be printed, 0 <= \a v <= 99
|
|
*/
|
|
void File::printTwoDigits(uint8_t v, cb_putc cb) {
|
|
cb('0' + v/10);
|
|
cb('0' + v % 10);
|
|
}
|
|
|
|
void File::printNumber(int16_t n, cb_putc cb) {
|
|
const uint8_t base = 10;
|
|
|
|
if (n < 0) {
|
|
cb('-');
|
|
n = -n;
|
|
}
|
|
|
|
char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
|
|
char *str = &buf[sizeof(buf) - 1];
|
|
|
|
*str = '\0';
|
|
|
|
do {
|
|
char c = n % base;
|
|
n /= base;
|
|
|
|
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
|
} while(n);
|
|
|
|
printStr(str, cb);
|
|
}
|
|
|
|
void File::printStr(const char *s, cb_putc cb) {
|
|
while(*s) cb(*s++);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Read byte from the file
|
|
* @retval Byte read
|
|
*/
|
|
int File::read()
|
|
{
|
|
UINT byteread;
|
|
int8_t data;
|
|
SD.lastError = f_read(&_d.fil, (void *)&data, 1, &byteread);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @brief Read an amount of data from the file
|
|
* @param buf: an array to store the read data from the file
|
|
* @param len: the number of elements to read
|
|
* @retval Number of bytes read
|
|
*/
|
|
int File::read(void* buf, size_t len)
|
|
{
|
|
UINT bytesread;
|
|
|
|
SD.lastError = f_read(&_d.fil, buf, len, &bytesread);
|
|
return bytesread;
|
|
|
|
}
|
|
|
|
UINT File::gets(char* buf, size_t len)
|
|
{
|
|
UINT bytesread=0;
|
|
|
|
while(len--){
|
|
|
|
uint8_t c;
|
|
uint8_t ret = read(&c,1);
|
|
if(!ret) break; // EOF
|
|
if(c=='\n') break;
|
|
if(c=='\r') continue;
|
|
*buf++=c;
|
|
*buf=0; // close string
|
|
bytesread++;
|
|
}
|
|
return bytesread;
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Close a file on the SD disk
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
void File::close()
|
|
{
|
|
if(_name){
|
|
if(is_dir) {
|
|
if(_d.dir.obj.fs != 0) {
|
|
SD.lastError = f_closedir(&_d.dir);
|
|
}
|
|
} else {
|
|
if(_d.fil.obj.fs != 0) {
|
|
/* Flush the file before close */
|
|
f_sync(&_d.fil);
|
|
|
|
/* Close the file */
|
|
SD.lastError = f_close(&_d.fil);
|
|
}
|
|
|
|
removeOpenFile(&_d.fil);
|
|
free(_name);
|
|
_name=NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Ensures that any bytes written to the file are physically saved to the SD card
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
void File::flush()
|
|
{
|
|
if(!is_dir) {
|
|
SD.lastError = f_sync(&_d.fil);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Read a byte from the file without advancing to the next one
|
|
* @param None
|
|
* @retval read byte
|
|
*/
|
|
int File::peek()
|
|
{
|
|
int data;
|
|
data = read();
|
|
seek(position() -1);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the current position within the file
|
|
* @param None
|
|
* @retval position within file
|
|
*/
|
|
uint32_t File::position()
|
|
{
|
|
return f_tell(&_d.fil);
|
|
}
|
|
|
|
/**
|
|
* @brief Seek to a new position in the file
|
|
* @param pos: The position to which to seek
|
|
* @retval TRUE or FALSE
|
|
*/
|
|
uint8_t File::seek(uint32_t pos)
|
|
{
|
|
if(is_dir) return false;
|
|
|
|
if(pos > size()) {
|
|
return FALSE;
|
|
} else {
|
|
SD.lastError = f_lseek(&_d.fil, pos);
|
|
return SD.lastError == FR_OK;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get the size of the file
|
|
* @param None
|
|
* @retval file's size
|
|
*/
|
|
uint32_t File::size()
|
|
{
|
|
if(is_dir) return 0;
|
|
return f_size(&_d.fil);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Write data to the file
|
|
* @param data: Data to write to the file
|
|
* @retval Number of data written (1)
|
|
*/
|
|
size_t File::write(uint8_t data)
|
|
{
|
|
return write(&data, 1);
|
|
}
|
|
|
|
/**
|
|
* @brief Write an array of data to the file
|
|
* @param buf: an array of characters or bytes to write to the file
|
|
* @param len: the number of elements in buf
|
|
* @retval Number of data written
|
|
*/
|
|
size_t File::write(const char *buf, size_t sz)
|
|
{
|
|
size_t byteswritten;
|
|
if(is_dir) return 0;
|
|
|
|
SD.lastError = f_write(&_d.fil, (const void *)buf, sz, (UINT *)&byteswritten);
|
|
return byteswritten;
|
|
}
|
|
|
|
size_t File::write(const uint8_t *buf, size_t sz)
|
|
{
|
|
return write((const char *)buf, sz);
|
|
}
|
|
|
|
/**
|
|
* @brief Print data to the file
|
|
* @param data: Data to write to the file
|
|
* @retval Number of data written (1)
|
|
*/
|
|
size_t File::print(const char* data)
|
|
{
|
|
return write(data, strlen(data));
|
|
}
|
|
|
|
/**
|
|
* @brief Print data to the file
|
|
* @retval Number of data written (1)
|
|
*/
|
|
size_t File::println()
|
|
{
|
|
return write("\r\n", 2);
|
|
}
|
|
|
|
/**
|
|
* @brief Print data to the file
|
|
* @param data: Data to write to the file
|
|
* @retval Number of data written (1)
|
|
*/
|
|
size_t File::println(const char* data)
|
|
{
|
|
size_t bytewritten = write(data, strlen(data));
|
|
bytewritten += println();
|
|
return bytewritten;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Check if there are any bytes available for reading from the file
|
|
* @retval Number of bytes available
|
|
*/
|
|
int File::available()
|
|
{
|
|
uint32_t n = size() - position();
|
|
return n > 0x7FFF ? 0x7FFF : n;
|
|
}
|
|
|
|
|
|
char* File::name()
|
|
{
|
|
char *fname = strrchr(_name, '/');
|
|
if (fname && fname[0] == '/')
|
|
fname++;
|
|
return fname;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the file is directory or normal file
|
|
* @retval TRUE if directory else FALSE
|
|
*/
|
|
uint8_t File::isDirectory()
|
|
{
|
|
//assert(_name != NULL );
|
|
|
|
if(_name == NULL) return false;
|
|
|
|
|
|
if (is_dir){
|
|
if(_d.dir.obj.fs != 0) return TRUE;
|
|
} else {
|
|
if(_d.fil.obj.fs != 0) return FALSE;
|
|
}
|
|
|
|
// if not init get info
|
|
FILINFO fno;
|
|
|
|
SD.lastError = f_stat(_name, &fno);
|
|
if (SD.lastError == FR_OK) {
|
|
if(fno.fattrib & AM_DIR){
|
|
is_dir = true;
|
|
return TRUE;
|
|
} else {
|
|
is_dir=false;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// TODO: some strange and not works at all
|
|
File File::openNextFile(uint8_t mode)
|
|
{
|
|
if(!is_dir) return File();
|
|
|
|
FRESULT res = FR_OK;
|
|
FILINFO fno;
|
|
char *fn;
|
|
char *fullPath = NULL;
|
|
size_t name_len= strlen(_name);
|
|
size_t len = name_len;
|
|
#if _USE_LFN
|
|
static char lfn[_MAX_LFN];
|
|
fno.lfname = lfn;
|
|
fno.lfsize = sizeof(lfn);
|
|
#endif
|
|
while(1) {
|
|
res = f_readdir(&_d.dir, &fno);
|
|
if(res != FR_OK || fno.fname[0] == 0) {
|
|
return File();
|
|
}
|
|
if(fno.fname[0] == '.') {
|
|
continue;
|
|
}
|
|
#if _USE_LFN
|
|
fn = *fno.lfname ? fno.lfname : fno.fname;
|
|
#else
|
|
fn = fno.fname;
|
|
#endif
|
|
len += strlen(fn) +2;
|
|
fullPath = (char*)malloc(len);
|
|
if (fullPath != NULL) {
|
|
// Avoid twice '/'
|
|
if ((name_len > 0) && (_name[name_len-1] == '/')) {
|
|
sprintf(fullPath, "%s%s", _name, fn);
|
|
} else {
|
|
sprintf(fullPath, "%s/%s", _name, fn);
|
|
}
|
|
File filtmp = SD.open(fullPath, mode);
|
|
free(fullPath);
|
|
return filtmp;
|
|
} else {
|
|
return File();
|
|
}
|
|
}
|
|
}
|
|
|
|
void File::rewindDirectory(void)
|
|
{
|
|
if(isDirectory()) {
|
|
if(_d.dir.obj.fs != 0) {
|
|
f_closedir(&_d.dir);
|
|
}
|
|
f_opendir(&_d.dir, _name);
|
|
}
|
|
}
|
|
|
|
#define MAX_OPEN_FILES 16
|
|
FIL* File::openFiles[MAX_OPEN_FILES]= {0};
|
|
uint8_t File::num_openFiles=0;
|
|
|
|
void File::syncAll(){
|
|
for(uint8_t i=0; i<num_openFiles;i++){
|
|
if(openFiles[i])
|
|
f_sync(openFiles[i]);
|
|
}
|
|
}
|
|
|
|
void File::addOpenFile(FIL *f){
|
|
for(uint8_t i=0; i<num_openFiles;i++){
|
|
if(!openFiles[i]) {
|
|
openFiles[i] = f;
|
|
return;
|
|
}
|
|
}
|
|
if(num_openFiles<MAX_OPEN_FILES){
|
|
openFiles[num_openFiles++] = f;
|
|
}
|
|
}
|
|
|
|
void File::removeOpenFile(FIL *f){
|
|
for(uint8_t i=0; i<num_openFiles;i++){
|
|
if(openFiles[i] == f) {
|
|
openFiles[i] = NULL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|