import type { Flasher } from "../interface"; import type { Transport } from "../../transport/interface"; import { EventEmitter } from "events"; import { TimeoutError } from "../../errors"; import { parseIntelHex } from "./intel-hex"; enum Statics { Cmnd_STK_GET_SYNC = 0x30, Cmnd_STK_SET_DEVICE = 0x42, Cmnd_STK_ENTER_PROGMODE = 0x50, Cmnd_STK_LOAD_ADDRESS = 0x55, Cmnd_STK_PROG_PAGE = 0x64, Cmnd_STK_LEAVE_PROGMODE = 0x51, Cmnd_STK_READ_SIGN = 0x75, Cmnd_STK_READ_PAGE = 0x74, Sync_CRC_EOP = 0x20, Resp_STK_INSYNC = 0x14, Resp_STK_NOSYNC = 0x15, Resp_STK_OK = 0x10, } const EVENT_SERIAL_DATA = "serial"; interface FlasherOptions { board?: "uno" | "nano"; verbose?: boolean; timeout?: number; signature?: Buffer; pageSize?: number; deviceOptions?: any; } export class STK500 extends EventEmitter implements Flasher { constructor(private transport: Transport) { super(); } private verbose = false; private log(...args: any[]) { if (this.verbose) { if (typeof window !== "undefined") { console.log( `%c[avrboy]%c[stk500v1]`, "color: #00A86B", "color: #87CEEB", ...args ); } else { console.log(`\x1b[32m[avrboy]`, `\x1b[34mstk500v1`, `\x1b[0m`, ...args); } } } async flash(hex: Buffer, options: FlasherOptions): Promise { hex = parseIntelHex(hex).data; await this.transport.open({ baudRate: 115200, }); this.startSerialRead(); const { timeout = 400, deviceOptions = {}, signature = Buffer.from([0x1e, 0x95, 0x0f]), pageSize = 128, verbose = false, } = options; this.verbose = verbose; try { this.log("flash begin"); const begin = Date.now(); await this.sync(timeout); await this.sync(timeout); await this.sync(timeout); await this.verifySignature(signature, timeout); await this.setDeviceOptions(deviceOptions, timeout); await this.enterProgrammingMode(timeout); await this.upload(hex, pageSize, timeout); await this.verify(hex, pageSize, timeout); await this.exitProgrammingMode(timeout); this.log("flash done, process time:", Date.now() - begin, "ms"); } finally { await this.transport.close(); this.stopSerialRead(); } } private async sync(timeout: number): Promise { const attempts = 3; let tries = 0; while (tries < attempts) { tries = tries + 1; try { this.log("sync tries:", tries); await this.send([Statics.Cmnd_STK_GET_SYNC], timeout); return; } catch (err) { if (tries >= attempts) { throw err; } } } } private async verifySignature(signature: Buffer, timeout: number) { this.log("verifySignature signature:", signature.toString("hex")); await this.send([Statics.Cmnd_STK_READ_SIGN], timeout, [ Statics.Resp_STK_INSYNC, ...signature, Statics.Resp_STK_OK, ]); } private async setDeviceOptions(options: any, timeout: number) { this.log("setDeviceOptions options:", options); await this.send( [ Statics.Cmnd_STK_SET_DEVICE, options.devicecode || 0, options.revision || 0, options.progtype || 0, options.parmode || 0, options.polling || 0, options.selftimed || 0, options.lockbytes || 0, options.fusebytes || 0, options.flashpollval1 || 0, options.flashpollval2 || 0, options.eeprompollval1 || 0, options.eeprompollval2 || 0, options.pagesizehigh || 0, options.pagesizelow || 0, options.eepromsizehigh || 0, options.eepromsizelow || 0, options.flashsize4 || 0, options.flashsize3 || 0, options.flashsize2 || 0, options.flashsize1 || 0, ], timeout ); } private async enterProgrammingMode(timeout: number) { this.log("enterProgrammingMode"); await this.send([Statics.Cmnd_STK_ENTER_PROGMODE], timeout); } private async upload(hex: Buffer, pageSize: number, timeout: number) { this.log("upload"); var pageaddr = 0; while (pageaddr < hex.length) { const useaddr = pageaddr >> 1; await this.loadAddress(useaddr, timeout); const writeBytes = hex.slice( pageaddr, hex.length > pageSize ? pageaddr + pageSize : hex.length - 1 ); await this.loadPage(writeBytes, timeout); pageaddr = pageaddr + writeBytes.length; await new Promise((resolve) => setTimeout(resolve, 4)); } } private async verify(hex: Buffer, pageSize: number, timeout: number) { this.log("verify"); var pageaddr = 0; while (pageaddr < hex.length) { const useaddr = pageaddr >> 1; await this.loadAddress(useaddr, timeout); const writeBytes = hex.slice( pageaddr, hex.length > pageSize ? pageaddr + pageSize : hex.length - 1 ); await this.verifyPage(writeBytes, pageSize, timeout); pageaddr = pageaddr + writeBytes.length; await new Promise((resolve) => setTimeout(resolve, 4)); } } private async loadAddress(useaddr: number, timeout: number) { this.log("loadAddress useaddr:", useaddr); var addrLow = useaddr & 0xff; var addrHigh = (useaddr >> 8) & 0xff; await this.send( [Statics.Cmnd_STK_LOAD_ADDRESS, addrLow, addrHigh], timeout ); } private async loadPage(writeBytes: Buffer, timeout: number) { this.log("loadPage"); var bytesLow = writeBytes.length & 0xff; var bytesHigh = writeBytes.length >> 8; await this.send( [Statics.Cmnd_STK_PROG_PAGE, bytesHigh, bytesLow, 0x46, ...writeBytes], timeout ); } private async verifyPage( writeBytes: Buffer, pageSize: number, timeout: number ) { this.log("verifyPage"); const size = writeBytes.length >= pageSize ? pageSize : writeBytes.length; await this.send( [Statics.Cmnd_STK_READ_PAGE, (size >> 8) & 0xff, size & 0xff, 0x46], timeout, [Statics.Resp_STK_INSYNC, ...writeBytes, Statics.Resp_STK_OK] ); } private async exitProgrammingMode(timeout: number) { this.log("exitProgrammingMode"); await this.send([Statics.Cmnd_STK_LEAVE_PROGMODE], timeout); } private async send( cmd: Buffer | number[], timeout: number, matchResponse?: Buffer | number[] ) { if (!matchResponse) { matchResponse = Buffer.from([ Statics.Resp_STK_INSYNC, Statics.Resp_STK_OK, ]); } cmd = Buffer.from(cmd); matchResponse = Buffer.from(matchResponse); try { // this.log("send:", cmd.toString("hex")); await this.transport.write(Buffer.from([...cmd, Statics.Sync_CRC_EOP])); } catch (e) { throw new Error( "Sending " + cmd.toString("hex") + ": " + (e as Error).message ); } if (matchResponse) { const resp = await this.waitSerialData(matchResponse, timeout); if (!resp.equals(matchResponse)) { const respHex = resp.toString("hex"); const matchHex = matchResponse.toString("hex"); throw new Error( `${cmd} response mismatch:\n matchHex:${matchHex}\n respHex:${respHex}` ); } } } private async waitSerialData(matchResponse: Buffer, timeout?: number) { this.log("waitSerialData matchResponse:", matchResponse.toString("hex")); return new Promise((resolve, reject) => { let buffer = Buffer.alloc(0); const handler = (data: Buffer) => { buffer = Buffer.concat([buffer, data]); if (buffer.length >= matchResponse.length) { if (buffer.includes(matchResponse)) { resolve(buffer); this.removeListener(EVENT_SERIAL_DATA, handler); } } }; this.on(EVENT_SERIAL_DATA, handler); if (timeout) { setTimeout(() => { reject(new TimeoutError("waitResponse Timeout")); this.removeListener("serial", resolve); }, timeout); } }); } private serialPolling = false; private async startSerialRead() { if (this.serialPolling) { await this.stopSerialRead(); } this.serialPolling = true; while (this.serialPolling) { const data = await this.transport.read(); this.emit(EVENT_SERIAL_DATA, data); if (typeof window !== "undefined") { await new Promise((resolve) => requestAnimationFrame(resolve)); } else { await new Promise((resolve) => setTimeout(resolve, 1)); } } } private async stopSerialRead() { this.serialPolling = false; } }