UNPKG

avr-isp

Version:

Library to allow Tessel to act as an AVR In-System Programmer

572 lines (487 loc) 14.3 kB
var tessel = require('tessel'); var Queue = require('sync-queue'); var fs = require('fs'); var CLOCKSPEED_FUSES = 125000 // Arduino's SPI_CLOCK_DIV128, 125Khz , CLOCKSPEED_FLASH = 2000000 // SPI_CLOCK_DIV8, 2Mhz , FUSES = {"lock": 0xFF, "low": 0xE2, "high": 0xDF, "ext": 0xFF} // pre program fuses (prot/lock, low, high, ext) , MASK = {"lock": 0xFF, "low": 0xFF, "high": 0xFF, "ext": 0xFF} , HIGH = 0x01 , LOW = 0x00 ; var debug = false; ISP = function(hardware, options){ this.chipSelect = hardware.digital[0].output().high(); this.reset = hardware.digital[1].output().high(); this.spi = new hardware.SPI( {clockSpeed:CLOCKSPEED_FUSES, mode:0 }); this.success = tessel.led[0]; this.programming = tessel.led[1]; options = options || {}; this.pageSize = options.pageSize || 64; this.fname = options.fileName || './attx4.hex' this.clockSpeed = CLOCKSPEED_FUSES; this.incorrect = 0; } ISP.prototype._clockChange = function (speed) { if (this.clockSpeed == speed) return; this.clockSpeed = speed; this.spi.setClockSpeed(speed); } // reads bottom 2 signature bytes and returns it ISP.prototype.readSignature = function(next){ var self = this; if (debug) console.log("Reading signature"); var signature; self.startProgramming(function(err){ if (!err){ self._transfer([0x30, 0x00, 0x00, 0x00], function(err, res){ self._transfer([0x30, 0x00, 0x01, 0x00], function(err, res){ if (debug) console.log("signature 1", res[3]); signature = res[3] << 8; self._transfer([0x30, 0x00, 0x02, 0x00], function(err, res){ if (debug) console.log("signature 2", res[3]); signature = signature | res[3]; if (debug) console.log("got signature", signature); if (signature == 0 || signature == 0xFFFF) { return next("Could not find a signature", signature); } return next(null, signature); }); }); }); } else { next(err); } }); } ISP.prototype.verifyFuses = function (fuses, mask, next) { // verifies only the low fuse for now var self = this; var queue = new Queue(); queue.place(function verifyLow(){ self._transfer([0x50, 0x00, 0x00, 0x00], function(err, res){ if (res[3] & mask.low) { return next(); } else { return next(new Error('Could not verify low fuse')); } }); }); } ISP.prototype.programFuses = function (next) { var self = this; // write only the low fuse for now var queue = new Queue(); queue.place(function programLow(){ self._transfer([0xAC, 0xA0, 0x00, FUSES.low], function(err, res){ self.verifyFuses(FUSES, MASK, function(err){ self.endProgramming(function(){ if (err){ next(err); } else { next(); } }); }); }); }); } ISP.prototype.readPagesFromHexFile = function(next){ var self = this; fs.readFile(self.fname, function(err, data){ if (err){ if (debug) console.log('File read error!', err); next(err); } else { var pos = {position: -1}; var pageAddr = 0; var pages = []; ;(function readPage(position){ if( position.position < data.length){ pos = self._readImagePage(pos.position, data, pageAddr); pages.push({ pageBuffer:pos.page, address: pageAddr}); pageAddr+=self.pageSize; setImmediate(function() { readPage(pos); }); } else { next(null, pages); } })(pos) } }); } // returns position of page read ISP.prototype._readImagePage = function (hexPos, hexText, pageAddr) { var self = this; var len; var page_idx = 0; var beginning = hexText; var page = []; var hexByte, checksum; function nextHex(){ hexByte = _hexToN(hexText[++hexPos]); hexByte = (hexByte << 4) + _hexToN(hexText[++hexPos]); checksum += hexByte; } // initiate the page by filling it with 0xFFs for (var i = 0; i<self.pageSize; i++){ page[i] = 0xFF; } while (1) { var lineAddr; if ( hexText[++hexPos] != 0x3a) { // 0x3a == ':' if (debug) console.log("no colon, stopping image read"); return; } len = _hexToN(hexText[++hexPos]); len = (len << 4) + _hexToN(hexText[++hexPos]); checksum = len; debug && console.log('Len',len); // High address byte nextHex(); lineAddr = hexByte; // Low address byte nextHex(); lineAddr = (lineAddr << 8) + hexByte; if (lineAddr >= (pageAddr + self.pageSize)){ console.log('Error: line address bigger than pages', lineAddr); return beginning; } nextHex(); // Check record type if (hexByte == 0x1) { debug && console.log("EOF record: 0x1"); hexPos=hexText.length; break; } if (debug) { console.log("line address = 0x", lineAddr); console.log("page address = 0x", pageAddr); } for (var i = 0; i<len; i++){ nextHex(); page[page_idx] = hexByte; page_idx++; if (page_idx > self.pageSize) { console.log("Error: too much code"); break; } } nextHex(); if (checksum%256 != 0){ console.log("Error: bad checksum. Got", checksum); } hexPos++; if (hexText[hexPos] != 0x0d) { if (hexText[hexPos] != 0x0a){ console.log("Error: no end of line"); break; } } else { if (hexText[++hexPos] != 0x0a){ console.log("Error: no end of line"); break; } } if (debug) console.log("page idx", page_idx); if (page_idx == self.pageSize) break; } debug && console.log("Total bytes read:", page_idx); return {"position": hexPos, "page": new Buffer(page)}; } ISP.prototype.flashImage = function(pages, next){ var self = this; var queue = new Queue(); var commands = []; for (var i=0; i<pages.length; i++){ queue.place(function(i){ debug && console.log(i, pages[i].pageBuffer); commands.push(self._queuePage(pages[i].pageBuffer, pages[i].address )); if (i+1 < pages.length){ queue.next(); } else { self.startProgramming(function(){ self._flashAll(commands, next); }); } }.bind(this, i)); } } ISP.prototype._flashAll = function(commands, next){ if (debug) console.log('starting flash'); var self = this; if (commands.length){ this.spi.send(new Buffer(commands[0]), function(err, res){ commands.shift(); self._flashAll(commands, next); }); } else { self.endProgramming(function(){ next(); }); } } ISP.prototype._queuePage = function(pageBuff, pageAddr, next) { var self = this; var queue = new Queue(); var spiQueue = []; for (var i = 0; i < self.pageSize/2; i++){ var addr = i+pageAddr/2 spiQueue.push(0x40, (addr >> 8) & 0xFF, addr & 0xFF, pageBuff[2*i]); spiQueue.push(0x48, (addr >> 8) & 0xFF, addr & 0xFF, pageBuff[2*i+1]); if ( i+1 == self.pageSize/2 ) { pageAddr = pageAddr/2 & 0xFFFF; spiQueue.push(0x4c, (pageAddr >> 8) & 0xFF, pageAddr & 0xFF, 0x00); return spiQueue } } } ISP.prototype.verifyImage = function(pages, next) { var self = this; var queue = new Queue(); self.incorrect = 0; for (var i=0; i<pages.length; i++){ queue.place(function(i){ self._readPage(pages[i].pageBuffer, pages[i].address, function(){ if (i+1 < pages.length){ queue.next(); } else { next(null, self.incorrect); } }); }.bind(this, i)); } } ISP.prototype._readPage = function(pageBuff, pageAddr, next) { var self = this; var queue = new Queue(); for (var i = 0; i < self.pageSize/2; i++){ queue.place(function(i){ self._readWord(LOW, i+pageAddr/2, pageBuff[2*i], function(err, res){ self._readWord(HIGH, i+pageAddr/2, pageBuff[2*i+1], function(err, res){ if (i+1 < self.pageSize/2 ){ queue.next(); } else { next(); } }); }); }.bind(this, i)); } } ISP.prototype._readWord = function(hilo, addr, data, next) { var self = this; if (debug) console.log("data", data); this._transfer([0x20+8*hilo, (addr >> 8) & 0xFF, addr & 0xFF, 0x00 ], function (err, res) { if ( data != res[3]){ if (debug) console.log('Error verifying data. Expected', data,', got', res[3], 'at 0x', addr.toString(16)); self.incorrect++; next(err, res); } else { next(err, res); } }); } ISP.prototype.startProgramming = function (next) { var self = this; self.reset.write(1); setTimeout(function(){ self.reset.write(0); self.programming.write(1); self.spi.transfer(new Buffer([0xAC, 0x53, 0x00, 0x00]), function(err, rec){ if (debug) console.log("SPI response", rec); if (rec && rec[2] == 0x53){ next() } else { next(new Error('Programming confirmation not received.')); } }); },50); } ISP.prototype.endProgramming = function (next) { this.reset.write(1); this.programming.write(0); next(); } ISP.prototype.eraseChip = function(next){ var self = this; self._transfer([0xAC, 0x80, 0, 0], function (err){ if (debug) console.log("sent erase, waiting for done signal"); self._busyWait(function(){ next(); }); }); } ISP.prototype.readEEPROM = function(numBytes, startAddress, callback) { // Allocate a buffer for the response var bytes = new Buffer(numBytes); // Create a queue for reach byte ready transaction var queue = new Queue(); // For each byte for (var i = 0; i < numBytes; i++){ // Place a transaction on the queue queue.place(function(i){ // The transaction consists of reading a byte at the specific offeset this.readEEPROMByte(startAddress + i, function(err, byte){ // If there was an error if (err) { // And a callback if (callback) { // Call the callback with the error callback(err); // Abort return; } } // If everything is hunky dory else { // Save the received byte into our return buffer bytes[i] = byte; // If this is the last byte if (i == numBytes-1) { // If a callback was provided if (callback) { // call the callback with read bytes callback(null, bytes); } } // If not else { // Make the next read queue.next(); } } }); }.bind(this, i)); } } ISP.prototype.writeEEPROM = function(byteArr, startAddress, callback) { // Create a queue of write transactions var queue = new Queue(); // Iterate through the bytes to write for (var i = 0; i < byteArr.length; i++){ // Place each write transaction into the queue queue.place(function(i){ // Write the byte at the specific offset this.writeEEPROMByte(byteArr[i], startAddress + i, function(err, byte){ // If there was an error if (err) { // And a callback was provided if (callback) { // Call the callback with the error callback(err); } // Abort return; } // If everything is still running smoothly else { // If this is the last byte if (i == byteArr.length-1) { // If a callback was provided if (callback) { // call the callback callback(null); } } // If not else { // Make the next write queue.next(); } } }); }.bind(this, i)); } } ISP.prototype.readEEPROMByte = function(address, callback) { // Read a single byte from the specified adddress this.accessEEPROMByte(0xA0, address, 0x00, callback); } ISP.prototype.writeEEPROMByte = function(byte, address, callback) { // Write the specified byte at the specified address this.accessEEPROMByte(0xC0, address, byte, callback); } ISP.prototype.accessEEPROMByte = function(command, address, data, callback) { var self = this; // Start the programming sequence self.startProgramming(function(err) { // If there was an issue if (err) { // And a callback if (callback) { // Call the callback callback(err); } // Abort return } // Make the transfer self._transfer([command, 0x00, address, data], function(err, response) { // If there was an error if (err) { // And a callback if (callback) { // Call the callback callback(err); } // Abort return } // End the programming self.endProgramming(function(err) { // If a callback was provided if (callback) { // Call the callback with the data byte callback(err, response && response[3]); } }); }); }) } ISP.prototype._transfer = function (arr, next){ if (arr.length%4 != 0) { var err = "isp transfer called with wrong size. needs to be 4 bytes, got "+arr; console.log(err); return next(err); } debug && console.log(arr.map(function(e){ return e.toString(16) })); this.spi.transfer(new Buffer(arr), function(err, res){ next(null, res); }); } // polls chip until it is no longer busy ISP.prototype._busyWait = function(next){ var self = this; this.spi.transfer(new Buffer([0xF0, 0x00, 0x00, 0x00]), function (err, res){ if (res[3] & 1) return self._busyWait(next); else return next(); }); } function _hexToN(hex) { if (hex >= 0x30 && hex <= 0x39) { return hex - 0x30; } if (hex >= 0x41 && hex <= 0x46){ return (hex - 0x41) + 10; } } function use (hardware, options) { return new ISP(hardware, options); } module.exports.ISP = ISP; module.exports.FUSES = FUSES; module.exports.MASK = MASK; module.exports.use = use;