mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-25 01:05:15 +03:00
Merge pull request #250 from tracernz/development
Enable flashing using USB-DFU protocol
This commit is contained in:
commit
afe62cc01b
12 changed files with 218 additions and 128 deletions
|
@ -10,7 +10,6 @@ var GUI_control = function () {
|
|||
this.active_tab;
|
||||
this.tab_switch_in_progress = false;
|
||||
this.operating_system;
|
||||
this.optional_usb_permissions = false; // controlled by usb permissions code
|
||||
this.interval_array = [];
|
||||
this.timeout_array = [];
|
||||
this.defaultAllowedTabsWhenDisconnected = [
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
var usbDevices = {
|
||||
STM32DFU: {'vendorId': 1155, 'productId': 57105}
|
||||
};
|
||||
|
||||
var PortHandler = new function () {
|
||||
this.initial_ports = false;
|
||||
this.port_detected_callbacks = [];
|
||||
this.port_removed_callbacks = [];
|
||||
this.dfu_available = false;
|
||||
};
|
||||
|
||||
PortHandler.initialize = function () {
|
||||
|
@ -131,30 +136,32 @@ PortHandler.check = function () {
|
|||
self.initial_ports = current_ports;
|
||||
}
|
||||
|
||||
if (GUI.optional_usb_permissions) {
|
||||
check_usb_devices();
|
||||
}
|
||||
self.check_usb_devices();
|
||||
|
||||
GUI.updateManualPortVisibility();
|
||||
setTimeout(function () {
|
||||
self.check();
|
||||
}, 250);
|
||||
});
|
||||
};
|
||||
|
||||
function check_usb_devices() {
|
||||
chrome.usb.getDevices(usbDevices.STM32DFU, function (result) {
|
||||
if (result.length) {
|
||||
if (!$("div#port-picker #port [value='DFU']").length) {
|
||||
$('div#port-picker #port').append('<option value="DFU">DFU</option>');
|
||||
$('div#port-picker #port').val('DFU');
|
||||
}
|
||||
} else {
|
||||
if ($("div#port-picker #port [value='DFU']").length) {
|
||||
$("div#port-picker #port [value='DFU']").remove();
|
||||
}
|
||||
PortHandler.check_usb_devices = function (callback) {
|
||||
chrome.usb.getDevices(usbDevices.STM32DFU, function (result) {
|
||||
if (result.length) {
|
||||
if (!$("div#port-picker #port [value='DFU']").length) {
|
||||
$('div#port-picker #port').append($('<option/>', {value: "DFU", text: "DFU", data: {isDFU: true}}));
|
||||
$('div#port-picker #port').val('DFU');
|
||||
}
|
||||
});
|
||||
}
|
||||
self.dfu_available = true;
|
||||
} else {
|
||||
if ($("div#port-picker #port [value='DFU']").length) {
|
||||
$("div#port-picker #port [value='DFU']").remove();
|
||||
}
|
||||
self.dfu_available = false;
|
||||
}
|
||||
|
||||
if(callback) callback(self.dfu_available);
|
||||
});
|
||||
};
|
||||
|
||||
PortHandler.update_port_select = function (ports) {
|
||||
|
|
|
@ -100,14 +100,25 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback)
|
|||
serial.send(bufferOut, function () {
|
||||
serial.disconnect(function (result) {
|
||||
if (result) {
|
||||
serial.connect(port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) {
|
||||
if (openInfo) {
|
||||
self.initialize();
|
||||
} else {
|
||||
GUI.connect_lock = false;
|
||||
GUI.log('<span style="color: red">Failed</span> to open serial port');
|
||||
}
|
||||
});
|
||||
// delay to allow board to boot in bootloader mode
|
||||
// required to detect if a DFU device appears
|
||||
setTimeout(function() {
|
||||
// refresh device list
|
||||
PortHandler.check_usb_devices(function(dfu_available) {
|
||||
if(dfu_available) {
|
||||
STM32DFU.connect(usbDevices.STM32DFU, hex, options);
|
||||
} else {
|
||||
serial.connect(port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) {
|
||||
if (openInfo) {
|
||||
self.initialize();
|
||||
} else {
|
||||
GUI.connect_lock = false;
|
||||
GUI.log('<span style="color: red">Failed</span> to open serial port');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
} else {
|
||||
GUI.connect_lock = false;
|
||||
}
|
||||
|
|
|
@ -61,13 +61,26 @@ var STM32DFU_protocol = function () {
|
|||
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.
|
||||
};
|
||||
|
||||
// Assume 2 kB page size (STM32F303)
|
||||
// Cannot read chip ID using DFU protocol and Chrome doesn't provide the interface
|
||||
// description string with flash page size information (at least on Linux anyway)
|
||||
this.page_size = 2048;
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.connect = function (device, hex, callback) {
|
||||
STM32DFU_protocol.prototype.connect = function (device, hex, options, callback) {
|
||||
var self = this;
|
||||
self.hex = hex;
|
||||
self.callback = callback;
|
||||
|
||||
self.options = {
|
||||
erase_chip: false
|
||||
};
|
||||
|
||||
if (options.erase_chip) {
|
||||
self.options.erase_chip = true;
|
||||
}
|
||||
|
||||
// reset and set some variables before we start
|
||||
self.upload_time_start = new Date().getTime();
|
||||
self.verify_hex = [];
|
||||
|
@ -89,12 +102,35 @@ STM32DFU_protocol.prototype.connect = function (device, hex, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
STM32DFU_protocol.prototype.checkChromeError = function() {
|
||||
if (chrome.runtime.lastError) {
|
||||
if(chrome.runtime.lastError.message)
|
||||
console.log(chrome.runtime.lastError.message);
|
||||
else
|
||||
console.log(chrome.runtime.lastError);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
STM32DFU_protocol.prototype.openDevice = function (device) {
|
||||
var self = this;
|
||||
|
||||
chrome.usb.openDevice(device, function (handle) {
|
||||
if(self.checkChromeError()) {
|
||||
console.log('Failed to open USB device!');
|
||||
GUI.log(chrome.i18n.getMessage('usbDeviceOpenFail'));
|
||||
if(GUI.operating_system === 'Linux') {
|
||||
GUI.log(chrome.i18n.getMessage('usbDeviceUdevNotice'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.handle = handle;
|
||||
|
||||
GUI.log(chrome.i18n.getMessage('usbDeviceOpened', handle.handle.toString()));
|
||||
console.log('Device opened with Handle ID: ' + handle.handle);
|
||||
self.claimInterface(0);
|
||||
});
|
||||
|
@ -104,6 +140,12 @@ STM32DFU_protocol.prototype.closeDevice = function () {
|
|||
var self = this;
|
||||
|
||||
chrome.usb.closeDevice(this.handle, function closed() {
|
||||
if(self.checkChromeError()) {
|
||||
console.log('Failed to close USB device!');
|
||||
GUI.log(chrome.i18n.getMessage('usbDeviceCloseFail'));
|
||||
}
|
||||
|
||||
GUI.log(chrome.i18n.getMessage('usbDeviceClosed'));
|
||||
console.log('Device closed with Handle ID: ' + self.handle.handle);
|
||||
|
||||
self.handle = null;
|
||||
|
@ -139,6 +181,8 @@ STM32DFU_protocol.prototype.resetDevice = function (callback) {
|
|||
};
|
||||
|
||||
STM32DFU_protocol.prototype.controlTransfer = function (direction, request, value, _interface, length, data, callback) {
|
||||
var self = this;
|
||||
|
||||
if (direction == 'in') {
|
||||
// data is ignored
|
||||
chrome.usb.controlTransfer(this.handle, {
|
||||
|
@ -150,6 +194,9 @@ STM32DFU_protocol.prototype.controlTransfer = function (direction, request, valu
|
|||
'index': _interface,
|
||||
'length': length
|
||||
}, function (result) {
|
||||
if(self.checkChromeError()) {
|
||||
console.log('USB transfer failed!');
|
||||
}
|
||||
if (result.resultCode) console.log(result.resultCode);
|
||||
|
||||
var buf = new Uint8Array(result.data);
|
||||
|
@ -174,6 +221,9 @@ STM32DFU_protocol.prototype.controlTransfer = function (direction, request, valu
|
|||
'index': _interface,
|
||||
'data': arrayBuf
|
||||
}, function (result) {
|
||||
if(self.checkChromeError()) {
|
||||
console.log('USB transfer failed!');
|
||||
}
|
||||
if (result.resultCode) console.log(result.resultCode);
|
||||
|
||||
callback(result);
|
||||
|
@ -217,7 +267,7 @@ STM32DFU_protocol.prototype.loadAddress = function (address, callback) {
|
|||
if (data[4] == self.state.dfuDNLOAD_IDLE) {
|
||||
callback(data);
|
||||
} else {
|
||||
console.log('Failed to execure address load');
|
||||
console.log('Failed to execute address load');
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
|
@ -256,32 +306,86 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
|
|||
});
|
||||
break;
|
||||
case 2:
|
||||
// full chip erase
|
||||
console.log('Executing global chip erase');
|
||||
$('span.progressLabel').text('Erasing ...');
|
||||
// erase
|
||||
if (self.options.erase_chip) {
|
||||
// full chip erase
|
||||
console.log('Executing global chip erase');
|
||||
$('span.progressLabel').text('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);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// local erase
|
||||
|
||||
var max_address = self.hex.data[self.hex.data.length - 1].address + self.hex.data[self.hex.data.length - 1].bytes - 0x8000000,
|
||||
erase_pages_n = Math.ceil(max_address / self.page_size),
|
||||
page = 0;
|
||||
|
||||
$('span.progressLabel').text('Erasing ...');
|
||||
console.log('Executing local chip erase');
|
||||
console.log('Erasing. page: 0x00 - 0x' + erase_pages_n.toString(16));
|
||||
|
||||
var erase_page = function() {
|
||||
var page_addr = page * self.page_size + 0x8000000;
|
||||
var cmd = [0x41, page_addr & 0xff, (page_addr >> 8) & 0xff, (page_addr >> 16) & 0xff, (page_addr >> 24) & 0xff];
|
||||
|
||||
self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, cmd, 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) {
|
||||
// update progress bar
|
||||
self.progress_bar_e.val((page + 1) / erase_pages_n * 100);
|
||||
page++;
|
||||
|
||||
if(page == erase_pages_n) {
|
||||
console.log("Erase: complete");
|
||||
GUI.log(chrome.i18n.getMessage('dfu_erased_kilobytes', (erase_pages_n * self.page_size / 1024).toString()));
|
||||
self.upload_procedure(4);
|
||||
}
|
||||
else
|
||||
erase_page();
|
||||
} else {
|
||||
console.log('Failed to erase page 0x' + self.current_page.toString(16));
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
}, delay);
|
||||
} else {
|
||||
console.log('Failed to initiate page erase, page 0x' + self.current_page.toString(16));
|
||||
self.upload_procedure(99);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// start
|
||||
erase_page();
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// upload
|
||||
// we dont need to clear the state as we are already using DFU_DNLOAD
|
||||
|
@ -296,9 +400,6 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
|
|||
var bytes_flashed_total = 0; // used for progress bar
|
||||
var wBlockNum = 2; // required by DFU
|
||||
|
||||
// start
|
||||
self.loadAddress(address, write);
|
||||
|
||||
var write = function () {
|
||||
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);
|
||||
|
@ -353,6 +454,10 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start
|
||||
self.loadAddress(address, write);
|
||||
|
||||
break;
|
||||
case 5:
|
||||
// verify
|
||||
|
@ -463,13 +568,13 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
|
|||
});
|
||||
}
|
||||
|
||||
// start
|
||||
clear_before_leave();
|
||||
break;
|
||||
case 99:
|
||||
// cleanup
|
||||
self.releaseInterface(0);
|
||||
|
||||
GUI.connect_lock = false;
|
||||
|
||||
var timeSpent = new Date().getTime() - self.upload_time_start;
|
||||
|
||||
console.log('Script finished after: ' + (timeSpent / 1000) + ' seconds');
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
$(document).ready(function () {
|
||||
|
||||
GUI.updateManualPortVisibility = function(){
|
||||
if ($('div#port-picker #port option:selected').data().isManual) {
|
||||
var selected_port = $('div#port-picker #port option:selected');
|
||||
if (selected_port.data().isManual) {
|
||||
$('#port-override-option').show();
|
||||
}
|
||||
else {
|
||||
$('#port-override-option').hide();
|
||||
}
|
||||
if (selected_port.data().isDFU) {
|
||||
$('select#baud').hide();
|
||||
}
|
||||
else {
|
||||
$('select#baud').show();
|
||||
}
|
||||
};
|
||||
|
||||
GUI.updateManualPortVisibility();
|
||||
|
@ -33,8 +40,10 @@ $(document).ready(function () {
|
|||
var selected_port = $('div#port-picker #port option:selected').data().isManual ?
|
||||
$('#port-override').val() :
|
||||
String($('div#port-picker #port').val());
|
||||
|
||||
if (selected_port != '0' && selected_port != 'DFU') {
|
||||
if (selected_port === 'DFU') {
|
||||
GUI.log(chrome.i18n.getMessage('dfu_connect_message'));
|
||||
}
|
||||
else if (selected_port != '0') {
|
||||
if (!clicks) {
|
||||
console.log('Connecting to: ' + selected_port);
|
||||
GUI.connecting_to = selected_port;
|
||||
|
|
36
js/usb.js
36
js/usb.js
|
@ -1,36 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var usbDevices = {
|
||||
STM32DFU: {'vendorId': 1155, 'productId': 57105}
|
||||
};
|
||||
var usbPermissions = {permissions: [{'usbDevices': [usbDevices.STM32DFU]}]};
|
||||
|
||||
function check_usb_permissions(callback) {
|
||||
chrome.permissions.contains(usbPermissions, function (result) {
|
||||
if (result) {
|
||||
GUI.optional_usb_permissions = true;
|
||||
} else {
|
||||
console.log('Optional USB permissions: missing');
|
||||
GUI.log(chrome.i18n.getMessage('please_grant_usb_permissions'));
|
||||
|
||||
// display optional usb permissions request box
|
||||
$('div.optional_permissions').show();
|
||||
|
||||
// UI hooks
|
||||
document.getElementById("requestOptionalPermissions").addEventListener('click', function () {
|
||||
chrome.permissions.request(usbPermissions, function (result) {
|
||||
if (result) {
|
||||
GUI.log(chrome.i18n.getMessage('usb_permissions_granted'));
|
||||
$('div.optional_permissions').hide();
|
||||
|
||||
GUI.optional_usb_permissions = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue