-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsam_ba.js
487 lines (449 loc) · 15.2 KB
/
sam_ba.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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
/**
* SAM_BA
* The clas that embodies the root protocol of the Atmel SAM-BA
* flashing ROM routines.
*/
class SamBA {
/**
* Create a SAM_BA object
*
* @param {serialport} serialPort this is the serialport object
* @param {number} debugLevel level of debugging output, 0 is none
*/
constructor(serialPort, debugLevel) {
this.serialPort = serialPort;
this.debugLevel = debugLevel;
this._data = [];
this._dataPromises = [];
this.stackAddress = 0;
this.jumpAddress = 0;
}
/**
* Set the information from the blob in order to call go()
*
* @param {number} stackAddress this is the serialport object
* @param {number} jumpAddress this is the serialport object
*/
setJumpData(stackAddress, jumpAddress) {
this.stackAddress = stackAddress;
this.jumpAddress = jumpAddress;
}
/**
* init - initilize the connection
*
* @return {Promise} Promise to indicate when init is done
*/
init() {
this.serialPort.on('data', (data)=>{
// this._log('< ' + data.toString('hex'));
this._data.push({data: data, pos: 0});
this._handleDataPromises();
});
// Start out in binary mode
return this._writeWithPromise('N#')
.then(()=>{
return this._readWithPromise(2);
})
.then(()=>{
// // ignore the response (it's just CRLF)
// // now read the reset vector
// return this.readWord(0);
// })
// .then((resetVector)=>{
// this.resetVector = resetVector;
// this._log('resetVector: 0x'+resetVector.toString(16));
// // now read the chipId1
// return this.readWord(0x400e0740);
// })
// .then((chipId1)=>{
// this.chipId1 = chipId1;
// this._log('chipId1: 0x'+chipId1.toString(16));
// now read the chipId2
return this.readWord(0x400e0940);
})
.then((chipId)=>{
this.chipId = chipId;
this._log('chipId: 0x'+chipId.toString(16));
// now read the version
return this.getVersion();
})
.then((vStr)=>{
this.vers = vStr;
this._log('vStr: '+vStr);
return Promise.resolve(vStr);
});
}
/**
* go - tell the board to execute a function at a given address
*
* @param {number} address The address withing the machine to jump to.
* @return {Promise} Promise to be fulfilled when the request is sent
*/
go(address) {
if (this.jumpAddress === 0 || this.stackAddress === 0) {
return Promise.reject('setJumpData() was apparently not called.');
}
return this.writeWord(this.jumpAddress, address)
.then(()=>{
return this.writeWord(this.stackAddress, address+1);
})
.then(()=>{
return this._writeWithPromise(this._goCmd(this.jumpAddress));
});
}
/**
* write - send data to the board
* @param {number} address the address to send the data to
* @param {Buffer} data a Buffer containing JUST the data to send
* @param {number} offset the offset into data to start sending from
* (default: 0)
* @return {Promise} Promise to be fulfilled when the data is sent
*/
write(address, data, offset=0) {
// return this._writeWithPromise(this._writeCmd(address, data))
// .then(()=>{
// return this._writeWithPromise(data, true);
// });
return this.writeWord(address, data.slice(offset, offset+4).readUInt32LE())
.then(()=>{
if ((offset + 8) <= data.length) {
return this.write(address+4, data, offset+4);
}
return Promise.resolve(); // we're done
});
}
/**
* verify - read data from the board and compare it to the data provided
* @param {number} address the address to send the data to
* @param {Buffer} data a Buffer containing JUST the data to verify
* @param {number} offset the offset into data to start verifying from
* (default: 0)
* @return {Promise} Promise to be fulfilled when the data is verified
*/
verify(address, data, offset=0) {
return this.readWord(address)
.then((value)=>{
let correctValue = data.slice(offset, offset+4).readUInt32LE();
if (value !== correctValue) {
this._log(`Verify failed: ${value.toString(16)} !== ${correctValue.toString(16)}`);
return Promise.resolve(false);
}
if ((offset + 8) <= data.length) {
this._log(`Verify passed: ${value.toString(16)} == ${correctValue.toString(16)}`);
return this.verify(address+4, data, offset+4);
}
return Promise.resolve(true); // we're done
});
}
/**
* writeWord - wrate a 32-bit (4-byte) word to the address
* @param {number} address the address to read the data from
* @param {number} value the value of a number to send
* @return {Promise} Promise to be fulfilled with the requested number
*/
writeWord(address, value) {
return this._writeWithPromise( this._writeWordCmd(address, value) );
}
/**
* read - read data from the board
* @param {number} address the address to read the data from
* @param {number} length the length (in bytes) of the data to read
* @return {Promise} Promise to be fulfilled with the Buffer of data
*/
read(address, length) {
// Warning from bossac:
// The SAM firmware has a bug reading powers of 2 over 32 bytes
// via USB. If that is the case here, then read the first byte
// with a readByte and then read one less than the requested size.
if (length > 32 && !(length & (length - 1))) {
return readByte(address).then((byte)=>{
return this._writeWithPromise(this._readCmd(address+1, length-1))
.then(()=>{
return this._readWithPromise(length-1);
})
.then( (buffer)=>{
let b2 = buffer.alloc(length);
b2.writeUInt8(byte);
buffer.copy(b2, 1);
return new Promise((r)=>r(b2));
});
});
}
return this._writeWithPromise(this._readCmd(address, length))
.then(()=>{
return this._readWithPromise(length);
});
}
/**
* readByte - read a byte
* @param {number} address the address to read the data from
* @return {Promise} Promise to be fulfilled with the requested number
*/
readByte(address) {
return this._writeWithPromise(this._readByteCmd(address))
.then(()=>{
return this._readWithPromise(1);
})
.then((buf)=>{
return new Promise((finish)=>{
finish(buf.readUInt8());
});
});
}
/**
* readWord - read a 32-bit (4-byte) word
* @param {number} address the address to read the data from
* @return {Promise} Promise to be fulfilled with the requested number
*/
readWord(address) {
return this._writeWithPromise(this._readWordCmd(address))
.then(()=>{
return this._readWithPromise(4);
})
.then((buf)=>{
return Promise.resolve(buf.readUInt32LE());
});
}
/**
* readWordTimeout - read a 32-bit (4-byte) word, with a timeout
* @param {number} address the address to read the data from
* @param {number} timeout the maximum number of ms to wait for a response
* @return {Promise} Promise to be fulfilled with the requested number
*/
readWordTimeout(address, timeout) {
return this._writeWithPromise(this._readWordCmd(address))
.then(()=>{
return this._readWithPromise(4, timeout);
})
// .then((buf)=>{
// if (undefined === buf) {
// this._log(`failed`);
// return Promise.reject("timed out");
// }
// return Promise.resolve(buf);
// })
.then((buf)=>{
return Promise.resolve(buf.readUInt32LE());
});
}
/**
* getVersion - read the version string which is of indeterminate size,
* so we use a timeout.
* @return {Promise} Promise to be fulfilled with version string
*/
getVersion() {
return this._writeWithPromise('V#')
.then(()=>{
return this._readWithPromise(128, 100);
})
.then((buf)=>{
return new Promise((finish)=>{
finish(buf.toString('utf8'));
});
});
}
/* -Internal use functions below- */
/**
* _readWithPromise - read the requested amount of data into a Buffer
*
* @param {number} length the number of bytes to read
* @param {number} timeout milliseconds for timeout
* @return {Promise} a Promise that will return the data on completion
*/
_readWithPromise(length, timeout=1000) {
return new Promise((fulfill, reject)=>{
let p = {
fulfill: fulfill,
reject: reject,
timedout: false,
buffer: Buffer.alloc(length),
filled: 0,
};
p.timeout = setTimeout(()=>{
p.timedout = true;
this._handleDataPromises();
}, timeout);
this._dataPromises.push(p);
// setTimeout(()=>{
// this.serialPort.flush((err)=> {
// if (err) {
// reject(err);
// return;
// }
// });
// }, 10);
});
}
/**
* _handleDataPromises - internal function
*/
_handleDataPromises() {
while ((this._dataPromises.length !== 0)) {
let topDataPromise = this._dataPromises[0];
if (this._data.length !== 0) {
let topData = this._data[0];
let needed = topDataPromise.buffer.length - topDataPromise.filled;
let available = topData.data.length - topData.pos;
if (needed >= available) {
// copy out the needed data
topData.data.copy(topDataPromise.buffer,
topDataPromise.filled,
topData.pos);
topDataPromise.filled += available;
this._data.shift(); // we're done with this data, shift it out
} else {
// available is more than needed, so we'll copy out what's available
topData.data.copy(topDataPromise.buffer,
topDataPromise.filled,
topData.pos,
topData.pos + needed);
topDataPromise.filled += needed;
topData.pos += needed;
}
}
// Logic is: if we timed out, then we just send what we have
if ((topDataPromise.timedout && topDataPromise.filled > 0) ||
(topDataPromise.buffer.length === topDataPromise.filled)) {
this._dataPromises.shift(); // we can remove this from the list
clearTimeout(topDataPromise);
topDataPromise.fulfill(topDataPromise.buffer);
// We handles one request, loop to see if there's another.
continue;
} else
if (topDataPromise.timedout) {
// we timed out but didn't get any data, fail
this._dataPromises.shift(); // we can remove this from the list
clearTimeout(topDataPromise);
topDataPromise.reject(new Error('Read request timed out'));
}
break;
}
}
/**
* _writeWithPromise - internal use only
*
* @param {Buffer} data raw Buffer to write tot he serial port
* @param {Boolean} logAsHex (dafault false)
* @return {Promise} Promise to be fulfilled when the data is sent
*/
_writeWithPromise(data, logAsHex=false) {
return new Promise((finalize, reject)=>{
if (logAsHex) {
this._log('> ' + data.toString('hex'));
} else {
this._log('> ' + data);
}
this.serialPort.write(data, (err)=>{
if (err) {
reject(err);
return;
}
// In some cases, SAM-BA needs to see the request in a different
// "packet" than what follows.
// Also, a serialport.write() calls the callback when the data is
// queued, NOT when the data is finished sending out of the machine.
process.nextTick(()=>{
this.serialPort.drain((err)=>{
if (err) {
reject(err);
return;
}
finalize();
}); // drain
}); // nextTick
}); // write
}); // Promise
}
/**
* _goCmd - internal use only
*
* @param {number} address Address to jump to
* @return {string} Returns the string needed to tell SAM-BA
* to jump to addr.
*/
_goCmd(address) {
let addrBuffer = Buffer.alloc(4);
addrBuffer.writeUInt32BE(address); // thumb mode, we go to the address + 1
return `G${addrBuffer.toString('hex')}#`;
}
/**
* _writeWordCmd - internal use only
* @param {number} address Address to write to
* @param {number} value Buffer object containing *only* the data to send
* @return {string} Returns the string to send to the machine to
* initialte a send request.
*/
_writeWordCmd(address, value) {
let addrBuffer = Buffer.alloc(4);
addrBuffer.writeUInt32BE(address);
let valueBuffer;
if (Buffer.isBuffer(value)) {
valueBuffer = value;
} else {
valueBuffer = Buffer.alloc(4);
valueBuffer.writeUInt32BE(value);
}
return `W${addrBuffer.toString('hex')},${valueBuffer.toString('hex')}#`;
}
/**
* _writeCmd - internal use only
* @param {number} address Address to write to
* @param {Buffer} data Buffer object containing *only* the data to send
* @return {string} Returns the string to send to the machine to
* initialte a send request.
*/
_writeCmd(address, data) {
let addrBuffer = Buffer.alloc(4);
addrBuffer.writeUInt32BE(address);
let sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32BE(data.length);
return `S${addrBuffer.toString('hex')},${sizeBuffer.toString('hex')}#`;
}
/**
* _readByteCmd - internal use only
* @param {number} address Address to write to
* @param {Buffer} data Buffer object containing *only* the data to send
* @return {string} Returns the string to send to the machine to
* initialte a send request.
*/
_readByteCmd(address) {
let addrBuffer = Buffer.alloc(4);
addrBuffer.writeUInt32BE(address);
return `o${addrBuffer.toString('hex')},#`;
}
/**
* _readWordCmd - internal use only
* @param {number} address Address to read from
* @return {string} Returns the string to send to the machine to
* initialte a read request.
*/
_readWordCmd(address) {
let addrBuffer = Buffer.alloc(4);
addrBuffer.writeUInt32BE(address);
return `w${addrBuffer.toString('hex')},#`;
}
/**
* _readCmd - internal use only
* @param {number} address Address to write to
* @param {number} length Buffer object containing *only* the data to send
* @return {string} Returns the string to send to the machine to
* initialte a read request.
*/
_readCmd(address, length) {
let addrBuffer = Buffer.alloc(4);
addrBuffer.writeUInt32BE(address);
let lengthBuffer = Buffer.alloc(4);
lengthBuffer.writeUInt32BE(length);
return `R${addrBuffer.toString('hex')},${lengthBuffer.toString('hex')}#`;
}
/**
* _log - internal use only
* @param {string} text value to log
*/
_log(text) {
if (this.debugLevel > 0) {
console.log(text);
}
}
}
module.exports = SamBA;