Compare commits
3 Commits
4b52aa7099
...
331d1ff86f
Author | SHA1 | Date |
---|---|---|
occheung | 331d1ff86f | |
occheung | b502b42c92 | |
occheung | 5438a81722 |
|
@ -13,6 +13,7 @@ embedded-hal = "0.2.4"
|
||||||
stm32h7xx-hal = {version = "0.7.1", features = [ "stm32h743v", "rt", "unproven", "ethernet", "phy_lan8742a" ] }
|
stm32h7xx-hal = {version = "0.7.1", features = [ "stm32h743v", "rt", "unproven", "ethernet", "phy_lan8742a" ] }
|
||||||
smoltcp = { version = "0.6.0", default-features = false, features = [ "ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp", "log" ] }
|
smoltcp = { version = "0.6.0", default-features = false, features = [ "ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp", "log" ] }
|
||||||
nb = "1.0.0"
|
nb = "1.0.0"
|
||||||
|
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" }
|
||||||
|
@ -32,6 +33,12 @@ branch = "issue-4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [ "build-info", "unit-frequency" ]
|
features = [ "build-info", "unit-frequency" ]
|
||||||
|
|
||||||
|
# Use below SCPI dependency when need to modify SCPI fork offline
|
||||||
|
# [dependencies.scpi]
|
||||||
|
# path = "../scpi-fork/scpi"
|
||||||
|
# default-features = false
|
||||||
|
# features = [ "build-info", "unit-frequency" ]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ethernet"
|
name = "ethernet"
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ use firmware::{
|
||||||
Channel3AttenuationCommand,
|
Channel3AttenuationCommand,
|
||||||
ClockSourceCommand,
|
ClockSourceCommand,
|
||||||
ClockDivisionCommand,
|
ClockDivisionCommand,
|
||||||
|
ProfileCommand
|
||||||
},
|
},
|
||||||
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
||||||
};
|
};
|
||||||
|
@ -208,7 +209,7 @@ fn main() -> ! {
|
||||||
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, 25_000_000, 25_000_000, 25_000_000]
|
[25_000_000.0, 25_000_000.0, 25_000_000.0, 25_000_000.0]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Setup ethernet pins
|
// Setup ethernet pins
|
||||||
|
|
|
@ -43,6 +43,7 @@ use firmware::{
|
||||||
Channel3AttenuationCommand,
|
Channel3AttenuationCommand,
|
||||||
ClockSourceCommand,
|
ClockSourceCommand,
|
||||||
ClockDivisionCommand,
|
ClockDivisionCommand,
|
||||||
|
ProfileCommand,
|
||||||
},
|
},
|
||||||
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
||||||
};
|
};
|
||||||
|
@ -209,7 +210,7 @@ fn main() -> ! {
|
||||||
|
|
||||||
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, 25_000_000, 25_000_000, 25_000_000]
|
[25_000_000.0, 25_000_000.0, 25_000_000.0, 25_000_000.0]
|
||||||
);
|
);
|
||||||
|
|
||||||
cp.SCB.invalidate_icache();
|
cp.SCB.invalidate_icache();
|
||||||
|
@ -272,7 +273,11 @@ fn main() -> ! {
|
||||||
.poll(|_client, topic, message, _properties| match topic {
|
.poll(|_client, topic, message, _properties| match topic {
|
||||||
topic => {
|
topic => {
|
||||||
info!("On '{:?}', received: {:?}", topic, message);
|
info!("On '{:?}', received: {:?}", topic, message);
|
||||||
context.run_with_mqtt(topic.as_bytes(), &mut buf);
|
// Why is topic a string while message is a slice?
|
||||||
|
context.run_with_mqtt(topic,
|
||||||
|
core::str::from_utf8(message).unwrap(),
|
||||||
|
&mut buf)
|
||||||
|
.unwrap();
|
||||||
},
|
},
|
||||||
}).is_ok();
|
}).is_ok();
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ where
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return selected configuration field
|
* Return selected configuration field
|
||||||
|
* TODO: Return result type instead for error checking
|
||||||
*/
|
*/
|
||||||
pub fn get_configuration(&mut self, config_type: CFGMask) -> u8 {
|
pub fn get_configuration(&mut self, config_type: CFGMask) -> u8 {
|
||||||
config_type.get_filtered_content(self.data) as u8
|
config_type.get_filtered_content(self.data) as u8
|
||||||
|
|
45
src/dds.rs
45
src/dds.rs
|
@ -1,6 +1,7 @@
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
|
use libm::round;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
||||||
|
@ -64,15 +65,15 @@ const READ_MASK :u8 = 0x80;
|
||||||
|
|
||||||
pub struct DDS<SPI> {
|
pub struct DDS<SPI> {
|
||||||
spi: SPI,
|
spi: SPI,
|
||||||
f_ref_clk: u64,
|
f_ref_clk: f64,
|
||||||
f_sys_clk: u64,
|
f_sys_clk: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SPI, E> DDS<SPI>
|
impl<SPI, E> DDS<SPI>
|
||||||
where
|
where
|
||||||
SPI: Transfer<u8, Error = E>
|
SPI: Transfer<u8, Error = E>
|
||||||
{
|
{
|
||||||
pub fn new(spi: SPI, f_ref_clk: u64) -> Self {
|
pub fn new(spi: SPI, f_ref_clk: f64) -> Self {
|
||||||
DDS {
|
DDS {
|
||||||
spi,
|
spi,
|
||||||
f_ref_clk,
|
f_ref_clk,
|
||||||
|
@ -121,7 +122,7 @@ where
|
||||||
// Ensure divider is not reset
|
// Ensure divider is not reset
|
||||||
(DDSCFRMask::REFCLK_IN_DIV_RESETB, 1),
|
(DDSCFRMask::REFCLK_IN_DIV_RESETB, 1),
|
||||||
])?;
|
])?;
|
||||||
self.f_sys_clk = self.f_ref_clk / 2;
|
self.f_sys_clk = self.f_ref_clk / 2.0;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,9 +139,9 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_pll(&mut self, f_sys_clk: u64) -> Result<(), Error<E>> {
|
pub fn enable_pll(&mut self, f_sys_clk: f64) -> Result<(), Error<E>> {
|
||||||
// Get a divider
|
// Get a divider
|
||||||
let divider = f_sys_clk / self.f_ref_clk;
|
let divider = (f_sys_clk / self.f_ref_clk) as u64;
|
||||||
// Reject extreme divider values. However, accept no frequency division
|
// Reject extreme divider values. However, accept no frequency division
|
||||||
if ((divider > 127 || divider < 12) && divider != 1) {
|
if ((divider > 127 || divider < 12) && divider != 1) {
|
||||||
// panic!("Invalid divider value for PLL!");
|
// panic!("Invalid divider value for PLL!");
|
||||||
|
@ -159,14 +160,16 @@ where
|
||||||
self.set_configurations(&mut [
|
self.set_configurations(&mut [
|
||||||
(DDSCFRMask::PFD_RESET, 0),
|
(DDSCFRMask::PFD_RESET, 0),
|
||||||
])?;
|
])?;
|
||||||
self.f_sys_clk = self.f_ref_clk * divider;
|
self.f_sys_clk = self.f_ref_clk * (divider as f64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change external clock source (ref_clk)
|
// Change external clock source (ref_clk)
|
||||||
pub fn set_ref_clk_frequency(&mut self, f_ref_clk: u64) -> Result<(), Error<E>> {
|
pub fn set_ref_clk_frequency(&mut self, f_ref_clk: f64) -> Result<(), Error<E>> {
|
||||||
|
// Override old reference clock frequency (ref_clk)
|
||||||
self.f_ref_clk = f_ref_clk;
|
self.f_ref_clk = f_ref_clk;
|
||||||
// TODO: Examine clock tree and update f_sys_clk
|
|
||||||
|
// Calculate the new system clock frequency, examine the clock tree
|
||||||
let mut configuration_queries = [
|
let mut configuration_queries = [
|
||||||
// Acquire PLL status
|
// Acquire PLL status
|
||||||
(DDSCFRMask::PLL_ENABLE, 0),
|
(DDSCFRMask::PLL_ENABLE, 0),
|
||||||
|
@ -179,7 +182,7 @@ where
|
||||||
self.get_configurations(&mut configuration_queries)?;
|
self.get_configurations(&mut configuration_queries)?;
|
||||||
if configuration_queries[0].1 == 1 {
|
if configuration_queries[0].1 == 1 {
|
||||||
// Recalculate sys_clk
|
// Recalculate sys_clk
|
||||||
let divider :u64 = configuration_queries[1].1.into();
|
let divider :f64 = configuration_queries[1].1.into();
|
||||||
let f_sys_clk = self.f_ref_clk * divider;
|
let f_sys_clk = self.f_ref_clk * divider;
|
||||||
// Adjust VCO
|
// Adjust VCO
|
||||||
match self.get_VCO_no(f_sys_clk, divider as u8) {
|
match self.get_VCO_no(f_sys_clk, divider as u8) {
|
||||||
|
@ -204,7 +207,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if configuration_queries[2].1 == 0 {
|
else if configuration_queries[2].1 == 0 {
|
||||||
self.f_sys_clk = self.f_ref_clk / 2;
|
self.f_sys_clk = self.f_ref_clk / 2.0;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -214,23 +217,23 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn get_VCO_no(&mut self, f_sys_clk: u64, 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
|
||||||
if divider == 1 {
|
if divider == 1 {
|
||||||
Ok(6) // Bypass PLL if no frequency division needed
|
Ok(6) // Bypass PLL if no frequency division needed
|
||||||
} else if f_sys_clk > 1_150_000_000 {
|
} else if f_sys_clk > 1_150_000_000.0 {
|
||||||
Err(Error::DDSCLKError)
|
Err(Error::DDSCLKError)
|
||||||
} else if f_sys_clk > 820_000_000 {
|
} else if f_sys_clk > 820_000_000.0 {
|
||||||
Ok(5)
|
Ok(5)
|
||||||
} else if f_sys_clk > 700_000_000 {
|
} else if f_sys_clk > 700_000_000.0 {
|
||||||
Ok(4)
|
Ok(4)
|
||||||
} else if f_sys_clk > 600_000_000 {
|
} else if f_sys_clk > 600_000_000.0 {
|
||||||
Ok(3)
|
Ok(3)
|
||||||
} else if f_sys_clk > 500_000_000 {
|
} else if f_sys_clk > 500_000_000.0 {
|
||||||
Ok(2)
|
Ok(2)
|
||||||
} else if f_sys_clk > 420_000_000 {
|
} else if f_sys_clk > 420_000_000.0 {
|
||||||
Ok(1)
|
Ok(1)
|
||||||
} else if f_sys_clk > 370_000_000 {
|
} else if f_sys_clk > 370_000_000.0 {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
} else {
|
} else {
|
||||||
Ok(7) // Bypass PLL if f_sys_clk is too low
|
Ok(7) // Bypass PLL if f_sys_clk is too low
|
||||||
|
@ -311,14 +314,14 @@ where
|
||||||
* Frequency: Must be integer
|
* Frequency: Must be integer
|
||||||
* 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: u64, 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>> {
|
||||||
|
|
||||||
assert!(profile < 8);
|
assert!(profile < 8);
|
||||||
assert!(phase_offset >= 0.0 && phase_offset < 360.0);
|
assert!(phase_offset >= 0.0 && phase_offset < 360.0);
|
||||||
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0);
|
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0);
|
||||||
|
|
||||||
let resolutions :[u64; 3] = [1 << 32, 1 << 16, 1 << 14];
|
let resolutions :[u64; 3] = [1 << 32, 1 << 16, 1 << 14];
|
||||||
let ftw = (resolutions[0] * f_out / self.f_sys_clk) as u32;
|
let ftw = ((resolutions[0] as f64) * f_out / self.f_sys_clk) as u32;
|
||||||
let pow = ((resolutions[1] as f64) * phase_offset / 360.0) as u16;
|
let pow = ((resolutions[1] as f64) * phase_offset / 360.0) as u16;
|
||||||
let asf :u16 = if amp_scale_factor == 1.0 {
|
let asf :u16 = if amp_scale_factor == 1.0 {
|
||||||
0x3FFF
|
0x3FFF
|
||||||
|
|
28
src/lib.rs
28
src/lib.rs
|
@ -1,5 +1,6 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(generic_associated_types)]
|
#![feature(generic_associated_types)]
|
||||||
|
#![feature(str_strip)]
|
||||||
extern crate embedded_hal;
|
extern crate embedded_hal;
|
||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
digital::v2::OutputPin,
|
digital::v2::OutputPin,
|
||||||
|
@ -81,7 +82,7 @@ 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: [u64; 4]) -> Self {
|
pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI, f_ref_clks: [f64; 4]) -> Self {
|
||||||
// Construct Urukul
|
// Construct Urukul
|
||||||
Urukul {
|
Urukul {
|
||||||
config_register: ConfigRegister::new(spi1),
|
config_register: ConfigRegister::new(spi1),
|
||||||
|
@ -130,7 +131,7 @@ where
|
||||||
|
|
||||||
// Clock tree reset. CPLD divides clock frequency by 4 by default.
|
// Clock tree reset. 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)?;
|
self.dds[chip_no].set_ref_clk_frequency(25_000_000.0)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -153,9 +154,10 @@ pub trait UrukulTraits {
|
||||||
type Error;
|
type Error;
|
||||||
fn get_channel_switch_status(&mut self, channel: u32) -> Result<bool, Self::Error>;
|
fn get_channel_switch_status(&mut self, channel: u32) -> Result<bool, Self::Error>;
|
||||||
fn set_channel_switch(&mut self, channel: u32, status: bool) -> Result<(), Self::Error>;
|
fn set_channel_switch(&mut self, channel: u32, status: bool) -> Result<(), Self::Error>;
|
||||||
fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Self::Error>;
|
fn set_clock_source(&mut self, source: ClockSource, frequency: f64) -> Result<(), Self::Error>;
|
||||||
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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SPI, E> UrukulTraits for Urukul<SPI>
|
impl<SPI, E> UrukulTraits for Urukul<SPI>
|
||||||
|
@ -190,7 +192,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Self::Error> {
|
fn set_clock_source(&mut self, source: ClockSource, frequency: f64) -> Result<(), Self::Error> {
|
||||||
|
// Change clock source through configuration register
|
||||||
match source {
|
match source {
|
||||||
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
||||||
(CFGMask::CLK_SEL0, 0),
|
(CFGMask::CLK_SEL0, 0),
|
||||||
|
@ -203,7 +206,16 @@ where
|
||||||
ClockSource::SMA => self.config_register.set_configurations(&mut [
|
ClockSource::SMA => self.config_register.set_configurations(&mut [
|
||||||
(CFGMask::CLK_SEL0, 1),
|
(CFGMask::CLK_SEL0, 1),
|
||||||
]),
|
]),
|
||||||
}.map(|_| ())
|
}?;
|
||||||
|
|
||||||
|
// Calculate reference clock frequency after clock division from configuration register
|
||||||
|
let frequency = frequency / (self.config_register.get_configuration(CFGMask::DIV) as f64);
|
||||||
|
|
||||||
|
// Update all DDS chips on reference clock frequency
|
||||||
|
for dds_channel in 0..4 {
|
||||||
|
self.dds[dds_channel].set_ref_clk_frequency(frequency)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_clock_division(&mut self, division: u8) -> Result<(), Self::Error> {
|
fn set_clock_division(&mut self, division: u8) -> Result<(), Self::Error> {
|
||||||
|
@ -224,4 +236,10 @@ where
|
||||||
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> {
|
||||||
self.attenuator.set_channel_attenuation(channel, attenuation)
|
self.attenuator.set_channel_attenuation(channel, attenuation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_profile(&mut self, profile: u8) -> Result<(), Self::Error> {
|
||||||
|
self.config_register.set_configurations(&mut [
|
||||||
|
(CFGMask::PROFILE, profile.into())
|
||||||
|
]).map(|_| ())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
68
src/scpi.rs
68
src/scpi.rs
|
@ -114,30 +114,27 @@ macro_rules! scpi_root {
|
||||||
macro_rules! scpi_tree {
|
macro_rules! scpi_tree {
|
||||||
() => {
|
() => {
|
||||||
scpi_root!(
|
scpi_root!(
|
||||||
["Control"] => {
|
"CHANNEL0" => {
|
||||||
["Urukul"] => {
|
"SWitch" => Channel0SwitchCommand,
|
||||||
"CHANNEL0" => {
|
"Attenuation" => Channel0AttenuationCommand
|
||||||
"SWitch" => Channel0SwitchCommand,
|
|
||||||
"Attenuation" => Channel0AttenuationCommand
|
|
||||||
},
|
|
||||||
"CHANNEL1" => {
|
|
||||||
"SWitch" => Channel1SwitchCommand,
|
|
||||||
"Attenuation" => Channel1AttenuationCommand
|
|
||||||
},
|
|
||||||
"CHANNEL2" => {
|
|
||||||
"SWitch" => Channel2SwitchCommand,
|
|
||||||
"Attenuation" => Channel2AttenuationCommand
|
|
||||||
},
|
|
||||||
"CHANNEL3" => {
|
|
||||||
"SWitch" => Channel3SwitchCommand,
|
|
||||||
"Attenuation" => Channel3AttenuationCommand
|
|
||||||
},
|
|
||||||
"CLOCK" => {
|
|
||||||
"SOURCE" => ClockSourceCommand,
|
|
||||||
"DIVision" => ClockDivisionCommand
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"CHANNEL1" => {
|
||||||
|
"SWitch" => Channel1SwitchCommand,
|
||||||
|
"Attenuation" => Channel1AttenuationCommand
|
||||||
|
},
|
||||||
|
"CHANNEL2" => {
|
||||||
|
"SWitch" => Channel2SwitchCommand,
|
||||||
|
"Attenuation" => Channel2AttenuationCommand
|
||||||
|
},
|
||||||
|
"CHANNEL3" => {
|
||||||
|
"SWitch" => Channel3SwitchCommand,
|
||||||
|
"Attenuation" => Channel3AttenuationCommand
|
||||||
|
},
|
||||||
|
"CLOCK" => {
|
||||||
|
"SOURCE" => ClockSourceCommand,
|
||||||
|
"DIVision" => ClockDivisionCommand
|
||||||
|
},
|
||||||
|
"PROFILE" => ProfileCommand,
|
||||||
["EXAMple"] => {
|
["EXAMple"] => {
|
||||||
"HELLO" => {
|
"HELLO" => {
|
||||||
"WORLD" => HelloWorldCommand
|
"WORLD" => HelloWorldCommand
|
||||||
|
@ -171,6 +168,7 @@ pub struct Channel0AttenuationCommand {}
|
||||||
pub struct Channel1AttenuationCommand {}
|
pub struct Channel1AttenuationCommand {}
|
||||||
pub struct Channel2AttenuationCommand {}
|
pub struct Channel2AttenuationCommand {}
|
||||||
pub struct Channel3AttenuationCommand {}
|
pub struct Channel3AttenuationCommand {}
|
||||||
|
pub struct ProfileCommand {}
|
||||||
|
|
||||||
impl<T: Device + UrukulTraits> Command<T> for Channel0SwitchCommand {
|
impl<T: Device + UrukulTraits> Command<T> for Channel0SwitchCommand {
|
||||||
nquery!();
|
nquery!();
|
||||||
|
@ -281,7 +279,7 @@ impl<T:Device + UrukulTraits> Command<T> for ClockSourceCommand {
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("Changing clock source to {:?} at {:?}", clock_source, frequency);
|
trace!("Changing clock source to {:?} at {:?}", clock_source, frequency);
|
||||||
context.device.set_clock_source(clock_source)
|
context.device.set_clock_source(clock_source, frequency.get::<hertz>())
|
||||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,6 +354,28 @@ impl<T:Device + UrukulTraits> Command<T> for Channel3AttenuationCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T:Device + UrukulTraits> Command<T> for ProfileCommand {
|
||||||
|
nquery!();
|
||||||
|
|
||||||
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||||
|
let profile :f32 = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::IllegalParameterValue)),
|
||||||
|
|token| token.try_into())?;
|
||||||
|
if ((profile as u8) as f32) != profile {
|
||||||
|
return Err(Error::new(ErrorCode::IllegalParameterValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("Selected Profile :{}", profile);
|
||||||
|
let profile = profile as u8;
|
||||||
|
if profile >= 8 {
|
||||||
|
Err(Error::new(ErrorCode::IllegalParameterValue))
|
||||||
|
} else {
|
||||||
|
context.device.set_profile(profile)
|
||||||
|
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implement "Device" trait from SCPI
|
* Implement "Device" trait from SCPI
|
||||||
* TODO: Implement mandatory commands
|
* TODO: Implement mandatory commands
|
||||||
|
|
|
@ -1,28 +1,56 @@
|
||||||
use scpi::prelude::*;
|
use scpi::prelude::*;
|
||||||
use scpi::Context;
|
use scpi::Context;
|
||||||
use scpi::error::Result;
|
use scpi::error::Result;
|
||||||
|
use log::{trace, info};
|
||||||
use arrayvec::{ArrayVec};
|
use arrayvec::{ArrayVec};
|
||||||
|
|
||||||
pub trait MqttScpiTranslator {
|
pub trait MqttScpiTranslator {
|
||||||
fn run_with_mqtt<FMT: Formatter>(&mut self, s: &[u8], response: &mut FMT) -> Result<()>;
|
// Convert an MQTT publish message into SCPI compatible command
|
||||||
|
// The argument 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<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Device> MqttScpiTranslator for Context<'a, T> {
|
impl<'a, T: Device> MqttScpiTranslator for Context<'a, T> {
|
||||||
fn run_with_mqtt<FMT>(&mut self, s: &[u8], response: &mut FMT) -> Result<()>
|
fn run_with_mqtt<FMT>(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>
|
||||||
where
|
where
|
||||||
FMT: Formatter,
|
FMT: Formatter,
|
||||||
{
|
{
|
||||||
let mut array_vec = ArrayVec::<[u8; 1024]>::new();
|
if !topic.starts_with("Urukul/Control") {
|
||||||
for i in s.into_iter() {
|
info!("Received a publish, but not for control! Topic: {}", topic);
|
||||||
if *i == b'/' {
|
return Ok(());
|
||||||
array_vec.try_push(b'/')
|
}
|
||||||
.map_err(|_| ErrorCode::OutOfMemory)?;
|
|
||||||
|
let command_topic = topic.strip_prefix("Urukul/Control/")
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
// Create a fixed-size buffer to handle slice operation
|
||||||
|
let mut buffer = ArrayVec::<[u8; 1024]>::new();
|
||||||
|
|
||||||
|
// Copy MQTT topic, convert it into SCPI header format
|
||||||
|
for i in command_topic.chars() {
|
||||||
|
if i == '/' {
|
||||||
|
// The topic separator is colon(':') in SCPI, and slash('/') in MQTT
|
||||||
|
buffer.try_push(b':')
|
||||||
|
.map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
} else {
|
} else {
|
||||||
array_vec.try_push(*i)
|
buffer.try_push(i as u8)
|
||||||
.map_err(|_| ErrorCode::OutOfMemory)?;
|
.map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.run(array_vec.as_slice(), response)
|
|
||||||
|
// Place a space bar between header and parameter
|
||||||
|
buffer.try_push(b' ')
|
||||||
|
.map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
|
|
||||||
|
// Copy the arguments into the buffer
|
||||||
|
for i in args.chars() {
|
||||||
|
buffer.try_push(i as u8)
|
||||||
|
.map_err(|_| ErrorCode::OutOfMemory)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass the message to SCPI processing unit
|
||||||
|
trace!("Translated MQTT message into SCPI. Translated command: {}",
|
||||||
|
core::str::from_utf8(buffer.as_slice()).unwrap());
|
||||||
|
self.run(buffer.as_slice(), response)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue