improve ADC API (#62)

wch-ch32v003
Matt Knight 1 year ago committed by GitHub
parent d1e35696d4
commit 7b7caa9eb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,7 +7,6 @@ const gpio = rp2040.gpio;
const adc = rp2040.adc;
const time = rp2040.time;
const temp_sensor: adc.Input = .temperature_sensor;
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
@ -18,6 +17,10 @@ pub const std_options = struct {
};
pub fn main() void {
adc.apply(.{
.temp_sensor_enabled = true,
});
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
@ -26,9 +29,12 @@ pub fn main() void {
});
rp2040.uart.init_logger(uart);
while (true) : (time.sleep_ms(1000)) {
const sample = temp_sensor.read();
const sample = adc.convert_one_shot_blocking(.temp_sensor) catch {
std.log.err("conversion failed!", .{});
continue;
};
std.log.info("temp value: {}", .{sample});
}
}

@ -7,196 +7,316 @@ const microzig = @import("microzig");
const ADC = microzig.chip.peripherals.ADC;
const gpio = @import("gpio.zig");
const resets = @import("resets.zig");
const clocks = @import("clocks.zig");
pub const temperature_sensor = struct {
pub inline fn init() void {
set_temp_sensor_enabled(true);
}
pub inline fn deinit() void {
set_temp_sensor_enabled(false);
}
pub inline fn read_raw() 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, read_raw());
const voltage: T = Vref * raw / 0x0fff;
return (27.0 - ((voltage - 0.706) / 0.001721));
}
pub const Error = error{
/// ADC conversion failed, one such reason is that the controller failed to
/// converge on a result.
Conversion,
};
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 => set_temp_sensor_enabled(true),
else => {
const pin = gpio.num(@as(u5, @enumToInt(input)) + 26);
pin.set_function(.null);
// TODO: implement these, otherwise adc isn't going to work.
//gpio.disablePulls(gpio_num);
//gpio.setInputEnabled(gpio_num, false);
},
}
/// temp_sensor is not valid because you can refer to it by name.
pub fn input(n: u2) Input {
return @intToEnum(Input, n);
}
/// 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 => set_temp_sensor_enabled(true),
else => {},
}
/// Enable the ADC controller.
pub fn set_enabled(enabled: bool) void {
ADC.CS.modify(.{ .EN = @boolToInt(enabled) });
}
/// 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().RESULT;
}
const Config = struct {
/// Note that this frequency is the sample frequency of the controller, not
/// each input. So for 4 inputs in round-robin mode you'd see 1/4 sample
/// rate for a given put vs what is set here.
sample_frequency: ?u32 = null,
round_robin: ?InputMask = null,
fifo: ?fifo.Config = null,
temp_sensor_enabled: bool = false,
};
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, &fields) |enum_field, *field|
field = 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 {
/// Applies configuration to ADC, leaves it in an enabled state by setting
/// CS.EN = 1. The global clock configuration is not needed to configure the
/// sample rate because the ADC hardware block requires a 48MHz clock.
pub fn apply(config: Config) void {
ADC.CS.write(.{
.EN = 1,
.TS_EN = 0,
.EN = 0,
.TS_EN = @boolToInt(config.temp_sensor_enabled),
.START_ONCE = 0,
.START_MANY = 0,
.READY = 0,
.ERR = 0,
.ERR_STICKY = 0,
.AINSEL = 0,
.RROBIN = 0,
.RROBIN = if (config.round_robin) |rr|
@bitCast(u5, rr)
else
0,
.reserved8 = 0,
.reserved12 = 0,
.reserved16 = 0,
.padding = 0,
});
while (ADC.CS.read().READY == 0) {}
if (config.sample_frequency) |sample_frequency| {
const cycles = (48_000_000 * 256) / @as(u64, sample_frequency);
ADC.DIV.write(.{
.FRAC = @truncate(u8, cycles),
.INT = @intCast(u16, (cycles >> 8) - 1),
.padding = 0,
});
}
/// Enable/disable ADC interrupt
pub inline fn irq_set_enabled(enable: bool) void {
// TODO: check if this works
ADC.INTE.write(.{ .FIFO = if (enable) @as(u1, 1) else @as(u1, 0) });
if (config.fifo) |fifo_config|
fifo.apply(fifo_config);
set_enabled(true);
}
/// Select analog input for next conversion.
pub inline fn select_input(input: Input) void {
ADC.CS.modify(.{ .AINSEL = @enumToInt(input) });
pub fn select_input(in: Input) void {
ADC.CS.modify(.{ .AINSEL = @enumToInt(in) });
}
/// Get the currently selected analog input. 0..3 are GPIO 26..29 respectively,
/// 4 is the temperature sensor.
pub inline fn get_selected_input() Input {
// TODO: ensure that the field shouldn't have other values
return @intToEnum(Input, ADC.CS.read().AINSEL);
pub fn get_selected_input() Input {
const cs = ADC.SC.read();
return @intToEnum(Input, cs.AINSEL);
}
pub const Input = enum(u3) {
/// The temperature sensor must be enabled using
/// `set_temp_sensor_enabled()` in order to use it
temp_sensor = 5,
_,
/// Get the corresponding GPIO pin for an ADC input. Panics if you give it
/// temp_sensor.
pub fn get_gpio_pin(in: Input) gpio.Pin {
return switch (in) {
else => gpio.num(@as(u5, @enumToInt(in)) + 26),
.temp_sensor => @panic("temp_sensor doesn't have a pin"),
};
}
/// Prepares an ADC input's corresponding GPIO pin to be used as an analog
/// input.
pub fn configure_gpio_pin(in: Input) void {
switch (in) {
else => {
const pin = in.get_gpio_pin();
pin.set_function(.null);
pin.set_pull(null);
pin.set_input_enabled(false);
},
.temp_sensor => {},
}
}
};
/// Set to true to power on the temperature sensor.
pub inline fn set_temp_sensor_enabled(enable: bool) void {
ADC.CS.modify(.{ .TS_EN = if (enable) @as(u1, 1) else @as(u1, 0) });
pub fn set_temp_sensor_enabled(enable: bool) void {
ADC.CS.modify(.{ .TS_EN = @boolToInt(enable) });
}
/// T must be floating point.
pub fn temp_sensor_result_to_celcius(comptime T: type, comptime vref: T, result: u12) T {
// TODO: consider fixed-point
const raw = @intToFloat(T, result);
const voltage: T = vref * raw / 0x0fff;
return (27.0 - ((voltage - 0.706) / 0.001721));
}
/// For selecting which inputs are to be used in round-robin mode
pub const InputMask = packed struct(u5) {
ain0: bool = false,
ain1: bool = false,
ain2: bool = false,
ain3: bool = false,
temp_sensor: bool = false,
};
/// 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 set_round_robin(comptime enabled_inputs: InputMask) void {
pub fn round_robin_set(enabled_inputs: InputMask) void {
ADC.CS.modify(.{ .RROBIN = @bitCast(u5, enabled_inputs) });
}
/// Disable round-robin sample mode.
pub inline fn disable_round_robin() void {
pub fn round_robin_disable() 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) });
pub const Mode = enum {
one_shot,
free_running,
};
/// Start the ADC controller. There are three "modes" that the controller
/// operates in:
///
/// - one shot: the input is selected and then conversion is started. The
/// controller stops once the conversion is complete.
///
/// - free running single input: the input is selected and then the conversion
/// is started. Once a conversion is complete the controller begins another
/// on the same input.
///
/// - free running round-robin: a mask of which inputs to sample is set using
/// `round_robin_set()`. Once conversion is completed for one input, a
/// conversion is started for the next set input in the mask.
pub fn start(mode: Mode) void {
switch (mode) {
.one_shot => ADC.CS.modify(.{
.START_ONCE = 1,
}),
.free_running => ADC.CS.modify(.{
.START_MANY = 1,
}),
}
}
/// Check whether the ADC controller has a conversion result
pub fn is_ready() bool {
const cs = ADC.CS.read();
return cs.READY != 0;
}
pub inline fn set_clk_div() void {
@compileError("todo");
/// Single-shot, blocking conversion
pub fn convert_one_shot_blocking(in: Input) Error!u12 {
select_input(in);
start(.one_shot);
while (!is_ready()) {}
return read_result();
}
/// The fifo is 4 samples long, if a conversion is completed and the FIFO is
/// full, the result is dropped.
/// Read conversion result from ADC controller, this function assumes that the
/// controller has a result ready.
pub fn read_result() Error!u12 {
const cs = ADC.CS.read();
return if (cs.ERR == 1)
error.Conversion
else blk: {
const conversion = ADC.RESULT.read();
break :blk conversion.RESULT;
};
}
/// The ADC FIFO can store up to four conversion results. It must be enabled in
/// order to use DREQ or IRQ driven streaming.
pub const fifo = struct {
pub inline fn setup() void {
@compileError("todo");
// There are a number of considerations wrt DMA and error detection
// TODO: what happens when DMA and IRQ are enabled?
pub const Config = struct {
/// Assert DMA requests when the fifo contains data
dreq_enabled: bool = false,
/// Assert Interrupt when fifo contains data
irq_enabled: bool = false,
/// DREQ/IRQ asserted when level >= threshold
thresh: u4 = 0,
/// Shift the conversion so it's 8-bit, good for DMAing to a byte
/// buffer
shift: bool = false,
};
/// Apply ADC FIFO configuration and enable it
pub fn apply(config: fifo.Config) void {
ADC.FCS.write(.{
.DREQ_EN = @boolToInt(config.dreq_enabled),
.THRESH = config.thresh,
.SHIFT = @boolToInt(config.shift),
.EN = 1,
.EMPTY = 0,
.FULL = 0,
.LEVEL = 0,
// As far as it is known, there is zero cost to being able to
// report errors in the FIFO, so let's.
.ERR = 1,
// Writing 1 to these will clear them if they're already set
.UNDER = 1,
.OVER = 1,
.reserved8 = 0,
.reserved16 = 0,
.reserved24 = 0,
.padding = 0,
});
irq_set_enabled(config.irq_enabled);
}
// TODO: do we need to acknowledge an ADC interrupt?
/// Enable/disable ADC interrupt.
pub fn irq_set_enabled(enable: bool) void {
// TODO: check if this works
ADC.INTE.write(.{
.FIFO = @boolToInt(enable),
.padding = 0,
});
}
/// Check if the ADC FIFO is full.
pub fn is_full() bool {
const fsc = ADC.FSC.read();
return fsc.FULL != 0;
}
/// Check if the ADC FIFO is empty.
pub fn is_empty() bool {
const fsc = ADC.FSC.read();
return fsc.EMPTY != 0;
}
/// Get the number of conversion in the ADC FIFO.
pub fn get_level() u4 {
const fsc = ADC.FSC.read();
return fsc.LEVEL;
}
/// Return true if FIFO is empty.
pub inline fn is_empty() bool {
@compileError("todo");
/// Check if the ADC FIFO has overflowed. When overflow happens, the new
/// conversion is discarded. This flag is sticky, to clear it call
/// `clear_overflowed()`.
pub fn has_overflowed() bool {
const fsc = ADC.FSC.read();
return fsc.OVER != 0;
}
/// Read how many samples are in the FIFO.
pub inline fn get_level() u8 {
@compileError("todo");
/// Clear the overflow status flag if it is set.
pub fn clear_overflowed() void {
ADC.FSC.modify(.{ .OVER = 1 });
}
/// Pop latest result from FIFO.
pub inline fn get() u16 {
@compileError("todo");
/// Check if the ADC FIFO has underflowed. This means that the FIFO
/// register was read while the FIFO was empty. This flag is sticky, to
/// clear it call `clear_underflowed()`.
pub fn has_underflowed() bool {
const fsc = ADC.FSC.read();
return fsc.UNDER != 0;
}
/// Block until result is available in FIFO, then pop it.
pub inline fn get_blocking() u16 {
@compileError("todo");
/// Clear the underflow status flag if it is set.
pub fn clear_underflowed() void {
ADC.FSC.modify(.{ .UNDER = 1 });
}
/// Wait for conversion to complete then discard results in FIFO.
pub inline fn drain() void {
@compileError("todo");
/// Pop conversion from ADC FIFO. This function assumes that the FIFO is
/// not empty.
pub fn pop() Error!u12 {
assert(!is_empty());
const result = ADC.FIFO.read();
return if (result.ERR == 1)
error.Conversion
else
result.VAL;
}
};

Loading…
Cancel
Save