Skip to content

AVR DU: native USB support - CDC serial, Pluggable USB, and USB CDC bootloader - #652

Open
ws-asahi wants to merge 29 commits into
SpenceKonde:masterfrom
ws-asahi:feat/avrdu-usb-support
Open

AVR DU: native USB support - CDC serial, Pluggable USB, and USB CDC bootloader#652
ws-asahi wants to merge 29 commits into
SpenceKonde:masterfrom
ws-asahi:feat/avrdu-usb-support

Conversation

@ws-asahi

@ws-asahi ws-asahi commented Aug 9, 2026

Copy link
Copy Markdown

Native USB support for the AVR DU-series.
Based on #651, the new commits start at "AVR DU: native USB stack".
Together, #650 + #651 + this PR supersede draft #637.

What this adds

  • Clean-room, interrupt-driven USB device stack for the DU peripheral (written from the USB 2.0 spec and DS40002548A; no vendor sample code).
    CDC-ACM Serial, plus the standard Pluggable USB API so existing libraries work unmodified.
  • Bundled basic HID library; USB MIDI works via MIDIUSB (Support the megaavr architecture (AVR-Dx/DU via DxCore) arduino-libraries/MIDIUSB#132; extended HID via Support AVR cores without UEDATX (e.g. AVR-DU via DxCore) NicoHood/HID#472).
  • usbcdcboot: a polled, clean-room USB CDC bootloader (STK500v1, 4 KB boot section with an app-callable SPM stub),
    with sources, build scripts, DESIGN/PROVENANCE notes,
    and prebuilt hex for every DU part - bit-for-bit reproducible from the included sources with the DxCore toolchain.
  • New board: AVR DU-series (USB CDC Bootloader) - upload over USB (1200 bps touch), Board Info recognizes both bootloader
    and application VID/PID. The generic no-bootloader board is unaffected.
  • Flash library can write from the app under the CDC bootloader
    (parameterized boot geometry; optiboot paths unchanged).
  • Docs: Ref_USBCDCboot (user-facing bootloader reference), and the DU filled in across the references
    (Ref_Timers, Ref_Serial, Ref_Defines, Ref_Digital, Ref_Analog, CORE_PART_ID, README, part pages)
    including hardware-verified corrections such as PC3 being a plain VDD-domain GPIO.

Testing (AVR64DU32 Curiosity Nano + Windows 11)

  • CDC: sketch upload over USB, Serial monitor I/O, 1200 bps touch reset into the bootloader.
  • HID: the stock Arduino KeyboardMessage example works unmodified.
  • Regression: Blink builds for AVR DA (no bootloader) and AVR DA (Optiboot) unchanged.

Open questions

  • The USB-CDC VID/PID is set to the test IDs listed in pid.codes (0x1209:0x0001/0x0002).
    These are IDs intended for functional testing, and you are not permitted to use them as-is in commercial products.
    Manufacturers must obtain and modify the VID/PID as necessary before manufacturing and shipping the hardware.

ws-asahi added 29 commits August 8, 2026 22:27
`!ADC0.CTRLA & 0x01` evaluates as `(!ADC0.CTRLA) & 0x01`, so the
early return for a disabled ADC never fired as intended. Parenthesize
the mask. The same expression appears twice (analogRead paths for the
two ADC generations); the other checks in this file already had the
parentheses right.
`(chan + 1) < 3` promotes chan to int, so the documented auto-select
value chan = 255 evaluated as 256 < 3 and could never take this path.
Compare explicitly instead.
…ost data pointer

Three bugs in the unaligned/odd-length paths:
- the unaligned leading byte was written without advancing the data
  pointer, so the rest of the buffer was written shifted by one
- writeWords was called without afterwards advancing tAddress/data/
  length, so the odd-byte epilogue used stale values
- the trailing byte was written to tAddress + length - 2 with a
  recomputed data offset, landing on the wrong address and corrupting
  the neighbouring byte while dropping the intended one

Rewrite the function to advance state explicitly at each stage. Also
return early on a zero-length request (FLASHWRITE_0LENGTH).
avrdu.build.highestcb was B2, and avr64du32 repeated that override. The
32/28-pin DU chip rows have no override of their own, so they inherited
B2 as well. No DU part has a TCB2.

Per DS40002548A (AVR64DU28/32):
- Features: "Two 16-bit Timer/Counter type B (TCB)".
- Product family overview table: the TCB row reads 2 for all four pin
  counts (14/20/28/32). Unlike the DD-series, pin count does not add a
  third TCB on the DU.
- Table 9-1 peripheral address map: TCB0 (0x0B00) and TCB1 (0x0B10)
  only, marked present at every pin count; no TCB2 row.
- Interrupt vector table: TCB0_INT (15) and TCB1_INT (29) only.
- PORTMUX.TCBROUTEA defines bit 0 (TCB0) and bit 1 (TCB1) only.
The ATPACK headers agree - ioavr64du32.h defines TCB0/TCB1 and no
TCB2_INT_vect. Ref_Timers.md already states it in prose: "Some parts
(the smaller pincount DD and all of the future DU and EB parts) do not
have a TCB2." The B2 value looks like a leftover from the avrdd block
this section was modelled on.

No behavioural change today: the DU millis menu sets
avrdu.menu.millis.tcbhighest.build.millistimer=B1 directly rather than
expanding {build.highestcb} (only the avrdd/avrddopti sections expand
it), so nothing currently reads these keys. They are still worth
correcting - if the DU menu is ever made consistent with the DD one,
B2 would emit -DMILLIS_USE_TIMERB2 and every 32/28-pin DU build would
stop at timers.h with "TCB2, selected for millis, does not exist on
this part".

The avr64du32 override and the four existing B1 chip overrides are all
redundant with the family default now; left in place to keep the diff
to the incorrect values.
…SCFG0

The generic avrdu SYSCFG0 template expands {bootloader.resetpinbits}
and {bootloader.eesavebit}, but neither key was defined for the DU
section, and no avrdudefuse5 template existed so fuse 5 (SYSCFG0) was
never written at all. Add the missing resetpinbits indirection, the
eesave menu (parity with avrda/avrdb/avrdd), and the fuse-5 write
templates.
SYSCFG.VUSBCTRL resets to regulator-off, and on the DU the PC3 input
buffer sits in the VUSB power domain - so a plain no-bootloader board
cannot even read PC3 until VUSB is powered, USB or not.

Add a 'VUSB Power Source' menu to the generic avrdu board (internal
regulator from VDD, the common case, or external 3.3 V on VUSB) and
have init() enable the regulator when USB_VREG_INTERNAL is defined.
The 10-bit compatibility path always did temp >>= 2, assuming a 12-bit
native ADC. The DU's ADC is natively 10-bit (SINGLE_8BIT/SINGLE_10BIT
only, DS40002548A section 32), so the extra shift squashed readings
into an 8-bit range. Shift by (ADC_NATIVE_RESOLUTION - 10) instead,
which is 2 on 12-bit parts (unchanged) and 0 on the DU. The same
pattern appears in both analogRead paths; both are fixed.
On the DU, the EVGENnSEL fields of PORTx.EVGENCTRLA are 3 bits wide and
bits 3/7 read as zero (DS40002548A 18.5.10), so the trick of using them
as allocation flags does not work - automatic channel selection
(chan = 255) is not possible there. Return 255 (failure) on the DU and
document the limitation; explicit channel numbers and the Event
library's software tracking work as before.
On the DU, USART1's only pin position is ALT2 (PD6/PD7) - its DEFAULT
mux value means 'no pin connection' (DS40002548A, USARTROUTEA). And
USART0's DEFAULT (PA0/PA1) collides with the main-clock crystal pins,
while PD4/PD5 (ALT3) is the one position present on every DU pincount
(PORTA options thin out on the 14-pin part).

