espruino-web-ide
Version:
A Terminal and Graphical code Editor for Espruino JavaScript Microcontrollers
431 lines (404 loc) • 14.9 kB
JavaScript
/**
Copyright 2014 Gordon Williams (gw@pur3.co.uk)
This Source Code is subject to the terms of the Mozilla Public
License, v2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
------------------------------------------------------------------
Actual low-level code for flashing Espruino Devices
------------------------------------------------------------------
**/
;
(function(){
var dataReceived = undefined; // listener for when data is received
var bytesReceived = []; // list of characters for when no handler is specified
var ACK = 0x79;
var NACK = 0x1F;
var DEFAULT_FLASH_OFFSET = 1024*10; /* Skip size of F1 bootloader by default */
var setStatus = function() {};
function init() {
}
var initialiseChip = function(callback, timeout) {
setStatus("Initialising...");
var iTimeout = setTimeout(function() {
dataReceived = undefined;
clearInterval(iPoll);
//callback("Can't find STM32 bootloader. Make sure the chip is reset with BOOT0=1 and BOOT1=0");
callback("Can't find STM32 bootloader. Make sure the chip is reset into bootloader mode by holding down BTN1 while pressing RST");
}, (timeout==undefined)?10000:timeout);
var iPoll = setInterval(function() {
console.log("Sending... 0x7F");
Espruino.Core.Serial.write("\x7f", false);
}, 70);
dataReceived = function (c) {
console.log("got "+c);
if (c==ACK || c==NACK) {
clearTimeout(iTimeout);
clearInterval(iPoll);
setStatus("Initialised.");
// wait for random extra data...
dataReceived = function(c){
console.log("Already ACKed but got "+c);
};
setTimeout(function() {
dataReceived = undefined;
// finally call callback
bodgeClock(callback);
}, 500);
}
};
};
var waitForACK = function(callback, timeout) {
var ms = timeout?timeout:1000;
var iTimeout = setTimeout(function() {
dataReceived = undefined;
callback("Timeout waiting for ACK - "+ms+"ms");
}, ms);
dataReceived = function (c) {
//console.log("Got data "+JSON.stringify(c));
dataReceived = undefined;
if (c==ACK) {
clearTimeout(iTimeout);
callback(undefined);
} else
callback("Expected ACK but got "+c);
};
};
var sendData = function(data, callback, timeout) {
var s = "";
var chksum = 0;
for (var i in data) {
chksum = chksum ^ data[i];
s += String.fromCharCode(data[i]);
}
Espruino.Core.Serial.write(s + String.fromCharCode(chksum), false);
/* wait for ACK *NOW* - not in the write callback, as by that time we
may have already received the ACK we were looking for */
waitForACK(callback, timeout);
};
var receiveData = function(count, callback, timeout) {
var data = new Uint8Array(count);
var dataCount = 0;
var iTimeout = setTimeout(function() {
dataReceived = undefined;
callback("Timeout reading "+count+" bytes");
}, timeout?timeout:2000);
dataReceived = function (c) {
data[dataCount++] = c;
if (dataCount == count) {
clearTimeout(iTimeout);
dataReceived = undefined;
callback(undefined,data);
}
};
};
var sendCommand = function(command, callback) {
Espruino.Core.Serial.write(String.fromCharCode(command) + String.fromCharCode(0xFF ^ command), false);
/* wait for ACK *NOW* - not in the write callback, as by that time we
may have already received the ACK we were looking for */
waitForACK(callback);
};
var eraseChip = function(callback) {
Espruino.Core.Status.setStatus("Erasing...");
// Extended erase
sendCommand(0x44, function(err) {
if (err) { callback(err); return; }
console.log("We may be some time...");
sendData([0xFF,0xFF], function(err) {
if (err) { callback(err); return; }
callback(undefined);
}, 20000/*timeout*/);
});
};
var readData = function(callback, addr, readBytes) {
console.log("Reading "+readBytes+" bytes from 0x"+addr.toString(16)+"...");
// send read command
sendCommand(0x11, function(err) {
if (err) {
console.log("Error sending command ("+err+").");
callback(err);
return;
}
// send address
sendData([(addr>>24)&0xFF,(addr>>16)&0xFF,(addr>>8)&0xFF,addr&0xFF], function(err) {
if (err) {
console.log("Error sending address. ("+err+")");
callback(err);
return;
}
// send amount of bytes we want
sendData([readBytes-1], function(err) {
if (err) {
console.log("Error while reading. ("+err+")");
callback(err);
return;
}
receiveData(readBytes, /*function(err) {
if (err) {
console.log("Error while reading. retrying...");
initialiseChip(function (err) {
if (err) callback(err);
else readData(callback, addr, readBytes);
}, 10000);
return;
}
callback(undefined, data);
}*/callback, 1000);
}, 2000/*timeout*/);
});
});
};
var bodgeClock = function(callback) {
/* 1v43 bootloader ran APB1 at 9Mhz, which isn't enough for
some STM32 silicon, which has a bug. Instead, set the APB1 clock
using the bootloader write command, which will fix it up enough for
flashing. */
var RCC_CFGR = 0x40021004;
readData(function(err, data) {
if (err) return callback(err);
var word = (data[3]<<24) | (data[2]<<16) | (data[1]<<8) | data[0];
console.log("RCC->CFGR = "+word);
var newword = (word&0xFFFFF8FF) | 0x00000400;
if (newword==word) {
console.log("RCC->CFGR is correct");
callback(undefined);
} else {
console.log("Setting RCC->CFGR to "+newword);
writeData(callback, RCC_CFGR, [newword&0xFF, (newword>>8)&0xFF, (newword>>16)&0xFF, (newword>>24)&0xFF]);
}
}, RCC_CFGR, 4);
};
var writeData = function(callback, addr, data) {
if (data.length>256) callback("Writing too much data");
console.log("Writing "+data.length+" bytes at 0x"+addr.toString(16)+"...");
// send write command
sendCommand(0x31, function(err) {
if (err) {
console.log("Error sending command ("+err+"). retrying...");
initialiseChip(function (err) {
if (err) callback(err);
else writeData(callback, addr, data);
}, 30000);
return;
}
// send address
sendData([(addr>>24)&0xFF,(addr>>16)&0xFF,(addr>>8)&0xFF,addr&0xFF], function(err) {
if (err) {
console.log("Error sending address ("+err+"). retrying...");
initialiseChip(function (err) {
if (err) callback(err);
else writeData(callback, addr, data);
}, 30000);
return;
}
// work out data to send
var sData = [ data.length-1 ];
// for (var i in data) doesn't just do 0..data.length-1 in node!
for (var i=0;i<data.length;i++) sData.push(data[i]&0xFF);
// send data
sendData(sData, function(err) {
if (err) {
console.log("Error while writing ("+err+"). retrying...");
initialiseChip(function (err) {
if (err) callback(err);
else writeData(callback, addr, data);
}, 30000);
return;
}
callback(undefined); // done
}, 2000/*timeout*/);
});
});
};
var writeAllData = function(binary, flashOffset, callback) {
var chunkSize = 256;
console.log("Writing "+binary.byteLength+" bytes");
Espruino.Core.Status.setStatus("Writing flash...", binary.byteLength);
var writer = function(offset) {
if (offset>=binary.byteLength) {
Espruino.Core.Status.setStatus("Write complete!");
callback(undefined); // done
return;
}
var len = binary.byteLength - offset;
if (len > chunkSize) len = chunkSize;
var data = new Uint8Array(binary, offset, len);
writeData(function(err) {
if (err) { callback(err); return; }
Espruino.Core.Status.incrementProgress(chunkSize);
writer(offset + chunkSize);
}, 0x08000000 + offset, data);
};
writer(flashOffset);
};
var readAllData = function(binaryLength, flashOffset, callback) {
var data = new Uint8Array(flashOffset);
var chunkSize = 256;
console.log("Reading "+binaryLength+" bytes");
Espruino.Core.Status.setStatus("Reading flash...", binaryLength);
var reader = function(offset) {
if (offset>=binaryLength) {
Espruino.Core.Status.setStatus("Read complete!");
callback(undefined, data); // done
return;
}
var len = binaryLength - offset;
if (len > chunkSize) len = chunkSize;
readData(function(err, dataChunk) {
if (err) { callback(err); return; }
for (var i in dataChunk)
data[offset+i] = dataChunk[i];
Espruino.Core.Status.incrementProgress(chunkSize);
reader(offset + chunkSize);
}, 0x08000000 + offset, chunkSize);
};
reader(flashOffset);
};
function flashBinaryToDevice(binary, flashOffset, callback, statusCallback) {
setStatus = function(x) {
if (!Espruino.Core.Status.hasProgress())
Espruino.Core.Status.setStatus(x);
if (statusCallback) statusCallback(x);
}
if (typeof flashOffset === 'function') {
// backward compatibility if flashOffset is missed
callback = flashOffset;
flashOffset = null;
}
if (!flashOffset && flashOffset !== 0) {
flashOffset = DEFAULT_FLASH_OFFSET;
}
if (typeof binary == "string") {
var buf = new ArrayBuffer(binary.length);
var a = new Uint8Array(buf);
for (var i=0;i<binary.length;i++)
a[i] = binary.charCodeAt(i);
binary = buf;
}
// add serial listener
dataReceived = undefined;
Espruino.Core.Serial.startListening(function (readData) {
var bufView=new Uint8Array(readData);
//console.log("Got "+bufView.length+" bytes");
for (var i=0;i<bufView.length;i++) bytesReceived.push(bufView[i]);
if (dataReceived!==undefined) {
for (var i=0;i<bytesReceived.length;i++) {
if (dataReceived===undefined) console.log("OH NO!");
dataReceived(bytesReceived[i]);
}
bytesReceived = [];
}
});
Espruino.Core.Serial.setBinary(true);
var hadSlowWrite = Espruino.Core.Serial.isSlowWrite();
Espruino.Core.Serial.setSlowWrite(false, true/*force*/);
var oldHandler;
if (Espruino.Core.Terminal) {
oldHandler = Espruino.Core.Terminal.setInputDataHandler(function() {
// ignore keyPress from terminal during flashing
});
}
var finish = function(err) {
Espruino.Core.Serial.setSlowWrite(hadSlowWrite);
Espruino.Core.Serial.setBinary(false);
if (Espruino.Core.Terminal)
Espruino.Core.Terminal.setInputDataHandler(oldHandler);
callback(err);
};
// initialise
initialiseChip(function (err) {
if (err) { finish(err); return; }
setStatus("Erasing...");
eraseChip(function (err) {
if (err) { finish(err); return; }
setStatus("Writing Firmware...");
writeAllData(binary, flashOffset, function (err) {
if (err) { finish(err); return; }
finish();
});
});
/*readAllData(binary.byteLength, function(err,chipData) {
if (err) {
finish(err);
return;
}
var errors = 0;
var needsErase = false;
var binaryData = new Uint8Array(binary, 0, binary.byteLength);
for (var i=FLASH_OFFSET;i<binary.byteLength;i++) {
if (binaryData[i]!=chipData[i]) {
if (chipData[i]!=0xFF) needsErase = true;
console.log(binaryData[i]+" vs "+data[i]);
errors++;
}
}
console.log(errors+" differences, "+(needsErase?"needs erase":"doesn't need erase"));
});*/
});
}
function flashDevice(url, flashOffset, callback, statusCallback) {
Espruino.Core.Utils.getBinaryURL(url, function (err, binary) {
if (err) { callback(err); return; }
console.log("Downloaded "+binary.byteLength+" bytes");
flashBinaryToDevice(binary, flashOffset, callback, statusCallback);
});
};
function resetDevice(callback) {
// add serial listener
dataReceived = undefined;
Espruino.Core.Serial.startListening(function (readData) {
var bufView=new Uint8Array(readData);
//console.log("Got "+bufView.length+" bytes");
for (var i=0;i<bufView.length;i++) bytesReceived.push(bufView[i]);
if (dataReceived!==undefined) {
for (var i=0;i<bytesReceived.length;i++) {
if (dataReceived===undefined) console.log("OH NO!");
dataReceived(bytesReceived[i]);
}
bytesReceived = [];
}
});
Espruino.Core.Serial.setBinary(true);
var hadSlowWrite = Espruino.Core.Serial.isSlowWrite();
Espruino.Core.Serial.setSlowWrite(false, true/*force*/);
var oldHandler = Espruino.Core.Terminal.setInputDataHandler(function() {
// ignore keyPress from terminal during flashing
});
var finish = function(err) {
Espruino.Core.Serial.setSlowWrite(hadSlowWrite);
Espruino.Core.Serial.setBinary(false);
Espruino.Core.Terminal.setInputDataHandler(oldHandler);
callback(err);
};
// initialise
initialiseChip(function (err) {
if (err) return finish(err);
var data = new Uint8Array([0x04,0x00,0xFA,0x05]);
var addr = 0xE000ED0C;
console.log("Writing "+data.length+" bytes at 0x"+addr.toString(16)+"...");
// send write command
sendCommand(0x31, function(err) {
if (err) return finish(err);
// send address
sendData([(addr>>24)&0xFF,(addr>>16)&0xFF,(addr>>8)&0xFF,addr&0xFF], function(err) {
if (err) return finish(err);
// work out data to send
// for (var i in data) doesn't just do 0..data.length-1 in node!
for (var i=0;i<data.length;i++) sData.push(data[i]&0xFF);
var s = "";
var chksum = 0;
for (var i in sData) {
chksum = chksum ^ sData[i];
s += String.fromCharCode(sData[i]);
}
Espruino.Core.Serial.write(s + String.fromCharCode(chksum), false, finish);
}, 2000/*timeout*/);
});
});
};
Espruino.Core.Flasher = {
init : init,
flashDevice : flashDevice,
flashBinaryToDevice : flashBinaryToDevice,
resetDevice : resetDevice
};
}());