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 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 ```shell
openocd -f openocd/openocd.cfg -f openocd/main.cfg 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 ```shell
openocd-flash main openocd-flash
``` ```
## Networking Setup ## Networking Setup for First-time User
At the moment, both IP addresses of the STM32 board and MQTT broker are hardcoded. Provide them to setup Humpback-DDS:
MAC address of the STM32 board is also hardcoded. - 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 Note:
IP address is hardcoded in the file `src/main.rs`, line 171. - IP/MAC address of Humpback-DDS should be unique inside a local area network.
```rust - Device name should be unique among all Humpback-DDS connected to the same MQTT broker.
store.ip_addrs[0] = net::wire::IpCidr::new(net::wire::IpAddress::v4(192, 168, 1, 200), 24); - The MQTT broker must accept TCP connection at port `1883`.
```
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.
### STM32 MAC Address Use the following Nix command.
IP address is hardcoded in the file `src/main.rs`, line 156. ```shell
```rust openocd-flash-customised <client_cidr_ip_addr> <mac_addr> <broker_addr> "<name>"
let mac_addr = net::wire::EthernetAddress([0xAC, 0x6F, 0x7A, 0xDE, 0xD6, 0xC8]);
``` ```
The MAC address shown above corresponds to `AC::6F::7A::DE::D6::C8`. Parameters:
Modify this line to the change the MAC address of STM32 board. - 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 ### Example
IP address is hardcoded in the file `src/main.rs`, line 241. ```shell
```rust openocd-flash-customised 192.168.1.200/24 AC:6F:7A:DE:D6:C8 192.168.1.125 "Urukul"
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 125)),
``` ```
This program will try attempt to connect to `192.168.1.125:1883`. The device will be named `Urukul`.
Modify this line to the change the IP address of MQTT broker. It has `192.168.1.200` as IPv4 Address, inside a `\24` network, with `AC:6F:7A:DE:D6:C8` as MAC address.
Note that the broker must accept TCP connection at `port 1883`. It will connect to a MQTT broker at `192.168.1.125:1883`.
## MQTT Broker ## MQTT Broker
Mosquitto is within the Nix package. Mosquitto is within the Nix package.
@ -85,23 +88,26 @@ publish-mqtt Foo/Bar "baz"
## List of Commands ## List of Commands
All currently supported commands are listed below. 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.** **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| | Subtopic | Message | Functionality|
| ---| ---| ---| | --------------------------------------- | ------------------------------------------------------------------ | ---|
| `Reset`| | Reset the device| | `Reset` | | Reset the device|
| `ChannelX/Switch`| `<off/on>`| Turn off/on the RF switch at channel X.| | `ChannelX/Switch` | `<off/on>` | Turn off/on the RF switch at channel X.|
| `ChannelX/Attenuation`| `<atten> [dB]`| Set attenuation of channel X.| | `ChannelX/Attenuation` | `<atten> [dB]` | Set attenuation of channel X.|
| `Clock/Source`| `<clk_src>`| Select the clock source of Urukul.| | `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/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/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.| | `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/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/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/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/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.| | `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.| | `Profile` | `<pr_num>` | Switch to a DDS profile across all channels.|
## Reset the device ## Reset the device
- Topic: `Urukul/Control/Reset` - Topic: `Urukul/Control/Reset`

View File

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

109
shell.nix
View File

@ -1,61 +1,66 @@
let let
mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
pkgs = import <nixpkgs> {overlays = [mozillaOverlay];}; pkgs = import <nixpkgs> {overlays = [mozillaOverlay];};
in with pkgs; in with pkgs;
let let
migen = callPackage ./nix/migen.nix {}; migen = callPackage ./nix/migen.nix {};
openocd = callPackage ./nix/openocd.nix {}; openocd = callPackage ./nix/openocd.nix {};
rustPlatform = callPackage ./nix/rustPlatform.nix {}; rustPlatform = callPackage ./nix/rustPlatform.nix {};
itm = callPackage ./nix/itm.nix {inherit rustPlatform;}; itm = callPackage ./nix/itm.nix {inherit rustPlatform;};
runOpenOcd = writeShellScriptBin "run-openocd" '' runOpenOcdBlock = writeShellScriptBin "run-openocd-block" ''
openocd \ openocd -f openocd/openocd.cfg
-f openocd/openocd.cfg \ '';
-c init &
sleep 1
'';
runOpenOcdBlock = writeShellScriptBin "run-openocd-block" '' openocdFlash = writeShellScriptBin "openocd-flash" ''
openocd -f openocd/openocd.cfg openocd -f openocd/openocd.cfg -f openocd/main.cfg
''; '';
setGDBConfigFile = writeShellScriptBin "set-gdb-config-file" '' publishMqtt = writeShellScriptBin "publish-mqtt" ''
if [[ $1 == "" ]] mosquitto_pub -h localhost -t $1 -m "$2" -d
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" '' openOCDFlashCustomised = writeShellScriptBin "openocd-flash-customised" ''
openocd -f openocd/openocd.cfg -f openocd/$1.cfg 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
publishMqtt = writeShellScriptBin "publish-mqtt" '' ((mac = (16#$a << 40) + (16#$b << 32) + (16#$c << 24) + (16#$d << 16) + (16#$e << 8) + 16#$f))
mosquitto_pub -h localhost -t $1 -m "$2" -d 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 in
stdenv.mkDerivation { stdenv.mkDerivation {
name = "nix-shell"; name = "Humpback-DDS";
buildInputs = with rustPlatform.rust; [ buildInputs = with rustPlatform.rust; [
(pkgs.python3.withPackages(ps: [ migen ])) (pkgs.python3.withPackages(ps: [ migen ]))
pkgs.yosys pkgs.yosys
pkgs.nextpnr pkgs.nextpnr
pkgs.icestorm pkgs.icestorm
pkgs.gdb pkgs.gdb
pkgs.mosquitto pkgs.mosquitto
openocd openocd
rustc rustc
cargo cargo
itm itm
runOpenOcd runOpenOcdBlock
runOpenOcdBlock openocdFlash
setGDBConfigFile publishMqtt
openocdFlash openOCDFlashCustomised
publishMqtt ];
]; }
}

View File

@ -187,6 +187,9 @@ where
} }
// Change external clock source (ref_clk) // 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>> { pub fn set_ref_clk_frequency(&mut self, f_ref_clk: f64) -> Result<(), Error<E>> {
// Override old reference clock frequency (ref_clk) // Override old reference clock frequency (ref_clk)
self.f_ref_clk = f_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>> { pub fn set_configurations(&mut self, mask_pairs: &mut[(DDSCFRMask, u32)]) -> Result<(), Error<E>> {
let mut data_array = self.get_all_configurations()?; let mut data_array = self.get_all_configurations()?;
for index in 0..mask_pairs.len() { 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 { if mask_pairs[index].0 == DDSCFRMask::LSB_FIRST || mask_pairs[index].0 == DDSCFRMask::SDIO_IN_ONLY {
continue; continue;
} }
@ -343,6 +346,9 @@ where
_ => panic!("Invalid DDSCFRMask!"), _ => 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()) self.set_all_configurations(data_array.clone())
} }
@ -379,10 +385,38 @@ 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 * Set frequency of a single tone profile
* Frequency: Must be non-negative * Frequency: Must be non-negative
* Keep other field unchanged in the register * Keep other field unchanged in the register
*/ */
pub fn set_single_tone_profile_frequency(&mut self, profile: u8, f_out: f64) -> Result<(), Error<E>> { pub fn set_single_tone_profile_frequency(&mut self, profile: u8, f_out: f64) -> Result<(), Error<E>> {
@ -408,7 +442,7 @@ where
/* /*
* Set phase offset of a single tone profile * Set phase offset of a single tone profile
* Phase: Expressed in positive degree, i.e. [0.0, 360.0) * Phase: Expressed in positive degree, i.e. [0.0, 360.0)
* Keep other field unchanged in the register * Keep other field unchanged in the register
*/ */
pub fn set_single_tone_profile_phase(&mut self, profile: u8, phase_offset: f64) -> Result<(), Error<E>> { pub fn set_single_tone_profile_phase(&mut self, profile: u8, phase_offset: f64) -> Result<(), Error<E>> {
@ -432,7 +466,7 @@ where
/* /*
* Set amplitude offset of a single tone profile * Set amplitude offset of a single tone profile
* Amplitude: In a scale from 0 to 1, taking float * Amplitude: In a scale from 0 to 1, taking float
* Keep other field unchanged in the register * Keep other field unchanged in the register
*/ */
pub fn set_single_tone_profile_amplitude(&mut self, profile: u8, amp_scale_factor: f64) -> Result<(), Error<E>> { pub fn set_single_tone_profile_amplitude(&mut self, profile: u8, amp_scale_factor: f64) -> Result<(), Error<E>> {
@ -831,7 +865,9 @@ macro_rules! impl_register_io {
} }
self.spi.transfer(&mut arr) self.spi.transfer(&mut arr)
.map(|_| ()) .map(|_| ())
.map_err(Error::SPI) .map_err(Error::SPI)?;
debug!("Write register: {:X}, Bytes: {:X?}", addr, bytes);
Ok(())
}, },
)* )*
_ => panic!("Bad address for DDS writing.") _ => panic!("Bad address for DDS writing.")
@ -851,6 +887,7 @@ macro_rules! impl_register_io {
for i in 0..$reg_byte_size { for i in 0..$reg_byte_size {
bytes[i] = ret[i+1]; bytes[i] = ret[i+1];
} }
debug!("Read register: {:X}, Bytes: {:X?}", addr, bytes);
Ok(bytes) Ok(bytes)
}, },
Err(e) => Err(e), Err(e) => Err(e),

View File

@ -18,7 +18,9 @@ use cortex_m;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use rtic::cyccnt::{Instant, U32Ext}; use rtic::cyccnt::{Instant, U32Ext};
use heapless::consts; use heapless::{ String, consts, consts::* };
use core::convert::TryInto;
#[macro_use] #[macro_use]
pub mod bitmask_macro; pub mod bitmask_macro;
@ -110,6 +112,58 @@ fn main() -> ! {
let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF);
let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); 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: // Note: ITM doesn't work beyond this, due to a pin conflict between:
// - FPGA_SPI: SCK (af5) // - FPGA_SPI: SCK (af5)
// - ST_LINK SWO (af0) // - ST_LINK SWO (af0)
@ -153,7 +207,7 @@ fn main() -> ! {
} }
// Configure ethernet // 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 { let (eth_dma, mut eth_mac) = unsafe {
ethernet::new_unchecked( ethernet::new_unchecked(
dp.ETHERNET_MAC, dp.ETHERNET_MAC,
@ -168,7 +222,7 @@ fn main() -> ! {
let store = unsafe { &mut NET_STORE }; 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[..]); let neighbor_cache = net::iface::NeighborCache::new(&mut store.neighbor_cache[..]);
@ -218,10 +272,9 @@ 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
); );
urukul.reset().unwrap(); 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 // Time unit in ms
let mut time: u32 = 0; let mut time: u32 = 0;
@ -238,8 +291,8 @@ fn main() -> ! {
let tcp_stack = NetworkStack::new(&mut net_interface, sockets); let tcp_stack = NetworkStack::new(&mut net_interface, sockets);
let mut client = MqttClient::<consts::U256, _>::new( let mut client = MqttClient::<consts::U256, _>::new(
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 125)), IpAddr::V4(broker_ipv4_addr),
"Urukul", device_name.as_str(),
tcp_stack, tcp_stack,
) )
.unwrap(); .unwrap();
@ -276,18 +329,19 @@ fn main() -> ! {
}; };
// Process MQTT response messages about Urukul // Process MQTT response messages about Urukul
match mqtt_mux.process_mqtt_egress().unwrap() { for (topic, message) in mqtt_mux.process_mqtt_egress().unwrap() {
Some((topic, message)) => client.publish( client.publish(
topic, topic.as_str(),
message.as_bytes(), message.as_bytes(),
QoS::AtMostOnce, QoS::AtMostOnce,
&[] &[]
).unwrap(), ).unwrap();
None => {},
} }
if connection && !has_subscribed && tick { 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, Ok(()) => has_subscribed = true,
Err(minimq::Error::NotReady) => {}, Err(minimq::Error::NotReady) => {},
_e => {}, _e => {},

View File

@ -6,8 +6,7 @@ use nom::character::complete::digit1;
use nom::character::is_space; use nom::character::is_space;
use nom::branch::{permutation, alt}; use nom::branch::{permutation, alt};
use nom::number::complete::{float, double}; use nom::number::complete::{float, double};
use heapless::String; use heapless::{ Vec, String, consts::* };
use heapless::consts::*;
use ryu; use ryu;
use embedded_hal::blocking::spi::Transfer; use embedded_hal::blocking::spi::Transfer;
use core::convert::TryInto; use core::convert::TryInto;
@ -36,7 +35,7 @@ pub enum MqttTopic {
// Such that Urukul accepts the enum directly // Such that Urukul accepts the enum directly
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum MqttCommand { pub enum MqttCommand {
ProcessError, ProcessError(&'static str),
Reset, Reset,
Switch(u8, bool), Switch(u8, bool),
Attenuation(u8, f32), Attenuation(u8, f32),
@ -52,19 +51,19 @@ pub enum MqttCommand {
Profile(u8) Profile(u8)
} }
pub struct MqttMux<SPI> { pub struct MqttMux<'s, SPI> {
urukul: Urukul<SPI>, urukul: Urukul<SPI>,
yet_to_respond: Option<MqttCommand>, yet_to_respond: Option<MqttCommand>,
str_builder: String<U128>, name: &'s str,
float_buffer: ryu::Buffer, float_buffer: ryu::Buffer,
} }
impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> { impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer<u8, Error = E> {
pub fn new(urukul: Urukul<SPI>) -> Self { pub fn new(urukul: Urukul<SPI>, name: &'s str) -> Self {
MqttMux { MqttMux {
urukul: urukul, urukul: urukul,
yet_to_respond: None, yet_to_respond: None,
str_builder: String::new(), name: name,
float_buffer: ryu::Buffer::new(), 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) { let topic = match self.parse_topic(topic) {
Ok(t) => t, Ok(t) => t,
Err(_) => { Err(_) => {
self.yet_to_respond = Some(MqttCommand::ProcessError); self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot prase MQTT topic"));
return; return;
} }
}; };
let command = match self.parse_message(topic, message) { let command = match self.parse_message(topic, message) {
Ok((_, cmd)) => cmd, Ok((_, cmd)) => cmd,
Err(_) => { Err(_) => {
self.yet_to_respond = Some(MqttCommand::ProcessError); self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot parse MQTT message"));
return; return;
} }
}; };
self.yet_to_respond = match self.execute(command.clone()) { self.yet_to_respond = match self.execute(command.clone()) {
Err(_) => Some(MqttCommand::ProcessError), Err(_) => Some(MqttCommand::ProcessError("Cannot execute MQTT command")),
Ok(()) => Some(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. // Be sure to call egress function after each ingress.
// Otherwise, response will be lost if successive valid MQTT messages were captured // Otherwise, response will be lost if successive valid MQTT messages were captured
// without calling egress in between // 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 // Remove previously executed command, and process it afterwards
let prev_cmd = self.yet_to_respond.clone(); let prev_cmd = self.yet_to_respond.clone();
self.yet_to_respond = None; self.yet_to_respond = None;
let mut vec = Vec::new();
match prev_cmd { match prev_cmd {
Some(cmd) => match cmd { Some(cmd) => match cmd {
MqttCommand::ProcessError => Ok( MqttCommand::ProcessError(e_str) => {
Some(( vec.push((
"Urukul/Feedback/Error",
String::from("Cannot parse the previous command.")
))
),
MqttCommand::Reset => Ok(
Some((
"Urukul/Feedback/Reset",
{ {
String::from( let mut topic_string = String::from(self.name);
match self.urukul.test() { topic_string.push_str("/Feedback/Error")
Ok(0) => "Reset successful.", .map_err(|_| Error::StringOutOfSpace)?;
_ => "Reset error!", topic_string
} },
) String::from(e_str)
} )).map_err(|_| Error::VectorOutOfSpace)?;
)) Ok(vec)
), }
MqttCommand::Switch(ch, _) => Ok(
Some(( MqttCommand::Reset => {
vec.push((
{ {
self.str_builder.clear(); let mut topic_string = String::from(self.name);
self.str_builder.push_str("Urukul/Feedback/Channel") topic_string.push_str("/Feedback/Reset")
.map_err(|_| Error::StringOutOfSpace)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push(char::from_digit(ch.into(), 10).unwrap()) topic_string
},
String::from(
match self.urukul.test() {
Ok(0) => "Reset successful.",
_ => "Reset error!",
}
)
)).map_err(|_| Error::VectorOutOfSpace)?;
Ok(vec)
}
MqttCommand::Switch(ch, _) => {
vec.push((
{
let mut topic_string = String::from(self.name);
topic_string.push_str("/Feedback/Channel")
.map_err(|_| Error::StringOutOfSpace)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push_str("/Switch") topic_string.push(char::from_digit(ch.into(), 10).unwrap())
.map_err(|_| Error::StringOutOfSpace)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.as_str() topic_string.push_str("/Switch")
.map_err(|_| Error::StringOutOfSpace)?;
topic_string
}, },
{ {
String::from( String::from(
@ -142,19 +154,21 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
} }
) )
} }
)) )).map_err(|_| Error::VectorOutOfSpace)?;
), Ok(vec)
MqttCommand::Attenuation(ch, _) => Ok( }
Some((
MqttCommand::Attenuation(ch, _) => {
vec.push((
{ {
self.str_builder.clear(); let mut topic_string = String::from(self.name);
self.str_builder.push_str("Urukul/Feedback/Channel") topic_string.push_str("/Feedback/Channel")
.map_err(|_| Error::StringOutOfSpace)?; .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)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push_str("/Attenuation") topic_string.push_str("/Attenuation")
.map_err(|_| Error::StringOutOfSpace)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.as_str() topic_string
}, },
{ {
String::from( String::from(
@ -163,19 +177,55 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
) )
) )
} }
)) )).map_err(|_| Error::VectorOutOfSpace)?;
), Ok(vec)
MqttCommand::SystemClock(ch, _) => Ok( }
Some((
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(); let mut topic_string = String::from(self.name);
self.str_builder.push_str("Urukul/Feedback/Channel") topic_string.push_str("/Feedback/Channel")
.map_err(|_| Error::StringOutOfSpace)?; .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)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.push_str("/SystemClock") topic_string.push_str("/SystemClock")
.map_err(|_| Error::StringOutOfSpace)?; .map_err(|_| Error::StringOutOfSpace)?;
self.str_builder.as_str() topic_string
}, },
{ {
let mut message_str = String::from( 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)?; .map_err(|_| Error::StringOutOfSpace)?;
message_str message_str
} }
)) )).map_err(|_| Error::VectorOutOfSpace)?;
), Ok(vec)
MqttCommand::Profile(_) => Ok( }
Some((
"Urukul/Feedback/Profile", 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 mut message_str = String::new();
let prof = self.urukul.get_profile()?; 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)?; .map_err(|_| Error::StringOutOfSpace)?;
message_str message_str
} }
)) )).map_err(|_| Error::VectorOutOfSpace)?;
), Ok(vec)
_ => Ok(Some(("Urukul/Feedback/Unimplemented", String::from("test")))), }
}, },
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 channel :u8 = 0;
let mut profile :u8 = 0; let mut profile :u8 = 0;
// Verify that the topic must start with Urukul/Control/ or /Urukul/Control/ // Verify that the topic starts with <name>/Control/ or /<name>/Control/
let mut header = topic.strip_prefix("/Urukul/Control/") let mut header = {
.or_else(|| topic.strip_prefix("Urukul/Control/")) let mut topic_builder_with_slash: String<U128> = String::from("/");
.ok_or(Error::MqttCommandError)?; 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 { loop {
match header { 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>> { fn execute(&mut self, command: MqttCommand) -> Result<(), Error<E>> {
match command { match command {
MqttCommand::ProcessError => Ok(()), MqttCommand::ProcessError(_) => Ok(()),
MqttCommand::Reset => self.urukul.reset(), MqttCommand::Reset => self.urukul.reset(),
MqttCommand::Switch(ch, state) => self.urukul.set_channel_switch(ch.into(), state), MqttCommand::Switch(ch, state) => self.urukul.set_channel_switch(ch.into(), state),
MqttCommand::Attenuation(ch, ampl) => self.urukul.set_channel_attenuation(ch, ampl), 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>> { fn get_clock_source_message(&mut self) -> Result<(String<U128>, String<U64>), Error<E>> {
self.urukul.get_channel_switch_status(channel.into()).map( Ok((
|stat| if stat { {
"on" let mut topic_string = String::from(self.name);
} else { topic_string.push_str("/Feedback/Clock/Source")
"off" .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) 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>> { pub fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Error<E>> {
// Change clock source through configuration register // Change clock source through configuration register
match source { match source {
@ -190,6 +202,10 @@ where
}.map(|_| ()) }.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>> { pub fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), Error<E>> {
// Update master clock frequency // Update master clock frequency
self.f_master_clk = frequency; self.f_master_clk = frequency;
@ -198,6 +214,15 @@ where
self.set_dds_ref_clk() 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>> { pub fn set_clock_division(&mut self, division: u8) -> Result<(), Error<E>> {
match division { match division {
1 => self.config_register.set_configurations(&mut [ 1 => self.config_register.set_configurations(&mut [
@ -266,6 +291,10 @@ where
} }
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude) 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>> { 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 { if channel >= 4 || profile >= 8 || frequency < 0.0 {