From 815cb80bd6026b51ec7696ddd3fb5d1465c72e09 Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Sat, 21 Feb 2026 15:01:54 +0000 Subject: [PATCH 1/8] Major functionality update Added support for F1 and G4 boards Serial aliased to SerialTinyUSB USB initialisation. task polloing, CDC flushing and DFU bootloader entry added added txt file with lines for boards.txt --- .../ports/stm32/Adafruit_TinyUSB_stm32.cpp | 335 +++++++++++++++--- .../ports/stm32/boards_txt_additions.txt | 29 ++ src/arduino/ports/stm32/tusb_config_stm32.h | 79 +++-- 3 files changed, 365 insertions(+), 78 deletions(-) create mode 100644 src/arduino/ports/stm32/boards_txt_additions.txt diff --git a/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp b/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp index f40a2a93..f9e0f15b 100644 --- a/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp +++ b/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp @@ -24,15 +24,28 @@ #include "tusb_option.h" -#if (defined(ARDUINO_ARCH_STM32) || \ - defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)) && \ - CFG_TUD_ENABLED +#if (defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)) && CFG_TUD_ENABLED #define USE_HAL_DRIVER + +// Include appropriate HAL headers based on MCU family +#if defined(STM32F1xx) + #include "stm32f1xx_hal.h" + #include "stm32f1xx_hal_rcc.h" +#elif defined(STM32F4xx) + #include "stm32f4xx_hal.h" + #include "stm32f4xx_hal_rcc.h" +#elif defined(STM32G4xx) + #include "stm32g4xx_hal.h" + #include "stm32g4xx_hal_rcc.h" + #include "stm32g4xx_hal_rcc_ex.h" + #define GPIO_AF10_USB 0x0AU // AF10 = USB FS on STM32G4xx (no named macro in HAL) +#else + #error "Unsupported STM32 family - only F1xx, F4xx and G4xx are currently supported" +#endif + #include "Arduino.h" #include "arduino/Adafruit_TinyUSB_API.h" -#include "stm32f4xx_hal.h" -#include "stm32f4xx_hal_rcc.h" #include "tusb.h" //--------------------------------------------------------------------+ @@ -40,61 +53,287 @@ //--------------------------------------------------------------------+ extern "C" { -void OTG_FS_IRQHandler(void) { tud_int_handler(0); } +#if defined(STM32F4xx) +void OTG_FS_IRQHandler(void) +{ + tud_int_handler(0); +} + +#elif defined(STM32F1xx) +void USB_LP_CAN1_RX0_IRQHandler(void) +{ + tud_int_handler(0); +} + +#elif defined(STM32G4xx) +// Guard against duplicate symbol from the core USBDevice library (usbd_conf.c). +// The boards.txt TinyUSB menu entry must set build.enable_usb= (empty) to stop +// the core USB stack compiling. This #ifndef is a belt-and-braces backup. +#ifndef USBCON +void USB_LP_IRQHandler(void) +{ + tud_int_handler(0); +} +void USB_HP_IRQHandler(void) +{ + tud_int_handler(0); +} +void USBWakeUp_IRQHandler(void) +{ + tud_int_handler(0); +} +#endif // USBCON + +#endif // MCU family IRQ handlers + +// tud_task() must be called regularly to service USB events. On STM32 the +// Arduino core does NOT call yield() at the bottom of loop(), so we cannot +// rely on yield() alone. Instead we hook HAL_IncTick() which is called every +// 1 ms from the SysTick ISR — giving us a reliable 1 kHz task pump. +// +// IMPORTANT: tud_task() must NOT be called from within an ISR context. +// We therefore only set a flag inside the ISR-called HAL_IncTick(), and +// do the actual work inside yield() which runs in thread context. +// +// yield() is still provided because it is called by delay() and gives us +// a thread-context opportunity to service the flag immediately, rather than +// waiting for the next loop() iteration. TINYUSB_NEED_POLLING_TASK is NOT +// defined — sketches do not need a manual TinyUSBDevice.task() call. + +static volatile bool _tusb_task_pending = false; -void yield(void) { - tud_task(); - if (tud_cdc_connected()) { - tud_cdc_write_flush(); - } +void yield(void) +{ + if (_tusb_task_pending) { + _tusb_task_pending = false; + tud_task(); + if (tud_cdc_connected()) { + tud_cdc_write_flush(); + } + } } } // extern "C" +// serialEventRun() is called by the STM32 Arduino core after every loop() +// iteration (it is the standard Arduino hook for background processing). +// This guarantees tud_task() runs even in tight loops that never call delay(). +void serialEventRun(void) +{ + yield(); +} + +// HAL_IncTick() is called every 1 ms from the SysTick_Handler ISR. +// The STM32 HAL defines it as __weak, so we override it here. +// We call the HAL's own uwTick increment, then set the flag for yield(). +extern "C" void HAL_IncTick(void) +{ + // Keep the HAL tick counter running (normally done by the weak default). + // uwTickFreq (HAL_TickFreqTypeDef) was added in newer HAL versions and is + // not present in the HAL bundled with the STM32 Arduino core, so we + // increment by 1 directly — correct for the default 1 ms SysTick period. + extern __IO uint32_t uwTick; + uwTick += 1U; + + // Signal yield() to run tud_task() at the next thread-context opportunity. + _tusb_task_pending = true; +} + +//--------------------------------------------------------------------+ +// Automatic initialisation via initVariant() +// +// The STM32 Arduino core calls initVariant() from main() after clocks +// and HAL are ready, but BEFORE setup() runs. +// +// TinyUSB_Device_Init(0) configures the hardware and calls tud_init(). +// D+ goes high and the host begins enumeration (~500 ms window). +// tud_task() is serviced every 1 ms via HAL_IncTick() → _tusb_task_pending +// → yield(), ensuring host setup packets are answered even if the sketch +// never calls delay() or TinyUSBDevice.task(). +//--------------------------------------------------------------------+ +void initVariant(void) +{ + TinyUSB_Device_Init(0); +} + //--------------------------------------------------------------------+ // Porting API //--------------------------------------------------------------------+ -void TinyUSB_Port_InitDevice(uint8_t rhport) { - (void)rhport; - - // Enable clocks FIRST - __HAL_RCC_GPIOA_CLK_ENABLE(); - __HAL_RCC_USB_OTG_FS_CLK_ENABLE(); - - // Configure USB pins (PA11 = DM, PA12 = DP) - GPIO_InitTypeDef GPIO_InitStruct = {}; - GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - - // Enable USB IRQ - NVIC_SetPriority(OTG_FS_IRQn, 0); - NVIC_EnableIRQ(OTG_FS_IRQn); - - // Disable VBUS sensing (we're bus-powered, don't need it) - USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; - USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSBSEN; - USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSASEN; - - // Initialize TinyUSB device stack - tud_init(rhport); +void TinyUSB_Port_InitDevice(uint8_t rhport) +{ + (void) rhport; + +#if defined(STM32F4xx) + //================================================================= + // STM32F4xx: USB OTG FS Configuration + //================================================================= + + // Enable clocks FIRST + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_USB_OTG_FS_CLK_ENABLE(); + + // Configure USB pins (PA11 = DM, PA12 = DP) + GPIO_InitTypeDef GPIO_InitStruct = {}; + GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + // Enable USB IRQ + NVIC_SetPriority(OTG_FS_IRQn, 0); + NVIC_EnableIRQ(OTG_FS_IRQn); + + // Disable VBUS sensing (bus-powered) + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSBSEN; + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSASEN; + +#elif defined(STM32F1xx) + //================================================================= + // STM32F1xx: USB FS Device Configuration + //================================================================= + + // On F1xx PA11/PA12 are automatically taken over by the USB peripheral + // when its clock is enabled - no GPIO AF configuration needed + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_USB_CLK_ENABLE(); + + // Enable USB IRQ + NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 0); + NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); + +#elif defined(STM32G4xx) + //================================================================= + // STM32G4xx: USB FS Device Configuration (WeAct G431, Nucleo, etc.) + //================================================================= + + // ---- Step 1: Enable HSI48 if not already on -------------------- + // Most Arduino core variants for G4 do NOT enable HSI48 by default. + // On G4, HSI48 is in the CRRCR register (Clock Recovery RC Register), + // not the CR register. We use direct register access to avoid + // disrupting the already-running PLL that feeds SYSCLK. + if (!(RCC->CRRCR & RCC_CRRCR_HSI48ON)) { + RCC->CRRCR |= RCC_CRRCR_HSI48ON; // Turn on HSI48 + while (!(RCC->CRRCR & RCC_CRRCR_HSI48RDY)); // Wait for it to stabilize + } + + // ---- Step 2: Route HSI48 to the USB clock domain --------------- + // The USB peripheral mux might default to PLLQ (which would be 170MHz + // and completely wrong). Force it to HSI48 (48 MHz). + RCC_PeriphCLKInitTypeDef periphClk = {}; + periphClk.PeriphClockSelection = RCC_PERIPHCLK_USB; + periphClk.UsbClockSelection = RCC_USBCLKSOURCE_HSI48; + HAL_RCCEx_PeriphCLKConfig(&periphClk); + + // ---- Step 3: Enable CRS for HSI48 auto-trimming via USB SOF ---- + // HSI48 alone is ~1% (10000 ppm) accurate. CRS uses USB SOF + // pulses (every 1 ms) to trim it down to <500 ppm, well within + // the USB FS spec of ±2500 ppm. + __HAL_RCC_CRS_CLK_ENABLE(); + RCC_CRSInitTypeDef crs = {}; + crs.Prescaler = RCC_CRS_SYNC_DIV1; + crs.Source = RCC_CRS_SYNC_SOURCE_USB; + crs.Polarity = RCC_CRS_SYNC_POLARITY_RISING; + crs.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000, 1000); + crs.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT; + crs.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT; + HAL_RCCEx_CRSConfig(&crs); + + // ---- Step 4: GPIO and USB peripheral clocks -------------------- + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_USB_CLK_ENABLE(); + + // Configure PA11 (DM) and PA12 (DP) as USB alternate function (AF10) + GPIO_InitTypeDef GPIO_InitStruct = {}; + GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF10_USB; // defined as 0x0AU above + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + // ---- Step 5: Enable USB interrupts ----------------------------- + NVIC_SetPriority(USB_LP_IRQn, 0); + NVIC_EnableIRQ(USB_LP_IRQn); + NVIC_SetPriority(USB_HP_IRQn, 0); + NVIC_EnableIRQ(USB_HP_IRQn); + NVIC_SetPriority(USBWakeUp_IRQn, 0); + NVIC_EnableIRQ(USBWakeUp_IRQn); + + // ---- Step 6: Enable USB peripheral power ----------------------- + // The USB peripheral on G4 has a power domain switch (USBRST). + // We must explicitly bring the peripheral out of reset. + __HAL_RCC_USB_FORCE_RESET(); + HAL_Delay(1); + __HAL_RCC_USB_RELEASE_RESET(); + +#endif + + // Initialize TinyUSB device stack (common for all MCUs) + tud_init(rhport); + + // Give the peripheral a moment to stabilize before attempting connection + HAL_Delay(10); } -void TinyUSB_Port_EnterDFU(void) { - // Optional - implement bootloader entry if needed +void TinyUSB_Port_EnterDFU(void) +{ + // Reboot into the ST system-memory (ROM) DFU bootloader. + // + // This is called by the Adafruit TinyUSB library (Adafruit_USBD_CDC.cpp) + // when it detects the Arduino IDE "touch 1200" sequence (CDC port opened + // at 1200 baud then closed). + // + // Approach: write the STM32duino magic value (0x515B) into the appropriate + // backup register, then issue a system reset. The STM32duino core startup + // code checks this register and jumps to the ST ROM DFU bootloader if the + // value matches. + // + // We disconnect from USB first and give the host time to register the + // disconnection before resetting, so the IDE knows to start looking for + // a DFU device rather than waiting for the COM port to reappear. + + tud_disconnect(); + HAL_Delay(100); + + // Enable access to the backup domain + __HAL_RCC_PWR_CLK_ENABLE(); + HAL_PWR_EnableBkUpAccess(); + +#if defined(STM32F1xx) + __HAL_RCC_BKP_CLK_ENABLE(); + BKP->DR1 = 0x515B; +#elif defined(STM32F4xx) + __HAL_RCC_RTC_ENABLE(); + RTC->BKP4R = 0x515B; +#elif defined(STM32G4xx) + __HAL_RCC_RTC_ENABLE(); + TAMP->BKP4R = 0x515B; +#endif + + NVIC_SystemReset(); } -uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { - volatile uint32_t *uid = - (volatile uint32_t *)0x1FFF7A10; // STM32F411 UID base - uint32_t *serial_32 = (uint32_t *)serial_id; - serial_32[0] = uid[0]; - serial_32[1] = uid[1]; - serial_32[2] = uid[2]; - return 12; +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) +{ +#if defined(STM32F4xx) + // STM32F4xx UID base address (e.g. F411) + volatile uint32_t *uid = (volatile uint32_t *)0x1FFF7A10; +#elif defined(STM32F1xx) + // STM32F1xx UID base address (e.g. F103) + volatile uint32_t *uid = (volatile uint32_t *)0x1FFFF7E8; +#elif defined(STM32G4xx) + // STM32G4xx UID base address (e.g. G431, G474) + volatile uint32_t *uid = (volatile uint32_t *)0x1FFF7590; +#endif + + uint32_t *serial_32 = (uint32_t *)serial_id; + serial_32[0] = uid[0]; + serial_32[1] = uid[1]; + serial_32[2] = uid[2]; + return 12; } #endif // ARDUINO_ARCH_STM32 \ No newline at end of file diff --git a/src/arduino/ports/stm32/boards_txt_additions.txt b/src/arduino/ports/stm32/boards_txt_additions.txt new file mode 100644 index 00000000..b3a17457 --- /dev/null +++ b/src/arduino/ports/stm32/boards_txt_additions.txt @@ -0,0 +1,29 @@ +# boards.txt additions for Adafruit TinyUSB (STM32) +# +# These lines need to be added to the STM32duino core's boards.txt file, located at: +# +# Arduino15/packages/STMicroelectronics/hardware/stm32/[version]/boards.txt +# +# HOW TO FIND YOUR BOARD'S PREFIX: +# Search boards.txt for your board name (e.g. "BlackPill", "BluePill", "Nucleo"). +# The prefix is the part before the first dot on any existing line for that board. +# For example, if you see "GenF4.name=Generic F4", your prefix is "GenF4". +# +# HOW TO ADD: +# Find the section for your board and add the appropriate lines below. +# Replace the prefix (GenF4 / GenF1 / GenG4) with the one for your board. +# Add the lines alongside any existing menu.usb entries for that board. +# +# ----------------------------------------------------------------------- + +# STM32F4 (e.g. Generic F4 -- tested on WeAct STM32F411 BlackPill) +GenF4.menu.usb.TinyUSB=Adafruit TinyUSB +GenF4.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB + +# STM32F1 (e.g. Generic F1 -- tested on STM32F103 BluePill) +GenF1.menu.usb.TinyUSB=Adafruit TinyUSB +GenF1.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB + +# STM32G4 (e.g. Generic G4 -- tested on WeAct STM32G431) +GenG4.menu.usb.TinyUSB=Adafruit TinyUSB +GenG4.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB diff --git a/src/arduino/ports/stm32/tusb_config_stm32.h b/src/arduino/ports/stm32/tusb_config_stm32.h index 9b719450..bc54d22f 100644 --- a/src/arduino/ports/stm32/tusb_config_stm32.h +++ b/src/arduino/ports/stm32/tusb_config_stm32.h @@ -25,12 +25,23 @@ #ifndef TUSB_CONFIG_STM32_H_ #define TUSB_CONFIG_STM32_H_ -//-------------------------------------------------------------------- -// COMMON CONFIGURATION -//-------------------------------------------------------------------- -// MCU / OS -#define CFG_TUSB_MCU OPT_MCU_STM32F4 -#define CFG_TUSB_OS OPT_OS_NONE +// USB Port +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#define CFG_TUSB_RHPORT0_SPEED OPT_FULL_SPEED +#define CFG_TUSB_RHPORT1_MODE OPT_MODE_NONE + +// MCU / OS - Auto-detect based on STM32 family +#if defined(STM32F1xx) + #define CFG_TUSB_MCU OPT_MCU_STM32F1 +#elif defined(STM32F4xx) + #define CFG_TUSB_MCU OPT_MCU_STM32F4 +#elif defined(STM32G4xx) + #define CFG_TUSB_MCU OPT_MCU_STM32G4 +#else + #error "Unsupported STM32 family - only F1xx, F4xx and G4xx are currently supported" +#endif + +#define CFG_TUSB_OS OPT_OS_NONE // Debug #ifndef CFG_TUSB_DEBUG @@ -40,37 +51,45 @@ #define CFG_TUSB_MEM_SECTION #define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) -//-------------------------------------------------------------------- -// DEVICE CONFIGURATION -//-------------------------------------------------------------------- -#define CFG_TUD_ENABLED 1 -#define CFG_TUD_MAX_SPEED OPT_MODE_FULL_SPEED // TODO some are highspeed +// Device stack +#define CFG_TUD_ENABLED 1 #define CFG_TUD_ENDPOINT0_SIZE 64 // Classes -#define CFG_TUD_CDC 1 -#define CFG_TUD_MSC 1 -#define CFG_TUD_HID 1 -#define CFG_TUD_MIDI 1 -#define CFG_TUD_VENDOR 1 +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 1 +#define CFG_TUD_HID 1 +#define CFG_TUD_MIDI 1 +#define CFG_TUD_VENDOR 0 // Buffer sizes -#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define CFG_TUD_MSC_EP_BUFSIZE 512 -#define CFG_TUD_HID_EP_BUFSIZE 64 -#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128) -#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128) -#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_RX_BUFSIZE 64 +#define CFG_TUD_CDC_TX_BUFSIZE 64 +#define CFG_TUD_CDC_EP_BUFSIZE 64 +#define CFG_TUD_HID_EP_BUFSIZE 64 +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 +#define CFG_TUD_MSC_EP_BUFSIZE 512 +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 // Serial Redirect #define Serial SerialTinyUSB -//-------------------------------------------------------------------- -// Host Configuration -//-------------------------------------------------------------------- -#define CFG_TUH_ENABLED 0 // disable for now +// TINYUSB_NEED_POLLING_TASK is intentionally NOT defined here. +// +// The STM32 port implements yield() to call tud_task() automatically +// whenever the Arduino core calls yield() (e.g. inside delay(), and at +// the bottom of every loop() iteration on cores that wrap loop() with +// a yield() call). This means TinyUSB is serviced without any explicit +// TinyUSBDevice.task() call in the sketch's loop(). +// +// Sketches that still contain the legacy polling guard: +// +// #ifdef TINYUSB_NEED_POLLING_TASK +// TinyUSBDevice.task(); +// #endif +// +// will simply compile out the block, which is correct behaviour. -#endif +#endif // TUSB_CONFIG_STM32_H_ \ No newline at end of file From 41ce33062b90947d6ea903cd4bc9fe5cb88a0092 Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Sat, 21 Feb 2026 15:35:40 +0000 Subject: [PATCH 2/8] Update Readme.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b6497116..1d198abd 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ Following is cores without built-in support - **mbed_rp2040** - **[stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)** - - Still WIP, only support/tested with F4, only support OTG_FS + - Still WIP, only supports F1, F4 & G4 so far. Requires additions to stm32duino boards.txt which can be found in /ports/stm32. No longer needs manual initialization/CDC flushing. DFU entry working. Serial now works as normal. + + It is still possible to use TinyUSB but with some limits such as: From d4ca7dc3b0da45b502f13e026ad4b2d4dda23f05 Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Sat, 21 Feb 2026 15:37:06 +0000 Subject: [PATCH 3/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d198abd..bbe462aa 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Following is cores without built-in support - **mbed_rp2040** - **[stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)** - - Still WIP, only supports F1, F4 & G4 so far. Requires additions to stm32duino boards.txt which can be found in /ports/stm32. No longer needs manual initialization/CDC flushing. DFU entry working. Serial now works as normal. + - Still WIP, only supports F1, F4 & G4 so far. Requires lines added to stm32duino boards.txt which can be found in /ports/stm32/boards_additions.txt. No longer needs manual initialization/CDC flushing. DFU entry working. Serial now works as normal. From 07a4c12755ce1884ec26b03682e75e7669538a2c Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Thu, 26 Feb 2026 02:53:55 +0000 Subject: [PATCH 4/8] STM32WB55 Support and Reliable Cross-Family DFU Implementation ## Description Major update to STM32 support introducing STM32WB55 support and a complete overhaul of software DFU entry across supported STM32 families. ## Added - STM32WB55 support (tested on WeAct STM32WB55CGU6) - Support via P-NUCLEO-WB55 and NUCLEO WB55 USB Dongle board definitions - Nucleo-64 board definition support (F1/F4/G4 variants should work) - `dfu_boot_stm32wb.c` for reliable WB55 DFU entry - Additional hardware validation: WeAct STM32G474 and STM32F405 ## Changed - DFU implementation rewritten for reliability: - F1/F4/G4 use direct ROM bootloader jump - WB55 uses magic value in `BKP0R` + `NVIC_SystemReset()` with early Reset_Handler interception - `boards.txt` now requires extra lines to enable 1200bps touch - Updated `boards_txt_additions.txt` with new format and Nucleo_64 entry - Added suggested change to readme to reflect status of this port ## Known Limitations - Generic WB55 board definitions lack proper USB clock configuration and are not supported. Use P-NUCLEO-WB55 or NUCLEO WB55 USB Dongle board definitions. - STM32F1 DFU entry requires further validation on boards with factory bootloader. This release significantly improves stability and expands supported hardware. --- README.md | 18 +- .../ports/stm32/Adafruit_TinyUSB_stm32.cpp | 298 ++++++++++++++---- .../ports/stm32/boards_txt_additions.txt | 26 +- src/arduino/ports/stm32/dfu_boot_stm32wb.c | 64 ++++ src/arduino/ports/stm32/tusb_config_stm32.h | 4 +- 5 files changed, 340 insertions(+), 70 deletions(-) create mode 100644 src/arduino/ports/stm32/dfu_boot_stm32wb.c diff --git a/README.md b/README.md index bbe462aa..8944d51d 100644 --- a/README.md +++ b/README.md @@ -47,20 +47,18 @@ Note: For ESP32 port, version before v3.0 requires all descriptors must be speci ### Cores without built-in support -Following is cores without built-in support +The following are cores without built-in support -- **mbed_rp2040** - **[stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)** - - Still WIP, only supports F1, F4 & G4 so far. Requires lines added to stm32duino boards.txt which can be found in /ports/stm32/boards_additions.txt. No longer needs manual initialization/CDC flushing. DFU entry working. Serial now works as normal. - + - Still WIP. Fully functional but with limited number of MCUs supported so far (F1xx, F4xx, some G4xx & WB55). Requires lines added to stm32duino boards.txt which can be found in /ports/stm32/boards_txt_additions.txt. +- **mbed_rp2040** + - It is still possible to use TinyUSB but with some limits such as: + - `TinyUSB_Device_Init()` need to be manually called in setup() + - `TinyUSB_Device_Task()` and/or `TinyUSB_Device_FlushCDC()` may (or not) need to be manually called in loop() + - Use `SerialTinyUSB` name instead of Serial for serial monitor -It is still possible to use TinyUSB but with some limits such as: - -- `TinyUSB_Device_Init()` need to be manually called in setup() -- `TinyUSB_Device_Task()` and/or `TinyUSB_Device_FlushCDC()` may (or not) need to be manually called in loop() -- Use `SerialTinyUSB` name instead of Serial for serial monitor -- And there could be more other issues, using on these cores should be considered as experimental +There could be more other issues on cores without built in support, using on these cores should be considered experimental. ## Class Driver API diff --git a/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp b/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp index f9e0f15b..ec573b87 100644 --- a/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp +++ b/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp @@ -40,8 +40,20 @@ #include "stm32g4xx_hal_rcc.h" #include "stm32g4xx_hal_rcc_ex.h" #define GPIO_AF10_USB 0x0AU // AF10 = USB FS on STM32G4xx (no named macro in HAL) +#elif defined(STM32WBxx) + #include "stm32wbxx_hal.h" + #include "stm32wbxx_hal_rcc.h" + #include "stm32wbxx_hal_rcc_ex.h" + #include "stm32wbxx_hal_gpio_ex.h" + #include "stm32wbxx_ll_hsem.h" + #include "stm32wbxx_ll_rcc.h" + #if defined(STM32WB55xx) + #include "stm32wb55xx.h" + #elif defined(STM32WB35xx) + #include "stm32wb35xx.h" + #endif #else - #error "Unsupported STM32 family - only F1xx, F4xx and G4xx are currently supported" + #error "Unsupported STM32 family - only F1xx, F4xx, G4xx and WBxx are currently supported" #endif #include "Arduino.h" @@ -84,6 +96,18 @@ void USBWakeUp_IRQHandler(void) } #endif // USBCON +#elif defined(STM32WBxx) +#ifndef USBCON +void USB_LP_IRQHandler(void) +{ + tud_int_handler(0); +} +void USB_HP_IRQHandler(void) +{ + tud_int_handler(0); +} +#endif // USBCON + #endif // MCU family IRQ handlers // tud_task() must be called regularly to service USB events. On STM32 the @@ -129,9 +153,6 @@ void serialEventRun(void) extern "C" void HAL_IncTick(void) { // Keep the HAL tick counter running (normally done by the weak default). - // uwTickFreq (HAL_TickFreqTypeDef) was added in newer HAL versions and is - // not present in the HAL bundled with the STM32 Arduino core, so we - // increment by 1 directly — correct for the default 1 ms SysTick period. extern __IO uint32_t uwTick; uwTick += 1U; @@ -150,14 +171,24 @@ extern "C" void HAL_IncTick(void) // tud_task() is serviced every 1 ms via HAL_IncTick() → _tusb_task_pending // → yield(), ensuring host setup packets are answered even if the sketch // never calls delay() or TinyUSBDevice.task(). +// +// NOTE: We do NOT wait for tud_mounted() here. Doing so would block until +// enumeration completes, meaning any MIDI.begin() or other USB interface +// registrations in setup() would be called too late to appear in the +// descriptor. Let setup() run freely and register all interfaces before +// the host finishes enumeration. +//--------------------------------------------------------------------+ +//--------------------------------------------------------------------+ +// Initialization //--------------------------------------------------------------------+ void initVariant(void) { TinyUSB_Device_Init(0); } + //--------------------------------------------------------------------+ -// Porting API +// Hardware Initialisation //--------------------------------------------------------------------+ void TinyUSB_Port_InitDevice(uint8_t rhport) { @@ -168,11 +199,9 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) // STM32F4xx: USB OTG FS Configuration //================================================================= - // Enable clocks FIRST __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USB_OTG_FS_CLK_ENABLE(); - // Configure USB pins (PA11 = DM, PA12 = DP) GPIO_InitTypeDef GPIO_InitStruct = {}; GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; @@ -181,7 +210,6 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - // Enable USB IRQ NVIC_SetPriority(OTG_FS_IRQn, 0); NVIC_EnableIRQ(OTG_FS_IRQn); @@ -200,7 +228,6 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USB_CLK_ENABLE(); - // Enable USB IRQ NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 0); NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); @@ -210,27 +237,18 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) //================================================================= // ---- Step 1: Enable HSI48 if not already on -------------------- - // Most Arduino core variants for G4 do NOT enable HSI48 by default. - // On G4, HSI48 is in the CRRCR register (Clock Recovery RC Register), - // not the CR register. We use direct register access to avoid - // disrupting the already-running PLL that feeds SYSCLK. if (!(RCC->CRRCR & RCC_CRRCR_HSI48ON)) { - RCC->CRRCR |= RCC_CRRCR_HSI48ON; // Turn on HSI48 - while (!(RCC->CRRCR & RCC_CRRCR_HSI48RDY)); // Wait for it to stabilize + RCC->CRRCR |= RCC_CRRCR_HSI48ON; + while (!(RCC->CRRCR & RCC_CRRCR_HSI48RDY)); } // ---- Step 2: Route HSI48 to the USB clock domain --------------- - // The USB peripheral mux might default to PLLQ (which would be 170MHz - // and completely wrong). Force it to HSI48 (48 MHz). RCC_PeriphCLKInitTypeDef periphClk = {}; periphClk.PeriphClockSelection = RCC_PERIPHCLK_USB; periphClk.UsbClockSelection = RCC_USBCLKSOURCE_HSI48; HAL_RCCEx_PeriphCLKConfig(&periphClk); // ---- Step 3: Enable CRS for HSI48 auto-trimming via USB SOF ---- - // HSI48 alone is ~1% (10000 ppm) accurate. CRS uses USB SOF - // pulses (every 1 ms) to trim it down to <500 ppm, well within - // the USB FS spec of ±2500 ppm. __HAL_RCC_CRS_CLK_ENABLE(); RCC_CRSInitTypeDef crs = {}; crs.Prescaler = RCC_CRS_SYNC_DIV1; @@ -245,13 +263,12 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USB_CLK_ENABLE(); - // Configure PA11 (DM) and PA12 (DP) as USB alternate function (AF10) GPIO_InitTypeDef GPIO_InitStruct = {}; GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF10_USB; // defined as 0x0AU above + GPIO_InitStruct.Alternate = GPIO_AF10_USB; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // ---- Step 5: Enable USB interrupts ----------------------------- @@ -262,70 +279,237 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) NVIC_SetPriority(USBWakeUp_IRQn, 0); NVIC_EnableIRQ(USBWakeUp_IRQn); - // ---- Step 6: Enable USB peripheral power ----------------------- - // The USB peripheral on G4 has a power domain switch (USBRST). - // We must explicitly bring the peripheral out of reset. + // ---- Step 6: Reset USB peripheral ------------------------------ __HAL_RCC_USB_FORCE_RESET(); HAL_Delay(1); __HAL_RCC_USB_RELEASE_RESET(); -#endif +#elif defined(STM32WBxx) + //================================================================= + // STM32WBxx: USB FS Device Configuration (WB55, WB35) + // + // CPU2 (the RF coprocessor) uses HSI48 to clock its RNG. When CPU2 + // finishes with the RNG it will shut HSI48 down UNLESS CPU1 holds + // hardware semaphore 5, which is the convention agreed between the + // two cores for signalling "CPU1 is using HSI48, leave it alone". + // + // We acquire semaphore 5 with a 1-step lock BEFORE enabling HSI48. + // This is a lightweight register write — it does not require the + // SHCI/IPCC BLE stack machinery at all. CPU2 respects the semaphore + // regardless of whether a BLE stack is running. + // + // HSI48 is trimmed to exactly 48 MHz by the CRS peripheral, which + // synchronises against USB SOF frames. This is the clock source the + // WB55 ROM bootloader also expects, so DFU entry is seamless. + //================================================================= + + // ---- Step 1: Acquire HSEM 5 to prevent CPU2 killing HSI48 ----- + LL_HSEM_1StepLock(HSEM, 5); + + // ---- Step 2: Enable HSI48 and wait for it to be ready ---------- + LL_RCC_HSI48_Enable(); + while (!LL_RCC_HSI48_IsReady()); + + // ---- Step 3: Route HSI48 to the USB clock domain --------------- + RCC_PeriphCLKInitTypeDef periphClk = {}; + periphClk.PeriphClockSelection = RCC_PERIPHCLK_USB; + periphClk.UsbClockSelection = RCC_USBCLKSOURCE_HSI48; + HAL_RCCEx_PeriphCLKConfig(&periphClk); + + // ---- Step 4: Enable CRS for HSI48 auto-trimming via USB SOF ---- + __HAL_RCC_CRS_CLK_ENABLE(); + RCC_CRSInitTypeDef crs = {}; + crs.Prescaler = RCC_CRS_SYNC_DIV1; + crs.Source = RCC_CRS_SYNC_SOURCE_USB; + crs.Polarity = RCC_CRS_SYNC_POLARITY_RISING; + crs.ReloadValue = RCC_CRS_RELOADVALUE_DEFAULT; + crs.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT; + crs.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT; + HAL_RCCEx_CRSConfig(&crs); + + // ---- Step 5: Enable VddUSB, GPIO, and USB peripheral clocks ---- + __HAL_RCC_GPIOA_CLK_ENABLE(); + + // Enable USB VDD supply — required on WB55 before the USB peripheral + // will come out of reset + HAL_PWREx_EnableVddUSB(); + + __HAL_RCC_USB_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStruct = {}; + GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF10_USB; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + // ---- Step 6: Enable USB interrupts ----------------------------- + // WB55 USB IRQs at priority 3 (leave room for BLE stack at 0-2) + NVIC_SetPriority(USB_LP_IRQn, 3); + NVIC_EnableIRQ(USB_LP_IRQn); + NVIC_SetPriority(USB_HP_IRQn, 3); + NVIC_EnableIRQ(USB_HP_IRQn); + + // ---- Step 7: Reset USB peripheral ------------------------------ + __HAL_RCC_USB_FORCE_RESET(); + HAL_Delay(5); + __HAL_RCC_USB_RELEASE_RESET(); + HAL_Delay(5); + +#endif // MCU family hardware init // Initialize TinyUSB device stack (common for all MCUs) tud_init(rhport); +#if defined(STM32WBxx) + // Assert D+ pull-up AFTER tud_init — tud_init resets the USB peripheral + // which clears BCDR, so setting DPPU before tud_init would have no effect. + USB->BCDR |= USB_BCDR_DPPU; +#endif + // Give the peripheral a moment to stabilize before attempting connection HAL_Delay(10); } +//--------------------------------------------------------------------+ +// DFU Bootloader Entry +//--------------------------------------------------------------------+ void TinyUSB_Port_EnterDFU(void) { - // Reboot into the ST system-memory (ROM) DFU bootloader. - // - // This is called by the Adafruit TinyUSB library (Adafruit_USBD_CDC.cpp) - // when it detects the Arduino IDE "touch 1200" sequence (CDC port opened - // at 1200 baud then closed). - // - // Approach: write the STM32duino magic value (0x515B) into the appropriate - // backup register, then issue a system reset. The STM32duino core startup - // code checks this register and jumps to the ST ROM DFU bootloader if the - // value matches. - // - // We disconnect from USB first and give the host time to register the - // disconnection before resetting, so the IDE knows to start looking for - // a DFU device rather than waiting for the COM port to reappear. +#if defined(STM32F4xx) + //================================================================= + // STM32F4xx: soft-disconnect via DCTL, then direct jump + //================================================================= + __HAL_RCC_SYSCFG_CLK_ENABLE(); + #if defined(USE_USB_HS) + USB_OTG_DeviceTypeDef* otg_dev = (USB_OTG_DeviceTypeDef*)((uint32_t)USB_OTG_HS + USB_OTG_DEVICE_BASE); + #else + USB_OTG_DeviceTypeDef* otg_dev = (USB_OTG_DeviceTypeDef*)((uint32_t)USB_OTG_FS + USB_OTG_DEVICE_BASE); + #endif + otg_dev->DCTL |= USB_OTG_DCTL_SDIS; + HAL_Delay(20); - tud_disconnect(); +#elif defined(STM32F1xx) + //================================================================= + // STM32F1xx: pull D+ low to signal disconnect, then direct jump + //================================================================= + __HAL_RCC_GPIOA_CLK_ENABLE(); + GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET); HAL_Delay(100); - // Enable access to the backup domain - __HAL_RCC_PWR_CLK_ENABLE(); - HAL_PWR_EnableBkUpAccess(); +#elif defined(STM32WBxx) + //================================================================= + // STM32WBxx: write magic value to RTC BKP0R, then NVIC_SystemReset(). + // dfu_boot_stm32wb.c intercepts the reset in Reset_Handler, reads + // the magic value, and jumps to the bootloader before SystemInit() + // runs — giving the bootloader a near-clean chip state. + //================================================================= -#if defined(STM32F1xx) - __HAL_RCC_BKP_CLK_ENABLE(); - BKP->DR1 = 0x515B; + // Gracefully disconnect USB from host + USB->BCDR &= ~USB_BCDR_DPPU; + HAL_Delay(20); + + // Enable PWR clock and unlock backup domain register access + RCC->APB1ENR1 |= (1u << 28u); // PWREN + __DSB(); + PWR->CR1 |= (1u << 8u); // DBP — backup domain write enable + __DSB(); + + // Write magic value to survive the reset + RTC->BKP0R = 0xDEADBEEFu; + + // Trigger clean reset — never returns for WBxx. + // All code below this point is unreachable on WBxx; + // it is only executed by F1xx, F4xx, and G4xx. + NVIC_SystemReset(); + +#endif + + __disable_irq(); + + // Disable USB clock +#if defined(STM32G4xx) + RCC->APB1ENR1 &= ~(RCC_APB1ENR1_USBEN); #elif defined(STM32F4xx) - __HAL_RCC_RTC_ENABLE(); - RTC->BKP4R = 0x515B; -#elif defined(STM32G4xx) - __HAL_RCC_RTC_ENABLE(); - TAMP->BKP4R = 0x515B; + RCC->AHB2ENR &= ~(RCC_AHB2ENR_OTGFSEN); +#elif defined(STM32F1xx) + RCC->APB1ENR &= ~(RCC_APB1ENR_USBEN); #endif - NVIC_SystemReset(); +#if defined(STM32WBxx) + // Unreachable — WBxx exits via NVIC_SystemReset() above. + // Released here defensively in case of future refactoring. + LL_HSEM_ReleaseLock(HSEM, 5, 0); +#endif + + // Clear all pending interrupts + for (uint32_t i = 0; i < 5u; i++) { + NVIC->ICER[i] = 0xFFFFFFFFu; + NVIC->ICPR[i] = 0xFFFFFFFFu; + } + + // Stop SysTick + SysTick->CTRL = 0u; + SysTick->LOAD = 0u; + SysTick->VAL = 0u; + + // Reset clocks to default +#if defined(STM32F4xx) + // Manual reset — more reliable than HAL_RCC_DeInit on F405 + RCC->CR |= RCC_CR_HSION; + while (!(RCC->CR & RCC_CR_HSIRDY)); + RCC->CFGR = 0x00000000; + while ((RCC->CFGR & RCC_CFGR_SWS) != 0x00); + RCC->CR &= ~(RCC_CR_PLLON | RCC_CR_HSEON); + RCC->PLLCFGR = 0x24003010; +#elif defined(STM32WBxx) + // Reconfigure USB pins as floating inputs before reset + GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); +#else + HAL_RCC_DeInit(); +#endif + + __enable_irq(); + + // Remap system memory to 0x00000000 and jump to bootloader +#if defined(STM32G4xx) || defined(STM32F4xx) || defined(STM32WBxx) + //HAL_Delay(20); + __HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH(); + #define BOOTLOADER_ADDR 0x1FFF0000u +#elif defined(STM32F1xx) + #define BOOTLOADER_ADDR 0x1FFFF000u +#endif + + typedef void (*BootJump_t)(void); + const uint32_t sp = *(volatile uint32_t *)BOOTLOADER_ADDR; + const uint32_t pc = *(volatile uint32_t *)(BOOTLOADER_ADDR + 4u); + __set_MSP(sp); + __DSB(); + __ISB(); + ((BootJump_t)pc)(); + while (1) {} } +//--------------------------------------------------------------------+ +// Serial Number +//--------------------------------------------------------------------+ uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { #if defined(STM32F4xx) - // STM32F4xx UID base address (e.g. F411) volatile uint32_t *uid = (volatile uint32_t *)0x1FFF7A10; #elif defined(STM32F1xx) - // STM32F1xx UID base address (e.g. F103) volatile uint32_t *uid = (volatile uint32_t *)0x1FFFF7E8; -#elif defined(STM32G4xx) - // STM32G4xx UID base address (e.g. G431, G474) +#elif defined(STM32G4xx) || defined(STM32WBxx) volatile uint32_t *uid = (volatile uint32_t *)0x1FFF7590; #endif diff --git a/src/arduino/ports/stm32/boards_txt_additions.txt b/src/arduino/ports/stm32/boards_txt_additions.txt index b3a17457..75427c61 100644 --- a/src/arduino/ports/stm32/boards_txt_additions.txt +++ b/src/arduino/ports/stm32/boards_txt_additions.txt @@ -14,16 +14,38 @@ # Replace the prefix (GenF4 / GenF1 / GenG4) with the one for your board. # Add the lines alongside any existing menu.usb entries for that board. # +# STM32WB55 -- NOT SUPPORTED via generic board definitions +# +# Generic WB55 variants lack USB clock configuration and will not enumerate. +# Use the P-NUCLEO-WB55 or NUCLEO WB55 USB Dongle board definition instead +# (covered by the Nucleo_64 entry above). Custom WB55 boards with a 32MHz +# HSE crystal should also work with these definitions. +# +# Proper generic support requires upstream changes to the STM32duino core. # ----------------------------------------------------------------------- -# STM32F4 (e.g. Generic F4 -- tested on WeAct STM32F411 BlackPill) + +# STM32F4 (e.g. Generic F4 -- tested on WeAct STM32F411 BlackPill and WeAct STM32F405) GenF4.menu.usb.TinyUSB=Adafruit TinyUSB GenF4.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +GenF4.menu.usb.TinyUSB.upload.use_1200bps_touch=true +GenF4.menu.usb.TinyUSB.upload.wait_for_upload_port=false # STM32F1 (e.g. Generic F1 -- tested on STM32F103 BluePill) GenF1.menu.usb.TinyUSB=Adafruit TinyUSB GenF1.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +GenF1.menu.usb.TinyUSB.upload.use_1200bps_touch=true +GenF1.menu.usb.TinyUSB.upload.wait_for_upload_port=false -# STM32G4 (e.g. Generic G4 -- tested on WeAct STM32G431) +# STM32G4 (e.g. Generic G4 -- tested on WeAct STM32G431 and WeAct STM32G474) GenG4.menu.usb.TinyUSB=Adafruit TinyUSB GenG4.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +GenG4.menu.usb.TinyUSB.upload.use_1200bps_touch=true +GenG4.menu.usb.TinyUSB.upload.wait_for_upload_port=false + +# Nucleo-64 boards -- added primarily for STM32WB55 support (see WB55 note above). +# Also enables TinyUSB for most Nucleo-64 boards with F1, F4, or G4 MCUs. +Nucleo_64.menu.usb.TinyUSB=Adafruit TinyUSB +Nucleo_64.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +Nucleo_64.menu.usb.TinyUSB.upload.use_1200bps_touch=true +Nucleo_64.menu.usb.TinyUSB.upload.wait_for_upload_port=false \ No newline at end of file diff --git a/src/arduino/ports/stm32/dfu_boot_stm32wb.c b/src/arduino/ports/stm32/dfu_boot_stm32wb.c new file mode 100644 index 00000000..fbb750ee --- /dev/null +++ b/src/arduino/ports/stm32/dfu_boot_stm32wb.c @@ -0,0 +1,64 @@ +#if defined(STM32WBxx) +#include "stm32wbxx.h" + +#define DFU_MAGIC_VALUE 0xDEADBEEFu +#define BOOTLOADER_ADDR 0x1FFF0000u + +// Force linker to retain the assembly startup's .isr_vector section. +// Overriding the weak Reset_Handler from a .c file can cause the linker +// to skip the assembly startup .o entirely, silently dropping the vector table. +extern uint32_t g_pfnVectors[]; +__attribute__((used)) static uint32_t * const _force_isr_vector = g_pfnVectors; + +extern void SystemInit(void); +extern void __libc_init_array(void); +extern int main(void); +extern uint32_t _estack; +extern uint32_t _sdata, _edata, _sidata; +extern uint32_t _sbss, _ebss; + +static void dfu_boot_check(void) +{ + // Enable PWR clock and unlock backup domain register access + RCC->APB1ENR1 |= (1u << 28u); // PWREN + __DSB(); + PWR->CR1 |= (1u << 8u); // DBP + __DSB(); + + if (RTC->BKP0R == DFU_MAGIC_VALUE) { + RTC->BKP0R = 0u; + typedef void (*BootJump_t)(void); + uint32_t sp = *(volatile uint32_t *)BOOTLOADER_ADDR; + uint32_t pc = *(volatile uint32_t *)(BOOTLOADER_ADDR + 4u); + __set_MSP(sp); + __DSB(); + __ISB(); + ((BootJump_t)pc)(); + while (1) {} + } +} + +__attribute__((naked, used)) +void Reset_Handler(void) +{ + __asm volatile ( + "ldr r0, =_estack \n" + "mov sp, r0 \n" + ); + dfu_boot_check(); + SystemInit(); + { + uint32_t *src = &_sidata; + uint32_t *dst = &_sdata; + while (dst < &_edata) { *dst++ = *src++; } + } + { + uint32_t *dst = &_sbss; + while (dst < &_ebss) { *dst++ = 0u; } + } + __libc_init_array(); + main(); + while (1) {} +} + +#endif // STM32WBxx \ No newline at end of file diff --git a/src/arduino/ports/stm32/tusb_config_stm32.h b/src/arduino/ports/stm32/tusb_config_stm32.h index bc54d22f..71e61ce3 100644 --- a/src/arduino/ports/stm32/tusb_config_stm32.h +++ b/src/arduino/ports/stm32/tusb_config_stm32.h @@ -37,8 +37,10 @@ #define CFG_TUSB_MCU OPT_MCU_STM32F4 #elif defined(STM32G4xx) #define CFG_TUSB_MCU OPT_MCU_STM32G4 +#elif defined(STM32WBxx) + #define CFG_TUSB_MCU OPT_MCU_STM32WB #else - #error "Unsupported STM32 family - only F1xx, F4xx and G4xx are currently supported" + #error "Unsupported STM32 family - only F1xx, F4xx, G4xx and WBxx are currently supported" #endif #define CFG_TUSB_OS OPT_OS_NONE From 1a15833d5d2175f42b87a08cf46940a7c5f24362 Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Thu, 26 Feb 2026 11:47:22 +0000 Subject: [PATCH 5/8] Update README.md Fixed incorrect path to boards_txt_additions.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8944d51d..a6dae448 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Note: For ESP32 port, version before v3.0 requires all descriptors must be speci The following are cores without built-in support - **[stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)** - - Still WIP. Fully functional but with limited number of MCUs supported so far (F1xx, F4xx, some G4xx & WB55). Requires lines added to stm32duino boards.txt which can be found in /ports/stm32/boards_txt_additions.txt. + - Still WIP. Fully functional but with limited number of MCUs supported so far (F1xx, F4xx, some G4xx & WB55). Requires lines added to stm32duino boards.txt which can be found in src/arduino/ports/stm32/boards_txt_additions.txt. - **mbed_rp2040** - It is still possible to use TinyUSB but with some limits such as: From 8a2800ff3395e822ea384b8b0be79ff07adac5ab Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Thu, 26 Feb 2026 11:48:15 +0000 Subject: [PATCH 6/8] Update src/arduino/ports/stm32/tusb_config_stm32.h Set CFG_TUSB_RHPORT0_MODE to include the speed bits Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/arduino/ports/stm32/tusb_config_stm32.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arduino/ports/stm32/tusb_config_stm32.h b/src/arduino/ports/stm32/tusb_config_stm32.h index 71e61ce3..dbf94145 100644 --- a/src/arduino/ports/stm32/tusb_config_stm32.h +++ b/src/arduino/ports/stm32/tusb_config_stm32.h @@ -26,7 +26,7 @@ #define TUSB_CONFIG_STM32_H_ // USB Port -#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED) #define CFG_TUSB_RHPORT0_SPEED OPT_FULL_SPEED #define CFG_TUSB_RHPORT1_MODE OPT_MODE_NONE From 08f141c26ac9581b037d16ca65cd750d9b2d6ae8 Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Thu, 26 Feb 2026 11:59:45 +0000 Subject: [PATCH 7/8] Address review feedback (tick handling, reset handler, config & docs) Addresses PR review comments: - Fix WB55 Nucleo-64 wording in boards_txt_additions.txt - Add build.enable_usb override to prevent duplicate STM32 core USB stack - Correct README path to boards_txt_additions.txt - Fix CFG_TUSB_RHPORT0_MODE to include speed bits - Update TINYUSB_NEED_POLLING_TASK comment to reflect actual scheduling mechanism - Rework HAL_IncTick() override to avoid hard-coded +1 tick increment - Replace naked Reset_Handler C implementation with safe assembly wrapper + C handler No functional changes beyond the requested fixes and clarifications. --- README.md | 2 +- src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp | 14 +++++--------- src/arduino/ports/stm32/dfu_boot_stm32wb.c | 12 ++++++++++-- src/arduino/ports/stm32/tusb_config_stm32.h | 10 +++++----- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 8944d51d..fcefdfa7 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Note: For ESP32 port, version before v3.0 requires all descriptors must be speci The following are cores without built-in support - **[stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)** - - Still WIP. Fully functional but with limited number of MCUs supported so far (F1xx, F4xx, some G4xx & WB55). Requires lines added to stm32duino boards.txt which can be found in /ports/stm32/boards_txt_additions.txt. + - Still WIP. Fully functional but with limited number of MCUs supported so far (F1xx, F4xx, some G4xx & WB55). Requires lines added to stm32duino boards.txt which can be found in src/arduino/ports/stm32/boards_txt_additions.tx - **mbed_rp2040** - It is still possible to use TinyUSB but with some limits such as: diff --git a/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp b/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp index ec573b87..10ff2394 100644 --- a/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp +++ b/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp @@ -147,16 +147,12 @@ void serialEventRun(void) yield(); } -// HAL_IncTick() is called every 1 ms from the SysTick_Handler ISR. -// The STM32 HAL defines it as __weak, so we override it here. -// We call the HAL's own uwTick increment, then set the flag for yield(). -extern "C" void HAL_IncTick(void) +// Hook into the HAL SysTick callback instead of overriding HAL_IncTick(). +// This preserves the HAL's own tick behaviour (including configurable +// tick frequency) while still giving us a reliable 1 ms-ish pump for +// tud_task(). +extern "C" void HAL_SYSTICK_Callback(void) { - // Keep the HAL tick counter running (normally done by the weak default). - extern __IO uint32_t uwTick; - uwTick += 1U; - - // Signal yield() to run tud_task() at the next thread-context opportunity. _tusb_task_pending = true; } diff --git a/src/arduino/ports/stm32/dfu_boot_stm32wb.c b/src/arduino/ports/stm32/dfu_boot_stm32wb.c index fbb750ee..3050252b 100644 --- a/src/arduino/ports/stm32/dfu_boot_stm32wb.c +++ b/src/arduino/ports/stm32/dfu_boot_stm32wb.c @@ -38,13 +38,21 @@ static void dfu_boot_check(void) } } +// Minimal naked wrapper that sets SP and branches to the C reset handler. __attribute__((naked, used)) void Reset_Handler(void) { __asm volatile ( - "ldr r0, =_estack \n" - "mov sp, r0 \n" + "ldr r0, =_estack \n" + "mov sp, r0 \n" + "b Reset_Handler_C \n" ); +} + +// Standard C reset handler: performs DFU check, system init, and +// data/BSS initialisation before entering main(). +static void Reset_Handler_C(void) +{ dfu_boot_check(); SystemInit(); { diff --git a/src/arduino/ports/stm32/tusb_config_stm32.h b/src/arduino/ports/stm32/tusb_config_stm32.h index 71e61ce3..c83fab8e 100644 --- a/src/arduino/ports/stm32/tusb_config_stm32.h +++ b/src/arduino/ports/stm32/tusb_config_stm32.h @@ -26,7 +26,7 @@ #define TUSB_CONFIG_STM32_H_ // USB Port -#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED) #define CFG_TUSB_RHPORT0_SPEED OPT_FULL_SPEED #define CFG_TUSB_RHPORT1_MODE OPT_MODE_NONE @@ -80,10 +80,10 @@ // TINYUSB_NEED_POLLING_TASK is intentionally NOT defined here. // -// The STM32 port implements yield() to call tud_task() automatically -// whenever the Arduino core calls yield() (e.g. inside delay(), and at -// the bottom of every loop() iteration on cores that wrap loop() with -// a yield() call). This means TinyUSB is serviced without any explicit +// The STM32 port implements the STM32 port implementation +// relies on a HAL tick hook plus serialEventRun() to ensure +// tud_task() runs even when the core doesn’t call yield() from loop(). +// This means TinyUSB is serviced without any explicit // TinyUSBDevice.task() call in the sketch's loop(). // // Sketches that still contain the legacy polling guard: From 2dd1f9d4882abb354768344e18f52226987bb5f5 Mon Sep 17 00:00:00 2001 From: code-fiasco Date: Thu, 26 Feb 2026 14:29:05 +0000 Subject: [PATCH 8/8] Update boards_txt_additions.txt Replace `build.usb_flags` with `build.enable_usb` to match existing boards.txt conventions --- src/arduino/ports/stm32/boards_txt_additions.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/arduino/ports/stm32/boards_txt_additions.txt b/src/arduino/ports/stm32/boards_txt_additions.txt index 75427c61..2a1ae6b6 100644 --- a/src/arduino/ports/stm32/boards_txt_additions.txt +++ b/src/arduino/ports/stm32/boards_txt_additions.txt @@ -27,25 +27,25 @@ # STM32F4 (e.g. Generic F4 -- tested on WeAct STM32F411 BlackPill and WeAct STM32F405) GenF4.menu.usb.TinyUSB=Adafruit TinyUSB -GenF4.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +GenF4.menu.usb.TinyUSB.build.enable_usb={build.extra_flags} -DARDUINO_ARCH_TINYUSB GenF4.menu.usb.TinyUSB.upload.use_1200bps_touch=true GenF4.menu.usb.TinyUSB.upload.wait_for_upload_port=false # STM32F1 (e.g. Generic F1 -- tested on STM32F103 BluePill) GenF1.menu.usb.TinyUSB=Adafruit TinyUSB -GenF1.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +GenF1.menu.usb.TinyUSB.build.enable_usb={build.extra_flags} -DARDUINO_ARCH_TINYUSB GenF1.menu.usb.TinyUSB.upload.use_1200bps_touch=true GenF1.menu.usb.TinyUSB.upload.wait_for_upload_port=false # STM32G4 (e.g. Generic G4 -- tested on WeAct STM32G431 and WeAct STM32G474) GenG4.menu.usb.TinyUSB=Adafruit TinyUSB -GenG4.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +GenG4.menu.usb.TinyUSB.build.enable_usb={build.extra_flags} -DARDUINO_ARCH_TINYUSB GenG4.menu.usb.TinyUSB.upload.use_1200bps_touch=true GenG4.menu.usb.TinyUSB.upload.wait_for_upload_port=false # Nucleo-64 boards -- added primarily for STM32WB55 support (see WB55 note above). # Also enables TinyUSB for most Nucleo-64 boards with F1, F4, or G4 MCUs. Nucleo_64.menu.usb.TinyUSB=Adafruit TinyUSB -Nucleo_64.menu.usb.TinyUSB.build.usb_flags={build.extra_flags} -DARDUINO_ARCH_TINYUSB +Nucleo_64.menu.usb.TinyUSB.build.enable_usb={build.extra_flags} -DARDUINO_ARCH_TINYUSB Nucleo_64.menu.usb.TinyUSB.upload.use_1200bps_touch=true Nucleo_64.menu.usb.TinyUSB.upload.wait_for_upload_port=false \ No newline at end of file