-
Notifications
You must be signed in to change notification settings - Fork 5
/
avrgirl-usbtinyisp.js
418 lines (368 loc) · 14.1 KB
/
avrgirl-usbtinyisp.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
const C = require('./lib/c');
const util = require('util');
const EventEmitter = require('events').EventEmitter;
const usbtinyisp = require('usbtinyisp');
const async = require('async');
const programmers = require('./lib/programmers');
const intelhex = require('intel-hex');
const fs = require('fs');
const chips = require('avrgirl-chips-json');
class avrgirlUsbTinyIsp extends EventEmitter {
/**
* Constructor
*
* @param {object} options - options for consumer to pass in
*/
constructor(options) {
super();
var self = this;
// set up noisy or quiet
self.debug = options.debug ? console.log.bind(console) : () => {};
// most people won't need this level of debug output
self.hackerLog = options.hackerMode ? console.log.bind(console) : () => {};
self.options = {
sck: options.sck || C.SCK_DEFAULT,
debug: options.debug || false,
chip: options.chip || chips.attiny85,
log: self.hackerLog, // for usbtinyisp lib
programmer: options.programmer || null
};
// fix this pls self, it's very unattractive
// do an error check too
if (options.programmer === 'custom') {
if (!options.pid || !options.vid) throw new Error('please ensure your custom programmer options include both vid and pid properties');
self.options.pid = options.pid;
self.options.vid = options.vid;
} else {
var p = self.options.programmer ? programmers[self.options.programmer] : null;
self.options.pid = p ? p.pid : 3231;
self.options.vid = p ? p.vid : 6017;
}
// create new instance of usbtiny isp as programmer instance
self.programmer = new usbtinyisp(self.options);
self.programmer.open(error => {
if (error) {
console.error(error);
return;
}
setImmediate(_emitReady, self);
});
}
/**
* Primes the programmer and the microchip for programming
* Sets the clock speed of the programmer, and enables programming mode on the chip
*
* @param {function} callback - function to run upon completion/error
*/
enterProgrammingMode(callback) {
var self = this;
var cmd = Buffer.from(self.options.chip.pgmEnable);
self.setSCK(self.options.sck, error => {
if (error) {
return callback(error);
}
// is this timeout necessary?
setTimeout(() =>
self.programmer.spi(cmd, error => callback(error)), 50);
});
}
/**
* Powers down the programmer, allows the chip to leave programming mode
*
* @param {function} callback - function to run upon completion/error
*/
exitProgrammingMode(callback) {
this.programmer.powerDown(error => callback(error));
}
/**
* Sets the clock speed of the programmer
*
* @param {number} rate - sck speed to set (not yet available)
* @param {function} callback - function to run upon completion/error
*/
setSCK(rate, callback) {
// preparing for next version is usbtinyisp to be published allowing for custom clock rate
if (!callback) {
callback = rate;
}
// error checking for rate being a number
if ((typeof rate !== 'number') || (rate < C.SCK_MIN) || (rate > C.SCK_MAX)) {
return callback(new Error(`Could not set SCK: rate should be a number between ${C.SCK_MIN} and ${C.SCK_MAX}.`));
}
this.programmer.setSCK(error => callback(error));
}
/**
* Returns the signature of the microchip connected, in array format
*
* @param {function} callback - function to run upon completion/error
*/
getChipSignature(callback) {
var response = [];
var signature = this.options.chip.signature;
var cmd = Buffer.from(signature.read);
var sigLen = signature.size;
var set = 0;
var sigPos = 3;
var self = this;
// recursive function, according to length of the signature requested
const getSigByte = () => {
self.programmer.spi(cmd, (error, data) => {
if (error) {
return callback(error);
}
response[set] = data[sigPos];
set += 1;
cmd[2] = set;
if (set < sigLen) {
return getSigByte();
}
return callback(null, response);
});
};
getSigByte();
}
/**
* Compares two signatures to see if they match, returns a boolean
*
* @param {buffer} sig1 - the first siganture to be compared
* @param {buffer} sig2 - the second siganture to be compared
* @param {function} callback - function to run upon completion/error
*/
verifySignature(sig1, sig2, callback) {
// check sigs are buffers
if (!Buffer.isBuffer(sig1) || !Buffer.isBuffer(sig2)) {
return callback(new Error('Could not verify signature: both signatures should be buffers.'));
}
if (!sig1.equals(sig2)) {
return callback(new Error('Failed to verify: signature does not match.'))
}
return callback(null);
}
/**
* Writes the contents of a hex file to the requested memory type of the chip.
*
* @param {string} memType - the type of memory being written. Either 'flash' or 'eeprom'
* @param {buffer} hex - a buffer containing the compiled hex program data
* @param {function} callback - function to run upon completion/error
*/
_writeMem(memType, hex, callback) {
var self = this;
var options = self.options.chip;
var pageAddress = 0;
var useAddress;
var pageSize = options[memType].pageSize;
var addressOffset = options[memType].addressOffset;
var addressOffset = 1;
var data, readFile;
var page = 0;
if(!pageSize){
return callback(new Error(`could not write ${memType}: pageSize is not set for your chip`));
}
if (Buffer.isBuffer(hex)) {
data = hex;
} else if (typeof hex === 'string') {
try {
readFile = fs.readFileSync(hex, { encoding: 'utf8' });
} catch (e) {
if (e.code === 'ENOENT') {
return callback(new Error(`could not write ${memType}: please supply a valid path to a hex file.`));
}
return callback(e);
}
data = intelhex.parse(readFile).data;
} else {
return callback(new Error(`could not write ${memType}: please supply either a hex buffer or a valid path to a hex file.`));
}
self.debug(`writing to ${memType}, please wait...`);
self.hackerLog('page length:', hex.length / pageSize);
async.whilst(
// we exit out of this while loop when we are at the end of the hex file
function testEndOfFile() {
return pageAddress < data.length;
},
// main function to program the current page with data
function programPage (pagedone) {
// grab the correct chunk of data from our hex file
var pageData = self._preparePageData(pageAddress, pageSize, data);
// load data into current page
self._loadPage(memType, 0, pageAddress, pageData, function (error) {
if (error) { pagedone(error); }
// load address once writing is done
self._pollForAddress(memType, pageAddress, addressOffset, function (error) {
// calculate the next page position
if (!error) { pageAddress = pageAddress + pageSize; }
// callback for the next page to be programmed
pagedone(error);
});
});
},
error => callback(error)
);
}
/**
* Pulls out a (page sized) chunk of data from the hex buffer supplied, returns as a sliced buffer
*
* @param {number} address - the starting byte index of the chunk
* @param {number} size - the page size, the size of data chunk you wish to receive back
* @param {buffer} hex - a buffer containing the compiled hex program data
*/
_preparePageData(address, size, hex) {
return hex.slice(address, (hex.length > size ? (address + size) : hex.length - 1));
}
/**
* Writes data to a page in the specified memory
*
* @param {string} memType - the type of memory being written. Either 'flash' or 'eeprom'
* @param {number} delay - the chip's delay setting for memory writing
* @param {number} address - the starting address index to write from
* @param {buffer} hex - a buffer containing the compiled hex program data
* @param {function} callback - function to run upon completion/error
*/
_loadPage(memType, delay, address, buffer, callback) {
if (memType === 'flash') {
this.programmer.writeFlash(delay, address, buffer, (error, result) => callback(error));
} else {
this.programmer.writeEeprom(delay, address, buffer, (error, result) => callback(error));
}
}
/**
* Loads an address location in memory, to prepare for writing to a page
*
* @param {string} memType - the type of memory being written. Either 'flash' or 'eeprom'
* @param {number} address - the starting address index to write from
* @param {function} callback - function to run upon completion/error
*/
_loadAddress(memType, address, callback) {
var memCmd = this.options.chip[memType].write[1];
var low = address & 0xff;
var high = (address >> 8) & 0xff;
var cmd = Buffer.from([memCmd, high, low, 0x00]);
this.programmer.spi(cmd, (error, result) => callback(error, result));
}
/**
* Polls for a successful libusb transfer from the loadAddress method. Stops an auto-failure upon a busy chip.
* Will retry 15 times before calling back with an error.
*
* @param {string} memType - the type of memory being written. Either 'flash' or 'eeprom'
* @param {number} address - the starting address index to write from
* @param {hex} offset - the chip's general offset setting
* @param {function} callback - function to run upon completion/error
*/
_pollForAddress(memType, address, offset, callback) {
var self = this;
var times = 0;
var useAddress = address >> offset;
// try to load next address
tryAddress();
// we loop over this until we no longer get a libusb IO error.
// this is for the Adafruit Trinket as it's both a chip breakout and a programmer in one
function tryAddress() {
self._loadAddress(memType, useAddress, function(error) {
// let's check for an error and act accordingly
handleState(error);
});
};
// checks for error and bumps try times count
function handleState(error) {
times += 1;
// this error is usually a libusb IO errno 1 (ie. the chip is busy still writing to the memory)
if (!error) {
self.hackerLog('_pollForAddress: success');
// success at loading the address, so we callback with no error
callback(null);
} else {
// how may times have we tried already without success?
if (times < 15) {
self.hackerLog('_pollForAddress: retrying ' + times);
// we haven't exhausted our attempts, so let's try again
setTimeout(tryAddress, 50);
} else {
self.hackerLog('_pollForAddress: ran out of attempts');
// exhausted attempts and no success, callback with the error
callback(error);
}
}
}
}
/**
* The public, straightforward method for writing hex data to the flash memory of a connected chip.
*
* @param {buffer} hex - a buffer containing the parsed hex file
* @param {function} callback - function to run upon completion/error
*/
writeFlash(hex, callback) {
// optional convenience method
this._writeMem('flash', hex, error => callback(error));
}
/**
* The public, straightforward method for reading data from the flash memory of a connected chip.
*
* @param {number} length - the length of bytes wishing to be read
* @param {number} address - the starting address from where to read
* @param {function} callback - function to run upon completion/error
*/
readFlash(length, address, callback) {
// check length is a number
if (typeof length !== 'number') {
return callback(new Error('Could not read from flash: length should be a number'));
}
// check address is a number
if (typeof address !== 'number') {
return callback(new Error('Could not read from flash: address should be a number'));
}
return this.programmer.readFlash(this.options.chip.flash.delay, address, length, callback);
}
/**
* The public, straightforward method for writing hex data to the eeprom memory of a connected chip.
*
* @param {buffer} hex - a buffer containing the parsed hex file
* @param {function} callback - function to run upon completion/error
*/
writeEeprom(hex, callback) {
// optional convenience method
this._writeMem('eeprom', hex, error => callback(error));
}
/**
* The public, straightforward method for reading data from the eeprom memory of a connected chip.
*
* @param {number} length - the length of bytes wishing to be read
* @param {number} address - the starting address from where to read
* @param {function} callback - function to run upon completion/error
*/
readEeprom(length, address, callback) {
// check length is a number
if (typeof length !== 'number') {
return callback(new Error('Could not read from eeprom: length should be a number'));
}
// check address is a number
if (typeof address !== 'number') {
return callback(new Error('Could not read from eeprom: address should be a number'));
}
return this.programmer.readEeprom(this.options.chip.eeprom.delay, address, length, callback);
}
/**
* The public method for erasing both eeprom and flash memories at the same time.
*
* @param {function} callback - function to run upon completion/error
*/
eraseChip(callback) {
var options = this.options;
var programmer = programmers[options.programmer];
// adafruit trinket has a reported erase delay of 900000µs but 500000µs seems to work ok, probably due to the runtime
// other usbtinyisp devices just need the regular delay, or theoretically no delay at all.
var delay = programmer.loris ? 500 : options.chip.erase.delay;
this.debug('erasing, please wait...');
this.programmer.spi(Buffer.from(options.chip.erase.cmd), error => setTimeout(() => callback(error), delay));
}
/**
* Will close the connection to the programmer and chip.
*/
close() {
return this.programmer.close();
}
}
// ready event emitter
function _emitReady (self) {
self.emit('ready');
}
module.exports = avrgirlUsbTinyIsp;