Created
May 21, 2015 16:54
-
-
Save ersatzavian/8dfcecb69e0ccfd90bc9 to your computer and use it in GitHub Desktop.
Spiflash_w_FAT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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