#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 |
#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; |
#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 | |||||
} | |||||
#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(); | |||||
} | |||||
SerialFlash KEYWORD1 | |||||
createWritable KEYWORD2 | |||||
erase KEYWORD2 | |||||
eraseAll KEYWORD2 | |||||
ready KEYWORD2 | |||||
create KEYWORD2 | |||||
createWritable KEYWORD2 | |||||
getAddress KEYWORD2 |