Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
d272589
neccessary -> necessary and fix linter bot issue
piotrfila Jan 10, 2026
3e3b6de
scan unhandled buffer linearly
piotrfila Jan 10, 2026
b587b46
redo endpoint handling
piotrfila Jan 10, 2026
a653711
separate getting usb rx data and requesting more
piotrfila Jan 10, 2026
9504866
make CdcClassDriver rx interrupt-safe
piotrfila Jan 10, 2026
52ea727
add usb packet length type
piotrfila Jan 10, 2026
b655989
make CdcClass driver tx interrupt-safe
piotrfila Jan 10, 2026
503c998
allow partial usb tx and require calling ep_writev and ep_listen only…
piotrfila Jan 10, 2026
93ce596
documentation and configurable handler names
piotrfila Jan 10, 2026
f210076
first draft of an example driver that explains the comptime driver in…
piotrfila Jan 10, 2026
942aa42
more error checking around interfaces and string descriptors
piotrfila Jan 10, 2026
340d9c3
endpoint_open -> ep_open for consistency
piotrfila Jan 10, 2026
9eabcab
style fixes
piotrfila Jan 10, 2026
870f0d6
appease linter bot
piotrfila Jan 11, 2026
2a79c78
add u32 little endian wrapper
piotrfila Jan 11, 2026
f553d34
respect endianness in setup packet and shortcuts for bulk and interru…
piotrfila Jan 11, 2026
27edab3
start at ch32v usb
Copper280z Jan 11, 2026
2ed6b4d
cleanum usb examples
piotrfila Jan 13, 2026
cd4e348
change the example usb driver to a simple echo
piotrfila Jan 13, 2026
0959d37
assign endpoints and interfaces (more) automatically
piotrfila Jan 13, 2026
36f02ea
better handling of device class, subclass and protocol
piotrfila Jan 13, 2026
b7bed05
separate usb device and controller
piotrfila Jan 13, 2026
93cbc87
more convenient descriptor creation
piotrfila Jan 13, 2026
55921cc
handle bus resets correctly
piotrfila Jan 13, 2026
f373714
fix startup
Copper280z Jan 17, 2026
bbe968f
more examples cleanup
piotrfila Jan 13, 2026
baee980
add BOS descriptor
piotrfila Jan 17, 2026
ae7d203
simplify setup packet handling
piotrfila Jan 17, 2026
a6e8c89
add usb device logging
piotrfila Jan 17, 2026
66f0116
better use scoped logging
piotrfila Jan 17, 2026
f07fe5c
reorganize ClassSubclassProtocol
piotrfila Jan 17, 2026
c40f907
adhere more to style guidelines
piotrfila Jan 17, 2026
b9f61f5
...again
piotrfila Jan 17, 2026
8dd3a7b
add USBHD/USBHS device backend implementation
Copper280z Jan 17, 2026
4c399ef
add enums to CDC
piotrfila Jan 18, 2026
3f494d0
cleanup
piotrfila Jan 18, 2026
5079a5b
Merge branch 'main' into usb-driver-rework
piotrfila Jan 18, 2026
6474eab
EpNum -> EP_Num and fix pins in examples
piotrfila Jan 18, 2026
4e00819
usb periph is alive and responding to host, but not enumerating corre…
Copper280z Jan 18, 2026
94c4e64
change to work with v307 evt
Copper280z Jan 18, 2026
32feeb7
Logging updates and call on_buffer at end of setup flag block
Copper280z Jan 18, 2026
5fff85c
add fifo overflow warning and reduce SOF spam
Copper280z Jan 18, 2026
efefeae
logging swirl
Copper280z Jan 18, 2026
6f6ba02
implement GetStatus
piotrfila Jan 18, 2026
c233851
make it to config descriptor but fail to send 2nd packet.
Copper280z Jan 18, 2026
872e794
driver handler type safety
piotrfila Jan 19, 2026
52396d6
successful enumeration!
Copper280z Jan 19, 2026
aa8034d
inline get_setup_packet()
piotrfila Jan 19, 2026
7667ea7
fix ep_writev only using the first buffer
piotrfila Jan 19, 2026
1a1aab8
add rp2xxx reset interface
piotrfila Jan 21, 2026
6c89d64
start at ch32v usb
Copper280z Jan 11, 2026
64d16ce
fix startup
Copper280z Jan 17, 2026
e8c6a40
add USBHD/USBHS device backend implementation
Copper280z Jan 17, 2026
8f4fdcc
usb periph is alive and responding to host, but not enumerating corre…
Copper280z Jan 18, 2026
d36e85c
change to work with v307 evt
Copper280z Jan 18, 2026
a3673d8
Logging updates and call on_buffer at end of setup flag block
Copper280z Jan 18, 2026
321c317
add fifo overflow warning and reduce SOF spam
Copper280z Jan 18, 2026
59450b4
logging swirl
Copper280z Jan 18, 2026
07bfc32
make it to config descriptor but fail to send 2nd packet.
Copper280z Jan 18, 2026
2bbaf5b
successful enumeration!
Copper280z Jan 19, 2026
66e9c32
minor changes while hunting down why cdc transfers don't work.
Copper280z Jan 22, 2026
00fe33b
Fix data toggle config
Copper280z Jan 22, 2026
2011590
Fixed data toggles, CDC Data flows!
Copper280z Jan 22, 2026
9865fe3
comment out some logging
Copper280z Jan 22, 2026
985d6e8
Merge branch 'origin/umain' of https://github.com/Copper280z/microzig…
Copper280z Jan 22, 2026
c337146
Refactor ch32 USB logging and enhance clock configuration for CH32V b…
Copper280z Jan 25, 2026
baed799
Refactor USBHS interrupt handling and switch toggle management to man…
Copper280z Jan 25, 2026
df4e181
Refactor USB polling logic into a separate function and clean up unus…
Copper280z Jan 25, 2026
dc8ec01
Merge remote-tracking branch 'upstream/main' into ch32_usb
Copper280z Jan 31, 2026
ad686c3
update for latest usb core changes
Copper280z Jan 31, 2026
fce6646
remove unfinished usbd driver
Copper280z Jan 31, 2026
8a0e752
revert to upstream with minimal updates for usbhs
Copper280z Jan 31, 2026
57f498f
revert changes to uart_log
Copper280z Jan 31, 2026
8b9704c
remove old example driver
Copper280z Jan 31, 2026
0049e03
cleanup
Copper280z Jan 31, 2026
01624dc
cleanup usbhs clock setup
Copper280z Jan 31, 2026
19cc7f3
more cleanup
Copper280z Jan 31, 2026
493c041
revert board clock setup to one that works on current upstream.
Copper280z Jan 31, 2026
02ce566
revert most of clocks.zig to upstream.
Copper280z Jan 31, 2026
349fb6a
mirror rp2xxx better
Copper280z Jan 31, 2026
e0cef46
revert usart changes, cleanup example to match rp2xxx better
Copper280z Jan 31, 2026
0717b51
adjust comment
Copper280z Jan 31, 2026
b9892ab
get rid of regs function, change a log call
Copper280z Jan 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/core/usb.zig
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
const desc_device: descriptor.Device = .{
.bcd_usb = config.bcd_usb,
.device_triple = config.device_triple,
.max_packet_size0 = @max(config.max_supported_packet_size, 64),
.max_packet_size0 = @min(config.max_supported_packet_size, 64),
.vendor = .from(config.vendor.id),
.product = .from(config.product.id),
.bcd_device = config.bcd_device,
Expand Down
1 change: 1 addition & 0 deletions examples/wch/ch32v/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub fn build(b: *std.Build) void {
.{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "blinky_systick_ch32v303", .file = "src/blinky_systick.zig" },
.{ .target = mb.ports.ch32v.boards.ch32v305.nano_ch32v305, .name = "nano_ch32v305_blinky", .file = "src/board_blinky.zig" },
.{ .target = mb.ports.ch32v.boards.ch32v307.ch32v307v_r1_1v0, .name = "ch32v307v_r1_1v0_blinky", .file = "src/blinky.zig" },
.{ .target = mb.ports.ch32v.boards.ch32v307.ch32v307v_r1_1v0, .name = "ch32v307v_r1_1v0_usb_cdc", .file = "src/usb_cdc.zig" },
};

for (available_examples) |example| {
Expand Down
134 changes: 134 additions & 0 deletions examples/wch/ch32v/src/usb_cdc.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const std = @import("std");
const microzig = @import("microzig");

const hal = microzig.hal;
const time = hal.time;
const gpio = hal.gpio;
const usb = microzig.core.usb;

const USB_Serial = usb.drivers.CDC;

const RCC = microzig.chip.peripherals.RCC;
const AFIO = microzig.chip.peripherals.AFIO;
const PFIC = microzig.chip.peripherals.PFIC;

const usart = hal.usart.instance.USART1;

const usart_tx_pin = gpio.Pin.init(0, 9); // PA9

pub const microzig_options = microzig.Options{
.logFn = hal.usart.log,
.log_level = .debug,
.log_scope_levels = &.{
.{ .scope = .usb_dev, .level = .warn },
.{ .scope = .usb_ctrl, .level = .warn },
.{ .scope = .usb_cdc, .level = .warn },
},
};

const USBController = usb.DeviceController(.{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Rename USBController to USB_Controller, it should be more in line with our style guidelines. This automation is not perfect so take it with a grain of salt.

.bcd_usb = .v2_00,
.device_triple = .unspecified,
.vendor = .{ .id = 0x2E8A, .str = "MicroZig" },
.product = .{ .id = 0x000A, .str = "ch32v307 Test Device" },
.bcd_device = .v1_00,
.serial = "someserial",
.max_supported_packet_size = 512,
.configurations = &.{.{
.attributes = .{ .self_powered = false },
.max_current_ma = 50,
.Drivers = struct { serial: USB_Serial },
}},
}, .{.{
.serial = .{ .itf_notifi = "Board CDC", .itf_data = "Board CDC Data" },
}});

pub var usb_dev: hal.usbhs.Polled(
.{ .prefer_high_speed = true },
) = undefined;

var usb_controller: USBController = .init;

pub fn main() !void {
// Board brings up clocks and time
microzig.board.init();
microzig.hal.init();

// Enable peripheral clocks for USART1 and GPIOA
RCC.APB2PCENR.modify(.{
.IOPAEN = 1, // Enable GPIOA clock
.AFIOEN = 1, // Enable AFIO clock
.USART1EN = 1, // Enable USART1 clock
});

// Configure TX pin as alternate function push-pull
usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz);

// Initialize USART1 at 115200 baud
usart.apply(.{ .baud_rate = 115200 });

hal.usart.init_logger(usart);
std.log.info("UART logging initialized.", .{});

std.log.info("Initializing USB device.", .{});

usb_dev = .init();

var i: u32 = 0;
var old: u64 = time.get_time_since_boot().to_us();
var new: u64 = 0;

while (true) {
if (usb_controller.drivers()) |drivers| {
new = time.get_time_since_boot().to_us();
if (new - old > 500000) {
old = new;
i += 1;
std.log.info("cdc test: {}", .{i});

usb_cdc_write(&drivers.serial, "This is very very very very very very very very long text sent from ch32v30x by USB CDC to your device: {}\r\n", .{i});
}

// read and print host command if present
const message = usb_cdc_read(&drivers.serial);
if (message.len > 0) {
usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message});
}
}
usb_dev.poll(false, &usb_controller);
}
}

