Adding work after functional DDS interface
This commit is contained in:
parent
8b4d5397be
commit
519fa86058
@ -9,7 +9,7 @@ members = [
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
incremental = false
|
||||
opt-level = 3
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
@ -19,7 +19,7 @@ use embedded_hal::{
|
||||
pub struct Ad9959<INTERFACE, DELAY, UPDATE> {
|
||||
interface: INTERFACE,
|
||||
delay: DELAY,
|
||||
reference_clock_frequency: u32,
|
||||
reference_clock_frequency: f32,
|
||||
system_clock_multiplier: u8,
|
||||
io_update: UPDATE,
|
||||
}
|
||||
@ -84,6 +84,7 @@ pub enum Channel {
|
||||
#[derive(Debug)]
|
||||
pub enum Error<InterfaceE> {
|
||||
Interface(InterfaceE),
|
||||
Check,
|
||||
Bounds,
|
||||
Pin,
|
||||
Frequency,
|
||||
@ -107,7 +108,7 @@ where
|
||||
io_update: UPDATE,
|
||||
delay: DELAY,
|
||||
desired_mode: Mode,
|
||||
clock_frequency: u32,
|
||||
clock_frequency: f32,
|
||||
multiplier: u8) -> Result<Self, Error<InterfaceE>>
|
||||
where
|
||||
RST: OutputPin,
|
||||
@ -135,7 +136,7 @@ where
|
||||
// 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)?;
|
||||
ad9959.interface.write(Register::CSR as u8, &csr)?;
|
||||
|
||||
// Configure the interface to the desired mode.
|
||||
ad9959.interface.configure_mode(Mode::FourBitSerial)?;
|
||||
@ -145,6 +146,13 @@ where
|
||||
|
||||
ad9959.interface.configure_mode(desired_mode)?;
|
||||
|
||||
// Read back the CSR to ensure it specifies the mode correctly.
|
||||
let mut updated_csr: [u8; 1] = [0];
|
||||
ad9959.interface.read(Register::CSR as u8, &mut updated_csr)?;
|
||||
if updated_csr[0] != csr[0] {
|
||||
return Err(Error::Check);
|
||||
}
|
||||
|
||||
// Set the clock frequency to configure the device as necessary.
|
||||
ad9959.configure_system_clock(clock_frequency, multiplier)?;
|
||||
Ok(ad9959)
|
||||
@ -164,21 +172,21 @@ where
|
||||
///
|
||||
/// 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.
|
||||
/// * `multiplier` - The frequency multiplier 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>>
|
||||
reference_clock_frequency: f32,
|
||||
multiplier: u8) -> Result<f64, Error<InterfaceE>>
|
||||
{
|
||||
self.reference_clock_frequency = reference_clock_frequency;
|
||||
|
||||
if prescaler != 1 && (prescaler > 20 || prescaler < 4) {
|
||||
if multiplier != 1 && (multiplier > 20 || multiplier < 4) {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
|
||||
let frequency = prescaler as f64 * self.reference_clock_frequency as f64;
|
||||
let frequency = multiplier as f64 * self.reference_clock_frequency as f64;
|
||||
if frequency > 500_000_000.0f64 {
|
||||
return Err(Error::Frequency);
|
||||
}
|
||||
@ -186,17 +194,28 @@ where
|
||||
// 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);
|
||||
fr1[0].set_bits(2..=6, multiplier);
|
||||
|
||||
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;
|
||||
self.system_clock_multiplier = multiplier;
|
||||
|
||||
Ok(self.system_clock_frequency())
|
||||
}
|
||||
|
||||
pub fn get_reference_clock_frequency(&self) -> f32 {
|
||||
self.reference_clock_frequency
|
||||
}
|
||||
|
||||
pub fn get_reference_clock_multiplier(&mut self) -> Result<u8, Error<InterfaceE>> {
|
||||
let mut fr1: [u8; 3] = [0, 0, 0];
|
||||
self.interface.read(Register::FR1 as u8, &mut fr1)?;
|
||||
|
||||
Ok(fr1[0].get_bits(2..=6) as u8)
|
||||
}
|
||||
|
||||
/// Perform a self-test of the communication interface.
|
||||
///
|
||||
/// Note:
|
||||
@ -262,6 +281,13 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_enabled(&mut self, channel: Channel) -> Result<bool, Error<InterfaceE>> {
|
||||
let mut csr: [u8; 1] = [0; 1];
|
||||
self.interface.read(Register::CSR as u8, &mut csr)?;
|
||||
|
||||
Ok(csr[0].get_bit(channel as usize + 4))
|
||||
}
|
||||
|
||||
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)?;
|
||||
@ -282,22 +308,48 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_channel(&mut self, channel: Channel, register: Register, mut data: &mut [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.read(register as u8, &mut data)?;
|
||||
|
||||
// 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.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.
|
||||
/// * `phase_turns` - The desired phase offset in turns.
|
||||
///
|
||||
/// Returns:
|
||||
/// The actual programmed phase offset of the channel in degrees.
|
||||
/// The actual programmed phase offset of the channel in turns.
|
||||
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 * (1 << 14) as f32) as u16 & 0x3FFFu16;
|
||||
|
||||
self.modify_channel(channel, Register::CPOW0, &phase_offset.to_be_bytes())?;
|
||||
|
||||
Ok((phase_offset as f32) / ((1 << 14) as f32))
|
||||
}
|
||||
|
||||
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)
|
||||
pub fn get_phase(&mut self, channel: Channel) -> Result<f32, Error<InterfaceE>> {
|
||||
let mut phase_offset: [u8; 2] = [0; 2];
|
||||
self.read_channel(channel, Register::CPOW0, &mut phase_offset)?;
|
||||
|
||||
let phase_offset = u16::from_be_bytes(phase_offset) & 0x3FFFu16;
|
||||
|
||||
Ok((phase_offset as f32) / ((1 << 14) as f32))
|
||||
}
|
||||
|
||||
/// Configure the amplitude of a specified channel.
|
||||
@ -313,17 +365,38 @@ where
|
||||
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]];
|
||||
let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16;
|
||||
|
||||
let mut acr: [u8; 3] = [0; 3];
|
||||
|
||||
// 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));
|
||||
if amplitude_control < (1 << 10) {
|
||||
|
||||
let masked_control = amplitude_control & 0x3FF;
|
||||
acr[1] = masked_control.to_be_bytes()[0];
|
||||
acr[2] = masked_control.to_be_bytes()[1];
|
||||
|
||||
// Enable the amplitude multiplier
|
||||
acr[1].set_bit(4, true);
|
||||
}
|
||||
|
||||
self.modify_channel(channel, Register::ACR, &acr)?;
|
||||
|
||||
Ok(amplitude_control as f32 / 1_u16.wrapping_shl(10) as f32)
|
||||
Ok(amplitude_control as f32 / (1 << 10) as f32)
|
||||
}
|
||||
|
||||
pub fn get_amplitude(&mut self, channel: Channel) -> Result<f32, Error<InterfaceE>> {
|
||||
let mut acr: [u8; 3] = [0; 3];
|
||||
self.read_channel(channel, Register::ACR, &mut acr)?;
|
||||
|
||||
if acr[1].get_bit(4) {
|
||||
let amplitude_control: u16 = (((acr[1] as u16) << 8) | (acr[2] as u16)) & 0x3FF;
|
||||
Ok(amplitude_control as f32 / (1 << 10) as f32)
|
||||
} else {
|
||||
Ok(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure the frequency of a specified channel.
|
||||
@ -347,4 +420,14 @@ where
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn get_frequency(&mut self, channel: Channel) -> Result<f64, Error<InterfaceE>> {
|
||||
// Read the frequency tuning word for the channel.
|
||||
let mut tuning_word: [u8; 4] = [0; 4];
|
||||
self.read_channel(channel, Register::CFTW0, &mut tuning_word)?;
|
||||
let tuning_word = u32::from_be_bytes(tuning_word);
|
||||
|
||||
// Convert the tuning word into a frequency.
|
||||
Ok(tuning_word as f64 * self.system_clock_frequency() / ((1u64 << 32)) as f64)
|
||||
}
|
||||
}
|
||||
|
@ -113,8 +113,8 @@ type AFE2 = afe::ProgrammableGainAmplifier<
|
||||
|
||||
macro_rules! route_request {
|
||||
($request:ident,
|
||||
readable_attributes: [$(($read_attribute:tt, $getter:tt)),*],
|
||||
modifiable_attributes: [$(($write_attribute:tt, $TYPE:ty, $setter:tt)),*]) => {
|
||||
readable_attributes: [$($read_attribute:tt: $getter:tt),*],
|
||||
modifiable_attributes: [$($write_attribute:tt: $TYPE:ty, $setter:tt),*]) => {
|
||||
match $request.req {
|
||||
server::AccessRequest::Read => {
|
||||
match $request.attribute {
|
||||
@ -126,17 +126,13 @@ macro_rules! route_request {
|
||||
"Failed to set attribute"),
|
||||
};
|
||||
|
||||
let encoded_data: String<U128> = match serde_json_core::to_string(&value) {
|
||||
let encoded_data: String<U256> = match serde_json_core::to_string(&value) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return server::Response::error($request.attribute,
|
||||
"Failed to encode attribute value"),
|
||||
};
|
||||
|
||||
// Encoding data into a string surrounds it with qutotations. Because this
|
||||
// value is then serialzed into another string, we remove the double
|
||||
// quotations because they cannot be properly escaped.
|
||||
server::Response::success($request.attribute,
|
||||
&encoded_data[1..encoded_data.len()-1])
|
||||
server::Response::success($request.attribute, &encoded_data)
|
||||
},
|
||||
)*
|
||||
_ => server::Response::error($request.attribute, "Unknown attribute")
|
||||
@ -146,22 +142,14 @@ macro_rules! route_request {
|
||||
match $request.attribute {
|
||||
$(
|
||||
$write_attribute => {
|
||||
// To avoid sending double quotations in the request, they are eliminated on
|
||||
// the sender side. However, to properly deserialize the data, quotes need
|
||||
// to be added back.
|
||||
let mut value: String<U128> = String::new();
|
||||
value.push('"').unwrap();
|
||||
value.push_str($request.value).unwrap();
|
||||
value.push('"').unwrap();
|
||||
|
||||
let new_value = match serde_json_core::from_str::<$TYPE>(value.as_str()) {
|
||||
let new_value = match serde_json_core::from_str::<$TYPE>(&$request.value) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return server::Response::error($request.attribute,
|
||||
"Failed to decode value"),
|
||||
};
|
||||
|
||||
match $setter(new_value) {
|
||||
Ok(_) => server::Response::success($request.attribute, $request.value),
|
||||
Ok(_) => server::Response::success($request.attribute, &$request.value),
|
||||
Err(_) => server::Response::error($request.attribute,
|
||||
"Failed to set attribute"),
|
||||
}
|
||||
@ -344,6 +332,17 @@ const APP: () = {
|
||||
spi
|
||||
};
|
||||
|
||||
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 = gpiog.pg4.into_push_pull_output();
|
||||
let mut fp_led_3 = gpiod.pd12.into_push_pull_output();
|
||||
|
||||
fp_led_0.set_low().unwrap();
|
||||
fp_led_1.set_low().unwrap();
|
||||
fp_led_2.set_low().unwrap();
|
||||
fp_led_3.set_low().unwrap();
|
||||
|
||||
|
||||
let pounder_devices = {
|
||||
let ad9959 = {
|
||||
let qspi_interface = {
|
||||
@ -375,7 +374,7 @@ const APP: () = {
|
||||
io_update,
|
||||
asm_delay,
|
||||
ad9959::Mode::FourBitSerial,
|
||||
100_000_000,
|
||||
100_000_000f32,
|
||||
5).unwrap()
|
||||
};
|
||||
|
||||
@ -426,16 +425,6 @@ const APP: () = {
|
||||
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 = gpiog.pg4.into_push_pull_output();
|
||||
let mut fp_led_3 = gpiod.pd12.into_push_pull_output();
|
||||
|
||||
fp_led_0.set_low().unwrap();
|
||||
fp_led_1.set_low().unwrap();
|
||||
fp_led_2.set_low().unwrap();
|
||||
fp_led_3.set_low().unwrap();
|
||||
|
||||
let mut eeprom_i2c = {
|
||||
let sda = gpiof.pf0.into_alternate_af4().set_open_drain();
|
||||
let scl = gpiof.pf1.into_alternate_af4().set_open_drain();
|
||||
@ -517,7 +506,7 @@ const APP: () = {
|
||||
hal::dma::DMAREQ_ID::TIM2_CH2);
|
||||
|
||||
// Configure timer 2 to trigger conversions for the ADC
|
||||
let mut timer2 = dp.TIM2.timer(500.khz(), &mut clocks);
|
||||
let mut timer2 = dp.TIM2.timer(50.khz(), &mut clocks);
|
||||
timer2.configure_channel(hal::timer::Channel::One, 0.25);
|
||||
timer2.configure_channel(hal::timer::Channel::Two, 0.75);
|
||||
|
||||
@ -572,7 +561,7 @@ const APP: () = {
|
||||
c.resources.dac1.send(output).unwrap();
|
||||
}
|
||||
|
||||
#[idle(resources=[net_interface, mac_addr, eth_mac, iir_state, iir_ch, afe1, afe2])]
|
||||
#[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afe1, afe2])]
|
||||
fn idle(mut c: idle::Context) -> ! {
|
||||
|
||||
let mut socket_set_entries: [_; 8] = Default::default();
|
||||
@ -617,7 +606,7 @@ const APP: () = {
|
||||
info!("Got request: {:?}", req);
|
||||
route_request!(req,
|
||||
readable_attributes: [
|
||||
("stabilizer/iir/state", (|| {
|
||||
"stabilizer/iir/state": (|| {
|
||||
let state = c.resources.iir_state.lock(|iir_state|
|
||||
server::Status {
|
||||
t: time,
|
||||
@ -628,13 +617,32 @@ const APP: () = {
|
||||
});
|
||||
|
||||
Ok::<server::Status, ()>(state)
|
||||
})),
|
||||
("stabilizer/afe1/gain", (|| c.resources.afe1.get_gain())),
|
||||
("stabilizer/afe2/gain", (|| c.resources.afe2.get_gain()))
|
||||
}),
|
||||
"stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()),
|
||||
"stabilizer/afe2/gain": (|| c.resources.afe2.get_gain()),
|
||||
"pounder/in0": (|| {
|
||||
c.resources.pounder.get_input_channel_state(
|
||||
pounder::Channel::In0)
|
||||
}),
|
||||
"pounder/in1": (|| {
|
||||
c.resources.pounder.get_input_channel_state(
|
||||
pounder::Channel::In1)
|
||||
}),
|
||||
"pounder/out0": (|| {
|
||||
c.resources.pounder.get_output_channel_state(
|
||||
pounder::Channel::Out0)
|
||||
}),
|
||||
"pounder/out1": (|| {
|
||||
c.resources.pounder.get_output_channel_state(
|
||||
pounder::Channel::Out1)
|
||||
}),
|
||||
"pounder/dds/clock": (|| {
|
||||
c.resources.pounder.get_dds_clock_config()
|
||||
})
|
||||
],
|
||||
|
||||
modifiable_attributes: [
|
||||
("stabilizer/iir1/state", server::IirRequest, (|req: server::IirRequest| {
|
||||
"stabilizer/iir1/state": server::IirRequest, (|req: server::IirRequest| {
|
||||
c.resources.iir_ch.lock(|iir_ch| {
|
||||
if req.channel > 1 {
|
||||
return Err(());
|
||||
@ -644,8 +652,8 @@ const APP: () = {
|
||||
|
||||
Ok::<server::IirRequest, ()>(req)
|
||||
})
|
||||
})),
|
||||
("stabilizer/iir2/state", server::IirRequest, (|req: server::IirRequest| {
|
||||
}),
|
||||
"stabilizer/iir2/state": server::IirRequest, (|req: server::IirRequest| {
|
||||
c.resources.iir_ch.lock(|iir_ch| {
|
||||
if req.channel > 1 {
|
||||
return Err(());
|
||||
@ -655,13 +663,32 @@ const APP: () = {
|
||||
|
||||
Ok::<server::IirRequest, ()>(req)
|
||||
})
|
||||
})),
|
||||
("stabilizer/afe1/gain", afe::Gain, (|gain| {
|
||||
}),
|
||||
"pounder/in0": pounder::ChannelState, (|state| {
|
||||
c.resources.pounder.set_channel_state(pounder::Channel::In0,
|
||||
state)
|
||||
}),
|
||||
"pounder/in1": pounder::ChannelState, (|state| {
|
||||
c.resources.pounder.set_channel_state(pounder::Channel::In1,
|
||||
state)
|
||||
}),
|
||||
"pounder/out0": pounder::ChannelState, (|state| {
|
||||
c.resources.pounder.set_channel_state(pounder::Channel::Out0,
|
||||
state)
|
||||
}),
|
||||
"pounder/out1": pounder::ChannelState, (|state| {
|
||||
c.resources.pounder.set_channel_state(pounder::Channel::Out1,
|
||||
state)
|
||||
}),
|
||||
"pounder/dds/clock": pounder::DdsClockConfig, (|config| {
|
||||
c.resources.pounder.configure_dds_clock(config)
|
||||
}),
|
||||
"stabilizer/afe1/gain": afe::Gain, (|gain| {
|
||||
Ok::<(), ()>(c.resources.afe1.set_gain(gain))
|
||||
})),
|
||||
("stabilizer/afe2/gain", afe::Gain, (|gain| {
|
||||
}),
|
||||
"stabilizer/afe2/gain": afe::Gain, (|gain| {
|
||||
Ok::<(), ()>(c.resources.afe2.set_gain(gain))
|
||||
}))
|
||||
})
|
||||
]
|
||||
)
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
use super::error::Error;
|
||||
use super::DdsChannel;
|
||||
use super::{Channel, Error};
|
||||
|
||||
pub trait AttenuatorInterface {
|
||||
fn modify(&mut self, attenuation: f32, channel: DdsChannel) -> Result<f32, Error> {
|
||||
fn set_attenuation(&mut self, channel: Channel, attenuation: f32) -> Result<f32, Error> {
|
||||
if attenuation > 31.5 || attenuation < 0.0 {
|
||||
return Err(Error::Bounds);
|
||||
}
|
||||
@ -14,28 +13,28 @@ 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(&mut channels)?;
|
||||
self.read_all_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.wrapping_shl(2);
|
||||
self.write_all(&channels)?;
|
||||
channels[channel as usize] = (!attenuation_code) << 2;
|
||||
self.write_all_attenuators(&channels)?;
|
||||
|
||||
// Finally, latch the output of the updated channel to force it into an active state.
|
||||
self.latch(channel)?;
|
||||
self.latch_attenuators(channel)?;
|
||||
|
||||
Ok(attenuation_code as f32 / 2.0)
|
||||
}
|
||||
|
||||
fn read(&mut self, channel: DdsChannel) -> Result<f32, Error> {
|
||||
fn get_attenuation(&mut self, channel: Channel) -> 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)?;
|
||||
self.read_all_attenuators(&mut channels)?;
|
||||
self.write_all_attenuators(&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
|
||||
@ -43,15 +42,15 @@ pub trait AttenuatorInterface {
|
||||
// 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);
|
||||
let attenuation_code = (!channels[channel as usize]) >> 2;
|
||||
|
||||
// Convert the desired channel code into dB of attenuation.
|
||||
Ok(attenuation_code as f32 / 2.0)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), Error>;
|
||||
fn reset_attenuators(&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>;
|
||||
fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error>;
|
||||
fn read_all_attenuators(&mut self, channels: &mut [u8; 4]) -> Result<(), Error>;
|
||||
fn write_all_attenuators(&mut self, channels: &[u8; 4]) -> Result<(), Error>;
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Spi,
|
||||
I2c,
|
||||
DDS,
|
||||
Qspi,
|
||||
Bounds,
|
||||
InvalidAddress,
|
||||
InvalidChannel,
|
||||
Adc,
|
||||
}
|
@ -1,16 +1,13 @@
|
||||
use mcp23017;
|
||||
use ad9959;
|
||||
|
||||
pub mod error;
|
||||
pub mod attenuators;
|
||||
use serde::{Serialize, Deserialize};
|
||||
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::{
|
||||
@ -18,17 +15,79 @@ use embedded_hal::{
|
||||
adc::OneShot
|
||||
};
|
||||
|
||||
const EXT_CLK_SEL_PIN: u8 = 8 + 7;
|
||||
#[allow(dead_code)]
|
||||
const OSC_EN_N_PIN: u8 = 8 + 7;
|
||||
|
||||
const EXT_CLK_SEL_PIN: u8 = 8 + 6;
|
||||
|
||||
const OSC_EN_N_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;
|
||||
const ATT_LE2_PIN: u8 = 8 + 2;
|
||||
const ATT_LE1_PIN: u8 = 8 + 1;
|
||||
const ATT_LE0_PIN: u8 = 8 + 0;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Error {
|
||||
Spi,
|
||||
I2c,
|
||||
Dds,
|
||||
Qspi,
|
||||
Bounds,
|
||||
InvalidAddress,
|
||||
InvalidChannel,
|
||||
Adc,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Channel {
|
||||
In0,
|
||||
In1,
|
||||
Out0,
|
||||
Out1,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct DdsChannelState {
|
||||
pub phase_offset: f32,
|
||||
pub frequency: f64,
|
||||
pub amplitude: f32,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct ChannelState {
|
||||
pub parameters: DdsChannelState,
|
||||
pub attenuation: f32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct InputChannelState {
|
||||
pub attenuation: f32,
|
||||
pub power: f32,
|
||||
pub mixer: DdsChannelState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct OutputChannelState {
|
||||
pub attenuation: f32,
|
||||
pub channel: DdsChannelState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct DdsClockConfig {
|
||||
pub multiplier: u8,
|
||||
pub reference_clock: f32,
|
||||
pub external_clock: bool,
|
||||
}
|
||||
|
||||
impl Into<ad9959::Channel> for Channel {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiInterface {
|
||||
pub qspi: hal::qspi::Qspi,
|
||||
@ -78,32 +137,40 @@ impl ad9959::Interface for QspiInterface {
|
||||
// Encode the address into the first 4 bytes.
|
||||
for address_bit in 0..8 {
|
||||
let offset: u8 = {
|
||||
if address_bit % 2 == 0 {
|
||||
if address_bit % 2 != 0 {
|
||||
4
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
if addr & address_bit != 0 {
|
||||
encoded_data[(address_bit >> 1) as usize] |= 1 << offset;
|
||||
// Encode MSB first. Least significant bits are placed at the most significant
|
||||
// byte.
|
||||
let byte_position = 3 - (address_bit >> 1) as usize;
|
||||
|
||||
if addr & (1 << address_bit) != 0 {
|
||||
encoded_data[byte_position] |= 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 {
|
||||
for bit in 0..8 {
|
||||
let offset: u8 = {
|
||||
if address_bit % 2 == 0 {
|
||||
if bit % 2 != 0 {
|
||||
4
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
if byte & address_bit != 0 {
|
||||
encoded_data[(byte_index + 1) * 4 + (address_bit >> 1) as usize] |= 1 << offset;
|
||||
// Encode MSB first. Least significant bits are placed at the most
|
||||
// significant byte.
|
||||
let byte_position = 3 - (bit >> 1) as usize;
|
||||
|
||||
if byte & (1 << bit) != 0 {
|
||||
encoded_data[(byte_index + 1) * 4 + byte_position] |= 1 << offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,9 +196,8 @@ impl ad9959::Interface for QspiInterface {
|
||||
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 {
|
||||
// This implementation only supports operation (read) in four-bit-serial mode.
|
||||
if self.mode != ad9959::Mode::FourBitSerial {
|
||||
return Err(Error::Qspi);
|
||||
}
|
||||
|
||||
@ -178,27 +244,100 @@ where
|
||||
|
||||
// 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.write_gpio(mcp23017::Port::GPIOA, 0x3F).map_err(|_| Error::I2c)?;
|
||||
devices.mcp23017.write_gpio(mcp23017::Port::GPIOB, 1 << 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)?;
|
||||
devices.select_onboard_clock(4u8)?;
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
pub fn select_external_clock(&mut self, frequency: u32, prescaler: u8) -> Result<(), Error>{
|
||||
fn select_external_clock(&mut self, frequency: f32, 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)?;
|
||||
self.ad9959.configure_system_clock(frequency, prescaler).map_err(|_| Error::Dds)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn select_onboard_clock(&mut self, prescaler: u8) -> Result<(), Error> {
|
||||
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)?;
|
||||
self.ad9959.configure_system_clock(100_000_000f32, prescaler).map_err(|_| Error::Dds)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure_dds_clock(&mut self, config: DdsClockConfig) -> Result<(), Error> {
|
||||
if config.external_clock {
|
||||
self.select_external_clock(config.reference_clock, config.multiplier)
|
||||
} else {
|
||||
self.select_onboard_clock(config.multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dds_clock_config(&mut self) -> Result<DdsClockConfig, Error> {
|
||||
let external_clock = self.mcp23017.digital_read(EXT_CLK_SEL_PIN).map_err(|_| Error::I2c)?;
|
||||
let multiplier = self.ad9959.get_reference_clock_multiplier().map_err(|_| Error::Dds)?;
|
||||
let reference_clock = self.ad9959.get_reference_clock_frequency();
|
||||
|
||||
Ok(DdsClockConfig{multiplier, reference_clock, external_clock})
|
||||
}
|
||||
|
||||
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: attenuation,
|
||||
power: power,
|
||||
mixer: channel_state
|
||||
})
|
||||
}
|
||||
_ => Err(Error::InvalidChannel),
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
let enabled = self.ad9959.is_enabled(channel.into()).map_err(|_| Error::Dds)?;
|
||||
|
||||
Ok(DdsChannelState {phase_offset, frequency, amplitude, enabled})
|
||||
}
|
||||
|
||||
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: attenuation,
|
||||
channel: channel_state,
|
||||
})
|
||||
}
|
||||
_ => Err(Error::InvalidChannel),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_channel_state(&mut self, channel: Channel, state: ChannelState) -> Result<(), Error> {
|
||||
self.ad9959.set_frequency(channel.into(), state.parameters.frequency).map_err(|_| Error::Dds)?;
|
||||
self.ad9959.set_phase(channel.into(), state.parameters.phase_offset).map_err(|_| Error::Dds)?;
|
||||
self.ad9959.set_amplitude(channel.into(), state.parameters.amplitude).map_err(|_| Error::Dds)?;
|
||||
|
||||
if state.parameters.enabled {
|
||||
self.ad9959.enable_channel(channel.into()).map_err(|_| Error::Dds)?;
|
||||
} else {
|
||||
self.ad9959.disable_channel(channel.into()).map_err(|_| Error::Dds)?;
|
||||
}
|
||||
|
||||
self.set_attenuation(channel, state.attenuation)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -206,21 +345,21 @@ where
|
||||
|
||||
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)?;
|
||||
fn reset_attenuators(&mut self) -> Result<(), Error> {
|
||||
self.mcp23017.digital_write(ATT_RST_N_PIN, false).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)?;
|
||||
self.mcp23017.digital_write(ATT_RST_N_PIN, true).map_err(|_| Error::I2c)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn latch(&mut self, channel: DdsChannel) -> Result<(), Error> {
|
||||
fn latch_attenuators(&mut self, channel: Channel) -> 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,
|
||||
Channel::In0 => ATT_LE0_PIN,
|
||||
Channel::In1 => ATT_LE2_PIN,
|
||||
Channel::Out0 => ATT_LE1_PIN,
|
||||
Channel::Out1 => ATT_LE3_PIN,
|
||||
};
|
||||
|
||||
self.mcp23017.digital_write(pin, true).map_err(|_| Error::I2c)?;
|
||||
@ -231,13 +370,13 @@ impl<DELAY> AttenuatorInterface for PounderDevices<DELAY>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_all(&mut self, channels: &mut [u8; 4]) -> Result<(), Error> {
|
||||
fn read_all_attenuators(&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> {
|
||||
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)?;
|
||||
@ -247,16 +386,17 @@ impl<DELAY> AttenuatorInterface for PounderDevices<DELAY>
|
||||
}
|
||||
|
||||
impl<DELAY> PowerMeasurementInterface for PounderDevices<DELAY> {
|
||||
fn sample_converter(&mut self, channel: InputChannel) -> Result<f32, Error> {
|
||||
fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error> {
|
||||
let adc_scale = match channel {
|
||||
InputChannel::Zero => {
|
||||
Channel::In0 => {
|
||||
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 => {
|
||||
Channel::In1 => {
|
||||
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
|
||||
},
|
||||
_ => return Err(Error::InvalidChannel),
|
||||
};
|
||||
|
||||
// Convert analog percentage to voltage.
|
||||
|
@ -1,10 +1,9 @@
|
||||
use super::Error;
|
||||
use super::InputChannel;
|
||||
use super::{Error, Channel};
|
||||
|
||||
pub trait PowerMeasurementInterface {
|
||||
fn sample_converter(&mut self, channel: InputChannel) -> Result<f32, Error>;
|
||||
fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error>;
|
||||
|
||||
fn measure_power(&mut self, channel: InputChannel) -> Result<f32, Error> {
|
||||
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
|
||||
|
@ -1,16 +0,0 @@
|
||||
|
||||
#[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,
|
||||
}
|
@ -27,10 +27,10 @@ pub enum AccessRequest {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Request<'a, 'b> {
|
||||
pub struct Request<'a> {
|
||||
pub req: AccessRequest,
|
||||
pub attribute: &'a str,
|
||||
pub value: &'b str,
|
||||
pub value: String<U256>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -42,24 +42,74 @@ pub struct IirRequest {
|
||||
#[derive(Serialize)]
|
||||
pub struct Response {
|
||||
code: i32,
|
||||
attribute: String<U128>,
|
||||
value: String<U128>,
|
||||
attribute: String<U256>,
|
||||
value: String<U256>,
|
||||
}
|
||||
|
||||
impl<'a> Request<'a> {
|
||||
pub fn restore_value(&mut self) {
|
||||
let mut new_value: String<U256> = String::new();
|
||||
for byte in self.value.as_str().chars() {
|
||||
if byte == '\'' {
|
||||
new_value.push('"').unwrap();
|
||||
} else {
|
||||
new_value.push(byte).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
self.value = new_value;
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
|
||||
fn sanitize_value(&mut self) {
|
||||
let mut new_value: String<U256> = String::new();
|
||||
for byte in self.value.as_str().chars() {
|
||||
if byte == '"' {
|
||||
new_value.push('\'').unwrap();
|
||||
} else {
|
||||
new_value.push(byte).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
self.value = new_value;
|
||||
}
|
||||
|
||||
fn wrap_and_sanitize_value(&mut self) {
|
||||
let mut new_value: String<U256> = String::new();
|
||||
new_value.push('\'').unwrap();
|
||||
for byte in self.value.as_str().chars() {
|
||||
if byte == '"' {
|
||||
new_value.push('\'').unwrap();
|
||||
} else {
|
||||
new_value.push(byte).unwrap();
|
||||
}
|
||||
}
|
||||
new_value.push('\'').unwrap();
|
||||
|
||||
self.value = new_value;
|
||||
}
|
||||
|
||||
pub fn success<'a, 'b>(attribute: &'a str, value: &'b str) -> Self
|
||||
{
|
||||
Self { code: 200, attribute: String::from(attribute), value: String::from(value)}
|
||||
let mut res = Self { code: 200, attribute: String::from(attribute), value: String::from(value)};
|
||||
res.sanitize_value();
|
||||
res
|
||||
}
|
||||
|
||||
pub fn error<'a, 'b>(attribute: &'a str, message: &'b str) -> Self
|
||||
{
|
||||
Self { code: 400, attribute: String::from(attribute), value: String::from(message)}
|
||||
let mut res = Self { code: 400, attribute: String::from(attribute), value: String::from(message)};
|
||||
res.wrap_and_sanitize_value();
|
||||
res
|
||||
}
|
||||
|
||||
pub fn custom<'a>(code: i32, message : &'a str) -> Self
|
||||
{
|
||||
Self { code: code, attribute: String::from(""), value: String::from(message)}
|
||||
let mut res = Self { code: code, attribute: String::from(""), value: String::from(message)};
|
||||
res.wrap_and_sanitize_value();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +123,7 @@ pub struct Status {
|
||||
}
|
||||
|
||||
pub fn json_reply<T: Serialize>(socket: &mut net::socket::TcpSocket, msg: &T) {
|
||||
let mut u: String<U128> = to_string(msg).unwrap();
|
||||
let mut u: String<U512> = to_string(msg).unwrap();
|
||||
u.push('\n').unwrap();
|
||||
socket.write_str(&u).unwrap();
|
||||
}
|
||||
@ -122,7 +172,8 @@ impl Server {
|
||||
} else {
|
||||
let r = from_slice::<Request>(&self.data[..self.data.len() - 1]);
|
||||
match r {
|
||||
Ok(res) => {
|
||||
Ok(mut res) => {
|
||||
res.restore_value();
|
||||
let response = f(&res);
|
||||
json_reply(socket, &response);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user