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:
Amerlander
2022-03-22 17:36:19 +01:00
committed by GitHub
parent d0a85fd0d2
commit 3e0c9b43a2
115 changed files with 16788 additions and 7690 deletions

View File

@ -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">

View File

@ -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);
}

View File

@ -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 {

View File

@ -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"
]
}
}