Skip to content
Merged

Fls new #1039

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 81 additions & 88 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ members = [
"stream",
"platform",
]

[workspace.dependencies]
arbitrary-int = { version = "1.3.0", features = ["serde", "hint"] }
bitbybit = "1.4"
thiserror = { version = "2.0.11", default-features = false }
idsp = "0.21.0"
dsp-process = "0.2.0"
dsp-fixedpoint = "0.1.0"
# Keep this synced with the miniconf version in py/setup.py
miniconf = { version = "0.20", features = [
miniconf = { version = "0.20.1", features = [
"json-core",
"derive",
"postcard",
Expand Down Expand Up @@ -85,7 +87,7 @@ usbd-serial = "0.2"
miniconf.workspace = true
miniconf_mqtt = { version = "0.20" }
tca9539 = "0.2"
bitbybit = "1.3.3"
bitbybit.workspace = true
arbitrary-int.workspace = true
thiserror.workspace = true
embedded-hal-compat = "0.13.0"
Expand All @@ -98,6 +100,7 @@ urukul = { version = "0.1.1", path = "urukul" }
ad9912 = { version = "0.1.1", path = "ad9912" }
bytemuck = { version = "1.21.0", features = [
"zeroable_maybe_uninit",
"min_const_generics",
"derive",
] }
num-complex = { version = "0.4", features = [
Expand Down Expand Up @@ -170,4 +173,6 @@ debug = true
lto = true

[patch.crates-io]
miniconf = { git = "https://github.com/quartiq/miniconf.git" }
idsp = { git = "https://github.com/quartiq/idsp.git" }
dsp-process = { git = "https://github.com/quartiq/idsp.git" }
dsp-fixedpoint = { git = "https://github.com/quartiq/idsp.git" }
2 changes: 1 addition & 1 deletion ad9912/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ arbitrary-int.workspace = true
thiserror.workspace = true
num-traits = { version = "0.2.19", default-features = false }
embedded-hal = "1.0"
bitbybit = "1.3.3"
bitbybit.workspace = true
2 changes: 1 addition & 1 deletion ad9959/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ description = "AD9959 4-channel DDS SPI driver"
[dependencies]
embedded-hal = {version = "0.2.7", features = ["unproven"]}
bytemuck = "1.21.0"
bitbybit = "1.3.3"
bitbybit.workspace = true
arbitrary-int.workspace = true
10 changes: 6 additions & 4 deletions ad9959/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![no_std]

use core::num::Wrapping;

use arbitrary_int::{Number, u2, u3, u4, u5, u10, u14, u24};
use bitbybit::{bitenum, bitfield};
use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin};
Expand Down Expand Up @@ -521,8 +523,8 @@ impl ProfileSerializer {
pub fn push(
&mut self,
channels: Channel,
ftw: Option<u32>,
pow: Option<u14>,
ftw: Option<Wrapping<i32>>,
pow: Option<Wrapping<u14>>, // a-i v2: i14
acr: Option<Acr>,
) {
self.push_write(
Expand All @@ -534,10 +536,10 @@ impl ProfileSerializer {
.to_be_bytes(),
);
if let Some(ftw) = ftw {
self.push_write(Address::CFTW0, &ftw.to_be_bytes());
self.push_write(Address::CFTW0, &ftw.0.to_be_bytes());
}
if let Some(pow) = pow {
self.push_write(Address::CPOW0, &pow.value().to_be_bytes());
self.push_write(Address::CPOW0, &pow.0.value().to_be_bytes());
}
if let Some(acr) = acr {
self.push_write(Address::ACR, &acr.raw_value().to_be_bytes());
Expand Down
14 changes: 8 additions & 6 deletions py/stabilizer/mpll.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,19 @@ async def main():
f = np.absolute(g)
gf = g * f
b = np.array([gf * s, gf / s, -f, -1j * f])
b = np.linalg.pinv(np.hstack((b.real, b.imag)).T) @ -np.hstack(
(gf.real, gf.imag)
b, _res, _rank, _sing = np.linalg.lstsq(
np.hstack((b.real, b.imag)).T,
-np.hstack((gf.real, gf.imag)),
rcond=None,
)
q = b[2] + 1j * b[3]
g1 = q / (s * b[0] + b[1] / s + 1)
angle = np.angle(q) / (2 * np.pi)
fmean = np.sqrt(b[1] / b[0]) / (2 * np.pi * t)
width = (1 - np.sqrt(4 * b[0] * b[1] + 1)) / (2 * b[0]) / (
2 * np.pi * t
) - fmean
bb = 4 * b[0] * b[1]
width = (1 - np.sqrt(bb + 1) + np.sqrt(bb)) / (2 * np.pi * t * b[0])
_logger.warning(
"ch%i IQ resonance %g V, %g kHz, %g turns, %g kHz half 1/sqrt(2) width",
"ch%i IQ resonance %g V, %g kHz, %g turns, %g kHz FWHM",
ch,
np.absolute(q),
fmean / 1e3,
Expand All @@ -168,6 +169,7 @@ async def main():
ax[ch, 0].semilogx(f, g.imag)
ax[ch, 0].semilogx(f, g1.real)
ax[ch, 0].semilogx(f, g1.imag)
ax[ch, 0].semilogx(f, np.absolute(g1))
ax[ch, 0].grid()
ax[ch, 1].plot(g.real, g.imag)
ax[ch, 1].plot(g1.real, g1.imag)
Expand Down
91 changes: 67 additions & 24 deletions py/stabilizer/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ def get_local_ip(remote):
sock.close()


class Frame:
"""Stream frame constisting of a header and multiple data batches"""

# The magic header half-word at the start of each packet.
magic = 0x057B
header_fmt = struct.Struct("<HBBI")
header = namedtuple("Header", "magic format_id batches sequence")
parsers = {}

@classmethod
def register(cls, fmt):
"""Register a format"""
cls.parsers[fmt.format_id] = fmt

@classmethod
def parse(cls, data):
"""Parse known length frame"""
header = cls.header._make(cls.header_fmt.unpack_from(data))
if header.magic != cls.magic:
raise ValueError(f"Bad frame magic: {header.magic:#04x}")
try:
parser = cls.parsers[header.format_id]
except KeyError as exc:
raise ValueError(f"No parser for format: {header.format_id}") from exc
return parser(header, data[cls.header_fmt.size :])


class AdcDac:
"""Stabilizer default striming data format"""

Expand Down Expand Up @@ -79,6 +106,9 @@ def to_traces(self):
]


Frame.register(AdcDac)


class ThermostatEem:
"""Thermostat-EEM format"""

Expand All @@ -99,8 +129,12 @@ def to_si(self):
)


Frame.register(ThermostatEem)


class Fls:
"""FLS application stream format"""

format_id = 2

def __init__(self, header, body):
Expand All @@ -122,11 +156,15 @@ def demod(self):
return self.to_mu()[:, :, :2]


Frame.register(Fls)


class Mpll:
"""MPLL application stream format"""

format_id = 4

dtype = np.dtype([("demod", "<i4", (2,2)), ("phase", "<i4"), ("frequency", "<i4")])
dtype = np.dtype([("demod", "<i4", (2, 2)), ("phase", "<i4"), ("frequency", "<i4")])

def __init__(self, header, body):
self.header = header
Expand All @@ -141,31 +179,29 @@ def to_mu(self):
return np.frombuffer(self.body, self.dtype)


class Frame:
"""Stream frame constisting of a header and multiple data batches"""
Frame.register(Mpll)

# The magic header half-word at the start of each packet.
magic = 0x057B
header_fmt = struct.Struct("<HBBI")
header = namedtuple("Header", "magic format_id batches sequence")
parsers = {
AdcDac.format_id: AdcDac,
ThermostatEem.format_id: ThermostatEem,
Fls.format_id: Fls,
Mpll.format_id: Mpll,
}

@classmethod
def parse(cls, data):
"""Parse known length frame"""
header = cls.header._make(cls.header_fmt.unpack_from(data))
if header.magic != cls.magic:
raise ValueError(f"Bad frame magic: {header.magic:#04x}")
try:
parser = cls.parsers[header.format_id]
except KeyError as exc:
raise ValueError(f"No parser for format: {header.format_id}") from exc
return parser(header, data[cls.header_fmt.size :])
class Fls2:
"""FLS2 application stream format"""

format_id = 5

def __init__(self, header, body):
self.header = header
self.body = body

def size(self):
"""Return the data size of the frame in bytes"""
return len(self.body)

def to_mu(self):
"""Return the raw data in machine units"""
data = np.frombuffer(self.body, "<i4")
return data


Frame.register(Fls2)


class Stream(asyncio.DatagramProtocol):
Expand Down Expand Up @@ -199,6 +235,13 @@ async def open(cls, port=9293, addr="0.0.0.0", local="0.0.0.0", maxsize=1):
socket.IP_ADD_MEMBERSHIP,
multiaddr + local,
)
try:
# OS filter
sock.setsockopt(
socket.IPPROTO_IP, getattr(socket, "IP_MULTICAST_ALL", 49), 0
)
except OSError:
pass # Windows
sock.bind((addr, port))
return await loop.create_datagram_endpoint(
lambda: cls(maxsize),
Expand Down
Loading
Loading