var usb_tx_buff: [1024]u8 = undefined;

// Transfer data to host
// NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled
pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytype) void {
const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{};
var write_buff = text;
while (write_buff.len > 0) {
write_buff = write_buff[serial.write(write_buff)..];
while (!serial.flush())
usb_dev.poll(false, &usb_controller);
}
}

var usb_rx_buff: [1024]u8 = undefined;

// Receive data from host
// NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation
pub fn usb_cdc_read(
serial: *USB_Serial,
) []const u8 {
var total_read: usize = 0;
var read_buff: []u8 = usb_rx_buff[0..];

while (true) {
const len = serial.read(read_buff);
read_buff = read_buff[len..];
total_read += len;
if (len == 0) break;
}

return usb_rx_buff[0..total_read];
}
3 changes: 2 additions & 1 deletion port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const ch32v = microzig.hal;

/// Clock configuration for this board
pub const clock_config: ch32v.clocks.Config = .{
.source = .hsi,
.source = .hse,
.hse_frequency = 8_000_000,
.target_frequency = 48_000_000,
};

Expand Down
13 changes: 13 additions & 0 deletions port/wch/ch32v/src/hals/ch32v30x.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,31 @@ pub const clocks = @import("clocks.zig");
pub const time = @import("time.zig");
pub const i2c = @import("i2c.zig");
pub const usart = @import("usart.zig");
const std = @import("std");
pub const usbhs = @import("./usbhs.zig");

