@@ -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 |