diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 41a4e6d44..231e6aaa1 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -16,6 +16,7 @@ pub fn build(b: *std.Build) void { .{ .name = "blinky", .file = "src/blinky.zig" }, .{ .name = "custom_clock_config", .file = "src/custom_clock_config.zig" }, .{ .name = "gpio_input", .file = "src/gpio_input.zig" }, + .{ .name = "i2c_accel", .file = "src/i2c_accel.zig" }, .{ .name = "i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, .{ .name = "i2c_temp", .file = "src/i2c_temp.zig" }, .{ .name = "i2c_display_sh1106", .file = "src/i2c_display_sh1106.zig" }, diff --git a/examples/espressif/esp/src/i2c_accel.zig b/examples/espressif/esp/src/i2c_accel.zig new file mode 100644 index 000000000..093d7b9c8 --- /dev/null +++ b/examples/espressif/esp/src/i2c_accel.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const ICM_20948 = microzig.drivers.sensor.ICM_20948(.{}); + +const esp = microzig.hal; +const i2c = esp.i2c; +const gpio = esp.gpio; +const I2C_Device = esp.drivers.I2C_Device; +const sleep_ms = esp.time.sleep_ms; + +var i2c0 = i2c.instance.num(0); + +const usb_serial_jtag = esp.usb_serial_jtag; + +pub const std_options = microzig.std_options(.{ + .log_level = .info, + .logFn = usb_serial_jtag.logger.log, +}); + +comptime { + _ = microzig.export_startup(); +} + +pub fn main() !void { + const sda_pin = gpio.num(5); + const scl_pin = gpio.num(6); + + // Setup SDA pin + sda_pin.apply(.{ + .output_enable = true, + .input_enable = true, + .open_drain = true, + .pull = .up, + }); + + // Setup SCL pin + scl_pin.apply(.{ + .output_enable = true, + .input_enable = true, + .open_drain = true, + .pull = .up, + }); + + i2c0.connect_pins(.{ .sda = sda_pin, .scl = scl_pin }); + try i2c0.apply(100_000); + + // Create i2c and clock devices + var i2c_device = I2C_Device.init(i2c0, null); + // Pass devices to driver to create sensor instance + var dev = try ICM_20948.init( + i2c_device.i2c_device(), + @fromBackingInt(@intCast(0x69)), + esp.drivers.clock_device(), + .{ + .accel_dlp = .@"6Hz", + .gyro_dlp = .@"6Hz", + .accel_odr_div = 21, // About 50Hz + .gyro_odr_div = 21, // About 50Hz + }, + ); + + try dev.setup(); + + while (true) { + const data = try dev.get_accel_gyro_mag_data(); + std.log.info( + "accel: x {d: >6.2} y {d: >6.2} z {d: >6.2} (m/s²) " ++ + "gyro: x {d: >6.2} y {d: >6.2} z {d: >6.2} (rads) " ++ + "temp: {d: >5.2}°C " ++ + "mag: x {d: >6.2} y {d: >6.2} z {d: >6.2} (µT)", + .{ data.accel.x, data.accel.y, data.accel.z, data.gyro.x, data.gyro.y, data.gyro.z, data.temp, data.mag.x, data.mag.y, data.mag.z }, + ); + + sleep_ms(500); + } +} diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 995f2de11..9c77bd56f 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -47,7 +47,9 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_echo", .file = "src/uart_echo.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_accel", .file = "src/i2c_accel.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_eeprom", .file = "src/i2c_eeprom.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_eeprom_dma", .file = "src/i2c_eeprom_dma.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_loopback", .file = "src/spi_loopback.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_flash_w25q", .file = "src/spi_flash_w25q.zig" }, diff --git a/examples/wch/ch32v/src/i2c_accel.zig b/examples/wch/ch32v/src/i2c_accel.zig new file mode 100644 index 000000000..b0b0d6c6a --- /dev/null +++ b/examples/wch/ch32v/src/i2c_accel.zig @@ -0,0 +1,77 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const board = microzig.board; + +const i2c = hal.i2c; + +const ICM_20948 = microzig.drivers.sensor.ICM_20948(.{ .log_level = null }); + +const uart = board.uart_setup; +const i2c_hw = board.i2c_setup; + +pub const std_options = microzig.std_options(.{ + .log_level = .info, + .logFn = hal.usart.log, +}); + +// Configure I2C1 with DMA support +const i2c_config = i2c.Config{ + .baud_rate = 100_000, // 100 kHz + .dma = .{ + .tx_channel = .Ch6, // I2C1 TX must use Ch6 + .rx_channel = .Ch7, // I2C1 RX must use Ch7 + .priority = .High, + // Lower DMA threshold for testing (accelerometer makes 6-byte reads) + .threshold = 4, + }, +}; + +comptime { + _ = microzig.export_startup(); +} + +pub fn main() !void { + // Board brings up clocks and time + board.init(); + + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); + + // Initialize I2C + 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 and clock devices + var i2c_device = I2C_DeviceType.init(i2c_hw.instance, null); + // Pass devices to driver to create sensor instance + var dev = try ICM_20948.init( + i2c_device.i2c_device(), + @fromBackingInt(@intCast(0x69)), + hal.drivers.clock_device(), + .{ + .accel_dlp = .@"6Hz", + .gyro_dlp = .@"6Hz", + .accel_odr_div = 21, // About 50Hz + .gyro_odr_div = 21, // About 50Hz + }, + ); + + try dev.setup(); + + while (true) { + const data = try dev.get_accel_gyro_mag_data(); + std.log.info( + "accel: x {d: >6.2} y {d: >6.2} z {d: >6.2} (m/s²) " ++ + "gyro: x {d: >6.2} y {d: >6.2} z {d: >6.2} (rads) " ++ + "temp: {d: >5.2}°C " ++ + "mag: x {d: >6.2} y {d: >6.2} z {d: >6.2} (µT)", + .{ data.accel.x, data.accel.y, data.accel.z, data.gyro.x, data.gyro.y, data.gyro.z, data.temp, data.mag.x, data.mag.y, data.mag.z }, + ); + + hal.time.sleep_ms(500); + } +} diff --git a/examples/wch/ch32v/src/i2c_eeprom_dma.zig b/examples/wch/ch32v/src/i2c_eeprom_dma.zig new file mode 100644 index 000000000..0c8914e2d --- /dev/null +++ b/examples/wch/ch32v/src/i2c_eeprom_dma.zig @@ -0,0 +1,73 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const board = microzig.board; +const i2c = hal.i2c; + +const uart = board.uart_setup; +const i2c_hw = board.i2c_setup; + +pub const panic = microzig.panic; + +pub const std_options = microzig.std_options(.{ + .log_level = .debug, + .logFn = hal.usart.log, +}); + +// Configure I2C1 with DMA support +const i2c_config = i2c.Config{ + .baud_rate = 400_000, // 400 kHz fast mode + .dma = .{ + .tx_channel = .Ch6, // I2C1 TX must use Ch6 + .rx_channel = .Ch7, // I2C1 RX must use Ch7 + .priority = .High, + }, +}; + +comptime { + _ = microzig.export_startup(); +} + +pub fn main() !void { + // Board brings up clocks and time + board.init(); + + // Initialize UART for logging + uart.apply(.{ .baud_rate = 115200 }); + hal.usart.init_logger(uart.instance); + + // Initialize I2C + i2c_hw.apply(i2c_config); + + const eeprom_address: i2c.Address = @fromBackingInt(0x50); + + // Example: Write large buffer using DMA + std.log.info("Writing large buffer manually", .{}); + var tx_buffer: [64]u8 = undefined; + for (&tx_buffer, 0..) |*byte, i| { + byte.* = @intCast(i); + } + + // Explicit DMA write + std.log.info("Writing to EEPROM via DMA", .{}); + try i2c_hw.instance.write_dma(i2c_config, eeprom_address, &tx_buffer, null); + + hal.time.sleep_ms(10); + + // Automatic selection (uses DMA for large transfers) + std.log.info("Writing to EEPROM via 'auto'", .{}); + try i2c_hw.instance.write_auto(i2c_config, eeprom_address, &tx_buffer, null); + + // Small transfer (automatic polling) + const small_buffer = [_]u8{ 0x12, 0x34 }; + try i2c_hw.instance.write_auto(i2c_config, eeprom_address, &small_buffer, null); + + // Read using DMA + std.log.info("Reading from EEPROM via DMA", .{}); + var rx_buffer: [64]u8 = undefined; + try i2c_hw.instance.read_dma(i2c_config, eeprom_address, &rx_buffer, null); + + while (true) { + hal.time.sleep_ms(1000); + } +} diff --git a/port/wch/ch32v/src/hals/drivers.zig b/port/wch/ch32v/src/hals/drivers.zig index cad346950..db2e27943 100644 --- a/port/wch/ch32v/src/hals/drivers.zig +++ b/port/wch/ch32v/src/hals/drivers.zig @@ -358,9 +358,9 @@ pub const ClockDevice = struct { /// /// Implementation of a `ClockDevice` that uses the HAL's `time` module. /// -pub fn clock_device() ClockDevice { +pub fn clock_device() drivers.ClockDevice { const S = struct { - const vtable: ClockDevice.VTable = .{ + const vtable: drivers.ClockDevice.VTable = .{ .get_time_since_boot = get_time_since_boot_fn, }; diff --git a/port/wch/ch32v/src/hals/i2c.zig b/port/wch/ch32v/src/hals/i2c.zig index f374d8cf8..b9174b9c3 100644 --- a/port/wch/ch32v/src/hals/i2c.zig +++ b/port/wch/ch32v/src/hals/i2c.zig @@ -523,7 +523,6 @@ pub const I2C = enum(u1) { try i2c.wait_flag_star2("BUSY", 0, deadline); } - /// Read using DMA (only available if DMA configured) pub fn read_dma( i2c: I2C, comptime config: Config, @@ -593,6 +592,10 @@ pub const I2C = enum(u1) { /// /// Automatically selects between DMA and polling based on transfer size. DMA is used for /// transfers over configured threshold, polling for smaller transfers. + /// + /// NOTE: The 16-byte default threshold is a heuristic based on typical DMA setup overhead vs + /// polling cost. It has not been benchmarked specifically for CH32V and may not be optimal for + /// all use cases. For explicit control, use write_dma() or write_blocking() directly. pub fn write_auto( i2c: I2C, comptime config: Config, @@ -612,6 +615,10 @@ pub const I2C = enum(u1) { /// /// Automatically selects between DMA and polling based on transfer size. DMA is used for /// transfers over configured threshold, polling for smaller transfers. + /// + /// NOTE: The 16-byte default threshold is a heuristic based on typical DMA setup overhead vs + /// polling cost. It has not been benchmarked specifically for CH32V and may not be optimal for + /// all use cases. For explicit control, use read_dma() or read_blocking() directly. pub fn read_auto( i2c: I2C, comptime config: Config,