UNPKG

stk500-esm

Version:

A modern, ESM-compatible, TypeScript implementation of the STK500v1 protocol for programming Arduino boards directly from Node.js or the browser.

348 lines (347 loc) 12.7 kB
import Constants from "./lib/constants.js"; import sendCommand from "./lib/sendCommand.js"; import parseIntelHex from "./lib/intelHexParser.js"; class STK500 { /** * Creates an instance of the STK500 programmer. * @param stream - The read/write stream for communication with the device. * @param board - The board configuration. * @param opts - Additional options for the STK500 instance. */ constructor(stream, board, opts = {}) { this.stream = stream; this.board = board; this.log = opts.quiet ? () => { /* logging disabled */ } : typeof window === "object" ? console.log.bind(window) : console.log; } /** * Attempts to synchronize communication with the device. * @param attempts - The number of synchronization attempts. * @returns A promise that resolves with the synchronization response data. * @throws Error if synchronization fails after all attempts. */ async sync(attempts) { this.log("sync"); let tries = 1; const opt = { cmd: [Constants.Cmnd_STK_GET_SYNC], responseData: Constants.OK_RESPONSE, timeout: this.board.timeout, }; while (tries <= attempts) { try { const data = await sendCommand(this.stream, opt); this.log("sync complete", data, tries); return data; } catch (err) { this.log(err); this.log("failed attempt again", tries); tries++; } } throw new Error("Sync failed after " + attempts + " attempts"); } /** * Verifies the device signature. * @returns A promise that resolves with the verification response data. * @throws Error if the signature verification fails. */ async verifySignature() { this.log("verify signature"); const match = new Uint8Array([ Constants.Resp_STK_INSYNC, ...this.board.signature, Constants.Resp_STK_OK, ]); const opt = { cmd: [Constants.Cmnd_STK_READ_SIGN], responseLength: match.length, timeout: this.board.timeout, }; try { const data = await sendCommand(this.stream, opt); this.log("confirm signature", data, Array.from(data) .map((b) => b.toString(16)) .join("")); return data; } catch (err) { this.log("confirm signature", err, "no data"); throw err; } } /** * Retrieves the device signature. * @returns A promise that resolves with the device signature data. */ async getSignature() { this.log("get signature"); const opt = { cmd: [Constants.Cmnd_STK_READ_SIGN], responseLength: 5, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("getSignature", data); return data; } /** * Sets device-specific options. * @param options - An object containing device-specific options. * @returns A promise that resolves when the options are set. */ async setOptions(options) { this.log("set device"); const opt = { cmd: [ Constants.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, ], responseData: Constants.OK_RESPONSE, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("setOptions", data); } /** * Enters programming mode on the device. * @returns A promise that resolves with the response data. */ async enterProgrammingMode() { this.log("send enter programming mode"); const opt = { cmd: [Constants.Cmnd_STK_ENTER_PROGMODE], responseData: Constants.OK_RESPONSE, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("sent enter programming mode", data); return data; } /** * Loads a memory address for subsequent operations. * @param useaddr - The address to load. * @returns A promise that resolves with the response data. */ async loadAddress(useaddr) { this.log("load address"); const addr_low = useaddr & 0xff; const addr_high = (useaddr >> 8) & 0xff; const opt = { cmd: [Constants.Cmnd_STK_LOAD_ADDRESS, addr_low, addr_high], responseData: Constants.OK_RESPONSE, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("loaded address", data); return data; } /** * Loads a page of data to be programmed. * @param writeBytes - The data to be programmed. * @returns A promise that resolves with the response data. */ async loadPage(writeBytes) { this.log("load page"); const bytes_low = writeBytes.length & 0xff; const bytes_high = writeBytes.length >> 8; const cmd = new Uint8Array([ Constants.Cmnd_STK_PROG_PAGE, bytes_high, bytes_low, 0x46, ...writeBytes, Constants.Sync_CRC_EOP, ]); const opt = { cmd: cmd, responseData: Constants.OK_RESPONSE, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("loaded page", data); return data; } /** * Uploads the provided hex data to the device. * @param hexData - The hex data to be uploaded, as a string or Uint8Array. * @param progressCallback - Optional callback to report upload progress. * @returns A promise that resolves when the upload is complete. */ async upload(hexData, progressCallback) { this.log("program"); const { data: hex } = parseIntelHex(hexData); let pageaddr = 0; let writeBytes; let useaddr; while (pageaddr < hex.length) { this.log("program page"); try { useaddr = this.board.use8BitAddresses ? pageaddr : pageaddr >> 1; await this.loadAddress(useaddr); writeBytes = hex.subarray(pageaddr, hex.length > this.board.pageSize ? pageaddr + this.board.pageSize : hex.length); await this.loadPage(writeBytes); this.log("programmed page"); pageaddr = pageaddr + writeBytes.length; await new Promise((resolve) => setTimeout(resolve, 4)); this.log("page done"); if (progressCallback) { progressCallback((pageaddr / hex.length) * 100); } } catch (error) { this.log("Error in page programming"); throw error; } } this.log("upload done"); } /** * Exits programming mode on the device. * @returns A promise that resolves with the response data. */ async exitProgrammingMode() { this.log("send leave programming mode"); const opt = { cmd: [Constants.Cmnd_STK_LEAVE_PROGMODE], responseData: Constants.OK_RESPONSE, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("sent leave programming mode", data); return data; } /** * Verifies the uploaded data against the provided hex data. * @param hexData - The hex data to verify against, as a string or Uint8Array. * @param progressCallback - Optional callback to report verification progress. * @returns A promise that resolves when verification is complete. */ async verify(hexData, progressCallback) { this.log("verify"); const { data: hex } = parseIntelHex(hexData); let pageaddr = 0; let writeBytes; let useaddr; while (pageaddr < hex.length) { this.log("verify page"); try { useaddr = this.board.use8BitAddresses ? pageaddr : pageaddr >> 1; await this.loadAddress(useaddr); writeBytes = hex.subarray(pageaddr, hex.length > this.board.pageSize ? pageaddr + this.board.pageSize : hex.length); await this.verifyPage(writeBytes); this.log("verified page"); pageaddr = pageaddr + writeBytes.length; await new Promise((resolve) => setTimeout(resolve, 4)); this.log("verify done"); if (progressCallback) { progressCallback((pageaddr / hex.length) * 100); } } catch (error) { this.log("Error in page verification"); throw error; } } this.log("verify done"); } /** * Verifies a single page of data. * @param writeBytes - The data to be verified. * @returns A promise that resolves with the verification response data. */ async verifyPage(writeBytes) { this.log("verify page"); const match = new Uint8Array([ Constants.Resp_STK_INSYNC, ...writeBytes, Constants.Resp_STK_OK, ]); const size = writeBytes.length >= this.board.pageSize ? this.board.pageSize : writeBytes.length; const opt = { cmd: [ Constants.Cmnd_STK_READ_PAGE, (size >> 8) & 0xff, size & 0xff, 0x46, ], responseLength: match.length, timeout: this.board.timeout, }; const data = await sendCommand(this.stream, opt); this.log("confirm page", data, Array.from(data) .map((b) => b.toString(16)) .join("")); return data; } /** * Performs the complete bootloading process for a device. * @param hexData - The hex data to be uploaded, as a string or Uint8Array. * @param progressCallback - Optional callback to report progress. * @returns A promise that resolves when the bootloading process is complete. */ async bootload(hexData, progressCallback) { const parameters = { pagesizehigh: (this.board.pageSize << 8) & 0xff, pagesizelow: this.board.pageSize & 0xff, }; const updateProgress = (status, percentage) => { if (progressCallback) { progressCallback(status, percentage); } }; updateProgress("Syncing", 0); await this.sync(3); await this.sync(3); await this.sync(3); updateProgress("Verifying signature", 10); await this.verifySignature(); updateProgress("Setting options", 20); await this.setOptions(parameters); updateProgress("Entering programming mode", 30); await this.enterProgrammingMode(); updateProgress("Uploading", 40); await this.upload(hexData, (uploadPercentage) => { updateProgress("Uploading", 40 + uploadPercentage * 0.3); }); updateProgress("Verifying", 70); await this.verify(hexData, (verifyPercentage) => { updateProgress("Verifying", 70 + verifyPercentage * 0.25); }); updateProgress("Exiting programming mode", 95); await this.exitProgrammingMode(); updateProgress("Complete", 100); } } export default STK500;