Define HWSERIAL0_MUX_DEFAULT = ALT3 and HWSERIAL1_MUX_DEFAULT = ALT2 in
the DU variants, so Serial0 is PD4/PD5 and Serial1 is PD6/PD7 out of
the box with no swap() call - the core's UART_swap.h already honours
these overrides. Add NOT_A_PIN placeholder rows for the absent ALT1
positions so the pinswap tables keep their row indexing, and correct
the 14-pin LED_BUILTIN comment (Serial1's TxD on PD6 is what collides,
not PD4).
The example assumed peripherals the DU does not have (TCD0 was consumed
by USB, and the pin/timer lists did not cover the DU mux options).
Adjust the timer/pin handling so the example builds and runs on DU
parts while behaving as before elsewhere; verified on an AVR64DU32.
Hardware testing showed PC3's digital I/O is powered from VDD and is
independent of VUSB and the regulator (the pinout legend is easy to
misread). Drop that claim from the menu and init() comments; the menu's
purpose stands - anything using the USB peripheral needs VUSB powered.
No functional change.
Clean-room USB device stack for the DU's USB peripheral, written from
the USB 2.0 specification and DS40002548A:

- usb_core / usb_standard / usb_descriptors: device controller driver,
  standard request handling, descriptor assembly (IAD-capable for
  composite devices such as USB MIDI)
- usb_cdc + USBSerial: CDC-ACM with an Arduino Serial-compatible
  interface (ring buffers, BREAK/1200bps-touch reset into bootloader)
- USBCore_DU + PluggableUSB bridge: the standard Arduino Pluggable USB
  API on top of the DU stack, so existing libraries (HID, MIDIUSB)
  work unmodified
- Fully interrupt-driven (USB0_BUSEVENT / USB0_TRNCOMPL); usbPoll()
  is a no-op kept for source compatibility
- main.cpp / Arduino.h / HardwareSerial.h: hook the stack into the
  core when USBCON is defined (USB boards only; no effect elsewhere)
