From 7c8d4302378be7d12f7af3f417e04c9a263de722 Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Fri, 30 Sep 2022 12:27:00 -0700 Subject: [PATCH] adc bindings, mostly there but not quite (#14) --- build.zig | 1 + examples/adc.zig | 38 ++++++++++ src/hal.zig | 1 + src/hal/adc.zig | 194 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 examples/adc.zig create mode 100644 src/hal/adc.zig diff --git a/build.zig b/build.zig index 01a26ae..889455d 100644 --- a/build.zig +++ b/build.zig @@ -63,6 +63,7 @@ fn root() []const u8 { } pub const Examples = struct { + adc: microzig.EmbeddedExecutable, blinky: microzig.EmbeddedExecutable, blinky_core1: microzig.EmbeddedExecutable, gpio_clk: microzig.EmbeddedExecutable, diff --git a/examples/adc.zig b/examples/adc.zig new file mode 100644 index 0000000..f8e8c66 --- /dev/null +++ b/examples/adc.zig @@ -0,0 +1,38 @@ +//! This example takes periodic samples of the temperature sensor and +//! prints it to the UART using the stdlib logging facility. +const std = @import("std"); +const microzig = @import("microzig"); +const rp2040 = microzig.hal; +const adc = rp2040.adc; +const time = rp2040.time; + +const temp_sensor: adc.Input = .temperature_sensor; +const uart_id = 0; +const baud_rate = 115200; +const uart_tx_pin = 0; +const uart_rx_pin = 1; + +pub const log = rp2040.uart.log; + +pub fn init() void { + rp2040.clock_config.apply(); + rp2040.gpio.reset(); + adc.init(); + temp_sensor.init(); + + const uart = rp2040.uart.UART.init(uart_id, .{ + .baud_rate = baud_rate, + .tx_pin = uart_tx_pin, + .rx_pin = uart_rx_pin, + .clock_config = rp2040.clock_config, + }); + + rp2040.uart.initLogger(uart); +} + +pub fn main() void { + while (true) : (time.sleepMs(1000)) { + const sample = temp_sensor.read(); + std.log.info("temp value: {}", .{sample}); + } +} diff --git a/src/hal.zig b/src/hal.zig index 61132bd..06c0cdd 100644 --- a/src/hal.zig +++ b/src/hal.zig @@ -1,6 +1,7 @@ const microzig = @import("microzig"); const regs = microzig.chip.registers; +pub const adc = @import("hal/adc.zig"); pub const pins = @import("hal/pins.zig"); pub const gpio = @import("hal/gpio.zig"); pub const clocks = @import("hal/clocks.zig"); diff --git a/src/hal/adc.zig b/src/hal/adc.zig new file mode 100644 index 0000000..a700651 --- /dev/null +++ b/src/hal/adc.zig @@ -0,0 +1,194 @@ +//! NOTE: no settling time is needed when switching analog inputs + +const std = @import("std"); +const assert = std.debug.assert; + +const microzig = @import("microzig"); +const ADC = microzig.chip.registers.ADC; +const rp2040 = microzig.hal; +const gpio = rp2040.gpio; +const resets = rp2040.resets; + +pub const temperature_sensor = struct { + pub inline fn init() void { + setTempSensorEnabled(true); + } + + pub inline fn deinit() void { + setTempSensorEnabled(false); + } + + pub inline fn readRaw() u16 { + return Input.read(.temperature_sensor); + } + + // One-shot conversion returning the temperature in Celcius + pub inline fn read(comptime T: type, comptime Vref: T) T { + // TODO: consider fixed-point + const raw = @intToFloat(T, readRaw()); + const voltage: T = Vref * raw / 0x0fff; + return (27.0 - ((voltage - 0.706) / 0.001721)); + } +}; + +pub const Input = enum(u3) { + ain0, + ain1, + ain2, + ain3, + temperature_sensor, + + /// Setup the GPIO pin as an ADC input + pub fn init(comptime input: Input) void { + switch (input) { + .temperature_sensor => setTempSensorEnabled(true), + else => { + const gpio_num = @as(u32, @enumToInt(input)) + 26; + + gpio.setFunction(gpio_num, .@"null"); + // TODO: implement these, otherwise adc isn't going to work. + //gpio.disablePulls(gpio_num); + //gpio.setInputEnabled(gpio_num, false); + }, + } + } + + /// Disables temp sensor, otherwise it does nothing if the input is + /// one of the others. + pub inline fn deinit(input: Input) void { + switch (input) { + .temperature_sensor => setTempSensorEnabled(true), + else => {}, + } + } + + /// Single-shot, blocking conversion + pub fn read(input: Input) u12 { + // TODO: not sure if setting these during the same write is + // correct + ADC.CS.modify(.{ + .AINSEL = @enumToInt(input), + .START_ONCE = 1, + }); + + // wait for the + while (ADC.CS.read().READY == 0) {} + + return ADC.RESULT.read(); + } +}; + +pub const InputMask = InputMask: { + const enum_fields = @typeInfo(Input).Enum.fields; + var fields: [enum_fields.len]std.builtin.Type.StructField = undefined; + + const default_value: u1 = 0; + for (enum_fields) |enum_field, i| + fields[i] = std.builtin.Type.StructField{ + .name = enum_field.name, + .field_type = u1, + .default_value = &default_value, + .is_comptime = false, + .alignment = 1, + }; + + break :InputMask @Type(.{ + .Struct = .{ + .layout = .Packed, + .fields = &fields, + .backing_integer = std.meta.Int(.Unsigned, enum_fields.len), + .decls = &.{}, + .is_tuple = false, + }, + }); +}; + +/// Initialize ADC hardware +pub fn init() void { + resets.reset(&.{.adc}); + ADC.CS.write(.{ + .EN = 1, + .TS_EN = 0, + .START_ONCE = 0, + .START_MANY = 0, + .READY = 0, + .ERR = 0, + .ERR_STICKY = 0, + .AINSEL = 0, + .RROBIN = 0, + }); + + while (ADC.CS.read().READY == 0) {} +} + +/// Enable/disable ADC interrupt +pub inline fn irqSetEnabled(enable: bool) void { + // TODO: check if this works + ADC.INTE.write(.{ .FIFO = if (enable) @as(u1, 1) else @as(u1, 0) }); +} + +/// Select analog input for next conversion. +pub inline fn selectInput(input: Input) void { + ADC.CS.modify(.{ .AINSEL = @enumToInt(input) }); +} + +/// Get the currently selected analog input. 0..3 are GPIO 26..29 respectively, +/// 4 is the temperature sensor. +pub inline fn getSelectedInput() Input { + // TODO: ensure that the field shouldn't have other values + return @intToEnum(Input, ADC.CS.read().AINSEL); +} + +/// Set to true to power on the temperature sensor. +pub inline fn setTempSensorEnabled(enable: bool) void { + ADC.CS.modify(.{ .TS_EN = if (enable) @as(u1, 1) else @as(u1, 0) }); +} + +/// Sets which of the inputs are to be run in round-robin mode. Setting all to +/// 0 will disable round-robin mode but `disableRoundRobin()` is provided so +/// the user may be explicit. +pub inline fn setRoundRobin(comptime enabled_inputs: InputMask) void { + ADC.CS.modify(.{ .RROBIN = @bitCast(u5, enabled_inputs) }); +} + +/// Disable round-robin sample mode. +pub inline fn disableRoundRobin() void { + ADC.CS.modify(.{ .RROBIN = 0 }); +} + +/// Enable free-running sample mode. +pub inline fn run(enable: bool) void { + ADC.CS.modify(.{ .START_MANY = if (enable) @as(u1, 1) else @as(u1, 0) }); +} + +/// TODO: implement +pub inline fn setClkDiv() void {} + +/// The fifo is 4 samples long, if a conversion is completed and the FIFO is +/// full, the result is dropped. +pub const fifo = struct { + /// TODO: implement + pub inline fn setup() void { + // There are a number of considerations wrt DMA and error detection + } + + /// TODO: implement + /// Return true if FIFO is empty. + pub inline fn isEmpty() bool {} + + /// TODO: implement + /// Read how many samples are in the FIFO. + pub inline fn getLevel() u8 {} + + /// TODO: implement + /// Pop latest result from FIFO. + pub inline fn get() u16 {} + + /// TODO: implement + /// Block until result is available in FIFO, then pop it. + pub inline fn getBlocking() u16 {} + + /// TODO: implement + /// Wait for conversion to complete then discard results in FIFO. + pub inline fn drain() void {} +};