forked from M-Labs/humpback-dds
dds: more clk ctrl; scpi: sys_clk ctrl
This commit is contained in:
parent
331d1ff86f
commit
e1abf87351
|
@ -18,7 +18,7 @@ libm = "0.2.0"
|
||||||
embedded-nal = "0.1.0"
|
embedded-nal = "0.1.0"
|
||||||
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
||||||
heapless = "0.5.5"
|
heapless = "0.5.5"
|
||||||
arrayvec = { version = "0.5.1", default-features = false, features = ["array-sizes-33-128", "array-sizes-129-255"] }
|
arrayvec = { version = "0.5.1", default-features = false, features = [ "array-sizes-33-128", "array-sizes-129-255" ] }
|
||||||
|
|
||||||
# Logging and Panicking
|
# Logging and Panicking
|
||||||
panic-itm = "0.4.1"
|
panic-itm = "0.4.1"
|
||||||
|
@ -31,7 +31,7 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
|
||||||
git = "https://github.com/occheung/scpi-rs"
|
git = "https://github.com/occheung/scpi-rs"
|
||||||
branch = "issue-4"
|
branch = "issue-4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [ "build-info", "unit-frequency" ]
|
features = [ "build-info", "unit-frequency", "unit-angle" ]
|
||||||
|
|
||||||
# Use below SCPI dependency when need to modify SCPI fork offline
|
# Use below SCPI dependency when need to modify SCPI fork offline
|
||||||
# [dependencies.scpi]
|
# [dependencies.scpi]
|
||||||
|
|
|
@ -47,13 +47,15 @@ use firmware::{
|
||||||
Channel1SwitchCommand,
|
Channel1SwitchCommand,
|
||||||
Channel2SwitchCommand,
|
Channel2SwitchCommand,
|
||||||
Channel3SwitchCommand,
|
Channel3SwitchCommand,
|
||||||
|
Channel0SystemClockCommand,
|
||||||
Channel0AttenuationCommand,
|
Channel0AttenuationCommand,
|
||||||
Channel1AttenuationCommand,
|
Channel1AttenuationCommand,
|
||||||
Channel2AttenuationCommand,
|
Channel2AttenuationCommand,
|
||||||
Channel3AttenuationCommand,
|
Channel3AttenuationCommand,
|
||||||
ClockSourceCommand,
|
ClockSourceCommand,
|
||||||
ClockDivisionCommand,
|
ClockDivisionCommand,
|
||||||
ProfileCommand
|
ProfileCommand,
|
||||||
|
Channel0Profile0Singletone
|
||||||
},
|
},
|
||||||
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
||||||
};
|
};
|
||||||
|
@ -208,8 +210,7 @@ fn main() -> ! {
|
||||||
let switch = CPLD::new(spi, (cs0, cs1, cs2), io_update);
|
let switch = CPLD::new(spi, (cs0, cs1, cs2), io_update);
|
||||||
let parts = switch.split();
|
let parts = switch.split();
|
||||||
let mut urukul = Urukul::new(
|
let mut urukul = Urukul::new(
|
||||||
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7,
|
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7
|
||||||
[25_000_000.0, 25_000_000.0, 25_000_000.0, 25_000_000.0]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Setup ethernet pins
|
// Setup ethernet pins
|
||||||
|
|
|
@ -44,6 +44,7 @@ use firmware::{
|
||||||
ClockSourceCommand,
|
ClockSourceCommand,
|
||||||
ClockDivisionCommand,
|
ClockDivisionCommand,
|
||||||
ProfileCommand,
|
ProfileCommand,
|
||||||
|
Channel0Profile0Singletone
|
||||||
},
|
},
|
||||||
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
||||||
};
|
};
|
||||||
|
@ -209,8 +210,7 @@ fn main() -> ! {
|
||||||
let parts = cpld.split();
|
let parts = cpld.split();
|
||||||
|
|
||||||
let mut urukul = Urukul::new(
|
let mut urukul = Urukul::new(
|
||||||
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7,
|
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7
|
||||||
[25_000_000.0, 25_000_000.0, 25_000_000.0, 25_000_000.0]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
cp.SCB.invalidate_icache();
|
cp.SCB.invalidate_icache();
|
||||||
|
|
20
src/dds.rs
20
src/dds.rs
|
@ -216,6 +216,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change sys_clk frequency, method to be determined
|
||||||
|
pub fn set_sys_clk_frequency(&mut self, f_sys_clk: f64) -> Result<(), Error<E>> {
|
||||||
|
// If f_sys_clk is exactly the same as f_ref_clk, then invoke enable_normal_ref_clk
|
||||||
|
if f_sys_clk == self.f_ref_clk {
|
||||||
|
self.enable_normal_ref_clk()
|
||||||
|
}
|
||||||
|
// Otherwise, if the requested sys_clk is half of ref_clk, invoke enable_divided_ref_clk
|
||||||
|
else if f_sys_clk == (self.f_ref_clk / 2.0) {
|
||||||
|
self.enable_divided_ref_clk()
|
||||||
|
}
|
||||||
|
// Finally, try enabling PLL
|
||||||
|
else {
|
||||||
|
self.enable_pll(f_sys_clk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn get_VCO_no(&mut self, f_sys_clk: f64, divider: u8) -> Result<u8, Error<E>> {
|
fn get_VCO_no(&mut self, f_sys_clk: f64, divider: u8) -> Result<u8, Error<E>> {
|
||||||
// Select a VCO
|
// Select a VCO
|
||||||
|
@ -310,8 +326,8 @@ where
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set a single tone profile
|
* Set a single tone profile
|
||||||
* Phase: Expressed in positive degree
|
* Phase: Expressed in positive degree, i.e. [0.0, 360.0)
|
||||||
* Frequency: Must be integer
|
* Frequency: Must be non-negative
|
||||||
* Amplitude: In a scale from 0 to 1, taking float
|
* Amplitude: In a scale from 0 to 1, taking float
|
||||||
*/
|
*/
|
||||||
pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
||||||
|
|
50
src/lib.rs
50
src/lib.rs
|
@ -72,6 +72,7 @@ pub struct Urukul<SPI> {
|
||||||
config_register: ConfigRegister<SPI>,
|
config_register: ConfigRegister<SPI>,
|
||||||
attenuator: Attenuator<SPI>,
|
attenuator: Attenuator<SPI>,
|
||||||
dds: [DDS<SPI>; 4],
|
dds: [DDS<SPI>; 4],
|
||||||
|
f_master_clk: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SPI, E> Urukul<SPI>
|
impl<SPI, E> Urukul<SPI>
|
||||||
|
@ -82,17 +83,21 @@ where
|
||||||
* Master constructor for the entire Urukul device
|
* Master constructor for the entire Urukul device
|
||||||
* Supply 7 SPI channels to Urukul and 4 reference clock frequencies
|
* Supply 7 SPI channels to Urukul and 4 reference clock frequencies
|
||||||
*/
|
*/
|
||||||
pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI, f_ref_clks: [f64; 4]) -> Self {
|
pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI) -> Self {
|
||||||
// Construct Urukul
|
// Construct Urukul
|
||||||
Urukul {
|
Urukul {
|
||||||
config_register: ConfigRegister::new(spi1),
|
config_register: ConfigRegister::new(spi1),
|
||||||
attenuator: Attenuator::new(spi2),
|
attenuator: Attenuator::new(spi2),
|
||||||
|
// Create 4 DDS instances with fixed 25MHz clock
|
||||||
|
// Counter-intuitive to assign urukul clock before having a urukul
|
||||||
dds: [
|
dds: [
|
||||||
DDS::new(spi4, f_ref_clks[1]),
|
DDS::new(spi4, 25_000_000.0),
|
||||||
DDS::new(spi5, f_ref_clks[1]),
|
DDS::new(spi5, 25_000_000.0),
|
||||||
DDS::new(spi6, f_ref_clks[2]),
|
DDS::new(spi6, 25_000_000.0),
|
||||||
DDS::new(spi7, f_ref_clks[3]),
|
DDS::new(spi7, 25_000_000.0),
|
||||||
],
|
],
|
||||||
|
// Default clock selection: OSC, fixed 100MHz speed
|
||||||
|
f_master_clk: 100_000_000.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +133,11 @@ where
|
||||||
for chip_no in 0..4 {
|
for chip_no in 0..4 {
|
||||||
self.dds[chip_no].init()?;
|
self.dds[chip_no].init()?;
|
||||||
}
|
}
|
||||||
|
// Clock tree reset. OSC clock source by default
|
||||||
// Clock tree reset. CPLD divides clock frequency by 4 by default.
|
self.f_master_clk = 100_000_000.0;
|
||||||
|
// CPLD divides clock frequency by 4 by default.
|
||||||
for chip_no in 0..4 {
|
for chip_no in 0..4 {
|
||||||
self.dds[chip_no].set_ref_clk_frequency(25_000_000.0)?;
|
self.dds[chip_no].set_ref_clk_frequency(self.f_master_clk / 4.0)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -158,6 +164,8 @@ pub trait UrukulTraits {
|
||||||
fn set_clock_division(&mut self, division: u8) -> Result<(), Self::Error>;
|
fn set_clock_division(&mut self, division: u8) -> Result<(), Self::Error>;
|
||||||
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error>;
|
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error>;
|
||||||
fn set_profile(&mut self, profile: u8) -> Result<(), Self::Error>;
|
fn set_profile(&mut self, profile: u8) -> Result<(), Self::Error>;
|
||||||
|
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Self::Error>;
|
||||||
|
fn set_channel_sys_clk(&mut self, channel: u8, sys_clk: f64) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SPI, E> UrukulTraits for Urukul<SPI>
|
impl<SPI, E> UrukulTraits for Urukul<SPI>
|
||||||
|
@ -207,13 +215,16 @@ where
|
||||||
(CFGMask::CLK_SEL0, 1),
|
(CFGMask::CLK_SEL0, 1),
|
||||||
]),
|
]),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
// Save the new master clock frequency
|
||||||
|
self.f_master_clk = frequency;
|
||||||
|
|
||||||
// Calculate reference clock frequency after clock division from configuration register
|
// Calculate reference clock frequency after clock division from configuration register
|
||||||
let frequency = frequency / (self.config_register.get_configuration(CFGMask::DIV) as f64);
|
let f_ref_clk = self.f_master_clk / (self.config_register.get_configuration(CFGMask::DIV) as f64);
|
||||||
|
|
||||||
// Update all DDS chips on reference clock frequency
|
// Update all DDS chips on reference clock frequency
|
||||||
for dds_channel in 0..4 {
|
for dds_channel in 0..4 {
|
||||||
self.dds[dds_channel].set_ref_clk_frequency(frequency)?;
|
self.dds[dds_channel].set_ref_clk_frequency(f_ref_clk)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -230,7 +241,16 @@ where
|
||||||
(CFGMask::DIV, 3),
|
(CFGMask::DIV, 3),
|
||||||
]),
|
]),
|
||||||
_ => Err(Error::ParameterError),
|
_ => Err(Error::ParameterError),
|
||||||
}.map(|_| ())
|
}?;
|
||||||
|
|
||||||
|
// Calculate reference clock frequency after clock division from configuration register
|
||||||
|
let f_ref_clk = self.f_master_clk / (division as f64);
|
||||||
|
|
||||||
|
// Update all DDS chips on reference clock frequency
|
||||||
|
for dds_channel in 0..4 {
|
||||||
|
self.dds[dds_channel].set_ref_clk_frequency(f_ref_clk)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error> {
|
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error> {
|
||||||
|
@ -242,4 +262,12 @@ where
|
||||||
(CFGMask::PROFILE, profile.into())
|
(CFGMask::PROFILE, profile.into())
|
||||||
]).map(|_| ())
|
]).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Self::Error> {
|
||||||
|
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Self::Error> {
|
||||||
|
self.dds[usize::from(channel)].set_sys_clk_frequency(f_sys_clk)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
106
src/main.rs
106
src/main.rs
|
@ -112,7 +112,7 @@ fn main() -> ! {
|
||||||
|
|
||||||
let mut config = ConfigRegister::new(parts.spi1);
|
let mut config = ConfigRegister::new(parts.spi1);
|
||||||
let mut att = Attenuator::new(parts.spi2);
|
let mut att = Attenuator::new(parts.spi2);
|
||||||
let mut dds0 = DDS::new(parts.spi4, 25_000_000);
|
let mut dds0 = DDS::new(parts.spi4, 25_000_000.0);
|
||||||
|
|
||||||
// Reset all DDS, set CLK_SEL to 0
|
// Reset all DDS, set CLK_SEL to 0
|
||||||
config.set_configurations(&mut [
|
config.set_configurations(&mut [
|
||||||
|
@ -135,75 +135,75 @@ fn main() -> ! {
|
||||||
(DDSCFRMask::READ_EFFECTIVE_FTW, 1),
|
(DDSCFRMask::READ_EFFECTIVE_FTW, 1),
|
||||||
]).unwrap();
|
]).unwrap();
|
||||||
|
|
||||||
dds0.enable_pll(1_000_000_000).unwrap();
|
dds0.set_sys_clk_frequency(1_000_000_000.0).unwrap();
|
||||||
|
|
||||||
// Attenuator
|
// Attenuator
|
||||||
att.set_attenuation([
|
att.set_attenuation([
|
||||||
0.0, 31.5, 24.0, 0.0
|
5.0, 31.5, 24.0, 0.0
|
||||||
]).unwrap();
|
]).unwrap();
|
||||||
|
|
||||||
dds0.set_single_tone_profile(1, 10_000_000, 0.0, 0.5).unwrap();
|
dds0.set_single_tone_profile(1, 10_000_000.0, 0.0, 0.5).unwrap();
|
||||||
config.set_configurations(&mut [
|
config.set_configurations(&mut [
|
||||||
(CFGMask::PROFILE, 1),
|
(CFGMask::PROFILE, 1),
|
||||||
]).unwrap();
|
]).unwrap();
|
||||||
|
|
||||||
// Setup RAM configuration
|
// // Setup RAM configuration
|
||||||
dds0.set_configurations(&mut [
|
// dds0.set_configurations(&mut [
|
||||||
(DDSCFRMask::RAM_ENABLE, 1),
|
// (DDSCFRMask::RAM_ENABLE, 1),
|
||||||
(DDSCFRMask::RAM_PLAYBACK_DST, 2),
|
// (DDSCFRMask::RAM_PLAYBACK_DST, 2),
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
// Configure RAM profile 0
|
// // Configure RAM profile 0
|
||||||
dds0.write_register(0x0E, &mut [
|
// dds0.write_register(0x0E, &mut [
|
||||||
0x00, // Open
|
// 0x00, // Open
|
||||||
0x09, 0xC4, // Address step rate (2500)
|
// 0x09, 0xC4, // Address step rate (2500)
|
||||||
0xFF, 0xC0, // End at address 1023
|
// 0xFF, 0xC0, // End at address 1023
|
||||||
0x00, 0x00, // Start at address 0
|
// 0x00, 0x00, // Start at address 0
|
||||||
0x04, // Recirculate mode
|
// 0x04, // Recirculate mode
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
debug!("{:#X?}", dds0.read_register(0x0E, &mut[
|
// debug!("{:#X?}", dds0.read_register(0x0E, &mut[
|
||||||
0x00, 0x00, 0x00, 0x00,
|
// 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00,
|
// 0x00, 0x00, 0x00, 0x00,
|
||||||
]).unwrap());
|
// ]).unwrap());
|
||||||
|
|
||||||
// Choose profile 0
|
// // Choose profile 0
|
||||||
config.set_configurations(&mut [
|
// config.set_configurations(&mut [
|
||||||
(CFGMask::PROFILE, 0),
|
// (CFGMask::PROFILE, 0),
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
// Set RAM to be amplitudes, disable RAM momentarily
|
// // Set RAM to be amplitudes, disable RAM momentarily
|
||||||
dds0.set_configurations(&mut [
|
// dds0.set_configurations(&mut [
|
||||||
(DDSCFRMask::RAM_PLAYBACK_DST, 0),
|
// (DDSCFRMask::RAM_PLAYBACK_DST, 0),
|
||||||
(DDSCFRMask::RAM_ENABLE, 0),
|
// (DDSCFRMask::RAM_ENABLE, 0),
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
let mut ram_data: [u8; ((1024 * 4) + 1)] = [0; (1024 * 4) + 1];
|
// let mut ram_data: [u8; ((1024 * 4) + 1)] = [0; (1024 * 4) + 1];
|
||||||
ram_data[0] = 0x16;
|
// ram_data[0] = 0x16;
|
||||||
for index in 0..1024 {
|
// for index in 0..1024 {
|
||||||
if index % 2 == 1 {
|
// if index % 2 == 1 {
|
||||||
ram_data[(index * 4) + 1] = 0x3F;
|
// ram_data[(index * 4) + 1] = 0x3F;
|
||||||
ram_data[(index * 4) + 2] = 0xFF;
|
// ram_data[(index * 4) + 2] = 0xFF;
|
||||||
} else {
|
// } else {
|
||||||
ram_data[(index * 4) + 1] = 0x00;
|
// ram_data[(index * 4) + 1] = 0x00;
|
||||||
ram_data[(index * 4) + 2] = 0x00;
|
// ram_data[(index * 4) + 2] = 0x00;
|
||||||
}
|
// }
|
||||||
// ram_data[(index * 4) + 1] = ((index >> 2) & 0xFF) as u8;
|
// // ram_data[(index * 4) + 1] = ((index >> 2) & 0xFF) as u8;
|
||||||
// ram_data[(index * 4) + 2] = ((index & 0x03) << 6) as u8;
|
// // ram_data[(index * 4) + 2] = ((index & 0x03) << 6) as u8;
|
||||||
}
|
// }
|
||||||
dds0.transfer(&mut ram_data).unwrap();
|
// dds0.transfer(&mut ram_data).unwrap();
|
||||||
|
|
||||||
config.set_configurations(&mut [
|
// config.set_configurations(&mut [
|
||||||
(CFGMask::PROFILE, 1),
|
// (CFGMask::PROFILE, 1),
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
config.set_configurations(&mut [
|
// config.set_configurations(&mut [
|
||||||
(CFGMask::PROFILE, 0),
|
// (CFGMask::PROFILE, 0),
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
dds0.set_configurations(&mut [
|
// dds0.set_configurations(&mut [
|
||||||
(DDSCFRMask::RAM_ENABLE, 1),
|
// (DDSCFRMask::RAM_ENABLE, 1),
|
||||||
]).unwrap();
|
// ]).unwrap();
|
||||||
|
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
87
src/scpi.rs
87
src/scpi.rs
|
@ -13,6 +13,7 @@ use scpi::{
|
||||||
};
|
};
|
||||||
use scpi::suffix::{Amplitude, Db};
|
use scpi::suffix::{Amplitude, Db};
|
||||||
use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency};
|
use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency};
|
||||||
|
use uom::si::angle::{degree, gon, minute as aminute, radian, revolution, Angle};
|
||||||
use uom::si::{f32, f64};
|
use uom::si::{f32, f64};
|
||||||
|
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
|
@ -116,7 +117,11 @@ macro_rules! scpi_tree {
|
||||||
scpi_root!(
|
scpi_root!(
|
||||||
"CHANNEL0" => {
|
"CHANNEL0" => {
|
||||||
"SWitch" => Channel0SwitchCommand,
|
"SWitch" => Channel0SwitchCommand,
|
||||||
"Attenuation" => Channel0AttenuationCommand
|
"Attenuation" => Channel0AttenuationCommand,
|
||||||
|
"SYSCLOCK" => Channel0SystemClockCommand,
|
||||||
|
"PROFILE0" => {
|
||||||
|
"SINGLEtone" => Channel0Profile0Singletone
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"CHANNEL1" => {
|
"CHANNEL1" => {
|
||||||
"SWitch" => Channel1SwitchCommand,
|
"SWitch" => Channel1SwitchCommand,
|
||||||
|
@ -164,11 +169,16 @@ pub struct Channel2SwitchCommand {}
|
||||||
pub struct Channel3SwitchCommand {}
|
pub struct Channel3SwitchCommand {}
|
||||||
pub struct ClockSourceCommand {}
|
pub struct ClockSourceCommand {}
|
||||||
pub struct ClockDivisionCommand {}
|
pub struct ClockDivisionCommand {}
|
||||||
|
pub struct Channel0SystemClockCommand {}
|
||||||
pub struct Channel0AttenuationCommand {}
|
pub struct Channel0AttenuationCommand {}
|
||||||
pub struct Channel1AttenuationCommand {}
|
pub struct Channel1AttenuationCommand {}
|
||||||
pub struct Channel2AttenuationCommand {}
|
pub struct Channel2AttenuationCommand {}
|
||||||
pub struct Channel3AttenuationCommand {}
|
pub struct Channel3AttenuationCommand {}
|
||||||
pub struct ProfileCommand {}
|
pub struct ProfileCommand {}
|
||||||
|
pub struct Channel0Profile0Singletone {}
|
||||||
|
pub struct Channel0Profile0SingletoneFrequency {}
|
||||||
|
pub struct Channel0Profile0SingletonePhase {}
|
||||||
|
pub struct Channel0Profile0SingletoneAmplitude {}
|
||||||
|
|
||||||
impl<T: Device + UrukulTraits> Command<T> for Channel0SwitchCommand {
|
impl<T: Device + UrukulTraits> Command<T> for Channel0SwitchCommand {
|
||||||
nquery!();
|
nquery!();
|
||||||
|
@ -253,7 +263,7 @@ impl<T:Device + UrukulTraits> Command<T> for ClockSourceCommand {
|
||||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
trace!("Received frequency: {:?}", frequency);
|
trace!("Received master clock frequency: {:?}", frequency);
|
||||||
|
|
||||||
let clock_source = match s_str {
|
let clock_source = match s_str {
|
||||||
source if source.eq_ignore_ascii_case("OSC") => {
|
source if source.eq_ignore_ascii_case("OSC") => {
|
||||||
|
@ -302,6 +312,28 @@ impl<T:Device + UrukulTraits> Command<T> for ClockDivisionCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T:Device + UrukulTraits> Command<T> for Channel0SystemClockCommand {
|
||||||
|
nquery!();
|
||||||
|
|
||||||
|
// Param: <frequency>
|
||||||
|
// The exact method of generating this frequency is auto-decided
|
||||||
|
// The process is delegated to individual DDS chip
|
||||||
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||||
|
|
||||||
|
let frequency: f64::Frequency = args.next_data(true)?
|
||||||
|
.map_or(Ok(f64::Frequency::new::<hertz>(0.0)), |t| {
|
||||||
|
t.numeric(|s| match s {
|
||||||
|
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||||
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
trace!("Received channel 0 system clock frequency: {:?}", frequency);
|
||||||
|
|
||||||
|
// Setup sys_clk through urukul interface
|
||||||
|
context.device.set_channel_sys_clk(0, frequency.get::<hertz>()).map_err(|_| Error::new(ErrorCode::IllegalParameterValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T:Device + UrukulTraits> Command<T> for Channel0AttenuationCommand {
|
impl<T:Device + UrukulTraits> Command<T> for Channel0AttenuationCommand {
|
||||||
nquery!();
|
nquery!();
|
||||||
|
|
||||||
|
@ -376,6 +408,57 @@ impl<T:Device + UrukulTraits> Command<T> for ProfileCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0Singletone {
|
||||||
|
nquery!();
|
||||||
|
|
||||||
|
// Params: frequency, phase, amplitude (all mandatory)
|
||||||
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||||
|
|
||||||
|
// Read output frequency
|
||||||
|
let frequency: f64::Frequency = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||||
|
t.numeric(|s| match s {
|
||||||
|
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||||
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency);
|
||||||
|
// Handle negative frequency
|
||||||
|
if frequency.get::<hertz>() < 0.0 {
|
||||||
|
return Err(ErrorCode::DataOutOfRange.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read phase offset
|
||||||
|
let phase: f64::Angle = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||||
|
t.numeric(
|
||||||
|
|s| match s {
|
||||||
|
NumericValues::Default => Ok(f64::Angle::new::<degree>(0.0)),
|
||||||
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
trace!("Received channel 0 profile 0 output single tone phase offset: {:?}", phase);
|
||||||
|
// Handle out-of-bound phase offset
|
||||||
|
if phase.get::<degree>() < 0.0 || phase.get::<degree>() >= 360.0 {
|
||||||
|
return Err(ErrorCode::DataOutOfRange.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read amplitude offset
|
||||||
|
let amplitude: f64 = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::MissingParameter)),
|
||||||
|
|token| token.try_into())?;
|
||||||
|
trace!("Received channel 0 profile 0 output single tone amplitude offset: {:?}", amplitude);
|
||||||
|
// Handle out-of-bound phase offset
|
||||||
|
if amplitude < 0.0 || amplitude > 1.0 {
|
||||||
|
return Err(ErrorCode::DataOutOfRange.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Setup single tone on DDS
|
||||||
|
context.device.set_channel_single_tone_profile(0, 0, frequency.get::<hertz>(), phase.get::<degree>(), amplitude)
|
||||||
|
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implement "Device" trait from SCPI
|
* Implement "Device" trait from SCPI
|
||||||
* TODO: Implement mandatory commands
|
* TODO: Implement mandatory commands
|
||||||
|
|
|
@ -5,8 +5,8 @@ use log::{trace, info};
|
||||||
use arrayvec::{ArrayVec};
|
use arrayvec::{ArrayVec};
|
||||||
|
|
||||||
pub trait MqttScpiTranslator {
|
pub trait MqttScpiTranslator {
|
||||||
// Convert an MQTT publish message into SCPI compatible command
|
// Unwrap an MQTT publish message into SCPI compatible command
|
||||||
// The argument part/ MQTT message must follow SCPI standard for parameter formatting
|
// The command part/ MQTT message must follow SCPI standard for parameter formatting
|
||||||
fn run_with_mqtt<FMT: Formatter>(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>;
|
fn run_with_mqtt<FMT: Formatter>(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,27 +20,27 @@ impl<'a, T: Device> MqttScpiTranslator for Context<'a, T> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let command_topic = topic.strip_prefix("Urukul/Control/")
|
// let command_topic = topic.strip_prefix("Urukul/Control")
|
||||||
.unwrap_or("");
|
// .unwrap_or("");
|
||||||
|
|
||||||
// Create a fixed-size buffer to handle slice operation
|
// Create a fixed-size buffer to handle slice operation
|
||||||
let mut buffer = ArrayVec::<[u8; 1024]>::new();
|
let mut buffer = ArrayVec::<[u8; 1024]>::new();
|
||||||
|
|
||||||
// Copy MQTT topic, convert it into SCPI header format
|
// // Copy MQTT topic, convert it into SCPI header format
|
||||||
for i in command_topic.chars() {
|
// for i in command_topic.chars() {
|
||||||
if i == '/' {
|
// if i == '/' {
|
||||||
// The topic separator is colon(':') in SCPI, and slash('/') in MQTT
|
// // The topic separator is colon(':') in SCPI, and slash('/') in MQTT
|
||||||
buffer.try_push(b':')
|
// buffer.try_push(b':')
|
||||||
.map_err(|_| ErrorCode::OutOfMemory)?;
|
// .map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
} else {
|
// } else {
|
||||||
buffer.try_push(i as u8)
|
// buffer.try_push(i as u8)
|
||||||
.map_err(|_| ErrorCode::OutOfMemory)?;
|
// .map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Place a space bar between header and parameter
|
// // Place a space bar between header and parameter
|
||||||
buffer.try_push(b' ')
|
// buffer.try_push(b' ')
|
||||||
.map_err(|_| ErrorCode::OutOfMemory)?;
|
// .map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
|
|
||||||
// Copy the arguments into the buffer
|
// Copy the arguments into the buffer
|
||||||
for i in args.chars() {
|
for i in args.chars() {
|
||||||
|
|
Loading…
Reference in New Issue