Adding WIP

This commit is contained in:
Ryan Summers 2020-11-09 12:30:02 +01:00
parent 28cb3906ac
commit 8d807fa09b
4 changed files with 185 additions and 282 deletions

View File

@ -13,12 +13,10 @@ use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin};
///
/// The chip supports a number of serial interfaces to improve data throughput, including normal,
/// dual, and quad SPI configurations.
pub struct Ad9959<INTERFACE, DELAY, UPDATE> {
pub struct Ad9959<INTERFACE> {
pub interface: INTERFACE,
delay: DELAY,
reference_clock_frequency: f32,
system_clock_multiplier: u8,
io_update: UPDATE,
communication_mode: Mode,
}
@ -31,8 +29,6 @@ pub trait Interface {
fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>;
fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>;
fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Self::Error>;
}
/// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to
@ -93,12 +89,7 @@ pub enum Error {
Frequency,
}
impl<PinE, INTERFACE, DELAY, UPDATE> Ad9959<INTERFACE, DELAY, UPDATE>
where
INTERFACE: Interface,
DELAY: DelayMs<u8>,
UPDATE: OutputPin<Error = PinE>,
{
impl<I: Interface> Ad9959<I> {
/// Construct and initialize the DDS.
///
/// Args:
@ -110,36 +101,26 @@ where
/// * `clock_frequency` - The clock frequency of the reference clock input.
/// * `multiplier` - The desired clock multiplier for the system clock. This multiplies
/// `clock_frequency` to generate the system clock.
pub fn new<RST>(
interface: INTERFACE,
reset_pin: &mut RST,
io_update: UPDATE,
delay: DELAY,
pub fn new(
interface: I,
reset_pin: &mut impl OutputPin,
delay: &mut impl DelayMs<u8>,
desired_mode: Mode,
clock_frequency: f32,
multiplier: u8,
) -> Result<Self, Error>
where
RST: OutputPin,
{
) -> Result<Self, Error> {
let mut ad9959 = Ad9959 {
interface,
io_update,
delay,
reference_clock_frequency: clock_frequency,
system_clock_multiplier: 1,
communication_mode: desired_mode,
};
ad9959.io_update.set_low().or_else(|_| Err(Error::Pin))?;
// Reset the AD9959
reset_pin.set_high().or_else(|_| Err(Error::Pin))?;
// Delay for a clock cycle to allow the device to reset.
ad9959
.delay
.delay_ms((1000.0 / clock_frequency as f32) as u8);
delay.delay_ms((1000.0 / clock_frequency as f32) as u8);
reset_pin.set_low().or_else(|_| Err(Error::Pin))?;
@ -156,9 +137,6 @@ where
.write(Register::CSR as u8, &csr)
.map_err(|_| Error::Interface)?;
// Latch the configuration registers to make them active.
ad9959.latch_configuration()?;
ad9959
.interface
.configure_mode(desired_mode)
@ -179,19 +157,6 @@ where
Ok(ad9959)
}
/// Latch the DDS configuration to ensure it is active on the output channels.
pub fn latch_configuration(&mut self) -> Result<(), Error> {
self.delay.delay_ms(2);
self.io_update.set_high().or_else(|_| Err(Error::Pin))?;
// The SYNC_CLK is 1/4 the system clock frequency. The IO_UPDATE pin must be latched for one
// full SYNC_CLK pulse to register. For safety, we latch for 5 here.
self.delay
.delay_ms((5000.0 / self.system_clock_frequency()) as u8);
self.io_update.set_low().or_else(|_| Err(Error::Pin))?;
Ok(())
}
/// Configure the internal system clock of the chip.
///
/// Arguments:
@ -232,8 +197,6 @@ where
.map_err(|_| Error::Interface)?;
self.system_clock_multiplier = multiplier;
self.latch_configuration()?;
Ok(self.system_clock_frequency())
}
@ -337,10 +300,6 @@ where
.write(register as u8, &data)
.map_err(|_| Error::Interface)?;
// Latch the configuration and restore the previous CSR. Note that the re-enable of the
// channel happens immediately, so the CSR update does not need to be latched.
self.latch_configuration()?;
Ok(())
}
@ -529,43 +488,55 @@ where
let tuning_word = u32::from_be_bytes(tuning_word);
// Convert the tuning word into a frequency.
Ok((tuning_word as f32 * self.system_clock_frequency()) / (1u64 << 32) as f32)
Ok((tuning_word as f32 * self.system_clock_frequency())
/ (1u64 << 32) as f32)
}
pub fn write_profile(&mut self, channel: Channel, freq: f32, turns: f32, amplitude: f32) -> Result<(), Error> {
pub fn serialize_profile(
&self,
channel: Channel,
freq: f32,
turns: f32,
amplitude: f32,
) -> Result<[u32; 4], Error> {
let csr: u8 = *0x00_u8
.set_bits(1..=2, self.communication_mode as u8)
.set_bit(4 + channel as usize, true);
// The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the
// frequency tuning word and f_s is the system clock rate.
let tuning_word: u32 = ((freq * (1u64 << 32) as f32) / self.system_clock_frequency()) as u32;
let tuning_word: u32 = ((freq * (1u64 << 32) as f32)
/ self.system_clock_frequency())
as u32;
let phase_offset: u16 = (turns * (1 << 14) as f32) as u16 & 0x3FFFu16;
let pow: u32 = *0u32.set_bits(24..32, Register::CPOW0 as u32)
let pow: u32 = *0u32
.set_bits(24..32, Register::CPOW0 as u32)
.set_bits(8..24, phase_offset as u32)
.set_bits(0..8, Register::CFTW0 as u32);
// Enable the amplitude multiplier for the channel if required. The amplitude control has
// full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever
// full-scale is used.
let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16;
let acr: u32 = *0u32.set_bits(24..32, Register::ACR as u32)
let acr: u32 = *0u32
.set_bits(24..32, Register::ACR as u32)
.set_bits(0..10, amplitude_control as u32 & 0x3FF)
.set_bit(12, amplitude_control < (1 << 10));
let serialized: [u32; 4] = [
u32::from_le_bytes([Register::CSR as u8, csr, Register::CSR as u8, csr]),
u32::from_le_bytes([
Register::CSR as u8,
csr,
Register::CSR as u8,
csr,
]),
acr.to_be(),
pow.to_be(),
tuning_word.to_be(),
];
self.interface.write_profile(serialized).map_err(|_| Error::Interface)?;
Ok(())
Ok(serialized)
}
}

View File

@ -1,12 +1,12 @@
use crate::hal;
use hal::rcc::{CoreClocks, ResetEnable, rec};
use hal::rcc::{rec, CoreClocks, ResetEnable};
pub enum Channel {
One,
Two,
}
struct HighResTimerE {
pub struct HighResTimerE {
master: hal::stm32::HRTIM_MASTER,
timer: hal::stm32::HRTIM_TIME,
common: hal::stm32::HRTIM_COMMON,
@ -15,15 +15,29 @@ struct HighResTimerE {
}
impl HighResTimerE {
pub fn new(timer_regs: hal::stm32::HRTIM_TIME, clocks: CoreClocks, prec: rec::Hrtim) -> Self {
let master = unsafe { &*hal::stm32::HRTIM_MASTER::ptr() };
let common = unsafe { &*hal::stm32::HRTIM_COMMON::ptr() };
pub fn new(
timer_regs: hal::stm32::HRTIM_TIME,
master_regs: hal::stm32::HRTIM_MASTER,
common_regs: hal::stm32::HRTIM_COMMON,
clocks: CoreClocks,
prec: rec::Hrtim,
) -> Self {
prec.reset().enable();
Self { master, timer: timer_regs, common, clocks }
Self {
master: master_regs,
timer: timer_regs,
common: common_regs,
clocks,
}
}
pub fn configure_single_shot(&mut self, channel: Channel, set_duration: f32, set_offset: f32) {
pub fn configure_single_shot(
&mut self,
channel: Channel,
set_duration: f32,
set_offset: f32,
) {
// Disable the timer before configuration.
self.master.mcr.modify(|_, w| w.tecen().clear_bit());
@ -32,12 +46,12 @@ impl HighResTimerE {
// is the APB bus clock.
let minimum_duration = set_duration + set_offset;
let source_frequency = self.clocks.timy_ker_ck;
let source_cycles = minimum_duration * source_frequency;
let source_frequency: u32 = self.clocks.timy_ker_ck().0;
let source_cycles = (minimum_duration * source_frequency as f32) as u32;
// Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that
// allows us the highest resolution per tick, so lower dividers are favored.
let divider = if source_cycles < 0xFFDF {
let divider: u8 = if source_cycles < 0xFFDF {
1
} else if (source_cycles / 2) < 0xFFDF {
2
@ -48,26 +62,32 @@ impl HighResTimerE {
};
// The period register must be greater than or equal to 3 cycles.
assert!((source_cycles / divider) > 2);
let period = (source_cycles / divider as u32) as u16;
assert!(period > 2);
// We now have the prescaler and the period registers. Configure the timer.
self.timer.timecr.modify(|_, w| unsafe{w.ck_pscx().bits(divider)})
self.timer.perer.write(|w| unsafe{w.per().bits(source_cycles / divider)});
self.timer
.timecr
.modify(|_, w| unsafe { w.ck_pscx().bits(divider) });
self.timer.perer.write(|w| unsafe { w.perx().bits(period) });
// Configure the comparator 1 level.
self.timer.cmpe1r.write(|w| unsafe{w.cmp1().bits(set_offset * source_frequency)});
let offset = (set_offset * source_frequency as f32) as u16;
self.timer
.cmp1er
.write(|w| unsafe { w.cmp1x().bits(offset) });
// Configure the set/reset signals.
// Set on compare with CMP1, reset upon reaching PER
match channel {
Channel::One => {
self.timer.sete1r().write(|w| w.cmp1().set_bit());
self.timer.resete1r().write(|w| w.per().set_bit());
},
self.timer.sete1r.write(|w| w.cmp1().set_bit());
self.timer.rste1r.write(|w| w.per().set_bit());
}
Channel::Two => {
self.timer.sete2r().write(|w| w.cmp1().set_bit());
self.timer.resete2r().write(|w| w.per().set_bit());
},
self.timer.sete2r.write(|w| w.cmp1().set_bit());
self.timer.rste2r.write(|w| w.per().set_bit());
}
}
// Enable the timer now that it is configured.

View File

@ -45,6 +45,7 @@ static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new();
mod afe;
mod eeprom;
mod hrtimer;
mod iir;
mod pounder;
mod server;
@ -187,11 +188,12 @@ const APP: () = {
'static,
'static,
'static,
ethernet::EthernetDMA<'static>>,
ethernet::EthernetDMA<'static>,
>,
eth_mac: ethernet::EthernetMAC,
mac_addr: net::wire::EthernetAddress,
pounder: Option<pounder::PounderDevices<asm_delay::AsmDelay>>,
pounder: Option<pounder::PounderDevices>,
#[init([[0.; 5]; 2])]
iir_state: [iir::IIRState; 2],
@ -439,41 +441,19 @@ const APP: () = {
.set_speed(hal::gpio::Speed::VeryHigh);
let qspi =
hal::qspi::Qspi::new(dp.QUADSPI, &mut clocks, 10.mhz())
hal::qspi::Qspi::new(dp.QUADSPI, &mut clocks, 50.mhz())
.unwrap();
pounder::QspiInterface::new(qspi).unwrap()
};
let mut reset_pin = gpioa.pa0.into_push_pull_output();
// Configure the IO_Update signal for the DDS.
let mut hrtimer = HighResTimerE::new(dp.HRTIM_TIME, ccdr.clocks, ccdr.peripheral.HRTIM);
// IO_Update should be latched for 50ns after the QSPI profile write. Profile writes
// are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32
// QSPI clock cycles. The QSPI is configured for 10MHz, so this comes out to an
// offset of 3.2uS.
// TODO: This currently does not meet the 2uS timing that we have for profile
// updates, since we want to send a profile update for every DAC update. We should
// increase the QSPI clock frequency.
hrtimer.configure_single_shot(hrtimer::Channel::Two, 50e-9, 3.2e-6);
let io_update = gpiog.pg7.into_push_pull_output();
let asm_delay = {
let frequency_hz = clocks.clocks.c_ck().0;
asm_delay::AsmDelay::new(asm_delay::bitrate::Hertz(
frequency_hz,
))
};
ad9959::Ad9959::new(
qspi_interface,
&mut reset_pin,
io_update,
asm_delay,
&mut delay,
ad9959::Mode::FourBitSerial,
100_000_000f32,
100_000_000_f32,
5,
)
.unwrap()
@ -533,10 +513,39 @@ const APP: () = {
let adc1_in_p = gpiof.pf11.into_analog();
let adc2_in_p = gpiof.pf14.into_analog();
let io_update_trigger = {
let _io_update = gpiog
.pg7
.into_alternate_af2()
.set_speed(hal::gpio::Speed::VeryHigh);
// Configure the IO_Update signal for the DDS.
let mut hrtimer = hrtimer::HighResTimerE::new(
dp.HRTIM_TIME,
dp.HRTIM_MASTER,
dp.HRTIM_COMMON,
clocks.clocks,
clocks.peripheral.HRTIM,
);
// IO_Update should be latched for 50ns after the QSPI profile write. Profile writes
// are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32
// QSPI clock cycles. The QSPI is configured for 50MHz, so this comes out to an
// offset of 640nS. We use 900ns to be safe.
hrtimer.configure_single_shot(
hrtimer::Channel::Two,
50_e-9,
900_e-9,
);
hrtimer
};
Some(
pounder::PounderDevices::new(
io_expander,
ad9959,
io_update_trigger,
spi,
adc1,
adc2,
@ -759,38 +768,6 @@ const APP: () = {
// TODO: Replace with reference to CPU clock from CCDR.
next_ms += 400_000.cycles();
match c.resources.pounder {
Some(pounder) => {
pounder.ad9959.interface.start_stream();
let state = pounder::ChannelState {
parameters: pounder::DdsChannelState {
phase_offset: 0.0,
frequency: 100_000_000.0,
amplitude: 1.0,
enabled: true,
},
attenuation: 0.0,
};
let state1 = pounder::ChannelState {
parameters: pounder::DdsChannelState {
phase_offset: 0.5,
frequency: 100_000_000.0,
amplitude: 1.0,
enabled: true,
},
attenuation: 0.0,
};
pounder.set_channel_state(pounder::Channel::Out0, state).unwrap();
pounder.set_channel_state(pounder::Channel::Out1, state1).unwrap();
pounder.ad9959.latch_configuration().unwrap();
},
_ => panic!("Failed"),
}
loop {
let tick = Instant::now() > next_ms;
@ -827,34 +804,6 @@ const APP: () = {
}),
"stabilizer/afe0/gain": (|| c.resources.afe0.get_gain()),
"stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()),
"pounder/in0": (|| {
match c.resources.pounder {
Some(pounder) =>
pounder.get_input_channel_state(pounder::Channel::In0),
_ => Err(pounder::Error::Access),
}
}),
"pounder/in1": (|| {
match c.resources.pounder {
Some(pounder) =>
pounder.get_input_channel_state(pounder::Channel::In1),
_ => Err(pounder::Error::Access),
}
}),
"pounder/out0": (|| {
match c.resources.pounder {
Some(pounder) =>
pounder.get_output_channel_state(pounder::Channel::Out0),
_ => Err(pounder::Error::Access),
}
}),
"pounder/out1": (|| {
match c.resources.pounder {
Some(pounder) =>
pounder.get_output_channel_state(pounder::Channel::Out1),
_ => Err(pounder::Error::Access),
}
}),
"pounder/dds/clock": (|| {
match c.resources.pounder {
Some(pounder) => pounder.get_dds_clock_config(),

View File

@ -4,6 +4,7 @@ mod attenuators;
mod rf_power;
use super::hal;
use super::hrtimer::HighResTimerE;
use attenuators::AttenuatorInterface;
use rf_power::PowerMeasurementInterface;
@ -111,9 +112,54 @@ impl QspiInterface {
})
}
pub fn start_stream(&mut self) {
pub fn start_stream(&mut self) -> Result<(), Error> {
if self.qspi.is_busy() {
return Err(Error::Qspi);
}
// Configure QSPI for infinite transaction mode using only a data phase (no instruction or
// address).
let qspi_regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
qspi_regs.fcr.modify(|_, w| w.ctcf().set_bit());
unsafe {
qspi_regs.dlr.write(|w| w.dl().bits(0xFFFF_FFFF));
qspi_regs
.ccr
.modify(|_, w| w.imode().bits(0).fmode().bits(1));
}
self.streaming = true;
self.qspi.enter_write_stream_mode().unwrap();
Ok(())
}
pub fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Error> {
if self.streaming == false {
return Err(Error::Qspi);
}
let qspi_regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
unsafe {
core::ptr::write_volatile(
&qspi_regs.dr as *const _ as *mut u32,
data[0],
);
core::ptr::write_volatile(
&qspi_regs.dr as *const _ as *mut u32,
data[1],
);
core::ptr::write_volatile(
&qspi_regs.dr as *const _ as *mut u32,
data[2],
);
core::ptr::write_volatile(
&qspi_regs.dr as *const _ as *mut u32,
data[3],
);
}
Ok(())
}
}
@ -223,11 +269,6 @@ impl ad9959::Interface for QspiInterface {
}
}
fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Error> {
self.qspi.write_profile(data);
Ok(())
}
fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Error> {
if (addr & 0x80) != 0 {
return Err(Error::InvalidAddress);
@ -238,17 +279,16 @@ impl ad9959::Interface for QspiInterface {
return Err(Error::Qspi);
}
self.qspi.read(0x80_u8 | addr, dest).map_err(|_| Error::Qspi)
self.qspi
.read(0x80_u8 | addr, dest)
.map_err(|_| Error::Qspi)
}
}
/// A structure containing implementation for Pounder hardware.
pub struct PounderDevices<DELAY> {
pub ad9959: ad9959::Ad9959<
QspiInterface,
DELAY,
hal::gpio::gpiog::PG7<hal::gpio::Output<hal::gpio::PushPull>>,
>,
pub struct PounderDevices {
pub ad9959: ad9959::Ad9959<QspiInterface>,
pub io_update_trigger: HighResTimerE,
mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>,
attenuator_spi: hal::spi::Spi<hal::stm32::SPI1>,
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
@ -257,26 +297,21 @@ pub struct PounderDevices<DELAY> {
adc2_in_p: hal::gpio::gpiof::PF14<hal::gpio::Analog>,
}
impl<DELAY> PounderDevices<DELAY>
where
DELAY: embedded_hal::blocking::delay::DelayMs<u8>,
{
impl PounderDevices {
/// Construct and initialize pounder-specific hardware.
///
/// Args:
/// * `ad9959` - The DDS driver for the pounder hardware.
/// * `attenuator_spi` - A SPI interface to control digital attenuators.
/// * `io_update_timer` - The HRTimer with the IO_update signal connected to the output.
/// * `adc1` - The ADC1 peripheral for measuring power.
/// * `adc2` - The ADC2 peripheral for measuring power.
/// * `adc1_in_p` - The input channel for the RF power measurement on IN0.
/// * `adc2_in_p` - The input channel for the RF power measurement on IN1.
pub fn new(
mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>,
ad9959: ad9959::Ad9959<
QspiInterface,
DELAY,
hal::gpio::gpiog::PG7<hal::gpio::Output<hal::gpio::PushPull>>,
>,
ad9959: ad9959::Ad9959<QspiInterface>,
io_update_trigger: HighResTimerE,
attenuator_spi: hal::spi::Spi<hal::stm32::SPI1>,
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
adc2: hal::adc::Adc<hal::stm32::ADC2, hal::adc::Enabled>,
@ -285,6 +320,7 @@ where
) -> Result<Self, Error> {
let mut devices = Self {
mcp23017,
io_update_trigger,
ad9959,
attenuator_spi,
adc1,
@ -312,6 +348,9 @@ where
// Select the on-board clock with a 4x prescaler (400MHz).
devices.select_onboard_clock(4u8)?;
// Run the DDS in stream-only mode (no read support).
devices.ad9959.interface.start_stream();
Ok(devices)
}
@ -390,91 +429,6 @@ where
})
}
/// Get the state of a Pounder input channel.
///
/// Args:
/// * `channel` - The pounder channel to get the state of. Must be an input channel
///
/// Returns:
/// The read-back channel input state.
pub fn get_input_channel_state(
&mut self,
channel: Channel,
) -> Result<InputChannelState, Error> {
match channel {
Channel::In0 | Channel::In1 => {
let channel_state = self.get_dds_channel_state(channel)?;
let attenuation = self.get_attenuation(channel)?;
let power = self.measure_power(channel)?;
Ok(InputChannelState {
attenuation,
power,
mixer: channel_state,
})
}
_ => Err(Error::InvalidChannel),
}
}
/// Get the state of a DDS channel.
///
/// Args:
/// * `channel` - The pounder channel to get the state of.
///
/// Returns:
/// The read-back channel state.
fn get_dds_channel_state(
&mut self,
channel: Channel,
) -> Result<DdsChannelState, Error> {
let frequency = self
.ad9959
.get_frequency(channel.into())
.map_err(|_| Error::Dds)?;
let phase_offset = self
.ad9959
.get_phase(channel.into())
.map_err(|_| Error::Dds)?;
let amplitude = self
.ad9959
.get_amplitude(channel.into())
.map_err(|_| Error::Dds)?;
Ok(DdsChannelState {
phase_offset,
frequency,
amplitude,
enabled: true,
})
}
/// Get the state of a DDS output channel.
///
/// Args:
/// * `channel` - The pounder channel to get the output state of. Must be an output channel.
///
/// Returns:
/// The read-back output channel state.
pub fn get_output_channel_state(
&mut self,
channel: Channel,
) -> Result<OutputChannelState, Error> {
match channel {
Channel::Out0 | Channel::Out1 => {
let channel_state = self.get_dds_channel_state(channel)?;
let attenuation = self.get_attenuation(channel)?;
Ok(OutputChannelState {
attenuation,
channel: channel_state,
})
}
_ => Err(Error::InvalidChannel),
}
}
/// Configure a DDS channel.
///
/// Args:
@ -485,16 +439,25 @@ where
channel: Channel,
state: ChannelState,
) -> Result<(), Error> {
self.ad9959.write_profile(channel.into(), state.parameters.frequency,
state.parameters.phase_offset, state.parameters.amplitude).map_err(|_| Error::Dds)?;
let profile = self
.ad9959
.serialize_profile(
channel.into(),
state.parameters.frequency,
state.parameters.phase_offset,
state.parameters.amplitude,
)
.map_err(|_| Error::Dds)?;
self.ad9959.interface.write_profile(profile).unwrap();
self.io_update_trigger.trigger();
//self.set_attenuation(channel, state.attenuation)?;
self.set_attenuation(channel, state.attenuation)?;
Ok(())
}
}
impl<DELAY> AttenuatorInterface for PounderDevices<DELAY> {
impl AttenuatorInterface for PounderDevices {
/// Reset all of the attenuators to a power-on default state.
fn reset_attenuators(&mut self) -> Result<(), Error> {
self.mcp23017
@ -566,7 +529,7 @@ impl<DELAY> AttenuatorInterface for PounderDevices<DELAY> {
}
}
impl<DELAY> PowerMeasurementInterface for PounderDevices<DELAY> {
impl PowerMeasurementInterface for PounderDevices {
/// Sample an ADC channel.
///
/// Args: