diff --git a/build.zig b/build.zig index 5da87f7..b1225d2 100644 --- a/build.zig +++ b/build.zig @@ -53,6 +53,7 @@ pub const Examples = struct { blinky_core1: *microzig.EmbeddedExecutable, gpio_clk: *microzig.EmbeddedExecutable, pwm: *microzig.EmbeddedExecutable, + spi_master: *microzig.EmbeddedExecutable, uart: *microzig.EmbeddedExecutable, //uart_pins: microzig.EmbeddedExecutable, diff --git a/examples/spi_master.zig b/examples/spi_master.zig new file mode 100644 index 0000000..ba1c0c2 --- /dev/null +++ b/examples/spi_master.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const rp2040 = microzig.hal; +const time = rp2040.time; +const gpio = rp2040.gpio; +const clocks = rp2040.clocks; +const peripherals = microzig.chip.peripherals; + +const BUF_LEN = 0x100; + +// Communicate with another RP2040 over spi +// Slave implementation: https://github.com/raspberrypi/pico-examples/blob/master/spi/spi_master_slave/spi_slave/spi_slave.c +pub fn main() !void { + const spi = rp2040.spi.SPI.init(0, .{ + .clock_config = rp2040.clock_config, + }); + var out_buf: [BUF_LEN]u8 = .{ 0xAA, 0xBB, 0xCC, 0xDD } ** (BUF_LEN / 4); + var in_buf: [BUF_LEN]u8 = undefined; + + while (true) { + _ = spi.transceive(&out_buf, &in_buf); + time.sleep_ms(1 * 1000); + } +} diff --git a/src/hal.zig b/src/hal.zig index 21323de..568c691 100644 --- a/src/hal.zig +++ b/src/hal.zig @@ -9,6 +9,7 @@ pub const multicore = @import("hal/multicore.zig"); pub const time = @import("hal/time.zig"); pub const uart = @import("hal/uart.zig"); pub const pwm = @import("hal/pwm.zig"); +pub const spi = @import("hal/spi.zig"); pub const resets = @import("hal/resets.zig"); pub const irq = @import("hal/irq.zig"); diff --git a/src/hal/spi.zig b/src/hal/spi.zig new file mode 100644 index 0000000..ea96f40 --- /dev/null +++ b/src/hal/spi.zig @@ -0,0 +1,159 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const SPI0 = peripherals.SPI0; +const SPI1 = peripherals.SPI1; + +const gpio = @import("gpio.zig"); +const clocks = @import("clocks.zig"); +const resets = @import("resets.zig"); +const time = @import("time.zig"); + +const SpiRegs = microzig.chip.types.peripherals.SPI0; + +pub const Config = struct { + clock_config: clocks.GlobalConfiguration, + tx_pin: ?u32 = 19, + rx_pin: ?u32 = 16, + sck_pin: ?u32 = 18, + csn_pin: ?u32 = 17, + baud_rate: u32 = 1000 * 1000, +}; + +pub const SPI = enum { + spi0, + spi1, + + fn get_regs(spi: SPI) *volatile SpiRegs { + return switch (spi) { + .spi0 => SPI0, + .spi1 => SPI1, + }; + } + + pub fn reset(spi: SPI) void { + switch (spi) { + .spi0 => resets.reset(&.{.spi0}), + .spi1 => resets.reset(&.{.spi1}), + } + } + + pub fn init(comptime id: u32, comptime config: Config) SPI { + const spi: SPI = switch (id) { + 0 => .spi0, + 1 => .spi1, + else => @compileError("there is only spi0 and spi1"), + }; + + spi.reset(); + + const peri_freq = config.clock_config.peri.?.output_freq; + _ = spi.set_baudrate(config.baud_rate, peri_freq); + + const spi_regs = spi.get_regs(); + + // set fromat + spi_regs.SSPCR0.modify(.{ + .DSS = 0b0111, // 8 bits + .SPO = 0, + .SPH = 0, + }); + + // Always enable DREQ signals -- harmless if DMA is not listening + spi_regs.SSPDMACR.modify(.{ + .TXDMAE = 1, + .RXDMAE = 1, + }); + + // Finally enable the SPI + spi_regs.SSPCR1.modify(.{ + .SSE = 1, + }); + + if (config.tx_pin) |pin| gpio.set_function(pin, .spi); + if (config.rx_pin) |pin| gpio.set_function(pin, .spi); + if (config.sck_pin) |pin| gpio.set_function(pin, .spi); + if (config.csn_pin) |pin| gpio.set_function(pin, .spi); + + return spi; + } + + pub fn is_writable(spi: SPI) bool { + return spi.get_regs().SSPSR.read().TNF == 1; + } + + pub fn is_readable(spi: SPI) bool { + return spi.get_regs().SSPSR.read().RNE == 1; + } + pub fn transceive(spi: SPI, src: []const u8, dst: []u8) usize { + const spi_regs = spi.get_regs(); + std.debug.assert(src.len == dst.len); + const fifo_depth = 8; + var rx_remaining = dst.len; + var tx_remaining = src.len; + + while (rx_remaining > 0 or tx_remaining > 0) { + if (tx_remaining > 0 and spi.is_writable() and rx_remaining < tx_remaining + fifo_depth) { + spi_regs.SSPDR.write_raw(src[src.len - tx_remaining]); + tx_remaining -= 1; + } + if (rx_remaining > 0 and spi.is_readable()) { + const bytes = std.mem.asBytes(&spi_regs.SSPDR.read().DATA); + dst[dst.len - rx_remaining] = bytes[0]; + rx_remaining -= 1; + } + } + + return src.len; + } + // Write len bytes directly from src to the SPI, and discard any data received back + pub fn write(spi: SPI, src: []const u8) usize { + const spi_regs = spi.get_regs(); + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + for (src) |s| { + while (!spi.is_writable()) { + std.log.debug("SPI not writable!", .{}); + } + spi_regs.SSPDR.write_raw(s); + } + // Drain RX FIFO, then wait for shifting to finish (which may be *after* + // TX FIFO drains), then drain RX FIFO again + while (spi.is_readable()) { + _ = spi_regs.SSPDR.raw; + } + while (spi.get_regs().SSPSR.read().BSY == 1) { + std.log.debug("SPI busy!", .{}); + } + while (spi.is_readable()) { + _ = spi_regs.SSPDR.raw; + } + // Don't leave overrun flag set + peripherals.SPI0.SSPICR.modify(.{ .RORIC = 1 }); + return src.len; + } + + fn set_baudrate(spi: SPI, baudrate: u32, freq_in: u32) u64 { + const spi_regs = spi.get_regs(); + // Find smallest prescale value which puts output frequency in range of + // post-divide. Prescale is an even number from 2 to 254 inclusive. + var prescale: u64 = 2; + while (prescale <= 254) : (prescale += 2) { + if (freq_in < (prescale + 2) * 256 * baudrate) break; + } + std.debug.assert(prescale <= 254); //Freq too low + // Find largest post-divide which makes output <= baudrate. Post-divide is + // an integer in the range 1 to 256 inclusive. + + var postdiv: u64 = 256; + while (postdiv > 1) : (postdiv -= 1) { + if (freq_in / (prescale * (postdiv - 1)) > baudrate) break; + } + spi_regs.SSPCPSR.modify(.{ .CPSDVSR = @intCast(u8, prescale) }); + spi_regs.SSPCR0.modify(.{ .SCR = @intCast(u8, postdiv - 1) }); + + // Return the frequency we were able to achieve + return freq_in / (prescale * postdiv); + } +};