Skip to content

Commit e72de4d

Browse files
authored
Merge pull request #1039 from quartiq/fls-new
Fls new
2 parents 7afc495 + a52c8f2 commit e72de4d

16 files changed

Lines changed: 934 additions & 1086 deletions

File tree

Cargo.lock

Lines changed: 81 additions & 88 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,16 @@ members = [
4040
"stream",
4141
"platform",
4242
]
43+
4344
[workspace.dependencies]
4445
arbitrary-int = { version = "1.3.0", features = ["serde", "hint"] }
46+
bitbybit = "1.4"
4547
thiserror = { version = "2.0.11", default-features = false }
4648
idsp = "0.21.0"
4749
dsp-process = "0.2.0"
4850
dsp-fixedpoint = "0.1.0"
4951
# Keep this synced with the miniconf version in py/setup.py
50-
miniconf = { version = "0.20", features = [
52+
miniconf = { version = "0.20.1", features = [
5153
"json-core",
5254
"derive",
5355
"postcard",
@@ -85,7 +87,7 @@ usbd-serial = "0.2"
8587
miniconf.workspace = true
8688
miniconf_mqtt = { version = "0.20" }
8789
tca9539 = "0.2"
88-
bitbybit = "1.3.3"
90+
bitbybit.workspace = true
8991
arbitrary-int.workspace = true
9092
thiserror.workspace = true
9193
embedded-hal-compat = "0.13.0"
@@ -98,6 +100,7 @@ urukul = { version = "0.1.1", path = "urukul" }
98100
ad9912 = { version = "0.1.1", path = "ad9912" }
99101
bytemuck = { version = "1.21.0", features = [
100102
"zeroable_maybe_uninit",
103+
"min_const_generics",
101104
"derive",
102105
] }
103106
num-complex = { version = "0.4", features = [
@@ -170,4 +173,6 @@ debug = true
170173
lto = true
171174

172175
[patch.crates-io]
173-
miniconf = { git = "https://github.com/quartiq/miniconf.git" }
176+
idsp = { git = "https://github.com/quartiq/idsp.git" }
177+
dsp-process = { git = "https://github.com/quartiq/idsp.git" }
178+
dsp-fixedpoint = { git = "https://github.com/quartiq/idsp.git" }

ad9912/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ arbitrary-int.workspace = true
1414
thiserror.workspace = true
1515
num-traits = { version = "0.2.19", default-features = false }
1616
embedded-hal = "1.0"
17-
bitbybit = "1.3.3"
17+
bitbybit.workspace = true

ad9959/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ description = "AD9959 4-channel DDS SPI driver"
1010
[dependencies]
1111
embedded-hal = {version = "0.2.7", features = ["unproven"]}
1212
bytemuck = "1.21.0"
13-
bitbybit = "1.3.3"
13+
bitbybit.workspace = true
1414
arbitrary-int.workspace = true

ad9959/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![no_std]
22

3+
use core::num::Wrapping;
4+
35
use arbitrary_int::{Number, u2, u3, u4, u5, u10, u14, u24};
46
use bitbybit::{bitenum, bitfield};
57
use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin};
@@ -521,8 +523,8 @@ impl ProfileSerializer {
521523
pub fn push(
522524
&mut self,
523525
channels: Channel,
524-
ftw: Option<u32>,
525-
pow: Option<u14>,
526+
ftw: Option<Wrapping<i32>>,
527+
pow: Option<Wrapping<u14>>, // a-i v2: i14
526528
acr: Option<Acr>,
527529
) {
528530
self.push_write(
@@ -534,10 +536,10 @@ impl ProfileSerializer {
534536
.to_be_bytes(),
535537
);
536538
if let Some(ftw) = ftw {
537-
self.push_write(Address::CFTW0, &ftw.to_be_bytes());
539+
self.push_write(Address::CFTW0, &ftw.0.to_be_bytes());
538540
}
539541
if let Some(pow) = pow {
540-
self.push_write(Address::CPOW0, &pow.value().to_be_bytes());
542+
self.push_write(Address::CPOW0, &pow.0.value().to_be_bytes());
541543
}
542544
if let Some(acr) = acr {
543545
self.push_write(Address::ACR, &acr.raw_value().to_be_bytes());

py/stabilizer/mpll.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,19 @@ async def main():
143143
f = np.absolute(g)
144144
gf = g * f
145145
b = np.array([gf * s, gf / s, -f, -1j * f])
146-
b = np.linalg.pinv(np.hstack((b.real, b.imag)).T) @ -np.hstack(
147-
(gf.real, gf.imag)
146+
b, _res, _rank, _sing = np.linalg.lstsq(
147+
np.hstack((b.real, b.imag)).T,
148+
-np.hstack((gf.real, gf.imag)),
149+
rcond=None,
148150
)
149151
q = b[2] + 1j * b[3]
150152
g1 = q / (s * b[0] + b[1] / s + 1)
151153
angle = np.angle(q) / (2 * np.pi)
152154
fmean = np.sqrt(b[1] / b[0]) / (2 * np.pi * t)
153-
width = (1 - np.sqrt(4 * b[0] * b[1] + 1)) / (2 * b[0]) / (
154-
2 * np.pi * t
155-
) - fmean
155+
bb = 4 * b[0] * b[1]
156+
width = (1 - np.sqrt(bb + 1) + np.sqrt(bb)) / (2 * np.pi * t * b[0])
156157
_logger.warning(
157-
"ch%i IQ resonance %g V, %g kHz, %g turns, %g kHz half 1/sqrt(2) width",
158+
"ch%i IQ resonance %g V, %g kHz, %g turns, %g kHz FWHM",
158159
ch,
159160
np.absolute(q),
160161
fmean / 1e3,
@@ -168,6 +169,7 @@ async def main():
168169
ax[ch, 0].semilogx(f, g.imag)
169170
ax[ch, 0].semilogx(f, g1.real)
170171
ax[ch, 0].semilogx(f, g1.imag)
172+
ax[ch, 0].semilogx(f, np.absolute(g1))
171173
ax[ch, 0].grid()
172174
ax[ch, 1].plot(g.real, g.imag)
173175
ax[ch, 1].plot(g1.real, g1.imag)

py/stabilizer/stream.py

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,33 @@ def get_local_ip(remote):
3737
sock.close()
3838

3939

40+
class Frame:
41+
"""Stream frame constisting of a header and multiple data batches"""
42+
43+
# The magic header half-word at the start of each packet.
44+
magic = 0x057B
45+
header_fmt = struct.Struct("<HBBI")
46+
header = namedtuple("Header", "magic format_id batches sequence")
47+
parsers = {}
48+
49+
@classmethod
50+
def register(cls, fmt):
51+
"""Register a format"""
52+
cls.parsers[fmt.format_id] = fmt
53+
54+
@classmethod
55+
def parse(cls, data):
56+
"""Parse known length frame"""
57+
header = cls.header._make(cls.header_fmt.unpack_from(data))
58+
if header.magic != cls.magic:
59+
raise ValueError(f"Bad frame magic: {header.magic:#04x}")
60+
try:
61+
parser = cls.parsers[header.format_id]
62+
except KeyError as exc:
63+
raise ValueError(f"No parser for format: {header.format_id}") from exc
64+
return parser(header, data[cls.header_fmt.size :])
65+
66+
4067
class AdcDac:
4168
"""Stabilizer default striming data format"""
4269

@@ -79,6 +106,9 @@ def to_traces(self):
79106
]
80107

81108

109+
Frame.register(AdcDac)
110+
111+
82112
class ThermostatEem:
83113
"""Thermostat-EEM format"""
84114

@@ -99,8 +129,12 @@ def to_si(self):
99129
)
100130

101131

132+
Frame.register(ThermostatEem)
133+
134+
102135
class Fls:
103136
"""FLS application stream format"""
137+
104138
format_id = 2
105139

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

124158

159+
Frame.register(Fls)
160+
161+
125162
class Mpll:
126163
"""MPLL application stream format"""
164+
127165
format_id = 4
128166

129-
dtype = np.dtype([("demod", "<i4", (2,2)), ("phase", "<i4"), ("frequency", "<i4")])
167+
dtype = np.dtype([("demod", "<i4", (2, 2)), ("phase", "<i4"), ("frequency", "<i4")])
130168

131169
def __init__(self, header, body):
132170
self.header = header
@@ -141,31 +179,29 @@ def to_mu(self):
141179
return np.frombuffer(self.body, self.dtype)
142180

143181

144-
class Frame:
145-
"""Stream frame constisting of a header and multiple data batches"""
182+
Frame.register(Mpll)
146183

147-
# The magic header half-word at the start of each packet.
148-
magic = 0x057B
149-
header_fmt = struct.Struct("<HBBI")
150-
header = namedtuple("Header", "magic format_id batches sequence")
151-
parsers = {
152-
AdcDac.format_id: AdcDac,
153-
ThermostatEem.format_id: ThermostatEem,
154-
Fls.format_id: Fls,
155-
Mpll.format_id: Mpll,
156-
}
157184

158-
@classmethod
159-
def parse(cls, data):
160-
"""Parse known length frame"""
161-
header = cls.header._make(cls.header_fmt.unpack_from(data))
162-
if header.magic != cls.magic:
163-
raise ValueError(f"Bad frame magic: {header.magic:#04x}")
164-
try:
165-
parser = cls.parsers[header.format_id]
166-
except KeyError as exc:
167-
raise ValueError(f"No parser for format: {header.format_id}") from exc
168-
return parser(header, data[cls.header_fmt.size :])
185+
class Fls2:
186+
"""FLS2 application stream format"""
187+
188+
format_id = 5
189+
190+
def __init__(self, header, body):
191+
self.header = header
192+
self.body = body
193+
194+
def size(self):
195+
"""Return the data size of the frame in bytes"""
196+
return len(self.body)
197+
198+
def to_mu(self):
199+
"""Return the raw data in machine units"""
200+
data = np.frombuffer(self.body, "<i4")
201+
return data
202+
203+
204+
Frame.register(Fls2)
169205

170206

171207
class Stream(asyncio.DatagramProtocol):
@@ -199,6 +235,13 @@ async def open(cls, port=9293, addr="0.0.0.0", local="0.0.0.0", maxsize=1):
199235
socket.IP_ADD_MEMBERSHIP,
200236
multiaddr + local,
201237
)
238+
try:
239+
# OS filter
240+
sock.setsockopt(
241+
socket.IPPROTO_IP, getattr(socket, "IP_MULTICAST_ALL", 49), 0
242+
)
243+
except OSError:
244+
pass # Windows
202245
sock.bind((addr, port))
203246
return await loop.create_datagram_endpoint(
204247
lambda: cls(maxsize),

0 commit comments

Comments
 (0)