Skip to content

Instantly share code, notes, and snippets.

@apla
Forked from blindman2k/DA14580.agent.nut
Last active August 29, 2015 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apla/02d42f0a491dad64352c to your computer and use it in GitHub Desktop.
Save apla/02d42f0a491dad64352c to your computer and use it in GitHub Desktop.
Dialog DA14580 programmer. Upload a HEX file and it programs the DA14580.
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")
})
/*
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