diff --git a/boards.txt b/boards.txt
index 65074d3a9f..b82635c93a 100644
--- a/boards.txt
+++ b/boards.txt
@@ -181,6 +181,7 @@ Nucleo_144.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 Nucleo_144.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Nucleo_144.menu.upload_method.dfuMethod.upload.protocol=2
 Nucleo_144.menu.upload_method.dfuMethod.upload.options=-g
+Nucleo_144.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Nucleo_144.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -466,6 +467,7 @@ Nucleo_64.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 Nucleo_64.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Nucleo_64.menu.upload_method.dfuMethod.upload.protocol=2
 Nucleo_64.menu.upload_method.dfuMethod.upload.options=-g
+Nucleo_64.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Nucleo_64.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -571,6 +573,7 @@ Nucleo_32.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 Nucleo_32.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Nucleo_32.menu.upload_method.dfuMethod.upload.protocol=2
 Nucleo_32.menu.upload_method.dfuMethod.upload.options=-g
+Nucleo_32.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Nucleo_32.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -727,6 +730,7 @@ Disco.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 Disco.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Disco.menu.upload_method.dfuMethod.upload.protocol=2
 Disco.menu.upload_method.dfuMethod.upload.options=-g
+Disco.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Disco.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -759,6 +763,7 @@ Eval.menu.upload_method.swdMethod.upload.tool=stm32CubeProg
 Eval.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Eval.menu.upload_method.dfuMethod.upload.protocol=2
 Eval.menu.upload_method.dfuMethod.upload.options=-g
+Eval.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Eval.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -847,6 +852,7 @@ GenF0.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 GenF0.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 GenF0.menu.upload_method.dfuMethod.upload.protocol=2
 GenF0.menu.upload_method.dfuMethod.upload.options=-g
+GenF0.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 GenF0.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -1152,6 +1158,7 @@ GenF1.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 GenF1.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 GenF1.menu.upload_method.dfuMethod.upload.protocol=2
 GenF1.menu.upload_method.dfuMethod.upload.options=-g
+GenF1.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 GenF1.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 GenF1.menu.upload_method.bmpMethod=BMP (Black Magic Probe)
@@ -1215,6 +1222,7 @@ GenF3.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 GenF3.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 GenF3.menu.upload_method.dfuMethod.upload.protocol=2
 GenF3.menu.upload_method.dfuMethod.upload.options=-g
+GenF3.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 GenF3.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 GenF3.menu.upload_method.bmpMethod=BMP (Black Magic Probe)
@@ -1623,6 +1631,7 @@ GenF4.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 GenF4.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 GenF4.menu.upload_method.dfuMethod.upload.protocol=2
 GenF4.menu.upload_method.dfuMethod.upload.options=-g
+GenF4.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 GenF4.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 GenF4.menu.upload_method.bmpMethod=BMP (Black Magic Probe)
@@ -1712,6 +1721,7 @@ GenL0.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 GenL0.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 GenL0.menu.upload_method.dfuMethod.upload.protocol=2
 GenL0.menu.upload_method.dfuMethod.upload.options=-g
+GenL0.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 GenL0.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 GenL0.menu.upload_method.bmpMethod=BMP (Black Magic Probe)
@@ -1752,6 +1762,7 @@ ESC_board.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 ESC_board.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 ESC_board.menu.upload_method.dfuMethod.upload.protocol=2
 ESC_board.menu.upload_method.dfuMethod.upload.options=-g
+ESC_board.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 ESC_board.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
@@ -1834,6 +1845,7 @@ LoRa.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 LoRa.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 LoRa.menu.upload_method.dfuMethod.upload.protocol=2
 LoRa.menu.upload_method.dfuMethod.upload.options=-g
+LoRa.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 LoRa.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ###############################
@@ -2010,6 +2022,7 @@ LoRa.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 3dprinter.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 3dprinter.menu.upload_method.dfuMethod.upload.protocol=2
 3dprinter.menu.upload_method.dfuMethod.upload.options=-g
+3dprinter.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 3dprinter.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 
@@ -2069,6 +2082,7 @@ Genericflight.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 Genericflight.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Genericflight.menu.upload_method.dfuMethod.upload.protocol=2
 Genericflight.menu.upload_method.dfuMethod.upload.options=-g
+Genericflight.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Genericflight.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 Genericflight.menu.upload_method.bmpMethod=BMP (Black Magic Probe)
@@ -2199,6 +2213,7 @@ Midatronics.menu.upload_method.serialMethod.upload.tool=stm32CubeProg
 Midatronics.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU)
 Midatronics.menu.upload_method.dfuMethod.upload.protocol=2
 Midatronics.menu.upload_method.dfuMethod.upload.options=-g
+Midatronics.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true
 Midatronics.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg
 
 ################################################################################