/// HSI (High Speed Internal) oscillator frequency
/// This is the fixed internal RC oscillator frequency for CH32V30x
pub const hsi_frequency: u32 = 8_000_000; // 8 MHz
pub const hse_frequency: u32 = 8_000_000; // 8 MHz

/// Default interrupt handlers provided by the HAL
pub const default_interrupts: microzig.cpu.InterruptOptions = .{
.TIM2 = time.tim2_handler,
// .USBHS = usb.usbhs_interrupt_handler,
};

/// Initialize HAL subsystems used by default
pub fn init() void {
// Configure TIM2 timing driver
time.init();
clocks.init(.{
.source = .hse,
.hse_frequency = hse_frequency,
.target_frequency = 48_000_000,
}); // 8 MHz external crystal
clocks.enable_usbhs_clock(.{
.ref_source_hz = hse_frequency,
.ref_source = .hse,
});
}
144 changes: 144 additions & 0 deletions port/wch/ch32v/src/hals/clocks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,150 @@ pub fn get_freqs() ClockSpeeds {
};
}

// ============================================================================
// Enable + configure USBHS clocks.
// ============================================================================

/// This configures RCC_CFGR2 fields for the USBHS PHY PLL reference (if present),
/// selects whether the USBHS 48MHz clock comes from the system PLL clock or the USB PHY,
/// and enables the AHB clock gate for USBHS.
///
/// Note: The SVD names bit31 as `USBFSSRC`, but the reference manual
/// describes it as `USBHSSRC` ("USBHS 48MHz clock source selection").
pub const UsbHsClockConfig = struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Rename UsbHsClockConfig to USB_HsClockConfig, it should be more in line with our style guidelines. This automation is not perfect so take it with a grain of salt.

pub const RefSource = enum { hse, hsi };
/// Desired PHY PLL reference frequency (the USBHSCLK field selects one of these).
/// If null, we'll pick the highest one we can generate exactly: 8MHz, then 5MHz, 4MHz, 3MHz.
pub const RefFreq = enum(u2) {
mhz3 = 0b00,
mhz4 = 0b01,
mhz8 = 0b10,
mhz5 = 0b11,
};

/// If true select USB PHY as the 48MHz source and enable the PHY internal PLL.
/// If false, select PLL CLK as the 48MHz source (you must ensure a valid 48MHz PLL clock exists).
use_phy_48mhz: bool = true,

/// PHY PLL reference source (only used when use_phy_48mhz).
ref_source: RefSource = .hse,

/// Frequency of the chosen ref_source, in Hz.
/// Typical: HSE crystal (e.g. 8_000_000, 12_000_000, 24_000_000) or HSI (often 8_000_000).
ref_source_hz: u32,

ref_freq: ?RefFreq = null,
};

fn div_to_usbhsdiv(div: u32) u3 {
// RCC_CFGR2 USBHSDIV encoding:
// 000: /1, 001: /2, ... 111: /8
return @as(u3, @intCast(div - 1));
}

fn hz_for_ref(ref: UsbHsClockConfig.RefFreq) u32 {
return switch (ref) {
.mhz3 => 3_000_000,
.mhz4 => 4_000_000,
.mhz5 => 5_000_000,
.mhz8 => 8_000_000,
};
}

const HSPLLSRC = enum(u2) {
hse = 0,
hsi = 1,
};

/// Selects USBHS Clock source, options are:
/// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or
/// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true),
/// in which case it also configures the PHY PLL reference (USBHSCLK/USBHSPLLSRC/USBHSDIV)
/// and enables the PHY internal PLL (USBHSPLL).
pub fn enable_usbhs_clock(comptime cfg: UsbHsClockConfig) void {
// Turn on the AHB clock gate for the USBHS peripheral block.

// If caller prefers PLL CLK, set PLL CLK selection and keep PHY PLL off.
if (!cfg.use_phy_48mhz) {
RCC.CFGR2.modify(.{
// SVD name mismatch: USBFSSRC field == USBHS 48MHz source select per RM.
.USBFSSRC = 0, // 0: PLL CLK
.USBHSPLL = 0, // PHY internal PLL disabled
});
return;
}

// Ensure the selected oscillator is on (best-effort; usually already enabled by system clock init).
switch (cfg.ref_source) {
.hse => {
if (RCC.CTLR.read().HSEON == 0) RCC.CTLR.modify(.{ .HSEON = 1 });
while (RCC.CTLR.read().HSERDY == 0) {}
},
.hsi => {
if (RCC.CTLR.read().HSION == 0) RCC.CTLR.modify(.{ .HSION = 1 });
while (RCC.CTLR.read().HSIRDY == 0) {}
},
}

// Choose a reference frequency and divider that is exactly achievable.
const candidates = [_]UsbHsClockConfig.RefFreq{ .mhz8, .mhz5, .mhz4, .mhz3 };

comptime var chosen_ref: UsbHsClockConfig.RefFreq = undefined;
comptime var chosen_div: u32 = 0;
comptime {
if (cfg.ref_freq) |forced| {
const want = hz_for_ref(forced);
var found = false;
for (1..9) |div| {
if (cfg.ref_source_hz % div == 0 and (cfg.ref_source_hz / div) == want) {
chosen_ref = forced;
chosen_div = div;
found = true;
break;
}
}
if (!found) {
@compileError("USBHS PHY PLL ref cannot be generated exactly from ref_source_hz with /1..8 prescaler.");
}
} else {
var found = false;
for (candidates) |ref| {
const want = hz_for_ref(ref);
for (1..9) |div| {
if (cfg.ref_source_hz % div == 0 and (cfg.ref_source_hz / div) == want) {
chosen_ref = ref;
chosen_div = div;
found = true;
break;
}
}
if (found) break;
}
if (!found) {
@compileError("USBHS PHY PLL ref cannot be generated: need 3/4/5/8MHz from ref_source_hz using /1..8 prescaler.");
}
}
}
// Program CFGR2:
// - USBHSPLLSRC: 0=HSE, 1=HSI
// - USBHSDIV: prescaler (/1..8)
// - USBHSCLK: selects which ref freq the PHY PLL expects (3/4/8/5 MHz)
// - USBHSPLL: enable PHY internal PLL
// - USBHSSRC (SVD calls it USBFSSRC): 1=USB PHY as 48MHz source
RCC.CFGR2.modify(.{
.USBHSPLLSRC = switch (cfg.ref_source) {
.hse => 0,
.hsi => 1,
},
.USBHSDIV = div_to_usbhsdiv(chosen_div),
.USBHSCLK = @as(u2, @intFromEnum(chosen_ref)),
.USBHSPLL = 1,
.USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY
});

RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 });
}

// ============================================================================
// Convenience Functions for HAL Modules
// ============================================================================
Expand Down
Loading
Loading