V4 updates for beta testing (#147)
* change simulator svg
* change radio image
* Remove google fonts cdn
* change color of 'advanced' button
* font fix
* font fix 2
* display fix
* change fullsceen simulator bg
* Continuous servo
* handle continuous state
* adding shims
* update rendering for continuous servos
* fixing sim
* fix sig
* typo
* fix sim
* bump pxt
* bump pxt
* rerun travis
* Input blocks revision
- add Button and Pin event types
- merge onPinPressed & onPinReleased in new onPinEvent function
- create new onButtonEvent function
* update input blocks in docs and tests
* remove device_pin_release block
* Hide DAL.x behind Enum
* bring back deprecated blocks, but hide them
* shims and locales files
* fix input.input. typing
* remove buildpr
* bump V3
* update simulator aspect ratio
* add Loudness Block
* revoke loudness block
* Adds soundLevel
To be replaced by pxt-common-packages when DAL is updated.
* Remove P0 & P3 from AnalogPin
* Fix Sound and replace AnalogPin.P0
* remove approved extensions
* V4 Updates from remote Repo
* locales
* add storage functions
* fix storage functions
* fix int/float values
* decrease decimal precision
* reorder blocks
* Update BLE Settings and Storage Blocks
* Fetch MicroBit changes up to v4.0.18
* Update timing for LED Matrix usage
* use 32kb ram (mini v2)
* resize gatt table
* Revert "use 32kb ram (mini v2)"
This reverts commit 4b15592f0f
.
* fix missleading indentation
* add support for 32kb and 16kb ram
* only MIT extensions in preferredRepos
* remove extensions without MIT License file
* add updated extensions
* add extensions with MIT license
Co-authored-by: Juri <gitkraken@juriwolf.de>
Co-authored-by: Juri <info@juriwolf.de>
This commit is contained in:
@ -12,7 +12,7 @@ export function renderUsbPairDialog(firmwareUrl?: string, failedOnce?: boolean):
|
||||
<div className="column">
|
||||
<div className="ui">
|
||||
<div className="image">
|
||||
<img alt={lf("Comic connecting micro:bit to computer")} className="ui medium rounded image" src="./static/download/connect.png" />
|
||||
<img alt={lf("Comic connecting calliope mini to computer")} className="ui medium rounded image" src="./static/download/connect.png" />
|
||||
</div>
|
||||
<div className="content">
|
||||
<div className="description">
|
||||
@ -27,7 +27,7 @@ export function renderUsbPairDialog(firmwareUrl?: string, failedOnce?: boolean):
|
||||
<div className="column">
|
||||
<div className="ui">
|
||||
<div className="image">
|
||||
<img alt={lf("Comic of successful micro:bit connection")} className="ui medium rounded image" src="./static/download/pair.png" />
|
||||
<img alt={lf("Comic of successful calliope mini connection")} className="ui medium rounded image" src="./static/download/pair.png" />
|
||||
</div>
|
||||
<div className="content">
|
||||
<div className="description">
|
||||
@ -51,7 +51,7 @@ export function renderUsbPairDialog(firmwareUrl?: string, failedOnce?: boolean):
|
||||
<div className="ui header inverted">{lf("Update Firmware")}</div>
|
||||
<strong className="ui small">{lf("You must have version 0249 or above of the firmware")}</strong>
|
||||
<div className="image">
|
||||
<img alt={lf("Comic rainbow updating micro:bit firmware")} className="ui image" src="./static/download/firmware.png" />
|
||||
<img alt={lf("Comic rainbow updating calliope mini firmware")} className="ui image" src="./static/download/firmware.png" />
|
||||
</div>
|
||||
<a className="ui button" role="button" href={firmwareUrl} target="_blank">{lf("Check Firmware")}</a>
|
||||
</div>
|
||||
@ -73,7 +73,7 @@ export function renderBrowserDownloadInstructions(): JSX.Element {
|
||||
<div className="column">
|
||||
<div className="ui">
|
||||
<div className="image">
|
||||
<img alt={lf("Comic connecting micro:bit to computer")} className="ui medium rounded image" src="./static/download/connect.png" />
|
||||
<img alt={lf("Comic connecting calliope mini to computer")} className="ui medium rounded image" src="./static/download/connect.png" />
|
||||
</div>
|
||||
<div className="content">
|
||||
<div className="description">
|
||||
@ -88,7 +88,7 @@ export function renderBrowserDownloadInstructions(): JSX.Element {
|
||||
<div className="column">
|
||||
<div className="ui">
|
||||
<div className="image">
|
||||
<img alt={lf("Comic moving hex file to micro:bit")} className="ui medium rounded image" src="./static/download/transfer.png" />
|
||||
<img alt={lf("Comic moving hex file to calliope mini")} className="ui medium rounded image" src="./static/download/transfer.png" />
|
||||
</div>
|
||||
<div className="content">
|
||||
<div className="description">
|
||||
|
@ -9,7 +9,7 @@ import * as flash from "./flash";
|
||||
import * as patch from "./patch";
|
||||
|
||||
pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
|
||||
pxt.debug('loading microbit target extensions...')
|
||||
pxt.debug('loading calliope mini target extensions...')
|
||||
|
||||
const manyAny = Math as any;
|
||||
if (!manyAny.imul)
|
||||
@ -24,35 +24,22 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
|
||||
};
|
||||
|
||||
const res: pxt.editor.ExtensionResult = {
|
||||
hexFileImporters: [{
|
||||
id: "blockly",
|
||||
canImport: data => data.meta.cloudId == "microbit.co.uk" && data.meta.editor == "blockly",
|
||||
importAsync: (project, data) => {
|
||||
pxt.tickEvent('import.legacyblocks.redirect');
|
||||
return dialogs.cantImportAsync(project);
|
||||
}
|
||||
}, {
|
||||
id: "td",
|
||||
canImport: data => data.meta.cloudId == "microbit.co.uk" && data.meta.editor == "touchdevelop",
|
||||
importAsync: (project, data) => {
|
||||
pxt.tickEvent('import.legacytd.redirect');
|
||||
return dialogs.cantImportAsync(project);
|
||||
}
|
||||
}]
|
||||
hexFileImporters: []
|
||||
};
|
||||
|
||||
pxt.usb.setFilters([{
|
||||
vendorId: 0x1366,
|
||||
productId: 0x1025
|
||||
},
|
||||
{
|
||||
vendorId: 0x0D28,
|
||||
productId: 0x0204
|
||||
productId: 0x0204,
|
||||
classCode: 0xff,
|
||||
subclassCode: 0x03 // the ctrl pipe endpoint
|
||||
}, {
|
||||
vendorId: 0x0D28,
|
||||
productId: 0x0204,
|
||||
classCode: 0xff,
|
||||
subclassCode: 0x00 // the custom CMSIS2 endpoint
|
||||
}])
|
||||
|
||||
res.mkPacketIOWrapper = flash.mkDAPLinkPacketIOWrapper;
|
||||
res.blocklyPatch = patch.patchBlocks;
|
||||
res.renderBrowserDownloadInstructions = dialogs.renderBrowserDownloadInstructions;
|
||||
res.renderUsbPairDialog = dialogs.renderUsbPairDialog;
|
||||
return Promise.resolve<pxt.editor.ExtensionResult>(res);
|
||||
}
|
||||
|
750
editor/flash.ts
750
editor/flash.ts
@ -1,9 +1,11 @@
|
||||
const imul = (Math as any).imul;
|
||||
const timeoutMessage = "timeout"
|
||||
const membase = 0x20000000
|
||||
const loadAddr = membase
|
||||
const dataAddr = 0x20002000
|
||||
const stackAddr = 0x20001000
|
||||
const timeoutMessage = "timeout";
|
||||
const membase = 0x20000000;
|
||||
const loadAddr = membase;
|
||||
const dataAddr = 0x20002000;
|
||||
const stackAddr = 0x20001000;
|
||||
const FULL_FLASH_TIMEOUT = 100000; // 100s
|
||||
const PARTIAL_FLASH_TIMEOUT = 60000; // 60s
|
||||
|
||||
const flashPageBIN = new Uint32Array([
|
||||
0xbe00be00, // bkpt - LR is set to this
|
||||
@ -35,7 +37,7 @@ function log(msg: string) {
|
||||
let ts = ("00000" + now).slice(-5)
|
||||
pxt.debug(`dap ${ts}: ${msg}`)
|
||||
}
|
||||
|
||||
const logV = /webusbdbg=1/.test(window.location.href) ? log : (msg: string) => { }
|
||||
|
||||
function murmur3_core(data: Uint8Array) {
|
||||
let h0 = 0x2F9BE6CC;
|
||||
@ -57,23 +59,41 @@ function murmur3_core(data: Uint8Array) {
|
||||
return [h0, h1]
|
||||
}
|
||||
|
||||
function bufferConcat(a: Uint8Array, b: Uint8Array) {
|
||||
const r = new Uint8Array(a.length + b.length)
|
||||
r.set(a, 0)
|
||||
r.set(b, a.length)
|
||||
return r
|
||||
}
|
||||
|
||||
class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
familyID: number;
|
||||
private dap: DapJS.DAP;
|
||||
private cortexM: DapJS.CortexM
|
||||
private cmsisdap: any;
|
||||
private flashing = false;
|
||||
private flashAborted = false;
|
||||
private readSerialId = 0;
|
||||
private pbuf = new pxt.U.PromiseBuffer<Uint8Array>();
|
||||
private pageSize = 1024;
|
||||
private numPages = 256;
|
||||
private binName = pxtc.BINARY_HEX;
|
||||
private usesCODAL = false;
|
||||
private forceFullFlash = /webusbfullflash=1/.test(window.location.href);
|
||||
private get useJACDAC() {
|
||||
return this.usesCODAL;
|
||||
}
|
||||
|
||||
onSerial = (buf: Uint8Array, isStderr: boolean) => { };
|
||||
onCustomEvent = (type: string, payload: Uint8Array) => { };
|
||||
|
||||
constructor(public readonly io: pxt.packetio.PacketIO) {
|
||||
this.familyID = 0x1366; // this is the microbit vendor id, not quite UF2 family id
|
||||
this.io.onDeviceConnectionChanged = (connect) =>
|
||||
this.familyID = 0x0D28; // this is the microbit vendor id, not quite UF2 family id
|
||||
this.io.onDeviceConnectionChanged = (connect) => {
|
||||
log(`device connection changed`);
|
||||
this.disconnectAsync()
|
||||
.then(() => connect && this.reconnectAsync());
|
||||
}
|
||||
|
||||
this.io.onData = buf => {
|
||||
// console.log("RD: " + pxt.Util.toHex(buf))
|
||||
this.pbuf.push(buf);
|
||||
@ -82,96 +102,199 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
this.allocDAP();
|
||||
}
|
||||
|
||||
icon = "usb";
|
||||
icon = "xicon microbit";
|
||||
|
||||
private pendingSerial: Uint8Array
|
||||
private lastPendingSerial: number
|
||||
|
||||
private processSerialLine(line: Uint8Array) {
|
||||
if (this.onSerial) {
|
||||
try {
|
||||
// catch encoding bugs
|
||||
this.onSerial(line, false)
|
||||
}
|
||||
catch (err) {
|
||||
log(`serial decoding error: ${err.message}`);
|
||||
pxt.tickEvent("hid.flash.serial.decode.error");
|
||||
console.error({ err, line })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async readSerial(): Promise<number> {
|
||||
let buf = await this.dapCmdNums(0x83)
|
||||
const len = buf[1]
|
||||
// concat received data with previous data
|
||||
if (len) {
|
||||
buf = buf.slice(2, 2 + len)
|
||||
if (this.pendingSerial) buf = bufferConcat(this.pendingSerial, buf)
|
||||
let ptr = 0
|
||||
let beg = 0
|
||||
while (ptr < buf.length) {
|
||||
if (buf[ptr] == 10 || buf[ptr] == 13) {
|
||||
ptr++;
|
||||
// eat \r\n
|
||||
while (ptr < buf.length && (buf[ptr] == 10 || buf[ptr] == 13))
|
||||
ptr++;
|
||||
const line = buf.slice(beg, ptr)
|
||||
if (line.length)
|
||||
this.processSerialLine(line);
|
||||
beg = ptr
|
||||
}
|
||||
else
|
||||
ptr++
|
||||
}
|
||||
buf = buf.slice(beg)
|
||||
this.pendingSerial = buf.length ? buf : null
|
||||
if (this.pendingSerial) {
|
||||
this.lastPendingSerial = Date.now()
|
||||
//logV(`pending serial ${this.pendingSerial.length}`)
|
||||
}
|
||||
} else if (this.pendingSerial) {
|
||||
const d = Date.now() - this.lastPendingSerial
|
||||
if (d > 500) {
|
||||
this.processSerialLine(this.pendingSerial)
|
||||
this.pendingSerial = null
|
||||
}
|
||||
}
|
||||
return len
|
||||
}
|
||||
|
||||
private startReadSerial() {
|
||||
log(`start read serial`)
|
||||
const rid = this.readSerialId;
|
||||
const readSerial = () => {
|
||||
if (rid != this.readSerialId) {
|
||||
log(`stopped read serial ${rid}`)
|
||||
return;
|
||||
}
|
||||
if (this.flashing) {
|
||||
setTimeout(readSerial, 500);
|
||||
return;
|
||||
}
|
||||
// done
|
||||
this.cmsisdap.cmdNums(0x83, [])
|
||||
.then((r: number[]) => {
|
||||
const len = r[1]
|
||||
let str = ""
|
||||
for (let i = 2; i < len + 2; ++i) {
|
||||
str += String.fromCharCode(r[i])
|
||||
const startTime = Date.now();
|
||||
log(`start read serial ${rid}`)
|
||||
const readSerialLoop = async () => {
|
||||
try {
|
||||
while (rid === this.readSerialId) {
|
||||
const len = await this.readSerial()
|
||||
const hasData = len > 0
|
||||
//if (hasData)
|
||||
// logV(`serial read ${len} bytes`)
|
||||
await this.jacdacProcess(hasData)
|
||||
}
|
||||
log(`stopped serial reader ${rid}`)
|
||||
} catch (err) {
|
||||
log(`serial error ${rid}: ${err.message}`);
|
||||
console.error(err)
|
||||
if (rid != this.readSerialId) {
|
||||
log(`stopped serial reader ${rid}`)
|
||||
} else {
|
||||
pxt.tickEvent("hid.flash.serial.error");
|
||||
const timeRunning = Date.now() - startTime
|
||||
await this.disconnectAsync(); // force disconnect
|
||||
// if we've been running for a while, try reconnecting
|
||||
if (timeRunning > 1000) {
|
||||
log(`auto-reconnect`)
|
||||
await this.reconnectAsync();
|
||||
}
|
||||
if (str.length > 0) {
|
||||
pxt.U.nextTick(readSerial)
|
||||
if (this.onSerial) {
|
||||
const utf8Str = pxt.U.toUTF8(str);
|
||||
this.onSerial(pxt.U.stringToUint8Array(utf8Str), false)
|
||||
}
|
||||
} else
|
||||
setTimeout(readSerial, 50)
|
||||
}, (err: any) => {
|
||||
log(`read error: ` + err.message);
|
||||
this.disconnectAsync(); // force disconnect
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
readSerial();
|
||||
|
||||
readSerialLoop();
|
||||
}
|
||||
|
||||
private stopSerialAsync() {
|
||||
log(`stopping serial reader`)
|
||||
log(`cancelling serial reader ${this.readSerialId}`)
|
||||
this.readSerialId++;
|
||||
return Promise.delay(200);
|
||||
return pxt.Util.delay(200);
|
||||
}
|
||||
|
||||
onSerial: (buf: Uint8Array, isStderr: boolean) => void;
|
||||
|
||||
private allocDAP() {
|
||||
log(`alloc dap`);
|
||||
const h = this.io;
|
||||
this.dap = new DapJS.DAP({
|
||||
write: writeAsync,
|
||||
write: data => h.sendPacketAsync(new Uint8Array(data)),
|
||||
close: this.disconnectAsync,
|
||||
read: readAsync,
|
||||
read: () => this.recvPacketAsync(),
|
||||
//sendMany: sendMany
|
||||
});
|
||||
this.cmsisdap = (this.dap as any).dap;
|
||||
this.cortexM = new DapJS.CortexM(this.dap);
|
||||
}
|
||||
|
||||
const h = this.io;
|
||||
const pbuf = this.pbuf;
|
||||
function writeAsync(data: ArrayBuffer) {
|
||||
//console.log("WR: " + pxt.Util.toHex(new Uint8Array(data)));
|
||||
return h.sendPacketAsync(new Uint8Array(data));
|
||||
get binName() {
|
||||
return (this.usesCODAL ? "mbcodal-" : "mbdal-") + pxtc.BINARY_HEX;
|
||||
}
|
||||
|
||||
unsupportedParts() {
|
||||
if (!this.usesCODAL) {
|
||||
return ["logotouch", "builtinspeaker", "microphone", "flashlog"]
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async reconnectAsync(): Promise<void> {
|
||||
log(`reconnect`)
|
||||
this.flashAborted = false;
|
||||
|
||||
function stringResponse(buf: Uint8Array) {
|
||||
return pxt.U.uint8ArrayToString(buf.slice(2, 2 + buf[1]))
|
||||
}
|
||||
|
||||
function readAsync() {
|
||||
return pbuf.shiftAsync();
|
||||
await this.stopSerialAsync()
|
||||
|
||||
this.allocDAP(); // clean dap apis
|
||||
|
||||
await this.io.reconnectAsync()
|
||||
|
||||
// before calling into dapjs, we use our dapCmdNums() a few times, which which will make sure the responses
|
||||
// to commends from previous sessions (if any) are flushed
|
||||
const info = await this.dapCmdNums(0x00, 0x04) // info
|
||||
const daplinkVersion = stringResponse(info)
|
||||
log(`daplink version: ${daplinkVersion}`)
|
||||
|
||||
const r = await this.dapCmdNums(0x80)
|
||||
this.usesCODAL = r[2] == 57 && r[3] == 57 && r[5] >= 51;
|
||||
const binVersion = stringResponse(r);
|
||||
log(`bin name: ${this.binName} v:${binVersion}`);
|
||||
|
||||
pxt.tickEvent("hid.flash.connect", { codal: this.usesCODAL ? 1 : 0, daplink: daplinkVersion, bin: binVersion });
|
||||
|
||||
const baud = new Uint8Array(5)
|
||||
baud[0] = 0x82 // set baud
|
||||
pxt.HF2.write32(baud, 1, 115200)
|
||||
await this.dapCmd(baud)
|
||||
// setting the baud rate on serial may reset NRF (depending on daplink version), so delay after
|
||||
await pxt.Util.delay(200);
|
||||
|
||||
// only init after setting baud rate, in case we got reset
|
||||
await this.cortexM.init()
|
||||
|
||||
const res = await this.readWords(0x10000010, 2);
|
||||
this.pageSize = res[0]
|
||||
this.numPages = res[1]
|
||||
log(`page size ${this.pageSize}, num pages ${this.numPages}`);
|
||||
|
||||
await this.checkStateAsync(true);
|
||||
await this.jacdacSetup();
|
||||
|
||||
this.startReadSerial();
|
||||
}
|
||||
|
||||
private async checkStateAsync(resume?: boolean): Promise<void> {
|
||||
const states = ["reset", "lockup", "sleeping", "halted", "running"]
|
||||
try {
|
||||
const state = await this.cortexM.getState();
|
||||
log(`cortex state: ${states[state]}`)
|
||||
if (resume && state == DapJS.CoreState.TARGET_HALTED)
|
||||
await this.cortexM.resume();
|
||||
} catch (e) {
|
||||
log(`cortex state failed`)
|
||||
pxt.tickEvent("hid.checkstate.error")
|
||||
console.debug(e)
|
||||
}
|
||||
}
|
||||
|
||||
reconnectAsync(): Promise<void> {
|
||||
log(`reconnect`)
|
||||
// configure serial at 115200
|
||||
return this.stopSerialAsync()
|
||||
.then(() => this.io.reconnectAsync())
|
||||
.then(() => this.cortexM.init())
|
||||
.then(() => this.cmsisdap.cmdNums(0x80, []))
|
||||
.then(r => {
|
||||
this.binName = (r[2] == 57 && r[3] == 57 && r[5] >= 51 ? "mbcodal-" : "") + pxtc.BINARY_HEX
|
||||
})
|
||||
.then(() => this.cortexM.memory.readBlock(0x10000010, 2, this.pageSize))
|
||||
.then(res => {
|
||||
this.pageSize = pxt.HF2.read32(res, 0)
|
||||
this.numPages = pxt.HF2.read32(res, 4)
|
||||
})
|
||||
.then(() => this.cmsisdap.cmdNums(0x82, [0x00, 0xC2, 0x01, 0x00]))
|
||||
.then(() => this.startReadSerial());
|
||||
private checkAborted() {
|
||||
if (this.flashAborted)
|
||||
throw new Error(lf("Download cancelled"));
|
||||
}
|
||||
|
||||
disconnectAsync() {
|
||||
log(`disconnect`)
|
||||
this.flashAborted = true;
|
||||
return this.stopSerialAsync()
|
||||
.then(() => this.io.disconnectAsync());
|
||||
}
|
||||
@ -180,72 +303,195 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
log("reflash")
|
||||
startTime = 0
|
||||
pxt.tickEvent("hid.flash.start");
|
||||
this.flashAborted = false;
|
||||
this.flashing = true;
|
||||
return (this.io.isConnected() ? Promise.resolve() : this.io.reconnectAsync())
|
||||
.then(() => this.stopSerialAsync())
|
||||
.then(() => this.cortexM.init())
|
||||
.then(() => this.cortexM.reset(true))
|
||||
.then(() => this.cortexM.memory.readBlock(0x10001014, 1, this.pageSize))
|
||||
.then(v => {
|
||||
if ((pxt.HF2.read32(v, 0) & 0xff) != 0) {
|
||||
.then(() => this.checkStateAsync())
|
||||
.then(() => this.readUICR())
|
||||
.then(uicr => {
|
||||
pxt.tickEvent("hid.flash.uicr", { uicr });
|
||||
// shortcut, do a full flash
|
||||
if (uicr != 0 || this.forceFullFlash) {
|
||||
pxt.tickEvent("hid.flash.uicrfail");
|
||||
return this.fullVendorCommandFlashAsync(resp);
|
||||
}
|
||||
return this.quickHidFlashAsync(resp);
|
||||
// check flash checksums
|
||||
return this.computeFlashChecksum(resp)
|
||||
.then(chk => {
|
||||
pxt.tickEvent("hid.flash.checksum", { quick: chk.quick ? 1 : 0, changed: chk.changed ? chk.changed.length : 0 });
|
||||
// let's do a quick flash!
|
||||
if (chk.quick)
|
||||
return this.quickHidFlashAsync(chk.changed);
|
||||
else
|
||||
return this.fullVendorCommandFlashAsync(resp);
|
||||
});
|
||||
})
|
||||
.then(() => this.checkStateAsync(true))
|
||||
.then(() => pxt.tickEvent("hid.flash.success"))
|
||||
.finally(() => { this.flashing = false })
|
||||
.then(() => Promise.delay(100))
|
||||
.then(() => this.disconnectAsync())
|
||||
// don't disconnect here
|
||||
// the micro:bit will automatically disconnect and reconnect
|
||||
// via the webusb events
|
||||
}
|
||||
|
||||
private recvPacketAsync() {
|
||||
if (this.io.recvPacketAsync)
|
||||
return this.io.recvPacketAsync()
|
||||
else
|
||||
return this.pbuf.shiftAsync()
|
||||
}
|
||||
|
||||
private dapCmd(buf: Uint8Array) {
|
||||
return this.io.sendPacketAsync(buf)
|
||||
.then(() => this.recvPacketAsync())
|
||||
.then(resp => {
|
||||
if (resp[0] != buf[0]) {
|
||||
pxt.tickEvent('hid.flash.cmderror', { req: buf[0], resp: resp[0] })
|
||||
const msg = `bad dapCmd response: ${buf[0]} -> ${resp[0]}`
|
||||
|
||||
// in case we got an invalid response, try to get another response, in case the current
|
||||
// response is a left-over from previous communications
|
||||
log(msg + "; retrying")
|
||||
return this.recvPacketAsync()
|
||||
.then(resp => {
|
||||
if (resp[0] == buf[0])
|
||||
return resp
|
||||
throw new Error(msg)
|
||||
}, err => {
|
||||
throw new Error(msg)
|
||||
})
|
||||
}
|
||||
return resp
|
||||
})
|
||||
}
|
||||
|
||||
private dapCmdNums(...nums: number[]) {
|
||||
return this.dapCmd(new Uint8Array(nums))
|
||||
}
|
||||
|
||||
private fullVendorCommandFlashAsync(resp: pxtc.CompileResult): Promise<void> {
|
||||
log("full flash")
|
||||
pxt.tickEvent("hid.flash.full.start");
|
||||
|
||||
const chunkSize = 62;
|
||||
let aborted = false;
|
||||
return Promise.resolve()
|
||||
let sentPages = 0;
|
||||
return pxt.Util.promiseTimeout(
|
||||
FULL_FLASH_TIMEOUT,
|
||||
Promise.resolve()
|
||||
.then(() => this.dapCmdNums(0x8A /* DAPLinkFlash.OPEN */, 1))
|
||||
.then((res) => {
|
||||
log(`daplinkflash open: ${pxt.U.toHex(res)}`)
|
||||
if (res[1] !== 0) {
|
||||
pxt.tickEvent('hid.flash.full.error.open', { res: res[1] })
|
||||
throw new Error(lf("Download failed, please try again"));
|
||||
}
|
||||
const binFile = resp.outfiles[this.binName];
|
||||
log(`bin file ${this.binName} in ${Object.keys(resp.outfiles).join(', ')}, ${binFile?.length || -1}b`)
|
||||
const hexUint8 = pxt.U.stringToUint8Array(binFile);
|
||||
log(`hex ${hexUint8?.byteLength || -1}b, ~${(hexUint8.byteLength / chunkSize) | 0} chunks of ${chunkSize}b`)
|
||||
|
||||
const sendPages = (offset: number = 0): Promise<void> => {
|
||||
const end = Math.min(hexUint8.length, offset + chunkSize);
|
||||
const nextPageData = hexUint8.slice(offset, end);
|
||||
const cmdData = new Uint8Array(2 + nextPageData.length)
|
||||
cmdData[0] = 0x8C /* DAPLinkFlash.WRITE */
|
||||
cmdData[1] = nextPageData.length
|
||||
cmdData.set(nextPageData, 2)
|
||||
if (sentPages % 128 == 0) // reduce logging
|
||||
log(`next page ${sentPages}: [${offset.toString(16)}, ${end.toString(16)}] (${Math.ceil((hexUint8.length - end) / 1000)}kb left)`)
|
||||
return this.dapCmd(cmdData)
|
||||
.then(() => {
|
||||
this.checkAborted()
|
||||
if (end < hexUint8.length) {
|
||||
sentPages++;
|
||||
return sendPages(end);
|
||||
}
|
||||
return Promise.resolve()
|
||||
});
|
||||
}
|
||||
|
||||
return sendPages();
|
||||
})
|
||||
.then(() => {
|
||||
log(`close`)
|
||||
return this.dapCmdNums(0x8B /* DAPLinkFlash.CLOSE */);
|
||||
})
|
||||
.then(res => {
|
||||
log(`daplinkclose: ${pxt.U.toHex(res)}`)
|
||||
return this.dapCmdNums(0x89 /* DAPLinkFlash.RESET */);
|
||||
})
|
||||
.then((res) => {
|
||||
log(`daplinkreset: ${pxt.U.toHex(res)}`)
|
||||
log(`full flash done`);
|
||||
pxt.tickEvent("hid.flash.full.success");
|
||||
}),
|
||||
timeoutMessage
|
||||
).catch((e) => {
|
||||
log(`error: abort`)
|
||||
pxt.tickEvent("hid.flash.full.error");
|
||||
this.flashAborted = true;
|
||||
return this.resetAndThrowAsync(e);
|
||||
});
|
||||
}
|
||||
|
||||
private resetAndThrowAsync(e: any) {
|
||||
log(`reset on error`)
|
||||
pxt.tickEvent("hid.flash.reset");
|
||||
console.debug(e)
|
||||
// reset any pending daplink
|
||||
return this.dapCmdNums(0x89 /* DAPLinkFlash.RESET */)
|
||||
.catch((e2: any) => {
|
||||
// Best effort reset, no-op if there's an error
|
||||
})
|
||||
.then(() => this.cortexM.reset(false))
|
||||
.catch((e2: any) => {
|
||||
// Best effort reset, no-op if there's an error
|
||||
})
|
||||
.then(() => {
|
||||
return this.cmsisdap.cmdNums(0x8A /* DAPLinkFlash.OPEN */, [1]);
|
||||
})
|
||||
.then((res) => {
|
||||
const hexUint8 = pxt.U.stringToUint8Array(resp.outfiles[this.binName]);
|
||||
const hexArray: number[] = Array.prototype.slice.call(hexUint8);
|
||||
|
||||
const sendPages = (offset: number = 0): Promise<void> => {
|
||||
const end = Math.min(hexArray.length, offset + chunkSize);
|
||||
const nextPage = hexArray.slice(offset, end);
|
||||
nextPage.unshift(nextPage.length);
|
||||
return this.cmsisdap.cmdNums(0x8C /* DAPLinkFlash.WRITE */, nextPage)
|
||||
.then(() => {
|
||||
if (!aborted && end < hexArray.length) {
|
||||
return sendPages(end);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return sendPages();
|
||||
})
|
||||
.then((res) => {
|
||||
return this.cmsisdap.cmdNums(0x8B /* DAPLinkFlash.CLOSE */, []);
|
||||
})
|
||||
.timeout(60000, timeoutMessage)
|
||||
.catch((e) => {
|
||||
aborted = true;
|
||||
return this.cmsisdap.cmdNums(0x89 /* DAPLinkFlash.RESET */, [])
|
||||
.catch((e2: any) => {
|
||||
// Best effort reset, no-op if there's an error
|
||||
})
|
||||
.then(() => {
|
||||
return Promise.reject(e);
|
||||
});
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
private quickHidFlashAsync(resp: pxtc.CompileResult): Promise<void> {
|
||||
private readUICR() {
|
||||
return this.readWords(0x10001014, 1)
|
||||
.then(v => {
|
||||
const uicr = v[0] & 0xff;
|
||||
log(`uicr: ${uicr.toString(16)} (${v[0].toString(16)})`);
|
||||
return uicr;
|
||||
});
|
||||
}
|
||||
|
||||
private computeFlashChecksum(resp: pxtc.CompileResult) {
|
||||
const binFile = resp.outfiles[this.binName];
|
||||
if (!binFile)
|
||||
throw new Error(`unable to find ${this.binName} in outfiles ${Object.keys(resp.outfiles).join(', ')}`);
|
||||
|
||||
return this.getFlashChecksumsAsync()
|
||||
.then(checksums => {
|
||||
log(`checksums ${pxt.Util.toHex(checksums)}`);
|
||||
// TODO this is seriously inefficient (130ms on a fast machine)
|
||||
const uf2 = ts.pxtc.UF2.newBlockFile();
|
||||
ts.pxtc.UF2.writeHex(uf2, binFile.split(/\r?\n/));
|
||||
const bytes = pxt.U.stringToUint8Array(ts.pxtc.UF2.serializeFile(uf2));
|
||||
const parsed = ts.pxtc.UF2.parseFile(bytes);
|
||||
|
||||
const aligned = DAPWrapper.pageAlignBlocks(parsed, this.pageSize);
|
||||
const changed = DAPWrapper.onlyChanged(aligned, checksums, this.pageSize);
|
||||
const quick = changed.length < aligned.length / 2;
|
||||
log(`pages: ${aligned.length}, changed ${changed.length}, ${quick ? "quick" : "full"}`);
|
||||
return {
|
||||
quick,
|
||||
changed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private quickHidFlashAsync(changed: ts.pxtc.UF2.Block[]): Promise<void> {
|
||||
log("quick flash")
|
||||
let logV = (msg: string) => { }
|
||||
//let logV = log
|
||||
let aborted = false;
|
||||
pxt.tickEvent("hid.flash.quick.start");
|
||||
|
||||
const runFlash = (b: ts.pxtc.UF2.Block, dataAddr: number) => {
|
||||
const cmd = this.cortexM.prepareCommand();
|
||||
@ -266,45 +512,39 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
return cmd.go()
|
||||
})
|
||||
.then(() => {
|
||||
logV("dbg en")
|
||||
// starts the program
|
||||
logV(`cortex.debug.enable`)
|
||||
return this.cortexM.debug.enable()
|
||||
})
|
||||
}
|
||||
|
||||
let checksums: Uint8Array
|
||||
return this.getFlashChecksumsAsync()
|
||||
.then(buf => {
|
||||
checksums = buf;
|
||||
log("write code");
|
||||
return this.cortexM.memory.writeBlock(loadAddr, flashPageBIN);
|
||||
})
|
||||
.then(() => {
|
||||
log("convert");
|
||||
// TODO this is seriously inefficient (130ms on a fast machine)
|
||||
let uf2 = ts.pxtc.UF2.newBlockFile();
|
||||
ts.pxtc.UF2.writeHex(uf2, resp.outfiles[this.binName].split(/\r?\n/));
|
||||
let bytes = pxt.U.stringToUint8Array(ts.pxtc.UF2.serializeFile(uf2));
|
||||
let parsed = ts.pxtc.UF2.parseFile(bytes);
|
||||
|
||||
let aligned = DAPWrapper.pageAlignBlocks(parsed, this.pageSize);
|
||||
log(`initial: ${aligned.length} pages`);
|
||||
aligned = DAPWrapper.onlyChanged(aligned, checksums, this.pageSize);
|
||||
log(`incremental: ${aligned.length} pages`);
|
||||
|
||||
return Promise.mapSeries(pxt.U.range(aligned.length),
|
||||
return pxt.Util.promiseTimeout(
|
||||
PARTIAL_FLASH_TIMEOUT,
|
||||
Promise.resolve()
|
||||
.then(() => this.cortexM.memory.writeBlock(loadAddr, flashPageBIN))
|
||||
.then(() => pxt.Util.promiseMapAllSeries(pxt.U.range(changed.length),
|
||||
i => {
|
||||
if (aborted) return Promise.resolve();
|
||||
let b = aligned[i];
|
||||
if (b.targetAddr >= 0x10000000)
|
||||
this.checkAborted();
|
||||
let b = changed[i];
|
||||
if (b.targetAddr >= 0x10000000) {
|
||||
log(`target address 0x${b.targetAddr.toString(16)} > 0x10000000`)
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
logV("about to write at 0x" + b.targetAddr.toString(16));
|
||||
|
||||
let writeBl = Promise.resolve();
|
||||
log(`about to write at 0x${b.targetAddr.toString(16)}`);
|
||||
|
||||
let thisAddr = (i & 1) ? dataAddr : dataAddr + this.pageSize;
|
||||
let nextAddr = (i & 1) ? dataAddr + this.pageSize : dataAddr;
|
||||
let writeBl;
|
||||
|
||||
if (i == 0) {
|
||||
let u32data = new Uint32Array(b.data.length / 4);
|
||||
for (let i = 0; i < b.data.length; i += 4)
|
||||
u32data[i >> 2] = pxt.HF2.read32(b.data, i);
|
||||
writeBl = this.cortexM.memory.writeBlock(thisAddr, u32data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (i == 0) {
|
||||
let u32data = new Uint32Array(b.data.length / 4);
|
||||
@ -316,7 +556,7 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
return writeBl
|
||||
.then(() => runFlash(b, thisAddr))
|
||||
.then(() => {
|
||||
let next = aligned[i + 1];
|
||||
let next = changed[i + 1];
|
||||
if (!next)
|
||||
return Promise.resolve();
|
||||
logV("write next");
|
||||
@ -330,18 +570,21 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
.then(() => {
|
||||
logV("done block");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
log("flash done");
|
||||
pxt.tickEvent("hid.flash.done");
|
||||
return this.cortexM.reset(false);
|
||||
});
|
||||
})
|
||||
.timeout(25000, timeoutMessage)
|
||||
.catch((e) => {
|
||||
aborted = true;
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}))
|
||||
.then(() => {
|
||||
log("quick flash done");
|
||||
return this.cortexM.reset(false);
|
||||
})
|
||||
.then(() => {
|
||||
pxt.tickEvent("hid.flash.quick.success");
|
||||
return this.checkStateAsync(true)
|
||||
}),
|
||||
timeoutMessage
|
||||
).catch((e) => {
|
||||
pxt.tickEvent("hid.flash.quick.error");
|
||||
this.flashAborted = true;
|
||||
return this.resetAndThrowAsync(e);
|
||||
});
|
||||
}
|
||||
|
||||
private getFlashChecksumsAsync() {
|
||||
@ -352,6 +595,21 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
.then(() => this.cortexM.memory.readBlock(dataAddr, pages * 2, this.pageSize))
|
||||
}
|
||||
|
||||
private readWords(addr: number, numWords: number) {
|
||||
return this.cortexM.memory.readBlock(addr, numWords, this.pageSize)
|
||||
// assume browser is little-endian
|
||||
.then(u8 => new Uint32Array(u8.buffer))
|
||||
}
|
||||
|
||||
private writeWords(addr: number, buf: Uint32Array) {
|
||||
return this.cortexM.memory.writeBlock(addr, buf)
|
||||
}
|
||||
|
||||
private readBytes(addr: number, numBytes: number) {
|
||||
return this.cortexM.memory.readBlock(addr, (numBytes + 3) >> 2, this.pageSize)
|
||||
.then(u8 => u8.length == numBytes ? u8 : u8.slice(0, numBytes))
|
||||
}
|
||||
|
||||
static onlyChanged(blocks: ts.pxtc.UF2.Block[], checksums: Uint8Array, pageSize: number) {
|
||||
return blocks.filter(b => {
|
||||
let idx = b.targetAddr / pageSize
|
||||
@ -392,6 +650,184 @@ class DAPWrapper implements pxt.packetio.PacketIOWrapper {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
//
|
||||
// jacdac stuff starts here
|
||||
//
|
||||
|
||||
private irqn: number
|
||||
private xchgAddr: number = null
|
||||
private lastXchg: number
|
||||
private currSend: SendItem
|
||||
private sendQ: SendItem[] = []
|
||||
private lastSend: number
|
||||
|
||||
sendCustomEventAsync(type: string, buf: Uint8Array): Promise<void> {
|
||||
if (type == "jacdac") {
|
||||
if (this.xchgAddr == null)
|
||||
return Promise.resolve()
|
||||
if (buf.length & 3) {
|
||||
const tmp = new Uint8Array((buf.length + 3) & ~3)
|
||||
tmp.set(buf)
|
||||
buf = tmp
|
||||
}
|
||||
return new Promise<void>(resolve => {
|
||||
this.sendQ.push({
|
||||
buf,
|
||||
cb: resolve
|
||||
})
|
||||
})
|
||||
}
|
||||
return Promise.reject(new Error("invalid custom event type"))
|
||||
}
|
||||
|
||||
private writeWord(addr: number, val: number) {
|
||||
return this.cortexM.memory.write32(addr, val)
|
||||
}
|
||||
|
||||
private async findJacdacXchgAddr() {
|
||||
const memStart = 0x2000_0000
|
||||
const memStop = memStart + 128 * 1024
|
||||
const checkSize = 1024
|
||||
|
||||
let p0 = 0x20006000
|
||||
let p1 = 0x20006000 + checkSize
|
||||
|
||||
const check = async (addr: number) => {
|
||||
if (addr < memStart)
|
||||
return null
|
||||
if (addr + checkSize > memStop)
|
||||
return null
|
||||
const buf = await this.readWords(addr, checkSize >> 2)
|
||||
for (let i = 0; i < buf.length; ++i) {
|
||||
if (buf[i] == 0x786D444A && buf[i + 1] == 0xB0A6C0E9)
|
||||
return addr + (i << 2)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const a0 = await check(p0)
|
||||
if (a0) return a0
|
||||
const a1 = await check(p1)
|
||||
if (a1) return a1
|
||||
if (a0 === null && a1 === null)
|
||||
return null
|
||||
p0 -= checkSize
|
||||
p1 += checkSize
|
||||
}
|
||||
}
|
||||
|
||||
private async jacdacSetup() {
|
||||
this.xchgAddr = null
|
||||
if (!this.useJACDAC) {
|
||||
log(`jacdac: disabled`)
|
||||
return
|
||||
}
|
||||
await pxt.Util.delay(700); // wait for the program to start and setup memory correctly
|
||||
const xchg = await this.findJacdacXchgAddr()
|
||||
if (xchg == null) {
|
||||
log("jacdac: xchg address not found")
|
||||
pxt.tickEvent("hid.flash.jacdac.error.missingxchg");
|
||||
return
|
||||
}
|
||||
const info = await this.readBytes(xchg, 16)
|
||||
this.irqn = info[8]
|
||||
if (info[12 + 2] != 0xff) {
|
||||
log("jacdac: invalid memory; try power-cycling the micro:bit")
|
||||
pxt.tickEvent("hid.flash.jacdac.error.invalidmemory");
|
||||
console.debug({ info, xchg })
|
||||
return
|
||||
}
|
||||
this.xchgAddr = xchg
|
||||
// clear initial lock
|
||||
await this.writeWord(xchg + 12, 0)
|
||||
log(`jacdac: exchange address 0x${xchg.toString(16)}; irqn=${this.irqn}`)
|
||||
pxt.tickEvent("hid.flash.jacdac.connected");
|
||||
}
|
||||
|
||||
private async triggerIRQ(irqn: number) {
|
||||
const addr = 0xE000E200 + (irqn >> 5) * 4
|
||||
await this.writeWord(addr, 1 << (irqn & 31))
|
||||
}
|
||||
|
||||
private async jacdacProcess(hadSerial: boolean) {
|
||||
if (this.xchgAddr == null) {
|
||||
if (!hadSerial)
|
||||
await this.dapDelay(5000)
|
||||
return
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
if (this.lastXchg && now - this.lastXchg > 50) {
|
||||
logV("slow xchg: " + (now - this.lastXchg) + "ms")
|
||||
}
|
||||
this.lastXchg = now
|
||||
|
||||
let numev = 0
|
||||
// TODO only read say 32 bytes first, and more if needed
|
||||
let inp = await this.readBytes(this.xchgAddr + 12, 256)
|
||||
if (inp[2]) {
|
||||
await this.writeWord(this.xchgAddr + 12, 0)
|
||||
await this.triggerIRQ(this.irqn)
|
||||
inp = inp.slice(0, inp[2] + 12)
|
||||
this.onCustomEvent("jacdac", inp)
|
||||
numev++
|
||||
}
|
||||
|
||||
let sendFree = false
|
||||
if (this.currSend) {
|
||||
const send = await this.readBytes(this.xchgAddr + 12 + 256, 4)
|
||||
if (!send[2]) {
|
||||
this.currSend.cb()
|
||||
this.currSend = null
|
||||
sendFree = true
|
||||
numev++
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.currSend && this.sendQ.length) {
|
||||
if (!sendFree) {
|
||||
const send = await this.readBytes(this.xchgAddr + 12 + 256, 4)
|
||||
if (!send[2])
|
||||
sendFree = true
|
||||
}
|
||||
if (sendFree) {
|
||||
this.currSend = this.sendQ.shift()
|
||||
const bbody = this.currSend.buf.slice(4)
|
||||
await this.writeWords(this.xchgAddr + 12 + 256 + 4, new Uint32Array(bbody.buffer))
|
||||
const bhead = this.currSend.buf.slice(0, 4)
|
||||
await this.writeWords(this.xchgAddr + 12 + 256, new Uint32Array(bhead.buffer))
|
||||
await this.triggerIRQ(this.irqn)
|
||||
this.lastSend = Date.now()
|
||||
numev++
|
||||
} else {
|
||||
if (this.lastSend) {
|
||||
const d = Date.now() - this.lastSend
|
||||
if (d > 50) {
|
||||
this.lastSend = 0
|
||||
console.error("failed to send packet fast enough")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numev == 0 && !hadSerial)
|
||||
await this.dapDelay(5000)
|
||||
}
|
||||
|
||||
private dapDelay(micros: number) {
|
||||
if (micros > 0xffff)
|
||||
throw new Error("too large delay")
|
||||
const cmd = new Uint8Array([0x09, 0, 0])
|
||||
pxt.HF2.write16(cmd, 1, micros)
|
||||
return this.dapCmd(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
interface SendItem {
|
||||
buf: Uint8Array
|
||||
cb: () => void
|
||||
}
|
||||
|
||||
export function mkDAPLinkPacketIOWrapper(io: pxt.packetio.PacketIO): pxt.packetio.PacketIOWrapper {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es2017",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
@ -12,6 +12,13 @@
|
||||
"rootDir": ".",
|
||||
"newLine": "LF",
|
||||
"sourceMap": false,
|
||||
"jsx": "react"
|
||||
"jsx": "react",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost",
|
||||
"es2017",
|
||||
"ES2018.Promise"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user