Compare commits

..

6 Commits

Author SHA1 Message Date
73e40ace0e Add and apply pre-commit config (basic fixers, rustfmt) 2024-04-25 09:55:51 +02:00
713545535e Apply rustfmt 2024-04-25 09:55:51 +02:00
65e280670a Add rustup toolchain file 2024-04-25 09:55:09 +08:00
6212ed09ea fpga: allow selecting the Humpback EEM port 2024-04-24 14:16:19 +02:00
4dabb16b4e add license 2024-04-23 18:18:52 +08:00
aaa29a73ba Add nix flakes support (#2)
Reviewed-on: M-Labs/humpback-dds#2
Co-authored-by: mwojcik <mw@m-labs.hk>
Co-committed-by: mwojcik <mw@m-labs.hk>
2022-01-25 10:13:35 +08:00
35 changed files with 1619 additions and 21721 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ target/
**/build **/build
**/__pycache__ **/__pycache__
itm.fifo itm.fifo
result

19
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,19 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: mixed-line-ending
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: check-added-large-files
- id: end-of-file-fixer
- id: trailing-whitespace
- id: no-commit-to-branch
args: [--branch, master]
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
entry: cargo +stable fmt
name: format rust sources

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) 2020-2022 M-Labs Limited
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -2,14 +2,20 @@
MQTT-controlled 4-channel DDS signal generator using Urukul, Humpback and STM32 NUCLEO. MQTT-controlled 4-channel DDS signal generator using Urukul, Humpback and STM32 NUCLEO.
- [Continuous Integration](https://nixbld.m-labs.hk/job/stm32/stm32/humpback-dds) - [Continuous Integration](https://nixbld.m-labs.hk/job/mcu/humpback-dds/humpback-dds)
- [Download latest firmware build](https://nixbld.m-labs.hk/job/stm32/stm32/humpback-dds/latest/download-by-type/file/binary-dist) - [Download latest firmware build](https://nixbld.m-labs.hk/job/mcu/humpback-dds/humpback-dds/latest/download-by-type/file/binary-dist)
## Nix commands ## Nix commands
Start nix shell before anything. Humpback-DDS firmware is packaged using the [Nix](https://nixos.org) Flakes system. Install Nix 2.4+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
Once you have Flakes enabled, you can use ``nix build`` to build the firmware.
Alternatively, you can develop and build it within Nix shell:
```shell ```shell
nix-shell nix develop
python fpga/fpga_config.py [--eem [0,1,2]]
cargo build --release
``` ```
**(For users who had completed the [networking setup](##networking-setup-for-first-time-user))** 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.

View File

@ -2,9 +2,9 @@ use std::process::Command;
fn main() { fn main() {
Command::new("python3") Command::new("python3")
.arg("fpga/fpga_config.py") .arg("fpga/fpga_config.py")
.spawn() .spawn()
.expect("FPGA bitstream file cannot be built!"); .expect("FPGA bitstream file cannot be built!");
println!("cargo:rerun-if-changed=fpga/fpga_config.py") println!("cargo:rerun-if-changed=fpga/fpga_config.py")
} }

View File

@ -1 +0,0 @@
"05lq7c5yz320gasp5q4g76lbj8s1hv1ypgjfg9iqb6jiryy4kv75"

99
flake.lock generated Normal file
View File

@ -0,0 +1,99 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1642348879,
"narHash": "sha256-ReoDCqqqGEQBmQHlQAXSLSk4LGO96HhBtxsF1TpOnLU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8a70a6808c884282161bd77706927caeac0c11e8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-21.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"src-migen": "src-migen"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1713924823,
"narHash": "sha256-kOeyS3GFwgnKvzuBMmFqEAX0xwZ7Nj4/5tXuvpZ0d4U=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8a2edac3ae926a2a6ce60f4595dcc4540fc8cad4",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"src-migen": {
"flake": false,
"locked": {
"lastModified": 1639659493,
"narHash": "sha256-qpVj/yJf4hDDc99XXpVPH4EbLC8aCmEtACn5qNc3DGI=",
"owner": "m-labs",
"repo": "migen",
"rev": "ac703010eaa06ac9b6e32f97c6fa98b15de22b31",
"type": "github"
},
"original": {
"owner": "m-labs",
"repo": "migen",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

133
flake.nix Normal file
View File

@ -0,0 +1,133 @@
{
description = "Firmware for MQTT-controlled 4-channel DDS signal generator using Urukul, Humpback and STM32 NUCLEO.";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
inputs.rust-overlay.url = github:oxalica/rust-overlay;
inputs.rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
inputs.src-migen = { url = github:m-labs/migen; flake = false; };
outputs = { self, nixpkgs, rust-overlay, src-migen }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import rust-overlay) ]; };
rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
migen = pkgs.python3Packages.buildPythonPackage rec {
name = "migen";
src = src-migen;
propagatedBuildInputs = [ pkgs.python3Packages.colorama ];
};
rustPlatform = pkgs.makeRustPlatform {
rustc = rustToolchain;
cargo = rustToolchain;
};
itm = rustPlatform.buildRustPackage rec {
version = "0.3.1";
pname = "itm";
src = pkgs.fetchFromGitHub {
owner = "rust-embedded";
repo = "itm";
rev = "v${version}";
sha256 = "sha256-UmWI3NOE8Lf8ICHOR8nNpbCP9+g3R8XHRX+nUJsH6pY=";
};
cargoPatches = [ ./itm-cargo-lock.patch ];
cargoSha256 = "sha256-3odQabrzjFm5rTkeqZWDFLnculwGeB3gG71jNuCtqIo=";
nativeBuildInputs = [ pkgs.pkgconfig ];
doCheck = false;
};
runOpenOcdBlock = pkgs.writeShellScriptBin "run-openocd-block" ''
openocd -f openocd/openocd.cfg
'';
openocdFlash = pkgs.writeShellScriptBin "openocd-flash" ''
openocd -f openocd/openocd.cfg -f openocd/main.cfg
'';
publishMqtt = pkgs.writeShellScriptBin "publish-mqtt" ''
mosquitto_pub -h localhost -t $1 -m "$2" -d
'';
openOCDFlashCustomised = pkgs.writeShellScriptBin "openocd-flash-customised" ''
python3 flash.py $@
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 write_image flash_config.bin 0x081e0000 bin
reset run
shutdown"
'';
humpback-dds = rustPlatform.buildRustPackage rec {
name = "humpback-dds";
version = "0.0.0";
src = self;
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"SaiTLS-0.1.0" = "sha256-T3hyASQGZAXGLKfOB3mh33VrvRlYSMc1CJdX4XvDFrQ=";
"rsa-0.3.0" = "sha256-9X2kDAOu0HG94HhwYoUtX/ezq99w7u95XMzn3h/JUwk=";
"minimq-0.1.0" = "sha256-yPqMAwyusZZW7571Jn3QVYK2jqmuSMNU6LiZXRYAGGM=";
"smoltcp-0.6.0" = "sha256-BCqcphWF3AotQfuIFYhDiWIMhh2eUCuqD4MPz4dASQ4=";
};
};
nativeBuildInputs = [
pkgs.llvm
pkgs.yosys
pkgs.nextpnr
pkgs.icestorm
(pkgs.python3.withPackages(ps: [ migen ]))
];
buildPhase = ''
cargo build --release --bin humpback-dds
'';
installPhase = ''
mkdir -p $out $out/nix-support
cp target/thumbv7em-none-eabihf/release/humpback-dds $out/humpback-dds.elf
echo file binary-dist $out/humpback-dds.elf >> $out/nix-support/hydra-build-products
llvm-objcopy -O binary target/thumbv7em-none-eabihf/release/humpback-dds $out/humpback-dds.bin
echo file binary-dist $out/humpback-dds.bin >> $out/nix-support/hydra-build-products
'';
doCheck = false;
dontFixup = true;
};
in {
packages.x86_64-linux = {
inherit humpback-dds;
};
hydraJobs = {
inherit humpback-dds;
};
devShell.x86_64-linux = pkgs.mkShell {
name = "humpback-dds-dev-shell";
buildInputs = with pkgs; [
rustPlatform.rust.rustc
rustPlatform.rust.cargo
openocd dfu-util
yosys nextpnr icestorm
gdb mosquitto
itm runOpenOcdBlock
openocdFlash publishMqtt
openOCDFlashCustomised
] ++ (with python3Packages; [
numpy matplotlib migen
]);
};
defaultPackage.x86_64-linux = humpback-dds;
};
}

View File

@ -1,3 +1,5 @@
import argparse
# Import built in I/O, Connectors & Platform template for Humpback # Import built in I/O, Connectors & Platform template for Humpback
from migen.build.platforms.sinara import humpback from migen.build.platforms.sinara import humpback
# Import migen pin record structure # Import migen pin record structure
@ -8,7 +10,7 @@ from migen.genlib.io import *
class UrukulConnector(Module): class UrukulConnector(Module):
def __init__(self, platform): def __init__(self, platform, eem_resource_name):
# Include extension # Include extension
spi_mosi = [ spi_mosi = [
("spi_mosi", 0, Pins("B16"), IOStandard("LVCMOS33")) ("spi_mosi", 0, Pins("B16"), IOStandard("LVCMOS33"))
@ -26,16 +28,16 @@ class UrukulConnector(Module):
platform.add_extension(spi_mosi) platform.add_extension(spi_mosi)
# Request EEM I/O & SPI # Request EEM I/O & SPI
eem0 = [ eem = [
platform.request("eem0", 0), platform.request(eem_resource_name, 0),
platform.request("eem0", 1), platform.request(eem_resource_name, 1),
# Supply EEM pin with negative polarity # Supply EEM pin with negative polarity
# See issue/PR: https://github.com/m-labs/migen/pull/181 # See issue/PR: https://github.com/m-labs/migen/pull/181
platform.request("eem0_n", 2), platform.request(f"{eem_resource_name}_n", 2),
platform.request("eem0", 3), platform.request(eem_resource_name, 3),
platform.request("eem0", 4), platform.request(eem_resource_name, 4),
platform.request("eem0", 5), platform.request(eem_resource_name, 5),
platform.request("eem0", 6) platform.request(eem_resource_name, 6)
] ]
spi = platform.request("spi") spi = platform.request("spi")
spi_mosi = platform.request("spi_mosi") spi_mosi = platform.request("spi_mosi")
@ -56,37 +58,47 @@ class UrukulConnector(Module):
self.specials += Instance("SB_IO", self.specials += Instance("SB_IO",
p_PIN_TYPE=C(0b000001, 6), p_PIN_TYPE=C(0b000001, 6),
p_IO_STANDARD="SB_LVDS_INPUT", p_IO_STANDARD="SB_LVDS_INPUT",
io_PACKAGE_PIN=eem0[2], io_PACKAGE_PIN=eem[2],
o_D_IN_0=self.miso_n o_D_IN_0=self.miso_n
) )
# Link EEM to SPI # Link EEM to SPI
self.comb += [ self.comb += [
eem0[0].p.eq(spi.clk), eem[0].p.eq(spi.clk),
eem0[0].n.eq(~spi.clk), eem[0].n.eq(~spi.clk),
eem0[1].p.eq(spi_mosi), eem[1].p.eq(spi_mosi),
eem0[1].n.eq(~spi_mosi), eem[1].n.eq(~spi_mosi),
spi.miso.eq(~self.miso_n), spi.miso.eq(~self.miso_n),
eem0[3].p.eq(spi_cs[0]), eem[3].p.eq(spi_cs[0]),
eem0[3].n.eq(~spi_cs[0]), eem[3].n.eq(~spi_cs[0]),
eem0[4].p.eq(spi_cs[1]), eem[4].p.eq(spi_cs[1]),
eem0[4].n.eq(~spi_cs[1]), eem[4].n.eq(~spi_cs[1]),
eem0[5].p.eq(spi_cs[2]), eem[5].p.eq(spi_cs[2]),
eem0[5].n.eq(~spi_cs[2]), eem[5].n.eq(~spi_cs[2]),
eem0[6].p.eq(io_update), eem[6].p.eq(io_update),
eem0[6].n.eq(~io_update), eem[6].n.eq(~io_update),
led.eq(1) led.eq(1)
] ]
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Build FPGA bitstream")
parser.add_argument(
"--eem",
type=int,
choices=[0, 1, 2],
default=0,
help="The Humpback EEM port the Urukul board is connected to."
)
args = parser.parse_args()
platform = humpback.Platform() platform = humpback.Platform()
platform.build(UrukulConnector(platform)) platform.build(UrukulConnector(platform, f"eem{args.eem}"))

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
{ stdenv, fetchFromGitHub, rustPlatform, pkgconfig }:
rustPlatform.buildRustPackage rec {
version = "0.3.1";
pname = "itm";
src = fetchFromGitHub {
owner = "rust-embedded";
repo = "itm";
rev = "v${version}";
sha256 = "15pa0ydm19vz8p3wairpx3vqzc55rp4lgki143ybgw44sgf8hraj";
};
cargoPatches = [ ./itm-cargo-lock.patch ];
cargoSha256 = "1x2pagfxwhgxliygw7325qsg3ccn276f4slg9cql0sf1s304qj4g";
nativeBuildInputs = [ pkgconfig ];
doCheck = false;
}

View File

@ -1,22 +0,0 @@
{ stdenv, fetchFromGitHub, python3Packages }:
python3Packages.buildPythonPackage rec {
name = "migen";
src = fetchFromGitHub {
owner = "m-labs";
repo = "migen";
rev = "7bc4eb1387b39159a74c1dbd1b820728e0bfbbaa";
sha256 = "039jk8y7f0vhr32svg3nd23i88c0bhws8ngxwk9bdznfxvhiy1h6";
fetchSubmodules = true;
};
propagatedBuildInputs = with python3Packages; [ colorama ];
meta = with stdenv.lib; {
description = "A refreshed Python toolbox for building complex digital hardware";
homepage = "https://m-labs.hk";
license = licenses.bsd2;
maintainers = [ maintainers.sb0 ];
};
}

View File

@ -1,66 +0,0 @@
{ stdenv, lib, fetchgit, autoreconfHook,fetchpatch, libftdi1, libusb1, pkgconfig, hidapi }:
stdenv.mkDerivation rec {
pname = "openocd";
version = "0.10.0-dev";
src = fetchgit {
url = "https://git.code.sf.net/p/openocd/code";
rev = "7c88e76a76588fa0e3ab645adfc46e8baff6a3e4";
sha256 = "04ia0rjyil5353dw4mmrmwpald6lqqliaypadp467421dvp0xv97";
fetchSubmodules = true;
};
nativeBuildInputs = [ pkgconfig autoreconfHook];
buildInputs = [ libftdi1 libusb1 hidapi];
configureFlags = [
"--enable-jtag_vpi"
"--enable-usb_blaster_libftdi"
(lib.enableFeature (! stdenv.isDarwin) "amtjtagaccel")
(lib.enableFeature (! stdenv.isDarwin) "gw16012")
"--enable-presto_libftdi"
"--enable-openjtag_ftdi"
(lib.enableFeature (! stdenv.isDarwin) "oocd_trace")
"--enable-buspirate"
(lib.enableFeature stdenv.isLinux "sysfsgpio")
"--enable-remote-bitbang"
];
NIX_CFLAGS_COMPILE = toString (lib.optionals stdenv.cc.isGNU [
"-Wno-implicit-fallthrough"
"-Wno-format-truncation"
"-Wno-format-overflow"
"-Wno-error=tautological-compare"
"-Wno-error=array-bounds"
"-Wno-error=cpp"
]);
postInstall = lib.optionalString stdenv.isLinux ''
mkdir -p "$out/etc/udev/rules.d"
rules="$out/share/openocd/contrib/60-openocd.rules"
if [ ! -f "$rules" ]; then
echo "$rules is missing, must update the Nix file."
exit 1
fi
ln -s "$rules" "$out/etc/udev/rules.d/"
'';
meta = with lib; {
description = "Free and Open On-Chip Debugging, In-System Programming and Boundary-Scan Testing";
longDescription = ''
OpenOCD provides on-chip programming and debugging support with a layered
architecture of JTAG interface and TAP support, debug target support
(e.g. ARM, MIPS), and flash chip drivers (e.g. CFI, NAND, etc.). Several
network interfaces are available for interactiving with OpenOCD: HTTP,
telnet, TCL, and GDB. The GDB server enables OpenOCD to function as a
"remote target" for source-level debugging of embedded systems using the
GNU GDB program.
'';
homepage = "http://openocd.sourceforge.net/";
license = licenses.gpl2Plus;
maintainers = with maintainers; [ bjornfor ];
platforms = platforms.unix;
};
}

View File

@ -1,24 +0,0 @@
{ recurseIntoAttrs, stdenv, lib,
makeRustPlatform, defaultCrateOverrides,
fetchurl, patchelf,
rustManifest ? ./channel-rust-nightly.toml
}:
let
targets = [
"thumbv7em-none-eabihf" # For ARM Cortex-M4 or M7 w/ FPU support
];
rustChannel =
lib.rustLib.fromManifestFile rustManifest {
inherit stdenv fetchurl patchelf;
};
rust =
rustChannel.rust.override {
inherit targets;
};
in
makeRustPlatform {
rustc = rust;
cargo = rust;
}

6
rust-toolchain.toml Normal file
View File

@ -0,0 +1,6 @@
[toolchain]
channel = "nightly-2020-10-30"
targets = [
"thumbv7em-none-eabihf",
]
profile = "default"

View File

@ -1,55 +0,0 @@
let
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;};
runOpenOcdBlock = writeShellScriptBin "run-openocd-block" ''
openocd -f openocd/openocd.cfg
'';
openocdFlash = writeShellScriptBin "openocd-flash" ''
openocd -f openocd/openocd.cfg -f openocd/main.cfg
'';
publishMqtt = writeShellScriptBin "publish-mqtt" ''
mosquitto_pub -h localhost -t $1 -m "$2" -d
'';
openOCDFlashCustomised = writeShellScriptBin "openocd-flash-customised" ''
python3 flash.py $1 $2 $3 $4
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 write_image flash_config.bin 0x081e0000 bin
reset run
shutdown"
'';
in
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
];
}

View File

@ -1,5 +1,5 @@
use embedded_hal::blocking::spi::Transfer;
use core::assert; use core::assert;
use embedded_hal::blocking::spi::Transfer;
use crate::urukul::Error; use crate::urukul::Error;
@ -10,7 +10,7 @@ pub struct Attenuator<SPI> {
impl<SPI, E> Attenuator<SPI> impl<SPI, E> Attenuator<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
pub fn new(spi: SPI) -> Self { pub fn new(spi: SPI) -> Self {
Attenuator { Attenuator {
@ -36,16 +36,21 @@ where
// Set data as attenuation * 2 // Set data as attenuation * 2
// Flip data using bitwise XOR, active low data // Flip data using bitwise XOR, active low data
// Data is most signifant attenuator first // Data is most signifant attenuator first
self.data[3-i] = (((atten * 2.0) as u8) ^ 0xFF) << 2 self.data[3 - i] = (((atten * 2.0) as u8) ^ 0xFF) << 2
} }
let mut clone = self.data.clone(); let mut clone = self.data.clone();
// Transmit SPI once to set attenuation // Transmit SPI once to set attenuation
self.spi.transfer(&mut clone) self.spi
.map(|_| ()) .transfer(&mut clone)
.map_err(|_| Error::AttenuatorError) .map(|_| ())
.map_err(|_| Error::AttenuatorError)
} }
pub fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> { pub fn set_channel_attenuation(
&mut self,
channel: u8,
attenuation: f32,
) -> Result<(), Error<E>> {
assert!(channel < 4); assert!(channel < 4);
let mut arr: [f32; 4] = self.get_attenuation()?; let mut arr: [f32; 4] = self.get_attenuation()?;
arr[channel as usize] = attenuation; arr[channel as usize] = attenuation;
@ -64,12 +69,12 @@ where
let mut clone = self.data.clone(); let mut clone = self.data.clone();
match self.spi.transfer(&mut clone).map_err(Error::SPI) { match self.spi.transfer(&mut clone).map_err(Error::SPI) {
Ok(arr) => { Ok(arr) => {
let mut ret :[f32; 4] = [0.0; 4]; let mut ret: [f32; 4] = [0.0; 4];
for index in 0..4 { for index in 0..4 {
ret[index] = ((arr[3 - index] ^ 0xFC) as f32) / 8.0; ret[index] = ((arr[3 - index] ^ 0xFC) as f32) / 8.0;
} }
Ok(ret) Ok(ret)
}, }
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
@ -82,16 +87,14 @@ where
// Test attenuators by getting back the attenuation // Test attenuators by getting back the attenuation
let mut error_count = 0; let mut error_count = 0;
// Convert cached SPI data into attenuation floats // Convert cached SPI data into attenuation floats
let att_floats :[f32; 4] = [ let att_floats: [f32; 4] = [
((self.data[3] ^ 0xFC) as f32) / 8.0, ((self.data[3] ^ 0xFC) as f32) / 8.0,
((self.data[2] ^ 0xFC) as f32) / 8.0, ((self.data[2] ^ 0xFC) as f32) / 8.0,
((self.data[1] ^ 0xFC) as f32) / 8.0, ((self.data[1] ^ 0xFC) as f32) / 8.0,
((self.data[0] ^ 0xFC) as f32) / 8.0, ((self.data[0] ^ 0xFC) as f32) / 8.0,
]; ];
// Set the attenuation to an arbitrary value, then read the attenuation // Set the attenuation to an arbitrary value, then read the attenuation
self.set_attenuation([ self.set_attenuation([3.5, 9.5, 20.0, 28.5])?;
3.5, 9.5, 20.0, 28.5
])?;
match self.get_attenuation() { match self.get_attenuation() {
Ok(arr) => { Ok(arr) => {
if arr[0] != 3.5 { if arr[0] != 3.5 {
@ -106,7 +109,7 @@ where
if arr[3] != 28.5 { if arr[3] != 28.5 {
error_count += 1; error_count += 1;
} }
}, }
Err(_) => return Err(Error::AttenuatorError), Err(_) => return Err(Error::AttenuatorError),
}; };
self.set_attenuation(att_floats)?; self.set_attenuation(att_floats)?;
@ -116,7 +119,7 @@ where
impl<SPI, E> Transfer<u8> for Attenuator<SPI> impl<SPI, E> Transfer<u8> for Attenuator<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
type Error = Error<E>; type Error = Error<E>;

View File

@ -1,16 +1,16 @@
use smoltcp as net;
use net::wire::IpCidr;
use net::wire::EthernetAddress; use net::wire::EthernetAddress;
use net::wire::IpCidr;
use smoltcp as net;
use embedded_nal as nal; use embedded_nal as nal;
use nal::IpAddr; use nal::IpAddr;
use heapless::{ String, consts::* }; use heapless::{consts::*, String};
use serde::{ Serialize, Deserialize }; use serde::{Deserialize, Serialize};
use crate::urukul::ClockSource;
use crate::flash_store::FlashStore; use crate::flash_store::FlashStore;
use crate::urukul::ClockSource;
use core::str::FromStr; use core::str::FromStr;
@ -72,7 +72,7 @@ pub fn get_net_config(store: &mut FlashStore) -> NetConfig {
eth_addr: { eth_addr: {
match store.read_str("MAC").unwrap() { match store.read_str("MAC").unwrap() {
Some(mac) => EthernetAddress::from_str(mac).unwrap(), Some(mac) => EthernetAddress::from_str(mac).unwrap(),
None => EthernetAddress::from_str("AC:6F:7A:DE:D6:C8").unwrap() None => EthernetAddress::from_str("AC:6F:7A:DE:D6:C8").unwrap(),
} }
}, },
broker_ip: { broker_ip: {
@ -84,9 +84,9 @@ pub fn get_net_config(store: &mut FlashStore) -> NetConfig {
name: { name: {
match store.read_str("Name").unwrap() { match store.read_str("Name").unwrap() {
Some(name) => String::from(name), Some(name) => String::from(name),
None => String::from("HumpbackDDS") None => String::from("HumpbackDDS"),
} }
} },
}; };
log::info!("Net config: {:?}", net_config); log::info!("Net config: {:?}", net_config);
net_config net_config

View File

@ -1,6 +1,6 @@
use embedded_hal::blocking::spi::Transfer;
use crate::urukul::Error; use crate::urukul::Error;
use core::mem::size_of; use core::mem::size_of;
use embedded_hal::blocking::spi::Transfer;
// Bitmasks for CFG // Bitmasks for CFG
construct_bitmask!(CFGMask; u32; construct_bitmask!(CFGMask; u32;
@ -33,13 +33,10 @@ pub struct ConfigRegister<SPI> {
impl<SPI, E> ConfigRegister<SPI> impl<SPI, E> ConfigRegister<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
pub fn new(spi: SPI) -> Self { pub fn new(spi: SPI) -> Self {
ConfigRegister { ConfigRegister { spi, data: 0 }
spi,
data: 0,
}
} }
/* /*
@ -47,16 +44,16 @@ where
* Return status * Return status
*/ */
fn set_all_configurations(&mut self) -> Result<u32, Error<E>> { fn set_all_configurations(&mut self) -> Result<u32, Error<E>> {
match self.spi.transfer(&mut [ match self
((self.data & 0x00FF0000) >> 16) as u8, .spi
((self.data & 0x0000FF00) >> 8) as u8, .transfer(&mut [
((self.data & 0x000000FF) >> 0) as u8, ((self.data & 0x00FF0000) >> 16) as u8,
]).map_err(Error::SPI) { ((self.data & 0x0000FF00) >> 8) as u8,
Ok(arr) => Ok( ((self.data & 0x000000FF) >> 0) as u8,
((arr[0] as u32) << 16) | ])
((arr[1] as u32) << 8) | .map_err(Error::SPI)
arr[2] as u32 {
), Ok(arr) => Ok(((arr[0] as u32) << 16) | ((arr[1] as u32) << 8) | arr[2] as u32),
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
@ -65,7 +62,7 @@ where
* Set configuration bits according to supplied configs * Set configuration bits according to supplied configs
* Return status * Return status
*/ */
pub fn set_configurations(&mut self, configs: &mut[(CFGMask, u32)]) -> Result<u32, Error<E>> { pub fn set_configurations(&mut self, configs: &mut [(CFGMask, u32)]) -> Result<u32, Error<E>> {
for config in configs.into_iter() { for config in configs.into_iter() {
config.0.set_data_by_arg(&mut self.data, config.1) config.0.set_data_by_arg(&mut self.data, config.1)
} }
@ -113,7 +110,7 @@ where
impl<SPI, E> Transfer<u8> for ConfigRegister<SPI> impl<SPI, E> Transfer<u8> for ConfigRegister<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
type Error = Error<E>; type Error = Error<E>;
@ -121,4 +118,3 @@ where
self.spi.transfer(words).map_err(Error::SPI) self.spi.transfer(words).map_err(Error::SPI)
} }
} }

View File

@ -1,10 +1,7 @@
use crate::urukul::Error;
use crate::spi_slave::Parts; use crate::spi_slave::Parts;
use crate::urukul::Error;
use embedded_hal::{ use embedded_hal::{blocking::spi::Transfer, digital::v2::OutputPin};
digital::v2::OutputPin,
blocking::spi::Transfer,
};
use core::cell; use core::cell;
@ -35,20 +32,25 @@ where
match chip & (1 << 0) { match chip & (1 << 0) {
0 => self.chip_select.0.set_low(), 0 => self.chip_select.0.set_low(),
_ => self.chip_select.0.set_high(), _ => self.chip_select.0.set_high(),
}.map_err(|_| Error::CSError)?; }
.map_err(|_| Error::CSError)?;
match chip & (1 << 1) { match chip & (1 << 1) {
0 => self.chip_select.1.set_low(), 0 => self.chip_select.1.set_low(),
_ => self.chip_select.1.set_high(), _ => self.chip_select.1.set_high(),
}.map_err(|_| Error::CSError)?; }
.map_err(|_| Error::CSError)?;
match chip & (1 << 2) { match chip & (1 << 2) {
0 => self.chip_select.2.set_low(), 0 => self.chip_select.2.set_low(),
_ => self.chip_select.2.set_high(), _ => self.chip_select.2.set_high(),
}.map_err(|_| Error::CSError)?; }
.map_err(|_| Error::CSError)?;
Ok(()) Ok(())
} }
pub(crate) fn issue_io_update(&mut self) -> Result<(), Error<E>> { pub(crate) fn issue_io_update(&mut self) -> Result<(), Error<E>> {
self.io_update.set_high().map_err(|_| Error::IOUpdateError)?; self.io_update
.set_high()
.map_err(|_| Error::IOUpdateError)?;
// I/O Update minimum pulse width: 1 SYNC_CLK cycle // I/O Update minimum pulse width: 1 SYNC_CLK cycle
// 1 SYNC_CLK cycle = 4 REF_CLK cycle, where f_ref_clk is at least 3.2 MHz // 1 SYNC_CLK cycle = 4 REF_CLK cycle, where f_ref_clk is at least 3.2 MHz
// Therefore the maximum required pulse length is 1.25 us, // Therefore the maximum required pulse length is 1.25 us,
@ -69,20 +71,25 @@ where
type Error = Error<E>; type Error = Error<E>;
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
self.data.try_borrow_mut().map_err(|_| Error::GetRefMutDataError)?.spi.transfer(words).map_err(Error::SPI) self.data
.try_borrow_mut()
.map_err(|_| Error::GetRefMutDataError)?
.spi
.transfer(words)
.map_err(Error::SPI)
} }
} }
impl<SPI, CS0, CS1, CS2, GPIO, E> CPLD<SPI, CS0, CS1, CS2, GPIO> where impl<SPI, CS0, CS1, CS2, GPIO, E> CPLD<SPI, CS0, CS1, CS2, GPIO>
where
SPI: Transfer<u8, Error = E>, SPI: Transfer<u8, Error = E>,
CS0: OutputPin, CS0: OutputPin,
CS1: OutputPin, CS1: OutputPin,
CS2: OutputPin, CS2: OutputPin,
GPIO: OutputPin GPIO: OutputPin,
{ {
// Constructor for CPLD // Constructor for CPLD
pub fn new(spi: SPI, chip_select: (CS0, CS1, CS2), io_update: GPIO) -> Self { pub fn new(spi: SPI, chip_select: (CS0, CS1, CS2), io_update: GPIO) -> Self {
// Init data // Init data
let data = CPLDData { let data = CPLDData {
spi, spi,

View File

@ -1,9 +1,9 @@
use embedded_hal::blocking::spi::Transfer;
use crate::urukul::Error; use crate::urukul::Error;
use core::mem::size_of;
use core::convert::TryInto; use core::convert::TryInto;
use heapless::Vec; use core::mem::size_of;
use embedded_hal::blocking::spi::Transfer;
use heapless::consts::*; use heapless::consts::*;
use heapless::Vec;
use log::debug; use log::debug;
/* /*
@ -63,8 +63,8 @@ construct_bitmask!(DDSCFRMask; u32;
DRV0, 28 +64, 2 DRV0, 28 +64, 2
); );
const WRITE_MASK :u8 = 0x00; const WRITE_MASK: u8 = 0x00;
const READ_MASK :u8 = 0x80; const READ_MASK: u8 = 0x80;
#[link_section = ".sram2.ram"] #[link_section = ".sram2.ram"]
static mut RAM_VEC: Vec<u8, U8192> = Vec(heapless::i::Vec::new()); static mut RAM_VEC: Vec<u8, U8192> = Vec(heapless::i::Vec::new());
@ -90,12 +90,12 @@ pub struct DDS<SPI> {
spi: SPI, spi: SPI,
f_ref_clk: f64, f_ref_clk: f64,
f_sys_clk: f64, f_sys_clk: f64,
ram_dest: RAMDestination ram_dest: RAMDestination,
} }
impl<SPI, E> DDS<SPI> impl<SPI, E> DDS<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
pub fn new(spi: SPI, f_ref_clk: f64) -> Self { pub fn new(spi: SPI, f_ref_clk: f64) -> Self {
DDS { DDS {
@ -109,7 +109,7 @@ where
impl<SPI, E> Transfer<u8> for DDS<SPI> impl<SPI, E> Transfer<u8> for DDS<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
type Error = Error<E>; type Error = Error<E>;
@ -118,18 +118,15 @@ where
} }
} }
impl<SPI, E> DDS<SPI> impl<SPI, E> DDS<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
/* /*
* Implement init: Set SDIO to be input only, using LSB first * Implement init: Set SDIO to be input only, using LSB first
*/ */
pub fn init(&mut self) -> Result<(), Error<E>> { pub fn init(&mut self) -> Result<(), Error<E>> {
match self.write_register(0x00, &mut [ match self.write_register(0x00, &mut [0x00, 0x00, 0x00, 0x02]) {
0x00, 0x00, 0x00, 0x02
]) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(e), Err(e) => Err(e),
} }
@ -182,9 +179,7 @@ where
// Reset PLL lock before re-enabling it // Reset PLL lock before re-enabling it
(DDSCFRMask::PFD_RESET, 1), (DDSCFRMask::PFD_RESET, 1),
])?; ])?;
self.set_configurations(&mut [ self.set_configurations(&mut [(DDSCFRMask::PFD_RESET, 0)])?;
(DDSCFRMask::PFD_RESET, 0),
])?;
self.f_sys_clk = self.f_ref_clk * (divider as f64); self.f_sys_clk = self.f_ref_clk * (divider as f64);
Ok(()) Ok(())
} }
@ -204,13 +199,13 @@ where
// Acquire N-divider, to adjust VCO if necessary // Acquire N-divider, to adjust VCO if necessary
(DDSCFRMask::N, 0), (DDSCFRMask::N, 0),
// Acquire REF_CLK divider bypass // Acquire REF_CLK divider bypass
(DDSCFRMask::REFCLK_IN_DIV_BYPASS, 0) (DDSCFRMask::REFCLK_IN_DIV_BYPASS, 0),
]; ];
self.get_configurations(&mut configuration_queries)?; self.get_configurations(&mut configuration_queries)?;
if configuration_queries[0].1 == 1 { if configuration_queries[0].1 == 1 {
// Recalculate sys_clk // Recalculate sys_clk
let divider :f64 = configuration_queries[1].1.into(); let divider: f64 = configuration_queries[1].1.into();
let f_sys_clk = self.f_ref_clk * divider; let f_sys_clk = self.f_ref_clk * divider;
// Adjust VCO // Adjust VCO
match self.get_VCO_no(f_sys_clk, divider as u8) { match self.get_VCO_no(f_sys_clk, divider as u8) {
@ -221,24 +216,20 @@ where
// Reset PLL lock before re-enabling it // Reset PLL lock before re-enabling it
(DDSCFRMask::PFD_RESET, 1), (DDSCFRMask::PFD_RESET, 1),
])?; ])?;
self.set_configurations(&mut [ self.set_configurations(&mut [(DDSCFRMask::PFD_RESET, 0)])?;
(DDSCFRMask::PFD_RESET, 0),
])?;
// Update f_sys_clk from recalculation // Update f_sys_clk from recalculation
self.f_sys_clk = f_sys_clk; self.f_sys_clk = f_sys_clk;
Ok(()) Ok(())
}, }
Err(_) => { Err(_) => {
// Forcibly turn off PLL, enable default clk tree (divide by 2) // Forcibly turn off PLL, enable default clk tree (divide by 2)
self.enable_divided_ref_clk() self.enable_divided_ref_clk()
} }
} }
} } else if configuration_queries[2].1 == 0 {
else if configuration_queries[2].1 == 0 {
self.f_sys_clk = self.f_ref_clk / 2.0; self.f_sys_clk = self.f_ref_clk / 2.0;
Ok(()) Ok(())
} } else {
else {
self.f_sys_clk = self.f_ref_clk; self.f_sys_clk = self.f_ref_clk;
Ok(()) Ok(())
} }
@ -269,7 +260,7 @@ where
fn get_VCO_no(&mut self, f_sys_clk: f64, divider: u8) -> Result<u8, Error<E>> { fn get_VCO_no(&mut self, f_sys_clk: f64, divider: u8) -> Result<u8, Error<E>> {
// Select a VCO // Select a VCO
if divider == 1 { if divider == 1 {
Ok(6) // Bypass PLL if no frequency division needed Ok(6) // Bypass PLL if no frequency division needed
} else if f_sys_clk > 1_150_000_000.0 { } else if f_sys_clk > 1_150_000_000.0 {
Err(Error::DDSCLKError) Err(Error::DDSCLKError)
} else if f_sys_clk > 820_000_000.0 { } else if f_sys_clk > 820_000_000.0 {
@ -285,7 +276,7 @@ where
} else if f_sys_clk > 370_000_000.0 { } else if f_sys_clk > 370_000_000.0 {
Ok(0) Ok(0)
} else { } else {
Ok(7) // Bypass PLL if f_sys_clk is too low Ok(7) // Bypass PLL if f_sys_clk is too low
} }
} }
@ -300,16 +291,28 @@ where
self.read_register(0x01, &mut cfr_reg[4..8])?; self.read_register(0x01, &mut cfr_reg[4..8])?;
self.read_register(0x02, &mut cfr_reg[8..12])?; self.read_register(0x02, &mut cfr_reg[8..12])?;
Ok([ Ok([
(cfr_reg[0] as u32) << 24 | (cfr_reg[1] as u32) << 16 | (cfr_reg[2] as u32) << 8 | (cfr_reg[3] as u32), (cfr_reg[0] as u32) << 24
(cfr_reg[4] as u32) << 24 | (cfr_reg[5] as u32) << 16 | (cfr_reg[6] as u32) << 8 | (cfr_reg[7] as u32), | (cfr_reg[1] as u32) << 16
(cfr_reg[8] as u32) << 24 | (cfr_reg[9] as u32) << 16 | (cfr_reg[10] as u32) << 8 | (cfr_reg[11] as u32) | (cfr_reg[2] as u32) << 8
| (cfr_reg[3] as u32),
(cfr_reg[4] as u32) << 24
| (cfr_reg[5] as u32) << 16
| (cfr_reg[6] as u32) << 8
| (cfr_reg[7] as u32),
(cfr_reg[8] as u32) << 24
| (cfr_reg[9] as u32) << 16
| (cfr_reg[10] as u32) << 8
| (cfr_reg[11] as u32),
]) ])
} }
/* /*
* Get a set of configurations using DDSCFRMask * Get a set of configurations using DDSCFRMask
*/ */
pub fn get_configurations<'w>(&mut self, mask_pairs: &'w mut[(DDSCFRMask, u32)]) -> Result<&'w [(DDSCFRMask, u32)], Error<E>> { pub fn get_configurations<'w>(
&mut self,
mask_pairs: &'w mut [(DDSCFRMask, u32)],
) -> Result<&'w [(DDSCFRMask, u32)], Error<E>> {
let data_array = self.get_all_configurations()?; let data_array = self.get_all_configurations()?;
for index in 0..mask_pairs.len() { for index in 0..mask_pairs.len() {
mask_pairs[index].1 = match mask_pairs[index].0.get_shift() { mask_pairs[index].1 = match mask_pairs[index].0.get_shift() {
@ -327,12 +330,15 @@ where
*/ */
fn set_all_configurations(&mut self, data_array: [u32; 3]) -> Result<(), Error<E>> { fn set_all_configurations(&mut self, data_array: [u32; 3]) -> Result<(), Error<E>> {
for register in 0x00..=0x02 { for register in 0x00..=0x02 {
self.write_register(register, &mut [ self.write_register(
((data_array[register as usize] >> 24) & 0xFF) as u8, register,
((data_array[register as usize] >> 16) & 0xFF) as u8, &mut [
((data_array[register as usize] >> 8 ) & 0xFF) as u8, ((data_array[register as usize] >> 24) & 0xFF) as u8,
((data_array[register as usize] >> 0 ) & 0xFF) as u8 ((data_array[register as usize] >> 16) & 0xFF) as u8,
])?; ((data_array[register as usize] >> 8) & 0xFF) as u8,
((data_array[register as usize] >> 0) & 0xFF) as u8,
],
)?;
} }
Ok(()) Ok(())
} }
@ -340,17 +346,28 @@ where
/* /*
* Set a set of configurations using DDSCFRMask * Set a set of configurations using DDSCFRMask
*/ */
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 SDIO_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;
} }
match mask_pairs[index].0.get_shift() { match mask_pairs[index].0.get_shift() {
0..=31 => mask_pairs[index].0.set_data_by_arg(&mut data_array[0], mask_pairs[index].1), 0..=31 => mask_pairs[index]
32..=63 => mask_pairs[index].0.set_data_by_arg(&mut data_array[1], mask_pairs[index].1), .0
64..=95 => mask_pairs[index].0.set_data_by_arg(&mut data_array[2], mask_pairs[index].1), .set_data_by_arg(&mut data_array[0], mask_pairs[index].1),
32..=63 => mask_pairs[index]
.0
.set_data_by_arg(&mut data_array[1], mask_pairs[index].1),
64..=95 => mask_pairs[index]
.0
.set_data_by_arg(&mut data_array[2], mask_pairs[index].1),
_ => panic!("Invalid DDSCFRMask!"), _ => panic!("Invalid DDSCFRMask!"),
}; };
} }
@ -366,12 +383,17 @@ where
* Frequency: Must be non-negative * Frequency: Must be non-negative
* Amplitude: In a scale from 0 to 1, taking float * Amplitude: In a scale from 0 to 1, taking float
*/ */
pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error<E>> { pub fn set_single_tone_profile(
&mut self,
profile: u8,
f_out: f64,
phase_offset: f64,
amp_scale_factor: f64,
) -> Result<(), Error<E>> {
assert!(profile < 8); assert!(profile < 8);
assert!(f_out >= 0.0); assert!(f_out >= 0.0);
assert!(phase_offset >= 0.0 && phase_offset < 360.0); assert!(phase_offset >= 0.0 && phase_offset < 360.0);
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0); assert!(amp_scale_factor >= 0.0 && amp_scale_factor <= 1.0);
let ftw = self.frequency_to_ftw(f_out); let ftw = self.frequency_to_ftw(f_out);
let pow = self.degree_to_pow(phase_offset); let pow = self.degree_to_pow(phase_offset);
@ -381,42 +403,42 @@ where
self.enable_single_tone_configuration()?; self.enable_single_tone_configuration()?;
// Transfer single tone profile data // Transfer single tone profile data
self.write_register(0x0E + profile, &mut [ self.write_register(
((asf >> 8 ) & 0xFF) as u8, 0x0E + profile,
((asf >> 0 ) & 0xFF) as u8, &mut [
((pow >> 8 ) & 0xFF) as u8, ((asf >> 8) & 0xFF) as u8,
((pow >> 0 ) & 0xFF) as u8, ((asf >> 0) & 0xFF) as u8,
((ftw >> 24) & 0xFF) as u8, ((pow >> 8) & 0xFF) as u8,
((ftw >> 16) & 0xFF) as u8, ((pow >> 0) & 0xFF) as u8,
((ftw >> 8 ) & 0xFF) as u8, ((ftw >> 24) & 0xFF) as u8,
((ftw >> 0 ) & 0xFF) as u8, ((ftw >> 16) & 0xFF) as u8,
]) ((ftw >> 8) & 0xFF) as u8,
((ftw >> 0) & 0xFF) as u8,
],
)
} }
/* /*
* Getter function for single tone profiles * Getter function for single tone profiles
*/ */
pub fn get_single_tone_profile(&mut self, profile: u8) -> Result<(f64, f64, f64), Error<E>> { pub fn get_single_tone_profile(&mut self, profile: u8) -> Result<(f64, f64, f64), Error<E>> {
assert!(profile < 8); assert!(profile < 8);
let mut profile_content: [u8; 8] = [0; 8]; let mut profile_content: [u8; 8] = [0; 8];
self.read_register(0x0E + profile, &mut profile_content)?; self.read_register(0x0E + profile, &mut profile_content)?;
// Convert ftw, pow and asf to f_out, phase and amplitude factor // Convert ftw, pow and asf to f_out, phase and amplitude factor
let ftw: u64 = (profile_content[4] as u64) << 24 | let ftw: u64 = (profile_content[4] as u64) << 24
(profile_content[5] as u64) << 16 | | (profile_content[5] as u64) << 16
(profile_content[6] as u64) << 8 | | (profile_content[6] as u64) << 8
(profile_content[7] as u64); | (profile_content[7] as u64);
let f_out: f64 = ((ftw as f64)/(((1_u64) << 32) as f64))*self.f_sys_clk; 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 | let pow: u64 = (profile_content[2] as u64) << 8 | (profile_content[3] as u64);
(profile_content[3] as u64); let phase: f64 = ((pow as f64) / (((1_u64) << 16) as f64)) * 360.0;
let phase: f64 = ((pow as f64)/(((1_u64) << 16) as f64))*360.0;
let asf: u64 = ((profile_content[0] & 0x3F) as u64) << 8 | let asf: u64 = ((profile_content[0] & 0x3F) as u64) << 8 | (profile_content[1] as u64);
(profile_content[1] as u64); let amplitude: f64 = (asf as f64) / (((1_u64) << 14) as f64);
let amplitude: f64 = (asf as f64)/(((1_u64) << 14) as f64);
Ok((f_out, phase, amplitude)) Ok((f_out, phase, amplitude))
} }
@ -426,8 +448,11 @@ where
* 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>> {
// Setup configuration registers before writing single tone register // Setup configuration registers before writing single tone register
self.enable_single_tone_configuration()?; self.enable_single_tone_configuration()?;
@ -440,8 +465,8 @@ where
// Overwrite FTW // Overwrite FTW
register[4] = ((ftw >> 24) & 0xFF) as u8; register[4] = ((ftw >> 24) & 0xFF) as u8;
register[5] = ((ftw >> 16) & 0xFF) as u8; register[5] = ((ftw >> 16) & 0xFF) as u8;
register[6] = ((ftw >> 8) & 0xFF) as u8; register[6] = ((ftw >> 8) & 0xFF) as u8;
register[7] = ((ftw >> 0) & 0xFF) as u8; register[7] = ((ftw >> 0) & 0xFF) as u8;
// Update FTW by writing back the register // Update FTW by writing back the register
self.write_register(0x0E + profile, &mut register) self.write_register(0x0E + profile, &mut register)
@ -452,8 +477,11 @@ where
* 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>> {
// Setup configuration registers before writing single tone register // Setup configuration registers before writing single tone register
self.enable_single_tone_configuration()?; self.enable_single_tone_configuration()?;
@ -464,8 +492,8 @@ where
self.read_register(0x0E + profile, &mut register)?; self.read_register(0x0E + profile, &mut register)?;
// Overwrite POW // Overwrite POW
register[2] = ((pow >> 8) & 0xFF) as u8; register[2] = ((pow >> 8) & 0xFF) as u8;
register[3] = ((pow >> 0) & 0xFF) as u8; register[3] = ((pow >> 0) & 0xFF) as u8;
// Update POW by writing back the register // Update POW by writing back the register
self.write_register(0x0E + profile, &mut register) self.write_register(0x0E + profile, &mut register)
@ -476,8 +504,11 @@ where
* 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>> {
// Setup configuration registers before writing single tone register // Setup configuration registers before writing single tone register
self.enable_single_tone_configuration()?; self.enable_single_tone_configuration()?;
@ -489,8 +520,8 @@ where
self.read_register(0x0E + profile, &mut register)?; self.read_register(0x0E + profile, &mut register)?;
// Overwrite POW // Overwrite POW
register[0] = ((asf >> 8) & 0xFF) as u8; register[0] = ((asf >> 8) & 0xFF) as u8;
register[1] = ((asf >> 0) & 0xFF) as u8; register[1] = ((asf >> 0) & 0xFF) as u8;
// Update POW by writing back the register // Update POW by writing back the register
self.write_register(0x0E + profile, &mut register) self.write_register(0x0E + profile, &mut register)
@ -499,16 +530,13 @@ where
// Helper function to switch into single tone mode // Helper function to switch into single tone mode
// Need to setup configuration registers before writing single tone register // Need to setup configuration registers before writing single tone register
fn enable_single_tone_configuration(&mut self) -> Result<(), Error<E>> { fn enable_single_tone_configuration(&mut self) -> Result<(), Error<E>> {
self.set_configurations(&mut [ self.set_configurations(&mut [
(DDSCFRMask::RAM_ENABLE, 0), (DDSCFRMask::RAM_ENABLE, 0),
(DDSCFRMask::DIGITAL_RAMP_ENABLE, 0), (DDSCFRMask::DIGITAL_RAMP_ENABLE, 0),
(DDSCFRMask::OSK_ENABLE, 0), (DDSCFRMask::OSK_ENABLE, 0),
(DDSCFRMask::PARALLEL_DATA_PORT_ENABLE, 0), (DDSCFRMask::PARALLEL_DATA_PORT_ENABLE, 0),
])?; ])?;
self.set_configurations(&mut [ self.set_configurations(&mut [(DDSCFRMask::EN_AMP_SCALE_SINGLE_TONE_PRO, 1)])
(DDSCFRMask::EN_AMP_SCALE_SINGLE_TONE_PRO, 1),
])
} }
// Helper function to configure the default frequency in the FTW register (0x07) // Helper function to configure the default frequency in the FTW register (0x07)
@ -531,19 +559,18 @@ where
pub fn get_default_ftw(&mut self) -> Result<f64, Error<E>> { pub fn get_default_ftw(&mut self) -> Result<f64, Error<E>> {
let mut ftw_bytes: [u8; 4] = [0; 4]; let mut ftw_bytes: [u8; 4] = [0; 4];
self.read_register(0x07, &mut ftw_bytes)?; self.read_register(0x07, &mut ftw_bytes)?;
let ftw: u64 = (ftw_bytes[0] as u64) << 24 | let ftw: u64 = (ftw_bytes[0] as u64) << 24
(ftw_bytes[1] as u64) << 16 | | (ftw_bytes[1] as u64) << 16
(ftw_bytes[2] as u64) << 8 | | (ftw_bytes[2] as u64) << 8
(ftw_bytes[3] as u64); | (ftw_bytes[3] as u64);
Ok(((ftw as f64)/(((1_u64) << 32) as f64))*self.f_sys_clk) Ok(((ftw as f64) / (((1_u64) << 32) as f64)) * self.f_sys_clk)
} }
pub fn get_default_asf(&mut self) -> Result<f64, Error<E>> { pub fn get_default_asf(&mut self) -> Result<f64, Error<E>> {
let mut asf_register: [u8; 4] = [0; 4]; let mut asf_register: [u8; 4] = [0; 4];
self.read_register(0x09, &mut asf_register)?; self.read_register(0x09, &mut asf_register)?;
let asf: u64 = ((asf_register[2] as u64) << 6) | let asf: u64 = ((asf_register[2] as u64) << 6) | ((asf_register[3] as u64) >> 2);
((asf_register[3] as u64) >> 2); Ok((asf as f64) / (((1_u64) << 14) as f64))
Ok((asf as f64)/(((1_u64) << 14) as f64))
} }
// Helper function to switch into RAM mode // Helper function to switch into RAM mode
@ -558,33 +585,39 @@ where
// Helper function to switch out of RAM mode // Helper function to switch out of RAM mode
// Need to setup configuration registers before writing into RAM profile register // Need to setup configuration registers before writing into RAM profile register
fn disable_ram_configuration(&mut self) -> Result<(), Error<E>> { fn disable_ram_configuration(&mut self) -> Result<(), Error<E>> {
self.set_configurations(&mut [ self.set_configurations(&mut [(DDSCFRMask::RAM_ENABLE, 0)])
(DDSCFRMask::RAM_ENABLE, 0),
])
} }
// Setup a RAM profile // Setup a RAM profile
pub fn set_up_ram_profile(&mut self, profile: u8, start_addr: u16, pub fn set_up_ram_profile(
end_addr: u16, no_dwell_high: bool, zero_crossing: bool, &mut self,
op_mode: RAMOperationMode, ramp_rate: u16 profile: u8,
)-> Result<(), Error<E>> start_addr: u16,
{ end_addr: u16,
no_dwell_high: bool,
zero_crossing: bool,
op_mode: RAMOperationMode,
ramp_rate: u16,
) -> Result<(), Error<E>> {
assert!(profile <= 7); assert!(profile <= 7);
assert!(end_addr >= start_addr); assert!(end_addr >= start_addr);
assert!(end_addr < 1024); assert!(end_addr < 1024);
self.enable_ram_configuration(self.ram_dest)?; self.enable_ram_configuration(self.ram_dest)?;
self.write_register(0x0E + profile, &mut [ self.write_register(
0x00, 0x0E + profile,
((ramp_rate >> 8) & 0xFF).try_into().unwrap(), &mut [
((ramp_rate >> 0) & 0xFF).try_into().unwrap(), 0x00,
((end_addr >> 2) & 0xFF).try_into().unwrap(), ((ramp_rate >> 8) & 0xFF).try_into().unwrap(),
((end_addr & 0x3) << 6).try_into().unwrap(), ((ramp_rate >> 0) & 0xFF).try_into().unwrap(),
((start_addr >> 2) & 0xFF).try_into().unwrap(), ((end_addr >> 2) & 0xFF).try_into().unwrap(),
((start_addr & 0x3) << 6).try_into().unwrap(), ((end_addr & 0x3) << 6).try_into().unwrap(),
((no_dwell_high as u8) << 5) | ((zero_crossing as u8) << 3) | (op_mode as u8) ((start_addr >> 2) & 0xFF).try_into().unwrap(),
]) ((start_addr & 0x3) << 6).try_into().unwrap(),
((no_dwell_high as u8) << 5) | ((zero_crossing as u8) << 3) | (op_mode as u8),
],
)
} }
pub fn get_ram_profile(&mut self, profile: u8) -> Result<(u16, u16, u16, u8), Error<E>> { pub fn get_ram_profile(&mut self, profile: u8) -> Result<(u16, u16, u16, u8), Error<E>> {
@ -627,29 +660,42 @@ where
// Write RAM bytes into DDS channel // Write RAM bytes into DDS channel
// Assume profile 7 is selected by the CPLD in prior // Assume profile 7 is selected by the CPLD in prior
pub unsafe fn commit_ram_buffer(&mut self, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error<E>> { pub unsafe fn commit_ram_buffer(
let ram_size = ((RAM_VEC.len() - 1) as u16)/4; &mut self,
if RAM_VEC.len() == 0 || RAM_VEC[0] != 0x16 || start_addr: u16,
(start_addr + ram_size) > 1024 || start_addr >= 1024 { ram_dest: RAMDestination,
return Err(Error::DDSRAMError) ) -> Result<(), Error<E>> {
let ram_size = ((RAM_VEC.len() - 1) as u16) / 4;
if RAM_VEC.len() == 0
|| RAM_VEC[0] != 0x16
|| (start_addr + ram_size) > 1024
|| start_addr >= 1024
{
return Err(Error::DDSRAMError);
} }
let end_addr: [u8; 2] = ((ram_size + start_addr - 1) << 6).to_be_bytes(); let end_addr: [u8; 2] = ((ram_size + start_addr - 1) << 6).to_be_bytes();
// Use profile 7 to setup a temperory RAM profile // Use profile 7 to setup a temperory RAM profile
self.enable_ram_configuration(ram_dest.clone())?; self.enable_ram_configuration(ram_dest.clone())?;
self.write_register(0x15, &mut [ self.write_register(
0x00, 0x15,
0x00, 0x01, &mut [
end_addr[0], end_addr[1], 0x00,
((start_addr >> 2) & 0xFF).try_into().unwrap(), 0x00,
((start_addr & 0x3) << 6).try_into().unwrap(), 0x01,
0x00 end_addr[0],
])?; end_addr[1],
((start_addr >> 2) & 0xFF).try_into().unwrap(),
((start_addr & 0x3) << 6).try_into().unwrap(),
0x00,
],
)?;
self.disable_ram_configuration()?; self.disable_ram_configuration()?;
log::info!("RAM buffer: {:?}", RAM_VEC); log::info!("RAM buffer: {:?}", RAM_VEC);
self.spi.transfer(&mut RAM_VEC) self.spi
.transfer(&mut RAM_VEC)
.map(|_| ()) .map(|_| ())
.map_err(Error::SPI)?; .map_err(Error::SPI)?;
RAM_VEC.clear(); RAM_VEC.clear();
@ -664,10 +710,7 @@ where
pub fn test(&mut self) -> Result<u32, Error<E>> { pub fn test(&mut self) -> Result<u32, Error<E>> {
// Test configuration register by getting SDIO_IN_ONLY and LSB_FIRST. // Test configuration register by getting SDIO_IN_ONLY and LSB_FIRST.
let mut error_count = 0; let mut error_count = 0;
let mut config_checks = [ let mut config_checks = [(DDSCFRMask::SDIO_IN_ONLY, 1), (DDSCFRMask::LSB_FIRST, 0)];
(DDSCFRMask::SDIO_IN_ONLY, 1),
(DDSCFRMask::LSB_FIRST, 0)
];
self.get_configurations(&mut config_checks)?; self.get_configurations(&mut config_checks)?;
if config_checks[0].1 == 0 { if config_checks[0].1 == 0 {
error_count += 1; error_count += 1;
@ -698,19 +741,17 @@ where
// Acquire N-divider, to adjust VCO if necessary // Acquire N-divider, to adjust VCO if necessary
(DDSCFRMask::N, 0), (DDSCFRMask::N, 0),
// Acquire REF_CLK divider bypass // Acquire REF_CLK divider bypass
(DDSCFRMask::REFCLK_IN_DIV_BYPASS, 0) (DDSCFRMask::REFCLK_IN_DIV_BYPASS, 0),
]; ];
self.get_configurations(&mut configuration_queries)?; self.get_configurations(&mut configuration_queries)?;
if configuration_queries[0].1 == 1 { if configuration_queries[0].1 == 1 {
// Recalculate sys_clk // Recalculate sys_clk
let divider :f64 = configuration_queries[1].1.into(); let divider: f64 = configuration_queries[1].1.into();
Ok(self.f_ref_clk * divider) Ok(self.f_ref_clk * divider)
} } else if configuration_queries[2].1 == 0 {
else if configuration_queries[2].1 == 0 {
Ok(self.f_ref_clk / 2.0) Ok(self.f_ref_clk / 2.0)
} } else {
else {
Ok(self.f_ref_clk) Ok(self.f_ref_clk)
} }
} }
@ -772,26 +813,8 @@ macro_rules! impl_register_io {
} }
impl_register_io!( impl_register_io!(
0x00, 4, 0x00, 4, 0x01, 4, 0x02, 4, 0x03, 4, 0x04, 4, 0x07, 4, 0x08, 2, 0x09, 4, 0x0A, 4, 0x0B, 8, 0x0C,
0x01, 4, 8, 0x0D, 4, 0x0E, 8, 0x0F, 8, 0x10, 8, 0x11, 8, 0x12, 8, 0x13, 8, 0x14, 8, 0x15, 8
0x02, 4,
0x03, 4,
0x04, 4,
0x07, 4,
0x08, 2,
0x09, 4,
0x0A, 4,
0x0B, 8,
0x0C, 8,
0x0D, 4,
0x0E, 8,
0x0F, 8,
0x10, 8,
0x11, 8,
0x12, 8,
0x13, 8,
0x14, 8,
0x15, 8
); );
// Append bytes to the RAM buffer // Append bytes to the RAM buffer

View File

@ -22,7 +22,7 @@ pub enum Error {
ErrorCorrectionCode, ErrorCorrectionCode,
ReadProtection, ReadProtection,
ReadSecure, ReadSecure,
WriteError WriteError,
} }
/// Embedded flash memory. /// Embedded flash memory.
@ -45,46 +45,58 @@ impl Flash {
// Unlock bank 1 if needed. // Unlock bank 1 if needed.
if self.bank1_is_locked() { if self.bank1_is_locked() {
self.registers.bank1_mut().keyr.write(|w| unsafe { self.registers
w.keyr().bits(0x45670123) .bank1_mut()
}); .keyr
self.registers.bank1_mut().keyr.write(|w| unsafe { .write(|w| unsafe { w.keyr().bits(0x45670123) });
w.keyr().bits(0xCDEF89AB) self.registers
}); .bank1_mut()
.keyr
.write(|w| unsafe { w.keyr().bits(0xCDEF89AB) });
} }
// Unlock bank 2 if needed. // Unlock bank 2 if needed.
if self.bank2_is_locked() { if self.bank2_is_locked() {
self.registers.bank2_mut().keyr.write(|w| unsafe { self.registers
w.keyr().bits(0x45670123) .bank2_mut()
}); .keyr
self.registers.bank2_mut().keyr.write(|w| unsafe { .write(|w| unsafe { w.keyr().bits(0x45670123) });
w.keyr().bits(0xCDEF89AB) self.registers
}); .bank2_mut()
.keyr
.write(|w| unsafe { w.keyr().bits(0xCDEF89AB) });
} }
} }
/// Unlocks the FLASH_OPTCR register /// Unlocks the FLASH_OPTCR register
pub fn unlock_optcr(&mut self) { pub fn unlock_optcr(&mut self) {
if self.optcr_is_locked() { if self.optcr_is_locked() {
self.registers.optkeyr_mut().write(|w| unsafe { self.registers
w.optkeyr().bits(0x08192A3B) .optkeyr_mut()
}); .write(|w| unsafe { w.optkeyr().bits(0x08192A3B) });
self.registers.optkeyr_mut().write(|w| unsafe { self.registers
w.optkeyr().bits(0x4C5D6E7F) .optkeyr_mut()
}); .write(|w| unsafe { w.optkeyr().bits(0x4C5D6E7F) });
} }
} }
/// Locks the FLASH_CR1/2 register. /// Locks the FLASH_CR1/2 register.
pub fn lock(&mut self) { pub fn lock(&mut self) {
self.registers.bank1_mut().cr.modify(|_, w| w.lock().set_bit()); self.registers
self.registers.bank2_mut().cr.modify(|_, w| w.lock().set_bit()); .bank1_mut()
.cr
.modify(|_, w| w.lock().set_bit());
self.registers
.bank2_mut()
.cr
.modify(|_, w| w.lock().set_bit());
} }
/// Lock the FLASH_OPTCR register /// Lock the FLASH_OPTCR register
pub fn lock_optcr(&mut self) { pub fn lock_optcr(&mut self) {
self.registers.optcr_mut().modify(|_, w| w.optlock().set_bit()); self.registers
.optcr_mut()
.modify(|_, w| w.optlock().set_bit());
} }
// More literal methods to get bank status // More literal methods to get bank status
@ -106,7 +118,7 @@ impl Flash {
fn is_busy(&self) -> bool { fn is_busy(&self) -> bool {
let (sr1, sr2) = ( let (sr1, sr2) = (
self.registers.bank1_mut().sr.read(), self.registers.bank1_mut().sr.read(),
self.registers.bank2_mut().sr.read() self.registers.bank2_mut().sr.read(),
); );
sr1.bsy().bit_is_set() || sr2.bsy().bit_is_set() sr1.bsy().bit_is_set() || sr2.bsy().bit_is_set()
} }
@ -115,7 +127,7 @@ impl Flash {
fn is_queuing(&self) -> bool { fn is_queuing(&self) -> bool {
let (sr1, sr2) = ( let (sr1, sr2) = (
self.registers.bank1_mut().sr.read(), self.registers.bank1_mut().sr.read(),
self.registers.bank2_mut().sr.read() self.registers.bank2_mut().sr.read(),
); );
sr1.qw().bit_is_set() || sr2.qw().bit_is_set() sr1.qw().bit_is_set() || sr2.qw().bit_is_set()
} }
@ -145,28 +157,26 @@ impl Flash {
// 4. Set START1/2 bit in FLASH_CR1/2 register // 4. Set START1/2 bit in FLASH_CR1/2 register
match bank_number { match bank_number {
1 => { 1 => {
self.registers.bank1_mut().cr.modify(|_, w| unsafe { self.registers
w.ser() .bank1_mut()
.set_bit() .cr
.snb() .modify(|_, w| unsafe { w.ser().set_bit().snb().bits(sector_number) });
.bits(sector_number) self.registers
}); .bank1_mut()
self.registers.bank1_mut().cr.modify(|_, w| { .cr
w.start().set_bit() .modify(|_, w| w.start().set_bit());
}); }
},
2 => { 2 => {
self.registers.bank2_mut().cr.modify(|_, w| unsafe { self.registers
w.ser() .bank2_mut()
.set_bit() .cr
.snb() .modify(|_, w| unsafe { w.ser().set_bit().snb().bits(sector_number) });
.bits(sector_number) self.registers
}); .bank2_mut()
self.registers.bank2_mut().cr.modify(|_, w| { .cr
w.start().set_bit() .modify(|_, w| w.start().set_bit());
}); }
}, _ => unreachable!(),
_ => unreachable!()
} }
// Lock the flash CR again // Lock the flash CR again
self.lock(); self.lock();
@ -187,24 +197,20 @@ impl Flash {
// mass erase is invoked since it supersedes sector erase. // mass erase is invoked since it supersedes sector erase.
match bank_number { match bank_number {
1 => { 1 => {
self.registers.bank1_mut().cr.modify(|_, w| { self.registers
w.ber() .bank1_mut()
.set_bit() .cr
.start() .modify(|_, w| w.ber().set_bit().start().set_bit());
.set_bit()
});
while self.registers.bank1_mut().sr.read().qw().bit_is_set() {} while self.registers.bank1_mut().sr.read().qw().bit_is_set() {}
}, }
2 => { 2 => {
self.registers.bank2_mut().cr.modify(|_, w| { self.registers
w.ber() .bank2_mut()
.set_bit() .cr
.start() .modify(|_, w| w.ber().set_bit().start().set_bit());
.set_bit()
});
while self.registers.bank2_mut().sr.read().qw().bit_is_set() {} while self.registers.bank2_mut().sr.read().qw().bit_is_set() {}
}, }
_ => unreachable!() _ => unreachable!(),
} }
// Lock the flash CR again // Lock the flash CR again
self.lock(); self.lock();
@ -219,9 +225,7 @@ impl Flash {
self.unlock(); self.unlock();
self.unlock_optcr(); self.unlock_optcr();
// 3. Set MER in FLASH_OPTCR to 1, wait until both QW to clear // 3. Set MER in FLASH_OPTCR to 1, wait until both QW to clear
self.registers.optcr_mut().modify(|_, w| { self.registers.optcr_mut().modify(|_, w| w.mer().set_bit());
w.mer().set_bit()
});
while self.is_queuing() {} while self.is_queuing() {}
// Lock the flash CR and OPTCR again // Lock the flash CR and OPTCR again
self.lock(); self.lock();
@ -231,11 +235,7 @@ impl Flash {
/// Program flash words (32-bytes). /// Program flash words (32-bytes).
/// Flashing incomplete flash word is "tolerated", but you have been warned.. /// Flashing incomplete flash word is "tolerated", but you have been warned..
pub fn program<'a, 'b>( pub fn program<'a, 'b>(&'a mut self, start_offset: usize, data: &'b [u8]) -> Result<(), Error> {
&'a mut self,
start_offset: usize,
data: &'b [u8],
) -> Result<(), Error> {
if (start_offset % 32 != 0) || (data.len() % 32 != 0) { if (start_offset % 32 != 0) || (data.len() % 32 != 0) {
log::warn!("Warning: This flash operation might not be supported..."); log::warn!("Warning: This flash operation might not be supported...");
log::warn!("Consider force writing the data in buffer..."); log::warn!("Consider force writing the data in buffer...");
@ -247,20 +247,19 @@ impl Flash {
while remaining_data.len() != 0 { while remaining_data.len() != 0 {
let single_write_size = 32 - (current_address % 32); let single_write_size = 32 - (current_address % 32);
// Determine the index that split the coming row and the remaining bytes. // Determine the index that split the coming row and the remaining bytes.
let splitting_index = core::cmp::min( let splitting_index = core::cmp::min(single_write_size, remaining_data.len());
single_write_size,
remaining_data.len()
);
let single_row_data = &remaining_data[..splitting_index]; let single_row_data = &remaining_data[..splitting_index];
// 1. Unlock FLASH_CR1/2 register if necessary // 1. Unlock FLASH_CR1/2 register if necessary
self.unlock(); self.unlock();
// 2. Set PG bit in FLASH_CR1/2 // 2. Set PG bit in FLASH_CR1/2
self.registers.bank1_mut().cr.modify(|_, w| { self.registers
w.pg().set_bit() .bank1_mut()
}); .cr
self.registers.bank2_mut().cr.modify(|_, w| { .modify(|_, w| w.pg().set_bit());
w.pg().set_bit() self.registers
}); .bank2_mut()
.cr
.modify(|_, w| w.pg().set_bit());
// 3. Check Protection // 3. Check Protection
// There should not be any data protection anyway... // There should not be any data protection anyway...
// 4. Write data byte by byte // 4. Write data byte by byte
@ -268,22 +267,32 @@ impl Flash {
while self.is_busy() {} while self.is_busy() {}
match self.check_errors() { match self.check_errors() {
Ok(_) => { Ok(_) => {
let address: *mut u8 = unsafe { let address: *mut u8 = unsafe { FLASH_BASE.add(current_address + index) };
FLASH_BASE.add(current_address + index)
};
if address > MAX_FLASH_ADDRESS { if address > MAX_FLASH_ADDRESS {
self.registers.bank1_mut().cr.modify(|_, w| w.pg().clear_bit()); self.registers
self.registers.bank2_mut().cr.modify(|_, w| w.pg().clear_bit()); .bank1_mut()
.cr
.modify(|_, w| w.pg().clear_bit());
self.registers
.bank2_mut()
.cr
.modify(|_, w| w.pg().clear_bit());
return Err(Error::WriteError); return Err(Error::WriteError);
} else { } else {
unsafe { unsafe {
core::ptr::write_volatile(address, *byte); core::ptr::write_volatile(address, *byte);
} }
} }
}, }
Err(error) => { Err(error) => {
self.registers.bank1_mut().cr.modify(|_, w| w.pg().clear_bit()); self.registers
self.registers.bank2_mut().cr.modify(|_, w| w.pg().clear_bit()); .bank1_mut()
.cr
.modify(|_, w| w.pg().clear_bit());
self.registers
.bank2_mut()
.cr
.modify(|_, w| w.pg().clear_bit());
return Err(error); return Err(error);
} }
} }
@ -295,8 +304,14 @@ impl Flash {
current_address += single_row_data.len(); current_address += single_row_data.len();
} }
// Reset PG1/2 // Reset PG1/2
self.registers.bank1_mut().cr.modify(|_, w| w.pg().clear_bit()); self.registers
self.registers.bank2_mut().cr.modify(|_, w| w.pg().clear_bit()); .bank1_mut()
.cr
.modify(|_, w| w.pg().clear_bit());
self.registers
.bank2_mut()
.cr
.modify(|_, w| w.pg().clear_bit());
// Lock FLASH_CR1/2 register // Lock FLASH_CR1/2 register
self.lock(); self.lock();
Ok(()) Ok(())
@ -304,27 +319,25 @@ impl Flash {
/// Force empty the bytes buffer for flash programming /// Force empty the bytes buffer for flash programming
/// Warning: It can invalidate the whole flash due to invalid CRC. /// Warning: It can invalidate the whole flash due to invalid CRC.
pub fn force_write<'a>( pub fn force_write<'a>(&'a mut self) -> Result<(), Error> {
&'a mut self
) -> Result<(), Error> {
if self.bank1_is_buffering() { if self.bank1_is_buffering() {
self.registers.bank1_mut().cr.modify( self.registers
|_, w| w.fw().set_bit() .bank1_mut()
); .cr
.modify(|_, w| w.fw().set_bit());
} }
if self.bank2_is_buffering() { if self.bank2_is_buffering() {
self.registers.bank2_mut().cr.modify( self.registers
|_, w| w.fw().set_bit() .bank2_mut()
); .cr
.modify(|_, w| w.fw().set_bit());
} }
Ok(()) Ok(())
} }
/// Read a slice from flash memory /// Read a slice from flash memory
pub fn read(&self, start_offset: usize, len: usize) -> &'static [u8] { pub fn read(&self, start_offset: usize, len: usize) -> &'static [u8] {
let address = unsafe { let address = unsafe { FLASH_BASE.add(start_offset) };
FLASH_BASE.add(start_offset)
};
unsafe { core::slice::from_raw_parts(address, len) } unsafe { core::slice::from_raw_parts(address, len) }
} }
@ -337,7 +350,7 @@ impl Flash {
fn check_errors(&self) -> Result<(), Error> { fn check_errors(&self) -> Result<(), Error> {
let (sr1, sr2) = ( let (sr1, sr2) = (
self.registers.bank1_mut().sr.read(), self.registers.bank1_mut().sr.read(),
self.registers.bank2_mut().sr.read() self.registers.bank2_mut().sr.read(),
); );
if sr1.wrperr().bit_is_set() || sr2.wrperr().bit_is_set() { if sr1.wrperr().bit_is_set() || sr2.wrperr().bit_is_set() {
@ -350,8 +363,11 @@ impl Flash {
Err(Error::Inconsistency) Err(Error::Inconsistency)
} else if sr1.operr().bit_is_set() || sr2.operr().bit_is_set() { } else if sr1.operr().bit_is_set() || sr2.operr().bit_is_set() {
Err(Error::Operation) Err(Error::Operation)
} else if sr1.sneccerr1().bit_is_set() || sr1.dbeccerr().bit_is_set() } else if sr1.sneccerr1().bit_is_set()
|| sr2.sneccerr1().bit_is_set() || sr2.dbeccerr().bit_is_set() { || sr1.dbeccerr().bit_is_set()
|| sr2.sneccerr1().bit_is_set()
|| sr2.dbeccerr().bit_is_set()
{
Err(Error::ErrorCorrectionCode) Err(Error::ErrorCorrectionCode)
} else if sr1.rdperr().bit_is_set() || sr2.rdperr().bit_is_set() { } else if sr1.rdperr().bit_is_set() || sr2.rdperr().bit_is_set() {
Err(Error::ReadProtection) Err(Error::ReadProtection)

View File

@ -1,8 +1,8 @@
use crate::flash::Flash;
use crate::flash::Error as FlashError; use crate::flash::Error as FlashError;
use crate::flash::Flash;
use sfkv::{Store, StoreBackend};
use stm32h7xx_hal::pac::FLASH; use stm32h7xx_hal::pac::FLASH;
use sfkv::{ StoreBackend, Store };
use log::error; use log::error;
@ -32,7 +32,7 @@ pub enum SFKVProcessType {
// Idea: Forces BACKUP_SPACE to act as a cache. // Idea: Forces BACKUP_SPACE to act as a cache.
pub struct FakeFlashManager { pub struct FakeFlashManager {
// FSM: Track the type of data being programmed // FSM: Track the type of data being programmed
process_type: SFKVProcessType process_type: SFKVProcessType,
} }
impl StoreBackend for FakeFlashManager { impl StoreBackend for FakeFlashManager {
@ -64,7 +64,7 @@ impl StoreBackend for FakeFlashManager {
let cache_ptr: *const u8 = &BACKUP_SPACE[offset] as *const u8; let cache_ptr: *const u8 = &BACKUP_SPACE[offset] as *const u8;
let payload_ptr: *const u8 = &(*payload)[0] as *const u8; let payload_ptr: *const u8 = &(*payload)[0] as *const u8;
BACKUP_SPACE[offset..(offset+payload.len())].copy_from_slice(payload); BACKUP_SPACE[offset..(offset + payload.len())].copy_from_slice(payload);
// This part program extra trailing termination bytes (4 0xFF s) // This part program extra trailing termination bytes (4 0xFF s)
// However, if the remaining space is too small, there is also no need to program these bytes // However, if the remaining space is too small, there is also no need to program these bytes
@ -74,10 +74,11 @@ impl StoreBackend for FakeFlashManager {
// then we can assert that no k-v pair were ditched // then we can assert that no k-v pair were ditched
// Then there is no concern of accidentally interpreting unused flash memory as malformatted. // Then there is no concern of accidentally interpreting unused flash memory as malformatted.
if (cache_ptr != payload_ptr) if (cache_ptr != payload_ptr)
&& (offset+payload.len()+4 <= FLASH_SECTOR_SIZE) && (offset + payload.len() + 4 <= FLASH_SECTOR_SIZE)
&& self.process_type == SFKVProcessType::Data && self.process_type == SFKVProcessType::Data
{ {
BACKUP_SPACE[(offset+payload.len())..(offset+payload.len()+4)].copy_from_slice(&[0xFF; 4]); BACKUP_SPACE[(offset + payload.len())..(offset + payload.len() + 4)]
.copy_from_slice(&[0xFF; 4]);
} }
} }
self.advance_state(); self.advance_state();
@ -98,18 +99,10 @@ impl FakeFlashManager {
pub fn advance_state(&mut self) { pub fn advance_state(&mut self) {
self.process_type = match self.process_type { self.process_type = match self.process_type {
SFKVProcessType::Length => { SFKVProcessType::Length => SFKVProcessType::Type,
SFKVProcessType::Type SFKVProcessType::Type => SFKVProcessType::Space,
}, SFKVProcessType::Space => SFKVProcessType::Data,
SFKVProcessType::Type => { SFKVProcessType::Data => SFKVProcessType::Length,
SFKVProcessType::Space
},
SFKVProcessType::Space => {
SFKVProcessType::Data
},
SFKVProcessType::Data => {
SFKVProcessType::Length
}
}; };
} }
@ -127,9 +120,7 @@ fn init_flash_cache(flash: FLASH) -> Flash {
// sfkv will perform in-place operation in cache // sfkv will perform in-place operation in cache
// flash will only be updated after invoking `save()` // flash will only be updated after invoking `save()`
unsafe { unsafe {
BACKUP_SPACE.copy_from_slice( BACKUP_SPACE.copy_from_slice(flash.read(FLASH_SECTOR_OFFSET, FLASH_SECTOR_SIZE));
flash.read(FLASH_SECTOR_OFFSET, FLASH_SECTOR_SIZE)
);
} }
flash flash
} }
@ -143,7 +134,8 @@ fn init_flash_store() -> FlashStore {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("corrupt store, erasing. error: {:?}", e); error!("corrupt store, erasing. error: {:?}", e);
let _ = store.erase() let _ = store
.erase()
.map_err(|e| error!("flash erase failed: {:?}", e)); .map_err(|e| error!("flash erase failed: {:?}", e));
} }
} }
@ -166,6 +158,8 @@ pub fn update_flash(flash: &mut Flash, store: &FlashStore) -> Result<(), FlashEr
flash_size + 0x20 - (flash_size % 0x20) flash_size + 0x20 - (flash_size % 0x20)
}; };
unsafe { unsafe {
flash.program(FLASH_SECTOR_OFFSET, &BACKUP_SPACE[..save_size]).map(|_| ()) flash
.program(FLASH_SECTOR_OFFSET, &BACKUP_SPACE[..save_size])
.map(|_| ())
} }
} }

View File

@ -1,7 +1,7 @@
use embedded_hal::{ use embedded_hal::{
digital::v2::{OutputPin, InputPin},
blocking::spi::Transfer,
blocking::delay::DelayUs, blocking::delay::DelayUs,
blocking::spi::Transfer,
digital::v2::{InputPin, OutputPin},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -14,31 +14,38 @@ pub enum FPGAFlashError {
const DATA: &'static [u8] = include_bytes!("../build/top.bin"); const DATA: &'static [u8] = include_bytes!("../build/top.bin");
// A public method to flash iCE40 FPGA on Humpback // A public method to flash iCE40 FPGA on Humpback
pub fn flash_ice40_fpga<SPI: Transfer<u8>, pub fn flash_ice40_fpga<
SS: OutputPin, SPI: Transfer<u8>,
RST: OutputPin, SS: OutputPin,
DELAY: DelayUs<u32>, RST: OutputPin,
DONE: InputPin> DELAY: DelayUs<u32>,
(mut spi: SPI, mut ss: SS, mut creset: RST, cdone: DONE, mut delay: DELAY) -> Result<(), FPGAFlashError> DONE: InputPin,
{ >(
mut spi: SPI,
mut ss: SS,
mut creset: RST,
cdone: DONE,
mut delay: DELAY,
) -> Result<(), FPGAFlashError> {
// Data buffer setup // Data buffer setup
let mut dummy_byte :[u8; 1] = [0x00]; let mut dummy_byte: [u8; 1] = [0x00];
let mut dummy_13_bytes :[u8; 13] = [0x00; 13]; let mut dummy_13_bytes: [u8; 13] = [0x00; 13];
// Drive CRESET_B low // Drive CRESET_B low
creset.set_low() creset
.map_err(|_| FPGAFlashError::NegotiationError)?; .set_low()
.map_err(|_| FPGAFlashError::NegotiationError)?;
// Drive SPI_SS_B low // Drive SPI_SS_B low
ss.set_low() ss.set_low().map_err(|_| FPGAFlashError::NegotiationError)?;
.map_err(|_| FPGAFlashError::NegotiationError)?;
// Wait at least 200ns // Wait at least 200ns
delay.delay_us(1_u32); delay.delay_us(1_u32);
// Drive CRESET_B high // Drive CRESET_B high
creset.set_high() creset
.map_err(|_| FPGAFlashError::NegotiationError)?; .set_high()
.map_err(|_| FPGAFlashError::NegotiationError)?;
// Wait at least another 1200us to clear internal config memory // Wait at least another 1200us to clear internal config memory
delay.delay_us(1200_u32); delay.delay_us(1200_u32);
@ -46,7 +53,7 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
// Before data transmission starts, check if C_DONE is truly low // Before data transmission starts, check if C_DONE is truly low
// If C_DONE is high, the FPGA reset procedure is unsuccessful // If C_DONE is high, the FPGA reset procedure is unsuccessful
match cdone.is_low() { match cdone.is_low() {
Ok(true) => {}, Ok(true) => {}
_ => return Err(FPGAFlashError::ResetStatusError), _ => return Err(FPGAFlashError::ResetStatusError),
}; };
@ -59,8 +66,7 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
.map_err(|_| FPGAFlashError::SPICommunicationError)?; .map_err(|_| FPGAFlashError::SPICommunicationError)?;
// Drive SPI_SS_B low // Drive SPI_SS_B low
ss.set_low() ss.set_low().map_err(|_| FPGAFlashError::NegotiationError)?;
.map_err(|_| FPGAFlashError::NegotiationError)?;
// Send the whole image without interruption // Send the whole image without interruption
for byte in DATA.into_iter() { for byte in DATA.into_iter() {
@ -80,12 +86,12 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
// Check the CDONE output from FPGA // Check the CDONE output from FPGA
// CDONE needs to be high // CDONE needs to be high
match cdone.is_high() { match cdone.is_high() {
Ok(true) => {}, Ok(true) => {}
_ => return Err(FPGAFlashError::ResetStatusError), _ => return Err(FPGAFlashError::ResetStatusError),
}; };
// Send at least another 49 clock cycles to activate IO pins (choosing same 13 bytes) // Send at least another 49 clock cycles to activate IO pins (choosing same 13 bytes)
spi.transfer(&mut dummy_13_bytes).map_err(|_| FPGAFlashError::SPICommunicationError)?; spi.transfer(&mut dummy_13_bytes)
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@
pub unsafe fn enable_itm( pub unsafe fn enable_itm(
dbgmcu: &stm32h7xx_hal::stm32::DBGMCU, dbgmcu: &stm32h7xx_hal::stm32::DBGMCU,
dcb: &mut cortex_m::peripheral::DCB, dcb: &mut cortex_m::peripheral::DCB,
itm: &mut cortex_m::peripheral::ITM itm: &mut cortex_m::peripheral::ITM,
) { ) {
// ARMv7-M DEMCR: Set TRCENA. Enables DWT and ITM units // ARMv7-M DEMCR: Set TRCENA. Enables DWT and ITM units
//unsafe { *(0xE000_EDFC as *mut u32) |= 1 << 24 }; //unsafe { *(0xE000_EDFC as *mut u32) |= 1 << 24 };
@ -56,20 +56,14 @@ use log::LevelFilter;
pub use cortex_m_log::log::Logger; pub use cortex_m_log::log::Logger;
use cortex_m_log::{ use cortex_m_log::{
destination::Itm as ItmDest, destination::Itm as ItmDest, modes::InterruptFree, printer::itm::InterruptSync,
printer::itm::InterruptSync, printer::itm::ItmSync,
modes::InterruptFree,
printer::itm::ItmSync
}; };
lazy_static! { lazy_static! {
static ref LOGGER: Logger<ItmSync<InterruptFree>> = Logger { static ref LOGGER: Logger<ItmSync<InterruptFree>> = Logger {
level: LevelFilter::Info, level: LevelFilter::Info,
inner: unsafe { inner: unsafe { InterruptSync::new(ItmDest::new(cortex_m::Peripherals::steal().ITM)) },
InterruptSync::new(
ItmDest::new(cortex_m::Peripherals::steal().ITM)
)
},
}; };
} }

View File

@ -4,36 +4,36 @@
#![feature(assoc_char_funcs)] #![feature(assoc_char_funcs)]
#![feature(alloc_error_handler)] #![feature(alloc_error_handler)]
use log::{ trace, warn }; use log::{trace, warn};
use stm32h7xx_hal::ethernet;
use stm32h7xx_hal::gpio::Speed; use stm32h7xx_hal::gpio::Speed;
use stm32h7xx_hal::rng::Rng; use stm32h7xx_hal::rng::Rng;
use stm32h7xx_hal::{pac, prelude::*, spi}; use stm32h7xx_hal::{pac, prelude::*, spi};
use stm32h7xx_hal::ethernet;
use smoltcp as net; use smoltcp as net;
use SaiTLS as tls; use SaiTLS as tls;
use minimq::{ MqttClient, QoS }; use minimq::{MqttClient, QoS};
use alloc_cortex_m::CortexMHeap;
use cortex_m; use cortex_m;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use alloc_cortex_m::CortexMHeap; use rand_core::{CryptoRng, RngCore};
use rtic::cyccnt::{Instant, U32Ext}; use rtic::cyccnt::{Instant, U32Ext};
use rand_core::{RngCore, CryptoRng};
use tls::TlsRng;
use tls::tls::TlsSocket;
use tls::tcp_stack::NetworkStack;
use heapless::{ String, consts, consts::* };
use core::alloc::Layout; use core::alloc::Layout;
use heapless::{consts, consts::*, String};
use tls::tcp_stack::NetworkStack;
use tls::tls::TlsSocket;
use tls::TlsRng;
#[macro_use] #[macro_use]
pub mod bitmask_macro; pub mod bitmask_macro;
pub mod spi_slave;
pub mod cpld; pub mod cpld;
pub mod spi_slave;
use crate::cpld::CPLD; use crate::cpld::CPLD;
pub mod config_register;
pub mod attenuator; pub mod attenuator;
pub mod config_register;
pub mod dds; pub mod dds;
pub mod net_store; pub mod net_store;
use crate::net_store::NetStorage; use crate::net_store::NetStorage;
@ -43,8 +43,8 @@ pub mod mqtt_mux;
use crate::mqtt_mux::MqttMux; use crate::mqtt_mux::MqttMux;
pub mod urukul; pub mod urukul;
use crate::urukul::Urukul; use crate::urukul::Urukul;
pub mod flash;
pub mod config; pub mod config;
pub mod flash;
use crate::config::get_net_config; use crate::config::get_net_config;
pub mod flash_store; pub mod flash_store;
use crate::flash_store::init_flash; use crate::flash_store::init_flash;
@ -77,7 +77,7 @@ static mut TX_STORAGE: [u8; 8192] = [0; 8192];
static mut RX_STORAGE: [u8; 8192] = [0; 8192]; static mut RX_STORAGE: [u8; 8192] = [0; 8192];
struct RngStruct { struct RngStruct {
rng: Rng rng: Rng,
} }
impl RngCore for RngStruct { impl RngCore for RngStruct {
@ -104,7 +104,6 @@ impl TlsRng for RngStruct {}
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
// Initialize the allocator BEFORE you use it // Initialize the allocator BEFORE you use it
let start = cortex_m_rt::heap_start() as usize; let start = cortex_m_rt::heap_start() as usize;
let size = 32768; // in bytes let size = 32768; // in bytes
@ -144,7 +143,7 @@ fn main() -> ! {
// Instantiate random number generator // Instantiate random number generator
let mut rng = RngStruct { let mut rng = RngStruct {
rng: dp.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks) rng: dp.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks),
}; };
// Create sfkv store and flash storage manager // Create sfkv store and flash storage manager
@ -267,7 +266,7 @@ fn main() -> ! {
let parts = switch.split(); let parts = switch.split();
let urukul = Urukul::new( let 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,
); );
let mut mqtt_mux = MqttMux::new(urukul, flash, flash_store, net_config.name.as_str()); let mut mqtt_mux = MqttMux::new(urukul, flash, flash_store, net_config.name.as_str());
@ -281,27 +280,17 @@ fn main() -> ! {
next_ms += 400_000.cycles(); next_ms += 400_000.cycles();
let mut tls_socket_entries: [_; 1] = Default::default(); let mut tls_socket_entries: [_; 1] = Default::default();
let mut tls_socket_set = tls::set::TlsSocketSet::new( let mut tls_socket_set = tls::set::TlsSocketSet::new(&mut tls_socket_entries[..]);
&mut tls_socket_entries[..]
);
let tx_buffer = net::socket::TcpSocketBuffer::new(unsafe { &mut TX_STORAGE[..] }); let tx_buffer = net::socket::TcpSocketBuffer::new(unsafe { &mut TX_STORAGE[..] });
let rx_buffer = net::socket::TcpSocketBuffer::new(unsafe { &mut RX_STORAGE[..] }); let rx_buffer = net::socket::TcpSocketBuffer::new(unsafe { &mut RX_STORAGE[..] });
let mut tcp_socket = net::socket::TcpSocket::new(rx_buffer, tx_buffer); let mut tcp_socket = net::socket::TcpSocket::new(rx_buffer, tx_buffer);
tcp_socket.set_keep_alive( tcp_socket.set_keep_alive(Some(net::time::Duration::from_secs(2)));
Some(net::time::Duration::from_secs(2))
);
let tls_socket = TlsSocket::new( let tls_socket = TlsSocket::new(tcp_socket, &mut rng, None);
tcp_socket,
&mut rng,
None
);
let _ = tls_socket_set.add(tls_socket); let _ = tls_socket_set.add(tls_socket);
let tls_stack = NetworkStack::new( let tls_stack = NetworkStack::new(tls_socket_set);
tls_socket_set
);
let mut client = MqttClient::<consts::U2048, _>::new( let mut client = MqttClient::<consts::U2048, _>::new(
net_config.broker_ip, net_config.broker_ip,
@ -325,28 +314,26 @@ fn main() -> ! {
// eth Poll if necessary // eth Poll if necessary
// Do not poll if eth link is down // Do not poll if eth link is down
while !eth_mac.phy_poll_link() {} while !eth_mac.phy_poll_link() {}
client.network_stack.poll(&mut net_interface, net::time::Instant::from_millis(time)); client
.network_stack
.poll(&mut net_interface, net::time::Instant::from_millis(time));
// Process MQTT messages about Urukul/Control // Process MQTT messages about Urukul/Control
let connection = match client let connection = match client.poll(|_client, topic, message, _properties| {
.poll(|_client, topic, message, _properties| { mqtt_mux.process_mqtt_ingress(topic, message);
mqtt_mux.process_mqtt_ingress(topic, message); }) {
}) { Ok(_) => true,
Ok(_) => true, Err(e) => {
Err(e) => { log::info!("Warn: {:?}", e);
log::info!("Warn: {:?}", e); false
false }
}, };
};
// Process MQTT response messages about Urukul // Process MQTT response messages about Urukul
for (topic, message) in mqtt_mux.process_mqtt_egress().unwrap() { for (topic, message) in mqtt_mux.process_mqtt_egress().unwrap() {
client.publish( client
topic.as_str(), .publish(topic.as_str(), message.as_bytes(), QoS::AtMostOnce, &[])
message.as_bytes(), .unwrap();
QoS::AtMostOnce,
&[]
).unwrap();
} }
if connection && !has_subscribed && tick { if connection && !has_subscribed && tick {
@ -354,8 +341,8 @@ fn main() -> ! {
str_builder.push_str("/Control/#").unwrap(); str_builder.push_str("/Control/#").unwrap();
match client.subscribe(str_builder.as_str(), &[]) { 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 => {}
}; };
} }
@ -363,4 +350,3 @@ fn main() -> ! {
tick = false; tick = false;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,8 @@
use embedded_hal::{
blocking::spi::Transfer,
digital::v2::OutputPin,
};
use crate::cpld::CPLD; use crate::cpld::CPLD;
use crate::urukul::Error; use crate::urukul::Error;
use embedded_hal::{blocking::spi::Transfer, digital::v2::OutputPin};
pub struct SPISlave<'a, SPI, CS0, CS1, CS2, GPIO> ( pub struct SPISlave<'a, SPI, CS0, CS1, CS2, GPIO>(
// SPI device to be multiplexed // SPI device to be multiplexed
&'a CPLD<SPI, CS0, CS1, CS2, GPIO>, &'a CPLD<SPI, CS0, CS1, CS2, GPIO>,
// Channel of SPI slave // Channel of SPI slave
@ -48,8 +45,12 @@ where
{ {
type Error = Error<E>; type Error = Error<E>;
fn transfer<'w>(&mut self, words: &'w mut[u8]) -> Result<&'w [u8], Self::Error> { fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
let mut dev = self.0.data.try_borrow_mut().map_err(|_| Error::GetRefMutDataError)?; let mut dev = self
.0
.data
.try_borrow_mut()
.map_err(|_| Error::GetRefMutDataError)?;
dev.select_chip(self.1).map_err(|_| Error::CSError)?; dev.select_chip(self.1).map_err(|_| Error::CSError)?;
let result = dev.spi.transfer(words).map_err(Error::SPI)?; let result = dev.spi.transfer(words).map_err(Error::SPI)?;
dev.select_chip(0).map_err(|_| Error::CSError)?; dev.select_chip(0).map_err(|_| Error::CSError)?;

View File

@ -1,15 +1,13 @@
extern crate embedded_hal; extern crate embedded_hal;
use embedded_hal::{ use embedded_hal::blocking::spi::Transfer;
blocking::spi::Transfer,
};
use serde::{ Serialize, Deserialize }; use serde::{Deserialize, Serialize};
use crate::config_register::ConfigRegister;
use crate::config_register::CFGMask;
use crate::config_register::StatusMask;
use crate::attenuator::Attenuator; use crate::attenuator::Attenuator;
use crate::dds::{ DDS, RAMOperationMode, RAMDestination }; use crate::config_register::CFGMask;
use crate::config_register::ConfigRegister;
use crate::config_register::StatusMask;
use crate::dds::{RAMDestination, RAMOperationMode, DDS};
/* /*
* Enum for structuring error * Enum for structuring error
@ -30,7 +28,7 @@ pub enum Error<E> {
MqttCommandError, MqttCommandError,
VectorOutOfSpace, VectorOutOfSpace,
StringOutOfSpace, StringOutOfSpace,
WaitRetry, // Prompt driver to just wait and retry WaitRetry, // Prompt driver to just wait and retry
} }
impl<E> Error<E> { impl<E> Error<E> {
@ -68,7 +66,15 @@ where
* Master constructor for the entire Urukul device * Master constructor for the entire Urukul device
* Supply 7 SPI channels to Urukul and 4 reference clock frequencies * Supply 7 SPI channels to Urukul and 4 reference clock frequencies
*/ */
pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI) -> Self { pub fn new(
spi1: SPI,
spi2: SPI,
spi3: SPI,
spi4: SPI,
spi5: SPI,
spi6: SPI,
spi7: SPI,
) -> Self {
// Construct Urukul // Construct Urukul
Urukul { Urukul {
config_register: ConfigRegister::new(spi1), config_register: ConfigRegister::new(spi1),
@ -100,21 +106,21 @@ where
self.config_register.set_configurations(&mut [ self.config_register.set_configurations(&mut [
(CFGMask::RST, 1), (CFGMask::RST, 1),
(CFGMask::IO_RST, 1), (CFGMask::IO_RST, 1),
(CFGMask::IO_UPDATE, 0) (CFGMask::IO_UPDATE, 0),
])?; ])?;
// Set 0 to all fields on configuration register. // Set 0 to all fields on configuration register.
self.config_register.set_configurations(&mut [ self.config_register.set_configurations(&mut [
(CFGMask::RF_SW, 0), (CFGMask::RF_SW, 0),
(CFGMask::LED, 0), (CFGMask::LED, 0),
(CFGMask::PROFILE, 0), (CFGMask::PROFILE, 0),
(CFGMask::IO_UPDATE, 0), (CFGMask::IO_UPDATE, 0),
(CFGMask::MASK_NU, 0), (CFGMask::MASK_NU, 0),
(CFGMask::CLK_SEL0, 0), (CFGMask::CLK_SEL0, 0),
(CFGMask::SYNC_SEL, 0), (CFGMask::SYNC_SEL, 0),
(CFGMask::RST, 0), (CFGMask::RST, 0),
(CFGMask::IO_RST, 0), (CFGMask::IO_RST, 0),
(CFGMask::CLK_SEL1, 0), (CFGMask::CLK_SEL1, 0),
(CFGMask::DIV, 0), (CFGMask::DIV, 0),
])?; ])?;
// Init all DDS chips. Configure SDIO as input only. // Init all DDS chips. Configure SDIO as input only.
for chip_no in 0..4 { for chip_no in 0..4 {
@ -145,12 +151,13 @@ where
impl<SPI, E> Urukul<SPI> impl<SPI, E> Urukul<SPI>
where where
SPI: Transfer<u8, Error = E> SPI: Transfer<u8, Error = E>,
{ {
pub fn get_channel_switch_status(&mut self, channel: u32) -> Result<bool, Error<E>> { pub fn get_channel_switch_status(&mut self, channel: u32) -> Result<bool, Error<E>> {
if channel < 4 { if channel < 4 {
self.config_register.get_status(StatusMask::RF_SW).map(|val| (val & (1 << channel)) != 0) self.config_register
.get_status(StatusMask::RF_SW)
.map(|val| (val & (1 << channel)) != 0)
} else { } else {
Err(Error::ParameterError) Err(Error::ParameterError)
} }
@ -166,15 +173,20 @@ where
prev & (!(1 << channel)) prev & (!(1 << channel))
} }
}; };
self.config_register.set_configurations(&mut [ self.config_register
(CFGMask::RF_SW, next), .set_configurations(&mut [(CFGMask::RF_SW, next)])
]).map(|_| ()) .map(|_| ())
} else { } else {
Err(Error::ParameterError) Err(Error::ParameterError)
} }
} }
pub fn set_clock(&mut self, source: ClockSource, frequency: f64, division: u8) -> Result<(), Error<E>> { pub fn set_clock(
&mut self,
source: ClockSource,
frequency: f64,
division: u8,
) -> Result<(), Error<E>> {
// Change clock source through configuration register // Change clock source through configuration register
self.set_clock_source(source)?; self.set_clock_source(source)?;
@ -188,30 +200,29 @@ where
pub fn get_clock_source(&mut self) -> Result<ClockSource, Error<E>> { pub fn get_clock_source(&mut self) -> Result<ClockSource, Error<E>> {
match ( match (
self.config_register.get_configuration(CFGMask::CLK_SEL0), self.config_register.get_configuration(CFGMask::CLK_SEL0),
self.config_register.get_configuration(CFGMask::CLK_SEL1) self.config_register.get_configuration(CFGMask::CLK_SEL1),
) { ) {
(0, 0) => Ok(ClockSource::OSC), (0, 0) => Ok(ClockSource::OSC),
(0, 1) => Ok(ClockSource::MMCX), (0, 1) => Ok(ClockSource::MMCX),
(1, _) => Ok(ClockSource::SMA), (1, _) => Ok(ClockSource::SMA),
_ => Err(Error::ConfigRegisterError) _ => 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 {
ClockSource::OSC => self.config_register.set_configurations(&mut [ ClockSource::OSC => self
(CFGMask::CLK_SEL0, 0), .config_register
(CFGMask::CLK_SEL1, 0), .set_configurations(&mut [(CFGMask::CLK_SEL0, 0), (CFGMask::CLK_SEL1, 0)]),
]), ClockSource::MMCX => self
ClockSource::MMCX => self.config_register.set_configurations(&mut [ .config_register
(CFGMask::CLK_SEL0, 0), .set_configurations(&mut [(CFGMask::CLK_SEL0, 0), (CFGMask::CLK_SEL1, 1)]),
(CFGMask::CLK_SEL1, 1), ClockSource::SMA => self
]), .config_register
ClockSource::SMA => self.config_register.set_configurations(&mut [ .set_configurations(&mut [(CFGMask::CLK_SEL0, 1)]),
(CFGMask::CLK_SEL0, 1), }
]), .map(|_| ())
}.map(|_| ())
} }
pub fn get_clock_frequency(&mut self) -> f64 { pub fn get_clock_frequency(&mut self) -> f64 {
@ -228,24 +239,24 @@ where
pub fn get_clock_division(&mut self) -> Result<u8, Error<E>> { pub fn get_clock_division(&mut self) -> Result<u8, Error<E>> {
match self.config_register.get_configuration(CFGMask::DIV) { match self.config_register.get_configuration(CFGMask::DIV) {
0| 3 => Ok(4), 0 | 3 => Ok(4),
1 => Ok(1), 1 => Ok(1),
2 => Ok(2), 2 => Ok(2),
_ => Err(Error::ConfigRegisterError) _ => 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
(CFGMask::DIV, 1), .config_register
]), .set_configurations(&mut [(CFGMask::DIV, 1)]),
2 => self.config_register.set_configurations(&mut [ 2 => self
(CFGMask::DIV, 2), .config_register
]), .set_configurations(&mut [(CFGMask::DIV, 2)]),
4 => self.config_register.set_configurations(&mut [ 4 => self
(CFGMask::DIV, 3), .config_register
]), .set_configurations(&mut [(CFGMask::DIV, 3)]),
_ => Err(Error::ParameterError), _ => Err(Error::ParameterError),
}?; }?;
@ -276,11 +287,16 @@ where
self.attenuator.get_channel_attenuation(channel) self.attenuator.get_channel_attenuation(channel)
} }
pub fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> { pub fn set_channel_attenuation(
&mut self,
channel: u8,
attenuation: f32,
) -> Result<(), Error<E>> {
if channel >= 4 || attenuation < 0.0 || attenuation > 31.5 { if channel >= 4 || attenuation < 0.0 || attenuation > 31.5 {
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
self.attenuator.set_channel_attenuation(channel, attenuation) self.attenuator
.set_channel_attenuation(channel, attenuation)
} }
pub fn get_profile(&mut self) -> Result<u8, Error<E>> { pub fn get_profile(&mut self) -> Result<u8, Error<E>> {
@ -291,38 +307,70 @@ where
if profile >= 8 { if profile >= 8 {
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
self.config_register.set_configurations(&mut [ self.config_register
(CFGMask::PROFILE, profile.into()) .set_configurations(&mut [(CFGMask::PROFILE, profile.into())])
]).map(|_| ()) .map(|_| ())
} }
pub fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> { pub fn set_channel_single_tone_profile(
if channel >= 4 || profile >= 8 || frequency < 0.0 || phase >= 360.0 || &mut self,
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 { channel: u8,
profile: u8,
frequency: f64,
phase: f64,
amplitude: f64,
) -> Result<(), Error<E>> {
if channel >= 4
|| profile >= 8
|| frequency < 0.0
|| phase >= 360.0
|| phase < 0.0
|| amplitude < 0.0
|| amplitude > 1.0
{
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
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>> { 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) 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 {
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency) self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency)
} }
pub fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Error<E>> { pub fn set_channel_single_tone_profile_phase(
&mut self,
channel: u8,
profile: u8,
phase: f64,
) -> Result<(), Error<E>> {
if channel >= 4 || profile >= 8 || phase >= 360.0 || phase < 0.0 { if channel >= 4 || profile >= 8 || phase >= 360.0 || phase < 0.0 {
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase) self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase)
} }
pub fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Error<E>> { pub fn set_channel_single_tone_profile_amplitude(
&mut self,
channel: u8,
profile: u8,
amplitude: f64,
) -> Result<(), Error<E>> {
if channel >= 4 || profile >= 8 || amplitude < 0.0 || amplitude > 1.0 { if channel >= 4 || profile >= 8 || amplitude < 0.0 || amplitude > 1.0 {
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
@ -330,18 +378,20 @@ where
} }
pub fn append_dds_ram_buffer(&mut self, data: &[u8]) -> Result<(), Error<E>> { pub fn append_dds_ram_buffer(&mut self, data: &[u8]) -> Result<(), Error<E>> {
unsafe { unsafe { Ok(crate::dds::append_ram_byte(data)) }
Ok(crate::dds::append_ram_byte(data))
}
} }
// Use profile 7 to write into the RAM // Use profile 7 to write into the RAM
pub fn commit_ram_buffer_to_channel(&mut self, channel: u8, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error<E>> { pub fn commit_ram_buffer_to_channel(
&mut self,
channel: u8,
start_addr: u16,
ram_dest: RAMDestination,
) -> Result<(), Error<E>> {
let profile = self.get_profile()?; let profile = self.get_profile()?;
self.set_profile(7)?; self.set_profile(7)?;
unsafe { unsafe {
self.dds[usize::from(channel)] self.dds[usize::from(channel)].commit_ram_buffer(start_addr, ram_dest)?;
.commit_ram_buffer(start_addr, ram_dest)?;
} }
self.set_profile(profile) self.set_profile(profile)
} }
@ -350,7 +400,11 @@ where
self.dds[usize::from(channel)].set_default_ftw(frequency) self.dds[usize::from(channel)].set_default_ftw(frequency)
} }
pub fn set_channel_default_asf(&mut self, channel: u8, amplitude_scale: f64) -> Result<(), Error<E>> { pub fn set_channel_default_asf(
&mut self,
channel: u8,
amplitude_scale: f64,
) -> Result<(), Error<E>> {
self.dds[usize::from(channel)].set_default_asf(amplitude_scale) self.dds[usize::from(channel)].set_default_asf(amplitude_scale)
} }
@ -362,14 +416,25 @@ where
self.dds[usize::from(channel)].get_default_asf() self.dds[usize::from(channel)].get_default_asf()
} }
pub fn set_channel_ram_profile(&mut self, channel: u8, profile: u8, start_addr: u16, pub fn set_channel_ram_profile(
end_addr: u16, op_mode: RAMOperationMode, ramp_rate: u16 &mut self,
channel: u8,
profile: u8,
start_addr: u16,
end_addr: u16,
op_mode: RAMOperationMode,
ramp_rate: u16,
) -> Result<(), Error<E>> { ) -> Result<(), Error<E>> {
self.dds[usize::from(channel)] self.dds[usize::from(channel)].set_up_ram_profile(
.set_up_ram_profile(profile, start_addr, end_addr, true, false, op_mode, ramp_rate) profile, start_addr, end_addr, true, false, op_mode, ramp_rate,
)
} }
pub fn get_channel_ram_profile(&mut self, channel: u8, profile: u8) -> Result<(u16, u16, u16, u8), Error<E>> { pub fn get_channel_ram_profile(
&mut self,
channel: u8,
profile: u8,
) -> Result<(u16, u16, u16, u8), Error<E>> {
self.dds[usize::from(channel)].get_ram_profile(profile) self.dds[usize::from(channel)].get_ram_profile(profile)
} }
@ -406,17 +471,28 @@ where
// Note: If a channel is masked, io_update must be completed through configuration register (IO_UPDATE bit-field) // Note: If a channel is masked, io_update must be completed through configuration register (IO_UPDATE bit-field)
// Implication: Deselect such channel if individual communication is needed. // Implication: Deselect such channel if individual communication is needed.
pub fn set_multi_channel_coverage(&mut self, channel: u8) -> Result<(), Error<E>> { pub fn set_multi_channel_coverage(&mut self, channel: u8) -> Result<(), Error<E>> {
self.config_register.set_configurations(&mut [ self.config_register
(CFGMask::MASK_NU, channel.into()) .set_configurations(&mut [(CFGMask::MASK_NU, channel.into())])
]).map(|_| ()) .map(|_| ())
} }
// Difference from individual single tone setup function: // Difference from individual single tone setup function:
// - Remove the need of passing channel // - Remove the need of passing channel
// All selected channels must share the same f_sys_clk // All selected channels must share the same f_sys_clk
pub fn set_multi_channel_single_tone_profile(&mut self, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> { pub fn set_multi_channel_single_tone_profile(
if profile >= 8 || frequency < 0.0 || phase >= 360.0 || &mut self,
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 { profile: u8,
frequency: f64,
phase: f64,
amplitude: f64,
) -> Result<(), Error<E>> {
if profile >= 8
|| frequency < 0.0
|| phase >= 360.0
|| phase < 0.0
|| amplitude < 0.0
|| amplitude > 1.0
{
return Err(Error::ParameterError); return Err(Error::ParameterError);
} }
// Check f_sys_clk of all selected channels // Check f_sys_clk of all selected channels
@ -434,18 +510,18 @@ where
} }
} }
self.multi_dds.set_sys_clk_frequency(reported_f_sys_clk)?; self.multi_dds.set_sys_clk_frequency(reported_f_sys_clk)?;
self.multi_dds.set_single_tone_profile(profile, frequency, phase, amplitude)?; self.multi_dds
.set_single_tone_profile(profile, frequency, phase, amplitude)?;
self.invoke_io_update() self.invoke_io_update()
} }
// Generate a pulse for io_update bit in configuration register // Generate a pulse for io_update bit in configuration register
// This acts like io_update in CPLD struct, but for multi-dds channel // This acts like io_update in CPLD struct, but for multi-dds channel
fn invoke_io_update(&mut self) -> Result<(), Error<E>> { fn invoke_io_update(&mut self) -> Result<(), Error<E>> {
self.config_register.set_configurations(&mut [ self.config_register
(CFGMask::IO_UPDATE, 1) .set_configurations(&mut [(CFGMask::IO_UPDATE, 1)])?;
])?; self.config_register
self.config_register.set_configurations(&mut [ .set_configurations(&mut [(CFGMask::IO_UPDATE, 0)])
(CFGMask::IO_UPDATE, 0) .map(|_| ())
]).map(|_| ())
} }
} }