diff --git a/cores/arduino/stm32/bootloader.h b/cores/arduino/stm32/bootloader.h
index 118bc7b860..cb077a624a 100644
--- a/cores/arduino/stm32/bootloader.h
+++ b/cores/arduino/stm32/bootloader.h
@@ -1,6 +1,8 @@
 #ifndef _BOOTLOADER_H_
 #define _BOOTLOADER_H_
 
+#include <stdint.h>
+
 /* Ensure DTR_TOGGLING_SEQ enabled */
 #if defined(BL_LEGACY_LEAF) || defined(BL_HID)
   #ifndef DTR_TOGGLING_SEQ
@@ -12,6 +14,21 @@
 extern "C" {
 #endif /* __cplusplus */
 
+void scheduleBootloaderReset();
+void cancelBootloaderReset();
+void bootloaderSystickHandler();
+
+/* Request to jump to system memory boot */
+void jumpToBootloaderRequested(void);
+
+/* Jump to system memory boot from user application */
+void jumpToBootloaderIfRequested(void);
+
+#ifdef DTR_TOGGLING_SEQ
+/* DTR toggling sequence management */
+void dtr_togglingHook(uint8_t *buf, uint32_t *len);
+#endif
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/cores/arduino/stm32/startup_stm32yyxx.S b/cores/arduino/stm32/startup_stm32yyxx.S
index 8572083cdd..088f6ccabc 100644
--- a/cores/arduino/stm32/startup_stm32yyxx.S
+++ b/cores/arduino/stm32/startup_stm32yyxx.S
@@ -3,3 +3,8 @@
 #if defined(CMSIS_STARTUP_FILE)
 #include CMSIS_STARTUP_FILE
 #endif
+
+# Expose Reset_Handler under a different name, to allow overriding it
+# with a strong symbol and then calling the original.
+.global Original_Reset_Handler
+.thumb_set Original_Reset_Handler,Reset_Handler
diff --git a/cores/arduino/stm32/usb/cdc/usbd_cdc.c b/cores/arduino/stm32/usb/cdc/usbd_cdc.c
index 9ee0d7885a..86185360d6 100644
--- a/cores/arduino/stm32/usb/cdc/usbd_cdc.c
+++ b/cores/arduino/stm32/usb/cdc/usbd_cdc.c
@@ -158,13 +158,26 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
   USB_DESC_TYPE_CONFIGURATION,          /* bDescriptorType: Configuration */
   USB_CDC_CONFIG_DESC_SIZ,              /* wTotalLength:no of returned bytes */
   0x00,
-  0x02,                                 /* bNumInterfaces: 2 interface */
+  0x03,                                 /* bNumInterfaces: 3 interface */
   0x01,                                 /* bConfigurationValue: Configuration value */
   0x00,                                 /* iConfiguration: Index of string descriptor describing the configuration */
   0xC0,                                 /* bmAttributes: self powered */
   0x32,                                 /* MaxPower 0 mA */
+
   /*---------------------------------------------------------------------------*/
+  /*Interface Association Descriptor*/
+  0x08,                                 /* bLength: Descriptor length */
+  0x0B,                                 /* bDescriptorType: IAD */
+  0x00,                                 /* bFirstInterface */
+  0x02,                                 /* bInterfaceCount */
+  0x02,                                 /* bFunctionClass (class of subdevice, should match first interface) */
+  0x02,                                 /* bFunctionSubclass (subclass of subdevice, should match first interface) */
+  0x00,                                 /* bFunctionProtocol (protocol of subdevice, should match first interface) */
+  /* TODO: Put a meaningful string here, which shows up in the Windows */
+  /* device manager when no driver is installed yet. */
+  0x00,                                 /* iFunction */
 
+  /*---------------------------------------------------------------------------*/
   /* Interface Descriptor */
   0x09,                                 /* bLength: Interface Descriptor size */
   USB_DESC_TYPE_INTERFACE,              /* bDescriptorType: Interface */
@@ -233,7 +246,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
   0x02,                                 /* bmAttributes: Bulk */
   LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
   HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
-  0x00                                  /* bInterval: ignore for Bulk transfer */
+  0x00,                                 /* bInterval: ignore for Bulk transfer */
+
+  DFU_RT_IFACE_DESC,
 };
 
 
@@ -244,11 +259,25 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
   USB_DESC_TYPE_CONFIGURATION,          /* bDescriptorType: Configuration */
   USB_CDC_CONFIG_DESC_SIZ,              /* wTotalLength:no of returned bytes */
   0x00,
-  0x02,                                 /* bNumInterfaces: 2 interface */
+  0x03,                                 /* bNumInterfaces: 3 interface */
   0x01,                                 /* bConfigurationValue: Configuration value */
   0x00,                                 /* iConfiguration: Index of string descriptor describing the configuration */
   0xC0,                                 /* bmAttributes: self powered */
   0x32,                                 /* MaxPower 0 mA */
