Add AVR DU-series support: USB CDC/HID stack, bootloader, and core peripherals#637
Draft
ws-asahi wants to merge 41 commits into
Draft
Add AVR DU-series support: USB CDC/HID stack, bootloader, and core peripherals#637ws-asahi wants to merge 41 commits into
ws-asahi wants to merge 41 commits into
Conversation
Implements a complete native USB stack for the AVR DU peripheral: - usb_core.c/h : low-level hw driver, ISR, endpoint table - usb_standard.c/h : control transfer handling (GET_DESCRIPTOR etc.) - usb_descriptors.c/h: composite device descriptors (CDC-ACM) - usb_ep_types.h : endpoint type/direction constants - usb_cdc.c/h : CDC-ACM class with 1200-bps touch-reset - USBSerial.cpp/h : Arduino Stream/Print API (Serial object) The 1200-bps auto-reset issues a software reset (RSTCTRL.SWRR) which sets RSTFR.SWRF. The bootloader checks SWRF to stay resident for avrdude. Order-independent detection: the condition is evaluated in both the SET_CONTROL_LINE_STATE and SET_LINE_CODING completion handlers. Tested on AVR64DU32 Curiosity Nano (EV59F82A) at 24 MHz: - USB CDC enumeration and bidirectional serial - 1200-bps auto-upload via Arduino IDE - HID keyboard/mouse/gamepad operation
Clean-room 4 KB bootloader (BOOTSIZE=8, app at 0x1000) providing an
avrdude STK500v1 upload path over native USB CDC.
- Same VID:PID (0x1209/0xDA33) as the runtime, so Arduino IDE keeps
the port visible through the touch-reset cycle.
- DFU entry:
SWRF (1200-bps touch via software reset) -> fast blink ~4 Hz
Double-tap RESET/PF6 -> medium blink ~2 Hz
Blank application slot -> slow blink ~1 Hz
A single PF6 press runs the application (Arduino standard).
- Passes reset cause to app via GPR.GPR0 (Optiboot/DxCore convention).
- Exits with WDT reset (WDRF) after upload so the new app runs cleanly.
Sources included under src/ (avr-gcc, build with make).
Adds board family 'avrduusb' for AVR DU parts with the USB CDC bootloader, and a sub-board entry for the Curiosity Nano EV59F82A. pins_arduino.h highlights (AVR64DU32, 32-pin): - LED_BUILTIN = PIN_PF2 (LED0, active LOW) - PIN_PF6 = RESET button (SW0) - Serial1 -> USART1 on PD6/PD7 (nEDBG virtual COM port) HWSERIAL1_MUX_DEFAULT=2 matches the board user guide wiring boards.txt: - Registers 'avrduusb' in the board family list - Full avrduusb.* definition (12/16/20/24 MHz, BOD, EESAVE, BOOTSIZE=8, USB PID 0xDA33 via -DUSB_PID=0xDA33) - avrduusb.menu.board.curiosity_nano_du sub-board option Verified: sketch upload, Serial, HID working on EV59F82A at 24 MHz.
Adds the C++/C bridge (USBCore_DU.cpp/.h) that connects the AVR-DU native USB stack to Arduino's PluggableUSB framework, plus passthrough headers (PluggableUSB.h, USBAPI.h) so bundled and user libraries can include them by the conventional names. Also adds the HID library (Mouse/Keyboard), adapted to use the AVR-DU endpoint definitions (usb_ep_types.h). This makes existing Arduino USB HID sketches portable to AVR-DU boards, as suggested in discussion SpenceKonde#630. Verified on hardware: AVR64DU32 Curiosity Nano enumerates as USB CDC plus HID (keyboard and mouse confirmed working).
Adds MIT license headers with copyright (Yusuke Shimizu / Workshop Asahi) and a clean-room provenance statement to the hand-written AVR-DU USB stack files. The statement documents that the implementation was written from public specifications (USB 2.0, CDC 1.2, HID 1.11, AVR64DU32 data sheet) without consulting Microchip's USB stack, ASF/START, TinyUSB, LUFA, or any other existing USB implementation. Vendored Arduino files (api/) and the HID library retain their original license headers and are unaffected.
Adds the full source tree and documentation for the AVR-DU CDC bootloader, which was previously committed only as a prebuilt .hex. Includes the clean-room implementation (src/), build setup (Makefile), and provenance/design notes (PROVENANCE.md, DESIGN.md, LICENSE.md). The bootloader is licensed LGPL 2.1 to match the host DxCore repository; its headers document that it was written from public specifications (AVR64DU32 data sheet, STK500 protocol app note, USB 2.0 / CDC PSTN 1.20) without consulting Optiboot, LUFA, TinyUSB, V-USB, or other bootloader/USB sources. Build artifacts (.elf/.lst/.map) are excluded via .gitignore; the prebuilt .hex remains tracked.
Reorders the avrdu FLMAP menu so 'unlocked' (no LOCK_FLMAP) is the default instead of 'lockdefault'. On 64K AVR-DU parts the previous default applied -DLOCK_FLMAP -DFLMAPSECTION1, which prevented sketches from running with the out-of-the-box board settings. The USB boards (avrduusb, avrduupdi) are unaffected: they do not enable the FLMAP menu and always build unlocked via their board-level build.flmapopts.
platform.local.txt holds environment-specific tool paths and should not be committed to the repository.
…to_init declaration
c2c1979 to
e481cc3
Compare
- user_from_peripheral(TCA0): add DU offset 0x0E (was falling through to 0x10 = USERTCB0CAPT, corrupting TCB0's event user) - set_user_pin(): add DU mapping (EVOUTA=0x09/EVOUTD=0x0A/EVOUTF=0x0B, no EVOUTC); generic 0x09+port shortcut wrote USART0IRDA/TCA0CNTA - set_generator(pin)/assign_generator_pin(): implement for parts with PORTx.EVGENCTRLA; usage tracked by scanning channel generator_type (EVGENnSEL has no OFF value, so the register alone is ambiguous) - get_generator_channel(pin): fix EVGEN1 compare (missing >>4), use PORT_EVGENnSEL_gm instead of 0x0F/0xF0 (bits 3/7 are read-only) - Event_parts.h: remove unreachable DU user enum from the Dx-only block - wiring_extra.cpp: fix (chan+1)<3 int promotion making chan=255 unreachable in _setEventPin(); on DU auto-select is not possible (flag bits read-only) - documented and guarded Hardware-verified on AVR64DU32 Curiosity Nano: 25 register-level checks pass; RTC_EVGEN0->EVOUTA(PA7) and PD1 pin event->EVOUTA confirmed working with no CPU involvement.
…tion Add the application-callable SPM entry that the Flash library relies on, generalizing the Optiboot_dx convention (stub in the last 6 bytes of the boot section) to this 4 KB bootloader: 0x0FFA: spm z+ ; ret (0x950895F8, or nop; ret with -DAPP_NOSPM) 0x0FFE: version word 0x1A01 (0x1Axx = AVR-DU CDC bootloader family) Placed via --section-start with -Wl,--undefined to survive --gc-sections; new src/spm_entry.c added to the build. Also fix a latent GCC 15 link problem: .rodata (USB descriptors, 253 bytes) was being placed at flash byte 0x8000 - the FLMAP-window default - i.e. *outside* the 4 KB boot section, inside application space. Uploading any application through the bootloader would have erased the bootloader's own descriptors. Building with -mrodata-in-ram folds the constants into the .data init image so the produced hex is fully contained in 0x0000-0x0FFF (verified: record range 0x0000-0x0FFF, total 3922 of 4096 bytes). This is the 'bootloader hex contains records in unexpected flash regions' issue observed earlier with GCC 15 builds.
When USING_AVRDU_CDC_BOOTLOADER is defined (the avrduusb board), SPMCOMMAND becomes 'call 0x0ffa' - the stub the CDC bootloader now carries in the last 6 bytes of its 4 KB boot section - and checkWritable() validates the new convention: BOOTSIZE fuse 0x08, version word 0x1Axx at 0x0FFE, and the spm z+ opcode (0x95F8) at 0x0FFA, with APP_NOSPM builds reported as FLASHWRITE_DISABLED. Takes precedence over the USING_OPTIBOOT path (both macros are defined for the CDC-bootloader boards). Adds Flash/examples/FlashWriteTest: prints checkWritable(), then erases the last flash page, writes a 16-word pattern and verifies it, refusing to touch the SPM entry when checkWritable() fails. Verified: the compiled sketch contains 'call 0xffa' at both write sites and builds for avrduusb (6080 bytes).
The previous commit added -mrodata-in-ram unconditionally. That is
wrong for this repository: DxCore's reference toolchain is
avr-gcc 7.3.0-atmel3.6.1-azduino8, which does not know the option
(GCC 15 introduced it) and aborts the build with
'unrecognized command line option'.
It is also unnecessary there. On avr-gcc <= 14 the avrxmega2 linker
script folds *(.rodata) into the .data output section (RAM, with the
init image loaded right after .text), so the bootloader hex is already
contained in the boot section; only GCC >= 15 changed the default to
place .rodata in the FLMAP window at flash 0x8000 - outside the boot
section - which is what the flag now guards against. avrxmega3 parts
(AVR16/32DUxx) map all of flash and load .rodata right after .text, so
they need no flag on any compiler.
RODATA_FLAGS is therefore derived from the compiler version
(CC -dumpversion) and the target: -mrodata-in-ram only for avr64du*
on GCC >= 15, empty otherwise, and still overridable from the command
line.
objcopy now also sets alloc,load on .spmtarg/.blversion explicitly
(as optiboot_dx does) so the stub is emitted into the hex regardless
of binutils version.
Verified by building the bootloader with both toolchains:
avr-gcc 7.3.0 (Atmel), avr64du32 : no flag, hex 0x0000-0x0FFF,
stub f8 95 08 95 01 1a at 0x0FFA
avr-gcc 7.3.0 (Atmel), avr32du32 : same
avr-gcc 15.2.0, avr64du32 : flag added, same result
checkWritable() was taught about the CDC bootloader, but every erase and
write function carries its own, separate guard that hard-codes Optiboot's
geometry:
if (FUSE.BOOTSIZE != 0x01) return FLASHWRITE_NOBOOT;
if (address < 512) return FLASHWRITE_BADADDR;
With the 4 KB CDC bootloader (BOOTSIZE = 8) those guards rejected every
operation with FLASHWRITE_NOBOOT (0x20 = 32) before the SPM entry was ever
called - as seen on hardware, where FlashDemo reported checkWritable() = OK
and then returned 32 from erasePage() and each write, leaving the flash
untouched (all 0xFF).
Replace the hard-coded numbers with REQUIRED_BOOTSIZE / BOOTLOADER_END,
defined next to SPMCOMMAND:
CDC bootloader : BOOTSIZE fuse 0x08, application flash starts at 4096
Optiboot : BOOTSIZE fuse 0x01, application flash starts at 512
SPM_FROM_APP : as Optiboot (the SPM routine lives in the first page)
BOOTLOADER_END also restores the intent of the lower address bound: refuse
writes that would land inside the bootloader (the hardware refuses them too
- per DS40002548A Table 11-3, code executing in BOOT may write APPCODE,
APPDATA, EEPROM, USERROW and BOOTROW, but never BOOT).
Verified: FlashDemo builds for avrduusb and emits 'call 0xffa' at all four
write sites.
… lost data pointer
Hardware run of FlashDemo exposed three bugs in writeBytes(), all present
upstream and not specific to the DU: the 15-byte array write left its last
byte unwritten (flash still 0xFF where a 0x00 was expected).
if (address & 0x01) {
status = writeByte(tAddress++, *(data)); // (1) data is never advanced
length--;
}
if (length > 1) {
status = writeWords(tAddress, (uint16_t*) data, (length >> 1));
}
if (length & 1) {
data += (length & 0xFFFE);
status = writeByte(tAddress + length - 2, *data); // (2) off by one
}
return status; // (3) may be uninitialised
(1) For an unaligned start address the leading byte is written from data[0],
but the pointer is not incremented, so the following words re-write
data[0..] instead of data[1..] - every byte after the first is wrong.
(2) The trailing byte of an odd-length write goes to (address + length - 2),
i.e. one byte too low: the final byte is never written, and the
second-to-last byte is silently ANDed with it (flash bits only clear).
For length == 1 this writes one byte BELOW the requested address.
(3) With length == 0 neither branch runs and an uninitialised status is
returned.
Rewritten to advance address, data pointer and length together, and to
return FLASHWRITE_0LENGTH for a zero-length write, as writeWords() does.
Also corrects FlashDemo's commentary: it announces a write of 0x2F but
writes 0x18, and the resulting mangling is not arbitrary - flash bits can
only go 1 -> 0, so 'l' (0x6C) & 0x18 = 0x08, a backspace that the terminal
acts on, which is why the last line prints as "Helo Flash".
The DU-series USERROW is 512 bytes at 0x1200 - 16x the 32 bytes of the DA/DB/DD parts. The NVMCTRL sequence the library uses (FLPER to erase, FLWR + ST through the mapped USERROW) is correct for the DU, but every 8-bit assumption in the implementation breaks at 512 bytes: - for (byte i = 0; i < USER_SIGNATURES_SIZE; i++) never terminates when the size is 512, because i wraps at 255. __USigload() and both loops in __USigflush() are infinite loops: the part hangs on the first write that needs an erase, and on any flush(). - The read/write index parameters and the USRef/USPtr index members are uint8_t, so USERSIG.write(300, x) silently writes to index 44; the idx &= (USER_SIGNATURES_SIZE - 1) wrap is a no-op on a uint8_t. - length() is constexpr uint8_t, so it returns 512 & 0xFF = 0 (and end() with it, breaking iteration). - The flush() diff counter is uint8_t, and its comparison is inverted: it counts *matching* bytes but treats 0 as "nothing to do". The erase-avoidance optimisation therefore never worked as intended - even on Dx, where an unchanged row still got an erase/write cycle, and a row in which every byte changed was silently discarded. - Off-by-one in the raw-write bounds check (idx > size, should be >=). Also: put() called __USigflush() inside its byte loop for objects larger than four bytes, i.e. one erase/write cycle of the entire USERROW *per byte* - a 12-byte struct cost 12 cycles out of a limited endurance budget. The commit now happens once, after the loop.
The library shipped without any example. UsersigTest exercises the USERROW
against the hardware and reports PASS/FAIL per step:
1 erase() - the whole row reads 0xFF
2 write() on an erased row - returns 1 (written straight to flash),
including indices 256, 300 and 511
3 write() needing an erase - returns 0 (buffered), read() sees the
pending value, the USERROW does not
4 flush() - reports the number of changed bytes, the
new value lands, the rest of the row
survives the erase/rewrite
5 put()/get() a 12-byte object at index 480
6 a persistence marker with a run counter
Every check verifies the *true* address by reading the mapped USERROW
directly, so a library that truncates the index to 8 bits cannot make a
test pass by writing and reading the same wrong byte.
The sketch starts with a safety gate: if USERSIG.length() disagrees with
USER_SIGNATURES_SIZE the library is not 512-byte safe, and it stops before
any write - on such a library a flush() would hang the part forever.
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.
This is a draft PR to share a working AVR DU-series implementation for
early feedback and integration testing. A few design points are still open
(see "Open questions"), so this is not yet proposed for merge as-is.
What this adds
boards.txt,pin variants for 14/20/28/32-pin packages
(currently as dedicated variants — see Open questions)
exposed through Arduino's Pluggable USB API
usbcdcboot): sources, build scripts, docs (DESIGN.md / PROVENANCE.md),and prebuilt hex files for all DU flash sizes
analogRead()range fix for DU parts, PWMTest.ino example fixDU-specific pin and resource definitions (CCL LUT pins, the single AC's
input pins, EVSYS output pins), reflecting that the DU dedicates PORTC
(and TCD/PLL) to native USB. Additive only — guarded for DU parts, no
change to other targets. Pin/resource definitions are derived from the
datasheet (DS40002548A).
enabling composite devices such as USB MIDI.
Board layout: two entries — the existing generic no-bootloader board
(
avrdu, programmed via UPDI) and a new USB board (avrduusb, with theCDC bootloader).
Design notes
Keyboard/Mouselibraries and third-partyHID libraries (HID-Project, ArduinoJoystickLibrary) work unmodified.
(
USB0_BUSEVENT_vectfor bus reset / SOF,USB0_TRNCOMPL_vectfor SETUP and per-endpoint transfer-complete).All USB activity - control transfers, HID/CDC/MIDI endpoints - is serviced from these ISRs.
EP0 host-to-device data stages are staged and re-dispatched through
PluggableUSB().setup(),so
USB_RecvControl()has Arduino-AVR-compatible semantics (HID SET_REPORT, LED feedback, feature reports, RawHID host-to-device).Dedicated interrupt/bulk OUT endpoints (
USB_Recv/USB_Available) are also implemented, used by USB MIDI.ASF/START, TinyUSB, LUFA, or V-USB code was consulted or copied.
See
PROVENANCE.mdandDESIGN.mdundermegaavr/bootloaders/usbcdcboot/.usbcdcboot_16du,_32du,_64du) plus 14-pin builds (_16du14,_32du14),which use PD6 as the LED pin instead of PA7, following the DA/DB/DD convention.
Serialmapping on the USB board: onavrduusb,Serialis mapped to the USB CDC port (USBSerial),following the convention of native-USB Arduino boards (Leonardo/Micro).
The hardware UART0 remains available as
Serial0. Other DxCore boards keepSerial=Serial0(UART0),so this is a deliberate divergence on this one board.
Verified on hardware (AVR64DU32 Curiosity Nano, EV59F82A)
Upload paths: both the CDC bootloader and the on-board nEDBG
debugger (UPDI) verified.
USB CDC:
Serialworks, including sketch upload through the CDCbootloader.
USB HID — the following examples enumerate and work with unmodified
libraries:
Blink,KeyboardSerial,ButtonMouseControlPWMTestBootKeyboard,ImprovedKeyboard,NKROKeyboard,KeyboardLED(host-to-device LED feedback verified),RawHID(both directions verified with the host-side tools from HID-Project extras),
AlternateLanguageLayout,KeyLayoutTest,Consumer,AbsoluteMouse,ImprovedMouse,GamePad,SurfaceDial,SystemUSB MIDI (MIDIUSB): the MIDIUSB_read / MIDIUSB_write examples work (MIDI in/out verified with MIDI-OX),
with
Serial(CDC) active in the same sketch.This exercises dynamic interrupt/bulk OUT endpoints (
USB_Recv) and IAD-based composite enumeration -paths beyond what CDC/HID alone use.
Peripherals — the following work with the stock core (only the analogRead fix was needed);
the CCL/AC/Event libraries additionally needed DU pin/resource definitions (see "What this adds"):
(PF6/PF7 are reset/UPDI)
(PA0/PA1 have no ADC channel)
the third-party LCD_I2C library works unmodified
no swap() needed since the variants set these as default mux positions (USART1 has only the ALT2 position on DU)
uses the standard modern-AVR WDT interface (PROTECTED_WRITE + WDT_PERIOD*_gc), as on other DxCore parts
EEPROM access through the usbcdcboot bootloader also works (avrdude
-U eeprom:r/wover the CDC serial port).(CCL/AC/Event required adding DU pin/resource definitions to those libraries — see "What this adds")
Known limitations
Relationship to PR #633
This PR intentionally does not include the OSCCFG / SYSCFG1 (USBSINK) fuse fixes for the generic DU chip entries - those are already proposed in #633 by @felias-fogg,
and I'd rather not duplicate his work here.
The USB board added by this PR carries its own full fuse definitions (including OSCCFG) and is verified on hardware,
so the two PRs are complementary.
Once #633 lands, I'll rebase this branch on top of it.
Open questions (would value your guidance)
extra_flags- raised in Discussion 630.AVR-DU series support: status check, contribution offer, and commercial board plans #630
(0x1209:0x0001 / 0x0002), which are intended for development only.
Before merge, real IDs are needed - happy to register with pid.codes or follow whatever scheme DxCore prefers for DU boards. Also raised in Discussion 630.
AVR-DU series support: status check, contribution offer, and commercial board plans #630
Serialnaming: isSerial= USB CDC (with UART0 asSerial0) the right convention for DU USB boards,or would you prefer to keep
Serial= UART0 and expose CDC under a different name (e.g.SerialUSB)?Happy to follow your preference.
I'm keeping this as a draft and am glad to restructure
(split, rework boards.txt layout, adjust variants) based on your preferences.