AVR DU: native USB support - CDC serial, Pluggable USB, and USB CDC bootloader - #652
Open
ws-asahi wants to merge 34 commits into
Open
AVR DU: native USB support - CDC serial, Pluggable USB, and USB CDC bootloader#652ws-asahi wants to merge 34 commits into
ws-asahi wants to merge 34 commits into
Conversation
`!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.
ws-asahi
force-pushed
the
feat/avrdu-usb-support
branch
from
August 25, 2026 07:30
cbba861 to
680de9a
Compare
Three defects in the DU branch of the ADC code (the ADC_LOWLAT_bm / no-TEMP2 branch), found while characterising the ADC on real hardware. All fail silently - no error is returned, the sketch just gets wrong numbers. 1. analogRead() and _analogReadEnh() masked the channel with 0x3F. The DU has a 7-bit MUXPOS and places its internal channels at 0x40 GND, 0x42 TEMPSENSE and 0x44 VDDDIV10 (DS40002548B 32.4.12), not at 0x30-0x33 like the neighbouring parts, so ADC_GROUND, ADC_TEMPERATURE and ADC_VDDDIV10 became AIN0/AIN2/AIN4 and read ordinary I/O pins. Observed as a VDD/10 reading averaging 143 with a 1..1023 spread (a floating header pin) and an on-chip temperature of 415 degC. All three mask sites are now 0x7F (_analogReadEnh masks twice - before and after its channel check). 2. Both channel checks used the 0x30-0x33 range, so the DU's real internal channels would be rejected once the mask was right. They now accept exactly 0x40, 0x42 and 0x44 and reject the reserved values in between rather than handing them to the mux. 3. analogClockSpeed() read its int16_t PROGMEM table with pgm_read_byte_near() (low byte only) in the search loop, and with plain array indexing in the comparison and the return - a code-space address dereferenced in the data space. It returned garbage (-9227 kHz on a 24 MHz part where the answer is 2000 kHz) and picked a wrong prescaler whenever a frequency was requested. All accesses now use pgm_read_word_near(), and the return masks CTRLB to its prescaler bits. The EA/EB branch of the same function already did all of this correctly. Verified on an AVR64DU32: GND reads 0, VDDDIV10 and TEMPSENSE read stable, plausible values, and analogClockSpeed() reports 2000 kHz at 24 MHz.
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.
Strip the .noinit reset-surviving breadcrumb block (g_bc_* / usbCdcDiag* accessors) and the g_cdc_rx_total / g_cdc_tx_starts / g_cdc_tx_pkts counters. These were development aids for bringing up the 1200 bps touch path and add nothing for users; the lightweight GPR-based diag_set() markers remain. No functional change.
The touch (SET_CONTROL_LINE_STATE / SET_LINE_CODING) is a control transfer; resetting inside the handler detached with the host's request still outstanding, so the Windows usbser driver waited out a multi-second timeout before the upload could proceed - the 'long pause after the 1200 bps touch'. Instead, arm a SOF-driven countdown and reset as soon as the control state machine returns to IDLE (status stage on the wire), with a ~15 ms ceiling as fallback - the Caterina 120 ms-WDT trick, but completion-aware, costing 1-2 ms typically. (A WDT cannot be used: the bootloader stays resident on SWRF, and a watchdog reset would set WDRF instead.) Also stop arming the status-stage ZLP from the SET_LINE_CODING completion path - handle_ep0_out_complete() does that itself after we return; doing it early left EP0 OUT misconfigured. Trim the post-detach settle delay from ~40 ms to ~10 ms. Verified on an AVR64DU32: uploads start noticeably faster after the touch, with no enumeration regressions.
Datasheet Rev. B (DS40002548B / DS40002576B, 06/2026) removed every INLVL bit from the PORT chapter, while the io headers still define it - and hardware testing shows the feature is functional: a VDD/2 input reads 0 at the Schmitt-trigger threshold and 1 after switching the pin to TTL levels (AVR64DU32, resistor-divider test). Document it in the DU section of Ref_Digital as an undocumented-but-working feature: no guaranteed thresholds, no promise it survives future revisions.
ws-asahi
force-pushed
the
feat/avrdu-usb-support
branch
from
August 27, 2026 10:39
680de9a to
bf688b9
Compare
The Serial_ class of the official native-USB Arduino cores exposes the
CDC control-line state and the host's requested line coding; USBSerial
had none of it, so sketches using them failed to compile against the
native USB Serial. Added with the same names, semantics and raw
encodings as ArduinoCore-avr USBAPI.h / CDC.cpp:
dtr() rts() - control line state bits
baud() stopbits() paritytype()
numbits() - host line coding
readBreak() - last SEND_BREAK duration, consumed
on read (-1 when none pending)
ONE_STOP_BIT / ... and
NO_PARITY / ... - the two Serial_ enums
The state was already tracked in usb_cdc.c; this exposes the missing
fields and latches the SEND_BREAK wValue, which the handler previously
acknowledged and discarded. usbCdcReadBreak() reads and clears
atomically against the EP0 ISR.
The classic use is a USB-to-serial bridge sketch that mirrors the
host's baud onto a hardware UART.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
CDC-ACM
Serial, plus the standard Pluggable USB API so existing libraries work unmodified.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.
and application VID/PID. The generic no-bootloader board is unaffected.
(parameterized boot geometry; optiboot paths unchanged).
(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)
Open questions
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.
Update (post-submission):
eliminating the multi-second usbser timeout wait before uploads; development diagnostics removed.
but is functional on silicon (verified with a VDD/2 divider test) - noted in Ref_Digital as undocumented-but-working.
removing the multi-second usbser timeout before uploads.