+
+  /*---------------------------------------------------------------------------*/
+  /*Interface Association Descriptor*/
+  0x08,                                 /* bLength: Descriptor length */
+  0x0B,                                 /* bDescriptorType: IAD */
+  0x00,                                 /* bFirstInterface */
+  0x02,                                 /* bInterfaceCount */
+  0x02,                                 /* bFunctionClass (class of subdevice, should match first interface) */
+  0x02,                                 /* bFunctionSubclass (subclass of subdevice, should match first interface) */
+  0x00,                                 /* bFunctionProtocol (protocol of subdevice, should match first interface) */
+  /* TODO: Put a meaningful string here, which shows up in the Windows */
+  /* device manager when no driver is installed yet. */
+  0x00,                                 /* iFunction */
+
   /*---------------------------------------------------------------------------*/
   /* Interface Descriptor */
   0x09,                                 /* bLength: Interface Descriptor size */
@@ -318,7 +347,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
   0x02,                                 /* bmAttributes: Bulk */
   LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
   HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
-  0x00                                  /* bInterval: ignore for Bulk transfer */
+  0x00,                                 /* bInterval: ignore for Bulk transfer */
+
+  DFU_RT_IFACE_DESC,
 };
 
 __ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = {
@@ -326,11 +357,26 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ]
   USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION,
   USB_CDC_CONFIG_DESC_SIZ,
   0x00,
-  0x02,                                 /* bNumInterfaces: 2 interfaces */
+  0x03,                                 /* bNumInterfaces: 3 interfaces */
   0x01,                                 /* bConfigurationValue: */
   0x04,                                 /* iConfiguration: */
   0xC0,                                 /* bmAttributes: */
   0x32,                                 /* MaxPower 100 mA */
+
+  /*---------------------------------------------------------------------------*/
+  /*Interface Association Descriptor*/
+  0x08,                                 /* bLength: Descriptor length */
+  0x0B,                                 /* bDescriptorType: IAD */
+  0x00,                                 /* bFirstInterface */
+  0x02,                                 /* bInterfaceCount */
+  0x02,                                 /* bFunctionClass (class of subdevice, should match first interface) */
+  0x02,                                 /* bFunctionSubclass (subclass of subdevice, should match first interface) */
+  0x00,                                 /* bFunctionProtocol (protocol of subdevice, should match first interface) */
+  /* TODO: Put a meaningful string here, which shows up in the Windows */
+  /* device manager when no driver is installed yet. */
+  0x00,                                 /* iFunction */
+
+  /*---------------------------------------------------------------------------*/
   /*Interface Descriptor */
   0x09,                                 /* bLength: Interface Descriptor size */
   USB_DESC_TYPE_INTERFACE,              /* bDescriptorType: Interface */
@@ -399,7 +445,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ]
   0x02,                                 /* bmAttributes: Bulk */
   0x40,                                 /* wMaxPacketSize: */
   0x00,
-  0x00                                  /* bInterval */
+  0x00,                                 /* bInterval */
+
+  DFU_RT_IFACE_DESC,
 };
 
 /**
@@ -539,7 +587,26 @@ static uint8_t  USBD_CDC_Setup(USBD_HandleTypeDef *pdev,
 
   switch (req->bmRequest & USB_REQ_TYPE_MASK) {
     case USB_REQ_TYPE_CLASS:
-      if (req->wLength != 0U) {
+      if ((req->bmRequest & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE
+          && req->wIndex == DFU_RT_IFACE_NUM) {
+        // Handle requests to the DFU interface separately
+        int device_to_host = (req->bmRequest & 0x80U);
+
+        if (!device_to_host && req->wLength > 0) {
+          // When data is sent, return an error, since the data receiving
+          // machinery will forget the target interface and handle as a CDC
+          // request instead.
+          ret = USBD_FAIL;
+        } else {
+          ret = USBD_DFU_Runtime_Control(req->bRequest, req->wValue, (uint8_t *)(void *)hcdc->data, req->wLength);
+        }
+
+        if (ret == USBD_FAIL) {
+          USBD_CtlError(pdev, req);
+        } else if (device_to_host && req->wLength > 0) {
+          USBD_CtlSendData(pdev, (uint8_t *)(void *)hcdc->data, req->wLength);
+        }
+      } else if (req->wLength != 0U) {
         if ((req->bmRequest & 0x80U) != 0U) {
           ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,
                                                             (uint8_t *)hcdc->data,
diff --git a/cores/arduino/stm32/usb/cdc/usbd_cdc.h b/cores/arduino/stm32/usb/cdc/usbd_cdc.h
index 5395708c06..fd4a8022ae 100644
--- a/cores/arduino/stm32/usb/cdc/usbd_cdc.h
+++ b/cores/arduino/stm32/usb/cdc/usbd_cdc.h
@@ -28,6 +28,7 @@ extern "C" {
 /* Includes ------------------------------------------------------------------*/
 #include "usbd_ioreq.h"
 #include "usbd_ep_conf.h"
