diff --git a/Cargo.lock b/Cargo.lock index 529f5b3..9fbbe91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,8 +404,9 @@ dependencies = [ [[package]] name = "mcp23017" -version = "0.1.1" -source = "git+https://github.com/lucazulian/mcp23017.git?rev=523d71d#523d71dcb11fc0ea4bd9385ef2527ae7a7eee987" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c32fd6627e73f1cfa95c00ddcdcb5a6a6ddbd10b308d08588a502c018b6e12c" dependencies = [ "embedded-hal", ] diff --git a/Cargo.toml b/Cargo.toml index 44fe9df..93eaa9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,17 +45,13 @@ ad9959 = { path = "ad9959" } miniconf = "0.1.0" shared-bus = {version = "0.2.2", features = ["cortex-m"] } serde-json-core = "0.4" +mcp23017 = "1.0" # rtt-target bump [dependencies.rtt-logger] git = "https://github.com/quartiq/rtt-logger.git" rev = "70b0eb5" -# rewrite -[dependencies.mcp23017] -git = "https://github.com/lucazulian/mcp23017.git" -rev = "523d71d" - [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] # version = "0.9.0" diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index 21dfa21..cafe4fe 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -43,6 +43,7 @@ pub enum Mode { /// The configuration registers within the AD9959 DDS device. The values of each register are /// equivalent to the address. +#[allow(clippy::upper_case_acronyms)] pub enum Register { CSR = 0x00, FR1 = 0x01, @@ -596,6 +597,22 @@ impl ProfileSerializer { self.index += value.len() + 1; } + fn pad(&mut self) { + // Pad the buffer to 32-bit (4 byte) alignment by adding dummy writes to CSR and LSRR. + match self.index & 3 { + 3 => { + // For a level of 3, we have to pad with 5 bytes to align things. + self.add_write(Register::CSR, &[(self.mode as u8) << 1]); + self.add_write(Register::LSRR, &[0, 0]); + } + 2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]), + 1 => self.add_write(Register::LSRR, &[0, 0]), + 0 => {} + + _ => unreachable!(), + } + } + /// Get the serialized profile as a slice of 32-bit words. /// /// # Note @@ -604,21 +621,8 @@ impl ProfileSerializer { /// /// # Returns /// A slice of `u32` words representing the serialized profile. - pub fn finalize<'a>(&'a mut self) -> &[u32] { - // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and LSRR. - let padding = 4 - (self.index % 4); - match padding { - 1 => { - // For a pad size of 1, we have to pad with 5 bytes to align things. - self.add_write(Register::CSR, &[(self.mode as u8) << 1]); - self.add_write(Register::LSRR, &[0, 0]); - } - 2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]), - 3 => self.add_write(Register::LSRR, &[0, 0]), - 4 => {} - - _ => unreachable!(), - } + pub fn finalize<'a>(&'a mut self) -> &'a [u32] { + self.pad(); unsafe { core::slice::from_raw_parts::<'a, u32>( &self.data as *const _ as *const u32, diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 02aed76..fca38e1 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -802,7 +802,7 @@ pub fn setup( let scl = gpiob.pb8.into_alternate_af4().set_open_drain(); let i2c1 = device.I2C1.i2c( (scl, sda), - 100.khz(), + 400.khz(), ccdr.peripheral.I2C1, &ccdr.clocks, ); diff --git a/src/hardware/pounder/attenuators.rs b/src/hardware/pounder/attenuators.rs index 156da9f..ef22814 100644 --- a/src/hardware/pounder/attenuators.rs +++ b/src/hardware/pounder/attenuators.rs @@ -30,16 +30,16 @@ pub trait AttenuatorInterface { // Read all the channels, modify the channel of interest, and write all the channels back. // This ensures the staging register and the output register are always in sync. let mut channels = [0_u8; 4]; - self.read_all_attenuators(&mut channels)?; + self.transfer_attenuators(&mut channels)?; // The lowest 2 bits of the 8-bit shift register on the attenuator are ignored. Shift the // attenuator code into the upper 6 bits of the register value. Note that the attenuator // treats inputs as active-low, so the code is inverted before writing. - channels[channel as usize] = (!attenuation_code) << 2; - self.write_all_attenuators(&channels)?; + channels[channel as usize] = !(attenuation_code << 2); + self.transfer_attenuators(&mut channels)?; // Finally, latch the output of the updated channel to force it into an active state. - self.latch_attenuators(channel)?; + self.latch_attenuator(channel)?; Ok(attenuation_code as f32 / 2.0) } @@ -57,8 +57,8 @@ pub trait AttenuatorInterface { // Reading the data always shifts data out of the staging registers, so we perform a // duplicate write-back to ensure the staging register is always equal to the output // register. - self.read_all_attenuators(&mut channels)?; - self.write_all_attenuators(&channels)?; + self.transfer_attenuators(&mut channels)?; + self.transfer_attenuators(&mut channels)?; // The attenuation code is stored in the upper 6 bits of the register, where each LSB // represents 0.5 dB. The attenuator stores the code as active-low, so inverting the result @@ -74,13 +74,10 @@ pub trait AttenuatorInterface { fn reset_attenuators(&mut self) -> Result<(), Error>; - fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error>; - fn read_all_attenuators( + fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error>; + + fn transfer_attenuators( &mut self, channels: &mut [u8; 4], ) -> Result<(), Error>; - fn write_all_attenuators( - &mut self, - channels: &[u8; 4], - ) -> Result<(), Error>; } diff --git a/src/hardware/pounder/mod.rs b/src/hardware/pounder/mod.rs index 32a8e80..3105632 100644 --- a/src/hardware/pounder/mod.rs +++ b/src/hardware/pounder/mod.rs @@ -17,14 +17,21 @@ use rf_power::PowerMeasurementInterface; use embedded_hal::{adc::OneShot, blocking::spi::Transfer}; -const EXT_CLK_SEL_PIN: u8 = 8 + 7; -#[allow(dead_code)] -const OSC_EN_N_PIN: u8 = 8 + 6; -const ATT_RST_N_PIN: u8 = 8 + 5; -const ATT_LE3_PIN: u8 = 8 + 3; -const ATT_LE2_PIN: u8 = 8 + 2; -const ATT_LE1_PIN: u8 = 8 + 1; -const ATT_LE0_PIN: u8 = 8; +pub enum GpioPin { + Led4Green = 0, + Led5Red = 1, + Led6Green = 2, + Led7Red = 3, + Led8Green = 4, + Led9Red = 5, + AttLe0 = 8, + AttLe1 = 8 + 1, + AttLe2 = 8 + 2, + AttLe3 = 8 + 3, + AttRstN = 8 + 5, + OscEnN = 8 + 6, + ExtClkSel = 8 + 7, +} #[derive(Debug, Copy, Clone)] pub enum Error { @@ -37,13 +44,15 @@ pub enum Error { Adc, } +/// The numerical value (discriminant) of the Channel enum is the index in the attenuator shift +/// register as well as the attenuator latch enable signal index on the GPIO extender. #[derive(Debug, Copy, Clone)] #[allow(dead_code)] pub enum Channel { - In0, - In1, - Out0, - Out1, + In0 = 0, + Out0 = 1, + In1 = 2, + Out1 = 3, } #[derive(Serialize, Deserialize, Copy, Clone, Debug)] @@ -80,14 +89,14 @@ pub struct DdsClockConfig { pub external_clock: bool, } -impl Into for Channel { +impl From for ad9959::Channel { /// Translate pounder channels to DDS output channels. - fn into(self) -> ad9959::Channel { - match self { - Channel::In0 => ad9959::Channel::Two, - Channel::In1 => ad9959::Channel::Four, - Channel::Out0 => ad9959::Channel::One, - Channel::Out1 => ad9959::Channel::Three, + fn from(other: Channel) -> Self { + match other { + Channel::In0 => Self::Two, + Channel::In1 => Self::Four, + Channel::Out0 => Self::One, + Channel::Out1 => Self::Three, } } } @@ -300,25 +309,20 @@ impl PounderDevices { adc2_in_p, }; - // Configure power-on-default state for pounder. All LEDs are on, on-board oscillator - // selected, attenuators out of reset. Note that testing indicates the output state needs to - // be set first to properly update the output registers. + // Configure power-on-default state for pounder. All LEDs are off, on-board oscillator + // selected and enabled, attenuators out of reset. Note that testing indicates the + // output state needs to be set first to properly update the output registers. devices .mcp23017 .all_pin_mode(mcp23017::PinMode::OUTPUT) .map_err(|_| Error::I2c)?; devices .mcp23017 - .write_gpio(mcp23017::Port::GPIOA, 0x3F) + .write_gpio(mcp23017::Port::GPIOA, 0x00) .map_err(|_| Error::I2c)?; devices .mcp23017 - .write_gpio(mcp23017::Port::GPIOB, 1 << 5) - .map_err(|_| Error::I2c)?; - - devices - .mcp23017 - .digital_write(EXT_CLK_SEL_PIN, false) + .write_gpio(mcp23017::Port::GPIOB, 0x2F) .map_err(|_| Error::I2c)?; Ok(devices) @@ -329,12 +333,11 @@ impl AttenuatorInterface for PounderDevices { /// Reset all of the attenuators to a power-on default state. fn reset_attenuators(&mut self) -> Result<(), Error> { self.mcp23017 - .digital_write(ATT_RST_N_PIN, false) + .write_gpio(mcp23017::Port::GPIOB, 0x0f) .map_err(|_| Error::I2c)?; - // TODO: Measure the I2C transaction speed to the RST pin to ensure that the delay is - // sufficient. Document the delay here. + // Duration of one I2C transaction is sufficiently long. self.mcp23017 - .digital_write(ATT_RST_N_PIN, true) + .write_gpio(mcp23017::Port::GPIOB, 0x2f) .map_err(|_| Error::I2c)?; Ok(()) @@ -344,31 +347,24 @@ impl AttenuatorInterface for PounderDevices { /// /// Args: /// * `channel` - The attenuator channel to latch. - fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error> { - let pin = match channel { - Channel::In0 => ATT_LE0_PIN, - Channel::In1 => ATT_LE2_PIN, - Channel::Out0 => ATT_LE1_PIN, - Channel::Out1 => ATT_LE3_PIN, - }; - + fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error> { + let pin = channel as u8; self.mcp23017 - .digital_write(pin, true) + .write_gpio(mcp23017::Port::GPIOB, 0x2f & !(1 << pin)) .map_err(|_| Error::I2c)?; - // TODO: Measure the I2C transaction speed to the RST pin to ensure that the delay is - // sufficient. Document the delay here. + // Duration of one I2C transaction is sufficiently long. self.mcp23017 - .digital_write(pin, false) + .write_gpio(mcp23017::Port::GPIOB, 0x2f) .map_err(|_| Error::I2c)?; - Ok(()) } /// Read the raw attenuation codes stored in the attenuator shift registers. /// /// Args: - /// * `channels` - A slice to store the channel readings into. - fn read_all_attenuators( + /// * `channels` - A 4 byte slice to be shifted into the + /// attenuators and to contain the data shifted out. + fn transfer_attenuators( &mut self, channels: &mut [u8; 4], ) -> Result<(), Error> { @@ -378,23 +374,6 @@ impl AttenuatorInterface for PounderDevices { Ok(()) } - - /// Write the attenuator shift registers. - /// - /// Args: - /// * `channels` - The data to write into the attenuators. - fn write_all_attenuators( - &mut self, - channels: &[u8; 4], - ) -> Result<(), Error> { - let mut result = [0_u8; 4]; - result.clone_from_slice(channels); - self.attenuator_spi - .transfer(&mut result) - .map_err(|_| Error::Spi)?; - - Ok(()) - } } impl PowerMeasurementInterface for PounderDevices { diff --git a/src/hardware/pounder/rf_power.rs b/src/hardware/pounder/rf_power.rs index ac82b5d..977bd64 100644 --- a/src/hardware/pounder/rf_power.rs +++ b/src/hardware/pounder/rf_power.rs @@ -1,20 +1,22 @@ use super::{Channel, Error}; -/// Provide an interface to measure RF input power in dB. +/// Provide an interface to measure RF input power in dBm. pub trait PowerMeasurementInterface { fn sample_converter(&mut self, channel: Channel) -> Result; /// Measure the power of an input channel in dBm. /// - /// Note: This function assumes the input channel is connected to an AD8363 output. - /// /// Args: - /// * `channel` - The pounder channel to measure the power of in dBm. + /// * `channel` - The pounder input channel to measure the power of. + /// + /// Returns: + /// Power in dBm after the digitally controlled attenuator before the amplifier. fn measure_power(&mut self, channel: Channel) -> Result { let analog_measurement = self.sample_converter(channel)?; - // The AD8363 with VSET connected to VOUT provides an output voltage of 51.7mV / dB at - // 100MHz. It also indicates a y-intercept of -58dBm. - Ok(analog_measurement / 0.0517 - 58.0) + // The AD8363 with VSET connected to VOUT provides an output voltage of 51.7 mV/dB at + // 100MHz with an intercept of -58 dBm. + // It is placed behind a 20 dB tap. + Ok(analog_measurement * (1. / 0.0517) + (-58. + 20.)) } }