-
Notifications
You must be signed in to change notification settings - Fork 2
/
loader.js
3374 lines (3285 loc) · 300 KB
/
loader.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
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (c) 2019 Parallax Inc., All Rights Reserved. */
// Programming metrics
let txData; //Data to transmit to the Propeller (size/contents created later)
const defaultClockSpeed = 80000000;
const defaultClockMode = 0x6F;
const maxDataSize = 1392; //Max data packet size (for packets sent to running Micro Boot Loader)
const mblRespSize = 8; //Size of Micro Boot Loader Response (and Expected) array buffers
const postResetDelay = 100; //Delay after reset and before serial stream
// propComm stage values
const sgIdle = -1;
const sgHandshake = 0;
const sgVersion = 1;
const sgRAMChecksum = 2;
const sgMBLResponse = 3; //NOTE: hearFromProp() requires all further to be wireless stages
const sgWXResponse = 4;
// hearFromProp data source values
const dsDontCare = -1;
const dsWired = 0;
const dsHTTP = 1;
const dsTelnet = 2;
// Propeller Communication (propComm) status; categorizes Propeller responses
let propComm = {}; //Holds current status
let mblRespAB = new ArrayBuffer(mblRespSize); //Buffer for Micro Boot Loader actual responses
let mblExpdAB = new ArrayBuffer(mblRespSize); //Buffer for Micro Boot Loader expected responses
const propCommStart = { //propCommStart is used to initialize propComm
stage : sgIdle, //Propeller Protocol Stage
port : null, //Wireless port to Propeller (if any)
response : null, //Micro Boot Loader response signal (Promise)
rxCount : 0, //Current count of receive bytes (for stage)
version : 0, //Propeller firmware version number
mblRespBuf : new Uint8Array(mblRespAB), //Micro Boot Loader response data (unsigned byte format)
mblRPacketId : new Int32Array(mblRespAB, 0, 1), //Micro Boot Loader requested next packet id (32-bit signed int format)
mblRTransId : new Int32Array(mblRespAB, 4, 1), //Micro Boot Loader response-to transmission id (32-bit signed int format)
mblEPacketId : new Int32Array(mblExpdAB, 0, 1), //Micro Boot Loader expected next packet id (32-bit signed int format)
mblETransId : new Int32Array(mblExpdAB, 4, 1), //Micro Boot Loader expected transmission id (32-bit signed int format)
timer : null, //Holds current timeout timer
timeoutError : "" //Error to issue at end of next timeout
};
//Loader type; used for generateLoaderPacket()
const ltUnEncCore = -2; //Generate unencoded core (used only for Wireless WX-Module downloads)
const ltCore = -1; //Generate encoded core (used only for wired downloads)
const ltVerifyRAM = 0; //Generate Verify RAM executable packet
const ltProgramEEPROM = 1; //Generate Program EEPROM executable packet
const ltReadyToLaunch = 2; //Generate Ready To Launch executable packet
const ltLaunchNow = 3; //Generate Launch Now executable packet
//Receiver Handshake pattern
const rxHandshake = [
0xEE,0xCE,0xCE,0xCF,0xEF,0xCF,0xEE,0xEF,0xCF,0xCF,0xEF,0xEF,0xCF,0xCE,0xEF,0xCF, //The rxHandshake array consists of 125 bytes encoded to represent
0xEE,0xEE,0xCE,0xEE,0xEF,0xCF,0xCE,0xEE,0xCE,0xCF,0xEE,0xEE,0xEF,0xCF,0xEE,0xCE, //the expected 250-bit (125-byte @ 2 bits/byte) response of
0xEE,0xCE,0xEE,0xCF,0xEF,0xEE,0xEF,0xCE,0xEE,0xEE,0xCF,0xEE,0xCF,0xEE,0xEE,0xCF, //continuing-LFSR stream bits from the Propeller, prompted by the
0xEF,0xCE,0xCF,0xEE,0xEF,0xEE,0xEE,0xEE,0xEE,0xEF,0xEE,0xCF,0xCF,0xEF,0xEE,0xCE, //timing templates following the txHandshake stream.
0xEF,0xEF,0xEF,0xEF,0xCE,0xEF,0xEE,0xEF,0xCF,0xEF,0xCF,0xCF,0xCE,0xCE,0xCE,0xCF,
0xCF,0xEF,0xCE,0xEE,0xCF,0xEE,0xEF,0xCE,0xCE,0xCE,0xEF,0xEF,0xCF,0xCF,0xEE,0xEE,
0xEE,0xCE,0xCF,0xCE,0xCE,0xCF,0xCE,0xEE,0xEF,0xEE,0xEF,0xEF,0xCF,0xEF,0xCE,0xCE,
0xEF,0xCE,0xEE,0xCE,0xEF,0xCE,0xCE,0xEE,0xCF,0xCF,0xCE,0xCF,0xCF
];
/***********************************************************
* Support Functions *
***********************************************************/
function deferredPromise() {
/* Create promise with externally-accessible resolve/reject functions
Credit: http://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/
*/
let res, rej;
//Create promise and expose its constructor's resolve/reject functions (normally only accessible within the constructor)
let promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
//Update promise to provide externally-accessible resolve/reject functions
promise.resolve = res;
promise.reject = rej;
//Return the enhanced promise (deferred promise)
return promise;
}
/***********************************************************
* Propeller Programming Functions *
***********************************************************/
//TODO Determine why port not found error message is not getting back to the UI
//TODO Determine how to gracefully handle the need to reset baudrate if error occurs but port is valid (as opposed to error caused by invalid port
//TODO Remove hard-coded example applications
function loadPropeller(sock, portName, action, payload, debug) {
/* Download payload to Propeller with action on portName. If debug, keep port open for communication with sock.
sock may be null (if for development purposes)
portName is wired or wireless port's name
action is 'RAM' or 'EEPROM'
payload is base-64 encoded .elf, .binary, or .eeprom data containing the Propeller Application image
debug is "none", "term", or "graph". "term" or "graph" if a terminal or graphing display (respectively) is intended to connect to the Propeller after download.*/
let binImage;
if (payload) {
//Extract Propeller Application from payload
binImage = parseFile(payload);
if (binImage.message !== undefined) {log("Error: " + binImage.message, mAll, sock); return;}
} else {
binImage = buf2ab(bin);
}
// Look for an existing port
let port = findPort(byName, portName);
if (port) {
// Port found
let connect;
let originalBaudrate;
// Halt timed events that may interfere with download
haltTimedEvents();
if (port.isWired) {
if (port.connId) {
// Connection exists, prep to reuse it
originalBaudrate = port.baud;
updatePort(port, {mode: "programming", bSocket: sock});
connect = function() {return changeBaudrate(port, initialBaudrate)}
} else {
// No connection yet, prep to create one
originalBaudrate = initialBaudrate;
connect = function() {return openPort(sock, portName, initialBaudrate, "programming")}
}
} else {
//TODO Retrieve actual current baudrate
originalBaudrate = initialBaudrate;
updatePort(port, {mode: "programming", bSocket: sock});
connect = function() {return Promise.resolve()};
}
// let startTime = Date.now();
// Use connection to download application to the Propeller
connect()
.then(function() {listen(port, true)}) //Enable listener
.then(function() {log(notice(000, ["Scanning port " + portName]), mUser, sock, -1)}) //Notify what port we're using
.then(function() {return talkToProp(sock, port, binImage, action === 'EEPROM')}) //Download user application to RAM or EEPROM
.then(function() {return changeBaudrate(port, originalBaudrate)}) //Restore original baudrate
.then(function() { //Success! Open terminal or graph if necessary
listen(port, false); //Disable listener
port.mode = (debug !== "none") ? "debug" : "programming";
log(notice(nsDownloadSuccessful), mAll, sock, -1);
if (sock && debug !== "none") { //If debug needed, open terminal/graph
sock.send(JSON.stringify({type:"ui-command", action:(debug === "term") ? "open-terminal" : "open-graph"}));
sock.send(JSON.stringify({type:"ui-command", action:"close-compile"}));
} else { //Else
updatePort(port, {mode: "none"}); // Clear port mode
if (port.isWireless) closePort(port, false).catch(function(e) {log(e.message, mAll, sock, -1);})// Close Telnet port (if wireless)
}
}) //Error? Disable listener and display error
.catch(function(e) {
listen(port, false);
log(e.message, mAll, sock, -1);
log(notice(neDownloadFailed), mAll, sock, -1);
updatePort(port, {mode: "none"});
if ((port.isWired && port.connId) || port.isWireless) {return changeBaudrate(port, originalBaudrate)}
})
.catch(function(e) {log(e.message, mAll, sock, -1)})
.then(function() {if (port.isWireless) return closePort(port, false)})
.catch(function(e) {log(e.message, mAll, sock, -1)})
// .then(function() {let stopTime = Date.now(); log('Processing time: ' + (stopTime-startTime).toString().slice(-5));})
.then(function() {resumeTimedEvents()}) // Resume timed events that were halted earlier
// .catch(function() {let stopTime = Date.now(); log('Processing time: ' + (stopTime-startTime).toString().slice(-5));})
.catch(function() {resumeTimedEvents()});
} else {
// Port not found
log(notice(neCanNotFindPort, [portName]), mAll, sock, -1);
log(notice(neDownloadFailed), mAll, sock, -1);
}
}
function listen(port, engage) {
/* Engage or disengage asynchronous programming receive listener.
port = the port to listen to.
engage = true to add listener; false to remove listener.*/
if (engage) {
resetPropComm(port, null, null, null, true);
chrome.serial.onReceive.removeListener(hearFromProp); //Safety: previous listener may be left over from uncaught promise (rare)
chrome.sockets.tcp.onReceive.removeListener(hearFromProp);
if (port.isWired) {
chrome.serial.onReceive.addListener(hearFromProp);
} else {
chrome.sockets.tcp.onReceive.addListener(hearFromProp);
}
} else {
if (port.isWired) {
chrome.serial.onReceive.removeListener(hearFromProp);
} else {
chrome.sockets.tcp.onReceive.removeListener(hearFromProp);
}
}
}
function resetPropComm(port, timeout, stage, error, command) {
/*Reset propComm object to default values
port = the port that PropComm is associated with.
timeout = [optional] period (in ms) for initial timeout. If provided, sets stage to initial value (according to wired/wireless), creates deferred promise, and creates timeout timer.
stage = [optional] stage to initialize with.
error = [optional] error notice code if timeout occurs
command [ignored unless wireless] must be true to, upon timeout, close socket to Wi-Fi Module's HTTP-based command service and false to close socket to Propeller via Telnet service*/
clearPropCommTimer(); //Clear old timer, if any
Object.assign(propComm, propCommStart); //Reset propComm object
if (timeout) { //If timeout provided
propComm.stage = (!stage) ? (port.isWired) ? sgHandshake : sgMBLResponse : stage; //Initialize the stage
propComm.port = port; //Remember wireless Propeller port (if any)
propComm.response = deferredPromise(); //Create new deferred promise for micro boot loader response
setPropCommTimer(timeout, (!error) ? notice(nePropellerNotFound) : error, command); //Default to "Propeller Not Found" error
};
}
function setPropCommTimer(timeout, timeoutError, command) {
/* Set timeout timer for Propeller Communication
timeout = timeout period (in ms)
timeoutError = string message to issue upon a timeout
command [ignored unless wireless] must be true to, upon timeout, close socket to Wi-Fi Module's HTTP-based command service and false to close socket to Propeller via Telnet service*/
clearPropCommTimer(); //Clear old timer now
propComm.timeoutError = timeoutError; //Prep for new error possibility
timeout = Math.trunc(timeout);
propComm.timer = setTimeout(function() { //If timeout occurs...
log("Timed out in " + timeout + " ms", mDbug);
clearPropCommTimer(); // Clear timer
propComm.stage = sgIdle; // Reset propComm stage to Idle (ignore incoming data)
Promise.resolve()
.then(function() {if (propComm.port.isWireless) {return closePort(propComm.port, command)}}) // Close and/or forget HTTP or Telnet service socket
.then(function() {propComm.response.reject(Error(propComm.timeoutError))}) // And reject with error
.catch(function(e) {propComm.response.reject(Error(propComm.timeoutError))})
}, timeout);
}
function clearPropCommTimer() {
/* Clear propComm timer, if it exists*/
if (propComm.timer) {
clearTimeout(propComm.timer);
propComm.timer = null;
}
}
function wait(ms) {
/* Actively delay for ms milliseconds.
This should only be used for time-critical delays as it doesn't release to the task queue but instead consumes CPU time until finished.
*/
let Done = Date.now() + ms;
while (Date.now() < Done){}
}
function talkToProp(sock, port, binImage, toEEPROM) {
/* Return promise to deliver Propeller Application (binImage) to Propeller
sock is the websocket to direct mUser messages at
port is the port's object (must be already-opened port if wired)
binImage must be an ArrayBuffer
toEEPROM is false to program RAM only, true to program RAM+EEPROM*/
return new Promise(function(resolve, reject) {
function sendLoader() {
/* Return a promise that gets Propeller's attention and delivers Micro Boot Loader application.
For wired connections- generates reset signal, waits post-reset delay time, then sends the communication package (Timing Pulses, Host Handshake,
and Micro Boot Loader), then waits for the responding Propeller Handshake, Version, and successful Micro Boot Loader delivery notice.
For wireless connections- sends Micro Boot Loader then waits for wireless programming response (error) or Micro Boot Loader response (success).
Rejects if any error occurs. Micro Boot Loader must respond with Packet ID (plus Transmission ID) for success (resolve).
Error is "Propeller not found" (generated by hearFromProp) unless handshake received (and proper) and version received (wired/wireless connection);
error is more specific thereafter.
*/
return new Promise(function(resolve, reject) {
function sendMBL() {
return new Promise(function(resolve, reject) {
//If wired, actively wait before transmitting Micro Boot Loader; active (vs. passive) wait prevents extended delays in CrOS v67+
if (port.isWired) {
log("Waiting " + Math.trunc(postResetDelay) + " ms", mDeep);
wait(postResetDelay);
}
//Prep for expected packetID:transmissionId response (Micro-Boot-Loader's "Ready" signal)
propComm.mblEPacketId[0] = packetId;
propComm.mblETransId[0] = 0; //MBL transmission's Id is always 0
//Send Micro Boot Loader package and get response; if wired port, unpause (may be auto-paused by incoming data error); wireless ports, carry on immediately
log("Transmitting Micro Boot Loader package", mDeep);
send(port, txData, true)
.then(function() {if (port.isWired) {return unPause(port)}}) //Unpause port (if wired)
.then(function() {return propComm.response}) //Wait for response (may timeout with rejection)
.then(function() {log(notice(000, ["Found Propeller"]), mUser+mDbug, sock, -1)}) //Succeeded!
.then(function() {return resolve()})
.catch(function(e) {return reject(e)}); //Failed!
});
}
Promise.resolve()
.then(function() { resetPropComm(port, mblDeliveryTime, null, null, true);}) //Reset propComm object
.then(function() {if (port.isWired) {log("Generating reset signal", mDeep);}}) //If wired...
.then(function() {if (port.isWired) {return setControl(port, {dtr: false});}}) // Start Propeller Reset Signal
.then(function() {if (port.isWired) {return flush(port);}}) // Flush transmit/receive buffers (during Propeller reset)
.then(function() {if (port.isWired) {return setControl(port, {dtr: true});}}) // End Propeller Reset
.then(function() { return sendMBL();}) //send comm package, including Micro Boot Loader; verify receipt
.then(function() { return resolve();}) //Propeller found? Resolve
.catch(function(e) { return reject(e);}) //Error! Reject with message
});
}
function prepForMBLResponse(timeout, timeoutError) {
/* Set propComm to prep for another Micro Boot Loader response.
timeout = timeout period (in ms)
timeoutError = string message to issue upon a timeout
*/
propComm.response = deferredPromise(); //Create new deferred promise for micro boot loader response
propComm.stage = sgMBLResponse;
propComm.rxCount = 0;
setPropCommTimer(timeout, timeoutError, false);
}
//TODO catch send() errors
//TODO add transmitPacket function to auto-retry 3 times if needing to harden against flaky wireless connections
//TODO determine if txPacketLength and idx can refer to bytes instead of longs to lessen iterative calculations
function sendUserApp() {
// Return a promise that delivers the user application to the Micro Boot Loader.
return new Promise(function(resolve, reject) {
function sendUA() {
return new Promise(function(resolve, reject) {
log((totalPackets-packetId+1) + " of " + totalPackets, mDbug);
log(notice(nsDownloading), mUser, sock, -1);
prepForMBLResponse(userDeliveryTime, notice(neCommunicationLost));
var txPacketLength = 2 + //Determine packet length (in longs); header + packet limit or remaining data length
Math.min(Math.trunc(maxDataSize / 4) - 2, Math.trunc(binImage.byteLength / 4) - pIdx);
txData = new ArrayBuffer(txPacketLength * 4); //Set packet length (in longs)}
txView = new Uint8Array(txData);
propComm.mblEPacketId[0] = packetId-1; //Set next expected packetId
propComm.mblETransId[0] = Math.floor(Math.random()*4294967296); //Set next random Transmission ID
(new DataView(txData, 0, 4)).setUint32(0, packetId, true); //Store Packet ID
(new DataView(txData, 4, 4)).setUint32(0, propComm.mblETransId[0], true); //Store random Transmission ID
//log('Sending PID/TID: '+txView.subarray(0, 4)+'/'+txView.subarray(4, 8), mDeep);
txView.set((new Uint8Array(binImage)).slice(pIdx * 4, pIdx * 4 + (txPacketLength - 2) * 4), 8); //Store section of binary image
send(port, txData, false) //Transmit packet
.then(function() {pIdx += txPacketLength - 2; packetId--; resolve();}); //Increment image index, decrement Packet ID (to next packet), resolve
});
}
sendUA() //Send user application packet
.then(function() {return propComm.response;}) //Wait for response
.then(function() {if (packetId > 0) {return sendUserApp()}}) //More packets? repeat (note: sendUserApp does not return execution here (promises continue at next .then/.catch))
.then(function() {return resolve()}) //else, resolve
.catch(function(e) {return reject(e)}); //Error? return error message
});
}
function* packetGenerator() {
//Packet specification generator; generates details for the next packet
yield {type: ltVerifyRAM, nextId: -checksum, sendLog: notice(nsVerifyingRAM), recvTime: userDeliveryTime, recvErr: notice(neRAMChecksumFailed)};
if (toEEPROM) {
yield {type: ltProgramEEPROM, nextId: -checksum*2, sendLog: notice(nsVerifyingEEPROM), recvTime: userDeliveryTime+4000, recvErr: notice(neEEPROMVerifyFailed)};
}
yield {type: ltReadyToLaunch, nextId: packetId-1, sendLog: notice(000, ["Ready for Launch"]), recvTime: userDeliveryTime, recvErr: notice(neCommunicationLost)};
yield {type: ltLaunchNow, nextId: -1, sendLog: notice(000, ["Launching"]), recvTime: 0, recvErr: ""};
}
function finalizeDelivery() {
// Return a promise that sends the final packets (special executable packets) that verifies RAM, programs and verifies EEPROM, and launches user code.
return new Promise(function(resolve, reject) {
function sendInstructionPacket() {
return new Promise(function(resolve, reject) {
next = instPacket.next();
log(next.value.sendLog, mAll, sock, -1);
generateLoaderPacket(next.value.type, packetId); //Generate next executable packet
if (next.value.type !== ltLaunchNow) { //Response expected from MBL?
prepForMBLResponse(next.value.recvTime, notice(neCommunicationLost)); // Prepare to receive next MBL response
packetId = next.value.nextId; // Ready next Packet ID
propComm.mblEPacketId[0] = packetId; // Note expected response
propComm.mblETransId[0] = Math.floor(Math.random()*4294967296);
}
(new DataView(txData, 4, 4)).setUint32(0, propComm.mblETransId[0], true); //Store random Transmission ID (or 0)
send(port, txData, false) //Transmit packet
.then(function() {
if (next.value.type !== ltLaunchNow) { // If not last instruction packet...
propComm.response // When response (promise)...
.then(function() {return sendInstructionPacket()}) // is success; send next packet (if any) and
.then(function() {return resolve()}) // resolve
.catch(function(e) {return reject(Error(next.value.recvErr))}); // is failure; reject (return the specific error)
} else { // Else, last instruction packet sent; success
resolve(); // Success; User App Launched!
}
});
});
}
//Set up continuous progress indicator during this phase
progress = setInterval(log, 1000, notice(nsDownloading), mUser, sock);
sendInstructionPacket()
.then(function() {clearInterval(progress); return resolve();})
.catch(function(e) {clearInterval(progress); return reject(e);});
});
}
//Determine number of required packets for target application image; value becomes first Packet ID
var totalPackets = Math.ceil(binImage.byteLength / (maxDataSize-4*2)); //binary image size (in bytes) / (max packet size - packet header)
var packetId = totalPackets;
var pIdx = 0; //Packet index (points to next data in binary image to send
//Calculate target application's full checksum (used for RAM Checksum confirmation)}
binView = new Uint8Array(binImage); //Create view of the Propeller Application Image
var checksum = 0x7EC; //Start with full checksum of initial call frame
for (idx = 0; idx < binView.byteLength; idx++) {checksum += binView[idx];} //Add to it all Propeller Application Image bytes (retaining full checksum value)
//Generate encoded communication and loader package (for wired port) or only unencoded loader package (for wireless port)
generateLoaderPacket((port.isWired) ? ltCore : ltUnEncCore, packetId, defaultClockSpeed, defaultClockMode);
//Prep packetGenerator iterator and next object
var instPacket = packetGenerator();
var next;
/* Calculate expected Micro Boot Loader and User Application delivery times
= 210 [max post-reset-delay] + ((10 [bits per byte] * (data bytes [transmitting] + 20 silence bytes [MBL waiting] + 8 MBL "ready" bytes [MBL responding])) /
initial baud rate) * 1,000 [to scale ms to integer] + 1 [to always round up] + 250 or 1500 [Rx hardware or Network to OS slack time] */
var mblDeliveryTime = Math.trunc(210+((10*(txData.byteLength+20+8))/initialBaudrate)*1000+1 + ((port.isWired) ? 250 : 1500));
//=((10 [bits per byte] * [max packet size]) / final baud rate) * 1,000 [to scale ms to integer] + 1 [to always round up] + 1500 or 250 [Network or Rx hardware to OS slack time]
var userDeliveryTime = Math.trunc(((10*maxDataSize)/finalBaudrate)*1000+1 + ((port.isWired) ? 250 : 1500));
Promise.resolve()
.then(function() {return sendLoader();}) //Get Propeller's attention and send initial application (Micro Boot Loader)
.then(function() {return changeBaudrate(port, finalBaudrate);}) //Bump up to faster finalBaudrate
.then(function() { //Send user application
log("Delivering user application packets", mDbug);
return sendUserApp();
})
.then(function() {return finalizeDelivery();}) //Finalize delivery and launch user application
.then(function() {return resolve();}) //Success!
.catch(function(e) {clearPropCommTimer(); reject(e);}); //Catch errors, clear timer, pass errors onward
});
}
function hearFromProp(info) {
/* Receive Propeller's responses during programming. Parse responses for expected stages.
This function is called asynchronously whenever data arrives and may receive data not related to programming (such as a debug stream or unrelated IP traffic)
so it filters and ignores what it doesn't need.*/
let dataSource = (info.hasOwnProperty("socketId")) ?
/*Is Expected Wireless HTTP?*/ (info.socketId === propComm.port.phSocket) ? dsHTTP :
/*Is Expected Wireless Telnet?*/ (!propComm.port.phSocket && propComm.stage === sgMBLResponse && info.socketId === propComm.port.ptSocket) ? dsTelnet :
/*Unexpected WL Protocol for State*/ dsDontCare :
/*Is Expected Wired stream?*/ (propComm.stage !== sgIdle) ? dsWired : dsDontCare;
// Exit if this isn't the data we're looking for
if (dataSource === dsDontCare) {
log("Ignoring " + info.data.byteLength + " unexpected bytes", mDeep);
// console.log(info.data);
return;
}
// Parse HTTP-command responses into proper object, or treat wired and Telnet-wireless streams as an unformatted array
let stream = (dataSource === dsHTTP) ? parseHTTP(info.data) : new Uint8Array(info.data)
//log("Received " + info.data.byteLength + " bytes:" + ((info.data.byteLength < 9) ? " " : "\n") + stream.toString(), mDeep);
var sIdx = 0;
/* Validate rxHandshake
To find a received handshake, a few problems must be overcome: 1) data doesn't always arrive in it's entirety per any receive event; a few bytes may appear
at one event, then a few more on the following event, 2) data may "never" match; it may suddenly stop arriving during a partial match, or continuous data may
be absolutely unrelated, and 2) a serial port flush does not guarantee pristine data; the handshake may appear buried at the end of non-related serial data
streamed in from the previous Propeller App execution, received just after the flush event and just before the reset signal reaches the Propeller. To combat
all this, surprisingly simple techniques are employed. 1) Receive and match handshake as a state machine, 2) use an independent timeout (in this case the
propComm.timer event) to abort if necessary, and 3) pattern match using unique rxHandshake attributes. Item #3 is explained further next.
(Item #3 Explanation)
The 2-bit per byte encoded rxHandshake begins with a unique 3-byte pattern (0xEE,0xCE,0xCE); that pattern does not appear anywhere else within it; however,
the first two bytes of that pattern do appear in multiple places. Using this knowledge, comparisons of a received stream against the rxHandshake, dumping
every unmatched byte (and realigning next received byte(s) to 3rd byte of rxHandshake), allows reception of a clean pattern, or an obscured pattern start,
or even a truncated matching pattern followed by a restarted full pattern, to be easily found and aligned to match. In addition, no received data need be
retained longer than each individual byte comparison.
The match technique is as follows (start at first byte of rxHandshake):
A) compare two bytes (current byte in stream against current byte in rxHandshake,
B) if they match- index to next byte of each and repeat "A",
if they don't match- index stream (only if on 3rd byte of rxHandshake), reset to 3rd byte of rxHandshake, and repeat "A".
Even though this technique sometimes skips a true comparison of the first two bytes of rxHandshake, all remaining 123 bytes are always matched perfectly.
Incredibly, in the case of receiving a partial match followed by a full handshake match, it gracefully recovers and re-aligns, even if the start of the full
happens to appear perfectly lined up with one of the seven deep places in rxHandshake that the first 2-byte pattern appears (0xEE,0xCE). Despite potentially
ignoring the first 2 bytes of rxHandshake, the chance of a false positive match is 1 in 2^984 (astronomically improbable).
*/
if (propComm.stage === sgHandshake) {
while (sIdx < stream.length && propComm.rxCount < rxHandshake.length) {
//More data to match against rxHandshake...
if (stream[sIdx] === rxHandshake[propComm.rxCount]) {
//Handshake matches so far...
sIdx++;
if (++propComm.rxCount === rxHandshake.length) {
//Entire handshake matches! Prep for next stage
propComm.rxCount = 0;
propComm.stage = sgVersion;
break;
}
} else {
//Handshake mismatch; realign and retry
sIdx += (propComm.rxCount === 2); //Match failed; index to next received byte (only if already compared against 3rd rxHandshake byte)
propComm.rxCount = 2; //Prep to compare with 3rd rxHandshake byte (see "Item #3 Explanation," above)
}
}
}
// Extract Propeller version
if (propComm.stage === sgVersion) {
while (sIdx < stream.length && propComm.rxCount < 4) {
//More data to decode into Propeller version (4 bytes, 2 data bits per byte)
propComm.version = (propComm.version >> 2 & 0x3F) | ((stream[sIdx] & 0x01) << 6) | ((stream[sIdx] & 0x20) << 2);
sIdx++;
if (++propComm.rxCount === 4) {
//Received all 4 bytes
if (propComm.version === 1) {
//Version matches expected value! Prep for next stage
//Found Propeller; update timeout for next possible error (if no RAM Checksum or Micro Boot Loader response received)
propComm.timeoutError = notice(neCommunicationLost);
propComm.rxCount = 0;
propComm.stage = sgRAMChecksum;
} else {
//Unexpected version! Note rejected; Ignore the rest
clearPropCommTimer();
propComm.response.reject(Error(notice(neUnknownPropellerVersion, [propComm.version])));
propComm.stage = sgIdle;
}
break;
}
}
}
// Receive RAM Checksum
if (propComm.stage === sgRAMChecksum && sIdx < stream.length) {
//Received RAM Checksum response?
if (stream[sIdx++] === 0xFE) {
//RAM Checksum valid; Prep for next stage
propComm.stage = sgMBLResponse;
} else {
//RAM Checksum invalid; Note rejected; Ignore the rest
clearPropCommTimer();
propComm.response.reject(Error(notice(neCommunicationFailed)));
propComm.stage = sgIdle;
}
propComm.rxCount = 0;
}
// Receive Micro Boot Loader's response. The first serves as its "Ready" signal, the rest are packet responses; all are formatted as 4-byte expected Packet ID followed by 4-byte Transmission ID.
// NOTE: For wireless, the first is contained in the body of an HTTP response and the rest are delivered as Telnet responses.
if (propComm.stage === sgMBLResponse) {
if (dataSource !== dsWired) {
// Wireless response
if (dataSource === dsHTTP) { //HTTP-command response; we'll assume right size, errors will be caught by timeout
if (stream.ResponseCode === 200) {
propComm.mblRespBuf.set(new Uint8Array(stream.Body.slice(-mblRespSize)));
propComm.rxCount = mblRespSize;
}
if (stream.hasOwnProperty("Connection") && stream.Connection === "close") {updatePort(propComm.port, {phSocket: null})} // Forget socket id (closed by host)}
} else { //Telnet response; we'll assume right size (perhaps tacked onto end of previous debug stream), errors will be caught by timeout
propComm.mblRespBuf.set(new Uint8Array(stream.slice(-mblRespSize)));
propComm.rxCount = mblRespSize;
}
} else {
// Wired response
//TODO consider set function here
while (sIdx < stream.length && propComm.rxCount < mblRespSize) {
propComm.mblRespBuf[propComm.rxCount++] = stream[sIdx++];
}
}
//Finish stage when expected response size received
if (propComm.rxCount === mblRespSize) {
// log("Response PacketId: "+ propComm.mblRPacketId+ " TransId: "+ propComm.mblRTransId, mDeep);
// log("Expected PacketId: "+ propComm.mblEPacketId+ " TransId: "+ propComm.mblETransId, mDeep);
if ((propComm.mblRPacketId[0] === propComm.mblEPacketId[0]) && (propComm.mblRTransId[0] === propComm.mblETransId[0])) {
//MBL Response is perfect; Note resolved
clearPropCommTimer();
propComm.stage = sgIdle;
propComm.response.resolve();
} else {
//MBL Response invalid; may be leftover debug data; ignore
propComm.rxCount = 0;
}
return;
}
}
// Receive WiFi Module response
if (propComm.stage === sgWXResponse) {
clearPropCommTimer();
propComm.stage = sgIdle;
if (stream.ResponseCode === 200) {
// propComm.mblRespBuf.set(new Uint8Array(stream.Body.slice(0, mblRespSize)));
// propComm.rxCount = mblRespSize;
propComm.response.resolve();
} else {
//TODO Designate a proper error here (probably best to pass on error from response)
propComm.response.reject(Error(notice(neLoaderFailed)));
}
if (stream.hasOwnProperty("Connection") && stream.Connection === "close") {updatePort(propComm.port, {phSocket: null})} // Forget socket id (closed by host)
}
}
function generateLoaderPacket(loaderType, packetId, clockSpeed, clockMode) {
/*Generate a packet (in txData) containing the portion of the Micro Boot Loader (IP_Loader.spin) indicated by LoaderType.
Initial call should use loaderType of ltCore (for wired downloads) or ltUnEncCore (for wireless downloads) and later calls use other loaderTypes.
If loaderType is ltCore or ltUnEncCore...
* Target application's total packet count must be included in packetID.
* Target application's system clock speed must be included in clockSpeed.
* Target application's system clock mode must be included in clockMode.
* If ltCore- generated packet contains the Propeller handshake, timing templates, and patched core code from the Micro Boot Loader
(IP_Loader.spin), with optimal encoding (3, 4, or 5 bits per byte; 7 to 11 bytes per long).
- Optimal encoding means, for every 5 contiguous bits in Propeller Application Image (LSB first) 3, 4, or 5 bits will be translated
to a byte. The process uses a translation array (for speed)- input up to 5 bits and the bit count (ie: indexed into the pDSTx array)
and output a byte containing the first 3, 4, or 5 bits of the input encoded into the Propeller download stream format plus the number
of bits actually encoded. If less than 5 bits were translated, the remaining bits lead the next 5-bit translation unit input to the
translation process.
* If ltUnEncCore- generated packet contains HTTP POST request plus the patched core code from the Micro Boot Loader (IP_Loader.spin).
No encoding performed. No Propeller handshake or timing templates need be included since the wireless device (Wi-Fi Module) takes care
of that automatically.
If loaderType is not ltCore...
* packetIds should be less than 0 for this type of packet in order to work with the Micro Boot Loader core.
* clockSpeed and clockMode are omitted.
* generated packet is a snippet of loader code aligned to be executable from inside the Core's packet buffer. This snippet is in unencoded
form and should be transmitted as such.
Propeller Download Stream Translator array- for encoding raw images. Index into this array with the "Binary Value" (usually 5 bits) to
translate the incoming bit size (again, usually 5), and the desired data element to retrieve (0 = translation, 1 = translated bit count.
A portion of the array is not applicable (unused "[0,0]") including the first column (0 bits input).
Propeller Download Stream Translator (pDSTx) Usage:
Binary Incoming Translation
Value Bit Size and Bit Count
pDSTx[0..31, 1..5, 0..1]
*/
const pDSTx = [
/* 0-BITs ***** 1-BIT ***** ***** 2-BIT ***** ***** 3-BIT ***** ***** 4-BIT ***** ***** 5-BIT ***** */
[ [0, 0], /*0b00000*/ [0xFE, 1], /*0b00000*/ [0xF2, 2], /*0b00000*/ [0x92, 3], /*0b00000*/ [0x92, 3], /*0b00000*/ [0x92, 3] ],
[ [0, 0], /*0b00001*/ [0xFF, 1], /*0b00001*/ [0xF9, 2], /*0b00001*/ [0xC9, 3], /*0b00001*/ [0xC9, 3], /*0b00001*/ [0xC9, 3] ],
[ [0, 0], [0, 0], /*0b00010*/ [0xFA, 2], /*0b00010*/ [0xCA, 3], /*0b00010*/ [0xCA, 3], /*0b00010*/ [0xCA, 3] ],
[ [0, 0], [0, 0], /*0b00011*/ [0xFD, 2], /*0b00011*/ [0xE5, 3], /*0b00011*/ [0x25, 4], /*0b00011*/ [0x25, 4] ],
[ [0, 0], [0, 0], [0, 0], /*0b00100*/ [0xD2, 3], /*0b00100*/ [0xD2, 3], /*0b00100*/ [0xD2, 3] ],
[ [0, 0], [0, 0], [0, 0], /*0b00101*/ [0xE9, 3], /*0b00101*/ [0x29, 4], /*0b00101*/ [0x29, 4] ],
[ [0, 0], [0, 0], [0, 0], /*0b00110*/ [0xEA, 3], /*0b00110*/ [0x2A, 4], /*0b00110*/ [0x2A, 4] ],
[ [0, 0], [0, 0], [0, 0], /*0b00111*/ [0xFA, 3], /*0b00111*/ [0x95, 4], /*0b00111*/ [0x95, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01000*/ [0x92, 3], /*0b01000*/ [0x92, 3] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01001*/ [0x49, 4], /*0b01001*/ [0x49, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01010*/ [0x4A, 4], /*0b01010*/ [0x4A, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01011*/ [0xA5, 4], /*0b01011*/ [0xA5, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01100*/ [0x52, 4], /*0b01100*/ [0x52, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01101*/ [0xA9, 4], /*0b01101*/ [0xA9, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01110*/ [0xAA, 4], /*0b01110*/ [0xAA, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], /*0b01111*/ [0xD5, 4], /*0b01111*/ [0xD5, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10000*/ [0x92, 3] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10001*/ [0xC9, 3] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10010*/ [0xCA, 3] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10011*/ [0x25, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10100*/ [0xD2, 3] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10101*/ [0x29, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10110*/ [0x2A, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b10111*/ [0x95, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11000*/ [0x92, 3] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11001*/ [0x49, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11010*/ [0x4A, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11011*/ [0xA5, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11100*/ [0x52, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11101*/ [0xA9, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11110*/ [0xAA, 4] ],
[ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /*0b11111*/ [0x55, 5] ]
];
//Power of 2 - 1 array. Index into this array with the desired power of 2 (1 through 5) and element value is mask equal to power of 2 minus 1
pwr2m1 = [0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F];
const txHandshake = [ //Transmit Handshake pattern.
0x49, //First timing template ('1' and '0') plus first two bits of handshake ('0' and '1')
0xAA, 0x52, 0xA5, 0xAA, 0x25, 0xAA, 0xD2, 0xCA, 0x52, 0x25, 0xD2, 0xD2, 0xD2, 0xAA, 0x49, 0x92, //Remaining 248 bits of handshake...
0xC9, 0x2A, 0xA5, 0x25, 0x4A, 0x49, 0x49, 0x2A, 0x25, 0x49, 0xA5, 0x4A, 0xAA, 0x2A, 0xA9, 0xCA,
0xAA, 0x55, 0x52, 0xAA, 0xA9, 0x29, 0x92, 0x92, 0x29, 0x25, 0x2A, 0xAA, 0x92, 0x92, 0x55, 0xCA,
0x4A, 0xCA, 0xCA, 0x92, 0xCA, 0x92, 0x95, 0x55, 0xA9, 0x92, 0x2A, 0xD2, 0x52, 0x92, 0x52, 0xCA,
0xD2, 0xCA, 0x2A, 0xFF,
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, //250 timing templates ('1' and '0')
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, //to receive 250-bit handshake from
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, //Propeller.}
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, //This is encoded as two pairs per}
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, //byte; 125 bytes}
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
0x29, 0x29, 0x29, 0x29, //8 timing templates ('1' and '0') to receive 8-bit Propeller ver; two pairs per byte; 4 bytes
0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0xF2 //Download command (1; program RAM and run); 11 bytes
];
const rawLoaderImage = [ //Raw loader image.
0x00, 0xB4, 0xC4, 0x04, 0x6F, 0x93, 0x10, 0x00, 0x88, 0x01, 0x90, 0x01, 0x80, 0x01, 0x94, 0x01, //This is the Micro Boot Loader, a Propeller
0x78, 0x01, 0x02, 0x00, 0x70, 0x01, 0x00, 0x00, 0x4D, 0xE8, 0xBF, 0xA0, 0x4D, 0xEC, 0xBF, 0xA0, //Application written in PASM that fits entirely into the initial
0x51, 0xB8, 0xBC, 0xA1, 0x01, 0xB8, 0xFC, 0x28, 0xF1, 0xB9, 0xBC, 0x80, 0xA0, 0xB6, 0xCC, 0xA0, //download packet. Once downloaded and launched, it assists with
0x51, 0xB8, 0xBC, 0xF8, 0xF2, 0x99, 0x3C, 0x61, 0x05, 0xB6, 0xFC, 0xE4, 0x59, 0x24, 0xFC, 0x54, //the remainder of the download (at a faster speed, without the
0x62, 0xB4, 0xBC, 0xA0, 0x02, 0xBC, 0xFC, 0xA0, 0x51, 0xB8, 0xBC, 0xA0, 0xF1, 0xB9, 0xBC, 0x80, //need for special encoding, and with more relaxed interstitial
0x04, 0xBE, 0xFC, 0xA0, 0x08, 0xC0, 0xFC, 0xA0, 0x51, 0xB8, 0xBC, 0xF8, 0x4D, 0xE8, 0xBF, 0x64, //timing capable of surviving unexpected delays, even when
0x01, 0xB2, 0xFC, 0x21, 0x51, 0xB8, 0xBC, 0xF8, 0x4D, 0xE8, 0xBF, 0x70, 0x12, 0xC0, 0xFC, 0xE4, //transmission is using Internet Protocol delivery. This image
0x51, 0xB8, 0xBC, 0xF8, 0x4D, 0xE8, 0xBF, 0x68, 0x0F, 0xBE, 0xFC, 0xE4, 0x48, 0x24, 0xBC, 0x80, //isn't used as-is; just before download, it is adjusted to contain
0x0E, 0xBC, 0xFC, 0xE4, 0x52, 0xA2, 0xBC, 0xA0, 0x54, 0x44, 0xFC, 0x50, 0x61, 0xB4, 0xFC, 0xA0, //special values assigned by this host (communication timing and
0x5A, 0x5E, 0xBC, 0x54, 0x5A, 0x60, 0xBC, 0x54, 0x5A, 0x62, 0xBC, 0x54, 0x04, 0xBE, 0xFC, 0xA0, //synchronization values) and then is translated into an optimized
0x54, 0xB6, 0xBC, 0xA0, 0x53, 0xB8, 0xBC, 0xA1, 0x00, 0xBA, 0xFC, 0xA0, 0x80, 0xBA, 0xFC, 0x72, //Propeller Download Stream understandable by the Propeller
0xF2, 0x99, 0x3C, 0x61, 0x25, 0xB6, 0xF8, 0xE4, 0x36, 0x00, 0x78, 0x5C, 0xF1, 0xB9, 0xBC, 0x80, //ROM-based boot loader.
0x51, 0xB8, 0xBC, 0xF8, 0xF2, 0x99, 0x3C, 0x61, 0x00, 0xBB, 0xFC, 0x70, 0x01, 0xBA, 0xFC, 0x29,
0x2A, 0x00, 0x4C, 0x5C, 0xFF, 0xC2, 0xFC, 0x64, 0x5D, 0xC2, 0xBC, 0x68, 0x08, 0xC2, 0xFC, 0x20,
0x55, 0x44, 0xFC, 0x50, 0x22, 0xBE, 0xFC, 0xE4, 0x01, 0xB4, 0xFC, 0x80, 0x1E, 0x00, 0x7C, 0x5C,
0x22, 0xB6, 0xBC, 0xA0, 0xFF, 0xB7, 0xFC, 0x60, 0x54, 0xB6, 0x7C, 0x86, 0x00, 0x8E, 0x68, 0x0C,
0x59, 0xC2, 0x3C, 0xC2, 0x09, 0x00, 0x54, 0x5C, 0x01, 0xB2, 0xFC, 0xC1, 0x63, 0x00, 0x70, 0x5C,
0x63, 0xB4, 0xFC, 0x84, 0x45, 0xC6, 0x3C, 0x08, 0x04, 0x8A, 0xFC, 0x80, 0x48, 0x7E, 0xBC, 0x80,
0x3F, 0xB4, 0xFC, 0xE4, 0x63, 0x7E, 0xFC, 0x54, 0x09, 0x00, 0x7C, 0x5C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0xFF, 0xFF, 0xF9, 0xFF, 0x10, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x40,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x07, 0x00, 0x00, 0x00, 0xB6, 0x02, 0x00, 0x00,
0x56, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x55, 0x73, 0xCB, 0x00, 0x18, 0x51, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x35, 0xC7, 0x08, 0x35, 0x2C, 0x32, 0x00, 0x00
];
/*Offset (in bytes) within the Raw Loader Image (above) to the start of host-initialized values that exist within it. Host-Initialized values are
constants in the source (Propeller Assembly code) that are intended to be replaced by the host (the computer running 'this' code) before packetization
and transmission of the image to the Propeller. Host-Initialized Values are Initial Bit Time, Final Bit Time, 1.5x Bit Time, Failsafe timeout,
End of Packet Timeout, Start/Stop Time, SCL High Time, SCL Low Time, and ExpectedID. In addition to replacing these values, the host needs to update
the image's checksum at word 5.*/
// Value Bytes Spin Bytes*
const InitOffset = rawLoaderImage.length - ( 10 * 4 ) - ( 8 ); // *DAT block data is always placed before the first Spin method
//Loader patching workspace
var patchWorkspace = new ArrayBuffer(rawLoaderImage.length);
var patchedLoader = new Uint8Array(patchWorkspace, 0);
var bootClkSpeed = new DataView(patchWorkspace, 0, 4); //Booter's clock speed
var bootClkMode = new DataView(patchWorkspace, 4, 1); //Booter's clock mode (1 byte)
var bootChecksum = new DataView(patchWorkspace, 5, 1); //Booter's checksum (1 byte)
var bootClkSel = new DataView(patchWorkspace, InitOffset, 4); //Booter's clock selection bits
var iBitTime = new DataView(patchWorkspace, InitOffset + 4, 4); //Initial Bit Time (baudrate in clock cycles)
var fBitTime = new DataView(patchWorkspace, InitOffset + 8, 4); //Final Bit Time (baudrate in clock cycles)
var bitTime1_5 = new DataView(patchWorkspace, InitOffset + 12, 4); //1.5x Final Bit Time
var failsafe = new DataView(patchWorkspace, InitOffset + 16, 4); //Failsafe Timeout
var endOfPacket = new DataView(patchWorkspace, InitOffset + 20, 4); //EndOfPacket Timeout
var sTime = new DataView(patchWorkspace, InitOffset + 24, 4); //Minimum EEPROM Start/Stop Condition setup/hold time
var sclHighTime = new DataView(patchWorkspace, InitOffset + 28, 4); //Minimum EEPROM SCL high time
var sclLowTime = new DataView(patchWorkspace, InitOffset + 32, 4); //Minimum EEPROM SCL low time
var expectedID = new DataView(patchWorkspace, InitOffset + 36, 4); //First Expected Packet ID; total packet count
//Loader encoding workspace
var encodeWorkspace = new ArrayBuffer(rawLoaderImage.length / 4 * 11); //Reserve up to 11 bytes per encoded long
var encodedLoader = new Uint8Array(encodeWorkspace, 0);
//Maximum number of cycles by which the detection of a start bit could be off (as affected by the Loader code)
const maxRxSenseError = 23;
//Loader VerifyRAM snippet
const verifyRAM = [
0x49, 0xBC, 0xBC, 0xA0, 0x45, 0xBC, 0xBC, 0x84, 0x02, 0xBC, 0xFC, 0x2A, 0x45, 0x8C, 0x14, 0x08,
0x04, 0x8A, 0xD4, 0x80, 0x66, 0xBC, 0xD4, 0xE4, 0x0A, 0xBC, 0xFC, 0x04, 0x04, 0xBC, 0xFC, 0x84,
0x5E, 0x94, 0x3C, 0x08, 0x04, 0xBC, 0xFC, 0x84, 0x5E, 0x94, 0x3C, 0x08, 0x01, 0x8A, 0xFC, 0x84,
0x45, 0xBE, 0xBC, 0x00, 0x5F, 0x8C, 0xBC, 0x80, 0x6E, 0x8A, 0x7C, 0xE8, 0x46, 0xB2, 0xBC, 0xA4,
0x09, 0x00, 0x7C, 0x5C
];
//Loader ProgramVerifyEEPROM snippet
const programVerifyEEPROM = [
0x03, 0x8C, 0xFC, 0x2C, 0x4F, 0xEC, 0xBF, 0x68, 0x82, 0x18, 0xFD, 0x5C, 0x40, 0xBE, 0xFC, 0xA0,
0x45, 0xBA, 0xBC, 0x00, 0xA0, 0x62, 0xFD, 0x5C, 0x79, 0x00, 0x70, 0x5C, 0x01, 0x8A, 0xFC, 0x80,
0x67, 0xBE, 0xFC, 0xE4, 0x8F, 0x3E, 0xFD, 0x5C, 0x49, 0x8A, 0x3C, 0x86, 0x65, 0x00, 0x54, 0x5C,
0x00, 0x8A, 0xFC, 0xA0, 0x49, 0xBE, 0xBC, 0xA0, 0x7D, 0x02, 0xFD, 0x5C, 0xA3, 0x62, 0xFD, 0x5C,
0x45, 0xC0, 0xBC, 0x00, 0x5D, 0xC0, 0x3C, 0x86, 0x79, 0x00, 0x54, 0x5C, 0x01, 0x8A, 0xFC, 0x80,
0x72, 0xBE, 0xFC, 0xE4, 0x01, 0x8C, 0xFC, 0x28, 0x8F, 0x3E, 0xFD, 0x5C, 0x01, 0x8C, 0xFC, 0x28,
0x46, 0xB2, 0xBC, 0xA4, 0x09, 0x00, 0x7C, 0x5C, 0x82, 0x18, 0xFD, 0x5C, 0xA1, 0xBA, 0xFC, 0xA0,
0x8D, 0x62, 0xFD, 0x5C, 0x79, 0x00, 0x70, 0x5C, 0x00, 0x00, 0x7C, 0x5C, 0xFF, 0xBD, 0xFC, 0xA0,
0xA0, 0xBA, 0xFC, 0xA0, 0x8D, 0x62, 0xFD, 0x5C, 0x83, 0xBC, 0xF0, 0xE4, 0x45, 0xBA, 0x8C, 0xA0,
0x08, 0xBA, 0xCC, 0x28, 0xA0, 0x62, 0xCD, 0x5C, 0x45, 0xBA, 0x8C, 0xA0, 0xA0, 0x62, 0xCD, 0x5C,
0x79, 0x00, 0x70, 0x5C, 0x00, 0x00, 0x7C, 0x5C, 0x47, 0x8E, 0x3C, 0x62, 0x90, 0x00, 0x7C, 0x5C,
0x47, 0x8E, 0x3C, 0x66, 0x09, 0xC0, 0xFC, 0xA0, 0x58, 0xB8, 0xBC, 0xA0, 0xF1, 0xB9, 0xBC, 0x80,
0x4F, 0xE8, 0xBF, 0x64, 0x4E, 0xEC, 0xBF, 0x78, 0x56, 0xB8, 0xBC, 0xF8, 0x4F, 0xE8, 0xBF, 0x68,
0xF2, 0x9D, 0x3C, 0x61, 0x56, 0xB8, 0xBC, 0xF8, 0x4E, 0xEC, 0xBB, 0x7C, 0x00, 0xB8, 0xF8, 0xF8,
0xF2, 0x9D, 0x28, 0x61, 0x91, 0xC0, 0xCC, 0xE4, 0x79, 0x00, 0x44, 0x5C, 0x7B, 0x00, 0x48, 0x5C,
0x00, 0x00, 0x68, 0x5C, 0x01, 0xBA, 0xFC, 0x2C, 0x01, 0xBA, 0xFC, 0x68, 0xA4, 0x00, 0x7C, 0x5C,
0xFE, 0xBB, 0xFC, 0xA0, 0x09, 0xC0, 0xFC, 0xA0, 0x58, 0xB8, 0xBC, 0xA0, 0xF1, 0xB9, 0xBC, 0x80,
0x4F, 0xE8, 0xBF, 0x64, 0x00, 0xBB, 0x7C, 0x62, 0x01, 0xBA, 0xFC, 0x34, 0x4E, 0xEC, 0xBF, 0x78,
0x57, 0xB8, 0xBC, 0xF8, 0x4F, 0xE8, 0xBF, 0x68, 0xF2, 0x9D, 0x3C, 0x61, 0x58, 0xB8, 0xBC, 0xF8,
0xA7, 0xC0, 0xFC, 0xE4, 0xFF, 0xBA, 0xFC, 0x60, 0x00, 0x00, 0x7C, 0x5C
];
//Loader LaunchStart snippet
const readyToLaunch = [
0xB8, 0x72, 0xFC, 0x58, 0x66, 0x72, 0xFC, 0x50, 0x09, 0x00, 0x7C, 0x5C, 0x06, 0xBE, 0xFC, 0x04,
0x10, 0xBE, 0x7C, 0x86, 0x00, 0x8E, 0x54, 0x0C, 0x04, 0xBE, 0xFC, 0x00, 0x78, 0xBE, 0xFC, 0x60,
0x50, 0xBE, 0xBC, 0x68, 0x00, 0xBE, 0x7C, 0x0C, 0x40, 0xAE, 0xFC, 0x2C, 0x6E, 0xAE, 0xFC, 0xE4,
0x04, 0xBE, 0xFC, 0x00, 0x00, 0xBE, 0x7C, 0x0C, 0x02, 0x96, 0x7C, 0x0C
];
//Loader LaunchFinal snippet
const launchNow = [0x66, 0x00, 0x7C, 0x5C];
//Executable code snippet array (indexed by loaderType starting at ltVerifyRAM)
var exeSnippet = [verifyRAM, programVerifyEEPROM, readyToLaunch, launchNow];
//Checksum value of Initial Call Frame (0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF); not included in
//Raw Loader Image, but auto-inserted by ROM-resident boot loader, so it's checksum value must be included in image.
const initCallFrameChecksum = 236;
//Maximum needed RAM Checksum timing pulses (per Propeller response window specs)
var timingPulses = new Array(3110).fill(0xF9);
//Packet workspace
var packet = new ArrayBuffer(5120);
if ((loaderType === ltCore) || (loaderType === ltUnEncCore)) {
//Generate patched stream of Micro Boot Loader's core
//Prepare Loader Image with patched clock metrics and host-initialized values in little-endian form (regardless of platform)
patchedLoader.set(rawLoaderImage, 0); //Copy raw loader image for adjustments and processing
bootClkSpeed.setUint32(0, clockSpeed, true); //Set booter's clock speed
bootClkMode.setUint8(0, clockMode); //Set booter's clock mode (1 byte)
bootClkSel.setUint32(0, clockMode & 0x07, true); //Booter's clock selection bits
iBitTime.setUint32(0, Math.round(clockSpeed / initialBaudrate), true); //Initial Bit Time (baudrate in clock cycles)
fBitTime.setUint32(0, Math.round(clockSpeed / finalBaudrate), true); //Final Bit Time (baudrate in clock cycles)
bitTime1_5.setUint32(0, Math.round(((1.5 * clockSpeed) / finalBaudrate) - maxRxSenseError), true); //1.5x Final Bit Time minus maximum start bit sense error
failsafe.setUint32(0, 2 * Math.trunc(clockSpeed / (3 * 4)), true); //Failsafe Timeout (seconds-worth of Loader's Receive loop iterations)
endOfPacket.setUint32(0, Math.round(1000 * clockSpeed / finalBaudrate * 10 / 12), true); //EndOfPacket Timeout (1000 bytes worth of Loader's Receive loop iterations)
sTime.setUint32(0, Math.max(Math.round(clockSpeed * 0.0000006), 14), true); //Minimum EEPROM Start/Stop Condition setup/hold time (400 KHz = 1/0.6 µS); Minimum 14 cycles}
sclHighTime.setUint32(0, Math.max(Math.round(clockSpeed * 0.0000006), 14), true); //Minimum EEPROM SCL high time (400 KHz = 1/0.6 µS); Minimum 14 cycles
sclLowTime.setUint32(0, Math.max(Math.round(clockSpeed * 0.0000013), 14), true); //Minimum EEPROM SCL low time (400 KHz = 1/1.3 µS); Minimum 26 cycles
expectedID.setUint32(0, packetId, true); //First Expected Packet ID; total packet count
//Recalculate and update checksum
bootChecksum.setUint8(0, 0);
var checksum = initCallFrameChecksum;
for (var idx = 0; idx < patchedLoader.byteLength; idx++) {
checksum += patchedLoader[idx];
}
bootChecksum.setUint8(0, 0x100 - (checksum & 0xFF));
if (loaderType === ltCore) {
//[ltCore] Generate Encoded Micro Boot Loader Download Stream from patchedLoader (for wired downloads)
var bCount = 0;
var loaderEncodedSize = 0;
while (bCount < patchedLoader.byteLength * 8) { //For all bits in data stream...
var bitsIn = Math.min(5, patchedLoader.byteLength * 8 - bCount); // Determine number of bits in current unit to translate; usually 5 bits
var bValue = ( (patchedLoader[Math.trunc(bCount / 8)] >>> (bCount % 8)) + // Extract next translation unit (contiguous bits, LSB first; usually 5 bits)
(patchedLoader[Math.trunc(bCount / 8) + 1] << (8 - (bCount % 8))) ) & pwr2m1[bitsIn];
encodedLoader[loaderEncodedSize++] = pDSTx[bValue][bitsIn][0]; // Translate unit to encoded byte
bCount += pDSTx[bValue][bitsIn][1]; // Increment bit index (usually 3, 4, or 5 bits, but can be 1 or 2 at end of stream)
}
//Prepare encoded loader packet
//Contains timing pulses + handshake + encoded Micro Boot Loader application + timing pulses
txData = new ArrayBuffer(txHandshake.length + 11 + loaderEncodedSize + timingPulses.length);
txView = new Uint8Array(txData);
txView.set(txHandshake, 0);
var txLength = txHandshake.length;
var rawSize = rawLoaderImage.length / 4;
for (idx = 0; idx < 11; idx++) {
txView[txLength++] = 0x92 | (idx < 10 ? 0x00 : 0x60) | rawSize & 1 | (rawSize & 2) << 2 | (rawSize & 4) << 4;
rawSize = rawSize >>> 3;
}
txView.set(encodedLoader, txLength);
txView.set(timingPulses, txLength + loaderEncodedSize);
} else /*loaderType === ltCore*/ {
//[ltUnEncCore] Prepare unencoded loader packet (for wireless downloads)
let postStr = str2ab("POST /propeller/load?baud-rate="+initialBaudrate+"&final-baud-rate="+finalBaudrate+"&reset-pin=12&response-size=8&response-timeout=1000 HTTP/1.1\r\nContent-Length: "+patchedLoader.byteLength+"\r\n\r\n");
txData = new ArrayBuffer(postStr.byteLength+patchedLoader.byteLength);
txView = new Uint8Array(txData);
txView.set(new Uint8Array(postStr), 0);
txView.set(patchedLoader, postStr.byteLength);
}
} else {
//Generate special loader's executable packet according to loaderType (> ltCore)
txData = new ArrayBuffer(2 * 4 + exeSnippet[loaderType].length); //Set txData size for header plus executable packet
txView = new Uint8Array(txData);
(new DataView(txData, 0, 4)).setUint32(0, packetId, true); //Store Packet ID (skip over Transmission ID field; it will be filled at time of transmission)
txView.set(exeSnippet[loaderType], 8); //Copy the executable packet code into it
}
}
//Temporary hard-coded Propeller Application for development testing
//Blink P26
/*const bin = [
0x00, 0xB4, 0xC4, 0x04, 0x6F, 0x61, 0x10, 0x00, 0x30, 0x00, 0x38, 0x00, 0x18, 0x00, 0x3C, 0x00,
0x20, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x38, 0x1A, 0x3D, 0xD6, 0x1C, 0x38, 0x1A, 0x3D,
0xD4, 0x47, 0x35, 0xC0, 0x37, 0x00, 0xF6, 0x3F, 0x91, 0xEC, 0x23, 0x04, 0x70, 0x32, 0x00, 0x00
];*/
//Clock Demo PABWX (adjusted for P20 through P27)
/* const bin = [
0x00, 0xB4, 0xC4, 0x04, 0x6F, 0x01, 0x10, 0x00, 0x1C, 0x02, 0x50, 0x02, 0x3E, 0x00, 0x54, 0x02,
0xBC, 0x00, 0x04, 0x01, 0x2E, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00,
0xBC, 0x00, 0x28, 0x00, 0x6B, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x6E, 0x00, 0x6F, 0x00,
0x6E, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x6B, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x3A,
0x4C, 0x4B, 0x40, 0x06, 0x04, 0x01, 0x38, 0x14, 0x38, 0x1B, 0x3E, 0xD6, 0x1C, 0x37, 0x00, 0x43,
0x15, 0x2C, 0x01, 0x8A, 0x24, 0xAA, 0xB4, 0x14, 0x06, 0x04, 0x02, 0x01, 0x05, 0x03, 0x01, 0x37,
0x21, 0x06, 0x04, 0x06, 0x88, 0x24, 0xB6, 0x14, 0x94, 0x34, 0xFC, 0x0A, 0x03, 0x8A, 0x24, 0x18,
0x04, 0x60, 0x32, 0x01, 0x38, 0x0A, 0x06, 0x04, 0x05, 0x38, 0x14, 0x38, 0x1B, 0x3E, 0xD6, 0x1C,
0x38, 0x1B, 0x3D, 0xD4, 0x1C, 0x37, 0x22, 0x08, 0x12, 0x3A, 0x01, 0xC1, 0x38, 0x3F, 0x91, 0xEC,
0x23, 0x36, 0x38, 0x14, 0x38, 0x1B, 0x3E, 0xD4, 0x43, 0x09, 0x6E, 0x37, 0x22, 0x08, 0x12, 0x3A,
0x01, 0xC1, 0x38, 0x3F, 0x91, 0xEC, 0x23, 0x36, 0x38, 0x14, 0x38, 0x1B, 0x3E, 0xD4, 0x42, 0x09,
0x6E, 0x04, 0x52, 0x32, 0x38, 0x14, 0x38, 0x1B, 0x3E, 0xD4, 0x1C, 0x01, 0x38, 0x7D, 0x06, 0x04,
0x05, 0x38, 0x14, 0x38, 0x1B, 0x3E, 0xD4, 0x18, 0x32, 0x00, 0x00, 0x00, 0x50, 0x01, 0x0B, 0x00,
0x5E, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xBD, 0x00, 0x00, 0x00, 0xE1, 0x00, 0x00, 0x00,
0xF7, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x00, 0x00, 0x1D, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x00,
0x32, 0x01, 0x00, 0x00, 0x42, 0x01, 0x00, 0x00, 0x00, 0x1B, 0xB7, 0x00, 0x20, 0x4E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x0D, 0x03, 0x00, 0x7D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x22, 0x2A, 0x32, 0x3A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x73, 0x74, 0x75, 0x76, 0x77, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x64, 0xC5, 0x34, 0xC4, 0x34, 0x38,
0x64, 0xF6, 0x39, 0x01, 0x7D, 0xE4, 0x37, 0x00, 0xD5, 0x38, 0x32, 0x38, 0x04, 0x80, 0x38, 0x18,
0xE8, 0xFF, 0x64, 0x38, 0x18, 0xE8, 0xF0, 0x0A, 0x2A, 0x38, 0x04, 0x80, 0x37, 0x22, 0xE8, 0x64,
0x38, 0x78, 0xE8, 0xEA, 0x35, 0xC0, 0x20, 0x38, 0x04, 0x80, 0x37, 0x22, 0xE8, 0x37, 0x00, 0xE5,
0xD4, 0x38, 0x38, 0x04, 0x80, 0x37, 0x22, 0xE8, 0x37, 0x21, 0xED, 0x35, 0xE4, 0xF3, 0xF4, 0x3F,
0x91, 0xEC, 0x23, 0x64, 0x64, 0x37, 0x00, 0xE5, 0xD4, 0x2C, 0x64, 0x37, 0x22, 0xE8, 0x37, 0x21,
0xED, 0x35, 0xE4, 0xF3, 0xF4, 0x62, 0x80, 0x20, 0x32, 0x64, 0xF1, 0x64, 0x64, 0xF1, 0x36, 0xED,
0xF3, 0xEB, 0xF1, 0x37, 0x21, 0xED, 0x35, 0xE4, 0x38, 0x05, 0xF4, 0xEC, 0x36, 0xED, 0x65, 0x64,
0x38, 0x1A, 0xF9, 0x0A, 0x07, 0x00, 0x64, 0x94, 0x44, 0x05, 0x02, 0x61, 0x32, 0x35, 0xC0, 0x3A,
0x0F, 0x42, 0x40, 0xF6, 0x64, 0xF4, 0x39, 0x0F, 0x58, 0xED, 0x39, 0x01, 0x7D, 0xE4, 0x3F, 0x91,
0xEC, 0x23, 0x32, 0x35, 0xC0, 0x39, 0x03, 0xE8, 0xF6, 0x64, 0xF4, 0x39, 0x0F, 0x5C, 0xED, 0x39,
0x01, 0x7D, 0xE4, 0x3F, 0x91, 0xEC, 0x23, 0x32, 0x35, 0xC0, 0x64, 0xF4, 0x39, 0x0B, 0xC8, 0xED,
0x39, 0x01, 0x7D, 0xE4, 0x3F, 0x91, 0xEC, 0x23, 0x32, 0x3F, 0x91, 0x41, 0x32, 0x35, 0xC0, 0x3A,
0x0F, 0x42, 0x40, 0xF6, 0x64, 0xF4, 0x39, 0x01, 0x7D, 0xE4, 0x42, 0xCC, 0x23, 0x32, 0x35, 0xC0,
0x39, 0x03, 0xE8, 0xF6, 0x64, 0xF4, 0x39, 0x01, 0x7D, 0xE4, 0x42, 0xCC, 0x23, 0x32, 0x35, 0xC0,
0x64, 0xF4, 0x39, 0x01, 0x7D, 0xE4, 0x42, 0xCC, 0x23, 0x32, 0x00, 0x00
];*/
//FloatMathDemoPABWX.spin
/*const bin = [
0x00, 0xB4, 0xC4, 0x04, 0x6F, 0xE7, 0x10, 0x00, 0x98, 0x19, 0x8C, 0x50, 0x4C, 0x00, 0x94, 0x50,
0xC8, 0x00, 0x02, 0x03, 0x3C, 0x00, 0x04, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x98, 0x17, 0xAC, 0x36,
0x08, 0x14, 0xAC, 0x36, 0xDB, 0x0F, 0x49, 0x40, 0xF3, 0x04, 0xB5, 0x3F, 0xF3, 0x04, 0x35, 0x3F,
0xEA, 0xA4, 0xAA, 0xAD, 0xD9, 0x89, 0x9D, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x82, 0xA8, 0x7B, 0x37,
0x6F, 0x2F, 0x17, 0x3F, 0x00, 0x40, 0x9A, 0x44, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x0C, 0x06,
0x02, 0x01, 0x01, 0x87, 0x80, 0xB9, 0x06, 0x02, 0x04, 0x01, 0x37, 0x00, 0x06, 0x02, 0x03, 0x01,
0x37, 0x04, 0x06, 0x04, 0x05, 0x66, 0x18, 0x64, 0xD4, 0x14, 0x0A, 0x14, 0x01, 0x38, 0x0D, 0x06,
0x02, 0x03, 0x01, 0x00, 0x66, 0xAE, 0xD4, 0x14, 0x06, 0x04, 0x01, 0x06, 0x02, 0x04, 0x04, 0x67,
0x01, 0x38, 0x0D, 0x06, 0x02, 0x03, 0x01, 0x37, 0x21, 0x06, 0x04, 0x04, 0x66, 0x18, 0x64, 0xD4,
0x2C, 0x0A, 0x16, 0x01, 0x38, 0x0D, 0x06, 0x02, 0x03, 0x01, 0x00, 0x66, 0xAE, 0xD4, 0x2C, 0x38,
0x6D, 0x06, 0x04, 0x03, 0x06, 0x02, 0x04, 0x04, 0x65, 0x37, 0x22, 0x35, 0x3E, 0xD6, 0x1C, 0x35,
0x65, 0x64, 0x37, 0x22, 0x35, 0x3E, 0xB4, 0x35, 0xC0, 0x38, 0x32, 0xF6, 0x3F, 0x91, 0xEC, 0x23,
0x35, 0x37, 0x27, 0x66, 0x02, 0x6B, 0x04, 0x67, 0x32, 0x46, 0x6C, 0x6F, 0x61, 0x74, 0x20, 0x44,
0x65, 0x6D, 0x6F, 0x2E, 0x2E, 0x2E, 0x0D, 0x00, 0x20, 0x02, 0x09, 0x02, 0x74, 0x00, 0x00, 0x00,
0xEF, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x62, 0x01, 0x00, 0x00, 0x6F, 0x01, 0x04, 0x00,
0xAD, 0x01, 0x00, 0x00, 0xD5, 0x01, 0x00, 0x00, 0xEC, 0x01, 0x00, 0x00, 0x20, 0x02, 0x24, 0x36,
0x98, 0x06, 0x28, 0x36, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x50, 0x0C, 0x4B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x05, 0x6C, 0xBC,
0x0A, 0x0C, 0x0D, 0x0E, 0x6A, 0x6C, 0x6D, 0x6E, 0xBA, 0xBC, 0xBD, 0xBE, 0xCB, 0x44, 0x37, 0x25,
0xEC, 0x39, 0x7F, 0xC0, 0xE8, 0x49, 0x35, 0x41, 0x35, 0x45, 0x48, 0x38, 0x06, 0xE2, 0x44, 0xEC,
0x40, 0x38, 0x0D, 0xF4, 0xEC, 0x44, 0x37, 0x03, 0xF4, 0x40, 0xEC, 0xB9, 0xB4, 0x84, 0x35, 0x38,
0x0D, 0x36, 0xED, 0x46, 0x02, 0x64, 0x35, 0x37, 0x03, 0x36, 0xED, 0x42, 0x02, 0x5A, 0x64, 0x38,
0x38, 0xE8, 0x36, 0xE3, 0x64, 0x37, 0x01, 0xE8, 0x37, 0x01, 0xFC, 0x38, 0x05, 0xE8, 0xEA, 0xC5,
0x34, 0x4F, 0xC7, 0x2C, 0x38, 0x0E, 0x1E, 0xAB, 0xB4, 0x84, 0x5D, 0xC7, 0x64, 0xC9, 0x20, 0x01,
0x4F, 0x06, 0x09, 0x01, 0x01, 0x06, 0x0A, 0x01, 0x01, 0x37, 0x03, 0x38, 0x0D, 0x35, 0x38, 0xD0,
0x48, 0x06, 0x0A, 0x03, 0x01, 0x36, 0x36, 0x38, 0x06, 0x35, 0x06, 0x0A, 0x12, 0x01, 0x35, 0x06,
0x0A, 0x07, 0x01, 0x35, 0x05, 0x03, 0x32, 0x01, 0x06, 0x09, 0x02, 0x01, 0x06, 0x0A, 0x02, 0x32,
0x39, 0x01, 0x61, 0x64, 0x35, 0x0D, 0x1A, 0x36, 0x37, 0x21, 0x0E, 0x1E, 0x37, 0x01, 0x37, 0x22,
0x0E, 0x1E, 0x38, 0x09, 0x0D, 0x23, 0x38, 0x0D, 0x0D, 0x2B, 0x37, 0x04, 0x38, 0x7E, 0x0E, 0x29,
0x0C, 0x01, 0x06, 0x0A, 0x04, 0x35, 0x46, 0x80, 0x41, 0x0C, 0x01, 0x64, 0x06, 0x0A, 0x06, 0x0C,
0x64, 0x37, 0x21, 0xE8, 0xD7, 0x64, 0xC9, 0x20, 0x0C, 0x01, 0x37, 0x04, 0x05, 0x03, 0x40, 0x37,
0x22, 0xE8, 0x0B, 0x75, 0x0C, 0x01, 0x05, 0x08, 0x0C, 0x01, 0x40, 0x38, 0x06, 0xF4, 0x44, 0xE6,
0x38, 0x0D, 0xF4, 0x38, 0x0E, 0xED, 0x67, 0x06, 0x0A, 0x10, 0x01, 0x06, 0x0A, 0x16, 0x42, 0xA6,
0x38, 0x2A, 0xFC, 0x0A, 0x03, 0x01, 0x05, 0x08, 0x0C, 0x32, 0x64, 0x16, 0x08, 0x08, 0x01, 0x66,
0xAE, 0x80, 0x05, 0x03, 0x09, 0x78, 0x32, 0x64, 0x35, 0xF9, 0x0A, 0x07, 0x66, 0x46, 0x01, 0x38,
0x2D, 0x05, 0x03, 0x3B, 0x3B, 0x9A, 0xCA, 0x00, 0x69, 0x38, 0x0A, 0x08, 0x27, 0x64, 0x68, 0xFE,
0x0A, 0x10, 0x01, 0x64, 0x68, 0xF6, 0x38, 0x30, 0xEC, 0x05, 0x03, 0x68, 0x66, 0x57, 0x62, 0x1C,
0x04, 0x0C, 0x60, 0x68, 0x36, 0xFC, 0xF2, 0x0A, 0x05, 0x01, 0x38, 0x30, 0x05, 0x03, 0x38, 0x0A,
0x6A, 0x56, 0x09, 0x59, 0x32, 0x37, 0x02, 0x68, 0xED, 0x37, 0x00, 0xE3, 0x66, 0x43, 0x68, 0x08,
0x1B, 0x01, 0x35, 0x39, 0x01, 0xD0, 0x37, 0x01, 0x66, 0xC1, 0x37, 0x23, 0xE8, 0x38, 0x30, 0x38,
0x39, 0x12, 0x38, 0x41, 0x38, 0x46, 0x12, 0x0F, 0x05, 0x03, 0x09, 0x65, 0x32, 0x37, 0x04, 0x68,
0xED, 0x66, 0x43, 0x68, 0x08, 0x0D, 0x01, 0x36, 0x66, 0xC1, 0x36, 0xE8, 0x38, 0x30, 0xEC, 0x05,
0x03, 0x09, 0x73, 0x32, 0x46, 0xA6, 0x37, 0x03, 0xFC, 0x0A, 0x2A, 0x01, 0x06, 0x0A, 0x16, 0x35,
0x41, 0x48, 0x40, 0x39, 0x03, 0x40, 0xF4, 0xEC, 0x45, 0x44, 0x44, 0x38, 0x34, 0xEC, 0x38, 0xC3,
0x1E, 0x44, 0x39, 0x03, 0x0C, 0xEC, 0x35, 0x38, 0x0D, 0x1A, 0x35, 0x37, 0x03, 0x36, 0xED, 0x42,
0x02, 0x5F, 0x37, 0x23, 0x45, 0x35, 0x41, 0x32, 0x78, 0x04, 0x03, 0x00, 0x60, 0x04, 0x00, 0x00,
0x6E, 0x04, 0x00, 0x00, 0x5C, 0x2A, 0xFE, 0xA0, 0x0A, 0x36, 0xFE, 0xA0, 0x15, 0x2D, 0xBE, 0x5C,
0x02, 0x36, 0xFE, 0xE4, 0x5C, 0x2A, 0xFE, 0xA0, 0x01, 0x6A, 0x7E, 0x61, 0xFC, 0xF6, 0x8D, 0xA0,
0x02, 0x6A, 0x7E, 0x62, 0x2C, 0x37, 0xBE, 0xA0, 0x4A, 0xA4, 0xFC, 0x5C, 0x03, 0x5D, 0x3E, 0xFC,
0x15, 0x2D, 0xBE, 0x5C, 0x09, 0x36, 0xFE, 0xE4, 0xF0, 0xF5, 0x3D, 0x08, 0x20, 0x37, 0xBE, 0xA0,
0x45, 0x92, 0xFC, 0x5C, 0x36, 0x49, 0xBE, 0xA0, 0x39, 0x39, 0xBE, 0xA0, 0x3B, 0x45, 0xBE, 0xA0,
0x01, 0xF0, 0xE9, 0x6C, 0x2B, 0xF0, 0x69, 0xEC, 0x4A, 0xA4, 0xFC, 0x5C, 0x1E, 0xFF, 0xBF, 0xA0,
0x80, 0x4B, 0xBE, 0x6C, 0x00, 0x4A, 0x7E, 0xFC, 0x38, 0x37, 0xBE, 0xA0, 0x21, 0xFF, 0xBF, 0xA0,
0x24, 0x4B, 0xBE, 0x04, 0xFD, 0x4A, 0xBE, 0x68, 0x06, 0x4A, 0xFE, 0x24, 0x25, 0x4D, 0xBE, 0x08,
0x10, 0x4A, 0xFE, 0x28, 0x25, 0x47, 0xBC, 0x50, 0x02, 0x48, 0xFE, 0x80, 0xFB, 0x4A, 0xBE, 0xA0,
0x80, 0x4B, 0xBE, 0x6C, 0x26, 0x4B, 0x3E, 0xFC, 0x1B, 0x36, 0xFE, 0xE4, 0x23, 0x49, 0xBE, 0x84,
0x1D, 0xFF, 0xBF, 0xA0, 0xFB, 0x4A, 0xBE, 0xA0, 0x80, 0x4B, 0xBE, 0x6C, 0x00, 0x4A, 0x7E, 0xFC,
0x13, 0x44, 0xFE, 0xE4, 0xFF, 0xFA, 0xBD, 0x20, 0x27, 0xFB, 0xBD, 0x81, 0xFF, 0xFA, 0xBD, 0x24,
0x12, 0x00, 0x4C, 0x5C, 0x23, 0x49, 0xBE, 0x80, 0x12, 0x38, 0xFE, 0xE4, 0x01, 0xF0, 0xE9, 0x6E,
0x01, 0x6A, 0x7E, 0x61, 0x1F, 0x37, 0xBE, 0xA0, 0x01, 0x36, 0xD2, 0x80, 0x45, 0x92, 0xFC, 0x5C,
0xF0, 0xF3, 0x15, 0x08, 0x4A, 0xA4, 0xE4, 0x5C, 0x29, 0xFF, 0xA7, 0xA0, 0x03, 0x5D, 0x26, 0xFC,
0xFC, 0xF6, 0xA5, 0x6C, 0x53, 0xB6, 0xFC, 0x5C, 0x04, 0xAF, 0xFC, 0x50, 0x05, 0xB3, 0xFC, 0x50,
0x55, 0xB6, 0xFC, 0x5C, 0x53, 0xB6, 0xFC, 0x5C, 0x2A, 0xFF, 0x97, 0xA0, 0x03, 0x5D, 0x16, 0xFC,
0x08, 0x00, 0x68, 0x5C, 0x04, 0x00, 0x7C, 0x5C, 0x4A, 0xA4, 0xFC, 0x5C, 0x80, 0x4B, 0xBE, 0x6C,
0x00, 0x4A, 0x7E, 0xFC, 0x45, 0x36, 0xFE, 0xE4, 0x00, 0x00, 0x7C, 0x5C, 0x01, 0x6A, 0x7E, 0x61,
0xFC, 0xF6, 0xB1, 0x6C, 0x30, 0xFF, 0xBF, 0xA0, 0xFB, 0x4A, 0xBE, 0xA0, 0x2E, 0x4B, 0xBE, 0x6C,