-
-
Save apla/02d42f0a491dad64352c to your computer and use it in GitHub Desktop.
Dialog DA14580 programmer. Upload a HEX file and it programs the DA14580.
This file contains hidden or 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
server.log("Agent started, URL is " + http.agenturl()); | |
//------------------------------------------------------------------------------------------------------------------------------ | |
program <- null; | |
html <- @"<HTML> | |
<BODY> | |
<form method='POST' enctype='multipart/form-data'> | |
Program the Dialog DA14580 BTLE module via the Imp.<br/><br/> | |
Step 1: Select an Intel HEX file to upload: <input type=file name=hexfile><br/> | |
Step 2: <input type=submit value=Press> to upload the file.<br/> | |
Step 3: Check out your module<br/> | |
</form> | |
</BODY> | |
</HTML> | |
"; | |
//------------------------------------------------------------------------------------------------------------------------------ | |
// Parses a hex string and turns it into an integer | |
function hextoint(str) { | |
local hex = 0x0000; | |
foreach (ch in str) { | |
local nibble; | |
if (ch >= '0' && ch <= '9') { | |
nibble = (ch - '0'); | |
} else { | |
nibble = (ch - 'A' + 10); | |
} | |
hex = (hex << 4) + nibble; | |
} | |
return hex; | |
} | |
//------------------------------------------------------------------------------------------------------------------------------ | |
// Parses a HTTP POST in multipart/form-data format | |
function parse_hexpost(req, res) { | |
local boundary = req.headers["content-type"].slice(30); | |
local bindex = req.body.find(boundary); | |
local hstart = bindex + boundary.len(); | |
local bstart = req.body.find("\r\n\r\n", hstart) + 4; | |
local fstart = req.body.find("\r\n\r\n--" + boundary + "--", bstart); | |
return req.body.slice(bstart, fstart); | |
} | |
//------------------------------------------------------------------------------------------------------------------------------ | |
// Parse the hex into an array of blobs | |
function parse_hexfile(hex) { | |
try { | |
// Look at this doc to work out what we need and don't. Max is about 122kb. | |
server.log("Parsing hex file"); | |
// Create and blank the program blob | |
program = blob(0x20000); // 128k maximum | |
for (local i = 0; i < program.len(); i++) program.writen(0xFF, 'b'); | |
program.seek(0); | |
local maxaddress = 0, from = 0, to = 0, line = "", offset = 0x00000000; | |
do { | |
if (to < 0 || to == null || to >= hex.len()) break; | |
from = hex.find(":", to); | |
if (from < 0 || from == null || from+1 >= hex.len()) break; | |
to = hex.find(":", from+1); | |
if (to < 0 || to == null || from >= to || to >= hex.len()) break; | |
line = hex.slice(from+1, to); | |
// server.log(format("[%d,%d] => %s", from, to, line)); | |
if (line.len() > 10) { | |
local len = hextoint(line.slice(0, 2)); | |
local addr = hextoint(line.slice(2, 6)); | |
local type = hextoint(line.slice(6, 8)); | |
// Ignore all record types except 00, which is a data record. | |
// Look out for 02 records which set the high order byte of the address space | |
if (type == 0) { | |
// Normal data record | |
} else if (type == 4 && len == 2 && addr == 0 && line.len() > 12) { | |
// Set the offset | |
// offset = hextoint(line.slice(8, 12)); // << 16; | |
if (offset != 0) { | |
server.log(format("Set offset to 0x%08X", offset)); | |
} | |
continue; | |
} else { | |
server.log("Skipped: " + line) | |
continue; | |
} | |
// Read the data from 8 to the end (less the last checksum byte) | |
program.seek(offset + addr) | |
// server.log(format("Seek offset %02X -> %02X", offset + addr, program.tell())) | |
for (local i = 8; i < 8+(len*2); i+=2) { | |
local datum = hextoint(line.slice(i, i+2)); | |
program.writen(datum, 'b') | |
} | |
// Checking the checksum would be a good idea but skipped for now | |
local checksum = hextoint(line.slice(-2)); | |
/// Shift the end point forward | |
if (program.tell() > maxaddress) maxaddress = program.tell(); | |
} | |
} while (from != null && to != null && from < to); | |
// Crop, save and send the program | |
server.log(format("Max address: 0x%08x", maxaddress)); | |
program.resize(maxaddress); | |
server.log("Free RAM: " + (imp.getmemoryfree()/1024) + " kb") | |
// return false; | |
return true; | |
} catch (e) { | |
server.log(e) | |
return false; | |
} | |
} | |
//------------------------------------------------------------------------------------------------------------------------------ | |
// Handle the agent requests | |
http.onrequest(function (req, res) { | |
if (req.method == "GET") { | |
// res.send(200, program); | |
res.send(200, html); | |
} else if (req.method == "POST") { | |
if ("content-type" in req.headers) { | |
if (req.headers["content-type"].len() >= 19 | |
&& req.headers["content-type"].slice(0, 19) == "multipart/form-data") { | |
local hex = parse_hexpost(req, res); | |
if (hex == "") { | |
res.header("Location", http.agenturl()); | |
res.send(302, "HEX file uploaded"); | |
} else { | |
if (parse_hexfile(hex)) { | |
device.on("done", function(ready) { | |
res.header("Location", http.agenturl()); | |
res.send(302, "HEX file uploaded"); | |
server.log("Programming completed") | |
}) | |
send(); | |
} else { | |
res.header("Location", http.agenturl()); | |
res.send(302, "HEX parsing failed"); | |
} | |
} | |
} else if (req.headers["content-type"] == "application/json") { | |
local json = null; | |
try { | |
json = http.jsondecode(req.body); | |
} catch (e) { | |
server.log("JSON decoding failed for: " + req.body); | |
return res.send(400, "Invalid JSON data"); | |
} | |
local log = ""; | |
foreach (k,v in json) { | |
if (typeof v == "array" || typeof v == "table") { | |
foreach (k1,v1 in v) { | |
log += format("%s[%s] => %s, ", k, k1, v1.tostring()); | |
} | |
} else { | |
log += format("%s => %s, ", k, v.tostring()); | |
} | |
} | |
server.log(log) | |
return res.send(200, "OK"); | |
} else { | |
return res.send(400, "Bad request"); | |
} | |
} else { | |
return res.send(400, "Bad request"); | |
} | |
} | |
}) | |
//------------------------------------------------------------------------------------------------------------------------------ | |
function send(success = true) { | |
if (program != null) { | |
server.log("Sending program to device: " + program.len() + " bytes"); | |
device.send("burn", program) | |
} else { | |
server.log("Sorry, we don't have any firmware for you right now.") | |
} | |
} | |
//------------------------------------------------------------------------------------------------------------------------------ | |
device.on("done", function(ready) { | |
server.log("Programming completed") | |
}) |
This file contains hidden or 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
/* | |
Impee-blimpee pin mux | |
1 - SPI (DA14850) | |
2 - SPI (flash) | |
5 - SPI (flash) | |
6 - UART | |
7 - SPI (flash) | |
8 - SPI (DA14850) | |
9 - SPI (DA14850) | |
A - Reset | |
B - SPI (DA14850 chip select) | |
C - SPI (flash chip select) | |
E - UART | |
*/ | |
// ============================================================================= | |
// MX25L3206E SPI Flash | |
// MX25L3206EM2I-12G | |
class spiFlash { | |
// spi interface | |
spi = null; | |
cs_l = null; | |
// The file allocation table (FAT) | |
fat = null; | |
load_pos = 0; | |
// The callbacks | |
init_callback = null; | |
load_files_callback = null; | |
load_files_list = null; | |
// ------------------------------------------------------------------------- | |
// constructor takes in pre-configured spi interface object and chip select GPIO | |
constructor(spiBus, csPin) { | |
const WREN = "\x06"; // write enable | |
const WRDI = "\x04"; // write disable | |
const RDID = "\x9F"; // read identification | |
const RDSR = "\x05"; // read status register | |
const READ = "\x03"; // read data | |
const FASTREAD = "\x0B"; // fast read data | |
const RDSFDP = "\x5A"; // read SFDP | |
const RES = "\xAB"; // read electronic ID | |
const REMS = "\x90"; // read electronic mfg & device ID | |
const DREAD = "\x3B"; // double output mode, which we don't use | |
const SE = "\x20"; // sector erase (Any 4kbyte sector set to 0xff) | |
const BE = "\x52"; // block erase (Any 64kbyte sector set to 0xff) | |
const CE = "\x60"; // chip erase (full device set to 0xff) | |
const PP = "\x02"; // page program | |
const RDSCUR = "\x2B"; // read security register | |
const WRSCUR = "\x2F"; // write security register | |
const ENSO = "\xB1"; // enter secured OTP | |
const EXSO = "\xC1"; // exit secured OTP | |
const DP = "\xB9"; // deep power down | |
const RDP = "\xAB"; // release from deep power down | |
const totalBlocks = 64; // 64 blocks of 64k each = 4mb | |
const SPI_CLOCK_SPEED_FLASH = 15000; | |
spi = spiBus; | |
cs_l = csPin; | |
spi.configure(CLOCK_IDLE_LOW | MSB_FIRST, SPI_CLOCK_SPEED_FLASH); | |
cs_l.configure(DIGITAL_OUT); | |
// wait for the flash to be completely ready for commands | |
local timeout = 60000; // time in us | |
local start = hardware.millis(); | |
while ((getStatus() & 0x01) == 0x01) { | |
if ((hardware.millis() - start) > timeout) { | |
server.error("BOOTING MEMORY TIMED OUT"); | |
return 1; | |
} | |
} | |
// read the manufacturer and device ID | |
local id = getId(); | |
server.log(format("SPI Flash IDs: %02x %04x", id.mfgID, id.devID)) | |
} | |
// ------------------------------------------------------------------------- | |
function getId() { | |
cs_l.write(0); | |
spi.write(RDID); | |
local data = spi.readblob(3); | |
local mfgID = data[0]; | |
local devID = (data[1] << 8) | data[2]; | |
cs_l.write(1); | |
return { mfgID = mfgID, devID = devID }; | |
} | |
// ------------------------------------------------------------------------- | |
function wrenable() { | |
for (local i = 0; i < 10; i++) { | |
cs_l.write(0); | |
spi.write(WREN); | |
cs_l.write(1); | |
if ((getStatus() & 0x03) == 0x02) { | |
return true; | |
} else { | |
imp.sleep(0.01); | |
server.error("Failed to set flash to WRENABLE, retrying") | |
} | |
} | |
throw "Gave up trying to set flash to WRENABLE"; | |
} | |
// ------------------------------------------------------------------------- | |
function read(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; | |
} | |
// ------------------------------------------------------------------------- | |
// pages should be pre-erased before writing | |
function write(addr, data) { | |
wrenable(); | |
// check the status register's write enabled bit | |
if (!(getStatus() & 0x02)) { | |
server.error("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.error("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 writeBlob(addr, data) { | |
// separate the chunk into pages | |
data.seek(0, 'b'); | |
for (local i = 0; i < data.len(); i += 256) { | |
local leftInBuffer = data.len() - data.tell(); | |
if (leftInBuffer < 256) { | |
write((addr+i), data.readblob(leftInBuffer)); | |
} else { | |
write((addr+i), data.readblob(256)); | |
} | |
} | |
} | |
// ------------------------------------------------------------------------- | |
function getStatus() { | |
cs_l.write(0); | |
spi.write(RDSR); | |
local status = spi.readblob(1); | |
cs_l.write(1); | |
return status[0]; | |
} | |
// ------------------------------------------------------------------------- | |
// set one block (64kb) to all 0xff | |
function blockErase(block, callback = null) { | |
// log(format("Erasing block: 0x%02x", block)); | |
wrenable(); | |
cs_l.write(0); | |
spi.write(BE); | |
spi.write(format("%c%c%c", (block >> 16) & 0xFF, (block >> 8) & 0xFF, block & 0xFF)); | |
cs_l.write(1); | |
if (callback) { | |
imp.wakeup(0.71, checkStatus(callback, 0.2).bindenv(this)); | |
} else { | |
imp.sleep(2.2); | |
} | |
} | |
// ------------------------------------------------------------------------- | |
// set the entire flash to all 0xff | |
function chipErase(callback = null) { | |
server.log("Erasing the flash"); | |
wrenable(); | |
cs_l.write(0); | |
spi.write(CE); | |
cs_l.write(1); | |
if (callback) { | |
imp.wakeup(10, checkStatus(callback).bindenv(this)); | |
} else { | |
imp.sleep(55); | |
} | |
} | |
// ------------------------------------------------------------------------- | |
// Checks the status of the last command (well returns a function that does) | |
function checkStatus(callback = null, interval = 1, mask = 0x01, timeout = 120) { | |
return function() { | |
local status = getStatus() & mask; | |
if (status) { | |
imp.wakeup(interval, checkStatus(callback, interval, mask, timeout).bindenv(this)); | |
} else { | |
callback(); | |
} | |
} | |
} | |
} | |
//------------------------------------------------------------------------------ | |
function calculateCRC(data, initial=0xFF) { | |
local crc = initial; | |
for (local i = 0; i < data.len(); i++) { | |
crc = crc ^ data[i]; | |
} | |
return crc; | |
} | |
//------------------------------------------------------------------------------ | |
function hexdump(data) { | |
local dump = ""; | |
foreach (ch in data) { | |
dump += format("%02X ", ch); | |
} | |
if (dump.len() > 0) server.log(dump.slice(0, -1)) | |
} | |
//------------------------------------------------------------------------------ | |
function send(data) { | |
local ch, crcin, crcout; | |
local length = data.len(); | |
local soh = 0x01; | |
local lsb = (length & 0x00FF); | |
local msb = (length >> 8) & 0x00FF; | |
local preamble = format("%c%c%c", soh, lsb, msb); | |
// Reset the BTLE chip and let it settle | |
rst.write(1); | |
while (uart.read() != -1); // Drain the buffer | |
imp.sleep(0.01); | |
rst.write(0); | |
// Wait for a 0x02 before starting | |
while (uart.read() != 0x02); | |
// Send the SOH, LSB and MSB | |
uart.write(preamble); | |
uart.flush(); | |
// Wait for an ACK | |
while (uart.read() != 0x06); | |
// Send the data | |
data.seek(0); | |
uart.write(data); | |
// Read the CRC | |
while ((crcin = uart.read()) == -1); | |
// Send a final ACK | |
uart.write(0x06); | |
// Check the CRC | |
crcout = calculateCRC(data, 0x00); | |
// server.log(format("CRC: %02X == %02X", crcin, crcout)) | |
return crcin == crcout; | |
} | |
const FLASH_MAGIC = 0xABCDEF01; | |
const FLASH_HEADER_LEN = 9; | |
const FLASH_HEADER_ADDR = 0x0000; | |
const FLASH_PROGRAM_ADDR = 0x0100; | |
//------------------------------------------------------------------------------ | |
function save_to_flash(program) { | |
server.log("Received program of " + program.len() + " bytes. Memory free: " + imp.getmemoryfree() + " bytes.") | |
// Write to Blimpee | |
if (send(program)) { | |
server.log("Programming of the DA14580 completed successully") | |
} else { | |
server.error("Programming of the DA14580 failed") | |
} | |
// Write to flash | |
flash.chipErase(function() { | |
// Write the magic number, a CRC and 32 bit length into page 0 | |
server.log("Writing the program to flash") | |
local header = blob(FLASH_HEADER_LEN); | |
header.writen(FLASH_MAGIC, 'i'); | |
header.writen(calculateCRC(program), 'b'); | |
header.writen(program.len(), 'i'); | |
flash.write(FLASH_HEADER_ADDR, header); | |
// Write the program starting at page 1 | |
flash.writeBlob(FLASH_PROGRAM_ADDR, program); | |
// Notify the agent that we are done. | |
agent.send("done", true); | |
}.bindenv(this)) | |
} | |
//------------------------------------------------------------------------------ | |
function load_from_flash(callback) { | |
// Read the magic number, the CRC and 32 bit length from page 0 | |
local header = flash.read(FLASH_HEADER_ADDR, FLASH_HEADER_LEN); | |
if (header == null || header.len() != FLASH_HEADER_LEN) { | |
return server.error("No response from SPI flash"); | |
} | |
local magic = header.readn('i'); | |
if (magic != FLASH_MAGIC) { | |
return server.error(format("Flash does not contain a DA14580 image (magic = 0x%08X)", magic)); | |
} | |
// Read the data from page 1 | |
local crc = header.readn('b'); | |
local len = header.readn('i'); | |
local program = flash.read(FLASH_PROGRAM_ADDR, len); | |
local crc2 = calculateCRC(program); | |
if (program.len() != len || crc2 != crc) { | |
return server.error(format("Flash does not contain a valud DA14580 image (len = %d, crc = 0x%02X != 0x%02X)", len, crc, crc2)) | |
} | |
// Program the bluetooth chip | |
if (send(program)) { | |
server.log("Programming of the DA14580 completed successully") | |
callback(true); | |
} else { | |
server.error("Programming of the DA14580 failed") | |
callback(false); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
function uart_event() { | |
server.log("UART") | |
} | |
//------------------------------------------------------------------------------ | |
server.log("Device booted."); | |
rst <- hardware.pinA; | |
rst.configure(DIGITAL_OUT); | |
rst.write(1); // Hold until we are ready | |
uart <- hardware.uart6E; | |
hardware.uart6E.configure(57600, 8, PARITY_NONE, 1, NO_CTSRTS); | |
flash <- spiFlash(hardware.spi257, hardware.pinC); | |
agent.on("burn", save_to_flash); | |
load_from_flash(function(success) { | |
if (success) { | |
hardware.uart6E.configure(115200, 8, PARITY_NONE, 1, NO_CTSRTS, uart_event); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment