| @@ -0,0 +1,98 @@ | |||
| #ifndef SerialFlash_h_ | |||
| #define SerialFlash_h_ | |||
| #include <Arduino.h> | |||
| #include <SPI.h> | |||
| class SerialFlashFile; | |||
| class SerialFlashChip | |||
| { | |||
| public: | |||
| static bool begin(); | |||
| static uint32_t capacity(); | |||
| static uint32_t blockSize(); | |||
| static void read(void *buf, uint32_t addr, uint32_t len); | |||
| static bool ready(); | |||
| static void wait(); | |||
| static void write(const void *buf, uint32_t addr, uint32_t len); | |||
| static void eraseAll(); | |||
| static SerialFlashFile open(const char *filename); | |||
| static bool create(const char *filename, uint32_t length, uint32_t align = 0); | |||
| static bool createWritable(const char *filename, uint32_t length) { | |||
| return create(filename, length, 256); | |||
| } | |||
| static bool createErasable(const char *filename, uint32_t length) { | |||
| return create(filename, length, blockSize()); | |||
| } | |||
| static void opendir() { dirindex = 0; } | |||
| static bool readdir(char *filename, uint32_t strsize, uint32_t &filesize); | |||
| private: | |||
| static uint16_t dirindex; // current position for readdir() | |||
| static uint8_t fourbytemode; // 0=use 24 bit address, 1=use 32 bit address | |||
| static uint8_t busy; // 0 = ready, 1 = suspendable busy, 2 = busy for realz | |||
| static uint8_t blocksize; // erasable uniform block size, 1=4K, 2=8K, etc | |||
| static uint8_t capacityId; // 3rd byte from 0x9F identification command | |||
| }; | |||
| extern SerialFlashChip SerialFlash; | |||
| class SerialFlashFile | |||
| { | |||
| public: | |||
| SerialFlashFile() : address(0) { | |||
| } | |||
| operator bool() { | |||
| if (address > 0) return true; | |||
| return false; | |||
| } | |||
| uint32_t read(uint8_t *buf, uint32_t rdlen) { | |||
| if (offset + rdlen > length) { | |||
| if (offset >= length) return 0; | |||
| rdlen = length - offset; | |||
| } | |||
| SerialFlash.read(buf, address + offset, rdlen); | |||
| offset += rdlen; | |||
| return rdlen; | |||
| } | |||
| uint32_t write(const void *buf, uint32_t wrlen) { | |||
| if (offset + wrlen > length) { | |||
| if (offset >= length) return 0; | |||
| wrlen = length - offset; | |||
| } | |||
| SerialFlash.write(buf, address + offset, wrlen); | |||
| offset += wrlen; | |||
| return wrlen; | |||
| } | |||
| void seek(uint32_t n) { | |||
| offset = n; | |||
| } | |||
| uint32_t position() { | |||
| return offset; | |||
| } | |||
| uint32_t size() { | |||
| return length; | |||
| } | |||
| uint32_t available() { | |||
| if (offset >= length) return 0; | |||
| return length - offset; | |||
| } | |||
| void erase(); | |||
| void flush() { | |||
| } | |||
| void close() { | |||
| } | |||
| uint32_t getFlashAddress() { | |||
| return address; | |||
| } | |||
| protected: | |||
| friend class SerialFlashChip; | |||
| uint32_t address; // where this file's data begins in the Flash, or zero | |||
| uint32_t length; // total length of the data in the Flash chip | |||
| uint32_t offset; // current read/write offset in the file | |||
| }; | |||
| #endif | |||
| @@ -0,0 +1,190 @@ | |||
| #include "SerialFlash.h" | |||
| #include "SPIFIFO.h" | |||
| #define CSCONFIG() pinMode(6, OUTPUT) | |||
| #define CSASSERT() digitalWriteFast(6, LOW) | |||
| #define CSRELEASE() digitalWriteFast(6, HIGH) | |||
| #define SPICONFIG SPISettings(50000000, MSBFIRST, SPI_MODE0) | |||
| uint16_t SerialFlashChip::dirindex = 0; | |||
| uint8_t SerialFlashChip::fourbytemode = 0; | |||
| uint8_t SerialFlashChip::busy = 0; | |||
| uint8_t SerialFlashChip::blocksize = 1; | |||
| uint8_t SerialFlashChip::capacityId = 0; | |||
| void SerialFlashChip::wait(void) | |||
| { | |||
| uint32_t status; | |||
| do { | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| status = SPI.transfer16(0x0500); | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| } while ((status & 1)); | |||
| busy = 0; | |||
| } | |||
| void SerialFlashChip::read(void *buf, uint32_t addr, uint32_t len) | |||
| { | |||
| uint8_t *p = (uint8_t *)buf; | |||
| uint8_t b; | |||
| memset(p, 0, len); | |||
| b = busy; | |||
| if (b) { | |||
| if (b == 1) { | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| SPI.transfer(0x75); // Suspend program/erase | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| delayMicroseconds(20); // Tsus = 20us | |||
| } else { | |||
| wait(); | |||
| } | |||
| } | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| // TODO: FIFO optimize.... | |||
| if (fourbytemode) { | |||
| SPI.transfer(0x13); | |||
| SPI.transfer16(addr >> 16); | |||
| SPI.transfer16(addr); | |||
| } else { | |||
| SPI.transfer16(0x0300 | ((addr >> 16) & 255)); | |||
| SPI.transfer16(addr); | |||
| } | |||
| SPI.transfer(p, len); | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| if (b == 1) { | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| SPI.transfer(0x7A); // Resume program/erase | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| } | |||
| } | |||
| void SerialFlashChip::write(const void *buf, uint32_t addr, uint32_t len) | |||
| { | |||
| const uint8_t *p = (const uint8_t *)buf; | |||
| uint32_t max, pagelen; | |||
| do { | |||
| if (busy) wait(); | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| SPI.transfer(0x06); | |||
| CSRELEASE(); | |||
| //delayMicroseconds(1); | |||
| max = 256 - (addr & 0xFF); | |||
| pagelen = (len <= max) ? len : max; | |||
| len -= pagelen; | |||
| CSASSERT(); | |||
| if (fourbytemode) { | |||
| SPI.transfer(0x12); | |||
| SPI.transfer16(addr >> 16); | |||
| SPI.transfer16(addr); | |||
| } else { | |||
| SPI.transfer16(0x0200 | ((addr >> 16) & 255)); | |||
| SPI.transfer16(addr); | |||
| } | |||
| do { | |||
| SPI.transfer(*p++); | |||
| } while (--pagelen > 0); | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| busy = 1; | |||
| } while (len > 0); | |||
| } | |||
| void SerialFlashChip::eraseAll() | |||
| { | |||
| if (busy) wait(); | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| SPI.transfer(0x06); | |||
| CSRELEASE(); | |||
| CSASSERT(); | |||
| SPI.transfer(0xC7); | |||
| CSRELEASE(); | |||
| busy = 2; | |||
| } | |||
| bool SerialFlashChip::ready() | |||
| { | |||
| uint32_t status; | |||
| if (!busy) return true; | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| status = SPI.transfer16(0x0500); | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| if ((status & 1)) return false; | |||
| busy = 0; | |||
| return true; | |||
| } | |||
| bool SerialFlashChip::begin() | |||
| { | |||
| SPI.begin(); | |||
| if (busy) wait(); | |||
| CSCONFIG(); | |||
| SPI.beginTransaction(SPICONFIG); | |||
| CSASSERT(); | |||
| SPI.transfer(0x9F); | |||
| SPI.transfer(0); // manufacturer ID | |||
| SPI.transfer(0); // memory type | |||
| capacityId = SPI.transfer(0); // capacity | |||
| CSRELEASE(); | |||
| SPI.endTransaction(); | |||
| //Serial.print("capacity = "); | |||
| //Serial.println(capacityId, HEX); | |||
| if ((capacityId & 0xF0) == 0x20) { | |||
| fourbytemode = 1; // chip larger than 16 MByte | |||
| } else { | |||
| fourbytemode = 0; | |||
| } | |||
| // TODO: how to detect the uniform sector erase size? | |||
| blocksize = 1; | |||
| return true; | |||
| } | |||
| uint32_t SerialFlashChip::capacity() | |||
| { | |||
| return 16777216; // TODO: compute this from capacityId... | |||
| } | |||
| uint32_t SerialFlashChip::blockSize() | |||
| { | |||
| return 4096; // TODO: how to discover this? | |||
| } | |||
| // size sector | |||
| // Part Mbit kbyte ID bytes Digikey | |||
| // ---- ---- ----- -------- ------- | |||
| // Winbond W25Q128FV 128 EF 40 18 W25Q128FVSIG-ND | |||
| // Winbond W25Q256FV 256 64 EF 40 19 | |||
| // SST SST25VF016B 16 BF 25 41 | |||
| // Spansion S25FL127S 128 64? 01 20 18 1274-1045-ND | |||
| // Spansion S25FL128P 128 01 20 18 | |||
| // Spansion S25FL064A 64 01 02 16 | |||
| // Macronix MX25L12805D 128 C2 20 18 | |||
| // Micron M25P80 8 20 20 14 | |||
| // Numonyx M25P128 128 20 20 18 | |||
| // SST SST25WF512 0.5 BF 25 01 | |||
| // SST SST25WF010 1 BF 25 02 | |||
| // SST SST25WF020 2 BF 25 03 | |||
| // SST SST25WF040 4 BF 25 04 | |||
| // Spansion FL127S 128 01 20 18 ? | |||
| // Spansion S25FL512S 512 01 02 20 ? | |||
| // Micron N25Q512A 512 4 20 BA 20 557-1569-ND | |||
| // Micron N25Q00AA 1024 4/64 20 BA 21 557-1571-5-ND | |||
| // Micron MT25QL02GC 2048 4/64 20 BB 22 | |||
| SerialFlashChip SerialFlash; | |||
| @@ -0,0 +1,266 @@ | |||
| #include "SerialFlash.h" | |||
| #include "util/crc16.h" | |||
| /* On-chip SerialFlash file allocation data structures: | |||
| uint32_t signature = 0xFA96554C; | |||
| uint16_t maxfiles | |||
| uint16_t stringssize // div by 4 | |||
| uint16_t hashes[maxfiles] | |||
| struct { | |||
| uint32_t file_begin | |||
| uint32_t file_length | |||
| uint16_t string_index // div4 | |||
| } fileinfo[maxfiles] | |||
| char strings[stringssize] | |||
| A 32 bit signature is stored at the beginning of the flash memory. | |||
| If 0xFFFFFFFF is seen, the entire chip should be assumed blank. | |||
| If any value other than 0xFA96554C is found, a different data format | |||
| is stored. This could should refuse to access the flash. | |||
| The next 4 bytes store number of files and size of the strings | |||
| section, which allow the position of every other item to be found. | |||
| The string section size is the 16 bit integer times 4, which allows | |||
| 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. | |||
| 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 | |||
| in the strings section. | |||
| Strings are null terminated. The remainder of the chip is file data. | |||
| */ | |||
| #define DEFAULT_MAXFILES 600 | |||
| #define DEFAULT_STRINGS_SIZE 25560 | |||
| static uint32_t check_signature(void) | |||
| { | |||
| uint32_t sig[2]; | |||
| SerialFlash.read(sig, 0, 8); | |||
| if (sig[0] == 0xFA96554C) return sig[1]; | |||
| if (sig[0] == 0xFFFFFFFF) { | |||
| sig[0] = 0xFA96554C; | |||
| sig[1] = ((DEFAULT_STRINGS_SIZE/4) << 16) | DEFAULT_MAXFILES; | |||
| SerialFlash.write(sig, 0, 8); | |||
| while (!SerialFlash.ready()) ; // TODO: timeout | |||
| SerialFlash.read(sig, 0, 8); | |||
| if (sig[0] == 0xFA96554C) return sig[1]; | |||
| } | |||
| return 0; | |||
| } | |||
| static uint16_t filename_hash(const char *filename) | |||
| { | |||
| uint16_t crc; | |||
| const char *p; | |||
| crc = 0xFFFF; | |||
| for (p=filename; *p; p++) { | |||
| // TODO: replace with fast CRC hardware? | |||
| crc = _crc16_update(crc, *p); | |||
| } | |||
| crc ^= 0xFFFF; | |||
| if (crc == 0xFFFF) crc = 0; | |||
| return crc; | |||
| } | |||
| static bool filename_compare(const char *filename, uint32_t straddr) | |||
| { | |||
| unsigned int i; | |||
| const char *p; | |||
| char buf[16]; | |||
| p = filename; | |||
| while (1) { | |||
| SerialFlash.read(buf, straddr, sizeof(buf)); | |||
| straddr += sizeof(buf); | |||
| for (i=0; i < sizeof(buf); i++) { | |||
| if (*p++ != buf[i]) return false; | |||
| if (buf[i] == 0) return true; | |||
| } | |||
| } | |||
| } | |||
| SerialFlashFile SerialFlashChip::open(const char *filename) | |||
| { | |||
| uint32_t maxfiles, straddr; | |||
| uint16_t hash, hashtable[8]; | |||
| uint32_t i, n, index=0; | |||
| uint32_t buf[3]; | |||
| SerialFlashFile file; | |||
| maxfiles = check_signature(); | |||
| if (!maxfiles) return file; | |||
| maxfiles &= 0xFFFF; | |||
| hash = filename_hash(filename); | |||
| while (index < maxfiles) { | |||
| n = 8; | |||
| if (n > maxfiles - index) n = maxfiles - index; | |||
| SerialFlash.read(hashtable, 8 + index * 2, n * 2); | |||
| for (i=0; i < n; i++) { | |||
| if (hashtable[i] == hash) { | |||
| buf[2] = 0; | |||
| SerialFlash.read(buf, 8 + maxfiles * 2 + (index+i) * 10, 10); | |||
| straddr = 8 + maxfiles * 12 + buf[2] * 4; | |||
| if (filename_compare(filename, straddr)) { | |||
| file.address = buf[0]; | |||
| file.length = buf[1]; | |||
| file.offset = 0; | |||
| return file; | |||
| } | |||
| } else if (hashtable[i] == 0xFFFF) { | |||
| return file; | |||
| } | |||
| } | |||
| index += n; | |||
| } | |||
| return file; | |||
| } | |||
| static uint32_t find_first_unallocated_file_index(uint32_t maxfiles) | |||
| { | |||
| uint16_t hashtable[8]; | |||
| uint32_t i, n, index=0; | |||
| do { | |||
| n = 8; | |||
| if (index + n > maxfiles) n = maxfiles - index; | |||
| SerialFlash.read(hashtable, 8 + index * 2, n * 2); | |||
| for (i=0; i < n; i++) { | |||
| if (hashtable[i] == 0xFFFF) return index + i; | |||
| } | |||
| index += n; | |||
| } while (index < maxfiles); | |||
| return 0xFFFFFFFF; | |||
| } | |||
| static uint32_t string_length(uint32_t addr) | |||
| { | |||
| char buf[16]; | |||
| const char *p; | |||
| uint32_t len=0; | |||
| while (1) { | |||
| SerialFlash.read(buf, addr, sizeof(buf)); | |||
| for (p=buf; p < buf + sizeof(buf); p++) { | |||
| if (*p == 0) return len; | |||
| len++; | |||
| } | |||
| addr += len; | |||
| } | |||
| } | |||
| // uint32_t signature = 0xFA96554C; | |||
| // uint16_t maxfiles | |||
| // uint16_t stringssize // div by 4 | |||
| // uint16_t hashes[maxfiles] | |||
| // struct { | |||
| // uint32_t file_begin | |||
| // uint32_t file_length | |||
| // uint16_t string_index // div 4 | |||
| // } fileinfo[maxfiles] | |||
| // char strings[stringssize] | |||
| bool 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; | |||
| // first, get the filesystem parameters | |||
| maxfiles = check_signature(); | |||
| if (!maxfiles) return false; | |||
| 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; | |||
| // compute where to store the filename and actual data | |||
| straddr = 8 + maxfiles * 12; | |||
| if (index == 0) { | |||
| address = straddr + stringsize; | |||
| } else { | |||
| buf[2] = 0; | |||
| SerialFlash.read(buf, 8 + maxfiles * 2 + (index-1) * 10, 10); | |||
| address = buf[0] + buf[1]; | |||
| straddr += buf[2] * 4; | |||
| straddr += string_length(straddr); | |||
| straddr = (straddr + 3) & 0x0003FFFC; | |||
| } | |||
| // for files aligned to pages or sectors, adjust addr & len | |||
| if (align > 0) { | |||
| address += align - 1; | |||
| address /= align; | |||
| address *= align; | |||
| length += align - 1; | |||
| length /= align; | |||
| length *= align; | |||
| } | |||
| // last check, if enough space exists... | |||
| len = strlen(filename); | |||
| // TODO: check for enough string space for filename | |||
| if (address + length > SerialFlash.capacity()) return false; | |||
| SerialFlash.write(filename, straddr, len+1); | |||
| buf[0] = address; | |||
| buf[1] = length; | |||
| buf[2] = (straddr - (8 + maxfiles * 12)) / 4; | |||
| SerialFlash.write(buf, 8 + maxfiles * 2 + index * 10, 10); | |||
| buf[0] = filename_hash(filename); | |||
| SerialFlash.write(buf, 8 + index * 2, 2); | |||
| while (!SerialFlash.ready()) ; // TODO: timeout | |||
| return false; | |||
| } | |||
| bool SerialFlashChip::readdir(char *filename, uint32_t strsize, uint32_t &filesize) | |||
| { | |||
| uint32_t maxfiles, index, straddr; | |||
| uint32_t i, n; | |||
| uint32_t buf[2]; | |||
| char str[16], *p=filename; | |||
| filename[0] = 0; | |||
| maxfiles = check_signature(); | |||
| if (!maxfiles) return false; | |||
| maxfiles &= 0xFFFF; | |||
| index = dirindex; | |||
| if (index >= maxfiles) return false; | |||
| dirindex = index + 1; | |||
| buf[1] = 0; | |||
| SerialFlash.read(buf, 8 + 4 + maxfiles * 2 + index * 10, 6); | |||
| if (buf[0] == 0xFFFFFFFF) return false; | |||
| filesize = buf[0]; | |||
| straddr = 8 + maxfiles * 12 + buf[1] * 4; | |||
| while (strsize) { | |||
| n = strsize; | |||
| if (n > sizeof(str)) n = sizeof(str); | |||
| SerialFlash.read(str, straddr, n); | |||
| for (i=0; i < n; i++) { | |||
| *p++ = str[i]; | |||
| if (str[i] == 0) { | |||
| return true; | |||
| } | |||
| } | |||
| strsize -= n; | |||
| } | |||
| *(p - 1) = 0; | |||
| return true; | |||
| } | |||
| void SerialFlashFile::erase() | |||
| { | |||
| // TODO: erase all the blocks of a file | |||
| // if it's been block aligned, of course | |||
| } | |||
| @@ -0,0 +1,96 @@ | |||
| #include <SerialFlash.h> | |||
| #include <SPI.h> | |||
| SerialFlashFile file; | |||
| void setup() { | |||
| char filename[40]; | |||
| uint32_t len; | |||
| while (!Serial) ; | |||
| delay(10); | |||
| SPI.setSCK(14); // Audio shield has SCK on pin 14 | |||
| SPI.setMOSI(7); // Audio shield has MOSI on pin 7 | |||
| Serial.println("Test Hardware"); | |||
| SerialFlash.begin(); | |||
| #if 0 | |||
| Serial.println("erase"); | |||
| SerialFlash.eraseAll(); | |||
| while (!SerialFlash.ready()) { | |||
| } | |||
| Serial.println("erase done"); | |||
| #endif | |||
| Serial.println("Directory:"); | |||
| while (SerialFlash.readdir(filename, sizeof(filename), len)) { | |||
| Serial.print(" file: "); | |||
| Serial.print(filename); | |||
| Serial.print(" bytes: "); | |||
| Serial.print(len); | |||
| Serial.println(); | |||
| } | |||
| Serial.println(); | |||
| Serial.println("simple.txt test"); | |||
| file = SerialFlash.open("simple.txt"); | |||
| if (file) { | |||
| Serial.println(" file opened"); | |||
| Serial.print(" length = "); | |||
| Serial.println(file.size()); | |||
| Serial.print(" addr on chip = "); | |||
| Serial.println(file.getFlashAddress()); | |||
| file.close(); | |||
| } else { | |||
| Serial.println(" create file"); | |||
| SerialFlash.create("simple.txt", 516); | |||
| } | |||
| Serial.println("soundfile.wav test"); | |||
| file = SerialFlash.open("soundfile.wav"); | |||
| if (file) { | |||
| Serial.println(" file opened"); | |||
| Serial.print(" length = "); | |||
| Serial.println(file.size()); | |||
| Serial.print(" addr on chip = "); | |||
| Serial.println(file.getFlashAddress()); | |||
| file.close(); | |||
| } else { | |||
| Serial.println(" create file"); | |||
| SerialFlash.createWritable("soundfile.wav", 3081000); | |||
| } | |||
| Serial.println("wavetable1 test"); | |||
| file = SerialFlash.open("wavetable1"); | |||
| if (file) { | |||
| Serial.println(" file opened"); | |||
| Serial.print(" length = "); | |||
| Serial.println(file.size()); | |||
| Serial.print(" addr on chip = "); | |||
| Serial.println(file.getFlashAddress()); | |||
| file.close(); | |||
| } else { | |||
| Serial.println(" create file"); | |||
| SerialFlash.create("wavetable1", 181003); | |||
| } | |||
| Serial.println("end"); | |||
| } | |||
| void loop() { | |||
| } | |||
| void printbuf(const void *buf, uint32_t len) | |||
| { | |||
| const uint8_t *p = (const uint8_t *)buf; | |||
| do { | |||
| Serial.print(*p++); | |||
| Serial.print(" "); | |||
| } while (--len > 0); | |||
| Serial.println(); | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| SerialFlash KEYWORD1 | |||
| createWritable KEYWORD2 | |||
| erase KEYWORD2 | |||
| eraseAll KEYWORD2 | |||
| ready KEYWORD2 | |||
| create KEYWORD2 | |||
| createWritable KEYWORD2 | |||
| getAddress KEYWORD2 | |||