Compare commits

..

12 Commits

7 changed files with 518 additions and 189 deletions

View File

@ -9,44 +9,47 @@ Start nix shell before anything.
nix-shell
```
Flash firmware onto STM32 NUCLEO-H743ZI2 using OpenOCD.
**(For users who had completed the [networking setup](##networking-setup-for-first-time-user))** Flash firmware onto STM32 NUCLEO-H743ZI2 using OpenOCD.
```shell
openocd -f openocd/openocd.cfg -f openocd/main.cfg
```
Alternatively, an equivalent Nix command can also do the work
**(For users who had completed the [networking setup](##networking-setup-for-first-time-user))** Alternatively, an equivalent Nix command can also do the work.
```shell
openocd-flash main
openocd-flash
```
## Networking Setup
At the moment, both IP addresses of the STM32 board and MQTT broker are hardcoded.
MAC address of the STM32 board is also hardcoded.
## Networking Setup for First-time User
Provide them to setup Humpback-DDS:
- IP Address of Humpback-DDS
- Address block of the local area network
- MAC Address of Humpback-DDS
- IP Address of MQTT broker
- Device name of Humpback-DDS
### STM32 IP Address
IP address is hardcoded in the file `src/main.rs`, line 171.
```rust
store.ip_addrs[0] = net::wire::IpCidr::new(net::wire::IpAddress::v4(192, 168, 1, 200), 24);
```
The IP address shown above corresponds to `192.168.1.200`, in a `/24` address block.
Modify this line to the change the IP address of STM32 board.
Note:
- IP/MAC address of Humpback-DDS should be unique inside a local area network.
- Device name should be unique among all Humpback-DDS connected to the same MQTT broker.
- The MQTT broker must accept TCP connection at port `1883`.
### STM32 MAC Address
IP address is hardcoded in the file `src/main.rs`, line 156.
```rust
let mac_addr = net::wire::EthernetAddress([0xAC, 0x6F, 0x7A, 0xDE, 0xD6, 0xC8]);
Use the following Nix command.
```shell
openocd-flash-customised <client_cidr_ip_addr> <mac_addr> <broker_addr> "<name>"
```
The MAC address shown above corresponds to `AC::6F::7A::DE::D6::C8`.
Modify this line to the change the MAC address of STM32 board.
Parameters:
- client_cidr_ip_addr: IP address for the Humpback-DDS device, in CIDR notation
- mac_addr: MAC address for the Humpback-DDS device
- broker_addr: IP address for the MQTT broker
- name: Device name of the Humpback-DDS
### Broker IP Address
IP address is hardcoded in the file `src/main.rs`, line 241.
```rust
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 125)),
### Example
```shell
openocd-flash-customised 192.168.1.200/24 AC:6F:7A:DE:D6:C8 192.168.1.125 "Urukul"
```
This program will try attempt to connect to `192.168.1.125:1883`.
Modify this line to the change the IP address of MQTT broker.
Note that the broker must accept TCP connection at `port 1883`.
The device will be named `Urukul`.
It has `192.168.1.200` as IPv4 Address, inside a `\24` network, with `AC:6F:7A:DE:D6:C8` as MAC address.
It will connect to a MQTT broker at `192.168.1.125:1883`.
## MQTT Broker
Mosquitto is within the Nix package.
@ -85,23 +88,26 @@ publish-mqtt Foo/Bar "baz"
## List of Commands
All currently supported commands are listed below.
**Note: The following table only lists the subtopic. To make a full topic, add `Urukul/Control/` in front of all subtopics.**
Example: Full topic of Reset command: `Urukul/Control/Reset`
### Example: Full topic of Reset command
```shell
Urukul/Control/Reset
```
| Subtopic| Message| Functionality|
| ---| ---| ---|
| `Reset`| | Reset the device|
| `ChannelX/Switch`| `<off/on>`| Turn off/on the RF switch at channel X.|
| `ChannelX/Attenuation`| `<atten> [dB]`| Set attenuation of channel X.|
| `Clock/Source`| `<clk_src>`| Select the clock source of Urukul.|
| `Clock/Frequency`| `<f_clk> [unit]`| Set the clock frequency of the clock source of Urukul.|
| `Clock/Source`| `<clk_div>`| Set the clock division of Urukul.|
| `Clock`| `frequency: <f_clk> [unit], source: <clk_src>, division: <clk_div>`| Setup the clock tree for Urukul.|
| `ChannelX/SystemClock`| `<f_sys_clk> [unit]`| Set the system clock frequency of channel X.|
| `ChannelX/ProfileY/Singletone/Frequency`| `<freq> [unit]`| Setup a single tone profile at channel X, profile Y, with frequency `<freq> [unit]`.|
| `ChannelX/ProfileY/Singletone/Amplitude`| `<ampl>`| Setup a single tone profile at channel X, profile Y, with amplitude factor `<ampl>`.|
| `ChannelX/ProfileY/Singletone/Phase`| `<phase> [deg]`| Setup a single tone profile at channel X, profile Y, with phase `<phase> [deg]`.|
| `ChannelX/ProfileY/Singletone`| `frequency: <freq> [unit], amplitude: <ampl>, phase: <phase>`| Setup a compelte single tone profile at channel X, profile Y.|
| `Profile`| `<pr_num>`| Switch to a DDS profile across all channels.|
| Subtopic | Message | Functionality|
| --------------------------------------- | ------------------------------------------------------------------ | ---|
| `Reset` | | Reset the device|
| `ChannelX/Switch` | `<off/on>` | Turn off/on the RF switch at channel X.|
| `ChannelX/Attenuation` | `<atten> [dB]` | Set attenuation of channel X.|
| `Clock/Source` | `<clk_src>` | Select the clock source of Urukul.|
| `Clock/Frequency` | `<f_clk> [unit]` | Set the clock frequency of the clock source of Urukul.|
| `Clock/Source` | `<clk_div>` | Set the clock division of Urukul.|
| `Clock` | `frequency: <f_clk> [unit], source: <clk_src>, division: <clk_div>`| Setup the clock tree for Urukul.|
| `ChannelX/SystemClock` | `<f_sys_clk> [unit]` | Set the system clock frequency of channel X.|
| `ChannelX/ProfileY/Singletone/Frequency`| `<freq> [unit]` | Setup a single tone profile at channel X, profile Y, with frequency `<freq> [unit]`.|
| `ChannelX/ProfileY/Singletone/Amplitude`| `<ampl>` | Setup a single tone profile at channel X, profile Y, with amplitude factor `<ampl>`.|
| `ChannelX/ProfileY/Singletone/Phase` | `<phase> [deg]` | Setup a single tone profile at channel X, profile Y, with phase `<phase> [deg]`.|
| `ChannelX/ProfileY/Singletone` | `frequency: <freq> [unit], amplitude: <ampl>, phase: <phase>` | Setup a compelte single tone profile at channel X, profile Y.|
| `Profile` | `<pr_num>` | Switch to a DDS profile across all channels.|
## Reset the device
- Topic: `Urukul/Control/Reset`

View File

@ -1,7 +1,7 @@
MEMORY
{
/* FLASH and RAM are mandatory memory regions */
FLASH : ORIGIN = 0x08000000, LENGTH = 2M
FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
RAM : ORIGIN = 0x20000000, LENGTH = 128K
/* AXISRAM */

View File

@ -8,39 +8,45 @@ let
rustPlatform = callPackage ./nix/rustPlatform.nix {};
itm = callPackage ./nix/itm.nix {inherit rustPlatform;};
runOpenOcd = writeShellScriptBin "run-openocd" ''
openocd \
-f openocd/openocd.cfg \
-c init &
sleep 1
'';
runOpenOcdBlock = writeShellScriptBin "run-openocd-block" ''
openocd -f openocd/openocd.cfg
'';
setGDBConfigFile = writeShellScriptBin "set-gdb-config-file" ''
if [[ $1 == "" ]]
then
sed -i "2s/.*/runner = \"gdb -q -x gdb_config\/openocd.gdb\"/" .cargo/config
echo "GDB config file: openocd.gdb"
else
sed -i "2s/.*/runner = \"gdb -q -x gdb_config\/$1.gdb\"/" .cargo/config
echo "GDB config file: $1.gdb"
fi
'';
openocdFlash = writeShellScriptBin "openocd-flash" ''
openocd -f openocd/openocd.cfg -f openocd/$1.cfg
openocd -f openocd/openocd.cfg -f openocd/main.cfg
'';
publishMqtt = writeShellScriptBin "publish-mqtt" ''
mosquitto_pub -h localhost -t $1 -m "$2" -d
'';
openOCDFlashCustomised = writeShellScriptBin "openocd-flash-customised" ''
IFS='.|/' read -r a b c d e <<< $1
((ip = (a << 32) + (b << 24) + (c << 16) + (d << 8) + e))
IFS=':' read -r a b c d e f <<< $2
((mac = (16#$a << 40) + (16#$b << 32) + (16#$c << 24) + (16#$d << 16) + (16#$e << 8) + 16#$f))
IFS='.' read -r a b c d <<< $3
((broker_ip = (a << 24) + (b << 16) + (c << 8) + d))
touch temp_name
printf "%s\x04" "$4" > temp_name
openocd -f openocd/openocd.cfg \
-c "init
reset init
halt
stm32h7x mass_erase 1
flash write_image erase target/thumbv7em-none-eabihf/release/humpback-dds
flash filld 0x08100000 $ip 1
flash filld 0x08100020 $mac 1
flash fillw 0x08100040 $broker_ip 1
flash write_image temp_name 0x08100060 bin
reset run
shutdown"
rm temp_name
'';
in
stdenv.mkDerivation {
name = "nix-shell";
name = "Humpback-DDS";
buildInputs = with rustPlatform.rust; [
(pkgs.python3.withPackages(ps: [ migen ]))
pkgs.yosys
@ -52,10 +58,9 @@ in
rustc
cargo
itm
runOpenOcd
runOpenOcdBlock
setGDBConfigFile
openocdFlash
publishMqtt
openOCDFlashCustomised
];
}

View File

@ -187,6 +187,9 @@ where
}
// Change external clock source (ref_clk)
// This will always provide a legitimate f_sys_clk by the following priority
// 1. Keep DDS clock tree untouched, record the new f_sys_clk
// 2. Use the default divided-by-2 clock tree, if PLL configuration becomes invalid
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;
@ -332,7 +335,7 @@ where
pub fn set_configurations(&mut self, mask_pairs: &mut[(DDSCFRMask, u32)]) -> Result<(), Error<E>> {
let mut data_array = self.get_all_configurations()?;
for index in 0..mask_pairs.len() {
// Reject any attempt to rewrite LSB_FIRST and SBIO_INPUT_ONLY
// Reject any attempt to rewrite LSB_FIRST and SDIO_INPUT_ONLY
if mask_pairs[index].0 == DDSCFRMask::LSB_FIRST || mask_pairs[index].0 == DDSCFRMask::SDIO_IN_ONLY {
continue;
}
@ -343,6 +346,9 @@ where
_ => panic!("Invalid DDSCFRMask!"),
};
}
// Deterministically maintain LSB_FIRST and SDIO_INPUT_ONLY
DDSCFRMask::LSB_FIRST.set_data_by_arg(&mut data_array[0], 0);
DDSCFRMask::SDIO_IN_ONLY.set_data_by_arg(&mut data_array[0], 1);
self.set_all_configurations(data_array.clone())
}
@ -379,6 +385,34 @@ where
])
}
/*
* Getter function for single tone profiles
*/
pub fn get_single_tone_profile(&mut self, profile: u8) -> Result<(f64, f64, f64), Error<E>> {
assert!(profile < 8);
let mut profile_content: [u8; 8] = [0; 8];
self.read_register(0x0E + profile, &mut profile_content)?;
// Convert ftw, pow and asf to f_out, phase and amplitude factor
let ftw: u64 = (profile_content[4] as u64) << 24 |
(profile_content[5] as u64) << 16 |
(profile_content[6] as u64) << 8 |
(profile_content[7] as u64);
let f_out: f64 = ((ftw as f64)/(((1_u64) << 32) as f64))*self.f_sys_clk;
let pow: u64 = (profile_content[2] as u64) << 8 |
(profile_content[3] as u64);
let phase: f64 = ((pow as f64)/(((1_u64) << 16) as f64))*360.0;
let asf: u64 = (profile_content[0] as u64) << 8 |
(profile_content[1] as u64);
let amplitude: f64 = (asf as f64)/(((1_u64) << 14) as f64);
Ok((f_out, phase, amplitude))
}
/*
* Set frequency of a single tone profile
* Frequency: Must be non-negative
@ -831,7 +865,9 @@ macro_rules! impl_register_io {
}
self.spi.transfer(&mut arr)
.map(|_| ())
.map_err(Error::SPI)
.map_err(Error::SPI)?;
debug!("Write register: {:X}, Bytes: {:X?}", addr, bytes);
Ok(())
},
)*
_ => panic!("Bad address for DDS writing.")
@ -851,6 +887,7 @@ macro_rules! impl_register_io {
for i in 0..$reg_byte_size {
bytes[i] = ret[i+1];
}
debug!("Read register: {:X}, Bytes: {:X?}", addr, bytes);
Ok(bytes)
},
Err(e) => Err(e),

View File

@ -18,7 +18,9 @@ use cortex_m;
use cortex_m_rt::entry;
use rtic::cyccnt::{Instant, U32Ext};
use heapless::consts;
use heapless::{ String, consts, consts::* };
use core::convert::TryInto;
#[macro_use]
pub mod bitmask_macro;
@ -110,6 +112,58 @@ fn main() -> ! {
let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF);
let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG);
// Acquire client/broker IP Address, client MAC address from flash memory
let ipv4_addr_cidr = unsafe {
let ipv4_bits = core::ptr::read(0x08100000 as *const u64);
net::wire::IpCidr::new(
net::wire::IpAddress::v4(
((ipv4_bits >> 32) & 0xFF).try_into().unwrap(),
((ipv4_bits >> 24) & 0xFF).try_into().unwrap(),
((ipv4_bits >> 16) & 0xFF).try_into().unwrap(),
((ipv4_bits >> 8) & 0xFF).try_into().unwrap()
),
((ipv4_bits >> 0) & 0xFF).try_into().unwrap()
)
};
let mac_addr = unsafe {
let mac_bits = core::ptr::read(0x08100020 as *const u64);
net::wire::EthernetAddress([
((mac_bits >> 40) & 0xFF).try_into().unwrap(),
((mac_bits >> 32) & 0xFF).try_into().unwrap(),
((mac_bits >> 24) & 0xFF).try_into().unwrap(),
((mac_bits >> 16) & 0xFF).try_into().unwrap(),
((mac_bits >> 8) & 0xFF).try_into().unwrap(),
(mac_bits & 0xFF).try_into().unwrap(),
])
};
let broker_ipv4_addr = unsafe {
let ipv4_bits = core::ptr::read(0x08100040 as *const u64);
Ipv4Addr::new(
((ipv4_bits >> 24) & 0xFF).try_into().unwrap(),
((ipv4_bits >> 16) & 0xFF).try_into().unwrap(),
((ipv4_bits >> 8) & 0xFF).try_into().unwrap(),
((ipv4_bits >> 0) & 0xFF).try_into().unwrap()
)
};
let device_name: String<U32> = unsafe {
let mut name = String::new();
let mut addr = 0x08100060;
loop {
let c = core::ptr::read(addr as *const u8);
if c == 4 {
break;
} else {
name.push(c as char).unwrap();
addr += 1;
}
}
name
};
// Note: ITM doesn't work beyond this, due to a pin conflict between:
// - FPGA_SPI: SCK (af5)
// - ST_LINK SWO (af0)
@ -153,7 +207,7 @@ fn main() -> ! {
}
// Configure ethernet
let mac_addr = net::wire::EthernetAddress([0xAC, 0x6F, 0x7A, 0xDE, 0xD6, 0xC8]);
// let mac_addr = net::wire::EthernetAddress([0xAC, 0x6F, 0x7A, 0xDE, 0xD6, 0xC8]);
let (eth_dma, mut eth_mac) = unsafe {
ethernet::new_unchecked(
dp.ETHERNET_MAC,
@ -168,7 +222,7 @@ fn main() -> ! {
let store = unsafe { &mut NET_STORE };
store.ip_addrs[0] = net::wire::IpCidr::new(net::wire::IpAddress::v4(192, 168, 1, 200), 24);
store.ip_addrs[0] = ipv4_addr_cidr;
let neighbor_cache = net::iface::NeighborCache::new(&mut store.neighbor_cache[..]);
@ -218,10 +272,9 @@ fn main() -> ! {
let mut urukul = Urukul::new(
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7
);
urukul.reset().unwrap();
let mut mqtt_mux = MqttMux::new(urukul);
let mut mqtt_mux = MqttMux::new(urukul, device_name.as_str());
// Time unit in ms
let mut time: u32 = 0;
@ -238,8 +291,8 @@ fn main() -> ! {
let tcp_stack = NetworkStack::new(&mut net_interface, sockets);
let mut client = MqttClient::<consts::U256, _>::new(
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 125)),
"Urukul",
IpAddr::V4(broker_ipv4_addr),
device_name.as_str(),
tcp_stack,
)
.unwrap();
@ -276,18 +329,19 @@ fn main() -> ! {
};
// Process MQTT response messages about Urukul
match mqtt_mux.process_mqtt_egress().unwrap() {
Some((topic, message)) => client.publish(
topic,
for (topic, message) in mqtt_mux.process_mqtt_egress().unwrap() {
client.publish(
topic.as_str(),
message.as_bytes(),
QoS::AtMostOnce,
&[]
).unwrap(),
None => {},
).unwrap();
}
if connection && !has_subscribed && tick {
match client.subscribe("Urukul/Control/#", &[]) {
let mut str_builder: String<U128> = String::from(device_name.as_str());
str_builder.push_str("/Control/#").unwrap();
match client.subscribe(str_builder.as_str(), &[]) {
Ok(()) => has_subscribed = true,
Err(minimq::Error::NotReady) => {},
_e => {},

View File

@ -6,8 +6,7 @@ use nom::character::complete::digit1;
use nom::character::is_space;
use nom::branch::{permutation, alt};
use nom::number::complete::{float, double};
use heapless::String;
use heapless::consts::*;
use heapless::{ Vec, String, consts::* };
use ryu;
use embedded_hal::blocking::spi::Transfer;
use core::convert::TryInto;
@ -36,7 +35,7 @@ pub enum MqttTopic {
// Such that Urukul accepts the enum directly
#[derive(Debug, Clone)]
pub enum MqttCommand {
ProcessError,
ProcessError(&'static str),
Reset,
Switch(u8, bool),
Attenuation(u8, f32),
@ -52,19 +51,19 @@ pub enum MqttCommand {
Profile(u8)
}
pub struct MqttMux<SPI> {
pub struct MqttMux<'s, SPI> {
urukul: Urukul<SPI>,
yet_to_respond: Option<MqttCommand>,
str_builder: String<U128>,
name: &'s str,
float_buffer: ryu::Buffer,
}
impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
pub fn new(urukul: Urukul<SPI>) -> Self {
impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
pub fn new(urukul: Urukul<SPI>, name: &'s str) -> Self {
MqttMux {
urukul: urukul,
yet_to_respond: None,
str_builder: String::new(),
name: name,
float_buffer: ryu::Buffer::new(),
}
}
@ -75,19 +74,19 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
let topic = match self.parse_topic(topic) {
Ok(t) => t,
Err(_) => {
self.yet_to_respond = Some(MqttCommand::ProcessError);
self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot prase MQTT topic"));
return;
}
};
let command = match self.parse_message(topic, message) {
Ok((_, cmd)) => cmd,
Err(_) => {
self.yet_to_respond = Some(MqttCommand::ProcessError);
self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot parse MQTT message"));
return;
}
};
self.yet_to_respond = match self.execute(command.clone()) {
Err(_) => Some(MqttCommand::ProcessError),
Err(_) => Some(MqttCommand::ProcessError("Cannot execute MQTT command")),
Ok(()) => Some(command)
};
}
@ -95,43 +94,56 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
// Be sure to call egress function after each ingress.
// Otherwise, response will be lost if successive valid MQTT messages were captured
// without calling egress in between
pub fn process_mqtt_egress(&mut self) -> Result<Option<(&str, String<U64>)>, Error<E>> {
pub fn process_mqtt_egress(&mut self) -> Result<Vec<(String<U128>, String<U64>), U4>, Error<E>> {
// Remove previously executed command, and process it afterwards
let prev_cmd = self.yet_to_respond.clone();
self.yet_to_respond = None;
let mut vec = Vec::new();
match prev_cmd {
Some(cmd) => match cmd {
MqttCommand::ProcessError => Ok(
Some((
"Urukul/Feedback/Error",
String::from("Cannot parse the previous command.")
))
),
MqttCommand::Reset => Ok(
Some((
"Urukul/Feedback/Reset",
MqttCommand::ProcessError(e_str) => {
vec.push((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Error")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
String::from(e_str)
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::Reset => {
vec.push((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Reset")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
String::from(
match self.urukul.test() {
Ok(0) => "Reset successful.",
_ => "Reset error!",
}
)
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
))
),
MqttCommand::Switch(ch, _) => Ok(
Some((
MqttCommand::Switch(ch, _) => {
vec.push((
{
self.str_builder.clear();
self.str_builder.push_str("Urukul/Feedback/Channel")
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Channel")
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push(char::from_digit(ch.into(), 10).unwrap())
topic_string.push(char::from_digit(ch.into(), 10).unwrap())
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push_str("/Switch")
topic_string.push_str("/Switch")
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.as_str()
topic_string
},
{
String::from(
@ -142,19 +154,21 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
}
)
}
))
),
MqttCommand::Attenuation(ch, _) => Ok(
Some((
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::Attenuation(ch, _) => {
vec.push((
{
self.str_builder.clear();
self.str_builder.push_str("Urukul/Feedback/Channel")
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Channel")
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push(char::from_digit(ch.into(), 10).unwrap())
topic_string.push(char::from_digit(ch.into(), 10).unwrap())
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push_str("/Attenuation")
topic_string.push_str("/Attenuation")
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.as_str()
topic_string
},
{
String::from(
@ -163,19 +177,55 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
)
)
}
))
),
MqttCommand::SystemClock(ch, _) => Ok(
Some((
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::Clock(_, _, _) => {
vec.push(
self.get_clock_source_message()?
).map_err(|_| Error::VectorOutOfSpace)?;
vec.push(
self.get_clock_frequency_message()?
).map_err(|_| Error::VectorOutOfSpace)?;
vec.push(
self.get_clock_division_message()?
).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::ClockSource(_) => {
vec.push(
self.get_clock_source_message()?
).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::ClockFrequency(_) => {
vec.push(
self.get_clock_frequency_message()?
).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::ClockDivision(_) => {
vec.push(
self.get_clock_division_message()?
).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::SystemClock(ch, _) => {
vec.push((
{
self.str_builder.clear();
self.str_builder.push_str("Urukul/Feedback/Channel")
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Channel")
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push(char::from_digit(ch.into(), 10).unwrap())
topic_string.push(char::from_digit(ch.into(), 10).unwrap())
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push_str("/SystemClock")
topic_string.push_str("/SystemClock")
.map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.as_str()
topic_string
},
{
let mut message_str = String::from(
@ -187,11 +237,32 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
.map_err(|_| Error::StringOutOfSpace)?;
message_str
}
))
),
MqttCommand::Profile(_) => Ok(
Some((
"Urukul/Feedback/Profile",
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::Singletone(ch, pr, _, _, _) |
MqttCommand::SingletoneFrequency(ch, pr, _) |
MqttCommand::SingletoneAmplitude(ch, pr, _) |
MqttCommand::SingletonePhase(ch, pr, _) => {
let (f_out, phase, ampl) = self.urukul.get_channel_single_tone_profile(ch, pr)?;
vec.push(self.get_single_tone_frequency_message(ch, f_out)?)
.map_err(|_| Error::StringOutOfSpace)?;
vec.push(self.get_single_tone_phase_message(ch, phase)?)
.map_err(|_| Error::StringOutOfSpace)?;
vec.push(self.get_single_tone_amplitude_message(ch, ampl)?)
.map_err(|_| Error::StringOutOfSpace)?;
Ok(vec)
}
MqttCommand::Profile(_) => {
vec.push((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Profile")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
{
let mut message_str = String::new();
let prof = self.urukul.get_profile()?;
@ -199,11 +270,11 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
.map_err(|_| Error::StringOutOfSpace)?;
message_str
}
))
),
_ => Ok(Some(("Urukul/Feedback/Unimplemented", String::from("test")))),
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
},
None => Ok(None),
None => Ok(vec),
}
}
@ -213,10 +284,20 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
let mut channel :u8 = 0;
let mut profile :u8 = 0;
// Verify that the topic must start with Urukul/Control/ or /Urukul/Control/
let mut header = topic.strip_prefix("/Urukul/Control/")
.or_else(|| topic.strip_prefix("Urukul/Control/"))
.ok_or(Error::MqttCommandError)?;
// Verify that the topic starts with <name>/Control/ or /<name>/Control/
let mut header = {
let mut topic_builder_with_slash: String<U128> = String::from("/");
topic_builder_with_slash.push_str(self.name)
.map_err(|_| Error::StringOutOfSpace)?;
topic_builder_with_slash.push_str("/Control/")
.map_err(|_| Error::StringOutOfSpace)?;
let topic_builder: &str = topic_builder_with_slash.as_str()
.strip_prefix("/")
.ok_or(Error::StringOutOfSpace)?;
topic.strip_prefix(topic_builder_with_slash.as_str())
.or_else(|| topic.strip_prefix(topic_builder))
.ok_or(Error::MqttCommandError)?
};
loop {
match header {
@ -399,7 +480,7 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
fn execute(&mut self, command: MqttCommand) -> Result<(), Error<E>> {
match command {
MqttCommand::ProcessError => Ok(()),
MqttCommand::ProcessError(_) => Ok(()),
MqttCommand::Reset => self.urukul.reset(),
MqttCommand::Switch(ch, state) => self.urukul.set_channel_switch(ch.into(), state),
MqttCommand::Attenuation(ch, ampl) => self.urukul.set_channel_attenuation(ch, ampl),
@ -416,13 +497,130 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
}
}
pub fn get_switch_status_message(&mut self, channel: u8) -> Result<&str, Error<E>> {
self.urukul.get_channel_switch_status(channel.into()).map(
|stat| if stat {
"on"
} else {
"off"
})
fn get_clock_source_message(&mut self) -> Result<(String<U128>, String<U64>), Error<E>> {
Ok((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Clock/Source")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
self.urukul.get_clock_source().map(
|src| match src {
UrukulClockSource::OSC => String::from("OSC"),
UrukulClockSource::MMCX => String::from("MMCX"),
UrukulClockSource::SMA => String::from("SMA")
}
)?
))
}
fn get_clock_frequency_message(&mut self) -> Result<(String<U128>, String<U64>), Error<E>> {
Ok((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Clock/Frequency")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
{
let mut freq_str = String::from(
self.float_buffer.format_finite(
self.urukul.get_clock_frequency()
)
);
freq_str.push_str(" Hz").map_err(|_| Error::StringOutOfSpace)?;
freq_str
}
))
}
fn get_clock_division_message(&mut self) -> Result<(String<U128>, String<U64>), Error<E>> {
Ok((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Clock/Division")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
{
self.urukul.get_clock_division().map(
|src| match src {
1 => String::from("1"),
2 => String::from("2"),
4 => String::from("4"),
_ => unreachable!()
}
)?
}
))
}
fn get_single_tone_frequency_message(&mut self, ch: u8, f_out: f64) -> Result<(String<U128>, String<U64>), Error<E>> {
Ok((
{
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("/Singletone/Frequency")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
{
let mut message_str = String::from(
self.float_buffer.format_finite(f_out)
);
message_str.push_str(" Hz")
.map_err(|_| Error::StringOutOfSpace)?;
message_str
}
))
}
fn get_single_tone_amplitude_message(&mut self, ch: u8, ampl: f64) -> Result<(String<U128>, String<U64>), Error<E>> {
Ok((
{
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("/Singletone/Amplitude")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
{
let message_str = String::from(
self.float_buffer.format_finite(ampl)
);
message_str
}
))
}
fn get_single_tone_phase_message(&mut self, ch: u8, phase: f64) -> Result<(String<U128>, String<U64>), Error<E>> {
Ok((
{
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("/Singletone/Phase")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
},
{
let mut message_str = String::from(
self.float_buffer.format_finite(phase)
);
message_str.push_str(" deg")
.map_err(|_| Error::StringOutOfSpace)?;
message_str
}
))
}
}

View File

@ -173,6 +173,18 @@ where
self.set_clock_division(division)
}
pub fn get_clock_source(&mut self) -> Result<ClockSource, Error<E>> {
match (
self.config_register.get_configuration(CFGMask::CLK_SEL0),
self.config_register.get_configuration(CFGMask::CLK_SEL1)
) {
(0, 0) => Ok(ClockSource::OSC),
(0, 1) => Ok(ClockSource::MMCX),
(1, _) => Ok(ClockSource::SMA),
_ => Err(Error::ConfigRegisterError)
}
}
pub fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Error<E>> {
// Change clock source through configuration register
match source {
@ -190,6 +202,10 @@ where
}.map(|_| ())
}
pub fn get_clock_frequency(&mut self) -> f64 {
self.f_master_clk
}
pub fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), Error<E>> {
// Update master clock frequency
self.f_master_clk = frequency;
@ -198,6 +214,15 @@ where
self.set_dds_ref_clk()
}
pub fn get_clock_division(&mut self) -> Result<u8, Error<E>> {
match self.config_register.get_configuration(CFGMask::DIV) {
0| 3 => Ok(4),
1 => Ok(1),
2 => Ok(2),
_ => Err(Error::ConfigRegisterError)
}
}
pub fn set_clock_division(&mut self, division: u8) -> Result<(), Error<E>> {
match division {
1 => self.config_register.set_configurations(&mut [
@ -267,6 +292,10 @@ where
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
}
pub fn get_channel_single_tone_profile(&mut self, channel: u8, profile: u8) -> Result<(f64, f64, f64), Error<E>> {
self.dds[usize::from(channel)].get_single_tone_profile(profile)
}
pub fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Error<E>> {
if channel >= 4 || profile >= 8 || frequency < 0.0 {
return Err(Error::ParameterError);