diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index bafa4d3f..98b64d31 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -86,29 +86,33 @@ extern char _estack; // provided by the linker script // Gets called from the startup assembly code void early_start_checks(void) { + if(_reboot_cookie == 0xDEADFE75) { + /* The STM DFU bootloader enables internal pull-up resistors on PB10 (AUX_H) + * and PB11 (AUX_L), thereby causing shoot-through on the brake resistor + * FETs and obliterating them unless external 3.3k pull-down resistors are + * present. Pull-downs are only present on ODrive 3.5 or newer. + * On older boards we disable DFU by default but if the user insists + * there's only one thing left that might save it: time. + * The brake resistor gate driver needs a certain 10V supply (GVDD) to + * make it work. This voltage is supplied by the motor gate drivers which get + * disabled at system reset. So over time GVDD voltage _should_ below + * dangerous levels. This is completely handwavy and should not be relied on + * so you are on your own on if you ignore this warning. + * + * This loop takes 5 cycles per iteration and at this point the system runs + * on the internal 16MHz RC oscillator so the delay is about 2 seconds. + */ + for (size_t i = 0; i < (16000000UL / 5UL * 2UL); ++i) { + __NOP(); + } + _reboot_cookie = 0xDEADBEEF; + } + /* We could jump to the bootloader directly on demand without rebooting but that requires us to reset several peripherals and interrupts for it to function correctly. Therefore it's easier to just reset the entire chip. */ if(_reboot_cookie == 0xDEADBEEF) { _reboot_cookie = 0xCAFEFEED; //Reset bootloader trigger - - /* - * This wait loop solves an obscure timing issue, but we don't exactly understand why. - * When the transition NVIC_SystemReset() => STM bootloader happens very quickly, - * there is a yet unexplained phenomenon where the ODrive would emit an audible click, - * followed by one the following symptoms: - * - Device reboots in normal mode (possibly due to the bootloader exiting immidiately) - * - Device goes into DFU mode and then the power supply turns off - * This manifests in the DFU script detecting the device in DFU mode but then - * losing the device immidiately after. - * There were no motors/encoders/brake resistor connected when testing this. As far as - * we can tell, the only way for the software to cause a short circuit is through the - * brake FETs. - */ - for (size_t i = 0; i < 1000000; ++i) { - __NOP(); - } - __set_MSP((uintptr_t)&_estack); // http://www.st.com/content/ccc/resource/technical/document/application_note/6a/17/92/02/58/98/45/0c/CD00264379.pdf/files/CD00264379.pdf void (*builtin_bootloader)(void) = (void (*)(void))(*((uint32_t *)0x1FFF0004)); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 9970e84b..ea3c155c 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -62,10 +62,22 @@ void erase_configuration(void) { NVM_erase(); } -void enter_dfu_mode(void) { - __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts - _reboot_cookie = 0xDEADBEEF; - NVIC_SystemReset(); +void enter_dfu_mode() { + if ((hw_version_major == 3) && (hw_version_minor >= 5)) { + __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts + _reboot_cookie = 0xDEADBEEF; + NVIC_SystemReset(); + } else { + /* + * DFU mode is only allowed on board version >= 3.5 because it can burn + * the brake resistor FETs on older boards. + * If you really want to use it on an older board, add 3.3k pull-down resistors + * to the AUX_L and AUX_H signals and _only then_ uncomment these lines. + */ + //__asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts + //_reboot_cookie = 0xDEADFE75; + //NVIC_SystemReset(); + } } extern "C" { diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index 8e68e508..03da784b 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -17,6 +17,10 @@ extern "C" { extern osThreadId comm_thread; +extern const uint8_t hw_version_major; +extern const uint8_t hw_version_minor; +extern const uint8_t hw_version_variant; + void init_communication(void); void communication_task(void * ctx); diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index d1b59f82..1efd4592 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -206,7 +206,7 @@ def show_deferred_message(message, cancellation_token): t.daemon = True t.start() -def put_odrive_into_dfu_mode(my_drive): +def put_odrive_into_dfu_mode(my_drive, cancellation_token): """ Puts the specified device into DFU mode """ @@ -216,15 +216,23 @@ def put_odrive_into_dfu_mode(my_drive): "DFU with this script should work fine." .format(my_drive.__channel__.usb_device.serial_number)) return - print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) - try: - my_drive.enter_dfu_mode() - except odrive.protocol.ChannelBrokenException as ex: - pass # this is expected because the device reboots - if platform.system() == "Windows": - show_deferred_message("Still waiting for the device to reappear.\n" - "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", - find_odrive_cancellation_token) + hw_version_major = my_drive.hw_version_major if hasattr(my_drive, 'hw_version_major') else 3 + hw_version_minor = my_drive.hw_version_minor if hasattr(my_drive, 'hw_version_minor') else 4 + if hw_version_major == 3 and hw_version_minor >= 5: + print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) + try: + my_drive.enter_dfu_mode() + except odrive.protocol.ChannelBrokenException: + pass # this is expected because the device reboots + if platform.system() == "Windows": + show_deferred_message("Still waiting for the device to reappear.\n" + "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", + cancellation_token) + else: + print("Found device {}".format(my_drive.__channel__.usb_device.serial_number)) + print(" DFU mode is not supported on board version 3.4 or earlier.") + print(" This is because entering DFU mode on such a device would") + print(" break the brake resistor FETs under some circumstances.") def launch_dfu(args, app_shutdown_token): """ @@ -251,7 +259,9 @@ def launch_dfu(args, app_shutdown_token): # Scan for ODrives not in DFU mode and put them into DFU mode once they appear # We only scan on USB because DFU is only possible over USB - odrive.discovery.find_all(args.path, serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token, app_shutdown_token) + odrive.discovery.find_all(args.path, serial_number, + lambda dev: put_odrive_into_dfu_mode(dev, find_odrive_cancellation_token), + find_odrive_cancellation_token, app_shutdown_token) # Poll libUSB until a device in DFU mode is found while not app_shutdown_token.is_set(): diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 09d26707..14ef07d2 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -256,7 +256,7 @@ class Channel(PacketSink): self._printer("receiver thread is exiting: " + traceback.format_exc()) finally: self._channel_broken.set() - threading.Thread(target=receiver_thread, daemon=True).start() + threading.Thread(target=receiver_thread).start() def remote_endpoint_operation(self, endpoint_id, input, expect_ack, output_length): if input is None: