From 15a9c865215ce30a2675a36b1d363a8454174f04 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Wed, 9 Sep 2026 17:16:10 -0400 Subject: [PATCH 1/2] ch32vxxx: i2c/spi: Support board defaults and Setup.apply flows --- examples/wch/ch32v/src/i2c_bus_scan.zig | 36 ++++--------- examples/wch/ch32v/src/i2c_eeprom.zig | 44 +++++---------- .../wch/ch32v/src/i2c_position_sensor.zig | 35 ++++-------- examples/wch/ch32v/src/sharp_niceview.zig | 43 +++++---------- examples/wch/ch32v/src/spi_flash_w25q.zig | 53 ++++++------------- examples/wch/ch32v/src/spi_loopback.zig | 43 ++++----------- port/wch/ch32v/src/boards/LANA_TNY.zig | 15 ++++++ port/wch/ch32v/src/boards/nanoCH32V203.zig | 15 ++++++ port/wch/ch32v/src/hals/i2c.zig | 20 +++++-- port/wch/ch32v/src/hals/spi.zig | 26 +++++++-- 10 files changed, 142 insertions(+), 188 deletions(-) diff --git a/examples/wch/ch32v/src/i2c_bus_scan.zig b/examples/wch/ch32v/src/i2c_bus_scan.zig index 7c462722a..6fab89807 100644 --- a/examples/wch/ch32v/src/i2c_bus_scan.zig +++ b/examples/wch/ch32v/src/i2c_bus_scan.zig @@ -1,11 +1,11 @@ const std = @import("std"); const microzig = @import("microzig"); const hal = microzig.hal; -const gpio = hal.gpio; +const board = microzig.board; const i2c = hal.i2c; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; +const i2c_hw = board.i2c_setup; pub const panic = microzig.panic; @@ -20,36 +20,20 @@ comptime { pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for alternate function (disable GPIO) - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); - // Initialize USART2 at 115200 baud (uses default pins PA2/PA3) - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); - - // I2C1 is on PB6 (SCL) and PB7 (SDA) - const scl_pin = hal.gpio.Pin.init(1, 6); // GPIOB pin 6 - const sda_pin = hal.gpio.Pin.init(1, 7); // GPIOB pin 7 - - // Configure I2C pins for alternate function (open-drain required for I2C) - scl_pin.configure_alternate_function(.open_drain, .max_50MHz); - sda_pin.configure_alternate_function(.open_drain, .max_50MHz); - - // Initialize I2C at 100kHz (uses default pins PB6/PB7) - const instance = i2c.instance.I2C1; - instance.apply(.{}); + // Initialize I2C + i2c_hw.apply(.{}); for (0..std.math.maxInt(u7)) |addr| { const a: i2c.Address = @fromBackingInt(@intCast(addr)); var rx_data: [1]u8 = undefined; - _ = instance.read_blocking(a, &rx_data, null) catch |e| { + _ = i2c_hw.instance.read_blocking(a, &rx_data, null) catch |e| { // Expected errors for non-present devices if (e != i2c.Error.NoAcknowledge and e != i2c.Error.Timeout) { std.log.warn("Unexpected error at 0x{X:0>2}: {}", .{ addr, e }); diff --git a/examples/wch/ch32v/src/i2c_eeprom.zig b/examples/wch/ch32v/src/i2c_eeprom.zig index a6f4d26b2..edac3efaa 100644 --- a/examples/wch/ch32v/src/i2c_eeprom.zig +++ b/examples/wch/ch32v/src/i2c_eeprom.zig @@ -1,11 +1,11 @@ const std = @import("std"); const microzig = @import("microzig"); const hal = microzig.hal; -const gpio = hal.gpio; +const board = microzig.board; const i2c = hal.i2c; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; +const i2c_hw = board.i2c_setup; pub const panic = microzig.panic; @@ -28,30 +28,14 @@ fn hex_dump(data: []const u8) void { pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for alternate function (disable GPIO) - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); - // Initialize USART2 at 115200 baud (uses default pins PA2/PA3) - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); - - // I2C1 is on PB6 (SCL) and PB7 (SDA) - const scl_pin = hal.gpio.Pin.init(1, 6); // GPIOB pin 6 - const sda_pin = hal.gpio.Pin.init(1, 7); // GPIOB pin 7 - - // Configure I2C pins for alternate function (open-drain required for I2C) - scl_pin.configure_alternate_function(.open_drain, .max_50MHz); - sda_pin.configure_alternate_function(.open_drain, .max_50MHz); - - // Initialize I2C at 100kHz (uses default pins PB6/PB7) - const instance = i2c.instance.I2C1; - instance.apply(.{}); + // Initialize I2C + i2c_hw.apply(.{}); const eeprom_address: i2c.Address = @fromBackingInt(0x50); @@ -60,9 +44,9 @@ pub fn main() !void { var data: [256]u8 = undefined; // Set address to 0x0000 (2 bytes: high, low) - try instance.write_blocking(eeprom_address, &.{ 0x00, 0x00 }, .from_ms(100)); + try i2c_hw.instance.write_blocking(eeprom_address, &.{ 0x00, 0x00 }, .from_ms(100)); // Sequential read - address auto-increments - try instance.read_blocking(eeprom_address, &data, .from_ms(100)); + try i2c_hw.instance.read_blocking(eeprom_address, &data, .from_ms(100)); std.log.info("Read {d} bytes from EEPROM:", .{data.len}); hex_dump(&data); @@ -70,14 +54,14 @@ pub fn main() !void { // Write value 0x42 at address 0x0005 // Format: [addr_high] [addr_low] [data] std.log.info("Writing 0x42 at address 0x0005", .{}); - try instance.write_blocking(eeprom_address, &.{ 0x00, 0x05, 0x42 }, .from_ms(100)); + try i2c_hw.instance.write_blocking(eeprom_address, &.{ 0x00, 0x05, 0x42 }, .from_ms(100)); // Wait for write cycle to complete (~5ms for AT24C256) hal.time.sleep_ms(10); // Read back to verify - try instance.write_blocking(eeprom_address, &.{ 0x00, 0x00 }, .from_ms(100)); - try instance.read_blocking(eeprom_address, &data, .from_ms(100)); + try i2c_hw.instance.write_blocking(eeprom_address, &.{ 0x00, 0x00 }, .from_ms(100)); + try i2c_hw.instance.read_blocking(eeprom_address, &data, .from_ms(100)); std.log.info("After write:", .{}); hex_dump(&data); diff --git a/examples/wch/ch32v/src/i2c_position_sensor.zig b/examples/wch/ch32v/src/i2c_position_sensor.zig index c4a791c55..1e9cbc594 100644 --- a/examples/wch/ch32v/src/i2c_position_sensor.zig +++ b/examples/wch/ch32v/src/i2c_position_sensor.zig @@ -1,14 +1,14 @@ const std = @import("std"); const microzig = @import("microzig"); const hal = microzig.hal; +const board = microzig.board; -const gpio = hal.gpio; const i2c = hal.i2c; const AS5600 = microzig.drivers.sensor.AS5600; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; +const i2c_hw = board.i2c_setup; pub const panic = microzig.panic; @@ -23,28 +23,13 @@ comptime { pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for alternate function (disable GPIO) - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); - // Initialize USART2 at 115200 baud (uses default pins PA2/PA3) - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); - - // I2C1 is on PB6 (SCL) and PB7 (SDA) - const scl_pin = hal.gpio.Pin.init(1, 6); // GPIOB pin 6 - const sda_pin = hal.gpio.Pin.init(1, 7); // GPIOB pin 7 - - // Configure I2C pins for alternate function (open-drain required for I2C) - scl_pin.configure_alternate_function(.open_drain, .max_50MHz); - sda_pin.configure_alternate_function(.open_drain, .max_50MHz); - - const instance = i2c.instance.I2C1; + // Initialize I2C with DMA const i2c_config = i2c.Config{ .baud_rate = 100_000, // 100 kHz .dma = .{ @@ -54,13 +39,13 @@ pub fn main() !void { .threshold = 4, // Threshold for DMA transfers }, }; - instance.apply(i2c_config); + i2c_hw.apply(i2c_config); // Get the specialized I2C_Device type for this config const I2C_DeviceType = hal.drivers.I2C_Device(i2c_config); // Create i2c device - var i2c_device = I2C_DeviceType.init(instance, null); + var i2c_device = I2C_DeviceType.init(i2c_hw.instance, null); // Pass device to driver to create sensor instance std.log.info("Creating AS5600 driver instance", .{}); var dev = AS5600.init(i2c_device.i2c_device()); diff --git a/examples/wch/ch32v/src/sharp_niceview.zig b/examples/wch/ch32v/src/sharp_niceview.zig index 03622579c..44e390399 100644 --- a/examples/wch/ch32v/src/sharp_niceview.zig +++ b/examples/wch/ch32v/src/sharp_niceview.zig @@ -32,11 +32,12 @@ const std = @import("std"); const microzig = @import("microzig"); const mdf = microzig.drivers; const hal = microzig.hal; +const board = microzig.board; const gpio = hal.gpio; const spi = hal.spi; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; +const spi_hw = board.spi_setup; pub const panic = microzig.panic; @@ -49,25 +50,16 @@ comptime { _ = microzig.export_startup(); } -// Pin definitions -const sck_pin = gpio.Pin.init(0, 5); // PA5 -const mosi_pin = gpio.Pin.init(0, 7); // PA7 +// CS pin (device-specific, not part of board SPI setup) const cs_pin = gpio.Pin.init(0, 3); // PA3 pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for logging - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); - - // Initialize USART2 at 115200 baud - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); std.log.info("Sharp Memory LCD (nice!view) Test", .{}); std.log.info("===================================", .{}); @@ -76,25 +68,15 @@ pub fn main() !void { std.log.info("Protocol: 3-wire SPI, LSB-first, CS active-high", .{}); std.log.info("", .{}); - // Configure SPI pins - std.log.info("Configuring SPI pins...", .{}); - sck_pin.configure_alternate_function(.push_pull, .max_50MHz); - mosi_pin.configure_alternate_function(.push_pull, .max_50MHz); - - // Configure control pins + // Configure CS pin (device-specific) cs_pin.enable_clock(); cs_pin.set_output_mode(.general_purpose_push_pull, .max_50MHz); - - // Initialize pins to safe states cs_pin.put(0); // CS low (idle state for Sharp - inverted logic) - // Initialize SPI1 with DMA support - const spi1 = spi.instance.SPI1; - // SPI configuration for Sharp Memory LCD // CRITICAL: LSB-first bit order, CS active-high! // Mode 0 (CPOL=0, CPHA=0), up to 2 MHz - std.log.info("Configuring SPI1...", .{}); + std.log.info("Configuring SPI...", .{}); std.log.info(" Baud rate: 1 MHz (matching ZMK device tree)", .{}); std.log.info(" Bit order: LSB-first (CRITICAL for Sharp!)", .{}); std.log.info(" CS polarity: Active-HIGH (CRITICAL for Sharp!)", .{}); @@ -109,13 +91,12 @@ pub fn main() !void { }, }; - // Note: spi.apply() automatically enables SPI1 clock - spi1.apply(spi_config); + spi_hw.apply(spi_config); // Create SPI Datagram Device wrapper const SPI_DD = hal.drivers.SPI_DatagramDevice(spi_config); var spi_dev = SPI_DD.init( - spi1, + spi_hw.instance, cs_pin, true, // Sharp needs CS HIGH during transmission (active-high) mdf.time.Duration.from_ms(100), diff --git a/examples/wch/ch32v/src/spi_flash_w25q.zig b/examples/wch/ch32v/src/spi_flash_w25q.zig index 8a9628146..c211fac0e 100644 --- a/examples/wch/ch32v/src/spi_flash_w25q.zig +++ b/examples/wch/ch32v/src/spi_flash_w25q.zig @@ -22,11 +22,12 @@ const std = @import("std"); const microzig = @import("microzig"); const mdf = microzig.drivers; const hal = microzig.hal; +const board = microzig.board; const gpio = hal.gpio; const spi = hal.spi; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; +const spi_hw = board.spi_setup; pub const panic = microzig.panic; @@ -67,48 +68,24 @@ const cs_pin = gpio.Pin.init(0, 3); // PA3 pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for logging - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); - - // Initialize USART2 at 115200 baud - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); std.log.info("W25Q128 SPI Flash Test", .{}); std.log.info("======================", .{}); std.log.info("", .{}); - // Configure SPI1 pins - // PA5: SCK (Alternate Function Push-Pull, 50MHz) - // PA6: MISO (Input Floating) - // PA7: MOSI (Alternate Function Push-Pull, 50MHz) - // PA4: CS (Output Push-Pull, 50MHz) - const sck_pin = gpio.Pin.init(0, 5); // PA5 - const miso_pin = gpio.Pin.init(0, 6); // PA6 - const mosi_pin = gpio.Pin.init(0, 7); // PA7 - - sck_pin.configure_alternate_function(.push_pull, .max_50MHz); - miso_pin.enable_clock(); - miso_pin.set_input_mode(.floating); - mosi_pin.configure_alternate_function(.push_pull, .max_50MHz); - - // Configure CS pin (manual control) + // Configure CS pin (manual control, device-specific) cs_pin.enable_clock(); cs_pin.set_output_mode(.general_purpose_push_pull, .max_50MHz); cs_pin.put(1); // Deselect (CS is active low) - // Initialize SPI1 - const spi1 = spi.instance.SPI1; - - // Test with DMA enabled + // Initialize SPI with DMA support std.log.info("Initializing SPI with DMA support...", .{}); - spi1.apply(.{ + spi_hw.apply(.{ .baud_rate = 4_000_000, // 4 MHz .polarity = .idle_low, .phase = .first_edge, @@ -124,7 +101,7 @@ pub fn main() !void { // Test 1: Read JEDEC ID std.log.info("Test 1: Reading JEDEC ID...", .{}); - const jedec_id = try read_jedec_id(spi1); + const jedec_id = try read_jedec_id(spi_hw.instance); std.log.info(" JEDEC ID: 0x{X:0>6}", .{jedec_id}); if (jedec_id == JEDEC_ID_EXPECTED) { @@ -136,7 +113,7 @@ pub fn main() !void { // Test 2: Read Status Register (small transfer - should use polling) std.log.info("Test 2: Reading Status Register (polling)...", .{}); - const status = try read_status_reg(spi1); + const status = try read_status_reg(spi_hw.instance); std.log.info(" Status: 0x{X:0>2}", .{status}); std.log.info(" BUSY: {}", .{status & W25Q_STATUS.BUSY != 0}); std.log.info(" WEL: {}", .{status & W25Q_STATUS.WEL != 0}); @@ -155,16 +132,16 @@ pub fn main() !void { } std.log.info(" Erasing sector at 0x{X:0>6}...", .{test_address}); - try erase_sector(spi1, test_address); + try erase_sector(spi_hw.instance, test_address); std.log.info(" Sector erased", .{}); std.log.info(" Writing {} bytes...", .{test_data_len}); - try write_page(spi1, test_address, write_buffer[0..test_data_len]); + try write_page(spi_hw.instance, test_address, write_buffer[0..test_data_len]); std.log.info(" Page written", .{}); std.log.info(" Reading {} bytes (DMA)...", .{test_data_len}); var read_buffer: [PAGE_SIZE]u8 = undefined; - try read_data(spi1, test_address, read_buffer[0..test_data_len]); + try read_data(spi_hw.instance, test_address, read_buffer[0..test_data_len]); std.log.info(" Page read", .{}); // Verify diff --git a/examples/wch/ch32v/src/spi_loopback.zig b/examples/wch/ch32v/src/spi_loopback.zig index 1b79afe14..cfb9c02b1 100644 --- a/examples/wch/ch32v/src/spi_loopback.zig +++ b/examples/wch/ch32v/src/spi_loopback.zig @@ -17,11 +17,11 @@ const std = @import("std"); const microzig = @import("microzig"); const mdf = microzig.drivers; const hal = microzig.hal; -const gpio = hal.gpio; +const board = microzig.board; const spi = hal.spi; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; +const spi_hw = board.spi_setup; pub const panic = microzig.panic; @@ -36,18 +36,11 @@ comptime { pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for logging - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); - - // Initialize USART2 at 115200 baud - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); std.log.info("SPI Loopback Test", .{}); std.log.info("==================", .{}); @@ -56,19 +49,6 @@ pub fn main() !void { std.log.info(" Connect PA7 (MOSI) to PA6 (MISO) with jumper wire", .{}); std.log.info("", .{}); - // Configure SPI1 pins - // PA5: SCK (Alternate Function Push-Pull, 50MHz) - // PA6: MISO (Input Floating) - // PA7: MOSI (Alternate Function Push-Pull, 50MHz) - const sck_pin = gpio.Pin.init(0, 5); // PA5 - const miso_pin = gpio.Pin.init(0, 6); // PA6 - const mosi_pin = gpio.Pin.init(0, 7); // PA7 - - sck_pin.configure_alternate_function(.push_pull, .max_50MHz); - miso_pin.enable_clock(); - miso_pin.set_input_mode(.floating); - mosi_pin.configure_alternate_function(.push_pull, .max_50MHz); - // Test patterns - mix of short (polling) and long (DMA) transfers const test_patterns = [_][]const u8{ &.{0x00}, @@ -91,7 +71,6 @@ pub fn main() !void { }, }; - const spi1 = spi.instance.SPI1; var rx_buffer: [64]u8 = undefined; // Increased to handle 32-byte pattern // SPI modes to test (all 4 combinations of CPOL and CPHA) @@ -111,7 +90,7 @@ pub fn main() !void { // Test each SPI mode (comptime inline loop for compile-time config) inline for (spi_modes) |mode| { std.log.info("Testing {s} at 1 MHz with DMA...", .{mode.name}); - spi1.apply(.{ + spi_hw.apply(.{ .baud_rate = 1_000_000, .polarity = mode.polarity, .phase = mode.phase, @@ -128,7 +107,7 @@ pub fn main() !void { @memset(&rx_buffer, 0); // Perform loopback - spi1.transceive_blocking(pattern, rx_buffer[0..pattern.len], mdf.time.Duration.from_ms(100)) catch |err| { + spi_hw.instance.transceive_blocking(pattern, rx_buffer[0..pattern.len], mdf.time.Duration.from_ms(100)) catch |err| { std.log.err(" Pattern {} failed: {}", .{ i, err }); continue; }; @@ -152,7 +131,7 @@ pub fn main() !void { std.log.info("", .{}); std.log.info("Testing vectored I/O with DMA (writev)...", .{}); - spi1.apply(.{ + spi_hw.apply(.{ .baud_rate = 1_000_000, .polarity = .idle_low, .phase = .first_edge, @@ -165,7 +144,7 @@ pub fn main() !void { const chunks = [_][]const u8{ &.{ 1, 2 }, &.{ 3, 4 }, &.{ 5, 6 } }; - spi1.writev_blocking(&chunks, mdf.time.Duration.from_ms(100)) catch |err| + spi_hw.instance.writev_blocking(&chunks, mdf.time.Duration.from_ms(100)) catch |err| std.log.err(" writev failed: {}", .{err}); std.log.info(" writev: PASS (if no errors above)", .{}); diff --git a/port/wch/ch32v/src/boards/LANA_TNY.zig b/port/wch/ch32v/src/boards/LANA_TNY.zig index 96333ee6a..3320758f6 100644 --- a/port/wch/ch32v/src/boards/LANA_TNY.zig +++ b/port/wch/ch32v/src/boards/LANA_TNY.zig @@ -27,6 +27,21 @@ pub const uart_setup: ch32v.usart.Setup = .{ .rx_pin = ch32v.gpio.Pin.init(0, 3), // PA3 }; +/// Default I2C: I2C1 on PB6 (SCL) / PB7 (SDA) (Qwiic connector) +pub const i2c_setup: ch32v.i2c.Setup = .{ + .instance = ch32v.i2c.instance.I2C1, + .scl_pin = ch32v.gpio.Pin.init(1, 6), // PB6 + .sda_pin = ch32v.gpio.Pin.init(1, 7), // PB7 +}; + +/// Default SPI: SPI1 on PA5 (SCK) / PA6 (MISO) / PA7 (MOSI) +pub const spi_setup: ch32v.spi.Setup = .{ + .instance = ch32v.spi.instance.SPI1, + .sck_pin = ch32v.gpio.Pin.init(0, 5), // PA5 + .mosi_pin = ch32v.gpio.Pin.init(0, 7), // PA7 + .miso_pin = ch32v.gpio.Pin.init(0, 6), // PA6 +}; + pub const pin_config = ch32v.pins.GlobalConfiguration{ .GPIOD = .{ .PIN0 = .{ diff --git a/port/wch/ch32v/src/boards/nanoCH32V203.zig b/port/wch/ch32v/src/boards/nanoCH32V203.zig index 5a1109406..d984c7c31 100644 --- a/port/wch/ch32v/src/boards/nanoCH32V203.zig +++ b/port/wch/ch32v/src/boards/nanoCH32V203.zig @@ -31,6 +31,21 @@ pub const uart_setup: ch32v.usart.Setup = .{ .rx_pin = ch32v.gpio.Pin.init(0, 10), // PA10 }; +/// Default I2C: I2C1 on PB6 (SCL) / PB7 (SDA) +pub const i2c_setup: ch32v.i2c.Setup = .{ + .instance = ch32v.i2c.instance.I2C1, + .scl_pin = ch32v.gpio.Pin.init(1, 6), // PB6 + .sda_pin = ch32v.gpio.Pin.init(1, 7), // PB7 +}; + +/// Default SPI: SPI1 on PA5 (SCK) / PA6 (MISO) / PA7 (MOSI) +pub const spi_setup: ch32v.spi.Setup = .{ + .instance = ch32v.spi.instance.SPI1, + .sck_pin = ch32v.gpio.Pin.init(0, 5), // PA5 + .mosi_pin = ch32v.gpio.Pin.init(0, 7), // PA7 + .miso_pin = ch32v.gpio.Pin.init(0, 6), // PA6 +}; + pub const pin_config = ch32v.pins.GlobalConfiguration{ .GPIOA = .{ .PIN15 = .{ diff --git a/port/wch/ch32v/src/hals/i2c.zig b/port/wch/ch32v/src/hals/i2c.zig index e4422caca..94164743d 100644 --- a/port/wch/ch32v/src/hals/i2c.zig +++ b/port/wch/ch32v/src/hals/i2c.zig @@ -8,6 +8,7 @@ const microzig = @import("microzig"); const mdf = microzig.drivers; const drivers = mdf.base; const hal = microzig.hal; +const gpio = hal.gpio; const dma = hal.dma; const I2C1 = microzig.chip.peripherals.I2C1; @@ -56,7 +57,6 @@ pub const Config = struct { repeated_start: bool = true, baud_rate: u32 = 100_000, duty_cycle: DutyCycle = .duty_2, - remap: Remap = .default, /// Optional DMA configuration - null means polling-only mode /// Example: .dma = .{ .tx_channel = .Ch6, .rx_channel = .Ch7 } @@ -73,6 +73,20 @@ pub const ConfigError = error{ InputFreqTooLow, }; +pub const Setup = struct { + instance: I2C, + scl_pin: gpio.Pin, + sda_pin: gpio.Pin, + remap: Remap = .default, + + /// Apply settings: configure I2C pins and peripheral. + pub fn apply(comptime self: Setup, comptime config: Config) void { + self.scl_pin.configure_alternate_function(.open_drain, .max_50MHz); + self.sda_pin.configure_alternate_function(.open_drain, .max_50MHz); + self.instance.apply(config, self.remap); + } +}; + pub const instance = struct { pub const I2C1: I2C = @fromBackingInt(0); pub const I2C2: I2C = @fromBackingInt(1); @@ -100,7 +114,7 @@ pub const I2C = enum(u1) { } /// Initializes the I2C HW block per the Config provided - pub fn apply(comptime i2c: I2C, comptime config: Config) void { + pub fn apply(comptime i2c: I2C, comptime config: Config, comptime remap: Remap) void { const regs = i2c.get_regs(); // Compile-time DMA validation @@ -129,7 +143,7 @@ pub const I2C = enum(u1) { hal.clocks.enable_afio_clock(); const AFIO = microzig.chip.peripherals.AFIO; switch (@backingInt(i2c)) { - 0 => AFIO.PCFR1.modify(.{ .I2C1_RM = @backingInt(config.remap) }), + 0 => AFIO.PCFR1.modify(.{ .I2C1_RM = @backingInt(remap) }), // I2C2 does not have remap support on CH32V20x 1 => {}, } diff --git a/port/wch/ch32v/src/hals/spi.zig b/port/wch/ch32v/src/hals/spi.zig index 8b4799966..df372edc6 100644 --- a/port/wch/ch32v/src/hals/spi.zig +++ b/port/wch/ch32v/src/hals/spi.zig @@ -32,6 +32,7 @@ const microzig = @import("microzig"); const mdf = microzig.drivers; const hal = microzig.hal; +const gpio = hal.gpio; const dma = hal.dma; const SPI1 = microzig.chip.peripherals.SPI1; @@ -104,7 +105,6 @@ pub const Config = struct { phase: Phase = .first_edge, bit_order: BitOrder = .msb_first, data_size: DataSize = .eight_bit, - remap: Remap = .default, /// Optional DMA configuration - null means polling-only mode /// Example: .dma = .{ .tx_channel = .Ch3, .rx_channel = .Ch2 } @@ -115,6 +115,26 @@ pub const Config = struct { // See SPI_DatagramDevice in drivers.zig for automatic CS management. }; +pub const Setup = struct { + instance: SPI, + sck_pin: gpio.Pin, + mosi_pin: ?gpio.Pin = null, + miso_pin: ?gpio.Pin = null, + remap: Remap = .default, + + /// Apply settings: configure whichever pins are present, then apply + /// the SPI peripheral config (clock, baud rate, etc.). + pub fn apply(comptime self: Setup, comptime config: Config) void { + self.sck_pin.configure_alternate_function(.push_pull, .max_50MHz); + if (self.mosi_pin) |mosi| mosi.configure_alternate_function(.push_pull, .max_50MHz); + if (self.miso_pin) |miso| { + miso.enable_clock(); + miso.set_input_mode(.floating); + } + self.instance.apply(config, self.remap); + } +}; + pub const instance = struct { pub const SPI1: SPI = @fromBackingInt(0); pub const SPI2: SPI = @fromBackingInt(1); @@ -165,7 +185,7 @@ pub const SPI = enum(u1) { } /// Initializes the SPI HW block per the Config provided - pub fn apply(comptime spi: SPI, comptime config: Config) void { + pub fn apply(comptime spi: SPI, comptime config: Config, comptime remap: Remap) void { const regs = spi.get_regs(); // Compile-time DMA validation @@ -194,7 +214,7 @@ pub const SPI = enum(u1) { hal.clocks.enable_afio_clock(); const AFIO = microzig.chip.peripherals.AFIO; switch (@backingInt(spi)) { - 0 => AFIO.PCFR1.modify(.{ .SPI1_RM = @backingInt(config.remap) }), + 0 => AFIO.PCFR1.modify(.{ .SPI1_RM = @backingInt(remap) }), // SPI2 does not have remap support on CH32V20x 1 => {}, } From b7acb74e1ecfb3a3769cb41f9cd34f4ecbcd0624 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Wed, 9 Sep 2026 17:38:24 -0400 Subject: [PATCH 2/2] Clarify remap --- examples/wch/ch32v/src/dma.zig | 20 ++++++-------------- examples/wch/ch32v/src/usb_cdc.zig | 2 +- port/wch/ch32v/src/boards/LANA_TNY.zig | 3 +++ port/wch/ch32v/src/boards/nanoCH32V203.zig | 3 +++ port/wch/ch32v/src/hals/i2c.zig | 2 ++ port/wch/ch32v/src/hals/spi.zig | 2 ++ port/wch/ch32v/src/hals/usart.zig | 10 ++++++---- 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/examples/wch/ch32v/src/dma.zig b/examples/wch/ch32v/src/dma.zig index 60accd571..18e908dd3 100644 --- a/examples/wch/ch32v/src/dma.zig +++ b/examples/wch/ch32v/src/dma.zig @@ -1,11 +1,10 @@ const std = @import("std"); const microzig = @import("microzig"); const hal = microzig.hal; -const gpio = hal.gpio; +const board = microzig.board; const dma = hal.dma; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +const uart = board.uart_setup; pub const panic = microzig.panic; @@ -40,18 +39,11 @@ inline fn read_stk_cnt() u64 { pub fn main() !void { // Board brings up clocks and time - microzig.board.init(); + board.init(); - // Configure USART2 TX pin (PA2) for alternate function (disable GPIO) - usart_tx_pin.configure_alternate_function(.push_pull, .max_50MHz); - - // Initialize USART2 at 115200 baud (uses default pins PA2/PA3) - usart.apply(.{ - .baud_rate = 115200, - .remap = .default, - }); - - hal.usart.init_logger(usart); + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); const chan = dma.Channel.Ch7; diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 192c31aa5..7fe9ddc59 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -64,7 +64,7 @@ pub fn main() !void { usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); // Initialize USART1 at 115200 baud - usart.apply(.{ .baud_rate = 115200 }); + usart.apply(.{ .baud_rate = 115200 }, .default); hal.usart.init_logger(usart); std.log.info("UART logging initialized.", .{}); diff --git a/port/wch/ch32v/src/boards/LANA_TNY.zig b/port/wch/ch32v/src/boards/LANA_TNY.zig index 3320758f6..de9032749 100644 --- a/port/wch/ch32v/src/boards/LANA_TNY.zig +++ b/port/wch/ch32v/src/boards/LANA_TNY.zig @@ -25,6 +25,7 @@ pub const uart_setup: ch32v.usart.Setup = .{ .instance = .USART2, .tx_pin = ch32v.gpio.Pin.init(0, 2), // PA2 .rx_pin = ch32v.gpio.Pin.init(0, 3), // PA3 + .remap = .default, // PA2/PA3 — change if using remapped pins }; /// Default I2C: I2C1 on PB6 (SCL) / PB7 (SDA) (Qwiic connector) @@ -32,6 +33,7 @@ pub const i2c_setup: ch32v.i2c.Setup = .{ .instance = ch32v.i2c.instance.I2C1, .scl_pin = ch32v.gpio.Pin.init(1, 6), // PB6 .sda_pin = ch32v.gpio.Pin.init(1, 7), // PB7 + .remap = .default, // PB6/PB7 — change to .remap1 for PB8/PB9 }; /// Default SPI: SPI1 on PA5 (SCK) / PA6 (MISO) / PA7 (MOSI) @@ -40,6 +42,7 @@ pub const spi_setup: ch32v.spi.Setup = .{ .sck_pin = ch32v.gpio.Pin.init(0, 5), // PA5 .mosi_pin = ch32v.gpio.Pin.init(0, 7), // PA7 .miso_pin = ch32v.gpio.Pin.init(0, 6), // PA6 + .remap = .default, // PA5/PA6/PA7 — change to .remap1 for PB3/PB4/PB5 }; pub const pin_config = ch32v.pins.GlobalConfiguration{ diff --git a/port/wch/ch32v/src/boards/nanoCH32V203.zig b/port/wch/ch32v/src/boards/nanoCH32V203.zig index d984c7c31..4bb9730e8 100644 --- a/port/wch/ch32v/src/boards/nanoCH32V203.zig +++ b/port/wch/ch32v/src/boards/nanoCH32V203.zig @@ -29,6 +29,7 @@ pub const uart_setup: ch32v.usart.Setup = .{ .instance = .USART1, .tx_pin = ch32v.gpio.Pin.init(0, 9), // PA9 .rx_pin = ch32v.gpio.Pin.init(0, 10), // PA10 + .remap = .default, // PA9/PA10 — change if using remapped pins }; /// Default I2C: I2C1 on PB6 (SCL) / PB7 (SDA) @@ -36,6 +37,7 @@ pub const i2c_setup: ch32v.i2c.Setup = .{ .instance = ch32v.i2c.instance.I2C1, .scl_pin = ch32v.gpio.Pin.init(1, 6), // PB6 .sda_pin = ch32v.gpio.Pin.init(1, 7), // PB7 + .remap = .default, // PB6/PB7 — change to .remap1 for PB8/PB9 }; /// Default SPI: SPI1 on PA5 (SCK) / PA6 (MISO) / PA7 (MOSI) @@ -44,6 +46,7 @@ pub const spi_setup: ch32v.spi.Setup = .{ .sck_pin = ch32v.gpio.Pin.init(0, 5), // PA5 .mosi_pin = ch32v.gpio.Pin.init(0, 7), // PA7 .miso_pin = ch32v.gpio.Pin.init(0, 6), // PA6 + .remap = .default, // PA5/PA6/PA7 — change to .remap1 for PB3/PB4/PB5 }; pub const pin_config = ch32v.pins.GlobalConfiguration{ diff --git a/port/wch/ch32v/src/hals/i2c.zig b/port/wch/ch32v/src/hals/i2c.zig index 94164743d..f374d8cf8 100644 --- a/port/wch/ch32v/src/hals/i2c.zig +++ b/port/wch/ch32v/src/hals/i2c.zig @@ -77,6 +77,8 @@ pub const Setup = struct { instance: I2C, scl_pin: gpio.Pin, sda_pin: gpio.Pin, + /// AFIO pin remap — must match the pins chosen above. + /// See the Remap enum doc comment for which pins each setting maps to. remap: Remap = .default, /// Apply settings: configure I2C pins and peripheral. diff --git a/port/wch/ch32v/src/hals/spi.zig b/port/wch/ch32v/src/hals/spi.zig index df372edc6..7a107e5f2 100644 --- a/port/wch/ch32v/src/hals/spi.zig +++ b/port/wch/ch32v/src/hals/spi.zig @@ -120,6 +120,8 @@ pub const Setup = struct { sck_pin: gpio.Pin, mosi_pin: ?gpio.Pin = null, miso_pin: ?gpio.Pin = null, + /// AFIO pin remap — must match the pins chosen above. + /// See the Remap enum doc comment for which pins each setting maps to. remap: Remap = .default, /// Apply settings: configure whichever pins are present, then apply diff --git a/port/wch/ch32v/src/hals/usart.zig b/port/wch/ch32v/src/hals/usart.zig index c3118cee9..cba8332d2 100644 --- a/port/wch/ch32v/src/hals/usart.zig +++ b/port/wch/ch32v/src/hals/usart.zig @@ -75,7 +75,6 @@ pub const Config = struct { stop_bits: StopBits = .one, parity: Parity = .none, flow_control: FlowControl = .none, - remap: Remap = .default, }; pub const TransmitError = error{ @@ -99,6 +98,9 @@ pub const Setup = struct { instance: USART, tx_pin: ?gpio.Pin = null, rx_pin: ?gpio.Pin = null, + /// AFIO pin remap — must match the pins chosen above. + /// See the Remap enum doc comment for which pins each setting maps to. + remap: Remap = .default, /// Apply settings: configure whichever pins are present, then apply /// the USART peripheral config (clock, baud rate, etc.). @@ -108,7 +110,7 @@ pub const Setup = struct { rx.enable_clock(); rx.set_input_mode(.floating); } - self.instance.apply(config); + self.instance.apply(config, self.remap); } }; @@ -230,7 +232,7 @@ pub const USART = enum(u2) { } /// Apply configuration to the USART peripheral - pub fn apply(comptime usart: USART, comptime config: Config) void { + pub fn apply(comptime usart: USART, comptime config: Config, comptime remap: Remap) void { const regs = usart.get_regs(); // Enable peripheral clock @@ -245,7 +247,7 @@ pub const USART = enum(u2) { // Configure AFIO remap hal.clocks.enable_afio_clock(); const AFIO = microzig.chip.peripherals.AFIO; - const remap_bits = @backingInt(config.remap); + const remap_bits = @backingInt(remap); switch (usart) { .USART1 => {