Browse Source

Initial commit

main
PaulStoffregen 9 years ago
commit
2956c3ab59
5 changed files with 658 additions and 0 deletions
  1. +98
    -0
      SerialFlash.h
  2. +190
    -0
      SerialFlashChip.cpp
  3. +266
    -0
      SerialFlashDirectory.cpp
  4. +96
    -0
      examples/TestHardware/TestHardware.ino
  5. +8
    -0
      keywords.txt

+ 98
- 0
SerialFlash.h View File

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

+ 190
- 0
SerialFlashChip.cpp View File

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

+ 266
- 0
SerialFlashDirectory.cpp View File

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


+ 96
- 0
examples/TestHardware/TestHardware.ino View File

@@ -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();
}


+ 8
- 0
keywords.txt View File

@@ -0,0 +1,8 @@
SerialFlash KEYWORD1
createWritable KEYWORD2
erase KEYWORD2
eraseAll KEYWORD2
ready KEYWORD2
create KEYWORD2
createWritable KEYWORD2
getAddress KEYWORD2

Loading…
Cancel
Save