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
|
||||
```
|
||||
|
||||
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`
|
||||
|
|
2
memory.x
2
memory.x
|
@ -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 */
|
||||
|
|
109
shell.nix
109
shell.nix
|
@ -1,61 +1,66 @@
|
|||
let
|
||||
mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
|
||||
pkgs = import <nixpkgs> {overlays = [mozillaOverlay];};
|
||||
mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
|
||||
pkgs = import <nixpkgs> {overlays = [mozillaOverlay];};
|
||||
in with pkgs;
|
||||
let
|
||||
migen = callPackage ./nix/migen.nix {};
|
||||
openocd = callPackage ./nix/openocd.nix {};
|
||||
rustPlatform = callPackage ./nix/rustPlatform.nix {};
|
||||
itm = callPackage ./nix/itm.nix {inherit rustPlatform;};
|
||||
migen = callPackage ./nix/migen.nix {};
|
||||
openocd = callPackage ./nix/openocd.nix {};
|
||||
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
|
||||
'';
|
||||
|
||||
runOpenOcdBlock = writeShellScriptBin "run-openocd-block" ''
|
||||
openocd -f openocd/openocd.cfg
|
||||
'';
|
||||
openocdFlash = writeShellScriptBin "openocd-flash" ''
|
||||
openocd -f openocd/openocd.cfg -f openocd/main.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
|
||||
'';
|
||||
publishMqtt = writeShellScriptBin "publish-mqtt" ''
|
||||
mosquitto_pub -h localhost -t $1 -m "$2" -d
|
||||
'';
|
||||
|
||||
openocdFlash = writeShellScriptBin "openocd-flash" ''
|
||||
openocd -f openocd/openocd.cfg -f openocd/$1.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";
|
||||
buildInputs = with rustPlatform.rust; [
|
||||
(pkgs.python3.withPackages(ps: [ migen ]))
|
||||
pkgs.yosys
|
||||
pkgs.nextpnr
|
||||
pkgs.icestorm
|
||||
pkgs.gdb
|
||||
pkgs.mosquitto
|
||||
openocd
|
||||
rustc
|
||||
cargo
|
||||
itm
|
||||
runOpenOcd
|
||||
runOpenOcdBlock
|
||||
setGDBConfigFile
|
||||
openocdFlash
|
||||
publishMqtt
|
||||
];
|
||||
}
|
||||
stdenv.mkDerivation {
|
||||
name = "Humpback-DDS";
|
||||
buildInputs = with rustPlatform.rust; [
|
||||
(pkgs.python3.withPackages(ps: [ migen ]))
|
||||
pkgs.yosys
|
||||
pkgs.nextpnr
|
||||
pkgs.icestorm
|
||||
pkgs.gdb
|
||||
pkgs.mosquitto
|
||||
openocd
|
||||
rustc
|
||||
cargo
|
||||
itm
|
||||
runOpenOcdBlock
|
||||
openocdFlash
|
||||
publishMqtt
|
||||
openOCDFlashCustomised
|
||||
];
|
||||
}
|
||||
|
|
47
src/dds.rs
47
src/dds.rs
|
@ -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,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
|
||||
* 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>> {
|
||||
|
||||
|
@ -408,7 +442,7 @@ where
|
|||
/*
|
||||
* Set phase offset of a single tone profile
|
||||
* 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>> {
|
||||
|
||||
|
@ -432,7 +466,7 @@ where
|
|||
/*
|
||||
* Set amplitude offset of a single tone profile
|
||||
* 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>> {
|
||||
|
||||
|
@ -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),
|
||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -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 => {},
|
||||
|
|
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::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((
|
||||
{
|
||||
String::from(
|
||||
match self.urukul.test() {
|
||||
Ok(0) => "Reset successful.",
|
||||
_ => "Reset error!",
|
||||
}
|
||||
)
|
||||
}
|
||||
))
|
||||
),
|
||||
MqttCommand::Switch(ch, _) => Ok(
|
||||
Some((
|
||||
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((
|
||||
{
|
||||
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/Reset")
|
||||
.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)?;
|
||||
self.str_builder.push_str("/Switch")
|
||||
topic_string.push(char::from_digit(ch.into(), 10).unwrap())
|
||||
.map_err(|_| Error::StringOutOfSpace)?;
|
||||
self.str_builder.as_str()
|
||||
topic_string.push_str("/Switch")
|
||||
.map_err(|_| Error::StringOutOfSpace)?;
|
||||
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
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 [
|
||||
|
@ -266,6 +291,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 {
|
||||
|
|
Loading…
Reference in New Issue