Reimporting pounder-specific code
This commit is contained in:
parent
86c4c1ea5e
commit
80661a51fb
|
@ -1,5 +1,13 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "ad9959"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"embedded-hal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.3.2"
|
||||
|
@ -46,6 +54,12 @@ dependencies = [
|
|||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
|
@ -427,6 +441,7 @@ dependencies = [
|
|||
name = "stabilizer"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"ad9959",
|
||||
"asm-delay",
|
||||
"cortex-m",
|
||||
"cortex-m-log",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
members = [
|
||||
"stabilizer",
|
||||
"stm32h7xx-hal",
|
||||
"ad9959",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "ad9959"
|
||||
version = "0.1.0"
|
||||
authors = ["Ryan Summers <ryan.summers@vertigo-designs.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
embedded-hal = {version = "0.2.3", features = ["unproven"]}
|
||||
bit_field = "0.10.0"
|
|
@ -0,0 +1,350 @@
|
|||
#![no_std]
|
||||
|
||||
use bit_field::BitField;
|
||||
use embedded_hal::{
|
||||
digital::v2::OutputPin,
|
||||
blocking::delay::DelayMs,
|
||||
};
|
||||
|
||||
/// A device driver for the AD9959 direct digital synthesis (DDS) chip.
|
||||
///
|
||||
/// This chip provides four independently controllable digital-to-analog output sinusoids with
|
||||
/// configurable phase, amplitude, and frequency. All channels are inherently synchronized as they
|
||||
/// are derived off a common system clock.
|
||||
///
|
||||
/// The chip contains a configurable PLL and supports system clock frequencies up to 500 MHz.
|
||||
///
|
||||
/// 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> {
|
||||
interface: INTERFACE,
|
||||
delay: DELAY,
|
||||
reference_clock_frequency: u32,
|
||||
system_clock_multiplier: u8,
|
||||
io_update: UPDATE,
|
||||
}
|
||||
|
||||
pub trait Interface {
|
||||
type Error;
|
||||
|
||||
fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>;
|
||||
|
||||
fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>;
|
||||
|
||||
fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Mode {
|
||||
SingleBitTwoWire = 0b00,
|
||||
SingleBitThreeWire = 0b01,
|
||||
TwoBitSerial = 0b10,
|
||||
FourBitSerial = 0b11,
|
||||
}
|
||||
|
||||
/// The configuration registers within the AD9959 DDS device. The values of each register are
|
||||
/// equivalent to the address.
|
||||
pub enum Register {
|
||||
CSR = 0x00,
|
||||
FR1 = 0x01,
|
||||
FR2 = 0x02,
|
||||
CFR = 0x03,
|
||||
CFTW0 = 0x04,
|
||||
CPOW0 = 0x05,
|
||||
ACR = 0x06,
|
||||
LSRR = 0x07,
|
||||
RDW = 0x08,
|
||||
FDW = 0x09,
|
||||
CW1 = 0x0a,
|
||||
CW2 = 0x0b,
|
||||
CW3 = 0x0c,
|
||||
CW4 = 0x0d,
|
||||
CW5 = 0x0e,
|
||||
CW6 = 0x0f,
|
||||
CW7 = 0x10,
|
||||
CW8 = 0x11,
|
||||
CW9 = 0x12,
|
||||
CW10 = 0x13,
|
||||
CW11 = 0x14,
|
||||
CW12 = 0x15,
|
||||
CW13 = 0x16,
|
||||
CW14 = 0x17,
|
||||
CW15 = 0x18,
|
||||
}
|
||||
|
||||
/// Specifies an output channel of the AD9959 DDS chip.
|
||||
pub enum Channel {
|
||||
One = 0,
|
||||
Two = 1,
|
||||
Three = 2,
|
||||
Four = 3,
|
||||
}
|
||||
|
||||
/// Possible errors generated by the AD9959 driver.
|
||||
#[derive(Debug)]
|
||||
pub enum Error<InterfaceE> {
|
||||
Interface(InterfaceE),
|
||||
Bounds,
|
||||
Pin,
|
||||
Frequency,
|
||||
}
|
||||
|
||||
impl <InterfaceE> From<InterfaceE> for Error<InterfaceE> {
|
||||
fn from(interface_error: InterfaceE) -> Self {
|
||||
Error::Interface(interface_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl <PinE, InterfaceE, INTERFACE, DELAY, UPDATE> Ad9959<INTERFACE, DELAY, UPDATE>
|
||||
where
|
||||
INTERFACE: Interface<Error = InterfaceE>,
|
||||
DELAY: DelayMs<u8>,
|
||||
UPDATE: OutputPin<Error = PinE>,
|
||||
|
||||
{
|
||||
pub fn new<RST>(interface: INTERFACE,
|
||||
reset_pin: &mut RST,
|
||||
io_update: UPDATE,
|
||||
delay: DELAY,
|
||||
desired_mode: Mode,
|
||||
clock_frequency: u32,
|
||||
multiplier: u8) -> Result<Self, Error<InterfaceE>>
|
||||
where
|
||||
RST: OutputPin,
|
||||
{
|
||||
let mut ad9959 = Ad9959 {
|
||||
interface: interface,
|
||||
io_update: io_update,
|
||||
delay: delay,
|
||||
reference_clock_frequency: clock_frequency,
|
||||
system_clock_multiplier: 1,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
reset_pin.set_low().or_else(|_| Err(Error::Pin))?;
|
||||
|
||||
ad9959.interface.configure_mode(Mode::SingleBitTwoWire)?;
|
||||
|
||||
// Program the interface configuration in the AD9959. Default to all channels enabled.
|
||||
let mut csr: [u8; 1] = [0xF0];
|
||||
csr[0].set_bits(1..3, desired_mode as u8);
|
||||
ad9959.interface.write(0, &csr)?;
|
||||
|
||||
// Configure the interface to the desired mode.
|
||||
ad9959.interface.configure_mode(Mode::FourBitSerial)?;
|
||||
|
||||
// Latch the configuration registers to make them active.
|
||||
ad9959.latch_configuration()?;
|
||||
|
||||
ad9959.interface.configure_mode(desired_mode)?;
|
||||
|
||||
// Set the clock frequency to configure the device as necessary.
|
||||
ad9959.configure_system_clock(clock_frequency, multiplier)?;
|
||||
Ok(ad9959)
|
||||
}
|
||||
|
||||
fn latch_configuration(&mut self) -> Result<(), Error<InterfaceE>> {
|
||||
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:
|
||||
/// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core.
|
||||
/// * `prescaler` - The frequency prescaler of the system clock. Must be 1 or 4-20.
|
||||
///
|
||||
/// Returns:
|
||||
/// The actual frequency configured for the internal system clock.
|
||||
pub fn configure_system_clock(&mut self,
|
||||
reference_clock_frequency: u32,
|
||||
prescaler: u8) -> Result<f64, Error<InterfaceE>>
|
||||
{
|
||||
self.reference_clock_frequency = reference_clock_frequency;
|
||||
|
||||
if prescaler != 1 && (prescaler > 20 || prescaler < 4) {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
let frequency = prescaler as f64 * self.reference_clock_frequency as f64;
|
||||
if frequency > 500_000_000.0f64 {
|
||||
return Err(Error::Frequency);
|
||||
}
|
||||
|
||||
// TODO: Update / disable any enabled channels?
|
||||
let mut fr1: [u8; 3] = [0, 0, 0];
|
||||
self.interface.read(Register::FR1 as u8, &mut fr1)?;
|
||||
fr1[0].set_bits(2..=6, prescaler);
|
||||
|
||||
let vco_range = frequency > 255e6;
|
||||
fr1[0].set_bit(7, vco_range);
|
||||
|
||||
self.interface.write(Register::FR1 as u8, &fr1)?;
|
||||
self.system_clock_multiplier = prescaler;
|
||||
|
||||
Ok(self.system_clock_frequency())
|
||||
}
|
||||
|
||||
/// Perform a self-test of the communication interface.
|
||||
///
|
||||
/// Note:
|
||||
/// This modifies the existing channel enables. They are restored upon exit.
|
||||
///
|
||||
/// Returns:
|
||||
/// True if the self test succeeded. False otherwise.
|
||||
pub fn self_test(&mut self) -> Result<bool, Error<InterfaceE>> {
|
||||
let mut csr: [u8; 1] = [0];
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
let old_csr = csr[0];
|
||||
|
||||
// Enable all channels.
|
||||
csr[0].set_bits(4..8, 0xF);
|
||||
self.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
// Read back the enable.
|
||||
csr[0] = 0;
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
if csr[0].get_bits(4..8) != 0xF {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Clear all channel enables.
|
||||
csr[0].set_bits(4..8, 0x0);
|
||||
self.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
// Read back the enable.
|
||||
csr[0] = 0xFF;
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
if csr[0].get_bits(4..8) != 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Restore the CSR.
|
||||
csr[0] = old_csr;
|
||||
self.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn system_clock_frequency(&self) -> f64 {
|
||||
self.system_clock_multiplier as f64 * self.reference_clock_frequency as f64
|
||||
}
|
||||
|
||||
/// Enable an output channel.
|
||||
pub fn enable_channel(&mut self, channel: Channel) -> Result<(), Error<InterfaceE>> {
|
||||
let mut csr: [u8; 1] = [0];
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
csr[0].set_bit(channel as usize + 4, true);
|
||||
self.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disable an output channel.
|
||||
pub fn disable_channel(&mut self, channel: Channel) -> Result<(), Error<InterfaceE>> {
|
||||
let mut csr: [u8; 1] = [0];
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
csr[0].set_bit(channel as usize + 4, false);
|
||||
self.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn modify_channel(&mut self, channel: Channel, register: Register, data: &[u8]) -> Result<(), Error<InterfaceE>> {
|
||||
let mut csr: [u8; 1] = [0];
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
|
||||
let mut new_csr = csr;
|
||||
new_csr[0].set_bits(4..8, 0);
|
||||
new_csr[0].set_bit(4 + channel as usize, true);
|
||||
|
||||
self.interface.write(Register::CSR as u8, &new_csr)?;
|
||||
|
||||
self.interface.write(register as u8, &data)?;
|
||||
|
||||
// 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()?;
|
||||
self.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure the phase of a specified channel.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `channel` - The channel to configure the frequency of.
|
||||
/// * `phase_turns` - The desired phase offset in normalized turns.
|
||||
///
|
||||
/// Returns:
|
||||
/// The actual programmed phase offset of the channel in degrees.
|
||||
pub fn set_phase(&mut self, channel: Channel, phase_turns: f32) -> Result<f32, Error<InterfaceE>> {
|
||||
if phase_turns > 1.0 || phase_turns < 0.0 {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
let phase_offset: u16 = (phase_turns * 1u32.wrapping_shl(14) as f32) as u16;
|
||||
self.modify_channel(channel, Register::CPOW0, &phase_offset.to_be_bytes())?;
|
||||
Ok((phase_offset as f32 / 1u32.wrapping_shl(14) as f32) * 360.0)
|
||||
}
|
||||
|
||||
/// Configure the amplitude of a specified channel.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `channel` - The channel to configure the frequency of.
|
||||
/// * `amplitude` - A normalized amplitude setting [0, 1].
|
||||
///
|
||||
/// Returns:
|
||||
/// The actual normalized amplitude of the channel relative to full-scale range.
|
||||
pub fn set_amplitude(&mut self, channel: Channel, amplitude: f32) -> Result<f32, Error<InterfaceE>> {
|
||||
if amplitude < 0.0 || amplitude > 1.0 {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
let amplitude_control: u16 = (amplitude / 1u16.wrapping_shl(10) as f32) as u16;
|
||||
let mut acr: [u8; 3] = [0, amplitude_control.to_be_bytes()[0], amplitude_control.to_be_bytes()[1]];
|
||||
|
||||
// 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.
|
||||
acr[1].set_bit(4, amplitude_control >= 1u16.wrapping_shl(10));
|
||||
|
||||
self.modify_channel(channel, Register::ACR, &acr)?;
|
||||
|
||||
Ok(amplitude_control as f32 / 1_u16.wrapping_shl(10) as f32)
|
||||
}
|
||||
|
||||
/// Configure the frequency of a specified channel.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `channel` - The channel to configure the frequency of.
|
||||
/// * `frequency` - The desired output frequency in Hz.
|
||||
///
|
||||
/// Returns:
|
||||
/// The actual programmed frequency of the channel.
|
||||
pub fn set_frequency(&mut self, channel: Channel, frequency: f64) -> Result<f64, Error<InterfaceE>> {
|
||||
if frequency < 0.0 || frequency > self.system_clock_frequency() {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
// 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 = ((frequency as f64 / self.system_clock_frequency())
|
||||
* 1u64.wrapping_shl(32) as f64) as u32;
|
||||
|
||||
self.modify_channel(channel, Register::CFTW0, &tuning_word.to_be_bytes())?;
|
||||
Ok((tuning_word as f64 / 1u64.wrapping_shl(32) as f64) * self.system_clock_frequency())
|
||||
}
|
||||
}
|
|
@ -49,6 +49,9 @@ version = "0.6"
|
|||
features = ["ethernet", "proto-ipv4", "socket-tcp", "proto-ipv6"]
|
||||
default-features = false
|
||||
|
||||
[dependencies.ad9959]
|
||||
path = "../ad9959"
|
||||
|
||||
[dependencies.stm32h7-ethernet]
|
||||
git = "https://github.com/quartiq/stm32h7-ethernet.git"
|
||||
features = ["stm32h743v"]
|
||||
|
|
|
@ -28,6 +28,7 @@ extern crate panic_halt;
|
|||
extern crate log;
|
||||
|
||||
// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
|
||||
use asm_delay;
|
||||
use rtfm::cyccnt::{Instant, U32Ext};
|
||||
use cortex_m_rt::exception;
|
||||
use cortex_m;
|
||||
|
@ -48,6 +49,7 @@ use smoltcp as net;
|
|||
static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new();
|
||||
|
||||
mod eth;
|
||||
mod pounder;
|
||||
mod server;
|
||||
mod afe;
|
||||
|
||||
|
@ -129,6 +131,8 @@ const APP: () = {
|
|||
_eth_mac: ethernet::EthernetMAC,
|
||||
mac_addr: net::wire::EthernetAddress,
|
||||
|
||||
pounder: pounder::PounderDevices<asm_delay::AsmDelay>,
|
||||
|
||||
#[init([[0.; 5]; 2])]
|
||||
iir_state: [IIRState; 2],
|
||||
#[init([IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; 2])]
|
||||
|
@ -161,6 +165,8 @@ const APP: () = {
|
|||
|
||||
clocks.rb.d2ccip1r.modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q());
|
||||
|
||||
let mut delay = hal::delay::Delay::new(cp.SYST, clocks.clocks);
|
||||
|
||||
let gpioa = dp.GPIOA.split(&mut clocks.ahb4);
|
||||
let gpiob = dp.GPIOB.split(&mut clocks.ahb4);
|
||||
let gpioc = dp.GPIOC.split(&mut clocks.ahb4);
|
||||
|
@ -274,6 +280,88 @@ const APP: () = {
|
|||
spi
|
||||
};
|
||||
|
||||
let pounder_devices = {
|
||||
let ad9959 = {
|
||||
let qspi_interface = {
|
||||
// Instantiate the QUADSPI pins and peripheral interface.
|
||||
// TODO: Place these into a pins structure that is provided to the QSPI
|
||||
// constructor.
|
||||
let _qspi_clk = gpiob.pb2.into_alternate_af9();
|
||||
let _qspi_ncs = gpioc.pc11.into_alternate_af9();
|
||||
let _qspi_io0 = gpioe.pe7.into_alternate_af10();
|
||||
let _qspi_io1 = gpioe.pe8.into_alternate_af10();
|
||||
let _qspi_io2 = gpioe.pe9.into_alternate_af10();
|
||||
let _qspi_io3 = gpioe.pe10.into_alternate_af10();
|
||||
|
||||
let qspi = hal::qspi::Qspi::new(dp.QUADSPI, &mut clocks, 10.mhz()).unwrap();
|
||||
pounder::QspiInterface::new(qspi).unwrap()
|
||||
};
|
||||
|
||||
let mut reset_pin = gpioa.pa0.into_push_pull_output();
|
||||
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,
|
||||
ad9959::Mode::FourBitSerial,
|
||||
100_000_000,
|
||||
5).unwrap()
|
||||
};
|
||||
|
||||
let io_expander = {
|
||||
let sda = gpiob.pb7.into_alternate_af4().set_open_drain();
|
||||
let scl = gpiob.pb8.into_alternate_af4().set_open_drain();
|
||||
let i2c1 = dp.I2C1.i2c((scl, sda), 100.khz(), &clocks);
|
||||
mcp23017::MCP23017::default(i2c1).unwrap()
|
||||
};
|
||||
|
||||
let spi = {
|
||||
let spi_mosi = gpiod.pd7.into_alternate_af5();
|
||||
let spi_miso = gpioa.pa6.into_alternate_af5();
|
||||
let spi_sck = gpiog.pg11.into_alternate_af5();
|
||||
|
||||
let config = hal::spi::Config::new(hal::spi::Mode{
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.frame_size(8);
|
||||
|
||||
dp.SPI1.spi((spi_sck, spi_miso, spi_mosi), config, 25.mhz(), &clocks)
|
||||
};
|
||||
|
||||
let adc1 = {
|
||||
let mut adc = dp.ADC1.adc(&mut delay, &mut clocks);
|
||||
adc.calibrate();
|
||||
|
||||
adc.enable()
|
||||
};
|
||||
|
||||
let adc2 = {
|
||||
let mut adc = dp.ADC2.adc(&mut delay, &mut clocks);
|
||||
adc.calibrate();
|
||||
|
||||
adc.enable()
|
||||
};
|
||||
|
||||
let adc1_in_p = gpiof.pf11.into_analog();
|
||||
let adc2_in_p = gpiof.pf14.into_analog();
|
||||
|
||||
pounder::PounderDevices::new(io_expander,
|
||||
ad9959,
|
||||
spi,
|
||||
adc1,
|
||||
adc2,
|
||||
adc1_in_p,
|
||||
adc2_in_p).unwrap()
|
||||
};
|
||||
|
||||
let mut fp_led_0 = gpiod.pd5.into_push_pull_output();
|
||||
let mut fp_led_1 = gpiod.pd6.into_push_pull_output();
|
||||
let mut fp_led_2 = gpiod.pd12.into_push_pull_output();
|
||||
|
@ -380,6 +468,7 @@ const APP: () = {
|
|||
_afe2: afe2,
|
||||
|
||||
timer: timer2,
|
||||
pounder: pounder_devices,
|
||||
|
||||
eeprom_i2c: eeprom_i2c,
|
||||
net_interface: network_interface,
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
use super::error::Error;
|
||||
use super::DdsChannel;
|
||||
|
||||
pub trait AttenuatorInterface {
|
||||
fn modify(&mut self, attenuation: f32, channel: DdsChannel) -> Result<f32, Error> {
|
||||
if attenuation > 31.5 || attenuation < 0.0 {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
// Calculate the attenuation code to program into the attenuator. The attenuator uses a
|
||||
// code where the LSB is 0.5 dB.
|
||||
let attenuation_code = (attenuation * 2.0) as u8;
|
||||
|
||||
// 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(&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.wrapping_shl(2);
|
||||
self.write_all(&channels)?;
|
||||
|
||||
// Finally, latch the output of the updated channel to force it into an active state.
|
||||
self.latch(channel)?;
|
||||
|
||||
Ok(attenuation_code as f32 / 2.0)
|
||||
}
|
||||
|
||||
fn read(&mut self, channel: DdsChannel) -> Result<f32, Error> {
|
||||
let mut channels = [0_u8; 4];
|
||||
|
||||
// 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(&mut channels)?;
|
||||
self.write_all(&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
|
||||
// (before the shift) has the affect of transforming the bits of interest (and the
|
||||
// dont-care bits) into an active-high state and then masking off the don't care bits. If
|
||||
// the shift occurs before the inversion, the upper 2 bits (which would then be don't
|
||||
// care) would contain erroneous data.
|
||||
let attenuation_code = (!channels[channel as usize]).wrapping_shr(2);
|
||||
|
||||
// Convert the desired channel code into dB of attenuation.
|
||||
Ok(attenuation_code as f32 / 2.0)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), Error>;
|
||||
|
||||
fn latch(&mut self, channel: DdsChannel) -> Result<(), Error>;
|
||||
fn read_all(&mut self, channels: &mut [u8; 4]) -> Result<(), Error>;
|
||||
fn write_all(&mut self, channels: &[u8; 4]) -> Result<(), Error>;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Spi,
|
||||
I2c,
|
||||
DDS,
|
||||
Qspi,
|
||||
Bounds,
|
||||
InvalidAddress,
|
||||
InvalidChannel,
|
||||
Adc,
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
use mcp23017;
|
||||
use ad9959;
|
||||
|
||||
pub mod error;
|
||||
pub mod attenuators;
|
||||
mod rf_power;
|
||||
pub mod types;
|
||||
|
||||
use super::hal;
|
||||
|
||||
use error::Error;
|
||||
use attenuators::AttenuatorInterface;
|
||||
use types::{DdsChannel, InputChannel};
|
||||
use rf_power::PowerMeasurementInterface;
|
||||
|
||||
use embedded_hal::{
|
||||
blocking::spi::Transfer,
|
||||
adc::OneShot
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
const OSC_EN_N_PIN: u8 = 8 + 7;
|
||||
|
||||
const EXT_CLK_SEL_PIN: u8 = 8 + 6;
|
||||
|
||||
const ATT_RST_N_PIN: u8 = 8 + 5;
|
||||
|
||||
const ATT_LE0_PIN: u8 = 8 + 0;
|
||||
const ATT_LE1_PIN: u8 = 8 + 1;
|
||||
const ATT_LE2_PIN: u8 = 8 + 2;
|
||||
const ATT_LE3_PIN: u8 = 8 + 3;
|
||||
|
||||
pub struct QspiInterface {
|
||||
pub qspi: hal::qspi::Qspi,
|
||||
mode: ad9959::Mode,
|
||||
}
|
||||
|
||||
impl QspiInterface {
|
||||
pub fn new(mut qspi: hal::qspi::Qspi) -> Result<Self, Error> {
|
||||
qspi.configure_mode(hal::qspi::QspiMode::FourBit).map_err(|_| Error::Qspi)?;
|
||||
Ok(Self { qspi: qspi, mode: ad9959::Mode::SingleBitTwoWire })
|
||||
}
|
||||
}
|
||||
|
||||
impl ad9959::Interface for QspiInterface {
|
||||
type Error = Error;
|
||||
|
||||
fn configure_mode(&mut self, mode: ad9959::Mode) -> Result<(), Error> {
|
||||
self.mode = mode;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Error> {
|
||||
if (addr & 0x80) != 0 {
|
||||
return Err(Error::InvalidAddress);
|
||||
}
|
||||
|
||||
// The QSPI interface implementation always operates in 4-bit mode because the AD9959 uses
|
||||
// IO3 as SYNC_IO in some output modes. In order for writes to be successful, SYNC_IO must
|
||||
// be driven low. However, the QSPI peripheral forces IO3 high when operating in 1 or 2 bit
|
||||
// modes. As a result, any writes while in single- or dual-bit modes has to instead write
|
||||
// the data encoded into 4-bit QSPI data so that IO3 can be driven low.
|
||||
match self.mode {
|
||||
ad9959::Mode::SingleBitTwoWire => {
|
||||
// Encode the data into a 4-bit QSPI pattern.
|
||||
|
||||
// In 4-bit mode, we can send 2 bits of address and data per byte transfer. As
|
||||
// such, we need at least 4x more bytes than the length of data. To avoid dynamic
|
||||
// allocation, we assume the maximum transaction length for single-bit-two-wire is
|
||||
// 2 bytes.
|
||||
let mut encoded_data: [u8; 12] = [0; 12];
|
||||
|
||||
if (data.len() * 4) > (encoded_data.len() - 4) {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
// Encode the address into the first 4 bytes.
|
||||
for address_bit in 0..8 {
|
||||
let offset: u8 = {
|
||||
if address_bit % 2 == 0 {
|
||||
4
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
if addr & address_bit != 0 {
|
||||
encoded_data[(address_bit >> 1) as usize] |= 1 << offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the data into the remaining bytes.
|
||||
for byte_index in 0..data.len() {
|
||||
let byte = data[byte_index];
|
||||
for address_bit in 0..8 {
|
||||
let offset: u8 = {
|
||||
if address_bit % 2 == 0 {
|
||||
4
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
if byte & address_bit != 0 {
|
||||
encoded_data[(byte_index + 1) * 4 + (address_bit >> 1) as usize] |= 1 << offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (encoded_address, encoded_payload) = {
|
||||
let end_index = (1 + data.len()) * 4;
|
||||
(encoded_data[0], &encoded_data[1..end_index])
|
||||
};
|
||||
|
||||
self.qspi.write(encoded_address, &encoded_payload).map_err(|_| Error::Qspi)
|
||||
},
|
||||
ad9959::Mode::FourBitSerial => {
|
||||
self.qspi.write(addr, &data).map_err(|_| Error::Qspi)
|
||||
},
|
||||
_ => {
|
||||
Err(Error::Qspi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, addr: u8, mut dest: &mut [u8]) -> Result<(), Error> {
|
||||
if (addr & 0x80) != 0 {
|
||||
return Err(Error::InvalidAddress);
|
||||
}
|
||||
|
||||
// It is not possible to read data from the AD9959 in single bit two wire mode because the
|
||||
// QSPI interface assumes that data is always received on IO1.
|
||||
if self.mode == ad9959::Mode::SingleBitTwoWire {
|
||||
return Err(Error::Qspi);
|
||||
}
|
||||
|
||||
self.qspi.read(0x80_u8 | addr, &mut dest).map_err(|_| Error::Qspi)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PounderDevices<DELAY> {
|
||||
pub ad9959: ad9959::Ad9959<QspiInterface,
|
||||
DELAY,
|
||||
hal::gpio::gpiog::PG7<hal::gpio::Output<hal::gpio::PushPull>>>,
|
||||
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>,
|
||||
adc2: hal::adc::Adc<hal::stm32::ADC2, hal::adc::Enabled>,
|
||||
adc1_in_p: hal::gpio::gpiof::PF11<hal::gpio::Analog>,
|
||||
adc2_in_p: hal::gpio::gpiof::PF14<hal::gpio::Analog>,
|
||||
}
|
||||
|
||||
impl<DELAY> PounderDevices<DELAY>
|
||||
where
|
||||
DELAY: embedded_hal::blocking::delay::DelayMs<u8>,
|
||||
{
|
||||
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>>>,
|
||||
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>,
|
||||
adc1_in_p: hal::gpio::gpiof::PF11<hal::gpio::Analog>,
|
||||
adc2_in_p: hal::gpio::gpiof::PF14<hal::gpio::Analog>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut devices = Self {
|
||||
mcp23017,
|
||||
ad9959,
|
||||
attenuator_spi,
|
||||
adc1,
|
||||
adc2,
|
||||
adc1_in_p,
|
||||
adc2_in_p,
|
||||
};
|
||||
|
||||
// Configure power-on-default state for pounder. All LEDs are on, on-board oscillator
|
||||
// selected, attenuators out of reset.
|
||||
devices.mcp23017.write_gpio(mcp23017::Port::GPIOA, 0xF).map_err(|_| Error::I2c)?;
|
||||
devices.mcp23017.write_gpio(mcp23017::Port::GPIOB,
|
||||
1_u8.wrapping_shl(5)).map_err(|_| Error::I2c)?;
|
||||
devices.mcp23017.all_pin_mode(mcp23017::PinMode::OUTPUT).map_err(|_| Error::I2c)?;
|
||||
|
||||
// Select the on-board clock with a 5x prescaler (500MHz).
|
||||
devices.select_onboard_clock(5u8)?;
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
pub fn select_external_clock(&mut self, frequency: u32, prescaler: u8) -> Result<(), Error>{
|
||||
self.mcp23017.digital_write(EXT_CLK_SEL_PIN, true).map_err(|_| Error::I2c)?;
|
||||
self.ad9959.configure_system_clock(frequency, prescaler).map_err(|_| Error::DDS)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn select_onboard_clock(&mut self, prescaler: u8) -> Result<(), Error> {
|
||||
self.mcp23017.digital_write(EXT_CLK_SEL_PIN, false).map_err(|_| Error::I2c)?;
|
||||
self.ad9959.configure_system_clock(100_000_000, prescaler).map_err(|_| Error::DDS)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<DELAY> AttenuatorInterface for PounderDevices<DELAY>
|
||||
{
|
||||
fn reset(&mut self) -> Result<(), Error> {
|
||||
self.mcp23017.digital_write(ATT_RST_N_PIN, true).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.
|
||||
self.mcp23017.digital_write(ATT_RST_N_PIN, false).map_err(|_| Error::I2c)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn latch(&mut self, channel: DdsChannel) -> Result<(), Error> {
|
||||
let pin = match channel {
|
||||
DdsChannel::Zero => ATT_LE1_PIN,
|
||||
DdsChannel::One => ATT_LE0_PIN,
|
||||
DdsChannel::Two => ATT_LE3_PIN,
|
||||
DdsChannel::Three => ATT_LE2_PIN,
|
||||
};
|
||||
|
||||
self.mcp23017.digital_write(pin, true).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.
|
||||
self.mcp23017.digital_write(pin, false).map_err(|_| Error::I2c)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_all(&mut self, channels: &mut [u8; 4]) -> Result<(), Error> {
|
||||
self.attenuator_spi.transfer(channels).map_err(|_| Error::Spi)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_all(&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<DELAY> PowerMeasurementInterface for PounderDevices<DELAY> {
|
||||
fn sample_converter(&mut self, channel: InputChannel) -> Result<f32, Error> {
|
||||
let adc_scale = match channel {
|
||||
InputChannel::Zero => {
|
||||
let adc_reading: u32 = self.adc1.read(&mut self.adc1_in_p).map_err(|_| Error::Adc)?;
|
||||
adc_reading as f32 / self.adc1.max_sample() as f32
|
||||
},
|
||||
InputChannel::One => {
|
||||
let adc_reading: u32 = self.adc2.read(&mut self.adc2_in_p).map_err(|_| Error::Adc)?;
|
||||
adc_reading as f32 / self.adc2.max_sample() as f32
|
||||
},
|
||||
};
|
||||
|
||||
// Convert analog percentage to voltage.
|
||||
Ok(adc_scale * 3.3)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use super::Error;
|
||||
use super::InputChannel;
|
||||
|
||||
pub trait PowerMeasurementInterface {
|
||||
fn sample_converter(&mut self, channel: InputChannel) -> Result<f32, Error>;
|
||||
|
||||
fn measure_power(&mut self, channel: InputChannel) -> 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.
|
||||
Ok(analog_measurement / 0.0517)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum DdsChannel {
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum InputChannel {
|
||||
Zero,
|
||||
One,
|
||||
}
|
Loading…
Reference in New Issue