diff --git a/SerialFlash.h b/SerialFlash.h index f51dc21..7f8a7f0 100644 --- a/SerialFlash.h +++ b/SerialFlash.h @@ -31,6 +31,16 @@ #include #include +// supports auto growing files when writing +#define SF_FEATURE_AUTO_GROW + +#define SF_OK 0 +#define SF_ERROR_FAILED 1 +#define SF_ERROR_WRITING 2 +#define SF_ERROR_DISK_FULL 3 +#define SF_ERROR_OVERFLOW 4 +#define SF_ERROR_CLOSED 5 + class SerialFlashFile; class SerialFlashChip @@ -50,18 +60,22 @@ class SerialFlashChip static void write(uint32_t addr, const void *buf, uint32_t len); static void eraseAll(); static void eraseBlock(uint32_t addr); + static void unprotectAll(); - static SerialFlashFile open(const char *filename); - static bool create(const char *filename, uint32_t length, uint32_t align = 0); - static bool createErasable(const char *filename, uint32_t length) { - return create(filename, length, blockSize()); - } + static SerialFlashFile open(const char *filename, char mode = 'r'); + static SerialFlashFile create(const char *filename, uint32_t length, uint32_t align = 0); + static SerialFlashFile createErasable(const char *filename, uint32_t length); static bool exists(const char *filename); static bool remove(const char *filename); static bool remove(SerialFlashFile &file); static void opendir() { dirindex = 0; } static bool readdir(char *filename, uint32_t strsize, uint32_t &filesize); + + static uint8_t lastErr; //last error private: + friend class SerialFlashFile; + static uint32_t totalCapacity; //flash total capacity + static bool writing; // file opened for incremental writing static uint16_t dirindex; // current position for readdir() static uint8_t flags; // chip features static uint8_t busy; // 0 = ready @@ -82,31 +96,70 @@ class SerialFlashFile return false; } uint32_t read(void *buf, uint32_t rdlen) { + if (!address) { + SerialFlashChip::lastErr = SF_ERROR_CLOSED; + return 0; + } if (offset + rdlen > length) { - if (offset >= length) return 0; + if (offset >= length) { + SerialFlashChip::lastErr = SF_ERROR_OVERFLOW; + return 0; + } rdlen = length - offset; } SerialFlash.read(address + offset, buf, rdlen); offset += rdlen; + SerialFlashChip::lastErr = SF_OK; return rdlen; } + char read() { + char b = -1; + read(&b, 1); + return b; + } uint32_t write(const void *buf, uint32_t wrlen) { - if (offset + wrlen > length) { - if (offset >= length) return 0; - wrlen = length - offset; + if (!address) { + SerialFlashChip::lastErr = SF_ERROR_CLOSED; + return 0; + } + if (length > 0) { + if (offset + wrlen > length) { + if (offset >= length) { + SerialFlashChip::lastErr = SF_ERROR_OVERFLOW; + return 0; + } + wrlen = length - offset; + } + } else { + // handle auto growing + if (address + offset < SerialFlashChip::totalCapacity) { + if (address + offset + wrlen > SerialFlashChip::totalCapacity) { + wrlen = SerialFlashChip::totalCapacity - address + offset; + } + } else { + SerialFlashChip::lastErr = SF_ERROR_DISK_FULL; + return 0; + } } + SerialFlash.write(address + offset, buf, wrlen); offset += wrlen; + SerialFlashChip::lastErr = SF_OK; return wrlen; } void seek(uint32_t n) { + // seeking is not allowed while writing to an auto growing file + if (length == 0) { + SerialFlashChip::lastErr = SF_ERROR_WRITING; + return; + } offset = n; } uint32_t position() { return offset; } uint32_t size() { - return length; + return length ? length : offset; } uint32_t available() { if (offset >= length) return 0; @@ -115,8 +168,7 @@ class SerialFlashFile void erase(); void flush() { } - void close() { - } + void close(); uint32_t getFlashAddress() { return address; } diff --git a/SerialFlashChip.cpp b/SerialFlashChip.cpp index a07618b..0af30f7 100644 --- a/SerialFlashChip.cpp +++ b/SerialFlashChip.cpp @@ -35,6 +35,9 @@ uint16_t SerialFlashChip::dirindex = 0; uint8_t SerialFlashChip::flags = 0; uint8_t SerialFlashChip::busy = 0; +bool SerialFlashChip::writing = false; +uint8_t SerialFlashChip::lastErr = SF_OK; +uint32_t SerialFlashChip::totalCapacity = 0; static volatile IO_REG_TYPE *cspin_basereg; static IO_REG_TYPE cspin_bitmask; @@ -51,6 +54,7 @@ static SPIClass& SPIPORT = SPI; void SerialFlashChip::wait(void) { uint32_t status; + uint8_t safety = 16; //prevent busy loop //Serial.print("wait-"); while (1) { SPIPORT.beginTransaction(SPICONFIG); @@ -73,6 +77,11 @@ void SerialFlashChip::wait(void) //Serial.printf("b=%02x.", status & 0xFF); if (!(status & 1)) break; } + safety--; + if (0 == safety) { + yield(); //prevent watchdog reset + safety = 16; + } } busy = 0; //Serial.println(); @@ -264,6 +273,23 @@ void SerialFlashChip::eraseAll() busy = 3; } +void SerialFlashChip::unprotectAll() +{ + SPIPORT.beginTransaction(SPICONFIG); + CSASSERT(); + // write enable command + SPIPORT.transfer(0x06); + CSRELEASE(); + delayMicroseconds(1); + CSASSERT(); + // Write status register + SPIPORT.transfer(0x01); + SPIPORT.transfer(0x02); //WEL=1, rest of the bits are 0 + CSRELEASE(); + SPIPORT.endTransaction(); + wait(); +} + void SerialFlashChip::eraseBlock(uint32_t addr) { uint8_t f = flags; @@ -393,6 +419,7 @@ bool SerialFlashChip::begin(uint8_t pin) } flags = f; readID(id); + totalCapacity = size; return true; } @@ -453,8 +480,9 @@ uint32_t SerialFlashChip::capacity(const uint8_t *id) { uint32_t n = 1048576; // unknown chips, default to 1 MByte - if (id[0] == ID0_ADESTO && id[1] == 0x89) { - n = 1048576*16; //16MB + if (id[0] == ID0_ADESTO) { + //bottom 5 bits of ID1 specify capacity + n = 1ul << (15 + (id[1] & 0b11111)); } else if (id[2] >= 16 && id[2] <= 31) { n = 1ul << id[2]; @@ -526,5 +554,5 @@ AT25SF128A 32 64 // SST26VF064 8 ? BF 26 43 // LE25U40CMC 1/2 64 62 06 13 // Adesto AT25SF128A 16 1F 89 01 - +// Adesto AT25SF641B 8 ? 1F 88 01 SerialFlashChip SerialFlash; diff --git a/SerialFlashDirectory.cpp b/SerialFlashDirectory.cpp index d97d971..dee83c3 100644 --- a/SerialFlashDirectory.cpp +++ b/SerialFlashDirectory.cpp @@ -53,6 +53,8 @@ up to 262140 bytes for string data. An array of 16 bit filename hashes allows for quick linear search for potentially matching filenames. A hash value of 0xFFFF indicates no file is allocated for the remainder of the array. +When a file is deleted the hash value is zeored and stored. Hash 0 +is still treated as valid, though it is an abandoned hash. Following the hashes, and array of 10 byte structs give the location and length of the file's actual data, and the offset of its filename @@ -67,6 +69,7 @@ Strings are null terminated. The remainder of the chip is file data. static uint32_t check_signature(void) { + static uint8_t eraseRepeat = 0; uint32_t sig[2]; SerialFlash.read(0, sig, 8); @@ -79,6 +82,12 @@ static uint32_t check_signature(void) while (!SerialFlash.ready()) ; // TODO: timeout SerialFlash.read(0, sig, 8); if (sig[0] == 0xFA96554C) return sig[1]; + } else if (eraseRepeat == 0) { + //unknown contents - unprotect and erase the chip + eraseRepeat = 1; + SerialFlash.unprotectAll(); + SerialFlash.eraseAll(); + return check_signature(); } return 0; } @@ -125,7 +134,7 @@ void pbuf(const void *buf, uint32_t len) } #endif -SerialFlashFile SerialFlashChip::open(const char *filename) +SerialFlashFile SerialFlashChip::open(const char *filename, char mode) { uint32_t maxfiles, straddr; uint16_t hash, hashtable[8]; @@ -133,6 +142,15 @@ SerialFlashFile SerialFlashChip::open(const char *filename) uint32_t buf[3]; SerialFlashFile file; + if (mode == 'w') { + file = open(filename, 'r'); + // check the file was not already pre-created + if (!file) { + // create an auto growing file (writing check is inside createErasable()) + return SerialFlashChip::createErasable(filename, 0); + } + return file; + } maxfiles = check_signature(); //Serial.printf("sig: %08X\n", maxfiles); if (!maxfiles) return file; @@ -142,9 +160,12 @@ SerialFlashFile SerialFlashChip::open(const char *filename) while (index < maxfiles) { n = 8; if (n > maxfiles - index) n = maxfiles - index; + //read next 8 (or less) filename hashes SerialFlash.read(8 + index * 2, hashtable, n * 2); //Serial.printf(" read %u: ", 8 + index * 2); //pbuf(hashtable, n * 2); + + // go through filename hashes hand try to find a match for (i=0; i < n; i++) { if (hashtable[i] == hash) { //Serial.printf(" hash match at index %u\n", index+i); @@ -164,6 +185,9 @@ SerialFlashFile SerialFlashChip::open(const char *filename) file.length = buf[1]; file.offset = 0; file.dirindex = index + i; + if (file.length == 0xFFFFFFFF) { + file.length = 0; + } return file; } } else if (hashtable[i] == 0xFFFF) { @@ -253,25 +277,30 @@ static uint32_t string_length(uint32_t addr) // } fileinfo[maxfiles] // char strings[stringssize] -bool SerialFlashChip::create(const char *filename, uint32_t length, uint32_t align) +SerialFlashFile SerialFlashChip::create(const char *filename, uint32_t length, uint32_t align) { uint32_t maxfiles, stringsize; uint32_t index, buf[3]; uint32_t address, straddr, len; SerialFlashFile file; + // check we are writing an auto-growing file + if (writing) { + SerialFlashChip::lastErr = SF_ERROR_WRITING; + return file; + } // check if the file already exists - if (exists(filename)) return false; + if (exists(filename)) return file; // first, get the filesystem parameters maxfiles = check_signature(); - if (!maxfiles) return false; + if (!maxfiles) return file; stringsize = (maxfiles & 0xFFFF0000) >> 14; maxfiles &= 0xFFFF; // find the first unused slot for this file index = find_first_unallocated_file_index(maxfiles); - if (index >= maxfiles) return false; + if (index >= maxfiles) return file; //Serial.printf("index = %u\n", index); // compute where to store the filename and actual data straddr = 8 + maxfiles * 12; @@ -294,9 +323,11 @@ bool SerialFlashChip::create(const char *filename, uint32_t length, uint32_t ali address /= align; address *= align; //Serial.printf("align address = %u\n", address); - length += align - 1; - length /= align; - length *= align; + if (length > 0) { + length += align - 1; + length /= align; + length *= align; + } //Serial.printf("align length = %u\n", length); } else { // always align every file to a page boundary @@ -312,11 +343,11 @@ bool SerialFlashChip::create(const char *filename, uint32_t length, uint32_t ali // TODO: check for enough string space for filename uint8_t id[5]; SerialFlash.readID(id); - if (address + length > SerialFlash.capacity(id)) return false; + if (address + length > SerialFlash.capacity(id)) return file; SerialFlash.write(straddr, filename, len+1); buf[0] = address; - buf[1] = length; + buf[1] = (length == 0) ? 0xFFFFFFFF : length; // length 0 - auto growing file (FF can be later overwitten) buf[2] = (straddr - (8 + maxfiles * 12)) / 4; SerialFlash.write(8 + maxfiles * 2 + index * 10, buf, 10); //Serial.printf(" write %u: ", 8 + maxfiles * 2 + index * 10); @@ -327,7 +358,14 @@ bool SerialFlashChip::create(const char *filename, uint32_t length, uint32_t ali //Serial.printf("hash = %04X\n", buf[0]); SerialFlash.write(8 + index * 2, buf, 2); while (!SerialFlash.ready()) ; // TODO: timeout - return true; + if (length == 0) { + writing = true; + } + file.address = address; + file.length = length; + file.offset = 0; + file.dirindex = index; + return file; } bool SerialFlashChip::readdir(char *filename, uint32_t strsize, uint32_t &filesize) @@ -378,16 +416,58 @@ bool SerialFlashChip::readdir(char *filename, uint32_t strsize, uint32_t &filesi return true; } +SerialFlashFile SerialFlashChip::createErasable(const char *filename, uint32_t length) { + return create(filename, length, blockSize()); +} + void SerialFlashFile::erase() { uint32_t i, blocksize; + if (!address) { + SerialFlashChip::lastErr = SF_ERROR_CLOSED; + return; + } blocksize = SerialFlash.blockSize(); if (address & (blocksize - 1)) return; // must begin on a block boundary if (length & (blocksize - 1)) return; // must be exact number of blocks for (i=0; i < length; i += blocksize) { SerialFlash.eraseBlock(address + i); } + SerialFlashChip::lastErr = SF_OK; } +void SerialFlashFile::close() +{ + if (!address) { + SerialFlashChip::lastErr = SF_ERROR_CLOSED; + return; + } + + // store current size of auto-growing file + if (!length) { + uint32_t maxfiles; + + maxfiles = check_signature(); + if (!maxfiles) { + SerialFlashChip::lastErr = SF_ERROR_FAILED; + return; + } + maxfiles &= 0xFFFF; + //Serial.printf("close: dirindex=%i maxfiles=%i size=%i\n", dirindex, maxfiles, offset); + + // overwrite the length (second 4byte integer), which should be 0xFFFFFFFF, + // therefore could be overwritten. + SerialFlash.write(8 + (maxfiles * 2) + ((dirindex) * 10) + 4, &offset, 4); + + // indicate the autogrowing file is no longer written + SerialFlashChip::writing = false; + } + + //invalidate the file + address = 0; + offset = 0; + + SerialFlashChip::lastErr = SF_OK; +}