Skip to content

Instantly share code, notes, and snippets.

@ersatzavian
Created May 21, 2015 16:54
Show Gist options
  • Save ersatzavian/8dfcecb69e0ccfd90bc9 to your computer and use it in GitHub Desktop.
Save ersatzavian/8dfcecb69e0ccfd90bc9 to your computer and use it in GitHub Desktop.
Spiflash_w_FAT
// MX25L3206E SPI Flash
class spiFlash {
// 16 blocks of 64k each = 1mb
static FAT_SIZE = 65536; // 1 block reserved for FAT
static TOTAL_MEMORY = 1048576; // 1 Megabytes
static LOAD_BUFFER_SIZE = 8192; // 8kb
// spi interface
spi = null;
cs_l = null;
// The file allocation table (FAT)
fat = null;
config = null;
load_pos = 0;
current_file = null;
// The callbacks
init_callback = null;
load_files_files = [];
load_files_callback = null;
// Status
busy = false;
// -------------------------------------------------------------------------
// constructor takes in pre-configured spi interface object and chip select GPIO
constructor(spiBus, csPin) {
spi = spiBus;
cs_l = csPin;
// Setup the event handlers
agent.on("flash.load.start", load_start.bindenv(this));
agent.on("flash.load.data", load_data.bindenv(this));
agent.on("flash.load.finish", load_finish.bindenv(this));
agent.on("flash.load.error", load_error.bindenv(this));
spi.configure(CLOCK_IDLE_LOW | MSB_FIRST, SPI_CLOCK_SPEED_FLASH);
cs_l.configure(DIGITAL_OUT);
// Check the flash is alive by readin the manufacturer details
cs_l.write(0);
local i = 0;
for (i = 0; i <= 100; i++) {
spi.write(RDID);
local data = spi.readblob(3);
if (data[0] != 0x0 && data[0] != 0xFF) {
// server.log(format("SPI Flash version: %d.%d", data[0], (data[1] << 8) | data[2]));
break;
} else {
imp.sleep(0.01);
}
}
cs_l.write(1);
if (i == 100) {
throw "SPI Flash didn't boot in time";
}
}
// -------------------------------------------------------------------------
function init(callback = null, clobber = false) {
// Prepare the initialisation callback
if (callback) init_callback = callback;
// Initialise the FAT
if (!clobber && load_fat()) {
if (init_callback) imp.wakeup(0, init_callback);
} else {
busy = true;
server.log("Start erasing the flash")
eraseChip(function () {
server.log("Formatting the FAT")
format_fat(function() {
busy = false;
if (init_callback) imp.wakeup(0, init_callback);
}.bindenv(this));
}.bindenv(this));
}
}
// -------------------------------------------------------------------------
function wrenable() {
cs_l.write(0);
spi.write(WREN);
cs_l.write(1);
}
// -------------------------------------------------------------------------
// pages should be pre-erased before writing
function write(addr, data) {
wrenable();
// check the status register's write enabled bit
if (!(getStatus() & 0x02)) {
server.log("Flash write not Enabled");
return 1;
}
cs_l.write(0);
spi.write(PP);
spi.write(format("%c%c%c", (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF));
spi.write(data);
cs_l.write(1);
// wait for the status register to show write complete
// typical 1.4 ms, max 5 ms
local timeout = 50000; // time in us
local start = hardware.micros();
while (getStatus() & 0x01) {
if ((hardware.micros() - start) > timeout) {
server.log("Timed out waiting for write to finish");
return 1;
}
}
return 0;
}
// -------------------------------------------------------------------------
// allow data chunks greater than one flash page to be written in a single op
function writeString(addr, data, callback = null) {
// separate the chunk into pages
for (local i = 0; i < data.len(); i+=256) {
local leftInBuffer = data.len() - i;
if (leftInBuffer < 256) {
write(addr+i, data.slice(i));
} else {
write(addr+i, data.slice(i, i+256));
}
}
if (callback) callback();
}
// -------------------------------------------------------------------------
// allow data chunks greater than one flash page to be written in a single op
function writeBlob(addr, data, callback = null) {
// separate the chunk into pages
local drb = data.readblob.bindenv(data);
data.seek(0,'b');
for (local i = 0; i < data.len(); i+=256) {
local leftInBuffer = data.len() - i;
if (leftInBuffer < 256) {
write((addr+i), drb(leftInBuffer));
} else {
write((addr+i), drb(256));
}
}
if (callback) callback();
}
// -------------------------------------------------------------------------
function readBlob(addr, bytes) {
// to read, send the read command and a 24-bit address
cs_l.write(0);
spi.write(READ);
spi.write(format("%c%c%c", (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF));
local readBlob = spi.readblob(bytes);
cs_l.write(1);
return readBlob;
}
// -------------------------------------------------------------------------
function getStatus() {
cs_l.write(0);
spi.write(RDSR);
local status = spi.readblob(1);
cs_l.write(1);
return status[0];
}
// -------------------------------------------------------------------------
// set any 64kbyte block of flash to all 0xff
// takes a starting address, 24-bit, MSB-first
function eraseBlock(addr, callback = null) {
wrenable();
cs_l.write(0);
spi.write(BE);
spi.write(format("%c%c%c", (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF));
cs_l.write(1);
imp.wakeup(0, checkStatus(callback).bindenv(this));
}
// -------------------------------------------------------------------------
// set the entire flash to all 0xff
function eraseChip(callback = null) {
wrenable();
cs_l.write(0);
spi.write(CE);
cs_l.write(1);
imp.wakeup(1, checkStatus(callback).bindenv(this));
}
// -------------------------------------------------------------------------
// Checks the status of the last command (well returns a function that does)
function checkStatus(callback = null, interval = 0.25, mask = 0x01, timeout = 120) {
return function() {
local status = getStatus() & mask;
if (status) {
imp.wakeup(interval, checkStatus(callback, interval, mask, timeout).bindenv(this));
} else {
callback();
}
}
}
// -------------------------------------------------------------------------
// Initialise the FAT
function format_fat(callback = null) {
fat = {};
fat.root <- {};
fat.root.start <- 0;
fat.root.finish <- FAT_SIZE-1;
fat.root.free <- FAT_SIZE;
fat.root.files <- 0;
write_fat(callback);
}
// -------------------------------------------------------------------------
// Loads the FAT if its valid
function load_fat() {
// Read the length
local hexlen = readBlob(0, 3);
local len = (hexlen[0] << 8 | (hexlen[1] & 0xFF));
local crc = hexlen[2];
server.log(format("Reading FAT Length = %d (0x%02x 0x%02x), CRC = 0x%02x", len, hexlen[0], hexlen[1], hexlen[2]));
if (len == 0x0 || len == 0xFFFF) {
server.log("Flash has no FAT data");
return false;
}
// Read and deserialize the data
local chunk = readBlob(3, len);
try {
local chunkcrc = serializer.LRC8(chunk);
if (chunkcrc == crc) {
chunk = serializer.deserialize(chunk.tostring());
} else {
server.log(format("CRC mismatch when loading FAT (0x%02x != 0x%02x)", crc, chunkcrc));
return false;
}
} catch (e) {
server.log("Exception: " + e);
return false;
}
if (!("fat" in chunk && "root" in chunk.fat)) {
server.log("No FAT or CONFIG loaded.")
fat = {};
config = { updated = 0, email = null };
return false;
}
fat = chunk.fat;
server.log("Loaded FAT. It contains " + fat.root.files + " files")
if ("config" in chunk && typeof chunk.config == "table") {
config = chunk.config;
server.log("Loaded config. It contains " + config.len() + " entries")
// server.log("CONFIG loaded from flash with: " + config.email);
} else {
config = { updated = 0, email = null };
}
return true;
}
// -------------------------------------------------------------------------
// Writes the FAT table to the first block
function write_fat(callback = null) {
local chunk = serializer.serialize({"fat": fat, "config": config});
server.log(format("Writing FAT and CONFIG, length = %d (0x%02x 0x%02x), CRC = 0x%02x", chunk.len()-3, chunk[0], chunk[1], chunk[2]));
// server.log("CONFIG written to flash with: " + config.email);
// Erase the FAT and write the new one
eraseBlock(0, function() {
writeString(0, chunk, function() {
if (callback) callback();
}.bindenv(this));
}.bindenv(this));
}
// -------------------------------------------------------------------------
// Marks the start of a new recording
function new_file(filename) {
fat[filename] <- {};
fat[filename].filename <- filename;
fat[filename].start <- fat.root.free;
fat[filename].finish <- fat.root.free;
fat[filename].status <- 0; // Init
fat.root.files++;
}
// -------------------------------------------------------------------------
// Writes a new buffer of data to the file system
function append_file(filename, buffer, length) {
if (fat.root.free + length >= TOTAL_MEMORY) {
local oldlength = length;
length = TOTAL_MEMORY - fat.root.free;
server.log("Cropping the last sample from " + oldlength + " to " + length);
}
if (length > 0) {
// server.log("Data: " + length + " bytes for " + filename + ":" + fat[filename].finish);
writeBlob(fat[filename].finish, buffer.readblob(length));
fat[filename].finish += length;
fat[filename].status = FILE_STATUS_INIT;
fat.root.free += length;
}
return length;
}
// -------------------------------------------------------------------------
// Finalises the recording of a file
function close_file(filename, callback) {
// Align the next free pointer to the next page boundary
local realignment = (fat.root.free % 256 == 0) ? 0 : (256-(fat.root.free % 256));
fat.root.free += realignment;
if (filename in fat) fat[filename].status = FILE_STATUS_READY;
write_fat(function() {
if (callback) callback(filename);
}.bindenv(this))
}
// -------------------------------------------------------------------------
// Asks the agent to load a file
function load(filename, url, callback = null) {
if (file_exists(filename)) {
// server.log("Skipping existing file '" + filename + "' from '" + url + "'");
if (callback) callback(filename);
return;
}
server.log("Loading file '" + filename + "' from '" + url + "'");
busy = true;
new_file(filename);
fat[filename].callback <- callback;
local request = {};
request.filename <- filename;
request.url <- url;
request.start <- 0;
request.finish <- LOAD_BUFFER_SIZE;
agent.send("flash.load", request);
}
// -------------------------------------------------------------------------
// Initiation of a audio upload by the agent instead of the device
function load_start(request) {
if ("filename" in request && "url" in request) {
if (file_exists(request.filename)) {
// server.log("Skipping requested existing file '" + filename + "' from '" + url + "'");
return;
}
server.log("Receiving file '" + request.filename + "' from agent");
busy = true;
new_file(request.filename);
fat[request.filename].callback <- null;
local response = {};
response.filename <- request.filename;
response.url <- request.url;
response.start <- 0;
response.finish <- LOAD_BUFFER_SIZE;
agent.send("flash.load", response);
}
}
// -------------------------------------------------------------------------
// Handle the loading of a new data chunk from the agent
function load_data(response) {
local written = append_file(response.filename, response.chunk, response.chunk.len());
if (written == response.chunk.len()) {
response.start += response.chunk.len();
response.finish += response.chunk.len();
agent.send("flash.load", response);
} else {
response.err <- "Only " + written + " of " + response.chunk.len() + " bytes where written to flash";
load_error(response);
}
}
// -------------------------------------------------------------------------
// Handle the finish of an entire file from the agent
function load_finish(response) {
server.log("Finished writing " + (fat[response.filename].finish - fat[response.filename].start) + " bytes of '" + response.filename + "' to flash");
busy = false;
local callback = fat[response.filename].callback;
delete fat[response.filename].callback;
close_file(response.filename, callback);
}
// -------------------------------------------------------------------------
// Handle an error loading a file from the agent
function load_error(response) {
server.log("Error loading '" + response.filename + "' for flash: " + response.err);
busy = false;
if (fat[response.filename].callback) {
local callback = fat[response.filename].callback;
delete fat[response.filename].callback;
callback(response.filename);
}
delete fat[response.filename];
fat.root.files--;
write_fat();
}
// -------------------------------------------------------------------------
// Checks if a file exists
function file_exists(filename, status = FILE_STATUS_READY) {
return (filename in fat) && ("status" in fat[filename]) && (fat[filename].status == status);
}
// -------------------------------------------------------------------------
// Delete a file (mark it as deleted)
function unlink(filename, callback = null) {
if (file_exists(filename, FILE_STATUS_SENDING)) {
server.log("Deleting: " + filename);
fat[filename].status = FILE_STATUS_DELETED;
fat.root.files--;
write_fat(callback);
} else {
if (callback) callback();
}
}
// -------------------------------------------------------------------------
function load_files(_files = null, callback = null) {
if (callback) load_files_callback = callback;
if (_files) load_files_files = _files;
// Load all the audio files recursively
if (load_pos < load_files_files.len()) {
local file = load_files_files[load_pos];
load_pos++;
if (file_exists(file.filename)) {
// Skip this file
imp.wakeup(0, load_files.bindenv(this));
} else {
// Load this file
server.log("Started loading audio file: " + file.filename);
load(file.filename, file.url, function (filename) {
// Now skip to the next one
imp.wakeup(0, load_files.bindenv(this));
}.bindenv(this))
}
} else {
server.log("Finished loading " + load_pos + " files into flash.")
load_pos = 0;
load_files_files = [];
if (load_files_callback) {
local callback = load_files_callback;
load_files_callback = null;
callback();
}
}
}
// -------------------------------------------------------------------------
function prev_file() {
// Load up the list of files into a numbered list
local filenames = [];
foreach (filename,stuff in fat) {
if (file_exists(filename)) {
filenames.push(filename);
}
}
// Find the current file, in order to find the prev file
if (filenames.len() == 0) {
current_file = null;
} else if (current_file == null || !file_exists(current_file)) {
current_file = filenames[filenames.len()-1];
} else {
for (local i = filenames.len()-1; i >= 0; i--) {
if (filenames[i] == current_file) {
if (i-1 >= 0) {
current_file = filenames[i-1];
} else {
current_file = filenames[filenames.len()-1];
}
break;
}
}
}
return current_file;
}
// -------------------------------------------------------------------------
function next_file() {
// Load up the list of files into a numbered list
local filenames = [];
foreach (filename,stuff in fat) {
if (file_exists(filename)) {
filenames.push(filename);
}
}
// Find the current file, in order to find the next file
if (filenames.len() == 0) {
current_file = null;
} else if (current_file == null || !file_exists(current_file)) {
current_file = filenames[0];
} else {
for (local i = 0; i < filenames.len(); i++) {
if (filenames[i] == current_file) {
if (i+1 < filenames.len()) {
current_file = filenames[i+1];
} else {
current_file = filenames[0];
}
break;
}
}
}
return current_file;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment