Compare commits

...

4 Commits

28 changed files with 1433 additions and 1288 deletions

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

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

View File

@ -11,4 +11,3 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -14,7 +14,7 @@ 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
nix develop
python fpga/fpga_config.py
python fpga/fpga_config.py [--eem [0,1,2]]
cargo build --release
```

View File

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

58
flake.lock generated
View File

@ -1,18 +1,20 @@
{
"nodes": {
"mozilla-overlay": {
"flake": false,
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1638887313,
"narHash": "sha256-FMYV6rVtvSIfthgC1sK1xugh3y7muoQcvduMdriz4ag=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "7c1e8b1dd6ed0043fb4ee0b12b815256b0b9de6f",
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
@ -34,11 +36,32 @@
},
"root": {
"inputs": {
"mozilla-overlay": "mozilla-overlay",
"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": {
@ -54,6 +77,21 @@
"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",

View File

@ -2,16 +2,15 @@
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.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
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, mozilla-overlay, src-migen }:
outputs = { self, nixpkgs, rust-overlay, src-migen }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
rustManifest = pkgs.fetchurl {
url = "https://static.rust-lang.org/dist/2020-10-30/channel-rust-nightly.toml";
sha256 = "0iygcwzh8s0lfdghj5809krvzifc1ii1wm4sd3qqn7s0rz1s14hi";
};
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";
@ -19,21 +18,10 @@
propagatedBuildInputs = [ pkgs.python3Packages.colorama ];
};
targets = [
"thumbv7em-none-eabihf"
];
rustChannelOfTargets = _channel: _date: targets:
(pkgs.lib.rustLib.fromManifestFile rustManifest {
inherit (pkgs) stdenv lib fetchurl patchelf;
}).rust.override {
inherit targets;
extensions = ["rust-src"];
};
rust = rustChannelOfTargets "nightly" null targets;
rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform {
rustc = rust;
cargo = rust;
});
rustPlatform = pkgs.makeRustPlatform {
rustc = rustToolchain;
cargo = rustToolchain;
};
itm = rustPlatform.buildRustPackage rec {
version = "0.3.1";

View File

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

6
rust-toolchain.toml Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
pub unsafe fn enable_itm(
dbgmcu: &stm32h7xx_hal::stm32::DBGMCU,
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
//unsafe { *(0xE000_EDFC as *mut u32) |= 1 << 24 };
@ -56,20 +56,14 @@ use log::LevelFilter;
pub use cortex_m_log::log::Logger;
use cortex_m_log::{
destination::Itm as ItmDest,
printer::itm::InterruptSync,
modes::InterruptFree,
printer::itm::ItmSync
destination::Itm as ItmDest, modes::InterruptFree, printer::itm::InterruptSync,
printer::itm::ItmSync,
};
lazy_static! {
static ref LOGGER: Logger<ItmSync<InterruptFree>> = Logger {
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)) },
};
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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