+#include "dfu_runtime.h"
 
 /** @addtogroup STM32_USB_DEVICE_LIBRARY
   * @{
@@ -51,7 +52,7 @@ extern "C" {
 
 /* CDC Endpoints parameters */
 
-#define USB_CDC_CONFIG_DESC_SIZ                     67U
+#define USB_CDC_CONFIG_DESC_SIZ                     67U + /* IAD */ 8 + DFU_RT_IFACE_DESC_SIZE
 #define CDC_DATA_HS_IN_PACKET_SIZE                  CDC_DATA_HS_MAX_PACKET_SIZE
 #define CDC_DATA_HS_OUT_PACKET_SIZE                 CDC_DATA_HS_MAX_PACKET_SIZE
 
diff --git a/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c b/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c
index c09675f95e..b340a6b553 100644
--- a/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c
+++ b/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c
@@ -33,6 +33,9 @@
   #define CDC_MAX_PACKET_SIZE USB_MAX_EP0_SIZE
 #endif
 
+// TODO: Put this elsewhere
+#define BOOTLOADER_RESET_1200_BAUD
+
 /*
  * The value USB_CDC_TRANSMIT_TIMEOUT is defined in terms of HAL_GetTick() units.
  * Typically it is 1ms value. The timeout determines when we would consider the
@@ -56,8 +59,7 @@ __IO bool receivePended = true;
 static uint32_t transmitStart = 0;
 
 #ifdef DTR_TOGGLING_SEQ
-  /* DTR toggling sequence management */
-  extern void dtr_togglingHook(uint8_t *buf, uint32_t *len);
+
   uint8_t dtr_toggling = 0;
 #endif
 
@@ -201,6 +203,19 @@ static int8_t USBD_CDC_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length)
       break;
   }
 
+#ifdef BOOTLOADER_RESET_1200_BAUD
+  if (cmd == CDC_SET_LINE_CODING || cmd == CDC_SET_CONTROL_LINE_STATE) {
+    // Auto-reset into the bootloader is triggered when the port, already
+    // open at 1200 bps, is closed. Cancel the reset when the port is
+    // opened again.
+    if (linecoding.bitrate == 1200 && !lineState) {
+      scheduleBootloaderReset();
+    } else {
+      cancelBootloaderReset();
+    }
+  }
+#endif /* BOOTLOADER_RESET_1200_BAUD */
+
   return ((int8_t)USBD_OK);
 }
 
diff --git a/cores/arduino/stm32/usb/dfu_runtime.h b/cores/arduino/stm32/usb/dfu_runtime.h
new file mode 100644
index 0000000000..052d65e5ae
--- /dev/null
+++ b/cores/arduino/stm32/usb/dfu_runtime.h
@@ -0,0 +1,151 @@
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef __USB_DFU_RUNTIME_H
+#define __USB_DFU_RUNTIME_H
+
+#include <bootloader.h>
+
+/**************************************************/
+/* DFU Requests  DFU states                       */
+/**************************************************/
+#define APP_STATE_IDLE                 0U
+#define APP_STATE_DETACH               1U
+#define DFU_STATE_IDLE                 2U
+#define DFU_STATE_DNLOAD_SYNC          3U
+#define DFU_STATE_DNLOAD_BUSY          4U
+#define DFU_STATE_DNLOAD_IDLE          5U
+#define DFU_STATE_MANIFEST_SYNC        6U
+#define DFU_STATE_MANIFEST             7U
+#define DFU_STATE_MANIFEST_WAIT_RESET  8U
+#define DFU_STATE_UPLOAD_IDLE          9U
+#define DFU_STATE_ERROR                10U
+
+/**************************************************/
+/* DFU errors                                     */
+/**************************************************/
+#define DFU_ERROR_NONE                 0x00U
+#define DFU_ERROR_TARGET               0x01U
+#define DFU_ERROR_FILE                 0x02U
+#define DFU_ERROR_WRITE                0x03U
+#define DFU_ERROR_ERASE                0x04U
+#define DFU_ERROR_CHECK_ERASED         0x05U
+#define DFU_ERROR_PROG                 0x06U
+#define DFU_ERROR_VERIFY               0x07U
+#define DFU_ERROR_ADDRESS              0x08U
+#define DFU_ERROR_NOTDONE              0x09U
+#define DFU_ERROR_FIRMWARE             0x0AU
+#define DFU_ERROR_VENDOR               0x0BU
+#define DFU_ERROR_USB                  0x0CU
+#define DFU_ERROR_POR                  0x0DU
+#define DFU_ERROR_UNKNOWN              0x0EU
+#define DFU_ERROR_STALLEDPKT           0x0FU
+
+typedef enum {
+  DFU_DETACH = 0U,
+  DFU_DNLOAD,
+  DFU_UPLOAD,
+  DFU_GETSTATUS,
+  DFU_CLRSTATUS,
+  DFU_GETSTATE,
+  DFU_ABORT
+} DFU_RequestTypeDef;
+
+#define DFU_DESCRIPTOR_TYPE            0x21U
+
+// Device will detach by itself (alternative is that the host sends a
+// USB reset within DETACH_TIMEOUT).
+#define DFU_RT_ATTR_WILL_DETACH 0x08U
+// Device is still accessible on USB after flashing (manifestation).
+// Probably not so relevant in runtime mode
+#define DFU_RT_ATTR_MANIFESTATION_TOLERANT 0x04U
+#define DFU_RT_ATTR_CAN_UPLOAD 0x02U
+#define DFU_RT_ATTR_CAN_DNLOAD 0x01U
+
+// Of these, only WILL_DETACH is relevant at runtime, but specify
+// CAN_UPLOAD and CAN_DNLOAD too, just in case there is a tool that
+// somehow checks these before resetting.
+#define DFU_RT_ATTRS DFU_RT_ATTR_WILL_DETACH \
+        | DFU_RT_ATTR_CAN_UPLOAD | DFU_RT_ATTR_CAN_DNLOAD
+
+// Detach timeout is only relevant when ATTR_WILL_DETACH is unset
+#define DFU_RT_DETACH_TIMEOUT 0
+// This should be only relevant for actual firmware uploads (the actual
+// value is read from the bootloader after reset), but specify a
+// conservative value here in case any tool fails to reread the value
+// after reset.
+// The max packet size for EP0 control transfers is specified in the
+// device descriptor.
+#define DFU_RT_TRANSFER_SIZE 64
+#define DFU_RT_DFU_VERSION 0x0101 // DFU 1.1
+
+#define DFU_RT_IFACE_NUM 2 // XXX: Hardcoded
+
+#define DFU_RT_IFACE_DESC_SIZE 18U
+#define DFU_RT_IFACE_DESC \
+  /*DFU Runtime interface descriptor*/ \
+  0x09,                                 /* bLength: Endpoint Descriptor size */                        \
+  USB_DESC_TYPE_INTERFACE,              /* bDescriptorType: */                                         \
+  DFU_RT_IFACE_NUM,                     /* bInterfaceNumber: Number of Interface */ \
+  0x00,                                 /* bAlternateSetting: Alternate setting */                     \
+  0x00,                                 /* bNumEndpoints: no endpoints used (only control endpoint) */ \
+  0xFE,                                 /* bInterfaceClass: Application Specific */                    \
+  0x01,                                 /* bInterfaceSubClass: Device Firmware Upgrade Code*/          \
+  0x01,                                 /* bInterfaceProtocol: Runtime Protocol*/                      \
+  /* TODO: Put a meaningful string here, which shows up in the Windows * */ \
+  /* device manager when no driver is installed yet. */ \
+  0x00,                                 /* iInterface: */                                              \
+  \
+  /*DFU Runtime Functional Descriptor*/ \
+  0x09,                                 /* bFunctionLength */                                          \
+  DFU_DESCRIPTOR_TYPE,                  /* bDescriptorType: DFU Functional */                          \
+  DFU_RT_ATTRS,                         /* bmAttributes: DFU Attributes */                             \
+  LOBYTE(DFU_RT_DETACH_TIMEOUT),        /* wDetachTimeout */                                           \
+  HIBYTE(DFU_RT_DETACH_TIMEOUT),        \
+  LOBYTE(DFU_RT_TRANSFER_SIZE),         /* wTransferSize */                                            \
+  HIBYTE(DFU_RT_TRANSFER_SIZE),         \
+  LOBYTE(DFU_RT_DFU_VERSION),           /* bcdDFUVersion */                                            \
+  HIBYTE(DFU_RT_DFU_VERSION)
+
+/**
+  * @brief  USBD_DFU_Runtime_Control
+  *         Manage the DFU interface control requests
+  * @param  bRequest: Command code from request
+  * @param  wValue: Value from request
+  * @param  data: Buffer for result
+  * @param  length: Number of data to be sent (in bytes)
+  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
+  */
+static int8_t USBD_DFU_Runtime_Control(uint8_t bRequest, uint16_t wValue, uint8_t *data, uint16_t len)
+{
+  UNUSED(wValue);
+  switch (bRequest) {
+    case DFU_GETSTATUS:
+      if (len != 6) {
+        return (USBD_FAIL);
+      }
+
+      data[0] = DFU_ERROR_NONE;
+      // Minimum delay until next GET_STATUS
+      data[1] = data[2] = data[3] = 0;
+      data[4] = APP_STATE_IDLE;
+      // State string descriptor
+      data[5] = 0;
+
+      return (USBD_OK);
+
+    case DFU_DETACH:
+      scheduleBootloaderReset();
+      return (USBD_OK);
+
+    case DFU_GETSTATE:
+      if (len != 1) {
+        return (USBD_FAIL);
+      }
+      data[0] = APP_STATE_IDLE;
+      return (USBD_OK);
+
+    default:
+      return (USBD_FAIL);
+  }
+}
+
+#endif // __USB_DFU_RUNTIME_H
diff --git a/cores/arduino/stm32/usb/usbd_conf.h b/cores/arduino/stm32/usb/usbd_conf.h
index c1a4808380..d0c9a7c8f5 100644
--- a/cores/arduino/stm32/usb/usbd_conf.h
+++ b/cores/arduino/stm32/usb/usbd_conf.h
@@ -70,7 +70,7 @@ extern "C" {
 #endif
 
 #ifndef USBD_MAX_NUM_INTERFACES
-#define USBD_MAX_NUM_INTERFACES             2U
+#define USBD_MAX_NUM_INTERFACES             3U
 #endif /* USBD_MAX_NUM_INTERFACES */
 
 #ifndef USBD_MAX_NUM_CONFIGURATION
diff --git a/cores/arduino/stm32/usb/usbd_desc.c b/cores/arduino/stm32/usb/usbd_desc.c
index 229112ea5f..6c92dc256c 100644
--- a/cores/arduino/stm32/usb/usbd_desc.c
+++ b/cores/arduino/stm32/usb/usbd_desc.c
@@ -170,9 +170,9 @@ __ALIGN_BEGIN uint8_t USBD_Class_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
   0x00,                       /* bcdUSB */
 #endif
   0x02,
-  0x02,                       /* bDeviceClass */
-  0x02,                       /* bDeviceSubClass */
-  0x00,                       /* bDeviceProtocol */
+  0xEF,                       /* bDeviceClass (Miscellaneous) */
+  0x02,                       /* bDeviceSubClass (Common Class) */
+  0x01,                       /* bDeviceProtocol (Interface Association Descriptor) */
   USB_MAX_EP0_SIZE,           /* bMaxPacketSize */
   LOBYTE(USBD_VID),           /* idVendor */
   HIBYTE(USBD_VID),           /* idVendor */
diff --git a/cores/arduino/stm32/usb/usbd_if.c b/cores/arduino/stm32/usb/usbd_if.c
index e579140f72..8a8c60301a 100644
--- a/cores/arduino/stm32/usb/usbd_if.c
+++ b/cores/arduino/stm32/usb/usbd_if.c
@@ -115,6 +115,56 @@
   #define USBD_DP_TRICK
 #endif
 
+/**
+ * @brief  USBD_early_startup_delay
+ * @param  us - number of us to delay
+ * @retval None
+ *
+ * This is a minimal delay which is usable in very early startup, when
+ * nothing has been initialized yet (no clocks, no memory, no systick
+ * timer). It works by counting CPU cycles, and assumes the system is
+ * still running from the HSI.
+ *
+ * If the systick timer is already enabled, this assumes everything is
+ * intialized and instead used the normal delayMicroseconds function.
+ *
+ * Max delay depends on HSI, but is around 268 sec with 16Mhz HSI.
+ */
+void USBD_early_startup_delay_us(uint32_t us)
+{
+  if (SysTick->CTRL & SysTick_CTRL_ENABLE_Msk) {
+    delayMicroseconds(us);
+    return;
+  }
+
+#if !HSI_VALUE
+#error "Missing HSI_VALUE"
+#endif
+
+#if HSI_VALUE % 4000000 != 0
+#warning "HSI not multiple of 4MHz, early startup delay will be inaccurate!"
+#endif
+
+  // To simplify this calculation, this assumes the HSI runs at a
+  // multiple of 4Mhz (1Mhz to scale to us, times 4 to account for 4
+  // cycles per loop).
+  const uint32_t loops_per_us = (HSI_VALUE / 1000000) / 4;
+  const uint32_t loop_count = us * loops_per_us;
+
+  // Assembly loop, designed to run at exactly 4 cycles per loop.
+  asm volatile(
+    // Use the unified ARM/Thumb syntax, which seems to be more
+    // universally used and corresponds to what avr-objdump outputs
+    // See https://sourceware.org/binutils/docs/as/ARM_002dInstruction_002dSet.html
+    ".syntax unified\n\t"
+    "1:\n\t"
+    "nop                         /* 1 cycle */\n\t"
+    "subs %[loop], %[loop], #1    /* 1 cycle */\n\t"
+    "bne  1b                     /* 2 if taken, 1 otherwise */\n\t"
+    : : [loop] "l"(loop_count)
+  );
+}
+
 /**
   * @brief  Force to re-enumerate USB.
   *
@@ -134,7 +184,7 @@ WEAK void USBD_reenumerate(void)
   digitalWriteFast(USBD_PULLUP_CONTROL_PINNAME, USBD_DETACH_LEVEL);
 
   /* Wait */
-  delay(USBD_ENUM_DELAY);
+  USBD_early_startup_delay_us(USBD_ENUM_DELAY * 1000);
 
   /* Attach */
 #if defined(USBD_DP_TRICK)
@@ -144,9 +194,18 @@ WEAK void USBD_reenumerate(void)
   digitalWriteFast(USBD_PULLUP_CONTROL_PINNAME, USBD_ATTACH_LEVEL);
 #endif /* defined(USBD_PULLUP_CONTROL_FLOATING) */
 #elif defined(USBD_HAVE_INTERNAL_PULLUPS)
-  USB_DevDisconnect(USBD_USB_INSTANCE);
-  delay(USBD_ENUM_DELAY);
-  USB_DevConnect(USBD_USB_INSTANCE);
+#ifdef USB_OTG_DCTL_SDIS
+  uint32_t USBx_BASE = (uint32_t)USBD_USB_INSTANCE;
+  USBx_DEVICE->DCTL |= USB_OTG_DCTL_SDIS;
+  //USB_DevDisconnect(USBD_USB_INSTANCE);
+  USBD_early_startup_delay_us(USBD_ENUM_DELAY * 1000);
+  //USB_DevConnect(USBD_USB_INSTANCE);
+  USBx_DEVICE->DCTL &= ~USB_OTG_DCTL_SDIS;
+#else
+  USBD_USB_INSTANCE->BCDR &= (uint16_t)(~(USB_BCDR_DPPU));
+  USBD_early_startup_delay_us(USBD_ENUM_DELAY * 1000);
+  USBD_USB_INSTANCE->BCDR |= (uint16_t)(USB_BCDR_DPPU);
+#endif
 #else
 #warning "No USB attach/detach method, USB might not be reliable through system resets"
 #endif
diff --git a/libraries/SrcWrapper/src/stm32/bootloader.c b/libraries/SrcWrapper/src/stm32/bootloader.c
index d7d86e968e..f75503f4bf 100644
--- a/libraries/SrcWrapper/src/stm32/bootloader.c
+++ b/libraries/SrcWrapper/src/stm32/bootloader.c
@@ -1,8 +1,156 @@
+#include <stdbool.h>
+
 #include "bootloader.h"
 
 #include "stm32_def.h"
 #include "backup.h"
+#include "stm32yyxx_ll_system.h"
+#include "usbd_if.h"
+
+/*
+ * STM32 built-in bootloader in system memory support
+ */
+#if !defined(STM32MP1xx)
+static const uint32_t BOOTLOADER_DELAY_MS = 250;
+static bool BootIntoBootloaderAfterReset;
+static uint32_t countdown = 0;
+
+
+/* Request to jump to system memory boot */
+WEAK void jumpToBootloaderRequested(void)
+{
+  BootIntoBootloaderAfterReset = true;
+  NVIC_SystemReset();
+}
+
+// Defined in startup assembly code
+void Original_Reset_Handler();
+
+// This overrides the Reset_Handler that is run on reset before
+// *anything* else (including memory initialization). Only the stack
+// pointer is set up by this time.
+void Reset_Handler()
+{
+  // Jump to the bootloader if needed.
+  jumpToBootloaderIfRequested();
+
+  // Continue with regular startup by calling the original reset handler
+  Original_Reset_Handler();
+}
+
+/* Figure out where the bootloader lives, remapping memory if needed,
+ * and return its address. The returned address should point to the
+ * bootloader's interrupt vector table, so to a SP to load followed by
+ * an address to jump to.
+ */
+WEAK uint32_t bootloaderAddress()
+{
+#ifdef __HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH
+  /* Remap system Flash memory at address 0x00000000 */
+  __HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH();
+  // Make the variable volatile to prevent the compiler from seeing a
+  // null-pointer dereference (which is undefined in C) and generating
+  // an UDF (undefined) instruction instead of just loading address 0.
+  return 0;
+#elif defined(STM32F1xx) && (defined (STM32F101xG) || defined (STM32F103xG))
+  // From AN2606, table 136 "Bootloader device-dependent parameters"
+  // STM32F10xxx XL-density, aka 768K-1M flash, aka F and G flash size codes
+  return 0x1FFFE000;
+#elif defined(STM32F1xx) && defined (STM32F105xC) || defined (STM32F107xC)
+  // STM32F105xx/107xx from AN2606, table 136 "Bootloader device-dependent parameters"
+  return 0x1FFFB000;
+#elif defined (STM32F100xB) || defined (STM32F100xE) || defined (STM32F101x6) || \
+  defined (STM32F101xB) || defined (STM32F101xE) ||  defined (STM32F102x6) || \
+  defined (STM32F102xB) || defined (STM32F103x6) || defined (STM32F103xB) || \
+  defined (STM32F103xE)
+  // STM32F10xxx from AN2606, table 136 "Bootloader device-dependent parameters"
+  // This does not check for STM32F1xx, to prevent catching
+  // STM32F105xx/STM32F107xx or XL-density chips that are introduced later.
+  // Defines from system/Drivers/CMSIS/Device/ST/STM32F1xx/Include/stm32f1xx.h
+  return 0x1FFFF000;
+#elif defined(STM32F7xx) || defined(STM32H7xx)
+  // From AN2606, table 136 "Bootloader device-dependent parameters"
+  // TODO: Reference manual for has a different value...
+  return 0x1FF00000;
+#else
+#error "System flash address unknown for this CPU"
+#endif
+}
+
+
+/* Jump to system memory boot from user application */
+WEAK void jumpToBootloaderIfRequested(void)
+{
+  // Boot into bootloader if BootIntoBootloaderAfterReset is set.
+  // Note that BootIntoBootloaderAfterReset is a noinit variable, so it
+  // s not automatically initialized on startup (so it can keep its
+  // value across resets). At initial poweron, its value can be
+  // *anything*, so only consider its value after a software reset. In
+  // all cases, clear its value (this both takes care of giving it an
+  // initial value after power-up, and prevents booting into the
+  // bootloader more than once for a single request).
+  bool doBootloader = __HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) && BootIntoBootloaderAfterReset;
+  BootIntoBootloaderAfterReset = false;
+
+  if (doBootloader) {
+    __HAL_RCC_CLEAR_RESET_FLAGS();
+
+#ifdef USBCON
+    USBD_reenumerate();
+#endif
+
+    uint32_t sys = bootloaderAddress();
+
+    // This is assembly to prevent modifying the stack pointer after
+    // loading it, and to ensure a jump (not call) to the bootloader.
+    // Not sure if the barriers are really needed, they were taken from
+    // https://github.com/GrumpyOldPizza/arduino-STM32L4/blob/ac659033eadd50cfe001ba1590a1362b2d87bb76/system/STM32L4xx/Source/boot_stm32l4xx.c#L159-L165
+    asm volatile(
+      "ldr r0, [%[sys], #0]   \n\t"  // get address of stack pointer
+      "msr msp, r0            \n\t"  // set stack pointer
+      "ldr r0, [%[sys], #4]   \n\t"  // get address of reset handler
+      "dsb                    \n\t"  // data sync barrier
+      "isb                    \n\t"  // instruction sync barrier
+      "bx r0                  \n\t"  // branch to bootloader
+      : : [sys] "l"(sys) : "r0"
+    );
+
+    __builtin_unreachable();
+  }
+}
+
+/**
+  * Scheduler a reset into the bootloader after a delay.
+  */
+void scheduleBootloaderReset()
+{
+  countdown = BOOTLOADER_DELAY_MS;
+}
+
+/**
+  * Cancel a previously scheduled bootloader reset.
+  */
+void cancelBootloaderReset()
+{
+  countdown = 0;
+}
+#endif /* !STM32MP1xx */
+
+/**
+  * Bootloader systick handler, should be called every ms
+  */
+void bootloaderSystickHandler()
+{
+#ifndef STM32MP1xx
+  if (countdown && --countdown == 0) {
+    jumpToBootloaderRequested();
+  }
+#endif /* !STM32MP1xx */
+}
 
+/*
+ * Legacy maple bootloader support
+ */
 #ifdef BL_LEGACY_LEAF
 void dtr_togglingHook(uint8_t *buf, uint32_t *len)
 {
@@ -17,6 +165,9 @@ void dtr_togglingHook(uint8_t *buf, uint32_t *len)
 }
 #endif /* BL_LEGACY_LEAF */
 
+/*
+ * HID bootloader support
+ */
 #ifdef BL_HID
 void dtr_togglingHook(uint8_t *buf, uint32_t *len)
 {
diff --git a/libraries/SrcWrapper/src/stm32/clock.c b/libraries/SrcWrapper/src/stm32/clock.c
index bea7adcdbc..4c65c98826 100644
--- a/libraries/SrcWrapper/src/stm32/clock.c
+++ b/libraries/SrcWrapper/src/stm32/clock.c
@@ -37,6 +37,7 @@
   */
 #include "backup.h"
 #include "clock.h"
+#include "bootloader.h"
 #include "stm32yyxx_ll_cortex.h"
 
 #ifdef __cplusplus
@@ -88,6 +89,7 @@ void SysTick_Handler(void)
   HAL_IncTick();
   HAL_SYSTICK_IRQHandler();
   osSystickHandler();
+  bootloaderSystickHandler();
 }
 
 /**
diff --git a/libraries/SrcWrapper/src/stm32/hw_config.c b/libraries/SrcWrapper/src/stm32/hw_config.c
index 3434d6da96..20013b2aa5 100644
--- a/libraries/SrcWrapper/src/stm32/hw_config.c
+++ b/libraries/SrcWrapper/src/stm32/hw_config.c
@@ -35,10 +35,11 @@
   *
   ******************************************************************************
   */
-#include "stm32_def.h"
+#include "bootloader.h"
+#include "dwt.h"
 #include "hw_config.h"
 #include "usbd_if.h"
-#include "dwt.h"
+#include "stm32_def.h"
 
 #ifdef __cplusplus
 extern "C" {