The standard Arduino HID module (keyboard/mouse-class descriptors over
Pluggable USB), so HID sketches work out of the box on the DU. Extended
HID (NicoHood's HID-Project) also works once NicoHood/HID#472 lands.
Polled (interrupt-free) CDC-ACM bootloader speaking optiboot-compatible
STK500v1, written from the USB 2.0 spec and DS40002548A - no Microchip
sample code or third-party USB stack. 4 KB boot section including an
app-callable SPM stub in the last page (used by the Flash library).
Sources, Makefile, per-part build scripts, and design/provenance notes
(DESIGN.md, PROVENANCE.md). Entry: 1200 bps touch (magic word + WDT reset), external reset, or
a blank application; LED indicator while resident.
usbcdcboot_{16,32,64}du[14][_novreg].hex - one per flash size, with
14-pin LED variants, and _novreg builds for boards feeding external
3.3 V into VUSB (matches the VUSB Power Source menu).
New avrduusb board entry: uploads through the CDC bootloader
(1200 bps touch, optiboot-style avrdude), registers both the
bootloader (0x1209:0x0001) and application (0x1209:0x0002) VID/PID
for Board Info, per-chip bootloader hex selection, and the same
menu set as the generic DU board. pid.codes test IDs - to be
replaced before release builds.

Variants gain the USB identity block (VID/PID/manufacturer/product)
and the Serial -> USBSerial redirect, active only when USBCON is
defined - the generic avrdu board is unaffected.
Parameterize the boot-section geometry (REQUIRED_BOOTSIZE /
BOOTLOADER_END) instead of hard-coding optiboot's 512 bytes, and call
the CDC bootloader's SPM stub (last page of the 4 KB boot section)
when USING_AVRDU_CDC_BOOTLOADER is defined. Optiboot and
SPM-from-app configurations are unchanged; FlashDemo gets the DU
addresses.
The DU part pages said USB support did not exist and that writing a
USB library was the only thing one could do with a DU; the README
still asked whether anyone knew how to make USB work. Update both to
describe what the core now provides (CDC Serial, Pluggable USB with
HID/MIDI, and the USB CDC bootloader board).
Add the same section-divider comments the other board entries use
(bootloading, upload, per-menu banners), so the avrduusb section reads
like the rest of the file. Comments only - no functional change.
…y', ADC label

- README: the Analog Comparator and ZCD availability lists had no DU
  entry (1 AC on all DU; no ZCD on the DU).
- AboutDxSeries: the DU was still described as 'on the way' with
  support 'planned'; it is shipping and supported.
- Ref_Timers: the DU is no longer a 'future' part.
- DU part pages: the spec tables said '12-bit ADC input pins'; the
  DU ADC is 10-bit (DS40002548A).
- Ref_Timers: the timer availability table had DA/DB, DD, EA and EB
  columns but no DU. Add it: TCA0/TCB0/TCB1 present, no TCD (the USB
  peripheral took its place), no TCB2 at any pincount, no TCE/TCF.
- Ref_Serial: the per-series USART pin-mapping list skipped the DU.
  Documented from DS40002548A (USARTROUTEA): USART0 has the DD options
  minus PORTC; USART1's only pin position is PD6/PD7 and its reset
  default is no-pin-connection, which is why the core defaults
  Serial1 there.
…oader

The counterpart of Ref_Optiboot for the USB CDC bootloader: what it
is, burning it, the entry conditions (1200 bps touch / reset button /
empty app), the LED, writing to flash from the app, and how it
differs from Optiboot in practice. Linked from the README's
documentation index alongside the Optiboot reference.
- The deprecated pincount-macro list had DA/DB/DD/DX/EA entries but not
  the DU ones, which core_devices.h does define.
- Document USBCON under peripheral detection: it is how a sketch or
  library detects that Serial is the native USB CDC port and the
  Pluggable USB API is present (defined by the USB board variants,
  not on the generic DU board).
- CORE_PART_ID: add the DU column (ID_AVR_DU = 0x48; 16/32/64k =
  0x48/0x58/0x68, parallel to the DD) and list ID_AVR_DU.
- Ref_Analog: the DU has no DAC either (only the comparator's DACREF).
- Ref_Digital: document PC3's split power domain - the input buffer is
  in the VUSB domain and needs VUSB powered, but the output driver runs
  from VDD (confirmed on hardware: full-VDD output with the regulator
  off and VUSB floating).
- Ref_Serial: on the DU USB boards, Serial wraps the native USB CDC
  port (USBSerial) instead of USART0; USART0/1 stay available as
  Serial0/Serial1.
… MUXNEG

- Ref_Digital: the earlier wording claimed PC3's input buffer needs
  VUSB powered; hardware testing shows PC3's digital I/O is powered
  from VDD and is entirely independent of VUSB and the regulator. The
  datasheet pinout legend is easy to misread here. Rewritten.
- Ref_Analog: state why the DU is absent from the MUXNEG table - its
  ADC is single-ended only, so there is no MUXNEG at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant