Compare commits
No commits in common. "master" and "master" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,4 +3,3 @@ target/
|
|||||||
**/build
|
**/build
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
itm.fifo
|
itm.fifo
|
||||||
result
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
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
13
LICENSE
@ -1,13 +0,0 @@
|
|||||||
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.
|
|
14
README.md
14
README.md
@ -2,20 +2,14 @@
|
|||||||
|
|
||||||
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/mcu/humpback-dds/humpback-dds)
|
- [Continuous Integration](https://nixbld.m-labs.hk/job/stm32/stm32/humpback-dds)
|
||||||
- [Download latest firmware build](https://nixbld.m-labs.hk/job/mcu/humpback-dds/humpback-dds/latest/download-by-type/file/binary-dist)
|
- [Download latest firmware build](https://nixbld.m-labs.hk/job/stm32/stm32/humpback-dds/latest/download-by-type/file/binary-dist)
|
||||||
|
|
||||||
## Nix commands
|
## Nix commands
|
||||||
|
|
||||||
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``).
|
Start nix shell before anything.
|
||||||
|
|
||||||
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 develop
|
nix-shell
|
||||||
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.
|
||||||
|
1
cargosha256.nix
Normal file
1
cargosha256.nix
Normal file
@ -0,0 +1 @@
|
|||||||
|
"05lq7c5yz320gasp5q4g76lbj8s1hv1ypgjfg9iqb6jiryy4kv75"
|
99
flake.lock
generated
99
flake.lock
generated
@ -1,99 +0,0 @@
|
|||||||
{
|
|
||||||
"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
133
flake.nix
@ -1,133 +0,0 @@
|
|||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
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
|
||||||
@ -10,7 +8,7 @@ from migen.genlib.io import *
|
|||||||
|
|
||||||
|
|
||||||
class UrukulConnector(Module):
|
class UrukulConnector(Module):
|
||||||
def __init__(self, platform, eem_resource_name):
|
def __init__(self, platform):
|
||||||
# Include extension
|
# Include extension
|
||||||
spi_mosi = [
|
spi_mosi = [
|
||||||
("spi_mosi", 0, Pins("B16"), IOStandard("LVCMOS33"))
|
("spi_mosi", 0, Pins("B16"), IOStandard("LVCMOS33"))
|
||||||
@ -28,16 +26,16 @@ class UrukulConnector(Module):
|
|||||||
platform.add_extension(spi_mosi)
|
platform.add_extension(spi_mosi)
|
||||||
|
|
||||||
# Request EEM I/O & SPI
|
# Request EEM I/O & SPI
|
||||||
eem = [
|
eem0 = [
|
||||||
platform.request(eem_resource_name, 0),
|
platform.request("eem0", 0),
|
||||||
platform.request(eem_resource_name, 1),
|
platform.request("eem0", 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(f"{eem_resource_name}_n", 2),
|
platform.request("eem0_n", 2),
|
||||||
platform.request(eem_resource_name, 3),
|
platform.request("eem0", 3),
|
||||||
platform.request(eem_resource_name, 4),
|
platform.request("eem0", 4),
|
||||||
platform.request(eem_resource_name, 5),
|
platform.request("eem0", 5),
|
||||||
platform.request(eem_resource_name, 6)
|
platform.request("eem0", 6)
|
||||||
]
|
]
|
||||||
spi = platform.request("spi")
|
spi = platform.request("spi")
|
||||||
spi_mosi = platform.request("spi_mosi")
|
spi_mosi = platform.request("spi_mosi")
|
||||||
@ -58,47 +56,37 @@ 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=eem[2],
|
io_PACKAGE_PIN=eem0[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 += [
|
||||||
|
|
||||||
eem[0].p.eq(spi.clk),
|
eem0[0].p.eq(spi.clk),
|
||||||
eem[0].n.eq(~spi.clk),
|
eem0[0].n.eq(~spi.clk),
|
||||||
|
|
||||||
eem[1].p.eq(spi_mosi),
|
eem0[1].p.eq(spi_mosi),
|
||||||
eem[1].n.eq(~spi_mosi),
|
eem0[1].n.eq(~spi_mosi),
|
||||||
|
|
||||||
spi.miso.eq(~self.miso_n),
|
spi.miso.eq(~self.miso_n),
|
||||||
|
|
||||||
eem[3].p.eq(spi_cs[0]),
|
eem0[3].p.eq(spi_cs[0]),
|
||||||
eem[3].n.eq(~spi_cs[0]),
|
eem0[3].n.eq(~spi_cs[0]),
|
||||||
|
|
||||||
eem[4].p.eq(spi_cs[1]),
|
eem0[4].p.eq(spi_cs[1]),
|
||||||
eem[4].n.eq(~spi_cs[1]),
|
eem0[4].n.eq(~spi_cs[1]),
|
||||||
|
|
||||||
eem[5].p.eq(spi_cs[2]),
|
eem0[5].p.eq(spi_cs[2]),
|
||||||
eem[5].n.eq(~spi_cs[2]),
|
eem0[5].n.eq(~spi_cs[2]),
|
||||||
|
|
||||||
eem[6].p.eq(io_update),
|
eem0[6].p.eq(io_update),
|
||||||
eem[6].n.eq(~io_update),
|
eem0[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, f"eem{args.eem}"))
|
platform.build(UrukulConnector(platform))
|
||||||
|
20285
nix/channel-rust-nightly.toml
Normal file
20285
nix/channel-rust-nightly.toml
Normal file
File diff suppressed because it is too large
Load Diff
21
nix/itm.nix
Normal file
21
nix/itm.nix
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{ 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;
|
||||||
|
}
|
22
nix/migen.nix
Normal file
22
nix/migen.nix
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{ 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 ];
|
||||||
|
};
|
||||||
|
}
|
66
nix/openocd.nix
Normal file
66
nix/openocd.nix
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{ 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
24
nix/rustPlatform.nix
Normal file
24
nix/rustPlatform.nix
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{ 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;
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
[toolchain]
|
|
||||||
channel = "nightly-2020-10-30"
|
|
||||||
targets = [
|
|
||||||
"thumbv7em-none-eabihf",
|
|
||||||
]
|
|
||||||
profile = "default"
|
|
55
shell.nix
Normal file
55
shell.nix
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
];
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
use core::assert;
|
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
|
use core::assert;
|
||||||
|
|
||||||
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 {
|
||||||
@ -40,17 +40,12 @@ where
|
|||||||
}
|
}
|
||||||
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
|
self.spi.transfer(&mut clone)
|
||||||
.transfer(&mut clone)
|
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| Error::AttenuatorError)
|
.map_err(|_| Error::AttenuatorError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_attenuation(
|
pub fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> {
|
||||||
&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;
|
||||||
@ -74,7 +69,7 @@ where
|
|||||||
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +89,9 @@ where
|
|||||||
((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([3.5, 9.5, 20.0, 28.5])?;
|
self.set_attenuation([
|
||||||
|
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 {
|
||||||
@ -109,7 +106,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)?;
|
||||||
@ -119,7 +116,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>;
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use net::wire::EthernetAddress;
|
|
||||||
use net::wire::IpCidr;
|
|
||||||
use smoltcp as net;
|
use smoltcp as net;
|
||||||
|
use net::wire::IpCidr;
|
||||||
|
use net::wire::EthernetAddress;
|
||||||
|
|
||||||
use embedded_nal as nal;
|
use embedded_nal as nal;
|
||||||
use nal::IpAddr;
|
use nal::IpAddr;
|
||||||
|
|
||||||
use heapless::{consts::*, String};
|
use heapless::{ String, consts::* };
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{ Serialize, Deserialize };
|
||||||
|
|
||||||
use crate::flash_store::FlashStore;
|
|
||||||
use crate::urukul::ClockSource;
|
use crate::urukul::ClockSource;
|
||||||
|
use crate::flash_store::FlashStore;
|
||||||
|
|
||||||
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
|
||||||
|
@ -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,10 +33,13 @@ 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 { spi, data: 0 }
|
ConfigRegister {
|
||||||
|
spi,
|
||||||
|
data: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -44,16 +47,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
|
match self.spi.transfer(&mut [
|
||||||
.spi
|
|
||||||
.transfer(&mut [
|
|
||||||
((self.data & 0x00FF0000) >> 16) as u8,
|
((self.data & 0x00FF0000) >> 16) as u8,
|
||||||
((self.data & 0x0000FF00) >> 8) as u8,
|
((self.data & 0x0000FF00) >> 8) as u8,
|
||||||
((self.data & 0x000000FF) >> 0) as u8,
|
((self.data & 0x000000FF) >> 0) as u8,
|
||||||
])
|
]).map_err(Error::SPI) {
|
||||||
.map_err(Error::SPI)
|
Ok(arr) => Ok(
|
||||||
{
|
((arr[0] as u32) << 16) |
|
||||||
Ok(arr) => Ok(((arr[0] as u32) << 16) | ((arr[1] as u32) << 8) | arr[2] as u32),
|
((arr[1] as u32) << 8) |
|
||||||
|
arr[2] as u32
|
||||||
|
),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +113,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>;
|
||||||
|
|
||||||
@ -118,3 +121,4 @@ where
|
|||||||
self.spi.transfer(words).map_err(Error::SPI)
|
self.spi.transfer(words).map_err(Error::SPI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
src/cpld.rs
33
src/cpld.rs
@ -1,7 +1,10 @@
|
|||||||
use crate::spi_slave::Parts;
|
|
||||||
use crate::urukul::Error;
|
use crate::urukul::Error;
|
||||||
|
use crate::spi_slave::Parts;
|
||||||
|
|
||||||
use embedded_hal::{blocking::spi::Transfer, digital::v2::OutputPin};
|
use embedded_hal::{
|
||||||
|
digital::v2::OutputPin,
|
||||||
|
blocking::spi::Transfer,
|
||||||
|
};
|
||||||
|
|
||||||
use core::cell;
|
use core::cell;
|
||||||
|
|
||||||
@ -32,25 +35,20 @@ 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
|
self.io_update.set_high().map_err(|_| Error::IOUpdateError)?;
|
||||||
.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,
|
||||||
@ -71,25 +69,20 @@ 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
|
self.data.try_borrow_mut().map_err(|_| Error::GetRefMutDataError)?.spi.transfer(words).map_err(Error::SPI)
|
||||||
.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>
|
impl<SPI, CS0, CS1, CS2, GPIO, E> CPLD<SPI, CS0, CS1, CS2, GPIO> where
|
||||||
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,
|
||||||
|
253
src/dds.rs
253
src/dds.rs
@ -1,9 +1,9 @@
|
|||||||
use crate::urukul::Error;
|
|
||||||
use core::convert::TryInto;
|
|
||||||
use core::mem::size_of;
|
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
use heapless::consts::*;
|
use crate::urukul::Error;
|
||||||
|
use core::mem::size_of;
|
||||||
|
use core::convert::TryInto;
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
|
use heapless::consts::*;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -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,15 +118,18 @@ 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 [0x00, 0x00, 0x00, 0x02]) {
|
match self.write_register(0x00, &mut [
|
||||||
|
0x00, 0x00, 0x00, 0x02
|
||||||
|
]) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
@ -179,7 +182,9 @@ 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 [(DDSCFRMask::PFD_RESET, 0)])?;
|
self.set_configurations(&mut [
|
||||||
|
(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(())
|
||||||
}
|
}
|
||||||
@ -199,7 +204,7 @@ 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)?;
|
||||||
@ -216,20 +221,24 @@ 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 [(DDSCFRMask::PFD_RESET, 0)])?;
|
self.set_configurations(&mut [
|
||||||
|
(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(())
|
||||||
}
|
}
|
||||||
@ -291,28 +300,16 @@ 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[0] as u32) << 24 | (cfr_reg[1] as u32) << 16 | (cfr_reg[2] as u32) << 8 | (cfr_reg[3] as u32),
|
||||||
| (cfr_reg[1] as u32) << 16
|
(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[2] as u32) << 8
|
(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[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>(
|
pub fn get_configurations<'w>(&mut self, mask_pairs: &'w mut[(DDSCFRMask, u32)]) -> Result<&'w [(DDSCFRMask, u32)], Error<E>> {
|
||||||
&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() {
|
||||||
@ -330,15 +327,12 @@ 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(
|
self.write_register(register, &mut [
|
||||||
register,
|
|
||||||
&mut [
|
|
||||||
((data_array[register as usize] >> 24) & 0xFF) as u8,
|
((data_array[register as usize] >> 24) & 0xFF) as u8,
|
||||||
((data_array[register as usize] >> 16) & 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] >> 8 ) & 0xFF) as u8,
|
||||||
((data_array[register as usize] >> 0) & 0xFF) as u8,
|
((data_array[register as usize] >> 0 ) & 0xFF) as u8
|
||||||
],
|
])?;
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -346,28 +340,17 @@ where
|
|||||||
/*
|
/*
|
||||||
* Set a set of configurations using DDSCFRMask
|
* Set a set of configurations using DDSCFRMask
|
||||||
*/
|
*/
|
||||||
pub fn set_configurations(
|
pub fn set_configurations(&mut self, mask_pairs: &mut[(DDSCFRMask, u32)]) -> Result<(), Error<E>> {
|
||||||
&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
|
if mask_pairs[index].0 == DDSCFRMask::LSB_FIRST || mask_pairs[index].0 == DDSCFRMask::SDIO_IN_ONLY {
|
||||||
|| 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..=31 => mask_pairs[index].0.set_data_by_arg(&mut data_array[0], mask_pairs[index].1),
|
||||||
.0
|
32..=63 => mask_pairs[index].0.set_data_by_arg(&mut data_array[1], mask_pairs[index].1),
|
||||||
.set_data_by_arg(&mut data_array[0], mask_pairs[index].1),
|
64..=95 => mask_pairs[index].0.set_data_by_arg(&mut data_array[2], 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!"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -383,13 +366,8 @@ 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(
|
pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
||||||
&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);
|
||||||
@ -403,9 +381,7 @@ where
|
|||||||
self.enable_single_tone_configuration()?;
|
self.enable_single_tone_configuration()?;
|
||||||
|
|
||||||
// Transfer single tone profile data
|
// Transfer single tone profile data
|
||||||
self.write_register(
|
self.write_register(0x0E + profile, &mut [
|
||||||
0x0E + profile,
|
|
||||||
&mut [
|
|
||||||
((asf >> 8 ) & 0xFF) as u8,
|
((asf >> 8 ) & 0xFF) as u8,
|
||||||
((asf >> 0 ) & 0xFF) as u8,
|
((asf >> 0 ) & 0xFF) as u8,
|
||||||
((pow >> 8 ) & 0xFF) as u8,
|
((pow >> 8 ) & 0xFF) as u8,
|
||||||
@ -414,30 +390,32 @@ where
|
|||||||
((ftw >> 16) & 0xFF) as u8,
|
((ftw >> 16) & 0xFF) as u8,
|
||||||
((ftw >> 8 ) & 0xFF) as u8,
|
((ftw >> 8 ) & 0xFF) as u8,
|
||||||
((ftw >> 0 ) & 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 | (profile_content[3] as u64);
|
let pow: u64 = (profile_content[2] as u64) << 8 |
|
||||||
|
(profile_content[3] as u64);
|
||||||
let phase: f64 = ((pow as f64)/(((1_u64) << 16) as f64))*360.0;
|
let phase: f64 = ((pow as f64)/(((1_u64) << 16) as f64))*360.0;
|
||||||
|
|
||||||
let asf: u64 = ((profile_content[0] & 0x3F) as u64) << 8 | (profile_content[1] as u64);
|
let asf: u64 = ((profile_content[0] & 0x3F) as u64) << 8 |
|
||||||
|
(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))
|
||||||
@ -448,11 +426,8 @@ 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(
|
pub fn set_single_tone_profile_frequency(&mut self, profile: u8, f_out: f64) -> Result<(), Error<E>> {
|
||||||
&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()?;
|
||||||
|
|
||||||
@ -477,11 +452,8 @@ 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(
|
pub fn set_single_tone_profile_phase(&mut self, profile: u8, phase_offset: f64) -> Result<(), Error<E>> {
|
||||||
&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()?;
|
||||||
|
|
||||||
@ -504,11 +476,8 @@ 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(
|
pub fn set_single_tone_profile_amplitude(&mut self, profile: u8, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
||||||
&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()?;
|
||||||
|
|
||||||
@ -530,13 +499,16 @@ 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 [(DDSCFRMask::EN_AMP_SCALE_SINGLE_TONE_PRO, 1)])
|
self.set_configurations(&mut [
|
||||||
|
(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)
|
||||||
@ -559,17 +531,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) | ((asf_register[3] as u64) >> 2);
|
let asf: u64 = ((asf_register[2] as u64) << 6) |
|
||||||
|
((asf_register[3] as u64) >> 2);
|
||||||
Ok((asf as f64)/(((1_u64) << 14) as f64))
|
Ok((asf as f64)/(((1_u64) << 14) as f64))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,29 +558,24 @@ 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 [(DDSCFRMask::RAM_ENABLE, 0)])
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::RAM_ENABLE, 0),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup a RAM profile
|
// Setup a RAM profile
|
||||||
pub fn set_up_ram_profile(
|
pub fn set_up_ram_profile(&mut self, profile: u8, start_addr: u16,
|
||||||
&mut self,
|
end_addr: u16, no_dwell_high: bool, zero_crossing: bool,
|
||||||
profile: u8,
|
op_mode: RAMOperationMode, ramp_rate: u16
|
||||||
start_addr: u16,
|
)-> Result<(), Error<E>>
|
||||||
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(
|
self.write_register(0x0E + profile, &mut [
|
||||||
0x0E + profile,
|
|
||||||
&mut [
|
|
||||||
0x00,
|
0x00,
|
||||||
((ramp_rate >> 8) & 0xFF).try_into().unwrap(),
|
((ramp_rate >> 8) & 0xFF).try_into().unwrap(),
|
||||||
((ramp_rate >> 0) & 0xFF).try_into().unwrap(),
|
((ramp_rate >> 0) & 0xFF).try_into().unwrap(),
|
||||||
@ -615,9 +583,8 @@ where
|
|||||||
((end_addr & 0x3) << 6).try_into().unwrap(),
|
((end_addr & 0x3) << 6).try_into().unwrap(),
|
||||||
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
||||||
((start_addr & 0x3) << 6).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),
|
((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>> {
|
||||||
@ -660,42 +627,29 @@ 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(
|
pub unsafe fn commit_ram_buffer(&mut self, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error<E>> {
|
||||||
&mut self,
|
|
||||||
start_addr: u16,
|
|
||||||
ram_dest: RAMDestination,
|
|
||||||
) -> Result<(), Error<E>> {
|
|
||||||
let ram_size = ((RAM_VEC.len() - 1) as u16)/4;
|
let ram_size = ((RAM_VEC.len() - 1) as u16)/4;
|
||||||
if RAM_VEC.len() == 0
|
if RAM_VEC.len() == 0 || RAM_VEC[0] != 0x16 ||
|
||||||
|| RAM_VEC[0] != 0x16
|
(start_addr + ram_size) > 1024 || start_addr >= 1024 {
|
||||||
|| (start_addr + ram_size) > 1024
|
return Err(Error::DDSRAMError)
|
||||||
|| 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(
|
self.write_register(0x15, &mut [
|
||||||
0x15,
|
|
||||||
&mut [
|
|
||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00, 0x01,
|
||||||
0x01,
|
end_addr[0], end_addr[1],
|
||||||
end_addr[0],
|
|
||||||
end_addr[1],
|
|
||||||
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
((start_addr >> 2) & 0xFF).try_into().unwrap(),
|
||||||
((start_addr & 0x3) << 6).try_into().unwrap(),
|
((start_addr & 0x3) << 6).try_into().unwrap(),
|
||||||
0x00,
|
0x00
|
||||||
],
|
])?;
|
||||||
)?;
|
|
||||||
self.disable_ram_configuration()?;
|
self.disable_ram_configuration()?;
|
||||||
|
|
||||||
log::info!("RAM buffer: {:?}", RAM_VEC);
|
log::info!("RAM buffer: {:?}", RAM_VEC);
|
||||||
self.spi
|
self.spi.transfer(&mut RAM_VEC)
|
||||||
.transfer(&mut RAM_VEC)
|
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(Error::SPI)?;
|
.map_err(Error::SPI)?;
|
||||||
RAM_VEC.clear();
|
RAM_VEC.clear();
|
||||||
@ -710,7 +664,10 @@ 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 = [(DDSCFRMask::SDIO_IN_ONLY, 1), (DDSCFRMask::LSB_FIRST, 0)];
|
let mut config_checks = [
|
||||||
|
(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;
|
||||||
@ -741,7 +698,7 @@ 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)?;
|
||||||
@ -749,9 +706,11 @@ where
|
|||||||
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -813,8 +772,26 @@ macro_rules! impl_register_io {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl_register_io!(
|
impl_register_io!(
|
||||||
0x00, 4, 0x01, 4, 0x02, 4, 0x03, 4, 0x04, 4, 0x07, 4, 0x08, 2, 0x09, 4, 0x0A, 4, 0x0B, 8, 0x0C,
|
0x00, 4,
|
||||||
8, 0x0D, 4, 0x0E, 8, 0x0F, 8, 0x10, 8, 0x11, 8, 0x12, 8, 0x13, 8, 0x14, 8, 0x15, 8
|
0x01, 4,
|
||||||
|
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
|
||||||
|
222
src/flash.rs
222
src/flash.rs
@ -22,7 +22,7 @@ pub enum Error {
|
|||||||
ErrorCorrectionCode,
|
ErrorCorrectionCode,
|
||||||
ReadProtection,
|
ReadProtection,
|
||||||
ReadSecure,
|
ReadSecure,
|
||||||
WriteError,
|
WriteError
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Embedded flash memory.
|
/// Embedded flash memory.
|
||||||
@ -45,58 +45,46 @@ impl Flash {
|
|||||||
|
|
||||||
// Unlock bank 1 if needed.
|
// Unlock bank 1 if needed.
|
||||||
if self.bank1_is_locked() {
|
if self.bank1_is_locked() {
|
||||||
self.registers
|
self.registers.bank1_mut().keyr.write(|w| unsafe {
|
||||||
.bank1_mut()
|
w.keyr().bits(0x45670123)
|
||||||
.keyr
|
});
|
||||||
.write(|w| unsafe { w.keyr().bits(0x45670123) });
|
self.registers.bank1_mut().keyr.write(|w| unsafe {
|
||||||
self.registers
|
w.keyr().bits(0xCDEF89AB)
|
||||||
.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
|
self.registers.bank2_mut().keyr.write(|w| unsafe {
|
||||||
.bank2_mut()
|
w.keyr().bits(0x45670123)
|
||||||
.keyr
|
});
|
||||||
.write(|w| unsafe { w.keyr().bits(0x45670123) });
|
self.registers.bank2_mut().keyr.write(|w| unsafe {
|
||||||
self.registers
|
w.keyr().bits(0xCDEF89AB)
|
||||||
.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
|
self.registers.optkeyr_mut().write(|w| unsafe {
|
||||||
.optkeyr_mut()
|
w.optkeyr().bits(0x08192A3B)
|
||||||
.write(|w| unsafe { w.optkeyr().bits(0x08192A3B) });
|
});
|
||||||
self.registers
|
self.registers.optkeyr_mut().write(|w| unsafe {
|
||||||
.optkeyr_mut()
|
w.optkeyr().bits(0x4C5D6E7F)
|
||||||
.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
|
self.registers.bank1_mut().cr.modify(|_, w| w.lock().set_bit());
|
||||||
.bank1_mut()
|
self.registers.bank2_mut().cr.modify(|_, w| w.lock().set_bit());
|
||||||
.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
|
self.registers.optcr_mut().modify(|_, w| w.optlock().set_bit());
|
||||||
.optcr_mut()
|
|
||||||
.modify(|_, w| w.optlock().set_bit());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// More literal methods to get bank status
|
// More literal methods to get bank status
|
||||||
@ -118,7 +106,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()
|
||||||
}
|
}
|
||||||
@ -127,7 +115,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()
|
||||||
}
|
}
|
||||||
@ -157,26 +145,28 @@ 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
|
self.registers.bank1_mut().cr.modify(|_, w| unsafe {
|
||||||
.bank1_mut()
|
w.ser()
|
||||||
.cr
|
.set_bit()
|
||||||
.modify(|_, w| unsafe { w.ser().set_bit().snb().bits(sector_number) });
|
.snb()
|
||||||
self.registers
|
.bits(sector_number)
|
||||||
.bank1_mut()
|
});
|
||||||
.cr
|
self.registers.bank1_mut().cr.modify(|_, w| {
|
||||||
.modify(|_, w| w.start().set_bit());
|
w.start().set_bit()
|
||||||
}
|
});
|
||||||
|
},
|
||||||
2 => {
|
2 => {
|
||||||
self.registers
|
self.registers.bank2_mut().cr.modify(|_, w| unsafe {
|
||||||
.bank2_mut()
|
w.ser()
|
||||||
.cr
|
.set_bit()
|
||||||
.modify(|_, w| unsafe { w.ser().set_bit().snb().bits(sector_number) });
|
.snb()
|
||||||
self.registers
|
.bits(sector_number)
|
||||||
.bank2_mut()
|
});
|
||||||
.cr
|
self.registers.bank2_mut().cr.modify(|_, w| {
|
||||||
.modify(|_, w| w.start().set_bit());
|
w.start().set_bit()
|
||||||
}
|
});
|
||||||
_ => unreachable!(),
|
},
|
||||||
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
// Lock the flash CR again
|
// Lock the flash CR again
|
||||||
self.lock();
|
self.lock();
|
||||||
@ -197,20 +187,24 @@ 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
|
self.registers.bank1_mut().cr.modify(|_, w| {
|
||||||
.bank1_mut()
|
w.ber()
|
||||||
.cr
|
.set_bit()
|
||||||
.modify(|_, w| w.ber().set_bit().start().set_bit());
|
.start()
|
||||||
|
.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
|
self.registers.bank2_mut().cr.modify(|_, w| {
|
||||||
.bank2_mut()
|
w.ber()
|
||||||
.cr
|
.set_bit()
|
||||||
.modify(|_, w| w.ber().set_bit().start().set_bit());
|
.start()
|
||||||
|
.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();
|
||||||
@ -225,7 +219,9 @@ 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| w.mer().set_bit());
|
self.registers.optcr_mut().modify(|_, w| {
|
||||||
|
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();
|
||||||
@ -235,7 +231,11 @@ 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>(&'a mut self, start_offset: usize, data: &'b [u8]) -> Result<(), Error> {
|
pub fn program<'a, 'b>(
|
||||||
|
&'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,19 +247,20 @@ 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(single_write_size, remaining_data.len());
|
let splitting_index = core::cmp::min(
|
||||||
|
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
|
self.registers.bank1_mut().cr.modify(|_, w| {
|
||||||
.bank1_mut()
|
w.pg().set_bit()
|
||||||
.cr
|
});
|
||||||
.modify(|_, w| w.pg().set_bit());
|
self.registers.bank2_mut().cr.modify(|_, w| {
|
||||||
self.registers
|
w.pg().set_bit()
|
||||||
.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
|
||||||
@ -267,32 +268,22 @@ 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 { FLASH_BASE.add(current_address + index) };
|
let address: *mut u8 = unsafe {
|
||||||
|
FLASH_BASE.add(current_address + index)
|
||||||
|
};
|
||||||
if address > MAX_FLASH_ADDRESS {
|
if address > MAX_FLASH_ADDRESS {
|
||||||
self.registers
|
self.registers.bank1_mut().cr.modify(|_, w| w.pg().clear_bit());
|
||||||
.bank1_mut()
|
self.registers.bank2_mut().cr.modify(|_, w| w.pg().clear_bit());
|
||||||
.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
|
self.registers.bank1_mut().cr.modify(|_, w| w.pg().clear_bit());
|
||||||
.bank1_mut()
|
self.registers.bank2_mut().cr.modify(|_, w| w.pg().clear_bit());
|
||||||
.cr
|
|
||||||
.modify(|_, w| w.pg().clear_bit());
|
|
||||||
self.registers
|
|
||||||
.bank2_mut()
|
|
||||||
.cr
|
|
||||||
.modify(|_, w| w.pg().clear_bit());
|
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,14 +295,8 @@ impl Flash {
|
|||||||
current_address += single_row_data.len();
|
current_address += single_row_data.len();
|
||||||
}
|
}
|
||||||
// Reset PG1/2
|
// Reset PG1/2
|
||||||
self.registers
|
self.registers.bank1_mut().cr.modify(|_, w| w.pg().clear_bit());
|
||||||
.bank1_mut()
|
self.registers.bank2_mut().cr.modify(|_, w| w.pg().clear_bit());
|
||||||
.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(())
|
||||||
@ -319,25 +304,27 @@ 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>(&'a mut self) -> Result<(), Error> {
|
pub fn force_write<'a>(
|
||||||
|
&'a mut self
|
||||||
|
) -> Result<(), Error> {
|
||||||
if self.bank1_is_buffering() {
|
if self.bank1_is_buffering() {
|
||||||
self.registers
|
self.registers.bank1_mut().cr.modify(
|
||||||
.bank1_mut()
|
|_, w| w.fw().set_bit()
|
||||||
.cr
|
);
|
||||||
.modify(|_, w| w.fw().set_bit());
|
|
||||||
}
|
}
|
||||||
if self.bank2_is_buffering() {
|
if self.bank2_is_buffering() {
|
||||||
self.registers
|
self.registers.bank2_mut().cr.modify(
|
||||||
.bank2_mut()
|
|_, w| w.fw().set_bit()
|
||||||
.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 { FLASH_BASE.add(start_offset) };
|
let address = unsafe {
|
||||||
|
FLASH_BASE.add(start_offset)
|
||||||
|
};
|
||||||
unsafe { core::slice::from_raw_parts(address, len) }
|
unsafe { core::slice::from_raw_parts(address, len) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +337,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() {
|
||||||
@ -363,11 +350,8 @@ 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()
|
} else if sr1.sneccerr1().bit_is_set() || sr1.dbeccerr().bit_is_set()
|
||||||
|| sr1.dbeccerr().bit_is_set()
|
|| sr2.sneccerr1().bit_is_set() || sr2.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)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::flash::Error as FlashError;
|
|
||||||
use crate::flash::Flash;
|
use crate::flash::Flash;
|
||||||
|
use crate::flash::Error as FlashError;
|
||||||
|
|
||||||
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 {
|
||||||
@ -77,8 +77,7 @@ impl StoreBackend for FakeFlashManager {
|
|||||||
&& (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)]
|
BACKUP_SPACE[(offset+payload.len())..(offset+payload.len()+4)].copy_from_slice(&[0xFF; 4]);
|
||||||
.copy_from_slice(&[0xFF; 4]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.advance_state();
|
self.advance_state();
|
||||||
@ -99,10 +98,18 @@ 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::Type,
|
SFKVProcessType::Length => {
|
||||||
SFKVProcessType::Type => SFKVProcessType::Space,
|
SFKVProcessType::Type
|
||||||
SFKVProcessType::Space => SFKVProcessType::Data,
|
},
|
||||||
SFKVProcessType::Data => SFKVProcessType::Length,
|
SFKVProcessType::Type => {
|
||||||
|
SFKVProcessType::Space
|
||||||
|
},
|
||||||
|
SFKVProcessType::Space => {
|
||||||
|
SFKVProcessType::Data
|
||||||
|
},
|
||||||
|
SFKVProcessType::Data => {
|
||||||
|
SFKVProcessType::Length
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +127,9 @@ 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(flash.read(FLASH_SECTOR_OFFSET, FLASH_SECTOR_SIZE));
|
BACKUP_SPACE.copy_from_slice(
|
||||||
|
flash.read(FLASH_SECTOR_OFFSET, FLASH_SECTOR_SIZE)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
flash
|
flash
|
||||||
}
|
}
|
||||||
@ -134,8 +143,7 @@ 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
|
let _ = store.erase()
|
||||||
.erase()
|
|
||||||
.map_err(|e| error!("flash erase failed: {:?}", e));
|
.map_err(|e| error!("flash erase failed: {:?}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,8 +166,6 @@ 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
|
flash.program(FLASH_SECTOR_OFFSET, &BACKUP_SPACE[..save_size]).map(|_| ())
|
||||||
.program(FLASH_SECTOR_OFFSET, &BACKUP_SPACE[..save_size])
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
38
src/fpga.rs
38
src/fpga.rs
@ -1,7 +1,7 @@
|
|||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
blocking::delay::DelayUs,
|
digital::v2::{OutputPin, InputPin},
|
||||||
blocking::spi::Transfer,
|
blocking::spi::Transfer,
|
||||||
digital::v2::{InputPin, OutputPin},
|
blocking::delay::DelayUs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -14,37 +14,30 @@ 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<
|
pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
||||||
SPI: Transfer<u8>,
|
|
||||||
SS: OutputPin,
|
SS: OutputPin,
|
||||||
RST: OutputPin,
|
RST: OutputPin,
|
||||||
DELAY: DelayUs<u32>,
|
DELAY: DelayUs<u32>,
|
||||||
DONE: InputPin,
|
DONE: InputPin>
|
||||||
>(
|
(mut spi: SPI, mut ss: SS, mut creset: RST, cdone: DONE, mut delay: DELAY) -> Result<(), FPGAFlashError>
|
||||||
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
|
creset.set_low()
|
||||||
.set_low()
|
|
||||||
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
||||||
|
|
||||||
// Drive SPI_SS_B low
|
// Drive SPI_SS_B low
|
||||||
ss.set_low().map_err(|_| FPGAFlashError::NegotiationError)?;
|
ss.set_low()
|
||||||
|
.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
|
creset.set_high()
|
||||||
.set_high()
|
|
||||||
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
||||||
|
|
||||||
// Wait at least another 1200us to clear internal config memory
|
// Wait at least another 1200us to clear internal config memory
|
||||||
@ -53,7 +46,7 @@ pub fn flash_ice40_fpga<
|
|||||||
// 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),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,7 +59,8 @@ pub fn flash_ice40_fpga<
|
|||||||
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
||||||
|
|
||||||
// Drive SPI_SS_B low
|
// Drive SPI_SS_B low
|
||||||
ss.set_low().map_err(|_| FPGAFlashError::NegotiationError)?;
|
ss.set_low()
|
||||||
|
.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() {
|
||||||
@ -86,12 +80,12 @@ pub fn flash_ice40_fpga<
|
|||||||
// 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)
|
spi.transfer(&mut dummy_13_bytes).map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
||||||
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,14 +56,20 @@ 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, modes::InterruptFree, printer::itm::InterruptSync,
|
destination::Itm as ItmDest,
|
||||||
printer::itm::ItmSync,
|
printer::itm::InterruptSync,
|
||||||
|
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 { InterruptSync::new(ItmDest::new(cortex_m::Peripherals::steal().ITM)) },
|
inner: unsafe {
|
||||||
|
InterruptSync::new(
|
||||||
|
ItmDest::new(cortex_m::Peripherals::steal().ITM)
|
||||||
|
)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
src/main.rs
68
src/main.rs
@ -5,35 +5,35 @@
|
|||||||
#![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 rand_core::{CryptoRng, RngCore};
|
use alloc_cortex_m::CortexMHeap;
|
||||||
use rtic::cyccnt::{Instant, U32Ext};
|
use rtic::cyccnt::{Instant, U32Ext};
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use core::alloc::Layout;
|
|
||||||
use heapless::{consts, consts::*, String};
|
|
||||||
use tls::tcp_stack::NetworkStack;
|
|
||||||
use tls::tls::TlsSocket;
|
|
||||||
use tls::TlsRng;
|
use tls::TlsRng;
|
||||||
|
use tls::tls::TlsSocket;
|
||||||
|
use tls::tcp_stack::NetworkStack;
|
||||||
|
use heapless::{ String, consts, consts::* };
|
||||||
|
use core::alloc::Layout;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod bitmask_macro;
|
pub mod bitmask_macro;
|
||||||
pub mod cpld;
|
|
||||||
pub mod spi_slave;
|
pub mod spi_slave;
|
||||||
|
pub mod cpld;
|
||||||
use crate::cpld::CPLD;
|
use crate::cpld::CPLD;
|
||||||
pub mod attenuator;
|
|
||||||
pub mod config_register;
|
pub mod config_register;
|
||||||
|
pub mod attenuator;
|
||||||
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 config;
|
|
||||||
pub mod flash;
|
pub mod flash;
|
||||||
|
pub mod config;
|
||||||
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,6 +104,7 @@ 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
|
||||||
@ -143,7 +144,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
|
||||||
@ -266,7 +267,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());
|
||||||
@ -280,17 +281,27 @@ 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(&mut tls_socket_entries[..]);
|
let mut tls_socket_set = tls::set::TlsSocketSet::new(
|
||||||
|
&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(Some(net::time::Duration::from_secs(2)));
|
tcp_socket.set_keep_alive(
|
||||||
|
Some(net::time::Duration::from_secs(2))
|
||||||
|
);
|
||||||
|
|
||||||
let tls_socket = TlsSocket::new(tcp_socket, &mut rng, None);
|
let tls_socket = TlsSocket::new(
|
||||||
|
tcp_socket,
|
||||||
|
&mut rng,
|
||||||
|
None
|
||||||
|
);
|
||||||
let _ = tls_socket_set.add(tls_socket);
|
let _ = tls_socket_set.add(tls_socket);
|
||||||
|
|
||||||
let tls_stack = NetworkStack::new(tls_socket_set);
|
let tls_stack = NetworkStack::new(
|
||||||
|
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,
|
||||||
@ -314,26 +325,28 @@ 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
|
client.network_stack.poll(&mut net_interface, net::time::Instant::from_millis(time));
|
||||||
.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.poll(|_client, topic, message, _properties| {
|
let connection = match client
|
||||||
|
.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
|
client.publish(
|
||||||
.publish(topic.as_str(), message.as_bytes(), QoS::AtMostOnce, &[])
|
topic.as_str(),
|
||||||
.unwrap();
|
message.as_bytes(),
|
||||||
|
QoS::AtMostOnce,
|
||||||
|
&[]
|
||||||
|
).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if connection && !has_subscribed && tick {
|
if connection && !has_subscribed && tick {
|
||||||
@ -341,8 +354,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 => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,3 +363,4 @@ fn main() -> ! {
|
|||||||
tick = false;
|
tick = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
999
src/mqtt_mux.rs
999
src/mqtt_mux.rs
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,9 @@
|
|||||||
|
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
|
||||||
@ -46,11 +49,7 @@ 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
|
let mut dev = self.0.data.try_borrow_mut().map_err(|_| Error::GetRefMutDataError)?;
|
||||||
.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)?;
|
||||||
|
230
src/urukul.rs
230
src/urukul.rs
@ -1,13 +1,15 @@
|
|||||||
extern crate embedded_hal;
|
extern crate embedded_hal;
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::{
|
||||||
|
blocking::spi::Transfer,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{ Serialize, Deserialize };
|
||||||
|
|
||||||
use crate::attenuator::Attenuator;
|
|
||||||
use crate::config_register::CFGMask;
|
|
||||||
use crate::config_register::ConfigRegister;
|
use crate::config_register::ConfigRegister;
|
||||||
|
use crate::config_register::CFGMask;
|
||||||
use crate::config_register::StatusMask;
|
use crate::config_register::StatusMask;
|
||||||
use crate::dds::{RAMDestination, RAMOperationMode, DDS};
|
use crate::attenuator::Attenuator;
|
||||||
|
use crate::dds::{ DDS, RAMOperationMode, RAMDestination };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enum for structuring error
|
* Enum for structuring error
|
||||||
@ -66,15 +68,7 @@ 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(
|
pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI) -> Self {
|
||||||
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),
|
||||||
@ -106,7 +100,7 @@ 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 [
|
||||||
@ -151,13 +145,12 @@ 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
|
self.config_register.get_status(StatusMask::RF_SW).map(|val| (val & (1 << channel)) != 0)
|
||||||
.get_status(StatusMask::RF_SW)
|
|
||||||
.map(|val| (val & (1 << channel)) != 0)
|
|
||||||
} else {
|
} else {
|
||||||
Err(Error::ParameterError)
|
Err(Error::ParameterError)
|
||||||
}
|
}
|
||||||
@ -173,20 +166,15 @@ where
|
|||||||
prev & (!(1 << channel))
|
prev & (!(1 << channel))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.config_register
|
self.config_register.set_configurations(&mut [
|
||||||
.set_configurations(&mut [(CFGMask::RF_SW, next)])
|
(CFGMask::RF_SW, next),
|
||||||
.map(|_| ())
|
]).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::ParameterError)
|
Err(Error::ParameterError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_clock(
|
pub fn set_clock(&mut self, source: ClockSource, frequency: f64, division: u8) -> Result<(), Error<E>> {
|
||||||
&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)?;
|
||||||
|
|
||||||
@ -200,29 +188,30 @@ 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
|
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
||||||
.config_register
|
(CFGMask::CLK_SEL0, 0),
|
||||||
.set_configurations(&mut [(CFGMask::CLK_SEL0, 0), (CFGMask::CLK_SEL1, 0)]),
|
(CFGMask::CLK_SEL1, 0),
|
||||||
ClockSource::MMCX => self
|
]),
|
||||||
.config_register
|
ClockSource::MMCX => self.config_register.set_configurations(&mut [
|
||||||
.set_configurations(&mut [(CFGMask::CLK_SEL0, 0), (CFGMask::CLK_SEL1, 1)]),
|
(CFGMask::CLK_SEL0, 0),
|
||||||
ClockSource::SMA => self
|
(CFGMask::CLK_SEL1, 1),
|
||||||
.config_register
|
]),
|
||||||
.set_configurations(&mut [(CFGMask::CLK_SEL0, 1)]),
|
ClockSource::SMA => self.config_register.set_configurations(&mut [
|
||||||
}
|
(CFGMask::CLK_SEL0, 1),
|
||||||
.map(|_| ())
|
]),
|
||||||
|
}.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clock_frequency(&mut self) -> f64 {
|
pub fn get_clock_frequency(&mut self) -> f64 {
|
||||||
@ -242,21 +231,21 @@ where
|
|||||||
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
|
1 => self.config_register.set_configurations(&mut [
|
||||||
.config_register
|
(CFGMask::DIV, 1),
|
||||||
.set_configurations(&mut [(CFGMask::DIV, 1)]),
|
]),
|
||||||
2 => self
|
2 => self.config_register.set_configurations(&mut [
|
||||||
.config_register
|
(CFGMask::DIV, 2),
|
||||||
.set_configurations(&mut [(CFGMask::DIV, 2)]),
|
]),
|
||||||
4 => self
|
4 => self.config_register.set_configurations(&mut [
|
||||||
.config_register
|
(CFGMask::DIV, 3),
|
||||||
.set_configurations(&mut [(CFGMask::DIV, 3)]),
|
]),
|
||||||
_ => Err(Error::ParameterError),
|
_ => Err(Error::ParameterError),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
@ -287,16 +276,11 @@ where
|
|||||||
self.attenuator.get_channel_attenuation(channel)
|
self.attenuator.get_channel_attenuation(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_attenuation(
|
pub fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> {
|
||||||
&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
|
self.attenuator.set_channel_attenuation(channel, attenuation)
|
||||||
.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>> {
|
||||||
@ -307,70 +291,38 @@ where
|
|||||||
if profile >= 8 {
|
if profile >= 8 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
}
|
}
|
||||||
self.config_register
|
self.config_register.set_configurations(&mut [
|
||||||
.set_configurations(&mut [(CFGMask::PROFILE, profile.into())])
|
(CFGMask::PROFILE, profile.into())
|
||||||
.map(|_| ())
|
]).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_single_tone_profile(
|
pub fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
||||||
&mut self,
|
if channel >= 4 || profile >= 8 || frequency < 0.0 || phase >= 360.0 ||
|
||||||
channel: u8,
|
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
||||||
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(
|
pub fn get_channel_single_tone_profile(&mut self, channel: u8, profile: u8) -> Result<(f64, f64, f64), Error<E>> {
|
||||||
&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(
|
pub fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Error<E>> {
|
||||||
&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(
|
pub fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Error<E>> {
|
||||||
&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(
|
pub fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Error<E>> {
|
||||||
&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);
|
||||||
}
|
}
|
||||||
@ -378,20 +330,18 @@ 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 { Ok(crate::dds::append_ram_byte(data)) }
|
unsafe {
|
||||||
|
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(
|
pub fn commit_ram_buffer_to_channel(&mut self, channel: u8, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error<E>> {
|
||||||
&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)].commit_ram_buffer(start_addr, ram_dest)?;
|
self.dds[usize::from(channel)]
|
||||||
|
.commit_ram_buffer(start_addr, ram_dest)?;
|
||||||
}
|
}
|
||||||
self.set_profile(profile)
|
self.set_profile(profile)
|
||||||
}
|
}
|
||||||
@ -400,11 +350,7 @@ 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(
|
pub fn set_channel_default_asf(&mut self, channel: u8, amplitude_scale: f64) -> Result<(), Error<E>> {
|
||||||
&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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,25 +362,14 @@ where
|
|||||||
self.dds[usize::from(channel)].get_default_asf()
|
self.dds[usize::from(channel)].get_default_asf()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_ram_profile(
|
pub fn set_channel_ram_profile(&mut self, channel: u8, profile: u8, start_addr: u16,
|
||||||
&mut self,
|
end_addr: u16, op_mode: RAMOperationMode, ramp_rate: u16
|
||||||
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)].set_up_ram_profile(
|
self.dds[usize::from(channel)]
|
||||||
profile, start_addr, end_addr, true, false, op_mode, ramp_rate,
|
.set_up_ram_profile(profile, start_addr, end_addr, true, false, op_mode, ramp_rate)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_channel_ram_profile(
|
pub fn get_channel_ram_profile(&mut self, channel: u8, profile: u8) -> Result<(u16, u16, u16, u8), Error<E>> {
|
||||||
&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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,28 +406,17 @@ 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
|
self.config_register.set_configurations(&mut [
|
||||||
.set_configurations(&mut [(CFGMask::MASK_NU, channel.into())])
|
(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(
|
pub fn set_multi_channel_single_tone_profile(&mut self, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
||||||
&mut self,
|
if profile >= 8 || frequency < 0.0 || phase >= 360.0 ||
|
||||||
profile: u8,
|
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
||||||
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
|
||||||
@ -510,18 +434,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
|
self.multi_dds.set_single_tone_profile(profile, frequency, phase, amplitude)?;
|
||||||
.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
|
self.config_register.set_configurations(&mut [
|
||||||
.set_configurations(&mut [(CFGMask::IO_UPDATE, 1)])?;
|
(CFGMask::IO_UPDATE, 1)
|
||||||
self.config_register
|
])?;
|
||||||
.set_configurations(&mut [(CFGMask::IO_UPDATE, 0)])
|
self.config_register.set_configurations(&mut [
|
||||||
.map(|_| ())
|
(CFGMask::IO_UPDATE, 0)
|
||||||
|
]).map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user