Merge branch 'master' into rj/fast-dbm

* master:
  pounder: clippy
  pounder: add comment on channel enum
  ad9959: refactor pad()
  pounder: enum for gpio ext pins
  pounder: fix attenuator indices (latch and shiftreg)
  pounder io extender: hack around some bug
  rf_power: fix measurement
  attenuators: use robust latching sequence
  deps: use mcp23017 release
  pounder: simplify attenuator spi interface
This commit is contained in:
Robert Jördens 2021-06-01 12:52:58 +02:00
commit 63716111df
7 changed files with 86 additions and 107 deletions

5
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -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,

View File

@ -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,
);

View File

@ -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>;
}

View File

@ -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<ad9959::Channel> for Channel {
impl From<Channel> 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 {

View File

@ -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<f32, Error>;
/// 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<f32, Error> {
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.))
}
}