Compare commits
12 Commits
7181bdc0ae
...
3fb8114df1
Author | SHA1 | Date |
---|---|---|
occheung | 3fb8114df1 | |
occheung | 28046a8740 | |
occheung | 41d3df26f2 | |
occheung | f8ccb66077 | |
occheung | d202886753 | |
occheung | 430fc12f54 | |
occheung | b577c8b715 | |
occheung | 9d49afa5a8 | |
occheung | 21999b6cbf | |
occheung | b75c83be61 | |
occheung | 95443a2283 | |
occheung | 3abc0c3e4e |
90
README.md
90
README.md
|
@ -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`
|
||||||
|
|
2
memory.x
2
memory.x
|
@ -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
109
shell.nix
|
@ -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
|
];
|
||||||
];
|
}
|
||||||
}
|
|
||||||
|
|
47
src/dds.rs
47
src/dds.rs
|
@ -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),
|
||||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -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 => {},
|
||||||
|
|
350
src/mqtt_mux.rs
350
src/mqtt_mux.rs
|
@ -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
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue