forked from M-Labs/humpback-dds
dds: add ram control
This commit is contained in:
parent
d0d475bfbf
commit
7729362d52
|
@ -35,11 +35,32 @@ pub struct ChannelConfig {
|
||||||
pub sw: bool,
|
pub sw: bool,
|
||||||
pub att: f32,
|
pub att: f32,
|
||||||
pub sys_clk: f64,
|
pub sys_clk: f64,
|
||||||
|
pub freq: f64,
|
||||||
|
pub asf: f64,
|
||||||
|
pub profile: ProfileSetup,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SingleTone {
|
||||||
pub freq: f64,
|
pub freq: f64,
|
||||||
pub phase: f64,
|
pub phase: f64,
|
||||||
pub asf: f64,
|
pub asf: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct RAM {
|
||||||
|
pub start: u16,
|
||||||
|
pub end: u16,
|
||||||
|
pub stride: u16,
|
||||||
|
pub op_mode: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ProfileSetup {
|
||||||
|
Singletone(SingleTone),
|
||||||
|
RAM(RAM),
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_net_config(store: &mut FlashStore) -> NetConfig {
|
pub fn get_net_config(store: &mut FlashStore) -> NetConfig {
|
||||||
let net_config = NetConfig {
|
let net_config = NetConfig {
|
||||||
ip_cidr: {
|
ip_cidr: {
|
||||||
|
|
371
src/dds.rs
371
src/dds.rs
|
@ -4,7 +4,7 @@ use core::mem::size_of;
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
use heapless::consts::*;
|
use heapless::consts::*;
|
||||||
use log::{ trace, debug, warn };
|
use log::debug;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
||||||
|
@ -66,9 +66,10 @@ construct_bitmask!(DDSCFRMask; u32;
|
||||||
const WRITE_MASK :u8 = 0x00;
|
const WRITE_MASK :u8 = 0x00;
|
||||||
const READ_MASK :u8 = 0x80;
|
const READ_MASK :u8 = 0x80;
|
||||||
|
|
||||||
|
#[link_section = ".sram2.ram"]
|
||||||
static mut RAM_VEC: Vec<u8, U8192> = Vec(heapless::i::Vec::new());
|
static mut RAM_VEC: Vec<u8, U8192> = Vec(heapless::i::Vec::new());
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub enum RAMDestination {
|
pub enum RAMDestination {
|
||||||
Frequency = 0,
|
Frequency = 0,
|
||||||
Phase = 1,
|
Phase = 1,
|
||||||
|
@ -76,7 +77,7 @@ pub enum RAMDestination {
|
||||||
Polar = 3,
|
Polar = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RAMOperationMode {
|
pub enum RAMOperationMode {
|
||||||
DirectSwitch = 0,
|
DirectSwitch = 0,
|
||||||
RampUp = 1,
|
RampUp = 1,
|
||||||
|
@ -89,6 +90,7 @@ pub struct DDS<SPI> {
|
||||||
spi: SPI,
|
spi: SPI,
|
||||||
f_ref_clk: f64,
|
f_ref_clk: f64,
|
||||||
f_sys_clk: f64,
|
f_sys_clk: f64,
|
||||||
|
ram_dest: RAMDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SPI, E> DDS<SPI>
|
impl<SPI, E> DDS<SPI>
|
||||||
|
@ -100,6 +102,7 @@ where
|
||||||
spi,
|
spi,
|
||||||
f_ref_clk,
|
f_ref_clk,
|
||||||
f_sys_clk: f_ref_clk,
|
f_sys_clk: f_ref_clk,
|
||||||
|
ram_dest: RAMDestination::Frequency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -508,6 +511,41 @@ where
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to configure the default frequency in the FTW register (0x07)
|
||||||
|
pub fn set_default_ftw(&mut self, frequency: f64) -> Result<(), Error<E>> {
|
||||||
|
let mut ftw: [u8; 4] = self.frequency_to_ftw(frequency).to_be_bytes();
|
||||||
|
self.write_register(0x07, &mut ftw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to configure the default amplitude in the ASF register (0x09)
|
||||||
|
pub fn set_default_asf(&mut self, amplitude: f64) -> Result<(), Error<E>> {
|
||||||
|
let shifted_asf: [u8; 2] = (self.amplitude_to_asf(amplitude) << 2).to_be_bytes();
|
||||||
|
let mut asf_register: [u8; 4] = [0; 4];
|
||||||
|
self.read_register(0x09, &mut asf_register)?;
|
||||||
|
// Override original ASF
|
||||||
|
asf_register[2] = shifted_asf[0];
|
||||||
|
asf_register[3] = (shifted_asf[1] & 0xFC) | (asf_register[3] & 0x03);
|
||||||
|
self.write_register(0x09, &mut asf_register)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_ftw(&mut self) -> Result<f64, Error<E>> {
|
||||||
|
let mut ftw_bytes: [u8; 4] = [0; 4];
|
||||||
|
self.read_register(0x07, &mut ftw_bytes)?;
|
||||||
|
let ftw: u64 = (ftw_bytes[0] as u64) << 24 |
|
||||||
|
(ftw_bytes[1] as u64) << 16 |
|
||||||
|
(ftw_bytes[2] as u64) << 8 |
|
||||||
|
(ftw_bytes[3] as u64);
|
||||||
|
Ok(((ftw as f64)/(((1_u64) << 32) as f64))*self.f_sys_clk)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_asf(&mut self) -> Result<f64, Error<E>> {
|
||||||
|
let mut asf_register: [u8; 4] = [0; 4];
|
||||||
|
self.read_register(0x09, &mut asf_register)?;
|
||||||
|
let asf: u64 = ((asf_register[2] as u64) << 6) |
|
||||||
|
((asf_register[3] as u64) >> 2);
|
||||||
|
Ok((asf as f64)/(((1_u64) << 14) as f64))
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to switch into RAM mode
|
// Helper function to switch into RAM mode
|
||||||
// Need to setup configuration registers before writing into RAM profile register
|
// Need to setup configuration registers before writing into RAM profile register
|
||||||
fn enable_ram_configuration(&mut self, ram_dst: RAMDestination) -> Result<(), Error<E>> {
|
fn enable_ram_configuration(&mut self, ram_dst: RAMDestination) -> Result<(), Error<E>> {
|
||||||
|
@ -525,275 +563,51 @@ where
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Setup a RAM profile
|
||||||
* Configure a RAM mode profile, wrt supplied frequency data
|
pub fn set_up_ram_profile(&mut self, profile: u8, start_addr: u16,
|
||||||
* This will setup the static RAM_VEC by converting frequency to ftw
|
end_addr: u16, no_dwell_high: bool, zero_crossing: bool,
|
||||||
*/
|
op_mode: RAMOperationMode, ramp_rate: u16
|
||||||
pub unsafe fn set_frequency_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
)-> Result<(), Error<E>>
|
||||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
{
|
||||||
frequency_data: &[f64]
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
|
||||||
assert!(profile <= 7);
|
assert!(profile <= 7);
|
||||||
assert!(end_addr >= start_addr);
|
assert!(end_addr >= start_addr);
|
||||||
assert!(end_addr < 1024);
|
assert!(end_addr < 1024);
|
||||||
assert_eq!(frequency_data.len() as u16, end_addr - start_addr + 1);
|
|
||||||
|
|
||||||
// Clear RAM vector, and add address byte
|
self.enable_ram_configuration(self.ram_dest)?;
|
||||||
RAM_VEC.clear();
|
|
||||||
RAM_VEC.push(0x16)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
|
|
||||||
// Convert frequency data into bytes recognized by DDS
|
|
||||||
for freq in frequency_data.iter() {
|
|
||||||
let ftw = self.frequency_to_ftw(*freq);
|
|
||||||
RAM_VEC.push(((ftw >> 24) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((ftw >> 16) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((ftw >> 8) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((ftw >> 0) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Frequency,
|
|
||||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Configure a RAM mode profile, wrt supplied amplitude data
|
|
||||||
* This will setup the static RAM_VEC by converting amplitude to asf
|
|
||||||
*/
|
|
||||||
pub unsafe fn set_amplitude_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
|
||||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
|
||||||
amplitude_data: &[f64]
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
|
||||||
assert!(profile <= 7);
|
|
||||||
assert!(end_addr >= start_addr);
|
|
||||||
assert!(end_addr < 1024);
|
|
||||||
assert_eq!(amplitude_data.len() as u16, end_addr - start_addr + 1);
|
|
||||||
|
|
||||||
// Clear RAM vector, and add address byte
|
|
||||||
RAM_VEC.clear();
|
|
||||||
RAM_VEC.push(0x16)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
|
|
||||||
// Convert amplitude data into bytes recognized by DDS
|
|
||||||
for amp in amplitude_data.iter() {
|
|
||||||
let asf = self.amplitude_to_asf(*amp);
|
|
||||||
RAM_VEC.push(((asf >> 8) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((asf << 2) & 0xFC) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(0)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(0)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Amplitude,
|
|
||||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Configure a RAM mode profile, wrt supplied phase data
|
|
||||||
* This will setup the static RAM_VEC by converting phase to ftw
|
|
||||||
*/
|
|
||||||
pub unsafe fn set_phase_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
|
||||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
|
||||||
phase_data: &[f64]
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
|
||||||
assert!(profile <= 7);
|
|
||||||
assert!(end_addr >= start_addr);
|
|
||||||
assert!(end_addr < 1024);
|
|
||||||
assert_eq!(phase_data.len() as u16, end_addr - start_addr + 1);
|
|
||||||
|
|
||||||
// Clear RAM vector, and add address byte
|
|
||||||
RAM_VEC.clear();
|
|
||||||
RAM_VEC.push(0x16)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
|
|
||||||
// Convert phase data into bytes recognized by DDS
|
|
||||||
for deg in phase_data.iter() {
|
|
||||||
let pow = self.degree_to_pow(*deg);
|
|
||||||
RAM_VEC.push(((pow >> 8) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((pow >> 0) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(0)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(0)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Phase,
|
|
||||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Configure a RAM mode profile, wrt supplied phase data
|
|
||||||
* This will setup the static RAM_VEC by converting phase to ftw
|
|
||||||
*/
|
|
||||||
pub unsafe fn set_polar_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
|
||||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
|
||||||
polar_data: &[(f64, f64)]
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
|
||||||
assert!(profile <= 7);
|
|
||||||
assert!(end_addr >= start_addr);
|
|
||||||
assert!(end_addr < 1024);
|
|
||||||
assert_eq!(polar_data.len() as u16, end_addr - start_addr + 1);
|
|
||||||
|
|
||||||
// Clear RAM vector, and add address byte
|
|
||||||
RAM_VEC.clear();
|
|
||||||
RAM_VEC.push(0x16)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
|
|
||||||
// Convert amplitude data into bytes recognized by DDS
|
|
||||||
for (deg, amp) in polar_data.iter() {
|
|
||||||
let pow = self.degree_to_pow(*deg);
|
|
||||||
let asf = self.amplitude_to_asf(*amp);
|
|
||||||
RAM_VEC.push(((pow >> 8) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((pow >> 0) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((asf >> 8) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((asf << 2) & 0xFC) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Phase,
|
|
||||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Configure a frequency sweep RAM mode profile
|
|
||||||
*/
|
|
||||||
pub unsafe fn set_frequency_sweep_profile(&mut self, profile: u8, start_addr: u16,
|
|
||||||
lower_boundary: f64, upper_boundary: f64, f_resolution: f64,
|
|
||||||
no_dwell_high: bool, op_mode: RAMOperationMode, playback_rate: f64
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
|
||||||
assert!(profile <= 7);
|
|
||||||
assert!(start_addr < 1024);
|
|
||||||
|
|
||||||
// Find out the required RAM size
|
|
||||||
// Frequencies may have to be repeated if the playback rate is too low
|
|
||||||
// Reject impossible setups
|
|
||||||
// E.g. Higher playback rate than f_dds_clk, insufficient RAM allocation
|
|
||||||
let nominal_step_rate = self.f_sys_clk/(4.0 * playback_rate);
|
|
||||||
if nominal_step_rate < 1.0 {
|
|
||||||
return Err(Error::DDSRAMError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle unfortunate scenario: step_rate / 0xFFFF gives a round number
|
|
||||||
// Current implmentation unnecessarily allocates 1 extra RAM space for each data
|
|
||||||
let duplication = (nominal_step_rate / (0xFFFF as f64)) as u64 + 1;
|
|
||||||
|
|
||||||
// Acquire the RAM size needed by multiplying duplication.
|
|
||||||
// All data needs to be duplicated such that a desired step_rate can be reached
|
|
||||||
// Return DDS RAM Error if it does not fix into the RAM
|
|
||||||
let span = upper_boundary - lower_boundary;
|
|
||||||
let data_size = if core::intrinsics::roundf64(span/f_resolution) == (span/f_resolution) {
|
|
||||||
(span/f_resolution) as u64 + 1
|
|
||||||
} else {
|
|
||||||
(span/f_resolution) as u64
|
|
||||||
};
|
|
||||||
let ram_size = data_size * duplication;
|
|
||||||
|
|
||||||
let end_addr = (start_addr as u64) + ram_size - 1;
|
|
||||||
trace!("Required RAM size: {}", ram_size);
|
|
||||||
if end_addr >= 1024 {
|
|
||||||
warn!("RAM address out of bound");
|
|
||||||
return Err(Error::DDSRAMError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear RAM vector, and add address byte
|
|
||||||
RAM_VEC.clear();
|
|
||||||
RAM_VEC.push(0x16)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
|
|
||||||
// Drop in the data into RAM_VEC
|
|
||||||
for data_index in 0..data_size {
|
|
||||||
let freq = lower_boundary + f_resolution * (data_index as f64);
|
|
||||||
for _ in 0..duplication {
|
|
||||||
let ftw = self.frequency_to_ftw(freq);
|
|
||||||
RAM_VEC.push(((ftw >> 24) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((ftw >> 16) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((ftw >> 8) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
RAM_VEC.push(((ftw >> 0) & 0xFF) as u8)
|
|
||||||
.map_err(|_| Error::DDSRAMError)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("start_addr: {}\nend_addr: {}\n, duplication: {}\n, data_size: {}\n",
|
|
||||||
start_addr, end_addr, duplication, data_size);
|
|
||||||
|
|
||||||
self.set_ram_profile(profile, start_addr, end_addr.try_into().unwrap(), RAMDestination::Frequency,
|
|
||||||
no_dwell_high, true, op_mode, playback_rate * (duplication as f64))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Configure a RAM mode profile, w.r.t static vector (RAM_VEC)
|
|
||||||
*/
|
|
||||||
fn set_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
|
||||||
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
|
||||||
op_mode: RAMOperationMode, playback_rate: f64
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
|
||||||
assert!(profile <= 7);
|
|
||||||
assert!(end_addr >= start_addr);
|
|
||||||
assert!(end_addr < 1024);
|
|
||||||
// assert_eq! RAM_VEC.len() as u16, ((end_addr - start_addr + 1) * 4) + 1);
|
|
||||||
|
|
||||||
// Calculate address step rate, and check legality
|
|
||||||
let step_rate = (self.f_sys_clk/(4.0 * playback_rate)) as u64;
|
|
||||||
trace!("Setting up RAM profile, step_rate: {}", step_rate);
|
|
||||||
if step_rate == 0 || step_rate > 0xFFFF {
|
|
||||||
return Err(Error::DDSRAMError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before setting up RAM, disable RAM_ENABLE
|
|
||||||
self.enable_ram_configuration(ram_dst.clone())?;
|
|
||||||
|
|
||||||
// Write a RAM profile, but include all data in RAM
|
|
||||||
self.write_register(0x0E + profile, &mut [
|
self.write_register(0x0E + profile, &mut [
|
||||||
0x00,
|
0x00,
|
||||||
((step_rate >> 8) & 0xFF).try_into().unwrap(),
|
((ramp_rate >> 8) & 0xFF).try_into().unwrap(),
|
||||||
((step_rate >> 0) & 0xFF).try_into().unwrap(),
|
((ramp_rate >> 0) & 0xFF).try_into().unwrap(),
|
||||||
((end_addr >> 2) & 0xFF).try_into().unwrap(),
|
((end_addr >> 2) & 0xFF).try_into().unwrap(),
|
||||||
((end_addr & 0x3) << 6).try_into().unwrap(),
|
((end_addr & 0x3) << 6).try_into().unwrap(),
|
||||||
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
||||||
((start_addr & 0x3) << 6).try_into().unwrap(),
|
((start_addr & 0x3) << 6).try_into().unwrap(),
|
||||||
((no_dwell_high as u8) << 5) | ((zero_crossing as u8) << 3) | (op_mode as u8)
|
((no_dwell_high as u8) << 5) | ((zero_crossing as u8) << 3) | (op_mode as u8)
|
||||||
])?;
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ram_profile(&mut self, profile: u8) -> Result<(u16, u16, u16, u8), Error<E>> {
|
||||||
|
assert!(profile <= 7);
|
||||||
|
|
||||||
// Temporarily disable RAM mode while accessing into RAM
|
let mut buffer: [u8; 8] = [0; 8];
|
||||||
self.disable_ram_configuration()?;
|
self.read_register(0x0E + profile, &mut buffer)?;
|
||||||
unsafe {
|
let start = u16::from_be_bytes([buffer[5], buffer[6]]) >> 6;
|
||||||
self.write_ram()?;
|
let end = u16::from_be_bytes([buffer[3], buffer[4]]) >> 6;
|
||||||
}
|
let stride = u16::from_be_bytes([buffer[1], buffer[2]]);
|
||||||
|
let op_mode = buffer[7] & 0x07;
|
||||||
|
|
||||||
// Properly configure start_addr and end_addr
|
Ok((start, end, stride, op_mode))
|
||||||
self.enable_ram_configuration(ram_dst)
|
}
|
||||||
|
|
||||||
|
pub fn ram_mode_enabled(&mut self) -> Result<bool, Error<E>> {
|
||||||
|
let mut ram_query = [(DDSCFRMask::RAM_ENABLE, 0)];
|
||||||
|
self.get_configurations(&mut ram_query)?;
|
||||||
|
Ok(ram_query[0].1 != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate ftw (frequency tuning word)
|
// Calculate ftw (frequency tuning word)
|
||||||
fn frequency_to_ftw(&mut self, f_out: f64) -> u32 {
|
pub fn frequency_to_ftw(&mut self, f_out: f64) -> u32 {
|
||||||
let f_res: u64 = 1 << 32;
|
let f_res: u64 = 1 << 32;
|
||||||
((f_res as f64) * f_out / self.f_sys_clk) as u32
|
((f_res as f64) * f_out / self.f_sys_clk) as u32
|
||||||
}
|
}
|
||||||
|
@ -811,11 +625,36 @@ where
|
||||||
((amp_res as f64) * amplitude) as u16
|
((amp_res as f64) * amplitude) as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write data in RAM
|
// Write RAM bytes into DDS channel
|
||||||
unsafe fn write_ram(&mut self) -> Result<(), Error<E>> {
|
// Assume profile 7 is selected by the CPLD in prior
|
||||||
|
pub unsafe fn commit_ram_buffer(&mut self, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error<E>> {
|
||||||
|
let ram_size = ((RAM_VEC.len() - 1) as u16)/4;
|
||||||
|
if RAM_VEC.len() == 0 || RAM_VEC[0] != 0x16 ||
|
||||||
|
(start_addr + ram_size) > 1024 || start_addr >= 1024 {
|
||||||
|
return Err(Error::DDSRAMError)
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_addr: [u8; 2] = ((ram_size + start_addr - 1) << 6).to_be_bytes();
|
||||||
|
|
||||||
|
// Use profile 7 to setup a temperory RAM profile
|
||||||
|
self.enable_ram_configuration(ram_dest.clone())?;
|
||||||
|
self.write_register(0x15, &mut [
|
||||||
|
0x00,
|
||||||
|
0x00, 0x01,
|
||||||
|
end_addr[0], end_addr[1],
|
||||||
|
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
||||||
|
((start_addr & 0x3) << 6).try_into().unwrap(),
|
||||||
|
0x00
|
||||||
|
])?;
|
||||||
|
self.disable_ram_configuration()?;
|
||||||
|
|
||||||
|
log::info!("RAM buffer: {:?}", RAM_VEC);
|
||||||
self.spi.transfer(&mut RAM_VEC)
|
self.spi.transfer(&mut RAM_VEC)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(Error::SPI)
|
.map_err(Error::SPI)?;
|
||||||
|
RAM_VEC.clear();
|
||||||
|
self.ram_dest = ram_dest;
|
||||||
|
self.enable_ram_configuration(ram_dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -954,3 +793,19 @@ impl_register_io!(
|
||||||
0x14, 8,
|
0x14, 8,
|
||||||
0x15, 8
|
0x15, 8
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Append bytes to the RAM buffer
|
||||||
|
pub unsafe fn append_ram_byte(data: &[u8]) {
|
||||||
|
assert!(data.len() <= 4096);
|
||||||
|
|
||||||
|
// Add RAM address if needed
|
||||||
|
if RAM_VEC.len() == 0 {
|
||||||
|
RAM_VEC.push(0x16).unwrap();
|
||||||
|
} else if RAM_VEC[0] != 0x16 || (data.len() + RAM_VEC.len()) >= RAM_VEC.capacity() {
|
||||||
|
RAM_VEC.clear();
|
||||||
|
RAM_VEC.push(0x16).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
RAM_VEC.extend_from_slice(data).unwrap();
|
||||||
|
log::info!("RAM buffer: {:?}", RAM_VEC);
|
||||||
|
}
|
||||||
|
|
439
src/mqtt_mux.rs
439
src/mqtt_mux.rs
|
@ -15,9 +15,10 @@ use crate::urukul::Urukul;
|
||||||
use crate::urukul::Error;
|
use crate::urukul::Error;
|
||||||
use crate::flash_store::{ FlashStore, update_flash };
|
use crate::flash_store::{ FlashStore, update_flash };
|
||||||
use crate::flash::Flash;
|
use crate::flash::Flash;
|
||||||
use crate::config::{ UrukulConfig, ChannelConfig };
|
use crate::config::{ UrukulConfig, ChannelConfig, ProfileSetup, SingleTone, RAM };
|
||||||
|
use crate::dds::{ RAMDestination, RAMOperationMode };
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum MqttTopic {
|
pub enum MqttTopic {
|
||||||
Reset,
|
Reset,
|
||||||
Switch(u8),
|
Switch(u8),
|
||||||
|
@ -34,6 +35,11 @@ pub enum MqttTopic {
|
||||||
Profile,
|
Profile,
|
||||||
Save,
|
Save,
|
||||||
Load,
|
Load,
|
||||||
|
DefaultFTW(u8),
|
||||||
|
DefaultASF(u8),
|
||||||
|
AppendBytes,
|
||||||
|
CommitBuffer,
|
||||||
|
RAM(u8, u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prossible change: Make this enum public to all comm protocol (if any)
|
// Prossible change: Make this enum public to all comm protocol (if any)
|
||||||
|
@ -56,6 +62,11 @@ pub enum MqttCommand {
|
||||||
Profile(u8),
|
Profile(u8),
|
||||||
Save,
|
Save,
|
||||||
Load,
|
Load,
|
||||||
|
DefaultFTW(u8, f64),
|
||||||
|
DefaultASF(u8, f64),
|
||||||
|
AppendBytes(usize),
|
||||||
|
CommitBuffer(RAMDestination, u16, u8),
|
||||||
|
RAM(u8, u8, u16, u16, RAMOperationMode, u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MqttMux<'s, SPI> {
|
pub struct MqttMux<'s, SPI> {
|
||||||
|
@ -118,13 +129,39 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
channel as u8,
|
channel as u8,
|
||||||
channel_config.sys_clk
|
channel_config.sys_clk
|
||||||
)?;
|
)?;
|
||||||
self.urukul.set_channel_single_tone_profile(
|
self.urukul.set_channel_default_ftw(
|
||||||
channel as u8,
|
channel as u8,
|
||||||
profile,
|
channel_config.freq
|
||||||
channel_config.freq,
|
|
||||||
channel_config.phase,
|
|
||||||
channel_config.asf,
|
|
||||||
)?;
|
)?;
|
||||||
|
self.urukul.set_channel_default_asf(
|
||||||
|
channel as u8,
|
||||||
|
channel_config.asf
|
||||||
|
)?;
|
||||||
|
if let ProfileSetup::Singletone(singletone) = channel_config.profile {
|
||||||
|
self.urukul.set_channel_single_tone_profile(
|
||||||
|
channel as u8,
|
||||||
|
profile,
|
||||||
|
singletone.freq,
|
||||||
|
singletone.phase,
|
||||||
|
singletone.asf
|
||||||
|
)?;
|
||||||
|
} else if let ProfileSetup::RAM(ram) = channel_config.profile {
|
||||||
|
let op_mode = match ram.op_mode {
|
||||||
|
0 => RAMOperationMode::DirectSwitch,
|
||||||
|
1 => RAMOperationMode::RampUp,
|
||||||
|
2 => RAMOperationMode::BidirectionalRamp,
|
||||||
|
3 => RAMOperationMode::ContinuousBidirectionalRamp,
|
||||||
|
_ => RAMOperationMode::ContinuousRecirculate,
|
||||||
|
};
|
||||||
|
self.urukul.set_channel_ram_profile(
|
||||||
|
channel as u8,
|
||||||
|
profile,
|
||||||
|
ram.start,
|
||||||
|
ram.end,
|
||||||
|
op_mode,
|
||||||
|
ram.stride
|
||||||
|
)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
None => ()
|
None => ()
|
||||||
};
|
};
|
||||||
|
@ -151,10 +188,26 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
unsafe { self.flash_store.write_value("urukul", &urukul_config, &mut SERDE_BUFFER).unwrap(); }
|
unsafe { self.flash_store.write_value("urukul", &urukul_config, &mut SERDE_BUFFER).unwrap(); }
|
||||||
|
|
||||||
for channel in 0..4 {
|
for channel in 0..4 {
|
||||||
let (freq, phase, asf) = self.urukul.get_channel_single_tone_profile(
|
let ram = self.urukul.get_channel_ram_mode_enabled(channel as u8)?;
|
||||||
channel as u8,
|
let profile_setup = if ram {
|
||||||
urukul_config.profile
|
let (start, end, stride, op_mode) = self.urukul.get_channel_ram_profile(
|
||||||
)?;
|
channel as u8,
|
||||||
|
urukul_config.profile
|
||||||
|
)?;
|
||||||
|
let ram_profile = RAM {
|
||||||
|
start, end, stride, op_mode
|
||||||
|
};
|
||||||
|
ProfileSetup::RAM(ram_profile)
|
||||||
|
} else {
|
||||||
|
let (freq, phase, asf) = self.urukul.get_channel_single_tone_profile(
|
||||||
|
channel as u8,
|
||||||
|
urukul_config.profile
|
||||||
|
)?;
|
||||||
|
let singletone_profile = SingleTone {
|
||||||
|
freq, phase, asf
|
||||||
|
};
|
||||||
|
ProfileSetup::Singletone(singletone_profile)
|
||||||
|
};
|
||||||
let channel_config = ChannelConfig {
|
let channel_config = ChannelConfig {
|
||||||
sw: {
|
sw: {
|
||||||
self.urukul.get_channel_switch_status(channel as u32)?
|
self.urukul.get_channel_switch_status(channel as u32)?
|
||||||
|
@ -165,9 +218,13 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
sys_clk: {
|
sys_clk: {
|
||||||
self.urukul.get_channel_sys_clk(channel as u8)?
|
self.urukul.get_channel_sys_clk(channel as u8)?
|
||||||
},
|
},
|
||||||
freq,
|
freq: {
|
||||||
phase,
|
self.urukul.get_channel_default_ftw(channel as u8)?
|
||||||
asf
|
},
|
||||||
|
asf: {
|
||||||
|
self.urukul.get_channel_default_asf(channel as u8)?
|
||||||
|
},
|
||||||
|
profile: profile_setup
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
self.flash_store.write_value(CHANNELS[channel], &channel_config, &mut SERDE_BUFFER).unwrap();
|
self.flash_store.write_value(CHANNELS[channel], &channel_config, &mut SERDE_BUFFER).unwrap();
|
||||||
|
@ -188,13 +245,26 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let command = match self.parse_message(topic, message) {
|
|
||||||
Ok((_, cmd)) => cmd,
|
// All MQTT messages should be parsed except appending bytes to buffer
|
||||||
Err(_) => {
|
// Directly pass the buffer to urukul in this case
|
||||||
self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot parse MQTT message"));
|
let command = if topic == MqttTopic::AppendBytes {
|
||||||
|
let length = message.len();
|
||||||
|
if self.urukul.append_dds_ram_buffer(message).is_err() {
|
||||||
|
self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot push bytes to buffer"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
MqttCommand::AppendBytes(length)
|
||||||
|
} else {
|
||||||
|
match self.parse_message(topic, message) {
|
||||||
|
Ok((_, cmd)) => cmd,
|
||||||
|
Err(_) => {
|
||||||
|
self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot parse MQTT message"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.yet_to_respond = match self.execute(command.clone()) {
|
self.yet_to_respond = match self.execute(command.clone()) {
|
||||||
Err(_) => Some(MqttCommand::ProcessError("Cannot execute MQTT command")),
|
Err(_) => Some(MqttCommand::ProcessError("Cannot execute MQTT command")),
|
||||||
Ok(()) => Some(command)
|
Ok(()) => Some(command)
|
||||||
|
@ -409,6 +479,110 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
)).map_err(|_| Error::VectorOutOfSpace)?;
|
)).map_err(|_| Error::VectorOutOfSpace)?;
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MqttCommand::DefaultFTW(ch, _freq) => {
|
||||||
|
vec.push((
|
||||||
|
{
|
||||||
|
let mut topic_string = String::from(self.name);
|
||||||
|
topic_string.push_str("/Feedback/Channel")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string.push(char::from_digit(ch.into(), 10).unwrap())
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string.push_str("/Background/Frequency")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let freq = self.urukul.get_channel_default_ftw(ch)?;
|
||||||
|
let mut message_str = String::from(
|
||||||
|
self.float_buffer.format_finite(freq)
|
||||||
|
);
|
||||||
|
message_str.push_str(" Hz")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
message_str
|
||||||
|
}
|
||||||
|
)).map_err(|_| Error::VectorOutOfSpace)?;
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttCommand::DefaultASF(ch, _ampl) => {
|
||||||
|
vec.push((
|
||||||
|
{
|
||||||
|
let mut topic_string = String::from(self.name);
|
||||||
|
topic_string.push_str("/Feedback/Channel")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string.push(char::from_digit(ch.into(), 10).unwrap())
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string.push_str("/Background/Amplitude")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let ampl = self.urukul.get_channel_default_asf(ch)?;
|
||||||
|
let message_str = String::from(
|
||||||
|
self.float_buffer.format_finite(ampl)
|
||||||
|
);
|
||||||
|
message_str
|
||||||
|
}
|
||||||
|
)).map_err(|_| Error::VectorOutOfSpace)?;
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttCommand::AppendBytes(len) => {
|
||||||
|
vec.push((
|
||||||
|
{
|
||||||
|
let mut topic_string = String::from(self.name);
|
||||||
|
topic_string.push_str("/Feedback/Buffer/Append")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let mut message_str = String::from("Pushed ");
|
||||||
|
message_str.push_str(self.float_buffer.format_finite(len as f64))
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
message_str.push_str(" bytes to buffer.")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
message_str
|
||||||
|
}
|
||||||
|
)).map_err(|_| Error::VectorOutOfSpace)?;
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttCommand::CommitBuffer(_dest, _start_addr, ch) => {
|
||||||
|
vec.push((
|
||||||
|
{
|
||||||
|
let mut topic_string = String::from(self.name);
|
||||||
|
topic_string.push_str("/Feedback/Buffer/Commit")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let mut message_str = String::from("Pushed bytes to channel ");
|
||||||
|
message_str.push(char::from_digit(ch.into(), 10).unwrap())
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
message_str
|
||||||
|
}
|
||||||
|
)).map_err(|_| Error::VectorOutOfSpace)?;
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttCommand::RAM(ch, _pr, _start_addr, _end_addr, _op_mode, _) => {
|
||||||
|
vec.push((
|
||||||
|
{
|
||||||
|
let mut topic_string = String::from(self.name);
|
||||||
|
topic_string.push_str("/Feedback/RAM")
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
topic_string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let mut message_str = String::from("Selected RAM profile for channel ");
|
||||||
|
message_str.push(char::from_digit(ch.into(), 10).unwrap())
|
||||||
|
.map_err(|_| Error::StringOutOfSpace)?;
|
||||||
|
message_str
|
||||||
|
}
|
||||||
|
)).map_err(|_| Error::VectorOutOfSpace)?;
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
None => Ok(vec),
|
None => Ok(vec),
|
||||||
}
|
}
|
||||||
|
@ -605,6 +779,41 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
return Ok(MqttTopic::Load);
|
return Ok(MqttTopic::Load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Background/Frequency" => {
|
||||||
|
if !assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::DefaultFTW(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
"Background/Amplitude" => {
|
||||||
|
if !assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::DefaultASF(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
"Buffer/Append" => {
|
||||||
|
if assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::AppendBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
"Buffer/Commit" => {
|
||||||
|
if assigned_channel || assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::CommitBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
"RAM" => {
|
||||||
|
if !assigned_channel || !assigned_profile {
|
||||||
|
return Err(Error::MqttCommandError)
|
||||||
|
}
|
||||||
|
return Ok(MqttTopic::RAM(channel, profile));
|
||||||
|
}
|
||||||
|
|
||||||
_ => return Err(Error::MqttCommandError),
|
_ => return Err(Error::MqttCommandError),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -627,6 +836,11 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
MqttTopic::Profile => profile_message(message),
|
MqttTopic::Profile => profile_message(message),
|
||||||
MqttTopic::Save => Ok((message, MqttCommand::Save)),
|
MqttTopic::Save => Ok((message, MqttCommand::Save)),
|
||||||
MqttTopic::Load => Ok((message, MqttCommand::Load)),
|
MqttTopic::Load => Ok((message, MqttCommand::Load)),
|
||||||
|
MqttTopic::DefaultFTW(ch) => default_frequency_message(ch, message),
|
||||||
|
MqttTopic::DefaultASF(ch) => default_amplitude_message(ch, message),
|
||||||
|
MqttTopic::AppendBytes => unreachable!(), // This topic should not be parsed
|
||||||
|
MqttTopic::CommitBuffer => commit_buffer_message(message),
|
||||||
|
MqttTopic::RAM(ch, pr) => ram_message(ch, pr, message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,6 +862,13 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
|
||||||
MqttCommand::Profile(prof) => self.urukul.set_profile(prof),
|
MqttCommand::Profile(prof) => self.urukul.set_profile(prof),
|
||||||
MqttCommand::Save => self.save_device(),
|
MqttCommand::Save => self.save_device(),
|
||||||
MqttCommand::Load => self.load_device(),
|
MqttCommand::Load => self.load_device(),
|
||||||
|
MqttCommand::DefaultFTW(ch, freq) => self.urukul.set_channel_default_ftw(ch, freq),
|
||||||
|
MqttCommand::DefaultASF(ch, ampl) => self.urukul.set_channel_default_asf(ch, ampl),
|
||||||
|
MqttCommand::AppendBytes(_) => Ok(()), // The bytes were not parsed and pushed
|
||||||
|
MqttCommand::CommitBuffer(dest, start_addr, ch) => self.urukul.commit_ram_buffer_to_channel(ch, start_addr, dest),
|
||||||
|
MqttCommand::RAM(ch, pr, start_addr, end_addr, op_mode, ramp_rate) => {
|
||||||
|
self.urukul.set_channel_ram_profile(ch, pr, start_addr, end_addr, op_mode, ramp_rate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -951,7 +1172,17 @@ fn singletone_frequency_message(channel: u8, profile: u8, message: &[u8]) -> IRe
|
||||||
)(message)
|
)(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser for Singletone AMplitude Command Message
|
// Parser for default frequency Command Message
|
||||||
|
fn default_frequency_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
read_frequency,
|
||||||
|
|freq: f64| MqttCommand::DefaultFTW(channel, freq)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser for Singletone Amplitude Command Message
|
||||||
fn singletone_amplitude_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
fn singletone_amplitude_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
all_consuming(
|
all_consuming(
|
||||||
map(
|
map(
|
||||||
|
@ -961,6 +1192,16 @@ fn singletone_amplitude_message(channel: u8, profile: u8, message: &[u8]) -> IRe
|
||||||
)(message)
|
)(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parser for Default Amplitude Command Message
|
||||||
|
fn default_amplitude_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
double,
|
||||||
|
|ampl: f64| MqttCommand::DefaultASF(channel, ampl)
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
// Parser for Phase Command Message
|
// Parser for Phase Command Message
|
||||||
fn singletone_phase_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
fn singletone_phase_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
all_consuming(
|
all_consuming(
|
||||||
|
@ -994,24 +1235,6 @@ fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
preceded(
|
|
||||||
tag_no_case("phase:"),
|
|
||||||
preceded(
|
|
||||||
whitespace,
|
|
||||||
terminated(
|
|
||||||
double,
|
|
||||||
preceded(
|
|
||||||
opt(
|
|
||||||
preceded(
|
|
||||||
whitespace,
|
|
||||||
tag_no_case("deg")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
message_separator
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
preceded(
|
preceded(
|
||||||
tag_no_case("amplitude:"),
|
tag_no_case("amplitude:"),
|
||||||
preceded(
|
preceded(
|
||||||
|
@ -1021,9 +1244,29 @@ fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8]
|
||||||
message_separator
|
message_separator
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
opt(
|
||||||
|
preceded(
|
||||||
|
tag_no_case("phase:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
double,
|
||||||
|
preceded(
|
||||||
|
opt(
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
tag_no_case("deg")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
|(freq, phase, ampl): (f64, f64, f64)| MqttCommand::Singletone(channel, profile, freq, phase, ampl)
|
|(freq, ampl, phase): (f64, f64, Option<f64>)| MqttCommand::Singletone(channel, profile, freq, phase.unwrap_or(0.0), ampl)
|
||||||
)
|
)
|
||||||
)(message)
|
)(message)
|
||||||
}
|
}
|
||||||
|
@ -1040,3 +1283,123 @@ fn profile_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
)
|
)
|
||||||
)(message)
|
)(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn commit_buffer_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
permutation((
|
||||||
|
preceded(
|
||||||
|
tag_no_case("dest:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
alt((
|
||||||
|
value(RAMDestination::Frequency, tag_no_case("frequency")),
|
||||||
|
value(RAMDestination::Amplitude, tag_no_case("amplitude")),
|
||||||
|
value(RAMDestination::Phase, tag_no_case("phase")),
|
||||||
|
value(RAMDestination::Polar, tag_no_case("polar")),
|
||||||
|
)),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
preceded(
|
||||||
|
tag_no_case("start:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
map_res(
|
||||||
|
digit1,
|
||||||
|
|start_addr: &[u8]| u16::from_str_radix(core::str::from_utf8(start_addr).unwrap(), 10)
|
||||||
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
preceded(
|
||||||
|
tag_no_case("ch:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
map_res(
|
||||||
|
digit1,
|
||||||
|
|ch: &[u8]| u8::from_str_radix(core::str::from_utf8(ch).unwrap(), 10)
|
||||||
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
|(dest, start_addr, ch): (RAMDestination, u16, u8)| {
|
||||||
|
MqttCommand::CommitBuffer(dest, start_addr, ch)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ram_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
|
all_consuming(
|
||||||
|
map(
|
||||||
|
permutation((
|
||||||
|
preceded(
|
||||||
|
tag_no_case("start:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
map_res(
|
||||||
|
digit1,
|
||||||
|
|ch: &[u8]| u16::from_str_radix(core::str::from_utf8(ch).unwrap(), 10)
|
||||||
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
preceded(
|
||||||
|
tag_no_case("end:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
map_res(
|
||||||
|
digit1,
|
||||||
|
|ch: &[u8]| u16::from_str_radix(core::str::from_utf8(ch).unwrap(), 10)
|
||||||
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
preceded(
|
||||||
|
tag_no_case("op_mode:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
alt((
|
||||||
|
value(RAMOperationMode::DirectSwitch, tag_no_case("DS")),
|
||||||
|
value(RAMOperationMode::RampUp, tag_no_case("RU")),
|
||||||
|
value(RAMOperationMode::BidirectionalRamp, tag_no_case("BDR")),
|
||||||
|
value(RAMOperationMode::ContinuousBidirectionalRamp, tag_no_case("CBDR")),
|
||||||
|
value(RAMOperationMode::ContinuousRecirculate, tag_no_case("CR")),
|
||||||
|
)),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
preceded(
|
||||||
|
tag_no_case("ramp:"),
|
||||||
|
preceded(
|
||||||
|
whitespace,
|
||||||
|
terminated(
|
||||||
|
map_res(
|
||||||
|
digit1,
|
||||||
|
|ch: &[u8]| u16::from_str_radix(core::str::from_utf8(ch).unwrap(), 10)
|
||||||
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
|(start_addr, end_addr, op_mode, ramp_rate): (u16, u16, RAMOperationMode, u16)| {
|
||||||
|
MqttCommand::RAM(channel, profile, start_addr, end_addr, op_mode, ramp_rate)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)(message)
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::config_register::ConfigRegister;
|
||||||
use crate::config_register::CFGMask;
|
use crate::config_register::CFGMask;
|
||||||
use crate::config_register::StatusMask;
|
use crate::config_register::StatusMask;
|
||||||
use crate::attenuator::Attenuator;
|
use crate::attenuator::Attenuator;
|
||||||
use crate::dds::{ DDS, RAMOperationMode };
|
use crate::dds::{ DDS, RAMOperationMode, RAMDestination };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enum for structuring error
|
* Enum for structuring error
|
||||||
|
@ -56,7 +56,7 @@ pub struct Urukul<SPI> {
|
||||||
config_register: ConfigRegister<SPI>,
|
config_register: ConfigRegister<SPI>,
|
||||||
attenuator: Attenuator<SPI>,
|
attenuator: Attenuator<SPI>,
|
||||||
multi_dds: DDS<SPI>,
|
multi_dds: DDS<SPI>,
|
||||||
dds: [DDS<SPI>; 4],
|
pub dds: [DDS<SPI>; 4],
|
||||||
f_master_clk: f64,
|
f_master_clk: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,14 +329,52 @@ where
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_frequency_sweep_profile(&mut self, channel: u8, profile: u8, start_addr: u16, lower_boundary: f64,
|
pub fn append_dds_ram_buffer(&mut self, data: &[u8]) -> Result<(), Error<E>> {
|
||||||
upper_boundary: f64, f_resolution: f64, playback_rate: f64) -> Result<(), Error<E>>
|
unsafe {
|
||||||
{
|
Ok(crate::dds::append_ram_byte(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use profile 7 to write into the RAM
|
||||||
|
pub fn commit_ram_buffer_to_channel(&mut self, channel: u8, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error<E>> {
|
||||||
|
let profile = self.get_profile()?;
|
||||||
|
self.set_profile(7)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
self.dds[usize::from(channel)]
|
self.dds[usize::from(channel)]
|
||||||
.set_frequency_sweep_profile(profile, start_addr, lower_boundary, upper_boundary,
|
.commit_ram_buffer(start_addr, ram_dest)?;
|
||||||
f_resolution, true, RAMOperationMode::ContinuousRecirculate, playback_rate)
|
|
||||||
}
|
}
|
||||||
|
self.set_profile(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_channel_default_ftw(&mut self, channel: u8, frequency: f64) -> Result<(), Error<E>> {
|
||||||
|
self.dds[usize::from(channel)].set_default_ftw(frequency)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_channel_default_asf(&mut self, channel: u8, amplitude_scale: f64) -> Result<(), Error<E>> {
|
||||||
|
self.dds[usize::from(channel)].set_default_asf(amplitude_scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_channel_default_ftw(&mut self, channel: u8) -> Result<f64, Error<E>> {
|
||||||
|
self.dds[usize::from(channel)].get_default_ftw()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_channel_default_asf(&mut self, channel: u8) -> Result<f64, Error<E>> {
|
||||||
|
self.dds[usize::from(channel)].get_default_asf()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_channel_ram_profile(&mut self, channel: u8, profile: u8, start_addr: u16,
|
||||||
|
end_addr: u16, op_mode: RAMOperationMode, ramp_rate: u16
|
||||||
|
) -> Result<(), Error<E>> {
|
||||||
|
self.dds[usize::from(channel)]
|
||||||
|
.set_up_ram_profile(profile, start_addr, end_addr, true, false, op_mode, ramp_rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_channel_ram_profile(&mut self, channel: u8, profile: u8) -> Result<(u16, u16, u16, u8), Error<E>> {
|
||||||
|
self.dds[usize::from(channel)].get_ram_profile(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_channel_ram_mode_enabled(&mut self, channel: u8) -> Result<bool, Error<E>> {
|
||||||
|
self.dds[usize::from(channel)].ram_mode_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Error<E>> {
|
pub fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Error<E>> {
|
||||||
|
|
Loading…
Reference in New Issue