mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-19 06:15:16 +03:00
move flashing protocols to separate folder
This commit is contained in:
parent
8d67811644
commit
93ab8dfba3
3 changed files with 2 additions and 2 deletions
664
js/protocols/stm32.js
Normal file
664
js/protocols/stm32.js
Normal file
|
@ -0,0 +1,664 @@
|
|||
/*
|
||||
STM32 F103 serial bus seems to properly initialize with quite a huge auto-baud range
|
||||
From 921600 down to 1200, i don't recommend getting any lower then that
|
||||
Official "specs" are from 115200 to 1200
|
||||
*/
|
||||
|
||||
var STM32_protocol = function() {
|
||||
this.hex; // ref
|
||||
this.verify_hex;
|
||||
|
||||
this.receive_buffer;
|
||||
|
||||
this.bytes_to_read = 0; // ref
|
||||
this.read_callback; // ref
|
||||
|
||||
this.upload_time_start;
|
||||
this.upload_process_alive;
|
||||
|
||||
this.status = {
|
||||
ACK: 0x79, // y
|
||||
NACK: 0x1F
|
||||
};
|
||||
|
||||
this.command = {
|
||||
get: 0x00, // Gets the version and the allowed commands supported by the current version of the bootloader
|
||||
get_ver_r_protect_s: 0x01, // Gets the bootloader version and the Read Protection status of the Flash memory
|
||||
get_ID: 0x02, // Gets the chip ID
|
||||
read_memory: 0x11, // Reads up to 256 bytes of memory starting from an address specified by the application
|
||||
go: 0x21, // Jumps to user application code located in the internal Flash memory or in SRAM
|
||||
write_memory: 0x31, // Writes up to 256 bytes to the RAM or Flash memory starting from an address specified by the application
|
||||
erase: 0x43, // Erases from one to all the Flash memory pages
|
||||
extended_erase: 0x44, // Erases from one to all the Flash memory pages using two byte addressing mode (v3.0+ usart).
|
||||
write_protect: 0x63, // Enables the write protection for some sectors
|
||||
write_unprotect: 0x73, // Disables the write protection for all Flash memory sectors
|
||||
readout_protect: 0x82, // Enables the read protection
|
||||
readout_unprotect: 0x92 // Disables the read protection
|
||||
};
|
||||
|
||||
// Erase (x043) and Extended Erase (0x44) are exclusive. A device may support either the Erase command or the Extended Erase command but not both.
|
||||
|
||||
this.available_flash_size = 0;
|
||||
this.page_size = 0;
|
||||
};
|
||||
|
||||
// no input parameters
|
||||
STM32_protocol.prototype.connect = function(hex) {
|
||||
var self = this;
|
||||
self.hex = hex;
|
||||
|
||||
var selected_port = String($('div#port-picker #port').val());
|
||||
var baud = parseInt($('div#port-picker #baud').val());
|
||||
|
||||
if (selected_port != '0') {
|
||||
// popular choices - 921600, 460800, 256000, 230400, 153600, 128000, 115200, 57600, 38400, 28800, 19200
|
||||
var flashing_bitrate;
|
||||
|
||||
switch (GUI.operating_system) {
|
||||
case 'Windows':
|
||||
case 'MacOS':
|
||||
case 'ChromeOS':
|
||||
case 'Linux':
|
||||
case 'UNIX':
|
||||
flashing_bitrate = 921600;
|
||||
break;
|
||||
|
||||
default:
|
||||
flashing_bitrate = 115200;
|
||||
}
|
||||
|
||||
if (!$('input.updating').is(':checked')) {
|
||||
serial.connect(selected_port, {bitrate: baud}, function(openInfo) {
|
||||
if (openInfo) {
|
||||
console.log('Sending ascii "R" to reboot');
|
||||
|
||||
// we are connected, disabling connect button in the UI
|
||||
GUI.connect_lock = true;
|
||||
|
||||
var bufferOut = new ArrayBuffer(1);
|
||||
var bufferView = new Uint8Array(bufferOut);
|
||||
|
||||
bufferView[0] = 0x52;
|
||||
|
||||
serial.send(bufferOut, function() {
|
||||
serial.disconnect(function(result) {
|
||||
if (result) {
|
||||
serial.connect(selected_port, {bitrate: flashing_bitrate, parityBit: 'even', stopBits: 'one'}, function(openInfo) {
|
||||
if (openInfo) {
|
||||
self.initialize();
|
||||
} else {
|
||||
GUI.log('<span style="color: red">Failed</span> to open serial port');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
GUI.connect_lock = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
GUI.log('<span style="color: red">Failed</span> to open serial port');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
serial.connect(selected_port, {bitrate: flashing_bitrate, parityBit: 'even', stopBits: 'one'}, function(openInfo) {
|
||||
if (openInfo) {
|
||||
// we are connected, disabling connect button in the UI
|
||||
GUI.connect_lock = true;
|
||||
|
||||
self.initialize();
|
||||
} else {
|
||||
GUI.log('<span style="color: red">Failed</span> to open serial port');
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('Please select valid serial port');
|
||||
GUI.log('<span style="color: red">Please select valid serial port</span>');
|
||||
}
|
||||
};
|
||||
|
||||
// initialize certain variables and start timers that oversee the communication
|
||||
STM32_protocol.prototype.initialize = function() {
|
||||
var self = this;
|
||||
|
||||
// reset and set some variables before we start
|
||||
self.receive_buffer = [];
|
||||
self.verify_hex = [];
|
||||
|
||||
self.upload_time_start = microtime();
|
||||
self.upload_process_alive = false;
|
||||
|
||||
// reset progress bar to initial state
|
||||
self.progress_bar_e = $('.progress');
|
||||
self.progress_bar_e.val(0);
|
||||
self.progress_bar_e.removeClass('valid invalid');
|
||||
|
||||
serial.onReceive.addListener(function(info) {
|
||||
self.read(info);
|
||||
});
|
||||
|
||||
GUI.interval_add('STM32_timeout', function() {
|
||||
if (self.upload_process_alive) { // process is running
|
||||
self.upload_process_alive = false;
|
||||
} else {
|
||||
console.log('STM32 - timed out, programming failed ...');
|
||||
GUI.log('STM32 - timed out, programming: <strong style="color: red">FAILED</strong>');
|
||||
googleAnalytics.sendEvent('Flashing', 'Programming', 'timeout');
|
||||
|
||||
// protocol got stuck, clear timer and disconnect
|
||||
GUI.interval_remove('STM32_timeout');
|
||||
|
||||
// exit
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
self.upload_procedure(1);
|
||||
};
|
||||
|
||||
// no input parameters
|
||||
// this method should be executed every 1 ms via interval timer
|
||||
STM32_protocol.prototype.read = function(readInfo) {
|
||||
// routine that fills the buffer
|
||||
var data = new Uint8Array(readInfo.data);
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
this.receive_buffer.push(data[i]);
|
||||
}
|
||||
|
||||
// routine that fetches data from buffer if statement is true
|
||||
if (this.receive_buffer.length >= this.bytes_to_read && this.bytes_to_read != 0) {
|
||||
var data = this.receive_buffer.slice(0, this.bytes_to_read); // bytes requested
|
||||
this.receive_buffer.splice(0, this.bytes_to_read); // remove read bytes
|
||||
|
||||
this.bytes_to_read = 0; // reset trigger
|
||||
|
||||
this.read_callback(data);
|
||||
}
|
||||
};
|
||||
|
||||
// we should always try to consume all "proper" available data while using retrieve
|
||||
STM32_protocol.prototype.retrieve = function(n_bytes, callback) {
|
||||
if (this.receive_buffer.length >= n_bytes) {
|
||||
// data that we need are there, process immediately
|
||||
var data = this.receive_buffer.slice(0, n_bytes);
|
||||
this.receive_buffer.splice(0, n_bytes); // remove read bytes
|
||||
|
||||
callback(data);
|
||||
} else {
|
||||
// still waiting for data, add callback
|
||||
this.bytes_to_read = n_bytes;
|
||||
this.read_callback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
// Array = array of bytes that will be send over serial
|
||||
// bytes_to_read = received bytes necessary to trigger read_callback
|
||||
// callback = function that will be executed after received bytes = bytes_to_read
|
||||
STM32_protocol.prototype.send = function(Array, bytes_to_read, callback) {
|
||||
// flip flag
|
||||
this.upload_process_alive = true;
|
||||
|
||||
var bufferOut = new ArrayBuffer(Array.length);
|
||||
var bufferView = new Uint8Array(bufferOut);
|
||||
|
||||
// set Array values inside bufferView (alternative to for loop)
|
||||
bufferView.set(Array);
|
||||
|
||||
// update references
|
||||
this.bytes_to_read = bytes_to_read;
|
||||
this.read_callback = callback;
|
||||
|
||||
// empty receive buffer before next command is out
|
||||
this.receive_buffer = [];
|
||||
|
||||
// send over the actual data
|
||||
serial.send(bufferOut, function(writeInfo) {});
|
||||
};
|
||||
|
||||
// val = single byte to be verified
|
||||
// data = response of n bytes from mcu (array)
|
||||
// result = true/false
|
||||
STM32_protocol.prototype.verify_response = function(val, data) {
|
||||
if (val != data[0]) {
|
||||
console.log('STM32 Communication failed, wrong response, expected: ' + val + ' received: ' + data[0]);
|
||||
GUI.log('STM32 Communication <span style="color: red">failed</span>, wrong response, expected: ' + val + ' received: ' + data[0]);
|
||||
|
||||
// disconnect
|
||||
this.upload_procedure(99);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// input = 16 bit value
|
||||
// result = true/false
|
||||
STM32_protocol.prototype.verify_chip_signature = function(signature) {
|
||||
switch (signature) {
|
||||
case 0x412: // not tested
|
||||
console.log('Chip recognized as F1 Low-density');
|
||||
break;
|
||||
case 0x410:
|
||||
console.log('Chip recognized as F1 Medium-density');
|
||||
this.available_flash_size = 131072;
|
||||
this.page_size = 1024;
|
||||
break;
|
||||
case 0x414: // not tested
|
||||
console.log('Chip recognized as F1 High-density');
|
||||
break;
|
||||
case 0x418: // not tested
|
||||
console.log('Chip recognized as F1 Connectivity line');
|
||||
break;
|
||||
case 0x420: // not tested
|
||||
console.log('Chip recognized as F1 Medium-density value line');
|
||||
break;
|
||||
case 0x428: // not tested
|
||||
console.log('Chip recognized as F1 High-density value line');
|
||||
break;
|
||||
case 0x430: // not tested
|
||||
console.log('Chip recognized as F1 XL-density value line');
|
||||
break;
|
||||
case 0x416: // not tested
|
||||
console.log('Chip recognized as L1 Medium-density ultralow power');
|
||||
break;
|
||||
case 0x436: // not tested
|
||||
console.log('Chip recognized as L1 High-density ultralow power');
|
||||
break;
|
||||
case 0x427: // not tested
|
||||
console.log('Chip recognized as L1 Medium-density plus ultralow power');
|
||||
break;
|
||||
case 0x411: // not tested
|
||||
console.log('Chip recognized as F2 STM32F2xxxx');
|
||||
break;
|
||||
case 0x440: // not tested
|
||||
console.log('Chip recognized as F0 STM32F051xx');
|
||||
break;
|
||||
case 0x444: // not tested
|
||||
console.log('Chip recognized as F0 STM32F050xx');
|
||||
break;
|
||||
case 0x413: // not tested
|
||||
console.log('Chip recognized as F4 STM32F40xxx/41xxx');
|
||||
break;
|
||||
case 0x419: // not tested
|
||||
console.log('Chip recognized as F4 STM32F427xx/437xx, STM32F429xx/439xx');
|
||||
break;
|
||||
case 0x432: // not tested
|
||||
console.log('Chip recognized as F3 STM32F37xxx, STM32F38xxx');
|
||||
break;
|
||||
case 0x422: // not tested
|
||||
console.log('Chip recognized as F3 STM32F30xxx, STM32F31xxx');
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.available_flash_size > 0) {
|
||||
if (this.hex.bytes_total < this.available_flash_size) {
|
||||
return true;
|
||||
} else {
|
||||
console.log('Supplied hex is bigger then flash available on the chip, HEX: ' + this.hex.bytes_total + ' bytes, limit = ' + this.available_flash_size + ' bytes');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Chip NOT recognized: ' + signature);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// first_array = usually hex_to_flash array
|
||||
// second_array = usually verify_hex array
|
||||
// result = true/false
|
||||
STM32_protocol.prototype.verify_flash = function(first_array, second_array) {
|
||||
for (var i = 0; i < first_array.length; i++) {
|
||||
if (first_array[i] != second_array[i]) {
|
||||
console.log('Verification failed on byte: ' + i + ' expected: 0x' + first_array[i].toString(16) + ' received: 0x' + second_array[i].toString(16));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Verification successful, matching: ' + first_array.length + ' bytes');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// step = value depending on current state of upload_procedure
|
||||
STM32_protocol.prototype.upload_procedure = function(step) {
|
||||
var self = this;
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
// initialize serial interface on the MCU side, auto baud rate settings
|
||||
GUI.log('Contacting bootloader ...');
|
||||
|
||||
var send_counter = 0;
|
||||
GUI.interval_add('stm32_initialize_mcu', function() { // 200 ms interval (just in case mcu was already initialized), we need to break the 2 bytes command requirement
|
||||
self.send([0x7F], 1, function(reply) {
|
||||
if (reply[0] == 0x7F || reply[0] == self.status.ACK || reply[0] == self.status.NACK) {
|
||||
GUI.interval_remove('stm32_initialize_mcu');
|
||||
console.log('STM32 - Serial interface initialized on the MCU side');
|
||||
|
||||
// proceed to next step
|
||||
self.upload_procedure(2);
|
||||
} else {
|
||||
GUI.interval_remove('stm32_initialize_mcu');
|
||||
GUI.log('Communication with bootloader <span style="color: red">failed</span>');
|
||||
|
||||
// disconnect
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
|
||||
if (send_counter++ > 3) {
|
||||
// stop retrying, its too late to get any response from MCU
|
||||
console.log('STM32 - no response from bootloader, disconnecting');
|
||||
GUI.log('No reponse from the bootloader, programming: <strong style="color: red">FAILED</strong>');
|
||||
GUI.interval_remove('stm32_initialize_mcu');
|
||||
GUI.interval_remove('STM32_timeout');
|
||||
|
||||
// exit
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
}, 250, true);
|
||||
break;
|
||||
case 2:
|
||||
// get version of the bootloader and supported commands
|
||||
self.send([self.command.get, 0xFF], 2, function(data) { // 0x00 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, data)) {
|
||||
self.retrieve(data[1] + 1 + 1, function(data) { // data[1] = number of bytes that will follow [– 1 except current and ACKs]
|
||||
console.log('STM32 - Bootloader version: ' + (parseInt(data[0].toString(16)) / 10).toFixed(1)); // convert dec to hex, hex to dec and add floating point
|
||||
|
||||
// proceed to next step
|
||||
self.upload_procedure(3);
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
// get ID (device signature)
|
||||
self.send([self.command.get_ID, 0xFD], 2, function(data) { // 0x01 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, data)) {
|
||||
self.retrieve(data[1] + 1 + 1, function(data) { // data[1] = number of bytes that will follow [– 1 (N = 1 for STM32), except for current byte and ACKs]
|
||||
var signature = (data[0] << 8) | data[1];
|
||||
console.log('STM32 - Signature: 0x' + signature.toString(16)); // signature in hex representation
|
||||
|
||||
if (self.verify_chip_signature(signature)) {
|
||||
// proceed to next step
|
||||
self.upload_procedure(4);
|
||||
} else {
|
||||
// disconnect
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 4:
|
||||
// erase memory
|
||||
GUI.log('Erasing ...');
|
||||
|
||||
if (!$('input.erase_chip').is(':checked')) {
|
||||
console.log('Executing local erase (only needed pages)');
|
||||
|
||||
self.send([self.command.erase, 0xBC], 1, function(reply) { // 0x43 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
// the bootloader receives one byte that contains N, the number of pages to be erased – 1
|
||||
var max_address = self.hex.data[self.hex.data.length - 1].address + self.hex.data[self.hex.data.length - 1].bytes - 0x8000000;
|
||||
var erase_pages_n = Math.ceil(max_address / self.page_size);
|
||||
|
||||
var buff = [];
|
||||
buff.push(erase_pages_n - 1);
|
||||
var checksum = buff[0];
|
||||
for (var i = 0; i < erase_pages_n; i++) {
|
||||
buff.push(i);
|
||||
checksum ^= i;
|
||||
}
|
||||
buff.push(checksum);
|
||||
|
||||
self.send(buff, 1, function(reply) {
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
console.log('Erasing: done');
|
||||
// proceed to next step
|
||||
self.upload_procedure(5);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Executing global chip erase');
|
||||
|
||||
self.send([self.command.erase, 0xBC], 1, function(reply) { // 0x43 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
self.send([0xFF, 0x00], 1, function(reply) {
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
console.log('Erasing: done');
|
||||
// proceed to next step
|
||||
self.upload_procedure(5);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
// upload
|
||||
console.log('Writing data ...');
|
||||
GUI.log('Flashing ...');
|
||||
|
||||
var blocks = self.hex.data.length - 1;
|
||||
var flashing_block = 0;
|
||||
var address = self.hex.data[flashing_block].address;
|
||||
|
||||
var bytes_flashed = 0;
|
||||
var bytes_flashed_total = 0; // used for progress bar
|
||||
|
||||
function write() {
|
||||
if (bytes_flashed < self.hex.data[flashing_block].bytes) {
|
||||
var bytes_to_write = ((bytes_flashed + 256) <= self.hex.data[flashing_block].bytes) ? 256 : (self.hex.data[flashing_block].bytes - bytes_flashed);
|
||||
|
||||
// console.log('STM32 - Writing to: 0x' + address.toString(16) + ', ' + bytes_to_write + ' bytes');
|
||||
|
||||
self.send([self.command.write_memory, 0xCE], 1, function(reply) { // 0x31 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
// address needs to be transmitted as 32 bit integer, we need to bit shift each byte out and then calculate address checksum
|
||||
var address_arr = [(address >> 24), (address >> 16), (address >> 8), address];
|
||||
var address_checksum = address_arr[0] ^ address_arr[1] ^ address_arr[2] ^ address_arr[3];
|
||||
|
||||
self.send([address_arr[0], address_arr[1], address_arr[2], address_arr[3], address_checksum], 1, function(reply) { // write start address + checksum
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
var array_out = new Array(bytes_to_write + 2); // 2 byte overhead [N, ...., checksum]
|
||||
array_out[0] = bytes_to_write - 1; // number of bytes to be written (to write 128 bytes, N must be 127, to write 256 bytes, N must be 255)
|
||||
|
||||
var checksum = array_out[0];
|
||||
for (var i = 0; i < bytes_to_write; i++) {
|
||||
array_out[i + 1] = self.hex.data[flashing_block].data[bytes_flashed]; // + 1 because of the first byte offset
|
||||
checksum ^= self.hex.data[flashing_block].data[bytes_flashed];
|
||||
|
||||
bytes_flashed++;
|
||||
}
|
||||
array_out[array_out.length - 1] = checksum; // checksum (last byte in the array_out array)
|
||||
|
||||
address += bytes_to_write;
|
||||
bytes_flashed_total += bytes_to_write;
|
||||
|
||||
self.send(array_out, 1, function(reply) {
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
// update progress bar
|
||||
self.progress_bar_e.val(bytes_flashed_total / (self.hex.bytes_total * 2) * 100);
|
||||
|
||||
// flash another page
|
||||
write();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// move to another block
|
||||
if (flashing_block < blocks) {
|
||||
flashing_block++;
|
||||
|
||||
address = self.hex.data[flashing_block].address;
|
||||
bytes_flashed = 0;
|
||||
|
||||
write();
|
||||
} else {
|
||||
// all blocks flashed
|
||||
console.log('Writing: done');
|
||||
|
||||
// proceed to next step
|
||||
self.upload_procedure(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start writing
|
||||
write();
|
||||
break;
|
||||
case 6:
|
||||
// verify
|
||||
console.log('Verifying data ...');
|
||||
GUI.log('Verifying ...');
|
||||
|
||||
var blocks = self.hex.data.length - 1;
|
||||
var reading_block = 0;
|
||||
var address = self.hex.data[reading_block].address;
|
||||
|
||||
var bytes_verified = 0;
|
||||
var bytes_verified_total = 0; // used for progress bar
|
||||
|
||||
// initialize arrays
|
||||
for (var i = 0; i <= blocks; i++) {
|
||||
self.verify_hex.push([]);
|
||||
}
|
||||
|
||||
function reading() {
|
||||
if (bytes_verified < self.hex.data[reading_block].bytes) {
|
||||
var bytes_to_read = ((bytes_verified + 256) <= self.hex.data[reading_block].bytes) ? 256 : (self.hex.data[reading_block].bytes - bytes_verified);
|
||||
|
||||
// console.log('STM32 - Reading from: 0x' + address.toString(16) + ', ' + bytes_to_read + ' bytes');
|
||||
|
||||
self.send([self.command.read_memory, 0xEE], 1, function(reply) { // 0x11 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
var address_arr = [(address >> 24), (address >> 16), (address >> 8), address];
|
||||
var address_checksum = address_arr[0] ^ address_arr[1] ^ address_arr[2] ^ address_arr[3];
|
||||
|
||||
self.send([address_arr[0], address_arr[1], address_arr[2], address_arr[3], address_checksum], 1, function(reply) { // read start address + checksum
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
var bytes_to_read_n = bytes_to_read - 1;
|
||||
|
||||
self.send([bytes_to_read_n, (~bytes_to_read_n) & 0xFF], 1, function(reply) { // bytes to be read + checksum XOR(complement of bytes_to_read_n)
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
self.retrieve(bytes_to_read, function(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
self.verify_hex[reading_block].push(data[i]);
|
||||
}
|
||||
|
||||
address += bytes_to_read;
|
||||
bytes_verified += bytes_to_read;
|
||||
bytes_verified_total += bytes_to_read;
|
||||
|
||||
// update progress bar
|
||||
self.progress_bar_e.val((self.hex.bytes_total + bytes_verified_total) / (self.hex.bytes_total * 2) * 100);
|
||||
|
||||
// verify another page
|
||||
reading();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// move to another block
|
||||
if (reading_block < blocks) {
|
||||
reading_block++;
|
||||
|
||||
address = self.hex.data[reading_block].address;
|
||||
bytes_verified = 0;
|
||||
|
||||
reading();
|
||||
} else {
|
||||
// all blocks read, verify
|
||||
|
||||
var verify = true;
|
||||
for (var i = 0; i <= blocks; i++) {
|
||||
verify = self.verify_flash(self.hex.data[i].data, self.verify_hex[i]);
|
||||
|
||||
if (!verify) break;
|
||||
}
|
||||
|
||||
if (verify) {
|
||||
console.log('Programming: SUCCESSFUL');
|
||||
GUI.log('Programming: <strong style="color: green">SUCCESSFUL</strong>');
|
||||
googleAnalytics.sendEvent('Flashing', 'Programming', 'success');
|
||||
|
||||
// update progress bar
|
||||
self.progress_bar_e.addClass('valid');
|
||||
|
||||
// proceed to next step
|
||||
self.upload_procedure(7);
|
||||
} else {
|
||||
console.log('Programming: FAILED');
|
||||
GUI.log('Programming: <strong style="color: red">FAILED</strong>');
|
||||
googleAnalytics.sendEvent('Flashing', 'Programming', 'fail');
|
||||
|
||||
// update progress bar
|
||||
self.progress_bar_e.addClass('invalid');
|
||||
|
||||
// disconnect
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start reading
|
||||
reading();
|
||||
break;
|
||||
case 7:
|
||||
// go
|
||||
// memory address = 4 bytes, 1st high byte, 4th low byte, 5th byte = checksum XOR(byte 1, byte 2, byte 3, byte 4)
|
||||
console.log('Sending GO command: 0x8000000');
|
||||
|
||||
self.send([self.command.go, 0xDE], 1, function(reply) { // 0x21 ^ 0xFF
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
var gt_address = 0x8000000;
|
||||
var address = [(gt_address >> 24), (gt_address >> 16), (gt_address >> 8), gt_address];
|
||||
var address_checksum = address[0] ^ address[1] ^ address[2] ^ address[3];
|
||||
|
||||
self.send([address[0], address[1], address[2], address[3], address_checksum], 1, function(reply) {
|
||||
if (self.verify_response(self.status.ACK, reply)) {
|
||||
// disconnect
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 99:
|
||||
// disconnect
|
||||
GUI.interval_remove('STM32_timeout'); // stop STM32 timeout timer (everything is finished now)
|
||||
|
||||
console.log('Script finished after: ' + (microtime() - self.upload_time_start).toFixed(4) + ' seconds');
|
||||
|
||||
// close connection
|
||||
serial.disconnect(function(result) {
|
||||
if (result) { // All went as expected
|
||||
} else { // Something went wrong
|
||||
}
|
||||
|
||||
PortUsage.reset();
|
||||
|
||||
// unlocking connect button
|
||||
GUI.connect_lock = false;
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// initialize object
|
||||
var STM32 = new STM32_protocol();
|
475
js/protocols/stm32usbdfu.js
Normal file
475
js/protocols/stm32usbdfu.js
Normal file
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
USB DFU uses:
|
||||
control transfers for communicating
|
||||
recipient is interface
|
||||
request type is class
|
||||
|
||||
Descriptors seems to be broken in current chrome.usb API implementation (writing this while using canary 37.0.2040.0
|
||||
|
||||
General rule to remember is that DFU doesn't like running specific operations while the device isn't in idle state
|
||||
that being said, it seems that certain level of CLRSTATUS is required before running another type of operation for
|
||||
example switching from DNLOAD to UPLOAD, etc, clearning the state so device is in dfuIDLE is highly recommended.
|
||||
*/
|
||||
|
||||
var STM32DFU_protocol = function() {
|
||||
this.hex; // ref
|
||||
this.verify_hex;
|
||||
|
||||
this.handle = null; // connection handle
|
||||
|
||||
this.request = {
|
||||
DETACH: 0x00, // OUT, Requests the device to leave DFU mode and enter the application.
|
||||
DNLOAD: 0x01, // OUT, Requests data transfer from Host to the device in order to load them into device internal Flash. Includes also erase commands
|
||||
UPLOAD: 0x02, // IN, Requests data transfer from device to Host in order to load content of device internal Flash into a Host file.
|
||||
GETSTATUS: 0x03, // IN, Requests device to send status report to the Host (including status resulting from the last request execution and the state the device will enter immediately after this request).
|
||||
CLRSTATUS: 0x04, // OUT, Requests device to clear error status and move to next step
|
||||
GETSTATE: 0x05, // IN, Requests the device to send only the state it will enter immediately after this request.
|
||||
ABORT: 0x06 // OUT, Requests device to exit the current state/operation and enter idle state immediately.
|
||||
};
|
||||
|
||||
this.status = {
|
||||
OK: 0x00, // No error condition is present.
|
||||
errTARGET: 0x01, // File is not targeted for use by this device.
|
||||
errFILE: 0x02, // File is for this device but fails some vendor-specific verification test
|
||||
errWRITE: 0x03, // Device is unable to write memory.
|
||||
errERASE: 0x04, // Memory erase function failed.
|
||||
errCHECK_ERASED: 0x05, // Memory erase check failed.
|
||||
errPROG: 0x06, // Program memory function failed.
|
||||
errVERIFY: 0x07, // Programmed memory failed verification.
|
||||
errADDRESS: 0x08, // Cannot program memory due to received address that is out of range.
|
||||
errNOTDONE: 0x09, // Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet.
|
||||
errFIRMWARE: 0x0A, // Device's firmware is corrupt. It cannot return to run-time (non-DFU) operations.
|
||||
errVENDOR: 0x0B, // iString indicates a vendor-specific error.
|
||||
errUSBR: 0x0C, // Device detected unexpected USB reset signaling.
|
||||
errPOR: 0x0D, // Device detected unexpected power on reset.
|
||||
errUNKNOWN: 0x0E, // Something went wrong, but the device does not know what it was.
|
||||
errSTALLEDPKT: 0x0F // Device stalled an unexpected request.
|
||||
};
|
||||
|
||||
this.state = {
|
||||
appIDLE: 0, // Device is running its normal application.
|
||||
appDETACH: 1, // Device is running its normal application, has received the DFU_DETACH request, and is waiting for a USB reset.
|
||||
dfuIDLE: 2, // Device is operating in the DFU mode and is waiting for requests.
|
||||
dfuDNLOAD_SYNC: 3, // Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS.
|
||||
dfuDNBUSY: 4, // Device is programming a control-write block into its nonvolatile memories.
|
||||
dfuDNLOAD_IDLE: 5, // Device is processing a download operation. Expecting DFU_DNLOAD requests.
|
||||
dfuMANIFEST_SYNC: 6, // Device has received the final block of firmware from the host and is waiting for receipt of DFU_GETSTATUS to begin the Manifestation phase; or device has completed the Manifestation phase and is waiting for receipt of DFU_GETSTATUS.
|
||||
dfuMANIFEST: 7, // Device is in the Manifestation phase. (Not all devices will be able to respond to DFU_GETSTATUS when in this state.)
|
||||
dfuMANIFEST_WAIT_RESET: 8, // Device has programmed its memories and is waiting for a USB reset or a power on reset. (Devices that must enter this state clear bitManifestationTolerant to 0.)
|
||||
dfuUPLOAD_IDLE: 9, // The device is processing an upload operation. Expecting DFU_UPLOAD requests.
|
||||
dfuERROR: 10 // An error has occurred. Awaiting the DFU_CLRSTATUS request.
|
||||
};
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.connect = function(hex) {
|
||||
var self = this;
|
||||
self.hex = hex;
|
||||
|
||||
// reset and set some variables before we start
|
||||
self.upload_time_start = microtime();
|
||||
self.verify_hex = [];
|
||||
|
||||
// reset progress bar to initial state
|
||||
self.progress_bar_e = $('.progress');
|
||||
self.progress_bar_e.val(0);
|
||||
self.progress_bar_e.removeClass('valid invalid');
|
||||
|
||||
chrome.usb.getDevices(usbDevices.STM32DFU, function(result) {
|
||||
if (result.length) {
|
||||
console.log('USB DFU detected with ID: ' + result[0].device);
|
||||
|
||||
self.openDevice(result[0]);
|
||||
} else {
|
||||
// TODO: throw some error
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.openDevice = function(device) {
|
||||
var self = this;
|
||||
|
||||
chrome.usb.openDevice(device, function(handle) {
|
||||
self.handle = handle;
|
||||
|
||||
console.log('Device opened with Handle ID: ' + handle.handle);
|
||||
self.claimInterface(0);
|
||||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.closeDevice = function() {
|
||||
var self = this;
|
||||
|
||||
chrome.usb.closeDevice(this.handle, function closed() {
|
||||
console.log('Device closed with Handle ID: ' + self.handle.handle);
|
||||
|
||||
self.handle = null;
|
||||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.claimInterface = function(interfaceNumber) {
|
||||
var self = this;
|
||||
|
||||
chrome.usb.claimInterface(this.handle, interfaceNumber, function claimed() {
|
||||
console.log('Claimed interface: ' + interfaceNumber);
|
||||
|
||||
self.upload_procedure(1);
|
||||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.releaseInterface = function(interfaceNumber) {
|
||||
var self = this;
|
||||
|
||||
chrome.usb.releaseInterface(this.handle, interfaceNumber, function released() {
|
||||
console.log('Released interface: ' + interfaceNumber);
|
||||
|
||||
self.closeDevice();
|
||||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.resetDevice = function(callback) {
|
||||
chrome.usb.resetDevice(this.handle, function(result) {
|
||||
console.log('Reset Device: ' + result);
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.controlTransfer = function(direction, request, value, interface, length, data, callback) {
|
||||
if (direction == 'in') {
|
||||
// data is ignored
|
||||
chrome.usb.controlTransfer(this.handle, {
|
||||
'direction': 'in',
|
||||
'recipient': 'interface',
|
||||
'requestType': 'class',
|
||||
'request': request,
|
||||
'value': value,
|
||||
'index': interface,
|
||||
'length': length
|
||||
}, function(result) {
|
||||
if (result.resultCode) console.log(result.resultCode);
|
||||
|
||||
var buf = new Uint8Array(result.data);
|
||||
callback(buf, result.resultCode);
|
||||
});
|
||||
} else {
|
||||
// length is ignored
|
||||
if (data) {
|
||||
var arrayBuf = new ArrayBuffer(data.length);
|
||||
var arrayBufView = new Uint8Array(arrayBuf);
|
||||
arrayBufView.set(data);
|
||||
} else {
|
||||
var arrayBuf = new ArrayBuffer(0);
|
||||
}
|
||||
|
||||
chrome.usb.controlTransfer(this.handle, {
|
||||
'direction': 'out',
|
||||
'recipient': 'interface',
|
||||
'requestType': 'class',
|
||||
'request': request,
|
||||
'value': value,
|
||||
'index': interface,
|
||||
'data': arrayBuf
|
||||
}, function(result) {
|
||||
if (result.resultCode) console.log(result.resultCode);
|
||||
|
||||
callback(result);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// routine calling DFU_CLRSTATUS until device is in dfuIDLE state
|
||||
STM32DFU_protocol.prototype.clearStatus = function(callback) {
|
||||
var self = this;
|
||||
|
||||
function check_status() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuIDLE) {
|
||||
callback(data);
|
||||
} else {
|
||||
var delay = data[1] | (data[2] << 8) | (data[3] << 16);
|
||||
|
||||
setTimeout(clear_status, delay);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clear_status() {
|
||||
self.controlTransfer('out', self.request.CLRSTATUS, 0, 0, 0, 0, check_status);
|
||||
}
|
||||
|
||||
check_status();
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.loadAddress = function(address, callback) {
|
||||
var self = this;
|
||||
|
||||
self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x21, address, (address >> 8), (address >> 16), (address >> 24)], function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuDNBUSY) {
|
||||
var delay = data[1] | (data[2] << 8) | (data[3] << 16);
|
||||
|
||||
setTimeout(function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuDNLOAD_IDLE) {
|
||||
callback(data);
|
||||
} else {
|
||||
console.log('Failed to execure address load');
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
}, delay);
|
||||
} else {
|
||||
console.log('Failed to request address load');
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// first_array = usually hex_to_flash array
|
||||
// second_array = usually verify_hex array
|
||||
// result = true/false
|
||||
STM32DFU_protocol.prototype.verify_flash = function(first_array, second_array) {
|
||||
for (var i = 0; i < first_array.length; i++) {
|
||||
if (first_array[i] != second_array[i]) {
|
||||
console.log('Verification failed on byte: ' + i + ' expected: 0x' + first_array[i].toString(16) + ' received: 0x' + second_array[i].toString(16));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Verification successful, matching: ' + first_array.length + ' bytes');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.upload_procedure = function(step) {
|
||||
var self = this;
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
self.clearStatus(function() {
|
||||
self.upload_procedure(2);
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
// full chip erase
|
||||
console.log('Executing global chip erase');
|
||||
GUI.log('Erasing ...');
|
||||
|
||||
self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x41], function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuDNBUSY) { // completely normal
|
||||
var delay = data[1] | (data[2] << 8) | (data[3] << 16);
|
||||
|
||||
setTimeout(function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuDNLOAD_IDLE) {
|
||||
self.upload_procedure(4);
|
||||
} else {
|
||||
console.log('Failed to execute global chip erase');
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
}, delay);
|
||||
} else {
|
||||
console.log('Failed to initiate global chip erase');
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 4:
|
||||
// upload
|
||||
// we dont need to clear the state as we are already using DFU_DNLOAD
|
||||
console.log('Writing data ...');
|
||||
GUI.log('Flashing ...');
|
||||
|
||||
var blocks = self.hex.data.length - 1;
|
||||
var flashing_block = 0;
|
||||
var address = self.hex.data[flashing_block].address;
|
||||
|
||||
var bytes_flashed = 0;
|
||||
var bytes_flashed_total = 0; // used for progress bar
|
||||
var wBlockNum = 2; // required by DFU
|
||||
|
||||
// start
|
||||
self.loadAddress(address, write);
|
||||
|
||||
function write() {
|
||||
if (bytes_flashed < self.hex.data[flashing_block].bytes) {
|
||||
var bytes_to_write = ((bytes_flashed + 2048) <= self.hex.data[flashing_block].bytes) ? 2048 : (self.hex.data[flashing_block].bytes - bytes_flashed);
|
||||
|
||||
var data_to_flash = self.hex.data[flashing_block].data.slice(bytes_flashed, bytes_flashed + bytes_to_write);
|
||||
|
||||
address += bytes_to_write;
|
||||
bytes_flashed += bytes_to_write;
|
||||
bytes_flashed_total += bytes_to_write;
|
||||
|
||||
self.controlTransfer('out', self.request.DNLOAD, wBlockNum++, 0, 0, data_to_flash, function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuDNBUSY) {
|
||||
var delay = data[1] | (data[2] << 8) | (data[3] << 16);
|
||||
|
||||
setTimeout(function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
if (data[4] == self.state.dfuDNLOAD_IDLE) {
|
||||
// update progress bar
|
||||
self.progress_bar_e.val(bytes_flashed_total / (self.hex.bytes_total * 2) * 100);
|
||||
|
||||
// flash another page
|
||||
write();
|
||||
} else {
|
||||
console.log('Failed to write ' + bytes_to_write + 'bytes to 0x' + address.toString(16));
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
}, delay);
|
||||
} else {
|
||||
console.log('Failed to initiate write ' + bytes_to_write + 'bytes to 0x' + address.toString(16));
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
if (flashing_block < blocks) {
|
||||
// move to another block
|
||||
flashing_block++;
|
||||
|
||||
address = self.hex.data[flashing_block].address;
|
||||
bytes_flashed = 0;
|
||||
wBlockNum = 2;
|
||||
|
||||
self.loadAddress(address, write);
|
||||
} else {
|
||||
// all blocks flashed
|
||||
console.log('Writing: done');
|
||||
|
||||
// proceed to next step
|
||||
self.upload_procedure(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
// verify
|
||||
console.log('Verifying data ...');
|
||||
GUI.log('Verifying ...');
|
||||
|
||||
var blocks = self.hex.data.length - 1;
|
||||
var reading_block = 0;
|
||||
var address = self.hex.data[reading_block].address;
|
||||
|
||||
var bytes_verified = 0;
|
||||
var bytes_verified_total = 0; // used for progress bar
|
||||
var wBlockNum = 2; // required by DFU
|
||||
|
||||
// initialize arrays
|
||||
for (var i = 0; i <= blocks; i++) {
|
||||
self.verify_hex.push([]);
|
||||
}
|
||||
|
||||
// start
|
||||
self.clearStatus(function() {
|
||||
self.loadAddress(address, function() {
|
||||
self.clearStatus(read);
|
||||
});
|
||||
});
|
||||
|
||||
function read() {
|
||||
if (bytes_verified < self.hex.data[reading_block].bytes) {
|
||||
var bytes_to_read = ((bytes_verified + 2048) <= self.hex.data[reading_block].bytes) ? 2048 : (self.hex.data[reading_block].bytes - bytes_verified);
|
||||
|
||||
self.controlTransfer('in', self.request.UPLOAD, wBlockNum++, 0, bytes_to_read, 0, function(data, code) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
self.verify_hex[reading_block].push(data[i]);
|
||||
}
|
||||
|
||||
address += bytes_to_read;
|
||||
bytes_verified += bytes_to_read;
|
||||
bytes_verified_total += bytes_to_read;
|
||||
|
||||
// update progress bar
|
||||
self.progress_bar_e.val((self.hex.bytes_total + bytes_verified_total) / (self.hex.bytes_total * 2) * 100);
|
||||
|
||||
// verify another page
|
||||
read();
|
||||
});
|
||||
} else {
|
||||
if (reading_block < blocks) {
|
||||
// move to another block
|
||||
reading_block++;
|
||||
|
||||
address = self.hex.data[reading_block].address;
|
||||
bytes_verified = 0;
|
||||
wBlockNum = 2;
|
||||
|
||||
self.clearStatus(function() {
|
||||
self.loadAddress(address, function() {
|
||||
self.clearStatus(read);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// all blocks read, verify
|
||||
|
||||
var verify = true;
|
||||
for (var i = 0; i <= blocks; i++) {
|
||||
verify = self.verify_flash(self.hex.data[i].data, self.verify_hex[i]);
|
||||
|
||||
if (!verify) break;
|
||||
}
|
||||
|
||||
if (verify) {
|
||||
console.log('Programming: SUCCESSFUL');
|
||||
GUI.log('Programming: <strong style="color: green">SUCCESSFUL</strong>');
|
||||
googleAnalytics.sendEvent('Flashing', 'Programming', 'success');
|
||||
|
||||
// update progress bar
|
||||
self.progress_bar_e.addClass('valid');
|
||||
|
||||
// proceed to next step
|
||||
self.upload_procedure(6);
|
||||
} else {
|
||||
console.log('Programming: FAILED');
|
||||
GUI.log('Programming: <strong style="color: red">FAILED</strong>');
|
||||
googleAnalytics.sendEvent('Flashing', 'Programming', 'fail');
|
||||
|
||||
// update progress bar
|
||||
self.progress_bar_e.addClass('invalid');
|
||||
|
||||
// disconnect
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
// jump to application code
|
||||
var address = self.hex.data[0].address;
|
||||
|
||||
self.clearStatus(function() {
|
||||
self.loadAddress(address, leave);
|
||||
});
|
||||
|
||||
function leave() {
|
||||
self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, 0, function() {
|
||||
self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
|
||||
self.upload_procedure(99);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// start
|
||||
clear_before_leave();
|
||||
break;
|
||||
case 99:
|
||||
// cleanup
|
||||
console.log('Script finished after: ' + (microtime() - self.upload_time_start).toFixed(4) + ' seconds');
|
||||
|
||||
self.releaseInterface(0);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// initialize object
|
||||
var STM32DFU = new STM32DFU_protocol();
|
Loading…
Add table
Add a link
Reference in a new issue